?下图是LSM9DS1参考手册中指出的通过I2C通信读取一字节数据的通信规则图
根据该规则我编写了I2C通信程序如下图所示:
bool I2C_Device_search(I2C_Handle handle, I2C_Transaction *transaction, uint8_t slave_addr, uint8_t regAddr, uint8_t* regData){
I2CCC32XX_Object *object = handle->object;
I2CCC32XX_HWAttrsV1 const *hwAttrs = handle->hwAttrs;
int_fast16_t status = I2C_STATUS_SUCCESS;
// uintptr_t key;
bool ret = true;
object->currentTransaction = transaction;
object->writeBuf = transaction->writeBuf;
object->writeCount = transaction->writeCount;
object->readBuf = transaction->readBuf;
object->readCount = transaction->readCount;
object->burstCount = 0;
object->burstStarted = false;
transaction->status = I2C_STATUS_INCOMPLETE;
uint32_t I2C_BASE = hwAttrs->baseAddr;
Display_printf(display,0,0,(char *)"Start connect with device!");
if(I2CMasterBusBusy(I2C_BASE)){ //首先判断I2C总线是否正在被占用
transaction->status = I2C_STATUS_BUS_BUSY;
status = I2C_STATUS_BUS_BUSY;
Display_printf(display,0,0,(char *)"I2C BUS BUSY!");
}
/* start to transmit but stop condition is not generated */
I2CMasterControl(I2C_BASE,0x00000003); //若未被占用则设置主机状态为开始传输数据状态
/* Write Slave Address to I2CMSA Transmit mode */
//向总线写入写模式的从机地址(SAD+W)
I2CMasterSlaveAddrSet ( I2C_BASE, \
slave_addr, \
false); ///< false - I2C write
while(I2CMasterBusBusy(I2C_BASE) == true); // 等待从机应答信号
/* Write data to I2CMDR */
I2CMasterDataPut(I2C_BASE, regAddr); //向总线写入要写入的数据,即要访问的寄存器地址(SUB)
while(I2CMasterBusBusy(I2C_BASE) == true); // 等待从机应答信号
Display_printf(display,0,0,(void *)"ERROR: 0x%x", I2CMasterErr(I2C_BASE));
/* Write Slave Address to I2CMSA Receive mode */
//向总写写入读模式从机地址(SAD+R)
I2CMasterSlaveAddrSet ( I2C_BASE, \
slave_addr, \
true); ///< true - I2C read
/* start to receive with NCK and stop conditon */
I2CMasterControl(I2C_BASE,I2C_MASTER_CMD_BURST_RECEIVE_START);//重新启动总线
while(I2CMasterBusBusy(I2C_BASE) == true); //等待从机应答
if ( I2CMasterErr(I2C_BASE) == I2C_MASTER_ERR_NONE )
{
/* get the reg data */
*regData= I2CMasterDataGet(I2C_BASE); //从机将对应寄存器的数据写入总线
ret = true;
return ret;
}
else
{
/* finish the transfer due to error */
I2CMasterControl(I2C_BASE,I2C_MASTER_CMD_BURST_SEND_ERROR_STOP);
Display_printf(display,0,0,(void *)"ERROR: 0x%x", I2CMasterErr(I2C_BASE));
ret = false;
return ret;
}
}
但在测试时发现串口调试助手上读取不到相应寄存器的值
在利用逻辑分析仪抓取I2C总线波形时发现,总线上仅写入了从机地址,并且从机没有应答
调试程序时发现在将主机状态设为开启模式(I2C_MASTER_CMD_BURST_SEND_START)时
I2C外设的MCS寄存器数据为
?查阅CC3235S的用户指南手册如下图所示:
MCS寄存器的最低三位分别代表了Busy,Start和Stop位
?我认为应该问题出现在状态配置上,于是我尝试了下面这个代码
/* start to transmit but stop condition is not generated */
I2CMasterControl(I2C_BASE,0x00000003);
?向MCS寄存器写入0x03,但是调试时发现执行到这一步时结果仍然如下图所示:
?为什么无法正常的改变MCS寄存器的值?
于是我去看了一下这个Control函数的源码:
void I2CMasterControl(uint32_t ui32Base, uint32_t ui32Cmd)
{
//
// Check the arguments.
//
ASSERT(_I2CBaseValid(ui32Base));
ASSERT((ui32Cmd == I2C_MASTER_CMD_SINGLE_SEND) ||
(ui32Cmd == I2C_MASTER_CMD_BURST_SEND_START) ||
(ui32Cmd == I2C_MASTER_CMD_SINGLE_RECEIVE) ||
(ui32Cmd == I2C_MASTER_CMD_BURST_SEND_CONT) ||
(ui32Cmd == I2C_MASTER_CMD_BURST_SEND_FINISH) ||
(ui32Cmd == I2C_MASTER_CMD_BURST_SEND_ERROR_STOP) ||
(ui32Cmd == I2C_MASTER_CMD_BURST_RECEIVE_START) ||
(ui32Cmd == I2C_MASTER_CMD_BURST_RECEIVE_CONT) ||
(ui32Cmd == I2C_MASTER_CMD_BURST_RECEIVE_FINISH) ||
(ui32Cmd == I2C_MASTER_CMD_BURST_RECEIVE_ERROR_STOP) ||
(ui32Cmd == I2C_MASTER_CMD_QUICK_COMMAND) ||
(ui32Cmd == I2C_MASTER_CMD_FIFO_SINGLE_SEND) ||
(ui32Cmd == I2C_MASTER_CMD_FIFO_SINGLE_RECEIVE) ||
(ui32Cmd == I2C_MASTER_CMD_FIFO_BURST_SEND_START) ||
(ui32Cmd == I2C_MASTER_CMD_FIFO_BURST_SEND_CONT) ||
(ui32Cmd == I2C_MASTER_CMD_FIFO_BURST_SEND_FINISH) ||
(ui32Cmd == I2C_MASTER_CMD_FIFO_BURST_SEND_ERROR_STOP) ||
(ui32Cmd == I2C_MASTER_CMD_FIFO_BURST_RECEIVE_START) ||
(ui32Cmd == I2C_MASTER_CMD_FIFO_BURST_RECEIVE_CONT) ||
(ui32Cmd == I2C_MASTER_CMD_FIFO_BURST_RECEIVE_FINISH) ||
(ui32Cmd == I2C_MASTER_CMD_FIFO_BURST_RECEIVE_ERROR_STOP) ||
(ui32Cmd == I2C_MASTER_CMD_HS_MASTER_CODE_SEND));
//
// Send the command.
//
HWREG(ui32Base + I2C_O_MCS) = ui32Cmd;
}
发现源码中仅有Assert断言和寄存器写入操作(上面的0x00000003与BURST_SEND_START宏定义相同,因此不会被断言)。
但是为什么状态数据没有被正常写入到MCS寄存器中呢?
在不了解的情况下我又查阅了MCS的几种状态:
- 0x26:快速写命令。执行完写命令后,主机返回Idle状态
- 0x03:重复START条件,之后跟随一个传输操作(主机保持在主机传输状态)
- 0x07:重复START条件,然后是一个传输操作和结束操作(之后主机进入空闲状态)
其中MCS寄存器的第五位在主机写入模式下时控制写入的命令是否为快速命令,经查阅快速命令与正常传输模式并不相同,会自动写入开始位和停止位,这是我不想要的,但是在上面调试过程中我在尝试修改这一位的时候却发现并没有修改成功。
后面想一下也对,从机地址没有被应答,那么肯定没法访问相应的寄存器,因此MCS寄存器也就无法被修改成功。
真正要解决的是为什么从机地址会被拒绝这个问题
针对现在的问题,我需要解决以下几个问题:
- 从机什么时候处于正常工作状态(可以进行I2C通信的状态),这需要去看从机的设备手册
- CC32XX规定的硬件I2C通信流程是什么?这需要去看CC32XX的技术指南手册
首先我看了以下BQ25895的手册
为什么去看BQ25895的手册了呢,那是因为之前调通了BQ25895的I2C通信程序,我想试一下之前的程序有没有问题,结果发现在搜寻BQ25895的地址时也会被拒绝,于是我就看了一下BQ25895的手册,确认BQ25895什么时候可以正常进行I2C通信。
看完手册后发现其中与I2C通信有关的篇幅并没有讲到BQ25895在通信前有需要什么额外的操作
之后我大体看了以下CC32XX规定的硬件I2C通信流程,收获如下:
- 当从机地址没有被从机应答时,SDA线会被强制拉高,从而I2C总线的通信会被中断。
- 当接收不到一个完整的字节时,接收端会保持时钟线为低电平,使传输进入等待状态。
- 当CC32XX处于读模式时,在控制寄存器(MCS)中的ACK位需要手动置1,并且在通信完成后要记得对该位清零。
- 如果从机需要CC32XX提供手动的应答或非应答信号的话,可以配置I2CSACKCTL寄存器
同时有一个疑惑:
? ? ? ? 当主机传输完一次数据后需要释放总线的控制权,这个释放操作是由CC32XX的硬件自动完成的吗,还是通过什么方式来完成的。
再确认一下与从机通信时的时序是否符合I2C要求
?从上面时序图来看,START是正常的,并且在传输从机地址数据时,SDA在时钟高电平保持,在时钟低电平改变也是正常的,有九个时钟周期产生,传入了数据11010100(0xD4)是正确的从机地址,由于第九个时钟周期为高电平,从机地址没有被从机应答,所以主机产生STOP停止信号。
去网上搜了一下有关从机地址不被应答的问题的帖子
网上说如果从机地址不被应答的原因大体有三种:
- 从机地址不对
- 时序不对
- 速率不匹配
如果上述三种情况都没有问题的话,大概率是从机的问题。
说实话有点扯,I2C通信的问题找到了:
对于BQ25895而言需要5V的VBUS输入,会有4.2V左右的输出电压,否则无法保证其处于正常工作,而之前是利用烧录器提供3.3V电压,无法使BQ25895正常工作,也就无法进行I2C通信
对于LSM9DS1传感器而言,应该是芯片没有焊好导致的,导致其同样没有处于正常工作状态,并且芯片在供电时会出现严重的发热现象也更加印证了这一点。
?但是在对BQ25895进行正常通信时又发现了程序的其他问题
BQ25895读取REG0B的信息时序波形不正确的问题
如果单独查询BQ25895从机地址和某一寄存器地址,则可以正常通信,其程序如下:
/* Write Slave Address to I2CMSA Transmit mode */
I2CMasterSlaveAddrSet ( I2C_BASE, slave_addr, false); ///< false - I2C write
/* Write data to I2CMDR */
I2CMasterDataPut(I2C_BASE, regAddr);
/* start to transmit but stop condition is not generated */
I2CMasterControl(I2C_BASE,I2C_MASTER_CMD_BURST_SEND_START);
其寄存器数据如下图所示:
I2C通信的时序图中可以看到正常的时序波形:
?但是如果读取该寄存器中的数据信息的话则会出现问题
下图是通过I2C读取某一寄存器中的信息的时序说明图:
并参考C32XX手册提供的I2C重启流程说明:
?我编写的程序如下:
/*向总线写入写模式的从机地址(SAD+W)*/
I2CMasterSlaveAddrSet ( I2C_BASE, slave_addr, 0);
/* 向总线写入要写入的数据,即要访问的寄存器地址(SUB) */
I2CMasterDataPut(I2C_BASE, 0x0B);
/* 设置主机状态为开始传输数据状态,并且不生成停止位 */
I2CMasterControl(I2C_BASE,I2C_MASTER_CMD_BURST_SEND_START);
/*判断总线是否完成传输*/
while(I2CMasterBusBusy(I2C_BASE) == true);
/* 向总线写入读模式的从机地址(SAD+R) */
I2CMasterSlaveAddrSet ( I2C_BASE, slave_addr, true);
/* 以单个数据读取模式重启总线 */
I2CMasterControl(I2C_BASE, I2C_MASTER_CMD_SINGLE_RECEIVE);
/*判断总线是否传输完成*/
while(I2CMasterBusBusy(I2C_BASE) == true);
/*判断传输是否有错误*/
if ( I2CMasterErr(I2C_BASE) == I2C_MASTER_ERR_NONE )
{
/* 从机将对应寄存器的数据写入总线 */
*regData= I2CMasterDataGet(I2C_BASE);
ret = true;
return ret;
}
else
{
/* finish the transfer due to error */
I2CMasterControl(I2C_BASE,I2C_MASTER_CMD_BURST_SEND_ERROR_STOP);
Display_printf(display,0,0,(void *)"ERROR: 0x%x", I2CMasterErr(I2C_BASE));
ret = false;
return ret;
}
按照我编写的程序逻辑,主机应该先产生开始信号,并向总线写入0XD4,再写入0X0B,再重启总线,再写入0XD5,再读取到REG0B寄存器中的信息,最后生成结束信号
?通过逻辑分析仪抓取的实际时序图如下所示:
?可以看到有以下几个问题:
- 为什么第一次从地址是以写入模式传输的
- 写入的数据是0x99,不知道是什么
- 在第一次写入完之后停止又开始了总线,这里是不应该停止的
调试一下程序(按照上面程序的顺序,每一个step都执行一行代码),其寄存器变化如
Step1:
?Step2:
执行DataPut函数时是不会改变MDR寄存器值吗?
Step3:
?这里看到MCS寄存器由0x20变为了0x40,表示I2C总线退出了默认的快速命令模式,并且可以利用FIFO进行连续的数据传输或接收,查阅控制寄存器的状态表,具体说明如下:
但是我向控制寄存器写入了0x03是什么时候改变呢?
Step4:
之后总线一直处于被占用的状态,卡死在了第四步while(I2CMasterBusBusy(I2C_BASE) == true);
起初是认为写入控制寄存器(MCS)中的状态数据有问题,于是参照着控制寄存器的状态表选取了几个不同的状态,其时序波形结果都相同
这样来看并不是MCS寄存器写入数据不正确导致的问题
现在我想换一块板子测试一下,看看是不是25895的问题,结果并不是从机的问题
现在问题仍归结于程序上的问题
接下来我想尝试利用driver提供的I2C的API与BQ25895建立通信
其代码如下:
i2cTransaction.readBuf = readBuf;
i2cTransaction.readCount = 1;
i2cTransaction.writeBuf = writeBuf;
i2cTransaction.writeCount = 1;
i2cTransaction.slaveAddress = BQ25895_ADDR;
salve_addr = BQ25895_ADDR;
writeBuf[0] = 0x0B;
I2C_transfer(i2c_handle,&i2cTransaction);
通信时序图如下所示:
?可见时序是正确的,并且读取到了0x0B寄存器中的数据
查看transfer函数的源码如何实现的
为什么我用driverlib写的I2C通信的程序就无法建立通信?
首先看一下I2C_transfer函数的源码:
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_transferTimeout()这个函数,这个函数的函数头如下,它会传入I2C的句柄和传输结构体,并返回完成传输后的I2C状态
int_fast16_t I2C_transferTimeout( I2C_Handle handle, \
I2C_Transaction *transaction, \
uint32_t timeout )
{
return (transaction->status);
}
?按照源码的思路尝试采用FIFO读写的形式进行I2C数据传输,将代码修改如下:
//判断总线是否在忙
while(I2CMasterBusBusy(I2C_BASE) == true);
//如果总线不忙,开始写数据
I2CMasterSlaveAddrSet (I2C_BASE, 0x6A, false);
//确定要写入的数据个数
object->burstCount = transaction->writeCount;
I2CMasterBurstLengthSet(I2C_BASE, object->burstCount);
//向FIFO中写入寄存器地址
I2CFIFODataPut(I2C_BASE, regAddr[0]);
//MCS寄存器配置为0x42
I2CMasterControl(I2C_BASE,I2C_MASTER_CMD_FIFO_BURST_SEND_START);
//判断总线是否在忙
while(I2CMasterBusBusy(I2C_BASE) == true);
//向总线写入读模式的从机地址(SAD+R)
I2CMasterSlaveAddrSet ( I2C_BASE, 0x6A, true);
//确定要读取的数据个数
object->burstCount = transaction->readCount;
I2CMasterBurstLengthSet(I2C_BASE, object->burstCount);
//从机将对应寄存器的数据写入总线
*regData= I2CFIFODataGet(I2C_BASE);
//MCS寄存器配置为0x46
I2CMasterControl(I2C_BASE, I2C_MASTER_CMD_FIFO_SINGLE_RECEIVE);
逻辑分析仪采集到的时序波形图:
?可以看到仍然存在从机地址读写模式不对应的问题
我刚开始设置的访问从机地址为写模式,为啥是0XD5???
?于是再次一步一步的看寄存器的变化
在执行完I2CMasterControl(I2C_BASE,I2C_MASTER_CMD_FIFO_BURST_SEND_START);??
之后FIFODATA为0x3D,MDR为0x3A,这都是问题,因为我写入FIFO的明明是0x01
?又发现一个问题:程序一步一步调试会卡在while(I2CMasterBusBusy(I2C_BASE) == true);
但是直接运行却不会卡住,者更奇怪了,说明程序跑飞了?
测试LSM9DS1的I2C通信?
首先我重新焊了一下LSM9DS1模块,并测量板子在工作情况下对IMU的供电情况
可以确定IMU的供电是正常的:
- 电压电压正常:传入IMU的电源电压VCC和VCC_IO都是3.3V,满足IMU的正常工作电压
- I2C总线空闲状态正常:连接到IMU的I2C总线都被拉高到了3.3V
写了如下程序读取LSM9DS1设备中的WHO_AM_I_M寄存器
i2cTransaction.readBuf = &readBuf;
i2cTransaction.readCount = 1;
i2cTransaction.writeBuf = &writeBuf;
i2cTransaction.writeCount = 1;
i2cTransaction.slaveAddress = MAG_ADDR;
writeBuf = 0x0F;
I2C_transfer(i2c_handle,&i2cTransaction);
逻辑分析仪抓取的结果如下:
再次确认IMU模块的硬件连接和I2C便携式时序:
原理图中对SDO_A/G和SDP_M引脚都做了拉低处理,对照MAG的地址说明:
?其地址SAD应该为0011100(0x1C),加上写模式0后为0x38,从地址没有问题。并且从地址的通信时序也没有问题,可判断主机这边的程序是没有问题的。
那么从机出了什么问题呢?
怎么确定IMU是否处于正常工作工作状态呢?
|