contiki是以一款应用于单片机上的操作系统,主要服务于WSN(无线传感器网络)。它的强大之处在于对于网络方面的支持,包括6lowpan, IEEE802.15.4,rime 等网络协议。不仅如此,低内存占有,protothread的设计也为人们津津乐道。它的作者是大名鼎鼎的Adam,同时也是uip和lwip的作者。还有就是完全开源,并辅以许多高质量论文,这对于有兴趣研究的同学,可算是关键问题了。
————————————
contiki 之 button sensor Sensor 简介数据结构 part1part2part3 Sensor模版设计 sensorsc 内的几个函数 get_sensor_indexsensors_firstsensors_nextsensors_changed sensors_process Button 初始化设计Button sensor的整体工作流程总结
sensor 顾名思义,就是生活中无处不在的传感器。可以用于检测温度、湿度、电流、压力等等参数。由于contiki主要服务于WSN(无线传感器网络),其中关于sensor方面的设计自然是重中之重。那么这些sensor检测到的数据是怎么传输给单片机处理的呢?下面将会以德州仪器的cc1310作为例子,介绍button sensor的工作原理。
路径&文件名 (contiki-3.0\core\lib\sensor.h)
struct sensors_sensor { char * type; int (* value) (int type); int (* configure) (int type, int value); int (* status) (int type); }; 123456 123456这个结构体用于定义每一个sensor属性。 - type:一个char类型的指针,一般会用一个字符串定义这个sensor的类型。 - value:一个输入参数为( int ) 类型,返回值也为int类型的函数指针。这个函数主要用于取得该sensor的值。 - configure:一个输入参数为( int ,int ) 类型,返回值也为int类型的函数指针。主要用于该sensor的初始化配置。 - status:主要用于查看该sensor的状态。
很明显,该宏定义用于创建一个名字为name的具体的sensors_sensor结构体。
刚才的SENSORS_SENSOR是为了创建一个具体的sensors_sensor,之所以每个sensor都用一个结构体来定义就是方便管理和使用。最终是需要把他们统一起来的。这里,SENSORS(…) 的作用就是把它们都统一到一个数组里面方便管理。但,这并不是单纯地把结构体都放在数组里面,而是把代表每个sensor的结构体指针放在数组里面。这样既不影响每个sensor的单独定义,又能够把它们放在一起集中管理。 宏定义的SENSORS_NUM能取到sensor的数量。利用它,能够定义一个相同数量的sensor_flags的数组,用于作为每个sensor的标记。
sensor的整体设计大致就是每个sensor触发后,就会交给sensor_process线程(PROCESS_THREAD)进行处理。下面先介绍sensor_process线程(PROCESS_THREAD)用到的几个函数。
路径&文件名 (contiki-3.0\core\lib\sensor.c)
它的输入参数为类型为sensors_sensor的结构体。也就是每个sensor在使用之初都会利用sensors_sensor结构体来定义自己的属性,详见 数据结构 part1。该函数遍历数组const struct sensors_sensor * sensors[]中的结构体,并找到代表该sensor的结构体在指针数组中的索引(index)位置。
该函数用于查找数组const struct sensors_sensor * sensors[]的第一个结构体,并返回该sensor的结构体指针。
找到下一个sensor的结构体,并返回该sensor的结构体指针。
这个函数是给具体的sensor写应用时调用的,sensor通过该函数传递信号给sensors_process。sensors_process会做出相应的处理。可以看到函数的内部,会调用get_sensor_index找到该sensor结构体的索引位置,然后把该位置的sensors_flags标记置成FLAG_CHANGED。接着调用process_poll,驱动一次sensors_process线程。 关于利用process_poll而不是process_post_synch,是一个值得讨论的问题。
路径&文件名 (contiki-3.0\core\lib\sensor.c)
PROCESS_THREAD(sensors_process, ev, data) { static int i; static int events; PROCESS_BEGIN(); sensors_event = process_alloc_event(); for(i = 0; sensors[i] != NULL; ++i) { sensors_flags[i] = 0; sensors[i]->configure(SENSORS_HW_INIT, 0); } num_sensors = i; while(1) { PROCESS_WAIT_EVENT(); do { events = 0; for(i = 0; i < num_sensors; ++i) { if(sensors_flags[i] & FLAG_CHANGED) { if(process_post(PROCESS_BROADCAST, sensors_event, (void *)sensors[i]) == \ PROCESS_ERR_OK) { PROCESS_WAIT_EVENT_UNTIL(ev == sensors_event); } sensors_flags[i] &= ~FLAG_CHANGED; events++; } } } while(events); } PROCESS_END(); } 123456789101112131415161718192021222324252627282930313233343536373839 123456789101112131415161718192021222324252627282930313233343536373839线程的初始化工作 sensors_event = process_alloc_event(); 分配了sensor类型的 接着遍历了所有sensor的结构体指针,并把他们的flag置为0,同时调用configure函数对sensor初始化配置。 接着,num_sensors得到sensor的总数。 线程的主体工作 1. PROCESS_WAIT_EVENT();等待事件的发生,即等待sensors_changed()的调用
当该线程被驱动后,则开始遍历sensors_flags[],查看到底是哪个sensor的动作(FLAG_CHANGED)。找到那个sensor后,就调用process_post()函数,以sensors_event事件的名义广播给所有的process(线程)。广播完成后会把相应的sensors_flags[i]数组中的内容清除。events++, 自加,然后并不会跳出do循环,而是接着进行第二次循环,events = 0; 后再确认确实没有sensor发生FLAG_CHANGED的动作后,才退出循环。PROCESS_WAIT_EVENT_UNTIL(ev == sensors_event); 这一句其实是我比较感兴趣的。既然都已经广播出去了,还要等自己的事件干什么呢。经过测试发现如果不这一句的话,PROCESS_WAIT_EVENT();会收到两次事件,一次是FLAG_CHANGED,另外一次则是自身的广播。所以加上这一句相当于少检测了一次。 猜测部分:另外,也能够避免当多个sensor的change事件并发的时候,连续process_post相同的事件,只是传递的sensors指针不同。导致前一次的传递被后一次覆盖。结果是前一次的process_post并没有执行。所以加上这一句也能保证该部分确实执行了。当然这部分有待考证了。下面以功能为select的button(按键)为例,介绍button的初始化部分的设计。路径&文件名:(contiki-master\platform\srf06-cc26xx\srf06\button-sensor.c ; button-sensor.h)
SENSORS_SENSOR(button_select_sensor, BUTTON_SENSOR, value_select, config_select, status_select); #define BUTTON_SENSOR "Button" #define SENSORS_SENSOR(name, type, value, configure, status) \ const struct sensors_sensor name = { type, value, configure, status } 123456 123456根据上面的介绍,对于SENSORS_SENSOR的作用应该比较清楚了。所以定义的结构体的(name)名字为button_select_sensor,type 为 Button, value函数为value_select, configure函数为 config_select,status函数为status_select。
static int value_select(int type) { if(type == BUTTON_SENSOR_VALUE_STATE) { return ti_lib_gpio_read_dio(BOARD_IOID_KEY_SELECT) == 0 ? BUTTON_SENSOR_VALUE_PRESSED : BUTTON_SENSOR_VALUE_RELEASED; } else if(type == BUTTON_SENSOR_VALUE_DURATION) { return (int)sel_timer.duration; } return 0; } 1234567891011 1234567891011输入参数为type, 如果type为BUTTON_SENSOR_VALUE_STATE,则会返回当前按键的状态是按下的还是,松开的。 如果type为BUTTON_SENSOR_VALUE_DURATION,则会返回当前值是按下了多久。
static int config_select(int type, int value) { config_buttons(type, value, BOARD_IOID_KEY_SELECT); return 1; } /*---------------------------------------------------------------------------*/ /** * \brief Configuration function for the button sensor for all buttons. * * \param type This function does nothing unless type == SENSORS_ACTIVE * \param c 0: disable the button, non-zero: enable * \param key: One of BOARD_KEY_LEFT, BOARD_KEY_RIGHT etc */ static void config_buttons(int type, int c, uint32_t key) { switch(type) { case SENSORS_HW_INIT: ti_lib_gpio_clear_event_dio(key); ti_lib_rom_ioc_pin_type_gpio_input(key); ti_lib_rom_ioc_port_configure_set(key, IOC_PORT_GPIO, BUTTON_GPIO_CFG); gpio_interrupt_register_handler(key, button_press_handler); break; case SENSORS_ACTIVE: if(c) { ti_lib_gpio_clear_event_dio(key); ti_lib_rom_ioc_pin_type_gpio_input(key); ti_lib_rom_ioc_port_configure_set(key, IOC_PORT_GPIO, BUTTON_GPIO_CFG); ti_lib_rom_ioc_int_enable(key); } else { ti_lib_rom_ioc_int_disable(key); } break; default: break; } } sensors[i]->configure(SENSORS_HW_INIT, 0); #define SENSORS_ACTIVATE(sensor) (sensor).configure(SENSORS_ACTIVE, 1) #define SENSORS_DEACTIVATE(sensor) (sensor).configure(SENSORS_ACTIVE, 0) /* some constants for the configure API */ #define SENSORS_HW_INIT 128 /* internal - used only for initialization */ #define SENSORS_ACTIVE 129 /* ACTIVE => 0 -> turn off, 1 -> turn on */ #define SENSORS_READY 130 /* read only */ 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849这是初始化button的函数。在sensors_process(线程)里面,用到了sensors[i]->configure(SENSORS_HW_INIT, 0);对其硬件方面进行初始化。其他的则是SENSORS_ACTIVATE(激活)和SENSORS_DEACTIVATE(失活)sensor。
/*---------------------------------------------------------------------------*/ /** * \brief Status function for the select button. * \param type SENSORS_ACTIVE or SENSORS_READY * \return 1 if the button's port interrupt is enabled (edge detect) * * This function will call status. It will pass type verbatim and it will also * pass the correct key_io_id */ static int status_select(int type) { return status(type, BOARD_IOID_KEY_SELECT); } /*---------------------------------------------------------------------------*/ /** * \brief Status function for all buttons * \param type SENSORS_ACTIVE or SENSORS_READY * \param key_io_id BOARD_IOID_KEY_LEFT, BOARD_IOID_KEY_RIGHT etc * \return 1 if the button's port interrupt is enabled (edge detect) * * This function will only be called by status_left, status_right and the * called will pass the correct key_io_id */ static int status(int type, uint32_t key_io_id) { switch(type) { case SENSORS_ACTIVE: case SENSORS_READY: if(ti_lib_ioc_port_configure_get(key_io_id) & IOC_INT_ENABLE) { return 1; } break; default: break; } return 0; } 123456789101112131415161718192021222324252627282930313233343536373839 123456789101112131415161718192021222324252627282930313233343536373839status_select()用于检查该按键的状态(边沿触发的中断是否被使能)。
/** * A timer. * * This structure is used for declaring a timer. The timer must be set * with timer_set() before it can be used. * * \hideinitializer */ struct timer { clock_time_t start; clock_time_t interval; }; struct btn_timer { struct timer debounce; clock_time_t start; clock_time_t duration; }; static struct btn_timer sel_timer; /*---------------------------------------------------------------------------*/ /** * \brief Handler for SmartRF button presses */ static void button_press_handler(uint8_t ioid) { if(ioid == BOARD_IOID_KEY_SELECT) { if(!timer_expired(&sel_timer.debounce)) { return; } timer_set(&sel_timer.debounce, DEBOUNCE_DURATION); /* * Start press duration counter on press (falling), notify on release * (rising) */ if(ti_lib_gpio_read_dio(BOARD_IOID_KEY_SELECT) == 0) { sel_timer.start = clock_time(); sel_timer.duration = 0; } else { sel_timer.duration = clock_time() - sel_timer.start; sensors_changed(&button_select_sensor); } } } 12345678910111213141516171819202122232425262728293031323334353637383940414243444546 12345678910111213141516171819202122232425262728293031323334353637383940414243444546 静态变量如果没有初始化,则会自动初始化为0。所以sel_timer初始值为0。按键按下,下降沿,进入button_press_handler timer_expired(&sel_timer.debounce)为1程序继续往下走,timer_set(),设置保持按下的时间为DEBOUNCE_DURATION (1/32 秒)。 检测到该键的值为0,保存按键开始按下的时间。按键松开,上升沿,进入button_press_handler 如果按键按下的时间超过DEBOUNCE_DURATION (1/32 秒),timer_expired(&sel_timer.debounce)为1,程序继续往下走。timer_set()这一次的设置不重要,只要比当前时间小就行。 接着读到按键引脚的状态为1,怎进入else,计算实际按键按下的时间,并通过sensors_changed(&button_select_sensor);告知sensors_process(线程),已经有select按键按下,需要做出处理。contiki中sensor部分的代码分析就是如上所述的内容了。初看虽然有一点难以理解,仔细分析还是能一点一点地找到思路的。sensor的框架设计方面是非常值得参考的。把sensor的相关功能完全都抽象出来了,然后用结构体指针数组把所有的sensor都放在一起管理。这和高级语言中类的思路其实是一样的,但是C在使用上就没有高级语言那么方便了。