1. 文档中的一个小错误
wlan_ioctl_get_scan_results
函数用于返回WLAN扫描的结果,每调用一次返回一个结果。返回的数据结构在文档中给出,但是这里文档写错了。。。Result entry前的56bytes应该是42bytes,而每次的结果总有有4+4+42=50(bytes)。把下面的结果加起来,也会发现各项的和是42而不是56. 这本来只是手册上的一个笔误,但在编程时,大家通常会使用一个结构体来接收返回的数据,如果这个字节数不对的话,就会影响内存对齐,从而导致返回的结果错误。这里,我使用的结构体定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | typedef struct _wlan_full_scan_results_args_t { /* 4 Bytes: number of networks found 4 Bytes: The status of the scan: 0 - agged results, 1 - results valid, 2 - no results { 1 bit isValid - is result valid or not 7 bits rssi - RSSI value; } { 2 bits: securityMode - security mode of the AP: 0 - Open, 1 - WEP, 2 WPA, 3 WPA2 6 bits: SSID name length } 2 bytes: the time at which the entry has entered into scans result table 32 bytes: SSID name 6 bytes: BSSID */ uint32_t ap_count; uint32_t ap_state; uint32_t ap_vaild : 1; uint32_t ap_rssi : 7; uint32_t ap_security : 2; uint32_t ap_ssidlen : 6; uint16_t ap_time; char ap_ssid[32]; uint8_t ap_bssid[6]; uint8_t reserved[2]; //for memory align } wlan_scan_result; |
另外,这个函数读到最后一条结果时,再读会返回一个长度为0的结果,即前四个字节(number of networks found)为零,然后再读才会读出下一次扫描的结果。这一点手册上并没有指出,是我们在编程实践中自己发现的。
2014年3月19日注:目前最新版的CC3000官方API文档中这个错误已经得到了修正。
2. 两组中断控制函数的实现
如果前面一条是一个小坑的话,这一条绝对是一个大坑。SpiPause
- SpiResume
与WlanInterruptDisable
-WlanInterruptEnable
这两组中断控制函数是CC3000整个驱动移植工作中最tricky的部分。目前为止,我见过的CC3000的驱动实现中,不论是开源的、产品正在开发而尚未开源的实现,除了官方的例程以外,还没有人将这两组函数写对。甚至有人能通过降低SPI速率、修改IO中断方式等等办法,使得整个程序得以正常运行,将错误带到了产品中。只要有人说:“我的CC3000驱动有点问题,调了很久都不行。。。”之类的话,不用等他说完,就可以猜想他是把这两组函数写错了。
这两组函数非常容易错,错了又非常难以发现。这两组函数的实现已经成为了CC3000驱动移植的主要障碍。其主要原因如下:
a. 是官方的Porting Guide没有明确指出这两组函数的区别。很多人则想当然地认为这两组函数是一样的。甚至是一组中调用另一组。再看官方的实现,又有点不知所云,所以就忽略了这个问题。
b. 忽略这个问题后,CC3000模块可以正常初始化,还可以正常扫描WiFi热点,有人还能正常进行Smart config,甚至有人还能正常打开socket并使用UDP协议发送数据!开发者根据CC3000驱动自下而上分层的架构来看,看到SPI已经可以进行正常通信,从而直接在心理上排除了SPI驱动实现有误的可能性,错误查来查去还是在原地兜圈子。
c. 由于这两组函数实现有误导致的错误几乎成了一个人品问题,在不同速度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的开发者挠头了。
3. TI提供的CC3000 Host Driver假设char类型是无符号的
这个问题可能很多人都不会遇到,因为在嵌入式开发中char通常都是无符号的,基本所有的用于嵌入式平台的编译器默认情况下都会这样设定。而我则比较习惯PC上有符号的char
,所以我手贱给GCC加了一个编译参数-fsigned-char
……
TI的Host Driver就比较扯淡,它假设char
是无符号的,并且在char
有符号时就会因符号位在强制转换中的行为不同而出错。这个问题不容易排查,因为需要跟踪到TI实现的Host Driver里,而大家通常都会假设这个实现是没错的,能跟进去就是一种勇气了……我当时遇到这个问题时,不知道是哪儿的错误,就在很多回调函数中添加了串口打印的语句,通过串口输出,观察各个函数被调用的情况,结合现象,再加以大胆的猜想,才解决了这个问题。
4. 供电
这个是一个硬件问题,一个不应该犯的错误。目前大家用的最多的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++编译时可能导致编译出错,并且把错误报到别的文件里,让人很困惑。。。缺少的代码如下,大家一看就懂了~
1 2 3 | #ifdef __cplusplus } #endif |
http://kuangqi.me/embedded/cc3000-host-driver-porting-guide/