0 背景
在研究rime过程中使用了example中的rime文件夹下的例程,中间遇到的问题进行记录。
1 make出错
在rime例程文件夹下执行如下命令,以错误退出。
1 | make TARGET=srf06-cc26xx BOARD=srf06/cc26xx |
后来分析原因是里面有多个历程,复制rime到新的文件夹,将没用的c文件删除,哪些没用?可以看makefile文件中all:
后面的名称对应的c文件,除了example-abc.c外的所有文件。
2 接收不到数据
接收方式:开发板+仿真器+smartrf radio 2.4.3 可以接收到数据但是有溢出警告。 警告没关系,看看数据对不对:
- 发送的内容:
Hello
- 对应的HEX:
48 65 65 60 6f
smartrf radio接收到的十六进制数据:
1 | 16:58:33.737 | 16792 | 0e cd ab ff ff 03 3b 80 00 48 65 6c 6c 6f 00 | -39 |
可以发现其中包含48 65 65 60 6f
,看来数据正确,只是两种格式不一样而已,所以报错的。
如何才能正确显示呢? 很简单,两个板子都烧写同样的程序就好了。
3 example-abc数据怎么出去的
要说过程的话还是这个图片介绍的清楚:转载
如图2所示,描述了Contiki中整个无线传输链路基本结构,主要包含了:网络层(NETSTACK_NETWORK)、链路层安全驱动(link layer security driver, NETSTACK_LLSEC)、MAC层(Media Access Control, NETSTACK_MAC),RDC层(Radio Duty Cycling,NETSTACK_RDC)以及物理层(NETSTACK_RADIO)。
在图2的左边一列为对应不同层次,对应需要实现的驱动接口结构,右边为各个层次之间相互的调用情况(只提取了最重要的部分,相关处理没有细化)。
其中网络层主要完成相关无线网络的连接、组网等情况,链路层安全驱动用于保证整个传输链路的数据安全,MAC层完成物理媒介的访问控制,RDC层用于控制无线电电路的开断,可以有效的控制无线电部分的耗电情况,RADIO层实现了不同无线媒介底层访问驱动,完成整个无线链路的访问与操作。
3.1 函数调用过程跟踪:
abc_send ->
rime_output(&c->channel); ->
NETSTACK_LLSEC.send(packet_sent, c); (nullsec_driver.send) =
nullsec_driver.send(packet_sent, c); ->
NETSTACK_MAC.send(sent, ptr); (csma_driver) =
csma_driver.send(sent, ptr); =
send_packet(sent, ptr); ->
schedule_transmission(n); ->
//这里用到ctiemr
ctimer_set(&n->transmit_timer, delay, transmit_packet_list, n); ->
transmit_packet_list(void *ptr) ->
NETSTACK_RDC.send_list(packet_sent, n, q); (contikimac_driver) =
contikimac_driver.qsend_list(packet_sent, n, q); ->
ret = send_packet(sent, ptr, curr, is_receiver_awake); ->
NETSTACK_RADIO.transmit(transmit_len); ->
ret = rf_core_send_cmd((uint32_t)&cmd, &cmd_status);
3.2 数据传递过程:
packetbuf.c文件中定义有如下变量
1 | struct packetbuf_attr packetbuf_attrs[PACKETBUF_NUM_ATTRS]; |
4 abc
1 | static struct abc_conn abc; |
其中abc_conn数据结构如下
1 | struct abc_conn { |
- abc_open(&abc, 128, &abc_call); abc_open使能了128通道,并将abc的通道添加到channel_list上;
并设置abc_call为abc的回调函数。 - packetbuf_copyfrom(“Hello”, 6);
此函数将Hello拷贝到*packetbuf,即packetbuf_aligned内。 abc_send(&abc); 函数中调用语句:
1
rime_output(&c->channel);
rime_output中调用函数
NETSTACK_LLSEC.send(packet_sent, c);
可以看到这些参数传递是通过struct abc_conn abc;
进行的。
5 llsec - Link layer security
驱动:
1 | const struct llsec_driver nullsec_driver = { |
这里没有用到该层功能,所以调用过程只做参数传递。 NETSTACK_LLSEC.send = nullsec_driver.send,而Send函数如下
1 | static void |
6 Carrier Sense Multiple Access (CSMA) MAC
驱动:
1 | const struct mac_driver csma_driver = { |
NETSTACK_MAC.send = csma_driver.send_packet
6.1 mac层send_packet函数解析
send_packet函数代码结构如下:
1 | static void |
数据结构:
rdc_buf_list
1 | /* List of packets to be sent by RDC layer */ |
neighbor_queue
1 | /* Every neighbor has its own packet queue */ |
const linkaddr_t *addr = packetbuf_addr(PACKETBUF_ADDR_RECEIVER);
1
2
3
4
5
6typedef union {
unsigned char u8[LINKADDR_SIZE];
uint16_t u16;
} linkaddr_t;packetbuf_addr函数执行语句
1
2
3
4
5return &packetbuf_addrs[type - PACKETBUF_ADDR_FIRST].addr;
//type = PACKETBUF_ADDR_RECEIVER
//#define PACKETBUF_ADDR_FIRST PACKETBUF_ADDR_SENDER
//PACKETBUF_ADDR_SENDER = PACKETBUF_ADDR_RECEIVER + 1
//这里等效为&packetbuf_addrs[1].addrpacketbuf.c中定义
1
2struct packetbuf_addr packetbuf_addrs[PACKETBUF_NUM_ADDRS];
//PACKETBUF_NUM_ADDRS = 4或2这里是这样的
1
2
3struct packetbuf_addr {
linkaddr_t addr;
};也就是说有以linkaddr_t为内容的packetbuf_addr结构体数组packetbuf_addrs,经过赋值语句
const linkaddr_t *addr = packetbuf_addr(PACKETBUF_ADDR_RECEIVER);
将接收相关的地址赋值给addr。LIST_STRUCT_INIT(n, queued_packet_list);
1
2
3
4
5
6
7
8
9
10
11
12
do { \
(struct_ptr)->name = &((struct_ptr)->LIST_CONCAT(name,_list)); \
(struct_ptr)->LIST_CONCAT(name,_list) = NULL; \
list_init((struct_ptr)->name); \
} while(0)
void
list_init(list_t list)
{
*list = NULL;
}==>
1
2
3n->queued_packet_list = &(n->queued_packet_list_list); //= next
n->queued_packet_list_list = NULL;
* n->queued_packet_list = NUL;packetbuf_set_attr(PACKETBUF_ATTR_MAC_SEQNO, seqno++);
1
2
3
4
5
6int
packetbuf_set_attr(uint8_t type, const packetbuf_attr_t val)
{
packetbuf_attrs[type].val = val;
return 1;
}1
struct packetbuf_attr packetbuf_attrs[PACKETBUF_NUM_ATTRS];
这些是对数组中变量的更新。
neighbor 一个设备周围的能与其通信的设备都称作邻居/临近节点。设备用链表方式记录其邻居信息,链表的数据结构如下:
1
2
3
4
5
6
7
8
9/* Every neighbor has its own packet queue */
struct neighbor_queue {
struct neighbor_queue *next;
linkaddr_t addr;
struct ctimer transmit_timer;
uint8_t transmissions;
uint8_t collisions;
LIST_STRUCT(queued_packet_list);
};还定义了一个链表查找函数
neighbor_queue_from_addr
,应用示例:1
2/* Look for the neighbor entry */
n = neighbor_queue_from_addr(addr); //---->如果邻居链表中有addr的节点则将其在链表中的位置返回,如果没有则返回NULL。同时后面的语句会根据这n值进行处理,如果为NULL则新建(memb_alloc)一个邻居节点,将addr赋值给新建的邻居节点,然后将该节点添加到邻居链表中。之后是给这个邻居分配内存空间,执行数据发送。
packet_memb 定义
MEMB(packet_memb, struct rdc_buf_list, MAX_QUEUED_PACKETS);
语句q = memb_alloc(&packet_memb);
进行分配,然后将待发送数据地址放入邻居节点的rdc_buf_list中。
其中1
2
3
4
5
6/* List of packets to be sent by RDC layer */
struct rdc_buf_list {
struct rdc_buf_list *next;
struct queuebuf *buf;
void *ptr;
};内存分配:
- q->ptr = memb_alloc(&metadata_memb);
- q->buf = queuebuf_new_from_packetbuf(); 分配内存赋值:
- struct qbuf_metadata metadata = (struct qbuf_metadata )q->ptr;
- metadata->max_transmissions = CSMA_MAX_MAX_FRAME_RETRIES + 1;
- metadata->sent = sent;
- metadata->cptr = ptr; //—->
schedule_transmission
1 | list_add(n->queued_packet_list, q); |
1 | static void |
- transmit_packet_list
1 | static void |
7 queuebuf
默认的queuebuf数量是8,自定义数量少于8则使用swap,并使用ram of cfs,如果大于等于8则不使用swap。
数据结构、申请内存空间、接口函数如下:
1 | /* ignore the debug and swap variable */ |
8 RDC层
驱动与数据结构如下
1 | /* List of packets to be sent by RDC layer */ |
驱动分别在两个文件中都有定义nordc.c和nullrdc.h,其中nordc中的驱动函数均是空函数。所以来分析nullrdc中的函数,驱动如下:
1 | // in nullrdc.c |
8.1 send_list
send_list函数比较简单函数体如下:
1 | //NETSTACK_RDC.send_list(packet_sent, n, q); //----> |
8.2 send_one_packet
该函数实现了封包和发送两件事;
- NETSTACK_FRAME.create完成组包
- NETSTACK_RADIO.prepare将数据从packetbuf搬移到ieee-mode.c中定义的tx_buf
- NETSTACK_RADIO.send完成发送
8.3 NETSTACK_FRAME.create
1 |
|
执行create后会完成如下数据结构的内容:
fcf = Frame control field 帧控制
1 | typedef struct { |