contiki SLIP代码分析

原创 2015年07月09日 17:08:39

contiki SLIP代码分析

前言

    本文通过分析slip_radio代码,结合C语法,体会contiki系统的文件的编写风格,理解contiki中的SLIP协议

正文

对于Contiki OS,一个任务(线程)slip_radio的加载比较常见的方法是使用AUTOSTART_PROCESSES

对于一个线程,通常可以看到三部分

1,//线程声明PROCESS(xxxx_process, "xxxx process");

2,//线程加载AUTOSTART_PROCESSES(&xxxx_process);

3,//线程实体PROCESS_THREAD(xxxx_process, ev, data)

对于slip_radio_process,具体有

//线程声明

PROCESS(slip_radio_process, "Slip radio process");

//线程加载

AUTOSTART_PROCESSES(&slip_radio_process);

//线程实体

PROCESS_THREAD(slip_radio_process, ev, data)

PROCESS_THREAD(slip_radio_process, ev, data)
{
  static struct etimer et;
  PROCESS_BEGIN();

  init();//slip初始化
  NETSTACK_RDC.off(1);
#ifdef SLIP_RADIO_CONF_SENSORS
  SLIP_RADIO_CONF_SENSORS.init();    //传感器
#endif
  printf("Slip Radio started...\n");

  etimer_set(&et, CLOCK_SECOND * 3); //设置事件定时器

  while(1) {
    PROCESS_YIELD();

    if(etimer_expired(&et)) {
      etimer_reset(&et);
#ifdef SLIP_RADIO_CONF_SENSORS
      SLIP_RADIO_CONF_SENSORS.send();
#endif
    }
  }
  PROCESS_END();
}
 在slip_radio_process线程实体中,无操作是如下格式
 PROCESS_BEGIN();
 etimer_set(&et, CLOCK_SECOND * 3);
   while(1) {
 PROCESS_YIELD();
   if(etimer_expired(&et)) {
      etimer_reset(&et);
}

这里注意集中应用程序上的分析,暂不分析OS

初始化init();

在init()中可以看到

static void
init(void)
{
#ifndef BAUD2UBR
#define BAUD2UBR(baud) baud
#endif
  slip_arch_init(BAUD2UBR(115200));
  process_start(&slip_process, NULL);
  slip_set_input_callback(slip_input_callback); //设置输入回调函数
  //input_callback=slip_input_callback 映射
  packet_pos = 0;
}

第一初始化硬件UART 115200

第二启动线程process_start(&slip_process, NULL);

第三设置输入回调函数,就是调用哪个函数处理输入的消息。这里分析一下怎么设置的

void
slip_set_input_callback(void (*c)(void))
{
  input_callback = c;
}

 Q:slip_set_input_callback传入参数void (*c)(void))啥意思?

 A: 一个指向一个返回值和参数均为空的函数的指针c,即C是一个函数指针

 函数指针最常见的两个用途是转换表(jump table)和作为参数传递给另一个函数。

注意:作为参数传递时,赋值(初始化)时一定要注意指向的函数类型要一致。那么看看input_callback 的定义

static void (* input_callback)(void) = NULL;

发现函数指针input_callback指向一个返回值和参数均为空的函数,可见是一致的,编译器不会有问题。赋值之后就变成了函数指针input_callback 指向slip_input_callback函数。

slip_input_callback函数原型:

static void
slip_input_callback(void)  //输入回调函数
{
  PRINTF("SR-SIN: %u '%c%c'\n", uip_len, uip_buf[0], uip_buf[1]);
  cmd_input(uip_buf, uip_len); //处理CMD输入
  uip_len = 0;
}

核心-CMD输入函数cmd_input(uip_buf, uip_len);//处理CMD输入语句原型

