Contiki學習筆記  第一個程式:Hello World

来源:http://www.cnblogs.com/abatei/archive/2016/01/18/5137392.html
-Advertisement-
Play Games

想來想去,還是得先寫一個程式,找下感覺,增強一下自信心,那就國際慣例Hello World吧。先到這個網址下一個Instant Contiki 2.7。之所以沒用3.0的,是因為有些問題,我源碼是下的3.0的。http://sourceforge.net/projects/contiki/files...


想來想去,還是得先寫一個程式,找下感覺,增強一下自信心,那就國際慣例Hello World吧。
先到這個網址下一個Instant Contiki 2.7。之所以沒用3.0的,是因為有些問題,我源碼是下的3.0的。
http://sourceforge.net/projects/contiki/files/Instant%20Contiki/
下完後裝個VMWear,載入Instant Contiki 2.7虛擬機,就可以在Ubuntu上用contiki了。
打開終端,預設是用user用戶名登錄,密碼也是user。ls一下,看見有contiki目錄就對了。接下來在user根目錄下建一個demo目錄用來存放自己的工程,然後在demo目錄下建一個helloworld目錄,然後進去。
建一個hello-world.c文件,輸入如下代碼:

 1 #include "contiki.h"
 2 #include <stdio.h>
 3 PROCESS(HW, "HWP");
 4 AUTOSTART_PROCESSES(&HW);
 5 PROCESS_THREAD(HW, ev, data)
 6 {
 7     PROCESS_BEGIN();
 8     printf("Hello world!\n"); //此處放自己的代碼
 9     PROCESS_END();
10 }

接下來回到user根目錄,然後進入contiki目錄,敲pwd命令,記下當前路徑,等下要用。重新進入helloworld目錄,新建一個Makefile文件,輸入如下代碼:

CONTIKI_PROJECT = hello-world
all: $(CONTIKI_PROJECT)
/* Contiki源文件根目錄,使用前面記下的路徑 */
CONTIKI = /home/user/contiki
include $(CONTIKI)/Makefile.include

 準備工作完成,敲入命令make,編譯、生成可執行文件。此處相當坑爹,代碼寫錯幾處,編譯不過,要刪除生成的文件再編譯,折磨死我了。先將就著,以後要換個工具寫代碼。生成完後,如圖所示,生成很多文件。

