引言
CC1101是TI公司推出的一款低于1 GHz的高性能射频收发器,设计旨在用于极低功耗RF应用,其主要针对工业、科研、医疗(ISM),以及短距离无线通信设备(SRD)。CC1101在各高校电子类实验室中得到了广泛的应用,在市场上也有良好的应用前景。目前,从网络资料及学术论文上来看,国内对于CC1101的应用有两方面的不足:第一,CC1101在各类低端单片机有着广泛应用,但是在嵌入式处理器上的应用却相对较少;第二,在通信信号不稳定的情况下,需要考虑到“面向连接”的通信模式,即需要保证通信质量的可靠性。本文较为详细地阐述了CC1101在ARM9处理器及嵌入式Linux系统下的开发过程,提出了一种简单可靠的主从无线通信协议模型。
1 面向连接的通信协议模型
在CC1101的通信过程中,因距离、障碍物等其他环境因素的影响,发送的数据并不一定能被确认接收,即可能出现丢包现象。因此,有必要设计一个简单的通信协议以保证CC1101通信过程的可靠性,避免丢包现象的出现。
在通信协议模型的设计上,借鉴了TCP协议“面向连接”的通信机制,简单地说就是在每次发送之后都需要等待一个应答,以保证本次发送数据的正确接收。
为了清晰地阐述“面向连接”的通信过程,将通信双方区分为server端与client端。在每次通信过程中,发送方为server,接收方为client,其主要通信过程如下:
① 在server端保存一全局变量seqno(sequence number),seqno的作用是标识发送的数据报文在server端的编号,在每次发送数据后seqno都要增加,同时区分报文为SEND与ACK两种状态,SEND标识本报文为正常发送报文,ACK标识为应答报文。
② server端发送数据给client时,server端将要发送的数据主体与对应的seqno组成一个数据报文,并标识报文状态为SEND,继而调用CC1101发送接口进行数据发送。
③ server端在发送后,启动一定时器,等待由client端回应的应答报文。
若client端接收到server端发送的报文,则client端会发送相应的应答报文给server端,client端应答报文中的seqno要等于从server端接收的seqno,并且报文状态标识为ACK。server端接收到应答报文后比较收到的seqno与自身的seqno是否相等,若相等,则说明本次发送成功,否则说明本次发送失败,进行数据报文的重发。
若client端未接收到server端的报文,client端不会发送应答报文给server端,server端在定时器超时前无法接收到应答报文,则说明本次发送失败,继而进行报文重发,在超过重发次数后,通知server端数据不可达。
④ client端的处理较为简单,在接收到server端发送的SEND报文后,检测其中的seqno,将其提取出来并构建ACK报文,直接发送回server端。
图1描述了server端面向连接发送的过程。
图1 面向连接通信模型
2 CC1101在单片机上的驱动
在芯片的控制上,CC1101芯片通过SPI总线的方式与外部控制器通信,这种简单的串行通信方式使得CC1101可以和大部分的MCU直接相连,即使在没有SPI控制器的MCU上也可以采取I/O口模拟SPI的方式来通信。而且从网络资料上看,大部分驱动CC1101芯片的代码也是采用通过I/O口模拟SPI的方式,这种I/O口模拟方式的优点在于屏蔽了不同类型MCU在SPI控制器设置上的不同,将驱动代码移植到各个MCU时,只需要按照I/O口的连接方式修改代码中SPI对应的接口信号SCK、MOSI、MISO和CSN。当然其缺点也很明显,SPI通信有一定的时序要求,在I/O模拟SPI时序时,不同MCU对应的驱动代码在时序模拟这部分需要适配。
除了4线SPI总线对应的SCK、MOSI、MISO和CSN引脚外,CC1101具有两个专用的可配置引脚(GDO0和GDO2),这些引脚可以用来对MCU产生中断。例如,当设置IOCFG0.GDO0_CFG=0x06时,接收/发送一个数据包,在RX和TX模式下GDO0引脚会分别产生下降沿电平信号,可作为MCU的外部中断。
一个典型的CC1101与MCU连接的方式如图2所示。
图2 CC1101与MCU连接典型电路
3 嵌入式Linux下CC1101的驱动
在嵌入式Linux系统的硬件平台上,选择的是三星公司的S3C2440/ARM9处理器,这款处理器在高校实验室有着广泛的应用;嵌入式Linux内核版本采用的是稳定的2.6版本。CC1101与S3C2440的连接方式如图3所示,本文采用的是S3C2440的GPIO模拟SPI时序的方法,CC1101的GDO0作为外部中断引脚连接至S3C2440的EINT15引脚。
图3 CC1101与S3C2440的连接
Linux下的设备可以分为字符设备、块设备和网络设备,在实际应用中CC1101一般只需要提供发送与接收接口,并且可以按照字节流读取,所以本文将CC1101归类为字符设备。
字符设备开发过程中最主要的数据结构为fop(file_operation)结构,它包括了一系列文件操作接口。和CC1101字符设备开发相关的主要部分包括CC1101的初始化、CC1101的发送/接收接口,以及CC1101各种状态寄存器修改接口等。需要实现上述fop结构中的接口包括:
① open:设置SCK、MOSI、CSN、GDO2对应的GPIO为输出方式,MISO对应GPIO为输入方式,GDO0对应的引脚为外部中断(EINT15)方式,完成通过GPIO模拟SPI初始化CC1101的工作,申请外部中断资源,由于采用了中断方式,在CC1101接收到数据时触发中断,在中断程序中进行CC1101接收缓存数据的读取,设置可读取标志位rcv_flag。
② write:CC1101的发送接口,用户态应用程序调用write系统,调用发送数据。
③ read:无阻塞地读取CC1101接收到的数据,重置可读取标志位。
④ poll:poll函数是为了配合用户态的select系统调用,只有在上述可读取标志位被设置的情况下,返回POLLIN。
⑤ ioctl:用于设置CC1101的其他状态,设置其内部寄存器。
⑥ release:释放中断资源,关闭字符设备。
4 用户态应用程序编写
在上述CC1101内核驱动完成之后,即可以在用户态程序中打开设备并使用它。为了实现第一部分介绍的面向连接通信过程cc1101_link_send,在每次发送数据之后,都要启动一个定时器,等待由对端发送而来的ACK报文,具体在Linux用户态程序中采用了select系统调用。此系统最终调用内核态fop中的poll函数,select函数原型为:
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
readfds表示读文件集。在timeout时间段内,若readfds读文件集没有变化,则select函数超时;readfds读文件集有变化则表示CC1101收到数据,此时再调用read系统函数读取CC1101接收的数据,比较seqno和报文状态即可以完成上述的面向连接通信机制。
面向连接的发送接口cc1101_link_send部分代码如下:
global_seqno++;/*发送之前,将全局seqno增加 */
if (0xffff == global_seqno)
global_seqno = 0;
frame_send->seqno = global_seqno;/*设置seqno与state */
frame_send->state = SEND;
ret = write(fd, frame_send, send_len);/*用户态调用write进行发送 */
if (send_len != ret){
printf("cc1101_link_send: send error!\\n");
return -1;
}
图4 用户态应用程序
printf("cc1101_link_send......\\n");
printf_cc1101_frame(frame_send);
FD_ZERO(&rfds);
FD_SET(fd, &rfds);
select(fd + 1, &rfds, NULL, NULL, &timeout);
/*在timeout时间内测试CC1101是否可读 */
if (FD_ISSET(fd, &rfds)){
ret = read(fd, rcv_buf, CC1101_BUFLEN);
if (CC1101_BUFLEN == ret){/*检测收到数据中的seqno*/
if (( global_seqno == frame_rcv->seqno) &&
(ACK == frame_rcv->state)){/*检测state是否为ACK*/
printf("cc1101 received:\\n");
printf_cc1101_frame(frame_rcv);
printf("cc1101_link_send: success!\\n\\n");
return ret;
}
else
return -1;
}
else{
printf("cc1101 read timeout!\\n");
return -1;
}//当定时器超时后,则需要进行报文的重发,这里只用简单的循环发送即可:
for (i = 0; i < CC1101_RETRY_CNT; i++){
ret = cc1101_link_send((unsigned char *)&cc1100_send_buf, CC1101_BUFLEN, fd, timeout);
if (0 > ret)
printf("cc1101_link_send: error, cnt:%d!\\n", i + 1);
else
break;
}
上述程序只是简单地实现了前述的面向连接通信机制,当然,用户可以参照现有的TCP/IP、ZigBee等成熟的通信协议,在应用程序层定制简单实用的通信协议,定义与修改C1101通信的报文格式,如增加地址、命令字等,以完成其他更加丰富的功能。
5 系统测试
将CC1101内核驱动编译成module模块加载至嵌入式Linux系统中,并运行用户态应用程序,图4(a)左侧显示了发送超时的情形,可见在发送超时后会继续发送直至最大发送次数,图4(b)则显示了正常的面向连接发送过程。经验证,在面向连接的通信模型下CC1101可保证通信质量。 41
结语
为了解决CC1101通信质量不稳定的问题,提出了一种面向连接的通信模型。在嵌入式Linux系统下开发了CC1101的驱动,并基于上述的通信模型编写了应用程序,验证了驱动及应用程序的正确性。本文在一定程度上弥补了关于嵌入式Linux系统下应用CC1101资料不丰富的不足,也为CC1101的无线通信协议开发提供参考。