织梦CMS - 轻松建站从此开始!

罗索实验室

CC3000驱动移植中的几个陷阱

落鹤生 发布于 2013-10-18 09:18 点击:次 
目前为止,我见过的CC3000的驱动实现中,不论是开源的、产品正在开发而尚未开源的实现,除了官方 的例程以外,还没有人将这两组函数写对。甚至有人能通过降低SPI速率、修改IO中断方式等等办法,使得整个程序得以正常运行,将错误带到了产品中。
TAG:

1. 文档中的一个小错误

CC3000驱动移植中的几个大坑 - 月下独酌 - The Bloom of Youth
 
wlan_ioctl_get_scan_results 函数用于返回WLAN扫描的结果,每调用一次返回一个结果。返回的数据结构在文档中给出,但是这里文档写错了。。。Result entry前的56bytes应该是42bytes,而每次的结果总有有4+4+42=50(bytes)。把下面的结果加起来,也会发现各项的和是42 而不是56. 这本来只是手册上的一个笔误,但在编程时,大家通常会使用一个结构体来接收返回的数据,如果这个字节数不对的话,就会影响内存对齐,从而导致返回的结果错误。这里,我使用的结构体定义如下:
  1. typedef struct _wlan_full_scan_results_args_t 
  2.     /* 
  3.         4 Bytes: number of networks found 
  4.         4 Bytes: The status of the scan: 0 - agged results, 1 - results valid, 2 - no results 
  5.         { 
  6.             1 bit isValid - is result valid or not 
  7.             7 bits rssi - RSSI value; 
  8.         } 
  9.         { 
  10.             2 bits: securityMode - security mode of the AP: 0 - Open, 1 - WEP, 2 WPA, 3 WPA2 
  11.             6 bits: SSID name length 
  12.         } 
  13.         2 bytes: the time at which the entry has entered into scans result table 
  14.         32 bytes: SSID name 
  15.         6 bytes: BSSID 
  16.     */ 
  17.     uint32_t ap_count; 
  18.     uint32_t ap_state; 
  19.     uint32_t ap_vaild    : 1; 
  20.     uint32_t ap_rssi     : 7; 
  21.     uint32_t ap_security : 2; 
  22.     uint32_t ap_ssidlen  : 6; 
  23.     uint16_t ap_time; 
  24.     char     ap_ssid[32]; 
  25.     uint8_t  ap_bssid[6]; 
  26.     uint8_t  reserved[2]; //for memory align 
  27. } wlan_scan_result; 
另外,这个函数读到最后一条结果时,再读会返回一个长度为0的结果,即前四个字节(number of networks found)为零,然后再读才会读出下一次扫描的结果。这一点手册上并没有指出,是我们在编程实践中自己发现的。

 

2. SpiPause - SpiResume与WlanInterruptDisable - WlanInterruptEnable的实现
如 果前面一条是一个小坑的话,这一条绝对是一个大坑。目前为止,我见过的CC3000的驱动实现中,不论是开源的、产品正在开发而尚未开源的实现,除了官方 的例程以外,还没有人将这两组函数写对。甚至有人能通过降低SPI速率、修改IO中断方式等等办法,使得整个程序得以正常运行,将错误带到了产品中。只要 有人说:“我的CC3000驱动有点问题,调了很久都不行。。。”之类的话,不用等他说完,就可以猜想他是把这两组函数写错了。
 
这两组函数非常容易错,错了又非常难以发现。这两组函数的实现已经成为了CC3000驱动移植的主要障碍。其主要原因如下:
(1)是官方的Porting Guide没有明确指出这两组函数的区别。很多人则想当然地认为这两组函数是一样的。甚至是一组中调用另一组。再看官方的实现,又有点不知所云,所以就忽略了这个问题。
(2) 忽略这个问题后,CC3000模块可以正常初始化,还可以正常扫描WiFi热点,有人还能正常进行Smart config,甚至有人还能正常打开socket并使用UDP协议发送数据!开发者根据CC3000驱动自下而上分层的架构来看,看到SPI已经可以进行 正常通信,从而直接在心理上排除了SPI驱动实现有误的可能性,错误查来查去还是在原地兜圈子。
(3)由于这两组函数实现有误导致的错误几乎成了一个人品问题,在不同速度MCU上的错误实现会导致不同的错误现象,以至于大家的描述不统一,很难从现象判断到底是哪儿错了,即使在网上根据错误现象搜索或者发帖求助,也很难得到有针对性的答案。
 