void
cmd_input(const uint8_t *data, int data_len)
{
  int i;
  for(i = 0; cmd_handlers[i] != NULL; i++) {
    if(cmd_handlers[i](data, data_len)) {
      /* Command has been handled */
      return;
    }
  }

  /* Unknown command */
  cmd_send((uint8_t *)"EUnknown command", 16);
}

 cmd_input函数使用数组cmd_handlers[]循环处理传入的消息,这个数组有什么特点呢?

cmd_handlers[]定义跳转到宏定义

CMD_HANDLERS(CMD_CONF_HANDLERS);

 首先这个是宏,看看完整的宏定义

typedef int (* cmd_handler_t)(const uint8_t *data, int len);

#define CMD_CONF_HANDLERS slip_radio_cmd_handler,cmd_handler_cc2520

#define CMD_HANDLERS(...) \
const cmd_handler_t cmd_handlers[] = {__VA_ARGS__, NULL}
先分析第一行,这句话用typedef 定义了一个什么呢?

首先知道typedef int * zzu 定义了一个指向int类型的指针的新类型zzu,

在分析typedef 的定义时可以用下面的方法: 
先去掉typedef 和别名, 剩下的就是原变量的类型

int (* )(const uint8_t *data, int len);
那么typedef int (* cmd_handler_t)(const uint8_t *data,int len)定义了一个cmd_handler_t新类型,就像int类型可定义一个int变量一样,cmd_handler_t可定义一个指向int (* )(const uint8_t *data, int len)的函数指针。

比如

定义一个函数指针        cmd handler_t cmd_handler_zzu

定义一个函数指针数组  cmd_handler_t cmd_handlers[]

 分析第二行

#define CMD_CONF_HANDLERS slip_radio_cmd_handler,cmd_handler_cc2520
CMD_CONF_HANDLERS宏定义成两个函数。怎么用你知道吗?

且看第三行可变参数宏定义

#define CMD_HANDLERS(...) \
const cmd_handler_t cmd_handlers[] = {__VA_ARGS__, NULL}
宏定义 CMD_HANDLERS,特点有一个__VA_ARGS__类型的预定义符号。

__VA_ARGS__ 是一个可变参数的宏,C99编译器当中出现的新类型,常见的预定义符号还有:

__LINE__ : 当前(源代码文件)行号 [整数]
__FILE__ : 当前正在编译的文件的文件名 [字符串]
__DATE__ : 当前日期,以“月月 日日 年年年年”的形式给出 [字符串]
__TIME__ : 当前时间,以“HH:mm:ss”的格式给出 [字符串]
__STDC__ : 如果编译器符合ANSI C标准,该宏为1,否则为0
__STDC_HOSTED__ : 如果实现了所有C标准库,该宏为1,否则为0
__STDC_VERSION__ : 被定义为199901L(不同编译器可能不一样,比如我用的gcc里就没有这个预定义符号)
注:这些预定义符号的首尾为两个下划线,如果是两个单词,中间以一个下划线连接。

这些符号不能用#define重新定义,也不能用#undef取消。即使用#define重新定义了,它的值还是系统默认定义的值。

总结输入消息处理

回头看cmd_handlers[]的宏定义

CMD_HANDLERS(CMD_CONF_HANDLERS);
解析成 

const cmd_handler_t cmd_handlers[] = {slip_radio_cmd_handler,cmd_handler_cc2520};
即完成了cmd_handlers[]数组的初始化,只不过这是一个函数指针数组,里边的元素都是函数。

完整的解析如下: 

