之前写了一个CC3235S通过I2C与bq25895通信的程序,之前是可以正常运行的,但是今天在测试时突然发现程序总会卡死在下面这段汇编的$1死循环中
ti_sysbios_family_arm_m3_Hwi_excHandlerAsm__I:
.asmfunc
tst lr, #4 ; context on PSP?
ite NE
mrsne r0, psp ; if yes, then use PSP
moveq r0, sp ; else use MSP
mov sp, r0 ; use this stack
stmfd sp!, {r4-r11} ; save r4-r11 while we're at it
mov r0, sp ; pass sp to exception handler
mov r1, lr ; pass lr too
mov r4, lr ; preserve LR in r4
ldr r2, excHandlerAddr
blx r2
mov r0, sp ; for ROV
mov r1, r4 ; for ROV
$1
b $1 ; spin here indefinitely
开始Debug时发现时在GPIO_init中卡住,跳转到这里来,去网上查阅资料,发现网友提到一个外设初始化两遍的话,第二遍就会卡死。
虽然不明白为什么,我立马就想到GPIO在Boardinit()函数中也个GPIOinit函数,于是我注释掉了main函数中的GPIO的初始化函数,果然不卡了。
补充:by the way,如果程序以相同的方式卡在了I2C或其他外设的Open函数上。考虑在上次使用该外设后,有没有正常关闭Close该外设,如果没有的话重复开启同一外设也会出现异常报错。解决方法也很简单,如果没有烧入到Flash中重新上电即可,如果烧录到Flash中重新擦除Flash即可
我发现在CC3235S通过I2C与其他从设备通信的过程中,并没有执行完就卡住了。
通信时序图如下图所示:
可以看到,在传输完寄存器地址(0x0F)后,SCL线被拉低,说明I2C通信被设置为了等待状态,很明显有什么事情中断了I2C通信,并且处理这个事情时还被卡死了。
Emmmm。。。。
按道理说,I2C_transfer()执行完写入数据后,会重启I2C总线,重新以读模式写入从机地址,但在需要重启I2C总线时,主机(CC3235S)去忙别的去了,并且还卡死在了上述汇编代码段。
难道说,I2C总线的重复的启动也会被卡吗。。。
于是只能硬着头皮继续排查,先按步执行debug了一下:
发现卡死在了获取I2C句柄锁定的判断中
/* Get the lock for this I2C handle */
if (SemaphoreP_pend(&(object->mutex), SemaphoreP_NO_WAIT)
== SemaphoreP_TIMEOUT) {
/* We were unable to get the mutex in CALLBACK mode */
if (object->transferMode == I2C_MODE_CALLBACK) {
/*
* Recursively call transfer() and attempt to place transaction
* on the queue. This may only occur if a thread is preempted
* after restoring interrupts and attempting to grab this mutex.
*/
return (I2C_transferTimeout(handle, transaction, timeout));
}
/* Wait for the I2C lock. If it times-out before we retrieve it, then
* return false to cancel the transaction. */
-> if(SemaphoreP_pend(&(object->mutex), timeout) == SemaphoreP_TIMEOUT) {
transaction->status = I2C_STATUS_TIMEOUT;
return (I2C_STATUS_TIMEOUT);
}
}
大体理解了一下,由于在I2C_transfer函数中给Timeout参数传入的是无限等待的指令,所以在上面的信号量等待判断语句处SemaphoreP_pend()会永远的等待下去
bool I2C_transfer(I2C_Handle handle, I2C_Transaction *transaction)
{
-> int_fast16_t result = (I2C_transferTimeout(handle, transaction, I2C_WAIT_FOREVER));
if (result == I2C_STATUS_SUCCESS) {
return (true);
}
else {
return (false);
}
}
产生这样的原因也是因为没有等到I2C lock,这是什么意思?
上网搜了一下I2C lock,只搜到了I2C lock-up,也就是I2C死锁:
I2C死锁的产生常见的有两种情况:一种是从设备在回复ACK时主设备异常复位;另一种是从设备在回复数据位是0的时候主设备异常复位。两种情况的相同点都是主设备异常复位时SDA处于被从设备拉低状态,而主设备复位后SCL处于高电平状态(空闲状态)。此时从设备会等待主设备拉低SCL取走ACK或者数据位,而主设备会等待从设备释放SDA线。主设备和从设备互相等待,隔空对望,进入死锁状态。
但是从上面的时序波形来看,并非产生了I2C死锁问题,因为SCL并未保持高电平,并且SDA线在应答完总线的寄存器地址后,在下一个时钟低电平时再次被总线拉高 ,此时SDA总线恢复空闲状态,按照正常时序接下来将有主机重启I2C总线并写入读方向的从机地址,但之后SCL线被一直拉低,对于这一问题有两个问题需要考虑:
首先在从机发送了应答信号后,SCL从高电平跳变到低电平。SDA线被拉高,说明在此器件从机释放了SDA线的控制权,所以SCL线不是从机拉低的。
那就是一定是主机拉低的吗?
有可能,但其实还有一种可能,就是硬件I2C本身有问题,导致主机中断了I2C通信。
具体是什么原因需要进一步排查。
于是我有搜索了一下上面那个excHandler,对此情况TI工程师给出了一些建议:
一个被触发的HWI异常处理程序表示CPU异常,实际上没有任何好的方法来调试这个异常。
为了快速评估可能的一般原因,您可以做的是检查在寄存器组CPU_SCS (CPU系统控制空间)下找到的寄存器CFSR(可配置故障状态寄存器)。在CCS中,进入视图->寄存器,然后展开CPU_SCS,然后展开CFSR。
大多数例外可分为三类:
堆栈溢出或损坏会导致任意代码执行。
几乎任何例外都是可能的。
NULL指针已被解引用并写入。
通常(IM) PRECISERR例外
一个外围模块(如UART、Timer等)在不通电的情况下被访问。
通常(IM) PRECISERR例外
当发生访问冲突时,异常类型为IMPRECISERR,因为对闪存和外围内存区域的写入大多是缓冲写入。
如果在异常发生时设置了CFSR:BFARVALID标志(典型的PRECISERR),则可以读取CPU_SCS中的BFAR寄存器来查找导致异常的内存地址。
你应该查看用户指南中的“解码CPU异常”部分,了解如何处理这个问题的更详细的指南:
http://dev.ti.com/tirex/content/simplelink_cc2640r2_sdk_1_50_00_58/docs/blestack/ble_user_guide/html/debugging/cpu_exceptions.html
总而言之,尝试隔离应用程序的哪个部分触发了CPU异常。如果能够根据在CFSR中观察到的异常类型缩小问题代码的范围,就应该能够找出导致异常的原因。
该方案是针对CC2640的,CC2640与CC3235S同属于Cortex-M3/M4架构,应该问题是相似的,但是我并没有找到带有CPU_SCS中的CFSR的寄存器。
我对照着相应的寄存器,找到了CC32XX中的FAULTSTAT寄存器,该寄存器指示产生各种Fault的原因,如内存管理故障、总线故障或使用故障等。这与CC26XX中的CFSR寄存器功能相同。
接下来我在调试程序时,留意了该寄存器的状态变化。
当程序卡死时,FAULT STAT,HFAULT STAT,FAULT ADDR等寄存器都会发生变化,查找变化的寄存位的说明信息来确定问题产生的原因。
如下图的寄存器信息,是我在新创建了一个线程后,发生了线程卡死的问题,寄存器状态如下:
FAULT STAT REG 的第10位被置为1,该位为BUS FAULT,具体说明如下:
说明程序卡死的地方发生了不明确的数据总线异常错误,该错误可能是由于当前运行的程序的优先级高于总线错误的优先级,导致总线错误的异常程序无法被执行而产生的。
查看HFAULT STAT寄存器的第30位置位信息也可以看到:
该位被置位的原因是:由于优先级的原因或由于禁用了优先级,无法处理的可配置优先级的故障升级产生了强制硬故障。
所以这表明我编写的应用程序,存在任务优先级异常的问题。
于是我修改了创建线程的代码,调整了创建的线程的优先级,问题被解决。
回到上面的问题,当程序卡死在I2C_transfer任务执行的过程中时,寄存器状态如下:
首先重新焊了上拉电阻,还是有相同的问题
于是换了一个底板,在两个底板上都遇到了I2C通信异常的问题,EXCEP函数由I2C异常退出引起
重新换了一个例程后,问题消失了,所以还是程序有问题,transfer函数执行前对I2C的配置有问题