为什么这两组函数写错了,会有这么神奇的问题呢?答案就是,这里的错误会导致程序的“竞态问题”,类似于数字电路基础中的“竞争冒险”。SPI速率、AP的品质和信号强度、IO口翻转速度、延时的误差等无关紧要的问题,都可能让竞态问题出现不同的现象。因此,出现的错误现象千奇百怪,貌似跟人品有关就不难解释了。说了这么多,下面就来解释一下这两组函数到底有什么区别:
 
WlanInterruptDisable的作用是关闭IO口的外部中断,也就是IRQ的下降沿中断。关闭以后,IRQ下降沿的中断将被丢弃,在调用WlanInterruptEnable时,会重新使能中断,之后发生在该IO上的外部中断又再次可以响应。而中断被禁用期间,发生的中断都被丢弃,永远不再响应。这个函数一般人都写对了。
 
SpiPauseSpi函数是暂时挂起IO口外部中断,也就是IRQ下降沿中断,如果在SpiPauseSpi调用之后,IRQ线上再有下降沿,该中断将一直保持Pending状态,暂时不调用中断服务程序,该中断必须在调用SpiResumeSpi被重新响应,而不能被丢弃。这里,很多很多很多人,都,写错了。。。
 
       这种中断的响应方式比较特殊,像STM32就没有在标准驱动库中实现相应的API,而必须通过操作寄存器的方式来手工实现。相应寄存器的定义,既不在芯 片Datasheet里,也不在那份大家熟知的STM32F1系列Reference Manual(RM0008)里,而是在很多STM32开发者都没有听说过的一份文档PM0056——STM32F10xxx/20xxx/21xxx/L1xxxx Cortex-M3 programming manual里。有很多MCU,甚至包括TI的MSP430,在硬件上根本就不支持这种响应方式,因此只能用一些猥琐的方法来实现。MSP430该函数的代码居然是向一个配置为输入的IO口写数据,足以让不熟悉MSP430的开发者挠头了。
 