const int (* cmd_handlers[])(const uint8_t *data, int len) = {slip_radio_cmd_handler,cmd_handler_cc2520};
看上去是不是很不好理解啊!还是使用typedef定义一个函数指针新类型,在定义数组比较好理解

  在定义复杂的类型名字,如函数指针或者指向数组的指针时,使用typedef 更为合适。

 在CMD输入函数cmd_inputfor循环调用数元素 cmd_handlers[i]-函数指针,处理传入的参数、 cmd_handlers[i]是一个 cmd_handler_t类型即指向(int (* )(const uint8_t *data, int len)函数的指针,初始化为{slip_radio_cmd_handler,cmd_handler_cc2520}。

 信息流输入:

step1:

  SLIP(串口)输入的数据传到contiki输入接口input_callback(函数指针),该指针初始化指向slip_input_callback,即我们的消息回调函数

step2:

  cmd_input中利用for循环调用cmd_handlers函数指针数组的元素(函数)(slip_radio_cmd_handler,cmd_handler_cc2520)处理流入的消息。

 技巧:这里可以看到宏定义,typedef,函数指针的影子。真是高手啊。

 

下面分析这两个消息处理函数

slip_radio_cmd_handler

函数通过数据帧定义来做相应的处理。

SLIP数据格式的定义

起始码

消息

结束码

Data-0

Data-1

Data-2

Data-X~

Data-N

Data-N

 

消息码

数据

 




 

消息帧定义

命令码

功能

16进制(HEX

Byte0

Byte1

前两个字节表示命令类型

 

!

S

!S 发送数据

21 53

?

M

?M查询MAC地址,副作用:返回!M+MAC地址

3F 4D

!

M

!M返回MAC地址

21 4D

!

C

!C设置RF的无线信道

 

?

C

?C查询无线RF信道channel,副作用:返回!C+channel

 

注:这里是测试用的,在实际应用中可以自定义相关命令码。

 

 

slip_radio_cmd_handler函数中

当接收到!S消息后调用 NETSTACK_MAC.send(packet_sent, &packet_ids[packet_pos])发送数据。层层调用最终通过无线RF发送;

当接收到?M查询MAC地址消息后,获得MAC地址,调用cmd_send(uip_buf, uip_len)通过串口返回数据。

//调用CMD发送
cmd_send(uip_buf, uip_len);
//直接调用宏
CMD_OUTPUT(data, data_len);
//#define CMD_CONF_OUTPUT slip_radio_cmd_output
//#define CMD_OUTPUT CMD_CONF_OUTPUT slip_send_packet(data, data_len);//slip_radio_cmd_output

 slip_send_packet(data, data_len)原型

  void
  slip_send_packet(const uint8_t *ptr, int len)
  {
    uint16_t i;
    uint8_t c;
  
    slip_arch_writeb(SLIP_END);
    for(i = 0; i < len; ++i) {
      c = *ptr++;
     if(c == SLIP_END) {
       slip_arch_writeb(SLIP_ESC);
       c = SLIP_ESC_END;
     } else if(c == SLIP_ESC) {
       slip_arch_writeb(SLIP_ESC);
       c = SLIP_ESC_ESC;
     }
     slip_arch_writeb(c);
   }
   slip_arch_writeb(SLIP_END);
}
    这里可以更清楚地理解SLIP的数据格式起始码和结束码,并且在数据内进行了字符转义

 cmd_handler_cc2520(const uint8_t *data, int len)函数处理关于无线的消息。

View Code

当接收到!C消息后调用 cc2520_set_channel(data[2])设置RF的无线信道。

当接收到?C查询无线RF信道channel,cc2520_get_channel()获得信道,在返回!C无线RF信道channel。

关于RF发送接收,没有在cmd_handler_cc2520处理,而是交给contiki OS分层模式去处理。这一点比较好。

版权声明:本文为博主原创文章,未经博主允许不得转载。 举报

相关文章推荐

Contiki协议栈Rime:引子introduction

思来想去,既然是程序员,当然还是用一个程序引入比较好。当然,这个程序必须满足以下几点: 足够简单,不会把大家给吓着了 能够引入足够多的知识点,可以串起来 能够说明包如何在网络中传输 然后我就找啊找,找...

contiki tunslip6(SLIP管道文件)

此文件原理: 1、首先main函数里面,利用getopt 等函数检测命令行参数(关于波特率、主机、端口等信息。并对相关变量进行赋值)。 2、如果指定了-a 参数,则利用-a参数的 host 信息...
返回顶部
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

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