本文将分析一个利用CC2530实现无线串口,文中将会列举部分代码并对CC2530的具体操作进行分析。本文的具体的内容包括以下几个部分
【代码仓库】代码仓库位于bitbucket,请使用Hg克隆或下载zip包,请勿使用任何版本的IE浏览器,除非您了解HTML4和HTML5对于浏览器的影响,推荐使用谷歌或火狐浏览器,若使用国产双核浏览器请切换到极速模式。
【视频链接】
为了实现无线串口功能,需要准备两套CC2530模块和一个仿真器。如果条件允许可以增加一个仿真器,仿真器可以是CC Debugger也可以是SmartRF04EB,同时也可以准备一套CC2531 USBDongle做为嗅探器,抓取RF发送数据做调试分析。
本文主要实现了无线串口功能,通过串口调试助手发送字节数据。例如通过串口向设备A发送Hello CC2530,设备B可收到Hello CC2530,并把该字符串通过串口调试助手打印至屏幕。设备B发送Hello RF,设备A同样可以收到数据并打印至屏幕。
图1.2.1(a-b) 设备A和设备B串口调试界面
图中中括号包含的数字为RSSI结果,RSSI表示接收信号强度,例如图中的-28。RSSI结果的单位为dBm,dBm为绝对单位且参考的标准为1mW。
RF部分的寄存器较多,需要耐心阅读数据手册和相关工具才可以完成设置。虽然RF部分的寄存器较多,但是还是借助smartRF工具、数据手册和示例代码,依然可以总结出使用CC2530无线部分的一般方法。
初始化部分包括接收数据包帧过滤控制,发射功率控制和信道选择;借助smartRF工具生成若干推荐值;打开接收终端并进入接收状态。
void rf_init() { FRMFILT0 =0x0C; //静止接收过滤,即接收所有数据包 TXPOWER =0xD5; //发射功率为1dBm FREQCTRL =0x0B; //选择通道11 CCACTRL0 =0xF8; //推荐值 smartRF软件生成 FSCAL1 = 0x00; TXFILTCFG =0x09; AGCCTRL1 = 0x15; AGCCTRL2 = 0xFE; TXFILTCFG =0x09; RFIRQM0 |=(1<<6); //使能RF数据包接收中断 IEN2 |=(1<<0); //使能RF中断 RFST =0xED; //清除RF接收缓冲区 ISFLUSHRX RFST =0xE3; // RF接收使能 ISRXON }
FRMFILT0的默认值为0x0D,该寄存器的最后一位为FRAME_FLITER_EN,该位的具体含义为使能帧过滤,该位在接收过程中发挥重要的作用。CC2530是符合802.15.4协议的RF芯片,在802.15.4协议中,MAC层有固定的协议格式和不同种类的命令,协议中包括命令类型、源地址和目标地址等重要信息,使能该位CC2530可以自动过滤不需要接收的无线数据帧,例如无线数据帧的目标地址和CC2530寄存器中的自身地址不符合,那么CC2530可以忽视该无线数据帧且不会触发中断。具体的过滤过程可以查看数据手册,在这里不详细解释。为了实现最简单的应用,禁止该位使得CC2530可以接收任意无线数据帧。
关于FRMCTRL0,本程序中保留了默认值所以并没有在代码中体现。从网上查找的代码中,绝大多数代码使能了AUTO_ACK标志位,即使得CC2530芯片可以自动应答无线数据帧。从表面上看,CC2530的应答机制可以保证无线通信的可靠性,但是这种应答机制需要遵循802.15.4标准,若使能自动应答,那么CC2530发送的数据包也必须符合802.15.4标准。为了尽可能的简单,本程序禁止了该位。
关于FRMCTRL0,AUTOCRC默认为使能状态,CC2530会自动进行CRC校验的计算和解析。
smartRF可以帮助用于生成若干比较冷门的寄存器的建议值,这些寄存器关系到CC2530无线调试和解调相关部分,在实际使用的过程中可以采纳建议值不做深究。
初始化最后,使能RF接收终端。并通过RFST寄存器写入清空接收缓冲区和进入接收状态命令。
void rf_send(char*pbuf,int len) { RFST =0xE3; // RF接收使能 ISRXON // 等待发送状态不活跃并且没有接收到SFD while( FSMSTAT1&((1<<1)|(1<<5))); RFIRQM0 &=~(1<<6); //禁止接收数据包中断 IEN2 &=~(1<<0); //清除RF全局中断 RFST =0xEE; //清除发送缓冲区 ISFLUSHTX RFIRQF1 =~(1<<1); //清除发送完成标志 // 填充缓冲区填充过程需要增加2字节,CRC校验自动填充 RFD = len+2; for(int i=0; i< len; i++) { RFD=*pbuf++; } RFST =0xE9; //发送数据包 ISTXON while(!(RFIRQF1&(1<<1))); //等待发送完成 RFIRQF1 =~(1<<1); //清除发送完成标志位 RFIRQM0 |=(1<<6); // RX接收中断 IEN2 |=(1<<0); }
发送过程本身不困难,大致可分为侦听SFD清除信道,关闭接收中断,填充缓冲区,启动发送并等待发送完成,最后恢复接收中断。在这几个过程中唯一需要说明的便是填充缓冲区过程,在初始化过程中提到FRMCTRL0寄存器,该寄存器中AUTO_CRC标志位默认为使能状态,阅读数据手册不难发现,CC2530的物理层负载部分第一个字节为长度域,填充实际负载之前需要先填充长度域,而物理层负载在原长度的基础上增加2。长度域数值增加2的原因是由于自动CRC的存在,CRC部分占两个字节CC2530会把这两个字节填充至发送缓冲区。
和发送部分略有不同,接收部分可以分为接收中断部分和接收数据帧处理部分。
#pragma vector=RF_VECTOR __interrupt void rf_isr(void) { EA =0; // 接收到一个完整的数据包 if(RFIRQF0&(1<<6)) { rf_receive_isr(); //调用接收中断处理函数 S1CON =0; //清除RF中断标志 RFIRQF0 &=~(1<<6); //清除RF接收完成数据包中断 } EA =1; } void rf_receive_isr() { int rf_rx_len=0; int rssi=0; char crc_ok=0; rf_rx_len = RFD-2; //长度去除两字节附加结果 rf_rx_len &=0x7F; for(int i=0; i< rf_rx_len; i++) { rf_rx_buf[i]= RFD; //连续读取接收缓冲区内容 } rssi = RFD-73; //读取RSSI结果 crc_ok = RFD; //读取CRC校验结果 BIT7 RFST =0xED; //清除接收缓冲区 if( crc_ok&0x80) { uart0_sendbuf( rf_rx_buf, rf_rx_len); //串口发送 printf("[%d]",rssi); } else { printf("\r\nCRC Error\r\n"); } }
无线接收部分可以分为两块内容,一块是无线接收中断处理,一块是无线数据帧处理。在前者中仅需查询标志位即可,RFIRQF0的第6位为完整数据包接收中断标志,若CC2530接收到一个完整的无线数据包,该标志位便会置位。由于CC2530存在多种RF中断类型,例如接收到一个完整的帧,帧通过过滤等,那么在进入中断服务函数之后可以通过查询标志位的方法进入相应的处理任务,接收过程中便是采用的这种方式。
进入数据包处理函数之后,首先读取接收缓冲区的第一个字节,第一个字为数据包长度,在这里需要减去2。长度域减去2的原理和发送过程相似,最后两个字节原为CRC校验,但是在CC2530处理过程中填充了更有用的信息,例如RSSI结果,而CRC校验只返回结果而不返回数值,CRC校验的结果只占用一位。
如果CRC校验成功,那么就依次读取接收缓冲区字节数据,通过串口发送这些字节数据,并附加一个RSSI结果且RSSI被中括号包围。如果CRC校验失败则通过串口打印CRCError。在程序刚开始调试的过程中,原先认为CRC校验结果是个“花架子”,但是实际中却发现当CC2530处于接收状态时,会不时地收到数据。这些数据杂乱无章,唯一的特征便是CRC校验结果错误。通过CRC校验结果可以有效的剔除数据,保证系统的可靠性。例如本文提供的程序,若CC2530一直处于接收状态,那么每隔半个小时便会出现一次CRC Error。
图4.2.1 CRC Error现象
串口部分的内容其实和RF部分无关,但是为了方便调试还是列举了该部分的代码。串口部分的代码包括定时器T1和UART两部分,UART中断中往接收缓冲区中填充数据并重新启动定时器,在定时器中断中指示串口数据接收完毕,改变一个软件标志位is_serial_receive。
void uart0_init() { PERCFG =0x00; // UART0选择位置0 [email protected] [email protected] P0SEL|=0x0C; // P0.3 P0.2选择外设功能 U0CSR|=0xC0; // UART模式接收器使能 U0GCR|=11; //查表获得 U0GCR和 U0BAUD U0BAUD =216; // 115200 UTX0IF =1; URX0IE =1; //使能接收中断 IEN0@BIT2 } void timer1_init() { T1CTL=0x0C; // @DIV分频系数 128 @MODE暂停运行 T1CCTL0 =0x44; // @IM通道0中断使能 @MODE比较匹配模式 T1STAT =0x00; //清除所有中断标志 T1IE =1; // IEN1@BIT1使能定时器1中断 T1CC0L =250; //溢出周期为2ms T1CC0H =0; } void timer1_disbale() { T1CTL&=~(1<<1); //恢复为停止模式 } void timer1_enable() { T1CTL|=(1<<1); //改变模式为比较匹配模式 MODE = 0x10; T1STAT =0x00; //清除中断标志位 T1CNTH =0; //重新开始计数 T1CNTL =0; } #pragma vector=URX0_VECTOR __interrupt void UART0_ISR(void) { URX0IF =0; //清除接收中断标志 serial_rxbuf[serial_rxpos]= U0DBUF; //填充缓冲区 serial_rxpos++; serial_rxlen++; timer1_enable(); //定时器重新开始计数 } #pragma vector=T1_VECTOR __interrupt void Timer1_ISR(void) { T1STAT &=~(1<<0); //清除定时器T1通道0中断标志 is_serial_receive =1; //串口数据到达 timer1_disbale(); }
大多数RF芯片都可以分为初始化,接收和发送这三个过程。而初始化过程可包括设置信道、功率、帧过滤等参数,由于RF芯片寄存器较多,可以通过官方的软件生成推荐值。发送过程可以采用等待方法,而接收过程往往使用中断方法。