# 快速上手新唐RTU980 Chili開發板及應用建立 ## 前言 :::spoiler 更新紀錄 |更新日期|內容| |---|---| |2023/06/12|shell script 撰寫,自動開機執行(未完成)| |2024/10/05|準備打掉重練| |2024/10/07|小幅度修改| |2024/10/12-15|新增開發流程簡要說明及工具、ioctrl部分說明,還有大頭貼| |2024/10/18</br>2024/11/3|新增html script相關說明| |2024/11/10|script 說明| |2024/11/26|新增Key Cgi部分內容| ::: 本人於2023年6月初開始撰寫本篇筆記,因為種種因素(學校、工作等等)維持不到10天就放棄了。 2024開始,我重新利用這片開發板進行嵌入式開發的學習,如果有任何錯誤或指教,歡迎各位客官發郵件給我。 目標是希望能利用這塊開發板學習buildroot、應用開發,再結合過往學習的硬體知識,帶領讀者從繪製原理圖開始到layout,完整做出專案或專題。 ## 一、簡介RTU980 Chili開發板 在剛拿到一個產品(或開發板)時,最重要的第一步,就是先認識上面的各種接口、介面或腳位。 如下圖(出自官方[使用者手冊](https://www.nuvoton.com/export/resource-files/en-us--UM_NuMaker_RTU_NUC980_EN_Rev1.21.pdf)) ![image](https://hackmd.io/_uploads/B1y1aYZyke.png =430x400) 新唐官方有提供[使用者手冊](https://www.nuvoton.com/export/resource-files/en-us--UM_NuMaker_RTU_NUC980_EN_Rev1.21.pdf),其中除了開發板的使用說明外,也包含了原理圖以及電路板佈局(PCB Placement)。 而我相信,各位看官應該有足夠的能力依靠官方給的[資料(拜託點這裡看一下有哪些參考資料)](https://www.nuvoton.com/products/iot-solution/iot-platform/numaker-rtu-nuc980/index.html)以及影片教學,成功啟動官方提供的應用。 >這些是官方提供的影片教學 >[新唐 Chili Board – 開發板與平台介紹](https://www.youtube.com/watch?v=tkYXZoYUY68) >[新唐 Chili Board (1) – 下載、編譯與燒錄教學](https://www.youtube.com/watch?v=9W8HGrzjGZ8) >[新唐 Chili Board (2) – 遠端監控與資料存取實例操作](https://www.youtube.com/watch?v=5640d9NxlCs) 在這篇筆記中,我並不打算提太多關於官方示例的內容,但還是稍微說一下。 ## 二、開發流程簡要說明及工具 ### 硬體工具 * 需要準備兩條Micro Usb線,或是你可以像我一樣,把一條USB A直接焊上去(帶出門很方便)。 其實在燒錄完之後,只會頻繁用到其中一個USB port,所以也可以常備一條就好。 ![S__73842715](https://hackmd.io/_uploads/S1QwqW_1kg.jpg =400x400) 這兩個Micro USB孔的作用,一個是作為COM Port,讓你能透過電腦跟開發版溝通,在開發階段能快速進入系統下達指令(Virtual COM);另一個是將系統image燒錄進去NOR Flash用的(USB0 Device)。 * 電腦一台(必須) * 一些杜邦線(非必須) ### 軟體工具 * NuWriter(官方提供):官方提供的燒錄軟體。 * Linux Virtual Machine(官方提供):官方已經建置好的開發環境。 * Putty(自行下載):透過Virtual COM跟開發板溝通的軟體介面。 ### 開發流程簡要說明 ![12200](https://hackmd.io/_uploads/H15upGOJ1l.png =840x250) * 步驟一、利用官方提供的「linux虛擬機」,對「NUC980 Chili」預設的「Buildroot包」進行編譯。 * 步驟二、將編譯出的檔案移到window系統中 * 步驟三、接上兩條Micro USB線,開啟「Putty」連接Virtual COM,查看開發板輸出的信息。 * 步驟四、使用「NuWriter」將編譯完的檔案燒錄進去開發板。 ## 三、針對官方示例二(IO控制)進行解說 在示例"新唐 Chili Board (2) – 遠端監控與資料存取實例操作"中,我們能透過網頁控制開發板的LED,以及接收按鈕信號。 > 實際上的控制畫面 > ![image](https://hackmd.io/_uploads/S1d4aYWkJe.png =400x300) 這邊假設各位已經能找到網頁以及IO控制的原始碼,若您沒有頭緒,請點開下面的詳細資料: :::spoiler *1.透過Buildroot取得示例2的原始碼* 我們可以透過新唐提供的Linux映像檔,建立開發環境,並參考Buildroot(RTU980 Chili預設配置)中的output,找到示例2的原始碼;或是透過開發板上的系統來取得示例2的原始碼,以下將分為兩個部分說明。 我們安裝完新唐提供的Linux映像後,需要將原先的預設配置(應該是NUC970)改為RTU980 Chili,以下是RTU980 Chili的預設配置。 *如果各位需要相關資訊或更詳細的說明請點這裡"[NUC980 Linux Environment on VMware](https://www.nuvoton.com/export/resource-files/zh-tw--UM_NUC980_Linux_environment_on_VMware_EN_Rev1.02.pdf)",可直接參考3.1.2* ![image](https://hackmd.io/_uploads/SJB3GeJkyg.png) 改為使用RTU980 Chili的預設配置,然後進行第一次編譯,如下圖 ![image](https://hackmd.io/_uploads/Skz-Sxykke.png) 然後你需要經過漫長的等待... 你可以花點時間看外國人介紹台灣茶 {%youtube vAPiWVxe-5I%} 你可以自己去泡壺茶 ![image](https://hackmd.io/_uploads/BkQlYxkyJg.png) 甚至可以閒到用小畫家畫一杯茶 ![image](https://hackmd.io/_uploads/Sy7BpxJkyx.png) 等到天荒地老,終於編譯完之後,你就可以在這裡找到: ![image](https://hackmd.io/_uploads/Hk3kqxyJJx.png) ::: :::spoiler 2.透過開發板取得示例2的原始碼 ![image](https://hackmd.io/_uploads/r1B70k11yx.png =560x300) > 如果想要參閱IO控制的網頁原始碼,可以參閱/usr/local/sbin/www/ioctrl.html > ::: 在嵌入式開發來說,[CGI(Common Gateway Interface (附上教學))](https://youtube.com/playlist?list=PLDhd2asKgB6Wo2uxQS3C9PBG_wjNGQLnP&si=CxYe9G0iLkjEHs9U)是一種常用來讓網頁跟硬體/系統交換訊息的後端程式。 在這裡我們針對三個比較重要的檔案進行說明: * ioctrl.html * key_cgi.c * led_cgi.c ### ioctrl.html > HTML(HyperText Markup Language,超文本標記語言)是打造網頁的基石。它表述並定義網頁的內容。伴隨 HTML 而來的技術還有描述網頁外觀(CSS)及功能性的程式語言(JavaScript)。—— [mdn web docs_](https://developer.mozilla.org/zh-TW/docs/Web/HTML) *所以我們可以大致分成三個部份來說明,分別是網頁內容、外觀以及功能性。* 由於本人並不是軟體/網頁相關從業人員,入門的網頁教學還要請各位參考彭彭老師的[JavaScript 網頁前端工程入門教學](https://youtube.com/playlist?list=PL-g0fdC5RMbpqZ0bmvJTgVTS4tS3txRVp&si=jKFbxWCJGA0cVKCP)(超有料超簡單)。 這裡先附上完整原始碼。 :::spoiler *展開完整原始碼* ```html= <html> <head> <title>Control IO by Web</title> <script type="text/javascript" src="jquery-3.5.1.js"></script> <style type="text/css"> div#led_content{ position: fixed; left:510px; top:20px; } div#key_content{ position: fixed; left:20px; top:20px; } input[type="submit"] { position: fixed; left:750px; top:100px; } img { position: fixed; left: 40px; top: 95px; } </style> </head> <body> <div id="led_content"> <form id="led_form" action="cgi-bin/led.cgi" method="GET"> <table width="300" border="1"> <tr> <td>LED1</td> <td>LED2</td> </tr> <tr> <td> <input type="radio" id="LED1" name="LED1" value="1">ON <br> <input type="radio" id="LED1" name="LED1" value="0" checked>OFF </td> <td> <input type="radio" id="LED2" name="LED2" value="1">ON <br> <input type="radio" id="LED2" name="LED2" value="0" checked>OFF </td> </tr> </table> <br> <input type="submit" id="led_submit" value="Submit" /> </form> </div> <div id="ledimg_content"> <img id="led_img" src="LED_1_off_2_off.jpg"> </div> <div id="key_content"> <form id="key_form" action="cgi-bin/key.cgi" method="GET"> <table width="300" border="1"> <tr> <td>KEY1</td> </tr> <tr> <td> <input type="radio" id="KEY1" name="KEY1" value="1">ON <br> <input type="radio" id="KEY1" name="KEY1" value="0" checked>OFF </td> </tr> </table> <br> </form> </div> <script type="text/javascript"> $(document).ready(function() { window.setInterval(function() { key_autoload(); }, 300); }); //END $(document).ready() var frm = $('#led_form'); frm.submit(function(e){ e.preventDefault(); $.ajax({ type: frm.attr('method'), url: frm.attr('action'), data: frm.serialize(), success: function(data){ $('#ledimg_content').html(data); } }); }); var key_frm = $('#key_form'); function key_autoload(){ $.ajax({ type: key_frm.attr('method'), url: key_frm.attr('action'), success: function(data){ $('#key_content').html(data); } }); } </script> </body> </html> ``` ::: #### 網頁內容 ![image](https://hackmd.io/_uploads/H1IxoXuJJx.png =500x400) > 這裡會稍微粗淺的提到一些html的語法,有錯再麻煩各位網頁大老糾正我。 舉幾個例子給各位: :::spoiler *"按鈕狀態表格"(紅框)* 在網頁元素中,**div是一個容器**,利用div能夠更好的**排版**,或是使用**CSS**做外觀的管理。在這個div裡面,主要包含了一個**表格(table)**。 可以看到第4-6行,**tr**、**td**是表格(table) 的**行(row)跟列(colume)**,這行的意義是在表格的某一個td中**顯示"KEY1"**。 以下面原始碼的結構來看,這是在第一個tr的第一個td(第一行第一列)顯示"KEY1",也就是紅框中的橘框表現出來的效果。 ```html= <div id="key_content"> <form id="key_form" action="cgi-bin/key.cgi" method="GET"> <table width="300" border="1"> <tr> <td>KEY1</td> </tr> <tr> <td> <input type="radio" id="KEY1" name="KEY1" value="1">ON <br> <input type="radio" id="KEY1" name="KEY1" value="0" checked>OFF </td> </tr> </table> <br> </form> </div> ``` ::: :::spoiler *"送出LED設定按鈕"(紫框)* 以下的原始碼表現的是右邊的"LED狀態設定表格"跟"送出按鈕"的區塊,他們被包在同一個div中。 可以看到,這裡的3-18行又是一個表格,但在4-7行的**第一個tr**中,包含了**兩個td**(LDE1、LDE2),能對應到圖片中的**第一行第一列**跟**第二行第一列**。 第20行是一個**input標籤**,**type是'submit'**,類似於'button'的功用;**'value'則是用來調整按鈕中的文字**。也就是紫框表現出來的效果。 [mdn web docs 參考資料](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/input) ![image](https://hackmd.io/_uploads/BkIoVc5yyl.png =500x140) ```html= <div id="led_content"> <form id="led_form" action="cgi-bin/led.cgi" method="GET"> <table width="300" border="1"> <tr> <td>LED1</td> <td>LED2</td> </tr> <tr> <td> <input type="radio" id="LED1" name="LED1" value="1">ON <br> <input type="radio" id="LED1" name="LED1" value="0" checked>OFF </td> <td> <input type="radio" id="LED2" name="LED2" value="1">ON <br> <input type="radio" id="LED2" name="LED2" value="0" checked>OFF </td> </tr> </table> <br> <input type="submit" id="led_submit" value="Submit" /> </form> </div> ``` ::: :::spoiler *"開發版圖片"(綠框)* 可以看到,這邊的做法一樣是使用div作區塊的控制。 img 標籤,顧名思義就是image,將圖片] ```html= <div id="ledimg_content"> <img id="led_img" src="LED_1_off_2_off.jpg"> </div> ``` ::: #### 外觀 外觀常使用css來進行設計,我並不是網頁方面的專家或從業人員,考量到我對於css的說明或理解可能會稍微爛一些,各位客官若是想要相關的基礎知識教學,可以參考這裡[網頁前端工程入門:基礎 CSS 教學 By 彭彭](https://youtu.be/Jr7lwHnTK68?si=Q9CJCsklh-Qj1KPi),本人就不針對外觀部分進行過多闡述。 在這個網頁範例中,針對各個不同的div(不同id)、input、img,都有對應的css進行外觀設定。 ```html= <style type="text/css"> div#led_content{ position: fixed; left:510px; top:20px; } div#key_content{ position: fixed; left:20px; top:20px; } input[type="submit"] { position: fixed; left:750px; top:100px; } img { position: fixed; left: 40px; top: 95px; } </style> ``` #### 功能性 終於來到最重要的部分,也就是實際上實現「讓網頁控制硬體電路」的部分。 我們先來開啟上帝視角,看看這件事情是怎麼實現的吧! ![image](https://hackmd.io/_uploads/H1c4ly1gyl.png =600x600) 透過網頁與開發板進行互動,實際上是由網頁中的「腳本(script)」來進行與後端(也就是開發板上實際控制電路行為的程式)進行互動,進而取得按鈕輸入狀態,以及控制LED燈。 **為了方便解釋,以及讓程式碼有highlight,這邊把html跟javascript的部分分開。(至於為何不擺在一起,大家可以參照一下markdown的部分。)** **以下正式開始介紹。** 可以看到以下部分是html head,在第6行使用script標籤,使用了jquery-3.5.1.js這個Javascript Library。 > *(jquery-3.5.1.js在開發板的root file system中與 ioctrl.html在同個資料夾下,需要更新的話直接在[官網下載](https://jquery.com/)最新板替換即可)。* 關於jquery是什麼,以及為何需要導入jquery,我在網路上有找到一篇淺顯易懂的文章,供各位參考: [jQuery是什麼,它跟JavaScript有什麼關係?它又有什麼能耐呢?——progressbar](https://progressbar.tw/posts/6) 如果你懶得點進去看,我在這邊說一下結論: jQuery是一個很方便好用的函式庫,簡單來說就是能有效簡化代碼,省事用的好工具,目前是主流。 15行是script標籤,代表javasrcipt腳本從此開始。 被 *開始標籤(script)* 與 *結束標籤(/script)* 所包含的是用javascript編寫而成的腳本程式。 ```html= <html> <head> ... ... ...省略 <script type="text/javascript" src="jquery-3.5.1.js"></script> ... ... ...省略 </head> <body> ... ... ...省略 <script type="text/javascript"> ``` 以下開始介紹script標籤中包含的部分。 1~5行使用了jQuery的 [$(document).ready](https://learn.jquery.com/using-jquery-core/document-ready/) 方法,保證在網頁加載完成後,才執行後面的function(){window.setInterval()}。 [window.setInterval()](https://developer.mozilla.org/zh-TW/docs/Web/API/Window/setInterval) 方法能夠重複執行任務,在這裡的設定是每300毫秒呼叫一次 key_autoload()。 第7行是使用jQuery選擇器取得前面的表單 "led_form" (如下),並令他為一個名為frm的變數。 第8~18行,frm.[submit()](https://www.w3schools.com/jquery/event_submit.asp)則是監聽該表單提交時(使用者按下前面介紹的紫框中的submit按鈕)的行為。 8~18的內容我放在下面的補充資料,以免排版過長。 :::spoiler 補充資料 *我還是要聲明一下,我不是網頁相關從業人員,所以只能仰賴網路資料跟生成式AI提供的資料。* 第9行,preventDefault是為了避免提交時重新加載頁面(因為提交表單的按鈕有可能預設的行為包含重整頁面)。 緊接著在10~17行發出AJAX(Asynchronous JavaScript and XML,非同步處理,在不重新加載網頁的情況下與伺服器交流,我認為是因為這樣才會在前面加上preventDefault),裡面的type、url使用attr取得led_form裡面的method跟action,向後端發送GET請求。 並且將表單的資料,轉為序列,送向後端,這可能是因為後端負責處裡的程式(剛剛action裡面的led.cgi)是以序列方式接收資料的。 ![image](https://hackmd.io/_uploads/BJYj4YyzJx.png) 14~16行,當傳輸成功時(success),將從伺服器取得led亮滅狀態(data),動態更改ledimg_content(更換圖片)。 ![image](https://hackmd.io/_uploads/BJUfmskMkl.png) ::: ![image](https://hackmd.io/_uploads/SkfKS6N-Je.png =360x150) 而20~29行的原理也是大同小異,基本上交互比對前面html表單元素與這邊的script就可以大致知道這裡的工作原理。 ```javascript= $(document).ready(function() { window.setInterval(function() { key_autoload(); }, 300); }); //END $(document).ready() var frm = $('#led_form'); frm.submit(function(e){ e.preventDefault(); $.ajax({ type: frm.attr('method'), url: frm.attr('action'), data: frm.serialize(), success: function(data){ $('#ledimg_content').html(data); } }); }); var key_frm = $('#key_form'); function key_autoload(){ $.ajax({ type: key_frm.attr('method'), url: key_frm.attr('action'), success: function(data){ $('#key_content').html(data); } }); } ``` 以下是script的結束標籤,代表javascript腳本到此為止。 ```html= </script> </body> </html> ``` 到這邊我們只剩下cgi的部分了。 ### key_cgi.c :::spoiler *展開完整原始碼* ```c= /* KEY CGI Program * * * * Note : * PORT NAME[PIN] = GPIO [id] * PORTA[ 0] = gpio[ 0x00] * PORTA[ 1] = gpio[ 0x01] * : * PORTA[31] = gpio[ 0x1F] * PORTB[ 0] = gpio[ 0x20] * : * PORTB[31] = gpio[ 0x3F] * : * : * : * PORTI[ 0] = gpio[ 0xC0] * : * : * PORTI[31] = gpio[ 0xDF] */ #include <string.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #if 0 //For NK-980IOT board #define LED1_NUM 0x28 //PB8--->LED3 #define LED2_NUM 0x2D //PB13--->LED5 #endif //For chili board #define LED1_NUM 0x4B //PC11--->LED1 #define LED2_NUM 0x43 //PC3--->LED2 #if 0 //For NuDesign-EtherU and NuDesign-EtherD #define LED1_NUM 0x6C //PD12 #define LED2_NUM 0x6D //PD13 #define LED3_NUM 0x6E //PD14 #endif int main(void) { FILE *fp; char str[256]; char *data; long led1,led2; data = getenv("QUERY_STRING"); if(data == NULL) printf("<P>Error! Error in passing data from form to script."); led1 = 0; led2 = 0; sscanf(data,"LED1=%ld&LED2=%ld",&led1,&led2); sprintf(str,"/sys/class/gpio/gpio%d/direction",LED1_NUM); if ((fp = fopen(str, "w")) == NULL) { //linux equivalent code "echo pin_num > export" to export the pin if ((fp = fopen("/sys/class/gpio/export", "w")) == NULL) { printf("Cannot open export file.\n"); exit(1); } fprintf(fp, "%d", LED1_NUM); fclose(fp); //linux equivalent code "echo out > direction" to set the pin as an output sprintf(str,"/sys/class/gpio/gpio%d/direction",LED1_NUM); if ((fp = fopen(str, "w")) != NULL) { fprintf(fp, "out"); fclose(fp); } } else{ fclose(fp); } sprintf(str,"/sys/class/gpio/gpio%d/direction",LED2_NUM); if ((fp = fopen(str, "w")) == NULL) { //linux equivalent code "echo pin_num > export" to export the pin if ((fp = fopen("/sys/class/gpio/export", "w")) == NULL) { printf("Cannot open export file.\n"); exit(1); } fprintf(fp, "%d", LED2_NUM); fclose(fp); //linux equivalent code "echo out > direction" to set the pin as an output sprintf(str,"/sys/class/gpio/gpio%d/direction",LED2_NUM); if ((fp = fopen(str, "w")) != NULL) { fprintf(fp, "out"); fclose(fp); } } else{ fclose(fp); } //linux equivalent code "echo "0" > value" to set the pin high/low sprintf(str,"/sys/class/gpio/gpio%d/value",LED1_NUM); if ((fp = fopen(str, "w")) != NULL) { if(led1) fprintf(fp, "%d", 0); else fprintf(fp, "%d", 1); fclose(fp); } //linux equivalent code "echo "0" > value" to set the pin high/low sprintf(str,"/sys/class/gpio/gpio%d/value",LED2_NUM); if ((fp = fopen(str, "w")) != NULL) { if(led2) fprintf(fp, "%d", 0); else fprintf(fp, "%d", 1); fclose(fp); } printf("<div id=\"ledimg_content\">\n"); if((led1 == 0) && (led2 == 0)){ printf("<img id=\"led_img\" src=\"LED_1_off_2_off.jpg\">\n"); } else if((led1 == 1) && (led2 == 0)){ printf("<img id=\"led_img\" src=\"LED_1_on_2_off.jpg\">\n"); } else if((led1 == 0) && (led2 == 1)){ printf("<img id=\"led_img\" src=\"LED_1_off_2_on.jpg\">\n"); } else if((led1 == 1) && (led2 == 1)){ printf("<img id=\"led_img\" src=\"LED_1_on_2_on.jpg\">\n"); } printf("</drv>\n"); return 0; } ``` ::: 這邊終於開始講C了! 最一開始是程式的功能說明及腳位地址轉換方式,通常C語言程式的開頭都會是程式的功能說明、授權相關文件等等。 關於C語言程式開頭、函數註解相對來說比較規矩的撰寫方式(或是說"生成方式"),請參考[Doxygen官方的Getting started](https://www.doxygen.nl/manual/starting.html)或是[@ichunlai撰寫的Doxygen入門](https://hackmd.io/@ichunlai/H1R9YD0B4),Doxygen是一個能夠生成C專案說明文件的工具。 以這段註解(說明)來說,第1行「KEY CGI Program」,表明此程式就是負責處理按鈕狀態的程式。 ```c= /* KEY CGI Program * Note : * PORT NAME[PIN] = GPIO [id] * PORTA[ 0] = gpio[ 0x00] * PORTA[ 1] = gpio[ 0x01] * : * PORTA[31] = gpio[ 0x1F] * PORTB[ 0] = gpio[ 0x20] * : * PORTB[31] = gpio[ 0x3F] * : * : * : * PORTI[ 0] = gpio[ 0xC0] * : * : * PORTI[31] = gpio[ 0xDF] */ ``` ```c= #include <string.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #if 0 //For NK-980IOT board #define LED1_NUM 0x28 //PB8--->LED3 #define LED2_NUM 0x2D //PB13--->LED5 #endif //For chili board #define LED1_NUM 0x4B //PC11--->LED1 #define LED2_NUM 0x43 //PC3--->LED2 #if 0 //For NuDesign-EtherU and NuDesign-EtherD #define LED1_NUM 0x6C //PD12 #define LED2_NUM 0x6D //PD13 #define LED3_NUM 0x6E //PD14 #endif ``` ### led_cgi.c :::spoiler *展開完整原始碼* ```c= /* LED CGI Program * * * Note : * PORT NAME[PIN] = GPIO [id] * PORTA[ 0] = gpio[ 0x00] * PORTA[ 1] = gpio[ 0x01] * : * PORTA[31] = gpio[ 0x1F] * PORTB[ 0] = gpio[ 0x20] * : * PORTB[31] = gpio[ 0x3F] * : * : * : * PORTI[ 0] = gpio[ 0xC0] * : * : * PORTI[31] = gpio[ 0xDF] */ #include <string.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> //For NK-980IOT board //#define KEY1_NUM 0x8A //PE10 //#define KEY2_NUM 0x8C //PE12 //For chili board #define KEY1_NUM 0x4F //PC15 int main(void) { FILE *fp; char str[256]; char buffer[10]; int key1; key1 = 0; sprintf(str,"/sys/class/gpio/gpio%d/direction",KEY1_NUM); if ((fp = fopen(str, "w")) == NULL) { //linux equivalent code "echo pin_num > export" to export the pin if ((fp = fopen("/sys/class/gpio/export", "w")) == NULL) { printf("Cannot open export file.\n"); exit(1); } fprintf(fp, "%d", KEY1_NUM); fclose(fp); //linux equivalent code "echo out > direction" to set the pin as an output sprintf(str,"/sys/class/gpio/gpio%d/direction",KEY1_NUM); if ((fp = fopen(str, "w")) != NULL) { fprintf(fp, "in"); fclose(fp); } } else{ fclose(fp); } sprintf(str,"/sys/class/gpio/gpio%d/value",KEY1_NUM); if ((fp = fopen(str, "rb")) != NULL) { key1 = 0; fread(buffer, sizeof(char), sizeof(buffer) - 1, fp); key1 = atoi(buffer); fclose(fp); } printf("<div id=\"key_content\">\n"); printf("<form id=\"key_form\" action=\"cgi-bin/key.cgi\" method=\"GET\">\n"); printf("<table width=\"300\" border=\"1\">\n"); printf("<tr>\n"); printf("<td>KEY1</td>\n"); printf("</tr>\n"); printf("<tr>\n"); printf("<td>\n"); if(!key1){ printf("<input type=\"radio\" id=\"KEY1\" name=\"KEY1\" value=\"1\" checked>ON <br>\n"); printf("<input type=\"radio\" id=\"KEY1\" name=\"KEY1\" value=\"0\">OFF\n"); } else{ printf("<input type=\"radio\" id=\"KEY1\" name=\"KEY1\" value=\"1\">ON <br>\n"); printf("<input type=\"radio\" id=\"KEY1\" name=\"KEY1\" value=\"0\" checked>OFF\n"); } printf("</td>\n"); printf("</tr>\n"); printf("</table>\n"); printf("<br>\n"); printf("</form>\n"); printf("</div>"); return 0; } ``` ::: <!-- 燒錄新唐開發板預設的uboot、uboot啟動參數(env.txt)、Image後,連接上網路線,並將電腦設置成與開發板(192.168.1.100)同網段,已經可以透過網頁成功點亮開發板LED並控制及接收按鈕狀態了。 但若是希望能開啟DHCP功能,就必須在console下達指令udhcpc,該如何透過shell script來執行開機自動化的dhcp呢? # 透過腳本進行開機自動DHCP ## 腳本的撰寫 我在/mnt/mtdblock1中建立了bin資料夾用於存放後續的應用。 >`> mkdir bin` 先建立腳本 autodhcp.sh 用來執行udhcpc,詳細如下 ![](https://hackmd.io/_uploads/rJZ5rFjI3.png) ```shell=+ #!/bin/sh # Auto DHCP Client booting # 2023/6/5 by maru /sbin/udhcpc echo -e "Auto DHCP Running" exit 0 ``` 當我們執行這個腳本時,會實現啟動udhcpc,並print "Auto DHCP Running" ![](https://hackmd.io/_uploads/BkAgvtjIn.png) 可以看到,已經確實的取得ip位址了。 不相信可以再檢驗看看: ![](https://hackmd.io/_uploads/r1tSwYiIn.png) ## 開關機自動化執行(未完成) 現在要讓他能夠開機自動執行,必須在/etc/init.d另外寫個腳本,在開機時自動呼叫我們的autodhcp.sh,或許你會覺得這樣很多此一舉,但這樣預留了許多自動化的擴充性。 甚至我們可以將該腳本視為我們自定義的開關機流程腳本,獨立於系統的其他自動化腳本,我可以呼叫不只autodhcp.sh,甚至我之後可以自定義開機詢問帳號密碼、自動執行NTP之類的。 目前看來要在編譯前更改board/nuvoton/rootfs-chili/etc/init.d/rcS 結論是只有mnt/mtdblock1是可讀寫的空間,其他都是開機load進ram的。所以要在編譯階段就改rcS,開機自動執行mtdblock1中的某些腳本。 ![image](https://hackmd.io/_uploads/S19L6Bezxl.png) ![](https://hackmd.io/_uploads/rJB_JqoI3.png) ```shell=+ #!/bin/sh # Enviroment Setup # 1.autodhcp # 2023/6/5 by maru umask 070 start(){ echo -e "Enviroment Setup Starting" /mnt/mtdblock1/bin/autodhcp.sh echo -e "Enviroment Setup Done" } stop(){ } restart(){ stop start } case "$1" in start) start ;; stop) stop ;; restart|reload) restart ;; *) echo "Usage: $0 {start|stop|restart}" exit 1 esac exit $? ``` ## 硬體結構樹 ![](https://hackmd.io/_uploads/SJwwieEvh.png) 這裡是以nuc980-dev-v1.0.dts為主 ``` /* * Device Tree Source for NUC980 DEV board * * Copyright (C) 2018 Nuvoton Technology Corp. * * The code contained herein is licensed under the GNU General Public * License. You may obtain a copy of the GNU General Public License * Version 2 or later at the following locations: * * http://www.opensource.org/licenses/gpl-license.html * http://www.gnu.org/copyleft/gpl.html */ /dts-v1/; /include/ "nuc980.dtsi" / { model = "Nuvoton NUC980 DEV V1.0"; compatible = "nuvoton,nuc980-dev-v1.0", "nuvoton,nuc980"; chosen { bootargs = "root=/dev/ram0 console=ttyS0,115200n8 rdinit=/sbin/init mem=64M lpj=744448"; }; apb { uart1: serial@b0071000 { status = "disabled"; }; uart2: serial@b0072000 { status = "disabled"; }; uart3: serial@b0073000 { status = "disabled"; }; uart4: serial@b0074000 { status = "disabled"; }; uart5: serial@b0075000 { status = "disabled"; }; uart6: serial@b0076000 { status = "disabled"; }; uart7: serial@b0077000 { status = "disabled"; }; uart8: serial@b0078000 { status = "disabled"; }; uart9: serial@b0079000 { status = "disabled"; }; can0: can@b00a0000 { status = "disabled"; }; can1: can@b00a1000 { status = "disabled"; }; rtc: rtc@b0041000 { status = "disabled"; }; nadc: nadc@b0043000 { status = "disabled"; }; pwm0: pwm0@b0058000 { status = "disabled"; }; pwm1: pwm1@b0059000 { status = "disabled"; }; qspi0: qspi0@b0060000 { status = "disabled"; #address-cells = <1>; #size-cells = <0>; num_cs = <2>; lsb = <0>; txneg = <1>; rxneg = <0>; clkpol = <0>; divider = <4>; sleep = <0>; txbitlen = <8>; bus_num = <0>; flash: m25p80@0 { compatible = "w25q128"; #address-cells = <1>; #size-cells = <1>; reg = <0>; spi-max-frequency = <30000000>; partition@0 { label = "kernel"; reg = <0x00000000 0x0800000>; }; partition@1 { label = "rootfs"; reg = <0x0800000 0x0800000>; }; }; }; spi0: spi0@b0061000 { status = "disabled"; #address-cells = <1>; #size-cells = <0>; num_cs = <2>; lsb = <0>; txneg = <1>; rxneg = <0>; clkpol = <0>; divider = <4>; sleep = <0>; txnum = <0>; txbitlen = <8>; bus_num = <1>; spidev@0x01 { compatible = "spidev"; spi-max-frequency = <30000000>; reg = <0>; }; }; spi1: spi1@b0062000 { status = "disabled"; #address-cells = <1>; #size-cells = <0>; num_cs = <2>; lsb = <0>; txneg = <1>; rxneg = <0>; clkpol = <0>; divider = <4>; sleep = <0>; txnum = <0>; txbitlen = <8>; bus_num = <2>; spidev@0x01 { compatible = "spidev"; spi-max-frequency = <30000000>; reg = <0>; }; }; etimer0: etimer0@b0050000 { status = "disabled"; }; etimer1: etimer1@b0050100 { status = "disabled"; }; etimer2: etimer2@b0051000 { status = "disabled"; }; etimer3: etimer3@b0051100 { status = "disabled"; }; i2c0: i2c0@b0080000 { status = "disabled"; nau8822: nau8822@1a { compatible = "nuvoton,nau8822"; reg = <0x1a> ; }; }; i2c1: i2c1@b0081000 { status = "disabled"; pinctrl-0 = <&pinctrl_i2c1_PB>; cap1_nt99141@2a { compatible = "nuvoton,cap1-nt99141"; reg = <0x2a>; }; }; i2c2: i2c2@b0082000 { status = "disabled"; pinctrl-0 = <&pinctrl_i2c2_PB>; cap0_nt99141@2a { compatible = "nuvoton,cap0-nt99141"; reg = <0x2a>; }; }; }; ahb { usbh_ehci@b0015000 { status = "okay"; }; usbh_ohci@b0017000{ status = "okay"; }; usbdev@b0016000 { status = "disabled"; }; fmi@b0019000 { compatible = "nuvoton,nuc980-fmi", "nand"; pinctrl-names = "default"; pinctrl-0 = <&pinctrl_nand>; status = "disabled"; #address-cells = <1>; #size-cells = <1>; partition@0x0 { label = "u-boot"; reg = <0x00000000 0x0200000>; }; partition@0x200000 { label = "Kernel"; reg = <0x0200000 0x1400000>; }; partition@0x1600000 { label = "user"; reg = <0x1600000 0x6480000>; }; }; sdh@b0018000 { status = "disabled"; }; emac0@b0012000 { status = "okay"; }; emac1@b0022000 { status = "disabled"; }; cap0@b0024000 { model = "nt99141"; status = "disabled"; }; cap1@b0014000 { model = "nt99141"; status = "disabled"; }; dma@b0008000 { status = "okay"; }; i2s: i2s@b0020000 { status = "disabled"; }; i2s_pcm: i2s_pcm { status = "disabled"; }; sound { compatible = "nuvoton,nuc980-audio"; i2s-controller = <&i2s>; i2s-platform = <&i2s_pcm>; status = "disabled"; }; ebi: ebi@b0010000 { status = "disabled"; }; }; }; ``` :::info ``` chosen { bootargs = "root=/dev/ram0 console=ttyS0,115200n8 rdinit=/sbin/init mem=64M lpj=744448"; }; ``` **bootargs="root=/dev/ram0** ::: -->