STM32F103上的实现如下
  1. void SpiPauseSpi(void
  2. //    if(IRQ_INT_CHANNEL >= 0 && IRQ_INT_CHANNEL <= 31) 
  3. //    { 
  4. //        //NVIC->ICER[0] = (1 << IRQ_INT_CHANNEL); 
  5. //    } 
  6. //    else if(IRQ_INT_CHANNEL >= 32 && IRQ_INT_CHANNEL <= 63) 
  7. //    { 
  8.     NVIC->ICER[1] = (1 << (IRQ_INT_CHANNEL - 32)); 
  9. //    } 
  10. //    else if(IRQ_INT_CHANNEL >= 64 && IRQ_INT_CHANNEL <= 67) 
  11. //    { 
  12. //        //NVIC->ICER[2] = (1 << (IRQ_INT_CHANNEL - 64)); 
  13. //    } 
  14. //    else 
  15. //    { 
  16. //        while(1); //Should not be here. Macro IRQ_INT_CHANNEL might be invalid. 
  17. //    } 
  18.  
  19. void SpiResumeSpi(void
  20. //    if(IRQ_INT_CHANNEL >= 0 && IRQ_INT_CHANNEL <= 31) 
  21. //    { 
  22. //        //NVIC->ISER[0] = (1 << IRQ_INT_CHANNEL); 
  23. //    } 
  24. //    else if(IRQ_INT_CHANNEL >= 32 && IRQ_INT_CHANNEL <= 63) 
  25. //    { 
  26.     NVIC->ISER[1] = (1 << (IRQ_INT_CHANNEL - 32)); 
  27. //    } 
  28. //    else if(IRQ_INT_CHANNEL >= 64 && IRQ_INT_CHANNEL <= 67) 
  29. //    { 
  30. //        //NVIC->ISER[2] = (1 << (IRQ_INT_CHANNEL - 64)); 
  31. //    } 
  32. //    else 
  33. //    { 
  34. //        while(1); //Should not be here. Macro IRQ_INT_CHANNEL might be invalid. 
  35. //    } 
另外一组的实现是
  1. void WlanInterruptEnable() 
  2.     EXTI_ClearITPendingBit(IRQ_INT_LINE); 
  3.     EXTI_InitTypeDef exti; 
  4.     exti.EXTI_Line = IRQ_INT_LINE; 
  5.     exti.EXTI_Mode = EXTI_Mode_Interrupt; 
  6.     exti.EXTI_Trigger = EXTI_Trigger_Falling; 
  7.     exti.EXTI_LineCmd = ENABLE; 
  8.     EXTI_Init(&exti); 
  9.  
  10. void WlanInterruptDisable() 
  11.     EXTI_ClearITPendingBit(IRQ_INT_LINE); 
  12.     EXTI_InitTypeDef exti; 
  13.     exti.EXTI_Line = IRQ_INT_LINE; 
  14.     exti.EXTI_Mode = EXTI_Mode_Interrupt; 
  15.     exti.EXTI_Trigger = EXTI_Trigger_Falling; 
  16.     exti.EXTI_LineCmd = DISABLE; 
  17.     EXTI_Init(&exti); 
3. TI提供的CC3000 Host Driver假设char类型是无符号的
这个问题可能很多人都不会遇到,因为在嵌入式开发中char通常都是无符号的,基本所有的用于嵌入式平台的编译器默认情况下都会这样设定。而我则比较习惯PC上有符号的char,所以我手贱给GCC加了一个编译参数-fsigned-char......
TI 的Host Driver就比较扯淡,它假设char是无符号的,并且在char有符号时就会因符号位在强制转换中的行为不同而出错。这个问题不容易排查,因为需要跟 踪到TI实现的Host Driver里,而大家通常都会假设这个实现是没错的,能跟进去就是一种勇气了……我当时遇到这个问题时,不知道是哪儿的错误,就在很多回调函数中添加了 串口打印的语句,通过串口输出,观察各个函数被调用的情况,结合现象,再加以大胆的猜想,才解决了这个问题。
 
4. 供电
CC3000驱动移植中的几个大坑 - 月下独酌 - The Bloom of Youth
 
这 个是一个硬件问题,一个不应该犯的错误。目前大家用的最多的CC3000模块还是Jorjin的WG1300,而Jorjin的文档中并未给出该模块工作 时所需的电流。在一些追求体积最小化的应用中,大家也普遍使用了SOT23-5封装的小型LDO,这类LDO的电流通常只有100mA左右。一个单片机才 几十毫安的电流,一个WiFi又能大到哪里去呢?
 
看 看德州仪器官方WiFi模块(TI的WiFi模块封装尺寸和引脚定义与WG1300完全相同,本人咨询Jorjin总代理后得知TI的官方模块正是由 Jorjin设计并制造的,与WG1300相比在硬件上也是完全一样的,只是外壳上的字不同而已) 的文档后,不禁大吃一惊!发送电流峰值275mA,接收峰值103mA,合起来有接近400mA的峰值电流!正是因为如此,我120mA的LDO被瞬间秒 杀,电压从3.3V拉到了2.6V……我居然还不知道!因为STM32可以在2.6V下正常工作,我的还是在各种加断点、各种单步跟踪,完全没有意识到模 块已经因电压过低死机了。。。看了一下Sparkcore的电路图,发现它用的LDO型号是MIC5219,一个SOT23-5封装,500mA的 LDO,哈哈~
 
另外还有一个不算是坑,或者说只是一个小坑~就是security.h文件最后少了一段代码,在用C++编译时可能导致编译出错,并且把错误报到别的文件里,让人很困惑。。。缺少的代码如下,大家一看就懂了~
#ifdef __cplusplus
}
#endif
(kqwd)
本站文章除注明转载外,均为本站原创或编译欢迎任何形式的转载,但请务必注明出处,尊重他人劳动,同学习共成长。转载请注明:文章转载自:罗索实验室 [http://www1.rosoo.net/a/201310/16773.html]
本文出处:网易博客 作者:kqwd
顶一下
(1)
100%
踩一下
(0)
0%
------分隔线----------------------------
相关文章
发表评论
请自觉遵守互联网相关的政策法规,严禁发布色情、暴力、反动的言论。
评价:
表情:
用户名: 验证码:点击我更换图片
栏目列表
将本文分享到微信
织梦二维码生成器
推荐内容