我用I2C2 DMA读取从机寄存器,问什么总是比我要读取的多了一个字节?
我代码里写了DMA bufsize是1,但逻辑分析仪看到的是读回来两个字节,请帮忙看看是哪里配置出错了么?
logan_DMA_I2C2_RX_Init((u32)RxData,1);
logan_i2c2_rx(0x5b, 0x12);
我用I2C2 DMA读取从机寄存器,问什么总是比我要读取的多了一个字节?
我代码里写了DMA bufsize是1,但逻辑分析仪看到的是读回来两个字节,请帮忙看看是哪里配置出错了么?
logan_DMA_I2C2_RX_Init((u32)RxData,1);
logan_i2c2_rx(0x5b, 0x12);
这是由于没有提前关闭I2C的自动应答ACK造成的。DMA配置的长度只决定DMA会从I2C外设搬运几个数据,而I2C接收多少数据是由I2C逻辑控制的。浏览代码发现I2C接收第一个字节前并未关闭自动应答ACK。这就造成从机收到主机的ACK从而认为主机需要再读1字节而继续发送,主机无法发出停止条件。
根据CH32FV2x_V3xRM的275页所述:
“在接收模式时,主设备需要在最后一个数据位的应答位置 NAK,接收到 NACK 后,从设备释放对 SCL 和 SDA 线的控制”
故对于只读1字节的情况,需要在读取之前关闭自动应答ACK。对于只读取1字节的情况,代码应如下修改:
while( !I2C_CheckEvent( I2C2, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED ) ); // 原有代码 I2C_AcknowledgeConfig( I2C2, DISABLE ); // 新增代码,提前关闭自动应答ACK DMA_Cmd( DMA1_Channel5, ENABLE ); // 原有代码
而对于读取2字节或更多的情况,自动应答ACK应等到还剩1字节待读取时再关闭。
另外附上一个X035系列I2C逻辑的状态机实现代码,楼主可以参考:(链接会直接跳转到读时序的接收阶段,可以看到若只剩1字节需要接收时,程序中关闭自动应答ACK的功能)
https://github.com/WuxiProject-offical/CH32X035-HelperLibrary/blob/main/I2C/Master/i2c.c#L617
灰常感谢,那如果用DMA的话有什么方法可以处理这个“BUG”,总不能去一直循环查询还剩多少个数据吧?
我用某业内主流32位MCU时遇到类似的问题,我的策略是DMA长度比需要读取的长度少一个,在DMA的传输完成中断里关闭自动应答ACK,然后最后一字节用中断接收;如果只读1字节,直接用中断读取而不开DMA。
但V203上,似乎可以卡bug做一个非常骚的操作,即直接设置DMA长度为比需要读取的长度少一个,不在DMA传输完成中断里添加代码。按你的测试现象来看,如果最后一个字节没有被DMA取走,主机会自动发NACK并结束通信?但这一行为我不确定是否可靠,建议等周一官方工作人员回复下。
我在手册里看到这样一个寄存器,是解决这个问题的,如下图。
初始化的时候加上这一句就行了 I2C_DMALastTransferCmd(I2C2,ENABLE);
“如果最后一个字节没有被DMA取走,主机会自动发NACK并结束通信?” 我在DMA完成中断里发送了STOP。
解决了上述问题,依然有两个BUG,帮忙看看是哪里的问题:
1.红色圈的地方,逻辑分析仪可以看到接受了4个数据,但print接受数字时,第一个字节没有正确输出。
2.紫色圈出来的位置,从逻辑分析仪看发送了5个字节(包括寄存器),但程序里TX的长度写成6才能正确发送4个字节。
附件是代码。
1.红色圈的地方,逻辑分析仪可以看到接受了4个数据,但print接受数字时,第一个字节没有正确输出。
针对这个问题如下代码中加了一个延时解决了,但不明白为什么会这样,如果不加延时该怎么处理?
void logan_i2c2_rx(u16 slave_add, u16 reg)
{
while( I2C_GetFlagStatus( I2C2, I2C_FLAG_BUSY ) != RESET );
I2C_GenerateSTART( I2C2, ENABLE);
while( !I2C_CheckEvent( I2C2, I2C_EVENT_MASTER_MODE_SELECT ) );
logan_send_addr( I2C2, slave_add, I2C_Direction_Transmitter);
while( !I2C_CheckEvent( I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED ) );
#if (Address_Lenth == Address_8bit)
I2C_SendData( I2C2, (u8) (reg & 0x00FF));
while( !I2C_CheckEvent( I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED ) );
#elif (Address_Lenth == Address_16bit)
I2C_SendData( I2C1, (u8)(reg>>8) );
while( !I2C_CheckEvent( I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED ) );
I2C_SendData( I2C1, (u8)(reg&0x00FF) );
while( !I2C_CheckEvent( I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED ) );
#endif
I2C_GenerateSTART( I2C2, ENABLE);
while( !I2C_CheckEvent( I2C2, I2C_EVENT_MASTER_MODE_SELECT ) );
logan_send_addr( I2C2, slave_add, I2C_Direction_Receiver);
while( !I2C_CheckEvent( I2C2, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED ) );
Delay_Us(100);
DMA_Cmd( DMA1_Channel5, ENABLE );
}
由于我手上并无V203的硬件,以下均为本人按照经验所做的推测,请酌情参考。
对于问题1,我没有什么头绪,不确定是否与问题2有关,建议只保留读取逻辑再次测试。
对于问题2,我个人认为是因为DMA发出最后一字节后,该字节数据刚刚装入I2C数据寄存器还未来得及发送,DMA就因数据已全部传输完毕而触发TC中断,而程序中TC中断立即发出了STOP信号,故I2C外设未来得及发出该字节即产生了停止序列。故,可在只写入目标器件的操作时,在DMA传输完毕的中断内开启I2C的BTF中断,并在I2C的BTF中断内再发出STOP信号。
您好,@雷龙飞-Logan.lei,麻烦具体说明一下你的程序如何测试使用,我这边打开你的程序发现没有做主从的区分,下载到测试板逻辑分析仪并没有采到波形,在收发通信时需要把另外收发代码注释掉么。可以跟我(lzs@wch.cn)描述一下使用方法,我这边具体测试一下。或我们V203 EVT有IIC DMA收发的例程,你也可以参考一下。
不用注释掉,我是CH32V203做主机,通过I2C2(PB10,PB11)对从机AW9523BTQR进行读写的。
您好,附件是修改后的例程,关于发送长度以及接收问题这边测试修改后是没有问题的,如下图。由于没有你所说的从机模块。例程是基于对EEPROM的读写测试的,所以对设备地址进行了修改。你拿回去测试时注意地址要改过来。
问题一。 打印读取到的I2C数据时, 第一个数据丢失。是因为DMA还没有传输完成。可以在设置标志位等传输完成在打印。
I2C状态转换,全放在中断里面进行。
I2C DMA主机模式测试没问题 。从机 MP4247可以正常通讯 。
I2C DMA从机模式不完整未测试。
大佬们可以优化一下。我这写的有点烂。
#define I2C_OUTTIME 70000
uint8_t i2cDataTx[Size] = { 0 };
uint8_t i2cDataRx[Size];
void I2C1_EV_IRQHandler(void) __attribute__((interrupt("WCH-Interrupt-fast")));
void DMA1_Channel7_IRQHandler(void) __attribute__((interrupt("WCH-Interrupt-fast")));
//void DMA1_Channel6_IRQHandler(void) __attribute__((interrupt("WCH-Interrupt-fast")));
void I2C1_ER_IRQHandler(void) __attribute__((interrupt("WCH-Interrupt-fast")));
//波特率 本机地址 (1
void IIC_Init(u32 bound, uint8_t myadr)
{
GPIO_InitTypeDef GPIO_InitStructure={0};
I2C_InitTypeDef I2C_InitTSturcture={0};
RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE );
RCC_APB1PeriphClockCmd( RCC_APB1Periph_I2C1, ENABLE );
RCC_AHBPeriphClockCmd( RCC_AHBPeriph_DMA1, ENABLE );
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init( GPIOB, &GPIO_InitStructure );
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init( GPIOB, &GPIO_InitStructure );
I2C_InitTSturcture.I2C_ClockSpeed = bound;
I2C_InitTSturcture.I2C_Mode = I2C_Mode_I2C;
I2C_InitTSturcture.I2C_DutyCycle = I2C_DutyCycle_2;
I2C_InitTSturcture.I2C_OwnAddress1 = myadr?myadr<<1:MYAdderss<<1;
I2C_InitTSturcture.I2C_Ack = I2C_Ack_Enable;
I2C_InitTSturcture.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
I2C_Init( I2C1, &I2C_InitTSturcture );
DMA_InitTypeDef DMA_InitStructure={0};
DMA_DeInit(DMA1_Channel7);
DMA_InitStructure.DMA_PeripheralBaseAddr = ( u32 )&I2C1->DATAR;
DMA_InitStructure.DMA_MemoryBaseAddr = ( u32)&i2cDataRx;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
DMA_InitStructure.DMA_BufferSize = Size;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA1_Channel7, &DMA_InitStructure );
DMA_DeInit(DMA1_Channel6);
DMA_InitStructure.DMA_PeripheralBaseAddr = ( u32 )&I2C1->DATAR;
DMA_InitStructure.DMA_MemoryBaseAddr =( u32)&i2cDataTx;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
DMA_InitStructure.DMA_BufferSize = Tize;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;//DMA_Priority_High;DMA_Priority_VeryHigh
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init( DMA1_Channel6, &DMA_InitStructure );
I2C_DMACmd( I2C1, ENABLE );
NVIC_InitTypeDef NVIC_InitStructure = {0};
NVIC_InitStructure.NVIC_IRQChannel = I2C1_EV_IRQn;//事件中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 5;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel7_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
// NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
// NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel6_IRQn;
NVIC_InitStructure.NVIC_IRQChannel = I2C1_ER_IRQn;//错误中断
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 7; //从优先级
NVIC_Init(&NVIC_InitStructure);
// DMA_ITConfig(DMA1_Channel7, DMA1_IT_TC1, ENABLE);
DMA1_Channel7->CFGR|=0x0002;//接收完成中断
// DMA1_Channel6->CFGR|=0x0002;//发送完成中断
I2C_ITConfig(I2C1, I2C_IT_EVT, ENABLE);//I2C_IT_EVT 开启事件中断 I 2C_IT_BUF|接收发送缓冲器中断 I2C_IT_ERR 错误中断
if(myadr)
{
I2C_GeneralCallCmd(I2C1,ENABLE);//响应广播
}
I2C_Cmd( I2C1, ENABLE );
// I2C_AcknowledgeConfig( I2C1, ENABLE );
Rst_i2c_salve();
}
uint8_t I2C_READ_REG8(uint8_t addr, uint8_t reg, uint8_t* rbuf, uint16_t rsize)
{
//[状态(bit7 1写读数据 0仅写数据 其他位为状态指示),发送数据尺寸H,发送数据尺寸L,读取数据尺寸H,读取数据尺寸L,发送地址 ,(写寄存器|读寄存器|指令|数据1),(数据2),...]
static uint8_t data[6+2];
uint32_t time_out=I2C_OUTTIME;
// I2C1->CTLR1 |= (uint16_t)0x0001;
while( I2C_GetFlagStatus( I2C1, I2C_FLAG_BUSY ) != RESET ) //等待I2C空闲
{
time_out--;
if(time_out==0)
{
Rst_i2c_salve();
return I2C_BUSY;
}
}
data[0] = 0x80;//写读
data[1] = 0;//发送数据字节数 高位
data[2] = 1;//发送数据字节数 低位
data[3] = (rsize>>8)&0xFF;//接收字节数 高位
data[4] = rsize&0xFF;//接收字节数 低位
data[5] = addr<<1;//从机地址
data[6] = reg;//写数据
//DMA接收设置
DMA1_Channel7->CFGR &= (uint16_t)(~DMA_CFGR1_EN);//关闭DMA指定通道
DMA1_Channel7->CNTR = 0;
DMA1_Channel7->MADDR = (uint32_t)(rbuf);
//DMA发送设置
DMA1_Channel6->CFGR &= (uint16_t)(~DMA_CFGR1_EN);//关闭DMA指定通道
DMA1_Channel6->CNTR = 7;
DMA1_Channel6->MADDR = (uint32_t)(data);
I2C_GenerateSTART(I2C1, ENABLE);//转换到主机模式 发起起始条件成功 SB状态置1 MSL状态置1表明当前为主机模式
time_out = I2C_OUTTIME;
while( !I2C_CheckEvent( I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED ))//等待传输完成 BTF=1 TXE=1 MSL=1 TRA=1 BUSY=1
{
time_out--;
if(time_out==0)
{
Rst_i2c_salve();
return I2C_ERROR;
}
}
return 0;
}
uint8_t I2C_WRITE_REG8(uint8_t addr, uint8_t reg, uint8_t*wbuf, uint16_t wsize)
{
//[状态(bit7 1写读数据 0仅写数据 其他位为状态指示),发送数据尺寸H,发送数据尺寸L,读取数据尺寸H,读取数据尺寸L,发送地址 ,(写寄存器|读寄存器|指令|数据1),(数据2),...]
static uint8_t data[6+8];
uint32_t i;
uint32_t time_out=I2C_OUTTIME;
wsize=wsize+1;
// I2C1->CTLR1 |= (uint16_t)0x0001;
while( I2C_GetFlagStatus( I2C1, I2C_FLAG_BUSY ) != RESET ) //等待I2C空闲
{
time_out--;
if(time_out==0)
{
// if(DMA1_Channel6->CNTR==0 && DMA1_Channel7->CNTR==0)
// {
Rst_i2c_salve();
// }
return I2C_BUSY;
}
}
if(wsize>8)
{
return 8;
}
data[0] = 0x00;//仅写
data[1] = (wsize>>8)&0xFF;//发送数据字节数 高位
data[2] = wsize&0xFF;//发送数据字节数 低位
data[3] = 0;//接收字节数 高位
data[4] = 0;//接收字节数 低位
data[5] = addr<<1;//从机地址
data[6] = reg;//从机寄存器地址或指令
for (i = 0; i < wsize; ++i) {
data[i+7] = wbuf[i];
}
DMA1_Channel6->CFGR &= (uint16_t)(~DMA_CFGR1_EN);//关闭DMA指定通道
DMA1_Channel6->CNTR = wsize;
DMA1_Channel6->MADDR = (uint32_t)(data);
I2C_GenerateSTART(I2C1, ENABLE);
return 0;
}
void I2C1_EV_IRQHandler(void)
{
volatile uint32_t temp = 0;
///////////////DMA///////////////////
static uint8_t *_status;
volatile uint32_t temp1 = 0;
volatile uint32_t temp2 = 0;
temp1 = I2C1->STAR1;//读状态1寄存器
temp2 = I2C1->STAR2;//读状态2寄存器
temp2 = temp2<<16;
temp = (temp1|temp2);
if((temp&I2C_FLAG_MSL)==I2C_FLAG_MSL)//主机模式
{
if((temp&I2C_IT_SB))//起始条件已发出 开始发送从机地址
{
if(DMA1_Channel6->CNTR)//首次发送数据 更新保存 数据基址 读从机寄存器操作(先写指令再读数据)第二次起始条件发出时 不应该再触发该处
{
_status = (uint8_t *)DMA1_Channel6->MADDR;
GPIO_WriteBit(GPIOB, GPIO_Pin_2, 1);//LED1 蓝色
}
I2C1->DATAR = _status[5];//bit0 0发送或读取地址bit0 1 写地址后会清除SB
_status[0]=(_status[0]&0x80)|1;//更新状态 发送地址
GPIO_WriteBit(GPIOB, GPIO_Pin_14, 0);//LED2 绿色
// GPIO_WriteBit(GPIOB, GPIO_Pin_15, 0);//LED3 红灯
}
// ADDR 用户读取状态寄存器 1 后,对状态寄存器 2 的读操作将会清除此位
if(temp&I2C_IT_ADDR)//从机地址匹配(应答) 开始写或者读数据
{
//开始DMA传输
if(DMA1_Channel6->CNTR)//发送
{
// [状态(bit7 1写读数据 0仅写数据 其他位为状态指示),发送数据尺寸H,发送数据尺寸L,读取数据尺寸H,读取数据尺寸L,发送地址,(写寄存器|读寄存器|指令|数据1),(数据2),...]
DMA1_Channel6->CFGR &= (uint16_t)(~DMA_CFGR1_EN);//关闭DMA指定通道
if((_status[0]&0x80))//写读
{
_status[5] = _status[5]|0x0001;//转换为读地址
_status[0] = (_status[0]&0x80)|2;//更新状态为读 等待数据传输完成
DMA1_Channel7->CNTR = (_status[3]<<8)|_status[4];
GPIO_WriteBit(GPIOB, GPIO_Pin_2, 0);//LED1 蓝色
}else {//仅写
_status[0]=(_status[0]&0x80)|3;//更新状态为仅写 等待数据传输完成
}
DMA1_Channel6->CNTR = (_status[1]<<8)|_status[2];
DMA1_Channel6->MADDR = (uint32_t)(_status+6);
DMA1_Channel6->CFGR |= DMA_CFGR1_EN;//开启DMA指定通道
}else if(DMA1_Channel7->CNTR)//接收
{
DMA1_Channel6->CFGR &= (uint16_t)(~DMA_CFGR1_EN);//关闭DMA指定通道
// I2C1->CTLR1 = ((uint16_t)0x0401);//开启应答
I2C1->CTLR1 |= ((uint16_t)0x0400);//开启应答
I2C1->CTLR2 |= ((uint16_t)0x1000);//主机接收时 DMA传输 最后一位NACK DMA接收不触发BTF 需要开启DMA传输完成中断处理发送停止事件
DMA1_Channel7->CFGR |= DMA_CFGR1_EN;//开启DMA指定通道
_status[0]=(_status[0]&0x80)|4;
}
}
// BTF字节发送结束标志位,用户读取状态寄存器 1后,对数据寄存器的读写将清除此位;
if(temp&I2C_IT_BTF)//数据传输完成 -> 一个数据传输完成 (硬件延时超时) (发送 数据寄存器空) (接收 数据寄存器有数据未被读取)触发完成事件
{
(void)I2C1->DATAR;
switch (_status[0]&0x7F) {
case 1://地址发送完成 等待地址匹配 在此不做任何事情
break;
case 2://写读数据状态 开始读
DMA1_Channel7->CFGR &= (uint16_t)(~DMA_CFGR1_EN);//关闭DMA指定通道
// I2C1->CTLR1 = ((uint16_t)0x0101);//再次发出开始信号 准备读取
I2C1->CTLR1 |= ((uint16_t)0x0100);//再次发出开始信号 准备读取
DMA1_Channel6->CNTR = (_status[3]<<8)|_status[4];//读取长度
_status[0]=(_status[0]&0x80)|4;
break;
case 3://仅写数据 完成
_status[0]=(_status[0]&0x80)|4;
// GPIO_WriteBit(GPIOB, GPIO_Pin_2, 1);//LED1 蓝色
case 4:// 读写完成
// I2C1->CTLR1 = ((uint16_t)0x0201);
I2C1->CTLR1 ^=((uint16_t)0x0400);// 取消应答
I2C1->CTLR1 |= ((uint16_t)0x0200);// 发起停止事件 转变为从机模式
_status[0]=(_status[0]&0x80)|5;
DMA1_Channel6->CFGR &= (uint16_t)(~DMA_CFGR1_EN);//关闭DMA指定通道
DMA1_Channel6->CNTR = 0;
// DMA1_Channel7->CFGR &= (uint16_t)(~DMA_CFGR1_EN);//关闭DMA指定通道
// if((_status[0]&0x7F)==4)GPIO_WriteBit(GPIOB, GPIO_Pin_14, 1);//LED2 绿灯
break;
default:
break;
}
}
}else {//从机模式 I2C_IT_ADDR I2C_IT_BTF I2C_IT_STOPF
// if(temp&I2C_IT_BTF)
// {
//
// }
if((temp&I2C_IT_STOPF)||(temp&I2C_IT_BTF))//从机检测到停止事件 或 发送完成标志
{
DMA1_Channel6->CFGR &= (uint16_t)(~DMA_CFGR1_EN);//关闭DMA指定通道
DMA1_Channel7->CFGR &= (uint16_t)(~DMA_CFGR1_EN);//关闭DMA指定通道
}
if(temp&I2C_IT_ADDR)
{
//主机请求数据 重复的起始条件、停止条件会清除 仲裁丢失、关闭I2C 该位硬件清零
if(temp&I2C_FLAG_TRA)//主机请求数据 根据接收的值准备数据上传
{
//从机 DMA发送数据 外部设置对应数据源
// DMA1_Channel6->CFGR &= (uint16_t)(~DMA_CFGR1_EN);//关闭DMA指定通道
// DMA1_Channel6->MADDR = ( u32)&i2cDataTx;
// DMA1_Channel6->CNTR = Tize;
DMA1_Channel6->CFGR |= DMA_CFGR1_EN;//开启DMA指定通道
}else//主机写数据 从机接收
{
/* 准备接收数据 设置大一点的缓冲区 */
DMA1_Channel7->MADDR = ( u32)&i2cDataRx;
DMA1_Channel7->CNTR = Size;
DMA1_Channel7->CFGR |= DMA_CFGR1_EN;
}
}
}
//////////////////DMA///////////////////
}
//SCL为高电平时,SDA由高变低表示起始信号;
//
//SCL为高电平时,SDA由低变高表示停止信号;
//
//起始信号和停止信号都是由主机发出,起始信号产生后总线处于占用状态,停止信号产生后总线被释放,处于空闲状态。
// 错误中断
void I2C1_ER_IRQHandler(void)
{
uint32_t temp = 0;
I2C_GenerateSTOP(I2C1, ENABLE);
DMA1_Channel6->CFGR &= (uint16_t)(~DMA_CFGR1_EN);//关闭DMA指定通道
DMA1_Channel7->CFGR &= (uint16_t)(~DMA_CFGR1_EN);//关闭DMA指定通道
temp = I2C1->STAR1;
I2C_Cmd(I2C1, DISABLE);
if(temp&0x0400)//应答错误
{
GPIO_WriteBit(GPIOB, GPIO_Pin_2, 0);//LED1 蓝色
}else if (temp&0x0100)//仲裁丢失
{
}else if (temp&0x4000)//超时
{
GPIO_WriteBit(GPIOB, GPIO_Pin_15, 1);//LED3 红灯
}else if (temp&0x0200)//过载 禁止时钟延长条件下
{
}else {
}
I2C_Cmd(I2C1, ENABLE);
}
//传输完成的回调函数
//extern void I2C_SlaveDMARxCpltCallback();
//I2C DMA接收
void DMA1_Channel7_IRQHandler(void)
{
if(DMA_GetITStatus(DMA1_IT_TC7)==SET)
{
// 从机 状态下 应该解析更新数据
// I2C_SlaveDMARxCpltCallback();
I2C1->CTLR1 ^=((uint16_t)0x0400);// 取消应答
I2C1->CTLR1 |= ((uint16_t)0x0200);// 发起停止事件 转换为从机模式 静默
DMA1_Channel7->CFGR &= (uint16_t)(~DMA_CFGR1_EN);//关闭dma指定通道
DMA_ClearITPendingBit(DMA1_IT_TC7);
GPIO_WriteBit(GPIOB, GPIO_Pin_14, 1);//LED2 绿色
}
}
//I2C DMA 发送完成
//void DMA1_Channel6_IRQHandler(void)
//{
// if(DMA_GetITStatus(DMA1_IT_TC6)==SET)
// {
// DMA1_Channel6->CFGR &= (uint16_t)(~DMA_CFGR1_EN);//关闭dma指定通道
// DMA_ClearITPendingBit(DMA1_IT_TC6);
//
// }
//}
//主机模式 尝试复位IIC
void Rst_i2c_salve()
{
uint8_t n = 9;
// GPIOB->CFGLR &= 0x00FFFFFF;// 复位
// GPIOB->CFGLR |= (uint32_t)0x44000000;//输入模式 PB6 PB7
// printf("IDR:0x%08X GPIOB:0x%08X\n",GPIOB->INDR,GPIOB->CFGLR);
if((GPIOB->INDR&0x00000080)==0)//读取SDA 总线 如果被拉低
{
I2C_GenerateSTART( I2C1, ENABLE );
Delay_Ms(5);
I2C_GenerateSTOP( I2C1, ENABLE );
Delay_Ms(20);
if((GPIOB->INDR&0x00000080))// SDA 为高 退出
{
return;
}
I2C_Cmd( I2C1, DISABLE );
GPIOB->CFGLR &= 0x00FFFFFF;// 复位
// GPIOB->CFGLR |= (uint32_t)0x33000000;//输出模式 PB6 PB7 通用推挽输出 50 MHz
GPIOB->CFGLR |= (uint32_t)0x77000000;//输出模式 PB6 PB7 通用开漏输出 50 MHz
// GPIOB->OUTDR |= 0x000000C0;
while(n>0)//发送9个时钟信号
{
GPIOB->OUTDR |= 0x00000040;//PB6 SCL 拉高
Delay_Ms(5);
GPIOB->OUTDR &= ~0x00000040;// SCL 拉低
n--;
if((GPIOB->INDR&0x00000080))// SDA 为高 退出
{
break;
}
}
// GPIOB->OUTDR &= ~0x00000080; //PB7 SDA 拉低
// GPIOB->OUTDR |= 0x00000040;//SCL 拉高
// Delay_Ms(20);
// GPIOB->OUTDR |= 0x00000080; //PB7 SDA 拉高
// GPIOB->CFGLR &= 0x00FFFFFF;// 复位
GPIOB->CFGLR |= 0xFF000000;// 复用 开漏输出 50 MHz
I2C1->STAR1 = 0;
I2C1->STAR2 = 0;
I2C_Cmd( I2C1, ENABLE );
}else if ((GPIOB->INDR&0x00000040)==0){//SCL 总线被拉低 尝试 恢复
I2C_GenerateSTOP( I2C1, ENABLE );
Delay_Ms(10);
I2C_Cmd( I2C1, DISABLE );
I2C1->STAR1 = 0;
I2C1->STAR2 = 0;
while((GPIOB->INDR&0x00000040)==0);//等待SCL总线 拉起
// Delay_Ms(500);
I2C_Cmd( I2C1, ENABLE );
}else {
I2C_Cmd( I2C1, DISABLE );
I2C1->DATAR = 0;
Delay_Ms(100);
I2C_Cmd( I2C1, ENABLE );
}
}