Contiki 实例hello_world详细剖析

转载 2015年07月07日 21:27:59
摘要:
    本文剖析Contiki最简单的实例hello_world,深入源码分析,详解了本实例用到的各个宏,进而给出一份完整展开的代码。最后把本实例用到的宏总结成API,并给出了创建一个进程的模型。

一、Hello World概览
hello_world.c用于向串口打印"Hello World",源代码如下,
  1. //filename:hello_world.c

  2. #include "contiki.h" 
  3. #include "debug-uart.h" /* For usart_puts()*/ 
  4. #include <stdio.h> /* For printf() */ 

  5. PROCESS(hello_world_process, "Hello world"); /*参照二*/ 
  6. AUTOSTART_PROCESSES(&hello_world_process); /*参照三*/ 

  7. /*Define the process code*/ 
  8. PROCESS_THREAD(hello_world_process, ev, data) /*参见四*/ 

  9.     PROCESS_BEGIN(); /*参照5.1*/

  10.     usart_puts("Hello, world!\n"); /*向串口打印字符串"Hello,world"*/ 

  11.     PROCESS_END(); /*参考5.2*/
  12. }
复制代码
接下来逐句分析。
二、PROCESS宏
PROCESS宏完成两个功能:
(1) 声明一个函数,该函数是进程的执行体,即进程的thread函数指针所指的函数
(2) 定义一个进程
源码展开如下:
  1. //PROCESS(hello_world_process, "Hello world");
  2. #define PROCESS(name, strname) PROCESS_THREAD(name, ev, data); \
  3. struct process name = { NULL, strname, process_thread_##name }
复制代码
对应参数展开为:
  1. #define PROCESS((hello_world_process, "Hello world")

  2. PROCESS_THREAD(hello_world_process, ev, data); \   /*分析见2.1*/

  3. struct process hello_world_process = { NULL, "Hello world", process_thread_hello_world_process }; /*分析见2.2*/
复制代码

2.1 PROCESS_THREAD宏
PROCESS_THREAD宏用于定义进程的执行主体,宏展开如下:
  1. #define PROCESS_THREAD(name, ev, data) \
  2. static PT_THREAD(process_thread_##name(struct pt *process_pt, process_event_t ev, process_data_t data))
复制代码
对应参数展开为:
  1. //PROCESS_THREAD(hello_world_process, ev, data);
  2. static PT_THREAD(process_thread_hello_world_process(struct pt *process_pt, process_event_t ev, process_data_t data));  /*分析见2.1.1*/
复制代码

2.1.1 PT_THREAD宏
PT_THREAD宏用于声明一个protothread,即进程的执行主体,宏展开如下:
  1. #define PT_THREAD(name_args) char name_args
复制代码
展开之后即为:
  1. //static PT_THREAD(process_thread_hello_world_process(struct pt *process_pt, process_event_t ev, process_data_t data));
  2. static char process_thread_hello_world_process(struct pt *process_pt, process_event_t ev, process_data_t data);
复制代码
    这下就很清楚了,声明一个静态的函数process_thread_hello_world_process,返回值是char类型。
另,struct pt *process_pt可以直接理解成lc,用于保存当前被中断的地方(保存程序断点),以便下次恢复执行。
2.2 定义一个进程
PROCESS宏展开的第二句,定义一个进程hello_world_process,源码如下:
  1. struct process hello_world_process = { NULL, "Hello world", process_thread_hello_world_process };
复制代码
结构体process定义如下:
  1. struct process  
  2. {
  3.     struct process *next;
  4.     const char *name;   /*此处略作简化,源代码包含了预编译#if。即可以通过配置,使得进程名称可有可无*/
  5.     PT_THREAD((* thread)(struct pt *, process_event_t, process_data_t));
  6.     struct pt pt;
  7.     unsigned char state, needspoll;
  8. };
复制代码
    可见进程hello_world_process 的lc、state、needspoll都默认置为0。关于process结构体请参见
Contiki 6lowpan开发教程资料之四:主要数据结构之进程
》 
三、AUTOSTART_PROCESSES宏
    AUTOSTART_PROCESSES宏实际上是定义一个指针数组,存放Contiki系统运行时需自动启动的进程,宏展开如下:
  1. //AUTOSTART_PROCESSES(&hello_world_process);
  2. #define AUTOSTART_PROCESSES(...) \ struct process * const autostart_processes[] = {__VA_ARGS__, NULL};
复制代码
    这里用到C99 支持可变参数宏的特性,如:#define debug(…) printf(__VA_ARGS__) ,缺省号代表一个可以变化的参数表,宏展开时,实际的参数就传递给 printf()了。例:debug("Y = %d\n", y); 被替换成printf("Y = %d\n", y); 。那么,AUTOSTART_PROCESSES(&hello_world_process); 实际上被替换成:
  1. struct process * const autostart_processes[] = {&hello_world_process, NULL};
复制代码
    这样就知道如何让多个进程自启动了,直接在宏AUTOSTART_PROCESSES()加入需自启动的进程地址,比如让hello_process和world_process这两个进程自启动,如下:
  1. AUTOSTART_PROCESSES(&hello_process,&world_process);
复制代码
    最后一个进程指针设成NULL,则是一种编程技巧,设置一个哨兵(提高算法效率的一个手段),以提高遍历整个数组的效率。
四、PROCESS_THREAD宏
    PROCESS(hello_world_process, "Hello world"); 展开成两句,其中有一句是也是PROCESS_THREAD(hello_world_process, ev, data) ;。这里要注意到分号,是一个函数声明。而这PROCESS_THREAD(hello_world_process, ev, data)没有分号,而是紧跟着"{}",是上述声明函数的实现。关于PROCESS_THREAD宏的分析,最后展开如下,展开过程参见上述2.1。
  1. static char process_thread_hello_world_process(struct pt *process_pt, process_event_t ev, process_data_t data);
复制代码
温馨提示:在阅读Contiki源码,手动展开宏时,要特别注意分号。

五、PROCESS_BEGIN宏和PROCESS_END宏
    原则上,所有代码都得放在PROCESS_BEGIN宏和PROCESS_END宏之间(如果程序全部使用静态局部变量,这样做总是对的。倘若使用局部变量,情况就比较复杂了,当然,不建议这样做),看完下面宏展开,就知道为什么了。
5.1 PROCESS_BEGIN宏
PROCESS_BEGIN宏一步步展开如下:
  1. #define PROCESS_BEGIN() PT_BEGIN(process_pt)
复制代码
    process_pt是struct pt*类型,在函数头传递过来的参数(见四),直接理解成lc,用于保存当前被中断的地方,以便下次恢复执行。继续展开:
  1. #define PT_BEGIN(pt) { char PT_YIELD_FLAG = 1; LC_RESUME((pt)->lc)
  2. #define LC_RESUME(s) switch(s) { case 0:
复制代码
把参数替换,结果如下:
  1. {
  2.     char PT_YIELD_FLAG = 1; /*将PT_YIELD_FLAG置1,类似于关中断???*/
  3.     switch(process_pt->lc) /*程序根据lc的值进行跳转,lc用于保存程序断点*/
  4.     {
  5.         case 0: /*第一次执行从这里开始,可以放一些初始化的东东*/
  6.             ;
复制代码
    很奇怪是吧,PROCESS_BEGIN宏展开都不是完整的语句,别急,看完下面的PROCESS_END就知道Contiki这些天才们是怎么设计的。
5.2 PROCESS_END宏
PROCESS_END宏一步步展开如下:
  1. #define PROCESS_END() PT_END(process_pt)
  2. #define PT_END(pt) LC_END((pt)->lc); PT_YIELD_FLAG = 0; \ PT_INIT(pt); return PT_ENDED; }
  3. #define LC_END(s) }
  4. #define PT_INIT(pt) LC_INIT((pt)->lc)
  5. #define LC_INIT(s) s = 0;
  6. #define PT_ENDED 3
复制代码
整理下,实际上如下代码:
  1.     }
  2.     PT_YIELD_FLAG = 0;
  3.     (process_pt)->pt = 0;
  4.    
  5.     return 3;
  6. }
复制代码
好了,现在回过头来看,PROCESS_BEGIN宏和PROCESS_END宏是如此的般配,天生一对呀,祝福他们:-)
六、总结
6.1 宏全部展开
根据上述的分析,该实例全部展开的代码如下,再浏览下,是不是有种神清气爽的感觉:-)
  1. #include "contiki.h" 
  2. #include "debug-uart.h" /* For usart_puts()*/
  3. #include <stdio.h> /* For printf() */
  4. static char process_thread_hello_world_process(struct pt *process_pt, process_event_t ev, process_data_t data);

  5. struct process hello_world_process = { ((void *)0), "Hello world process", process_thread_hello_world_process};
  6. struct process * const autostart_processes[ = {&hello_world_process, ((void *)0)};
  7. char process_thread_hello_world_process(struct pt *process_pt, process_event_t ev, process_data_t data)
  8. {
  9.     {
  10.         char PT_YIELD_FLAG = 1;
  11.         switch((process_pt)->lc)
  12.         {
  13.             case 0:
  14.                 ;
  15.                 usart_puts("Hello, world!\n");
  16.         };
  17.      }
  18.      PT_YIELD_FLAG = 0;
  19.      (process_pt)->lc = 0;;
  20.      return 3;
  21. }
复制代码

6.2 宏总结
对本实例用到的宏总结如上,以后就直接把宏当API用了。
PROCESS(name, strname)
声明进程 name的主体函数process_thread_##name(进程的thread函数指针所指的函数),并定义一个进程name
AUTOSTART_PROCESSES(...)
定义一个进程指针数组 autostart_processes
PROCESS_THREAD(name, ev, data)
进程name的定义或声明,取决于宏后面是";"还是"{}"
PROCESS_BEGIN()
进程的主体函数从这里开始
PROCESS_END()
进程的主体函数从这里结束
6.3 编程模型
    这实例虽说很简单,但却给出了定义一个进程的模型(还以Hello world为例),实际编程过程中,只需要将usart_puts("Hello, world!\n"); 换成自己需要实现的代码。
  1. //假设进程名称为Hello world
  2. #include "contiki.h"
  3. #include <stdio.h> /* For printf() */
  4. PROCESS(hello_world_process, "Hello world"); //PROCESS(name, strname)
  5. AUTOSTART_PROCESSES(&hello_world_process); //AUTOSTART_PROCESS(...)
  6. /*Define the process code*/
  7. PROCESS_THREAD(hello_world_process, ev, data) //PROCESS_THREAD(name, ev, data)
  8. {
  9.     PROCESS_BEGIN();
  10.     /***这里填入你的代码***/
  11.     PROCESS_END();
  12. }
复制代码
    注:声明变量最好不要放在PROCESS_BEGIN之前,因为进程再次被调度,总是从头开始执行,直到PROCESS_BEGIN宏中的switch判断才跳转到断点case __LINE__。也就是说,进程被调度总是会执行PROCESS_BEGIN之前的代码。

STM32移植contiki 从入门到进阶学习

STM32移植contiki 从入门到进阶学习
  • hurry_liu
  • hurry_liu
  • 2013年09月23日 09:39
  • 1351

Contiki 编程用户手册

  • 2012年03月05日 13:21
  • 725KB
  • 下载

Contiki高级程序设计

基于Contiki系统,实现进程阻塞和线程调度。
  • jiangjunjie_2005
  • jiangjunjie_2005
  • 2015年12月06日 09:04
  • 1936

Contiki教程——进程

Contiki中的代码可以运行在下列两种执行上下文之一:合作式或者抢占式。合作式代码按顺序运行,抢占式代码可以暂停正在运行的合作式代码。Contiki中的进程运行在合作式上下文中,而中断和实时定时器运...
  • tidyjiang
  • tidyjiang
  • 2016年05月11日 21:47
  • 3616

Contiki入门学习【摘录】

该文章转载自该地址http://blogt.chinaunix.net/space.php?uid=9112803&do=blog&id=2829353 一、Contiki简介 Contiki...
  • ferlansue
  • ferlansue
  • 2012年02月14日 10:06
  • 22740

Contiki Timers 详解

Timers Contiki系统提供了一套时钟库用于应用程序和系统本身。时钟库包含了检查时间超出、将系统从低功耗模式唤醒到预定时间,以及实时任务安排等功能。时钟也用于应用程序,让系统和其他一起工作,...
  • broadCE
  • broadCE
  • 2015年06月01日 17:41
  • 1092

contiki学习笔记 spi部分解析

1前言 contiki是一款非常有意思的无线传感网络操作系统。认真学习了一段时间,跟踪了contiki的发展深知国内和国外在无线传感网方面的差距(这个以后可以慢慢分析)。contiki中支持的平台很多...
  • xukai871105
  • xukai871105
  • 2013年06月14日 22:43
  • 6061

contiki学习笔记1:helloworld+process结构分析

contiki学习笔记参考的是阿巴睇的博客,简直太棒了,真是大神,我按照他的思路一边看我移植好的程序,一遍配合大神的博客进行分析。写的很多都一样,之所以再写一遍是想理清思路。阿巴睇的博客再贴一遍连接。...
  • zhou307
  • zhou307
  • 2016年05月05日 12:28
  • 1240

Contiki 配置参数“技巧”说明

contiki中有很多的参数设置,这些参数设置包括uIP、6lowpan、通用驱动和具体平台等等,contiki的配置参数散落在Demo makefile文件、Demo project_conf.h、...
  • xukai871105
  • xukai871105
  • 2016年05月07日 10:18
  • 11612

Webbench源代码分析

Web Bench是一个网站压力测试的工具。其最后更新时间是2004年,已经十年多了。其源代码总共才500多行,全部使用C语言编写,最多可以模拟上万个并发连接。 其原理也比较简单,源代码值得一读。...
  • KangRoger
  • KangRoger
  • 2015年01月07日 20:57
  • 10944
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Contiki 实例hello_world详细剖析
举报原因:
原因补充:

(最多只允许输入30个字)