查看: 54769|回复: 58

[原创]奥特曼Zigbee读书日记(八)--freakz移植之物理层

[复制链接]
outman 发表于 2010-10-7 14:39:20 | 显示全部楼层 |阅读模式
  Freakz是什么?Contiki又是什么?这和我们现在学习的Zigbee有什么关系?也许看到这篇文章的读者有不少对此还一无所知。如果您在学习Zigbee,相信对TI的CC系列IC应该有所了解吧,那您肯定听过ZStack和OSAL这两个词,ZStack是TI公司按照Zigbee标准实现的一个Zigbee协议栈,而OSAL则为ZStack所使用的“操作系统”。如果这两个概念搞得清楚,相信freakz和contiki就不难理解了。其实,freakz也是一个zigbee协议栈,相应的contiki则为freakz所使用的操作系统。

  [注:本文源自www.feibit.com--“飞比”Zigbee论坛,为尊重劳动者成果,如需转载请保留此行,并通知作者]

  那我们为什么不直接用ZStack+OSAL,而费这么大的劲用freakz+contiki呢?其实原因很简单,不同于ZStack的“应用层开源”,freakz是一个彻底的开源zigbee协议,而contiki也是一个彻底的开源操作系统,而且这个操作系统短小精悍,非常适合“物联网”时代的MINI型设备,同时,这套系统在全球已经拥有了众多的支持与使用者,已经开发了非常多的应用,甚至有像IPV6这么强大而且的应用,可以在其官方网站上下载到全套的代码!

  相对于另外一个开源的WSN(Wireless Sensor Networking,无线传感网)操作系统TinyOS来讲,contiki的代码全部为C语言写成,用GCC进行编译,对广大应用C语言多年的开发者来说,减少了学习另外一种语言与编译平台所带来的时间花费。同时,我们考虑到很多开发者,尤其是初学者,对Linux+GCC的平台也并不熟悉,所以,我们选用了IAR这个极其稳定、易用的编译平台,对contiki进行移植。您可以在其官方网站上下载试用版进行学习,对于商业用途的开发,请您购买正版。

  至于我们为什么最终选择了freakz而不是别的开源协议,主要是因为这是目前我们找得到的相对最完整的,也是代码写得比较规范的一套协议。不过,为什么我们总得用别人的?这个问题确实值得思考!!

  不扯远了,回到主题上来。目前这个项目,断断续续已经做了将近两个月了,采用的思路是,从操作系统的核心到周边,再到基本的物理层和MAC层通讯一点点地进行修改和编译,理解一点就修改一点,成功后再继续下一步。到现在为止,已经实现了节点1应用层发送一个字符串“feibit”,MAC层按照协议规范进行编码,由物理层发送出去;节点2收到信息后,完成上述的反过程,直至收到字符串“feibit”,而且整个过程是在contiki系统框架内完成的。

  这个时候,我在犹豫一个问题。实际上,freakz已经用“软件模拟器”实现了MAC层以上的协议内容,并且在一个基于AVR的硬件平台上进行了移植。理论上讲,现在我们已经在FB2530EB的平台上基本实现了CC2530的MAC层通讯,与上层对接后实现协议栈的基本功能应该不是太难了。但反复考虑后,我觉得这不是一个好的方法。也许现在一股脑地把所有的代码都加进来,解决一些warning与error后,我可以骄傲地跟人讲,我实现了开源zigbee移植。但这并不是我的目的。我希望论坛有一个“开放”跟“踏实”的氛围,也希望自己跟论坛里的每一位学习者有一个不急功近利的心态。我们学习的目的不是跟随,而是创造!

  在freakz移植的过程中,我有一个感受,虽然我们做的只是“移植”而不是原创,但这种学习方式比由上而下的方式来得有效,并且深入得多。当然,让一个完全对zigbee的基本概念与功能完全不懂的初学者来说,这可能也不是个最好的方法。所以,我觉得这两种方式应该结合起来,“what”与“how”这两个问题采用由上而下的方式,而“why”这个问题,就采用由下而上,从零搭建的方式,当然前提要是开源!而且我希望后面的事情,不是由我来写,大家来读,而是希望有更多的人能参与进来。哪怕我把整个协议栈重新写了一遍,对于读者来讲,并不一定能对其中的重点内容有深刻的体会。而且,如果能有更多的人参与,大家共同讨论,共同进步,这样的学习效率是单兵作战所无法比拟的!

  所以,在FB2530EB开发板发行之前,移植的工作准备暂时告一段落。我会把这几天物理层与MAC层移植的相关内容,以笔记的形式写出来,作为一个起点。有兴趣的朋友可以在此基础上继续一点点地理解,一点点地移植。同时也希望我们能有组织、有规划地去实现一个共同的目标!
 楼主| outman 发表于 2010-10-7 14:42:39 | 显示全部楼层
