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分层模式去处理。这一点比较好。

相关文章推荐

LwIP源代码文件目录解析

1 -- LwIP源代码文件目录 root@motadou:/home/motadou/lwip/lwip-1.4.1# tree . ├── CHANGELOG ├── COPYING ├── d...

PPP/SLIP 协议分析

PPP协议   在学习PPP协议之前我们需要先了解一下它的前辈SLIP协议, 80年代家庭用户是通过PC的RS232串口与Modem连接在一起进行上网冲浪的。SLIP(Serial Line I...

SLIP协议和PPP协议

SLIP协议和PPP协议都是数据链路层协议。SLIP和PPP是串行线上最常用的两个链路层通信协议,它们为在点对点链路上直接相连的两个设备之间提供一种传送数据报的方法。互联的两端设备可以是主机与主机、路...
  • wuruixn
  • wuruixn
  • 2012年09月09日 21:45
  • 6591

C# 根据SLIP协议封装报文

最近在做一个TCP通讯项目时,用到了SLIP(Serial Line Internet Protocol,串行线路网际协议)协议。该协议我就不介绍了网上资料一大堆。 通信数据报采用了简单的帧封装结构,...
  • mt122
  • mt122
  • 2013年05月30日 00:02
  • 1344

读串口总结

项目需求从串口中读取数据,刚开始方案是一次从串口中read 512字节的数据然后解析,代码写完后发现有时候接收的数据不是完整的一包数据,导致数据无法解析,为解决这个问题,修改方案为循环读取,一直读到需...

Delphi7高级应用开发随书源码

  • 2003年04月30日 00:00
  • 676KB
  • 下载

一.Contiki之进程(1)——进程结构体定义

进程是contiki的主要数据结构之一,直接上源代码,在注释里给出分析 struct process { struct process *next; //指向下一个进程 //宏PROCESS_...

contiki系统分析三:进程分析

1. contiki中进程的类型     由图示我们可以看到,contiki中包含两种类型的进程,preemptive(可抢占的)和cooperative(合作的,由于只有两...
  • wfing
  • wfing
  • 2013年03月21日 21:19
  • 2879

蓝屏代码分析

  • 2016年04月30日 21:26
  • 15KB
  • 下载

ucoss中os-tmr.c中的代码分析

  • 2017年07月21日 14:40
  • 51KB
  • 下载
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:contiki SLIP代码分析
举报原因:
原因补充:

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