第一個程式:Hello World - 阿巴睇 - 阿巴睇的博客  看到綠色文件沒?執行它,結果如圖所示: 第一個程式:Hello World - 阿巴睇 - 阿巴睇的博客  出現Hello World!後程式不會自動退出,這跟在Linux下寫C程式可是不一樣的。按Ctrl+C退出程式。   好,舉杯慶祝邁出了關鍵一步。下麵的大段分析就以此展開。 hello-world.c的代碼真正屬於自己的代碼只有printf語句,其他都是固定格式。也就是說將來寫程式是在PROCESS_BEGIN();和PROCESS_END();之間寫自己的代碼。main()方法呢?main方法是有,不在這裡,不用我們自己寫,習慣就好。 好,先分析第一句代碼 PROCESS(HW, "HWP"); 先看看PROCESS源碼,就在前一篇process結構體上面:
 1 #if PROCESS_CONF_NO_PROCESS_NAMES
 2 #define PROCESS(name, strname)                \
 3   PROCESS_THREAD(name, ev, data);            \
 4   struct process name = { NULL,                \
 5                           process_thread_##name }
 6 #else
 7 #define PROCESS(name, strname)                \
 8   PROCESS_THREAD(name, ev, data);            \
 9   struct process name = { NULL, strname,        \
10                           process_thread_##name }
和之前一樣,只考慮有名字的情況,代入實參PROCESS變為: PROCESS(HW, "HWP"); 再代入下麵公式,變為:   PROCESS_THREAD(HW, ev, data); \   struct process HW= { NULL, "HWP", process_thread_HW } 接下來看看PROCESS_THREAD的聲明,
 1 /**
 2  * Define the body of a process.
 3  *定義process主體
 4  * This macro is used to define the body (protothread) of a
 5  * process. The process is called whenever an event occurs in the
 6  * system, A process always start with the PROCESS_BEGIN() macro and
 7  * end with the PROCESS_END() macro.
 8  *此巨集用於定義一個process的主體,當某事件發生時,process被調用。process總是從PROCESS_BEGIN()巨集開始,並結束於
 9  *PROCESS_END() 巨集
10  */
11 #define PROCESS_THREAD(name, ev, data)                 \
12 static PT_THREAD(process_thread_##name(struct pt *process_pt,    \
13                        process_event_t ev,    \
14                        process_data_t data))

越來越複雜了,繼續代入吧 PROCESS_THREAD(HW, ev, data); 變為:

static PT_THREAD(process_thread_HW(struct pt *process_pt,    
                       process_event_t ev,    
                       process_data_t data))

還沒完,還得跟蹤PT_THREAD,在Pt.h頭文件中,先看看定義:

#define PT_THREAD(name_args) char name_args

這個……這個上一篇日誌中剛接觸過,用於把一個東西變成函數指針,先代入看看:

static char process_thread_HW(struct pt *process_pt,    process_event_t ev,  process_data_t data)

這回沒變成函數指針,而是一個方法,看來PT_THREAD這個巨集定義專門用來生成函數,它有註釋,看看怎麼說:

* Declaration of a protothread.  *聲明一個線程原型  * This macro is used to declare a protothread. All protothreads must  * be declared with this macro.  *此巨集用於聲明線程原型,所有的線程原型必須通過此巨集聲明  * \param name_args The name and arguments of the C function  *參數name_args:C函數的名稱和參數  * implementing the protothread. 小結:一系列動作下來觀察到,PROCESS巨集實際上就是給定一個name參數,此處可看做函數名稱的一部分,生成一個靜態函數,函數返回值為char,名稱為process_thread_+name,函數裡面有三個參數。結構體pt也是第二次碰到了(參考上一篇日誌),就是一個數字。剩餘兩參數後面用到再回來討論。   還沒完,下麵來分析第二句 struct process HW= { NULL, "HWP", process_thread_HW } 這裡初始化了一個process結構體變數HW,上一篇日誌我們已經分析了process結構,再貼上來看看
struct process {
      struct process *next;
      const char *name;
      char  (* thread)(struct pt *, process_event_t, process_data_t)
      struct pt pt;
      unsigned char state; 
      unsigned char needspoll;
};
結構體HW變數的第一個成員是指向下一元素指針,設為NULL,還未加入鏈表,只是NULL了。 第二個成員表示進程的名稱,這裡為"HWP",這是我們起的名字。 第三個成員,表示一個函數指針,每個process都有一個函數,process執行的就是這個函數,我們看看它的名字:process_thread_HW ,這不正是我們之前通過PT_THREAD展開的函數嘛。 還有三個參數沒賦初值。 好,現在可以做一個總結了: helloworld的第一句代碼PROCESS(HW, "HWP");聲明瞭此進程所對應的函數原型process_thread_HW ,還聲明瞭此進程所對應的process結構體HW。並將函數原型作為HW的成員。 天啊,太複雜了,這才是第一句代碼。   越來越有意思了,有些東西,外面看很抗拒,一旦鑽進去,卻又愛不釋手。C語言一些簡單的語法,能實現C#要非常複雜才能實現的功能。真是不同的世界觀,準確地說,應該是讓在不同的角度去看世界。繼續分析第二句代碼
AUTOSTART_PROCESSES(&HW);

先找到AUTOSTART_PROCESSES定義,在Autostart.h頭文件中

#define AUTOSTART_PROCESSES(...)                    \
struct process * const autostart_processes[] = {__VA_ARGS__, NULL}
先上網查查__VA_ARGS__是什麼: __VA_ARGS__ 是一個可變參數的巨集(gcc支持)。實現思想就是巨集定義中參數列表的最後一個參數為省略號(也就是三個點)。這樣預定義巨集_ _VA_ARGS_ _就可以被用在替換部分中,替換省略號所代表的字元串。 經過替換,語句變成如下形式:
struct process * const autostart_processes[] = {&HW, NULL}
這裡聲明瞭一個指針數組,數組裡的每一個指針都指向了一個process,數組聲明的同時初始化了兩個元素,第一個元素是指向HW的指針。回頭看上一篇日誌,HW正是表示當前process的結構體變數。這個數組用來做什麼呢?後面用到再講解。 下麵繼續講解第三條語句:PROCESS_THREAD(HW, ev, data) PROCESS_THREAD巨集是老朋友了(請閱上一篇日誌),專門用於生成process所要執行的函數原型。代入後的語句如下:
static char process_thread_HW(struct pt *process_pt,    \
                       process_event_t ev,    \
                       process_data_t data)
和上一篇日誌展開後一樣,上一次用於聲明函數原型,這次聲明的是函數主體。    繼續第四條語句:PROCESS_BEGIN(); 這個是關鍵,先找找巨集定義,Process.h頭文件中:
/**
 * Define the beginning of a process.
 *定義process的開始部分
 * This macro defines the beginning of a process, and must always
 * appear in a PROCESS_THREAD() definition. The PROCESS_END() macro
 * must come at the end of the process.
 *此巨集用於定義一個process的開始部分,並只能在PROCESS_THREAD() 函數體中定義。在process結尾處必須緊接著定義
 *PROCESS_END() 巨集。
 */
#define PROCESS_BEGIN()             PT_BEGIN(process_pt)

繼續代入吧,有啥可說的呢,語句變為:

PT_BEGIN(process_pt);

接下來找PT_BEGIN巨集,Pt.h頭文件中,原型如下:

/**
 * Declare the start of a protothread inside the C function
 * implementing the protothread.
 *用於線上程原型函數主體中聲明一個線程的開始部分
 * This macro is used to declare the starting point of a
 * protothread. It should be placed at the start of the function in
 * which the protothread runs. All C statements above the PT_BEGIN()
 * invokation will be executed each time the protothread is scheduled.
 *此巨集放線上程運行的開始部分。線程將會根據執行在PT_BEGIN()中聲明的調用。
 * \param pt A pointer to the protothread control structure.
 * \hideinitializer
 */
#define PT_BEGIN(pt) { char PT_YIELD_FLAG = 1; if (PT_YIELD_FLAG) {;} LC_RESUME((pt)->lc);

代入,語句變為:

{ char PT_YIELD_FLAG = 1; if (PT_YIELD_FLAG) {;} LC_RESUME((process_pt)->lc)

整下容,變為

{ 
    char PT_YIELD_FLAG = 1; 
    if (PT_YIELD_FLAG) 
    {;} 
    LC_RESUME((process_pt)->lc);

繼續追蹤LC_RESUME巨集:

#define LC_RESUME(s) switch(s) { case 0:

代入上式,最終PROCESS_BEGIN();變成:

{ 
    char PT_YIELD_FLAG = 1; 
    if (PT_YIELD_FLAG) 
    {;} 
    switch((process_pt)->lc)
    { 
        case 0:;
這……這……這是什麼?半條語句,if (PT_YIELD_FLAG)  {;} 意義何在?先不管了,將來有機會弄明白再說吧。     最後,PROCESS_END() 找巨集
#define PROCESS_END()               PT_END(process_pt)

再找PT_END

#define PT_END(pt) LC_END((pt)->lc); PT_YIELD_FLAG = 0; \
                   PT_INIT(pt); return PT_ENDED; }

最終,語句變為:

LC_END((process_pt)->lc); PT_YIELD_FLAG = 0; \
                   PT_INIT(process_pt); return PT_ENDED; }
整下容變成:
        LC_END((process_pt)->lc); 
        PT_YIELD_FLAG = 0; \               
        PT_INIT(process_pt); 
        return PT_ENDED; 
}

LC_END定義為:

#define LC_END(s) }

PT_INIT定義為:

#define PT_INIT(pt)   LC_INIT((pt)->lc)

LC_INIT定義為:

#define LC_INIT(s) s = 0;

PT_ENDED定義為:

#define PT_ENDED   3

一層層代入,最終PROCESS_END()變成:

       }
        PT_YIELD_FLAG = 0; \               
        (process_pt)->lc = 0;
        return 3; 
}

