contiki SLIP代码分析

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

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
对于移植 Contiki 操作系统到特定硬件平台,以下是一些基本步骤: 1. 了解目标硬件平台:首先,你需要详细了解目标硬件平台的架构、处理器类型、存储器配置以及外设等信息。这将帮助你在移植过程中进行必要的配置和适配。 2. 下载 Contiki:从 Contiki 官方网站下载最新版本的 Contiki 操作系统。Contiki 是一个开源项目,可以在其官方网站上找到相关的文档和资源。 3. 配置编译环境:根据目标硬件平台的要求,配置合适的交叉编译工具链和开发环境。这些工具可用于将 Contiki 操作系统编译成适合目标硬件平台的可执行文件。 4. 修改配置文件:Contiki 提供了一个名为 'platform' 的目录,其中包含了不同硬件平台的配置文件。你需要根据目标硬件平台的要求,修改或创建适当的配置文件。 5. 实现驱动程序:根据目标硬件平台的外设要求,在 Contiki 中实现相应的驱动程序。这可能涉及到与硬件交互的底层代码编写。 6. 适配网络协议栈:Contiki 自带了一个轻量级的网络协议栈,你需要将其适配到目标硬件平台上。这包括配置网络接口、设置网络参数以及处理网络数据包等。 7. 编译和烧录:使用之前配置好的交叉编译工具链,将 Contiki 操作系统编译为可执行文件。然后,将可执行文件烧录到目标硬件平台上进行运行和测试。 请注意,移植 Contiki 操作系统可能需要一些底层嵌入式系统和操作系统的知识。确保你对目标硬件平台和 Contiki 的要求有足够的了解,并参考 Contiki 官方文档和社区资源来帮助你进行移植过程中的各种配置和适配。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值