STM32移植contiki入门之三:从LED灯程序到contiki编程模型
本篇深入前面的led灯程序,分析contiki系统编程,给出一般的contiki编程模型。鉴于篇幅原因,不再将LED的代码放在此,如果大家想看,可以参考我的上一篇文章STM32移植contiki入门之二:简单编程点亮LED灯
一、led程序分析
PROCESS(led_on_process, "led on");
AUTOSTART_PROCESSES(&led_on_process);
PROCESS_THREAD(led_on_process, ev, data)
{
PROCESS_BEGIN();
PROCESS_END();
}
process_start(&led_on_process,NULL);
autostart_start(autostart_processes);
(一)、PROCESS宏
PROCESS宏完成两个功能:
(1) 声明一个函数,该函数是进程的执行体,即进程的thread函数指针所指的函数
(2) 定义一个进程
PROCESS宏源码展开如下(我这里只是部分代码,并没有完全展开):
#define PROCESS(name, strname) \
PROCESS_NOLOAD(name, strname); \
PROCESS_LOAD(name)
#define PROCESS_NOLOAD(name, strname) \
PROCESS_THREAD(name, ev, data); \
struct process name = { NULL, strname, \
process_thread_##name }
#define PROCESS_THREAD(name, ev, data) \
static PT_THREAD(process_thread_##name(struct pt *process_pt, \
process_event_t ev, \
process_data_t data))
(二)、PROCESS_THREAD宏
PROCESS_THREAD宏的功能是进程name的定义或声明,这取决于宏后面是";"还是"{}"
PROCESS(led_on_process, "led on"); 展开成两句,其中有一句是也是PROCESS_THREAD(led_on_process, ev, data) ;。这里要注意到分号,是一个函数声明。而这PROCESS_THREAD(led_on_process, ev, data)没有分号,而是紧跟着"{}",是上述声明函数的实现。关于PROCESS_THREAD宏的分析,最后展开如下
contiki的进程之间不是没有联系的,我们之前说过,contiki维护着一个事件链表,当一个事件(进程)需要切换到另一个进程时,就是通过这里来的。(我的理解是这样的)
(三)、AUTOSTART_PROCESSES宏
AUTOSTART_PROCESSES宏功能是定义一个指针数组,存放Contiki系统运行时需自动启动的进程
宏展开如下:
#define AUTOSTART_PROCESSES(...) \ struct process * const autostart_processes[] = {__VA_ARGS__, NULL}
AUTOSTART_PROCESSES(&led_on_process); 被替换成
struct process * const autostart_processes[] = {&led_on_process, NULL};
这样就就可以让多个进程自启动了,直接在宏AUTOSTART_PROCESSES()加入需自启动的进程地址,比如有两个进程hello_process和world_process需要自启动,用如下的语句就能启动:
AUTOSTART_PROCESSES(&hello_process,&world_process);
(四)、PROCESS_BEGIN宏和PROCESS_END宏
PROCESS_BEGIN()进程的主体函数的开始,PROCESS_END()进程的主体函数的结束
原则上,所有代码都得放在PROCESS_BEGIN宏和PROCESS_END宏之间(如果程序全部使用静态局部变量,这样做总是对的。倘若使用局部变量,情况就比较复杂了,当然,不建议这样做),看完下面宏展开,就知道为什么了。
PROCESS_BEGIN宏
PROCESS_BEGIN宏一步步展开如下:
#define PROCESS_BEGIN() PT_BEGIN(process_pt)
process_pt是struct pt*类型,在函数头传递过来的参数(见四),直接理解成lc,用于保存当前被中断的地方,以便下次恢复执行。继续展开:
#define PT_BEGIN(pt) { char PT_YIELD_FLAG = 1; LC_RESUME((pt)->lc)
#define LC_RESUME(s) switch(s) { case 0:
把参数替换,结果如下:
{
char PT_YIELD_FLAG = 1; /*将PT_YIELD_FLAG置1,类似于关中断*/
switch(process_pt->lc) /*程序根据lc的值进行跳转,lc用于保存程序断点*/
{
case 0: /*第一次执行从这里开始*/
;
最开始看到这里的时候,我很奇怪,PROCESS_BEGIN宏展开并不是完整的语句,后来才知道,这里有门道,继续向后看吧
PROCESS_END宏
PROCESS_END宏一步步展开如下:
#define PROCESS_END() PT_END(process_pt)
#define PT_END(pt) LC_END((pt)->lc); PT_YIELD_FLAG = 0; \ PT_INIT(pt); return PT_ENDED; }
#define LC_END(s) }
#define PT_INIT(pt) LC_INIT((pt)->lc)
#define LC_INIT(s) s = 0;
#define PT_ENDED 3
整理下,实际上如下代码:
}
PT_YIELD_FLAG = 0;
(process_pt)->pt = 0;
return 3;
}
现在我们可以看到,PROCESS_BEGIN中不完整的语句,都被消除了,PROCESS_BEGIN和PROCESS_END这样的设计,真是别具一格,让人耳目一新的感觉。同时我们也应该注意到:PROCESS_BEGIN和PROCESS_END必须是成对出现的,有你有我,互相依靠。呵呵
综合上面的分析,我们可以手动的展开上一章中LED的代码,得到如下的程序。
static char process_thread_led_on_process(struct pt *process_pt, process_event_t ev, process_data_t data);
struct process led_on_process = { ((void *)0), "led on", process_thread_led_on_process};
struct process * const autostart_processes[] = {&led_on_process, ((void *)0)};
char process_thread_led_on_process(struct pt *process_pt, process_event_t ev, process_data_t data)
{
{
char PT_YIELD_FLAG = 1;
switch((process_pt)->lc)
{
case 0:
;
FIO1DIR2 = 0x01 << 18;
FIO5DIR0 = 0x07 << 2;
FIO1SET2 = (~0x01) << 18;
FIO5SET0 = (~0x07) << 2;
};
}
PT_YIELD_FLAG = 0;
(process_pt)->lc = 0;;
return 3;
}
好吧,到这里,估计很多人都糊涂了,我也糊涂了,这些宏到底是怎么展开的?其实非常简单
PROCESS -> PROCESS_THREAD(声明) -> PT_THREAD
PROCESS_THREAD -------->定义
即:PROCESS宏展开为声明,PROCESS_THREAD展开为定义。如果你发现PROCESS宏之后跟的是分号,而PROCESS_THREAD宏之后跟的是{},就可以联想到,这是C语言中的声明和定义了。
二、编程模型
通过以上分析,我们可以得出contiki的一个编程模型。
这实例虽说很简单,但却给出了定义一个进程的模型(还以Hello world为例),实际编程过程中,只需要将usart_puts("Hello, world!\n"); 换成自己需要实现的代码。
//假设进程名称为Hello world
PROCESS(hello_world_process, "Hello world"); //声明进程
AUTOSTART_PROCESSES(&hello_world_process); //将进程放到启动process中
PROCESS_THREAD(hello_world_process, ev, data) //进程定义,真正需要实现的代码放在这里
{
PROCESS_BEGIN();
/***填写你的代码***/
PROCESS_END();
}