凌亂了,整理下思緒,休息一下把Helloworld.c全部展開看看

腦袋有點不夠用了,慢慢展開吧,看看廬山真面目:

 1 static char process_thread_HW(struct pt *process_pt,    process_event_t ev,  process_data_t data)
 2 struct process HW= { NULL, "HWP", process_thread_HW }
 3 struct process * const autostart_processes[] = {&HW, NULL}
 4 static char process_thread_HW(struct pt *process_pt,    process_event_t ev,  process_data_t data)
 5 { 
 6         char PT_YIELD_FLAG = 1; 
 7         if (PT_YIELD_FLAG) 
 8         {;} 
 9         switch((process_pt)->lc)
10         { 
11                 case 0:
12                 printf("Hello world!\n");
13         };
14         PT_YIELD_FLAG = 0; \               
15         (process_pt)->lc = 0;
16         return 3; 
17 }

下麵給代碼加上我自己的理解

 1 //聲明一個函數原型,用於process所執行的方法
 2 static char process_thread_HW(struct pt *process_pt,    process_event_t ev,  process_data_t data)
 3 //聲明代表進程的結構體,並把之前的函數原型做為其參數代入
 4 struct process HW= { NULL, "HWP", process_thread_HW }
 5 //聲明一個process的指針數組,用於存放多個process(此程式只有一個),最後放入NULL只是為了方便查找到數組結尾。這
 6 //里沒有用鏈表,說明不需要刪除process(個人猜測)
 7 struct process * const autostart_processes[] = {&HW, NULL}
 8 //函數主體,對應上面的函數原型
 9 static char process_thread_HW(struct pt *process_pt,    process_event_t ev,  process_data_t data)