在前面的笔记(五)中,我们已经对物理层的基本概念做了详细的介绍。本篇可作为其理论知识的实践篇,从一个基本的字符串“feibit”的发送与接收来学习CC2530的物理层收发功能,看看这个物理层“小弟”到底是怎么干活的。

  另外,要说明一点,zigbee本身是一个很复杂的协议,其中任何一层,如果要将每一个细节都讲清楚,都会是很庞大的工作。本文的侧重点在于基本概念的理解,对于IC与Zigbee协议的细节请参考其规格书。

  [注:本文源自www.feibit.com--“飞比”Zigbee论坛,为尊重劳动者成果,如需转载请保留此行,并通知作者]

  IC的应用并不是一件简单的事情,因为涉及到了很多IC的细节,如果没有详细的说明及应用例程,这些细节有时甚至是无从得知的。还好,相比其他家的无线IC,TI的资料与例程相对丰富,这也是我们选择TI作为开始的主要原因之一。本文中涉及到CC2530的应用部分,有很多是源于TI提供的BasicRF平台的,因为这也是一个开源的平台,方便我们进行移植。

  要实现数据的收和发,最早想到的自然是IC的收发机制与相应寄存器的设置。走个捷径,我们从BasicRF的代码入手,了解CC2530的RADIO部分的初始化与读取代码。

  首先是初始化的过程,这是保证IC在我们所需要的状态下正常工作的前提。从BasicRF的per_test例程中,我们可以看到,这部分工作主要在以下两个函数中进行,halRfInit与basicRfInit。我们总结下其中与硬件相关的初始化工作如下:

  1、设置MCU的晶振频率,BasicRF下所用的函数:halMcuInit
  这个工作在开始的时候容易被忽视,设置如果不对,发送的数据包可能是乱的,也可能会有些莫名其妙的问题。

  2、设置数据数据传输所使用的频道,halRfSetChannel
  这里我们选择频道11,则为2.405G

  3、设备的PANID号,halRfSetPanId,我们指定为0x2007

  4、设备的短地址,halRfSetShortAddr
  由于我们暂时先不涉及网络层组网及地址安排等问题,仿照BasicRF,发送与接收端分别指定如下:
  #define TX_ADDR               0x2520
  #define RX_ADDR               0xBEEF

  5、对于发送端来说,需要增加一个发射功率的设置,halRfSetTxPower
  我们按照无PA模式的最大功率4dB进行设置,其实际值为2。

  在系统的初始化阶段,执行以上过程,则将CC2530置于准备状态下,随时可用于收发。首先我们来看下数据的发送:

  从BasicRF的basicRfSendPacket函数中,我们可以总结出如下的发送流程:
  1.         halRfReceiveOn();
  2.         // Wait until the transceiver is idle
  3.         halRfWaitTransceiverReady();
  4.         // Turn off RX frame done interrupt to avoid interference on the SPI interface
  5.         halRfDisableRxInterrupt();
  6.         // write frame to buffer
  7.         halRfWriteTxBuf(buf->dptr, buf->len);
  8.         // Turn on RX frame done interrupt for ACK reception
  9.         halRfEnableRxInterrupt();
  10.         // Send frame with CCA. return FAILED if not successful
  11.         while (halRfTransmit() != SUCCESS);
  12.         halRfReceiveOff();