10 { 
11         //由於這個程式沒用到事件,此參數無用,所以下麵三句都是廢話
12         char PT_YIELD_FLAG = 1; 
13         if (PT_YIELD_FLAG) 
14         {;} 
15         //process_pt為函數第一個參數,並無賦值,此時值為0
16         switch((process_pt)->lc)
17         { 
18                 case 0:
19                 printf("Hello world!\n"); //由於process_pt的值為0,所以執行此句
20         };
21         PT_YIELD_FLAG = 0; //此處無用           
22         (process_pt)->lc = 0; //此處無用   
23         return 3;  //返回PT_ENDED,從字面意義上理解protothread_ended,指示此process已經game over。
24 }

有點凌亂,但也只能如此理解。這個程式只列印一句話,沒用到事件,所以產生了一些無用語句。只能等下次代入事件,看看會不會有什麼新的理解。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一.概述 互斥量是線程同步的一種機制,用來保護多線程的共用資源。同一時刻,只允許一個線程對臨界區進行訪問。互斥量的工作流程:創建一個互斥量,把這個互斥量的加鎖調用放在臨界區的開始位置,解鎖調用放到臨界區的結束位置。當內核優先把某個線程調度到臨界區的開始...
  • 2016年最新64位win7系統快速版 V2016年1月軟體介紹2016年最新64位win7系統快速版 V2016年1月, 系統具有更安全、更穩定、更人性化等特點。集成最常用的裝機軟體,集成最全面的硬體驅動,精心挑選的系統維護工具,加上純凈版獨有人2016年最新32位win7系統快速版 V2016年...
  • 之前安裝 Microsoft Sql Server 2012 R2 的時候總是報這樣的錯誤:SQL Server Setup has encountered the following error:The SQL Server license agreement cannot be located ...
  • 一、簡介 Typist (gtypist)是一個打字練習軟體,用來提升打字的速度。 二、安裝 1)源碼方式 http://ftp.gnu.org/gnu/gtypist/ 三、使用 http://blog.chinaunix.net/uid-28503021-id-3944296.html 四、其他...
  • 作業要求:輸入用戶名,密碼認證成功顯示歡迎信息輸入錯誤三次後鎖定用戶Readme1.user_id.txt是存放用戶id及密碼的文件2.user_lock.txt是存放被鎖定的用戶id的文檔,預設為空.3.程式會對user_id.txt里的合法用戶id進行判斷,若連續輸入用戶id錯誤達三次程式直接退...
  • 今天,在用icinga伺服器端測試客戶端腳本時,報如下錯誤:[root@mysql-server1 etc]# /usr/local/icinga/libexec/check_nrpe -H 192.168.244.146 -c check_users -a 10 20CHECK_NRPE: Rec...
  • 一.概述 這裡以Linux為例。Linux歷史上,最開始使用的線程是LinuxThreads,但LinuxThreads有些方面受限於內核的特性,從而違背了SUSV3 Pthreads標準。即它要根據內核的特性來實現線程,有些地方沒有遵循統一的標準。後...
  • 1.Nginx的簡單說明 a. Nginx是一個高性能的HTTP和反向代理伺服器,也是一個IMAP/POP3/SMTP伺服器,期初開發的目的就是為了代理電子郵件伺服器室友:Igor Sysoev開發,源代碼符合BSD開源。其特點就是占用記憶體少併發能力強,在天朝使用Nginx的大型網站已經有很多:百....
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...