复制代码
注:为突出基本原理的理解,我会经常把一段代码中的关键部分提取出来进行分析,因为我希望把复杂的事情变得简单,而不是相反!

  有兴趣的读者,可以将以上的函数逐一展开,参照CC2530 datasheet进行详细了解。此处我们只关注其数据的发送函数halRfWriteTxBuf

  1. /*************************************************************************
  2. * @fn      halRfWriteTxBuf
  3. *
  4. * @brief   Write to TX buffer
  5. *
  6. * @param   uint8* pData - buffer to write
  7. *          uint8 length - number of bytes
  8. *
  9. * @return  none
  10. */
  11. void halRfWriteTxBuf(uint8* pData, uint8 length)
  12. {
  13.     uint8 i;

  14.     ISFLUSHTX();          // Making sure that the TX FIFO is empty.

  15.     RFIRQF1 = ~IRQ_TXDONE;   // Clear TX done interrupt

  16.     // Insert data
  17.     for(i=0;i<length;i++){
  18.         RFD = pData[ i ];
  19.     }
  20. }
复制代码
从中我们可以明显地看出,要发送的数据pData,在此函数中,通过逐个写进RFD的方式发送出去了。这不正是我们苦苦寻找的物理层“小弟”吗?不就是他一个一个字节地让“比特”飞翔了起来??

  按照我们现在的理解试试能不能把数据发送出去:
  1.   uint8 test_str[6]="feibit";
  2.   halRfWriteTxBuf(test_str, 6);
复制代码
难道就这么简单?小心翼翼地编译、下载,在IAR下单步进行跟踪,程序顺利地执行了halRfWriteTxBuf(test_str, 6);这一句。但不要高兴地太早,程序在执行到下面,halRfTransmit()这个函数的时候,进了死循环,一直出不来了。这是怎么回事?

  我们看一下到底这个函数在做什么?
  1. /************************************************************************
  2. * @fn      halRfTransmit
  3. *
  4. * @brief   Transmit frame with Clear Channel Assessment.
  5. *
  6. * @param   none
  7. *
  8. * @return  uint8 - SUCCESS or FAILED
  9. */
  10. uint8 halRfTransmit(void)
  11. {
  12.     uint8 status;

  13.     ISTXON(); // Sending

  14.     // Waiting for transmission to finish
  15.     while(!(RFIRQF1 & IRQ_TXDONE) );

  16.     RFIRQF1 = ~IRQ_TXDONE;
  17.     status= SUCCESS;

  18.     return status;
  19. }
复制代码
程序明显死在了while(!(RFIRQF1 & IRQ_TXDONE) ),这句话是在等什么呢?看datasheet!



  很明显,其中的TXDONE位是用来记录发送有没有完成的,如果完成了,则返回一个中断标志,在收到此标志后,程序才会继续。可是我们已经让它发送“feibit”这个字符串了,怎么会一直发不完呢??看来让这个“小弟”干活也不是件容易事。。。

  做什么事都不能太急功近利!回顾下笔记(五)中所讲的理论知识,翻一下datasheet,再比较下basicRF中所发送的数据与我们的数据的不同,从中不难看出端倪。

  还记得笔记(五)中提到的“付费”信息--payload和“免费赠送”的概念吗?虽然物理层小弟是最底层的工人,但是我们只想传送payload(即字符串“feibit”)也是不行的,必须也要带上赠送部分,这虽然是附加信息,但却是非常重要的。没有了这个,甚至IC连发送完成的中断都不产生了!

  复习下802.15.4中的物理层所规定的数据桢结构:



  从中我们可以看出,除了PHY Payload外,还有一个很重要的PHR(PHY层头信息),其中包含了物理桢长度信息—Frame Length。

  按照这个思路,我们再改一下发送的数据:
  uint8 test_str[7]={8,’f’,’e’,’i', ‘b’, ‘i', ‘t’}; //在其头部增加payload长度值8。
  halRfWriteTxBuf(test_str, 9); //发送数据长度也相应增加一个。

  重新下载程序,发现可以产生发送完成中断了。是不是这样就意味着数据已经发送到了空中呢?我们怎么来验证有没有成功发送呢?TI为我们提供了很好的一个工具—Packet Sniffer!有人翻译为“协议分析仪”,从字面理解也可以叫“数据包嗅探器”,其软件可以在TI的官方网站上下载,专用的硬件网上也可以买得到。不过,我们即将推出的FB2530EB+CC Debugger其实通过简单的跳线及软件设置即可实现packet sniffer的全部功能。其操作详见“CC2530-MDK中文使用说明书”,在此不做赘述。

  打开Packet Sniffer后,在“select protocol and chip type”中选择“Generic”。点击开始,同时启动发送端的数据发送,此时在Packet Sniffer中可收到如下数据:



  其中的黄色“Payload”部分为“66 65 69 62 69 74”,这实际上就是“feibit”这个字符串的ASIC值。至此,发送的核心部分已经完成。从上面的问题中,我们也可以对CC2530的发送机制有个了解,“发送完成”的中断,是在发送完成了数据桢第一个字节所指定的数量后产生的,这也不难理解,我们之前为什么进了死循环了,因为我们的第一个数据是"f",即0x66,而实际上我们只发送了6个字符出去,当然没有办法产生中断了!这也让我们知道了一点,实际上CC2530从硬件设计上已经支持了802.15.4的协议标准!


  附上成功发送的源代码,供大家学习之用:


另外,移植中两个关键步骤的代码:
最小化的contiki操作系统代码:


成功控制CC2530外设的代码:

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
ygz20036 发表于 2010-10-7 22:15:26 | 显示全部楼层
kankan diyige1
 楼主| outman 发表于 2010-10-8 00:44:37 | 显示全部楼层
楼上抢先了,接下来数据的接收原理

  上文完成了一个任务,将带着物理层信息的字符串“feibit”,变成一连串的比特流,通过CC2530的调制电路,将其坐上了2.405GHz的电磁波在空中传播。但电磁波的能量在传播到一定距离之后会随之消失,如果没有下面要介绍的接收部分工作,那我们前面的事情等于白做了,而且消耗了能量,不利于国家“节能减排”目标的实现~

  当电磁波遇到了CC2530RF模块的天线时,这种情况就改观了。由磁生电,汇集到解调电路后,又恢复了我们发射的那串比例流。于是我们后面的故事就开始了…

[注:本文源自www.feibit.com--“飞比”Zigbee论坛,为尊重劳动者成果,如需转载请保留此行,并通知作者]

  这个时候首先产生了一个“中断”信号,也许CC2530正在“睡觉”,但是马上醒了过来,进入了ISR(中断服务程序)。从basicRF的“basicRfRxFrmDoneIsr”函数中,我们来分析一下,第一步要做的事情:
  1. static void basicRfRxFrmDoneIsr(void)
  2. {
  3.     basicRfPktHdr_t *pHdr;

  4.     // 读取payload长度
  5.     halRfReadRxBuf(&pHdr->packetLength,1);
  6.     pHdr->packetLength &= BASIC_RF_PLD_LEN_MASK; // Ignore MSB

  7.     // 判断是否是ACK数据包,只有ACK数据包的长度为5
  8.     if (pHdr->packetLength == BASIC_RF_ACK_PACKET_SIZE) {

  9.    … …

  10.     } else {
  11.     // 如果不是ACK,则为普通数据包

  12.         // 读取数据包内容
  13.         rxi.length = pHdr->packetLength - BASIC_RF_PACKET_OVERHEAD_SIZE;
  14.         halRfReadRxBuf(&rxMpdu[1], pHdr->packetLength);

  15.         … …       
  16.      }
复制代码
像以前的教程一样,我们暂时不涉及太多的细节,而侧重于基本概念的理解。ACK部分先不管,我们可以理解为一个有特殊意义的数据包而已。

  在接收数据之前,首先要把“容器”准备好,在freakz里,物理层数据的接收格式定义如下:
  1. typedef struct _buffer_t
  2. {
  3.     struct      _buffer_t *next;               //数据链表中的下一个指针
  4.     bool        alloc;                       
  5.     U8          *dptr;                      //实际数据块的存放地址
  6.     U8          len;                        //数据块长度
  7.     U8          lqi;                         //连接质量指示LQI
  8.     U8          index;                     
  9.     U8          buf[aMaxPHYPacketSize+1];     //实际数据块数组
  10. } buffer_t;
复制代码
由于在发送部分,我们只是发了一个“feibit”字符串而已,我们暂时先不关注太多,只关注长度len和存放数据块的数组buf[]

  结合freakz的要求,我们可以对接收中断RF_VECTOR的ISR进行如下更改:
  1. ISR(RADIO_VECT)
  2. {
  3.     buffer_t *buf;
  4.     U8 len, dummy, *rx_data=0;

  5.     //申请数据接收的buffer空间
  6.     BUF_ALLOC(buf, RX);
  7.                        
  8.     // 读取payload长度
  9.     halRfReadRxBuf(&len,1);
  10.     len &= BASIC_RF_PLD_LEN_MASK; // Ignore MSB
  11.    
  12.     // 实际数据块的存放地址位于数组buf[127]的最后部分,
  13. // 最后的两个字节为rssi值,不进行保存
  14.     buf->dptr = &buf->buf[aMaxPHYPacketSize - (len - 2)];
  15.    
  16.     // 确保接收到的数据在buffer的长度内,防止溢出
  17.     if ((len >= HAL_MIN_FRAME_LENGTH) && (len <= HAL_MAX_FRAME_LENGTH))
  18.     {
  19.         rx_data     = buf->dptr;
  20.         buf->len    = len - 2;
  21.         // 接收到的长度值减二为要保存的数据长度
  22.         len -= 2;
  23.         // 从FIFO寄存器中读取数据包
  24.         halRfReadRxBuf(rx_data, len);
  25.         // 丢弃最后两个字节的内容
  26.         halRfReadRxBuf(&dummy, 1);
  27.         halRfReadRxBuf(&dummy, 1);
  28.     }
  29.     else
  30.     {
  31.         buf->len = 0;
  32.         buf->lqi = 0;
  33.     }

  34.     //将接收到的物理层数据向MAC层传递。
  35.     mac_queue_buf_insert(buf);
  36.     //通知MAC层,进行相应的事件处理
  37.     drvr_set_data_rx_flag(TRUE);
复制代码
至此,物理层即完成其使命,完成了数据的接收,并将其传递至上级部门。由于本文的重点是数据的收发机制,读者如果自行进行移植时,可能会遇到contiki系统下,添加中断、编写线程等相关问题,此部分以及MAC层以上的通信的说明将在后续的文档中一一进行解读。
leewei 发表于 2010-10-8 10:04:59 | 显示全部楼层
outman大作啊!必须顶的!
顺便补充一点,while(!(RFIRQF1 & IRQ_TXDONE) ),这里是查询方式发送,可以打开中断屏蔽码,以中断方式实现发送,具体查看一下RFIRQM1这个寄存器,同样接收也是一个道理的
kennan 发表于 2010-10-8 10:16:11 | 显示全部楼层
必须顶。期待尽快发布啊
rlz1981 发表于 2010-10-8 10:40:59 | 显示全部楼层
outman又出大作了,受教了。我什么才能理解到这个份上……!要加倍努力……
catzl7 发表于 2010-10-8 11:03:18 | 显示全部楼层
outman...强悍!
wuxiujiang 发表于 2010-10-8 11:32:02 | 显示全部楼层
支持起!!!!
 楼主| outman 发表于 2010-10-8 11:39:36 | 显示全部楼层
outman大作啊!必须顶的!
顺便补充一点,while(!(RFIRQF1 & IRQ_TXDONE) ),这里是查询方式发送,可以打开 ...
leewei 发表于 2010-10-8 10:04



看了下leewei提到的“RFIRQM1”寄存器与“RFIRQF1 ”的区别,实际上前者是中断的“使能”位(mask),而后者是“标志”位(flag)。查看是否收到数据是通过“RFIRQF1 ”,而前提是“RFIRQM1”必须要打开
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

快速回复 返回顶部 返回列表