目录
前言
MCU:stm32f103C8T6
在IIC的硬件配置中并没有找到选择主从模式的原因是这个,默认从模式,但是发送起始信号的时候会自动切换,比较方便,等于双模式
stm32硬件iicBUG在于总线挂死以及事件处理(比如初始化硬件IIC后双线被拉低以及在通信过程中出现死锁,时钟或数据线被锁住,以及挂死,时钟或数据线被一直拉低,无法释放),比较麻烦,所以硬件外设自身问题比较多,默认为从模式,当发起起始信号的时候转为主模式,主模式停止后马上转从模式,这是stm32硬件IIC的特性,所有事件在数据手册上有
以下操作基于IIC标准库功能函数实现自定义通信功能函数(比如检测应答和发生应答或非应答以及操作性),函数本质也是操作寄存器,可以自己手写也可以调用。所有主模式的实现需要符合以下两张事件发生图的操作,解决完事件就可以正常使用
1.起始信号
// 发送起始信号切换主模式并寻址 需要指定是写地址或读地址 0 write 1 read 返回0表示寻址成功 u8 IIC1_START_Address(u8 addr, u8 mode) { uint32_t num = 0; I2C_GenerateSTART(I2C1, ENABLE); // 起始信号 主模式下可以重复起始位 // EV5 while (I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT) != SUCCESS && num < 1000) // 等待事件出来 { num++; } I2C1->SR1; // 读SR1; if (!mode) { I2C_Send7bitAddress(I2C1, addr, I2C_Direction_Transmitter); // EV6 I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED 发送 while (I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED) != SUCCESS) // 等待置1 ADDR位 { num++; if (num > 10000) { return 1; } } } else { I2C_Send7bitAddress(I2C1, addr, I2C_Direction_Receiver); // 接收地址的EV6事件 while (I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED) != SUCCESS) // 等待置1 ADDR位 { num++; if (num > 10000) { return 1; } } } I2C1->SR1; // 读SR1; I2C1->SR2; // 读取SR2清除 return 0; }
这里唯一需要注意的是,调用发送器件地址函数 I2C_Send7bitAddress 我开始以为填7bit的地址,然后它自己会补全,但是通过逻辑分析仪看到它没有补全,就硬生生发了7位地址,所以在这里需要填7bit器件地址+1bit读写地址,因为读写的器件寻址各有参数,所以我在这里设置了函数参数进行区分,EV6是寻址共有的事件,表示寻址成功
I2C_CheckEvent这个函数是标准库函数,查询对应事件的状态有没有发生,这个很关键,我们需要等待这个事件出来再去清除,如果清除的操作过快,可能事件还没出来我们就已经跳过,所以需要等待
2.发送数据以及检测应答
// 发送一字节数据并检测应答 返回0表示ACK 1 NACK u8 IIC1_Send_data(u8 data) { uint32_t num = 0; // EV8_1 这里目前可加可不加有待考察 建议不加 // while (I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTING) != SUCCESS) // { // num++; // if (num > 10000) // { // return 1; // } // } // I2C1->DR = 1;//写DR寄存器清除 while (!(I2C1->SR1 & (1 << 7))); // 等待发送数据为空 I2C_SendData(I2C1, data); while (!(I2C1->SR1 & (1 << 2))) // 等待置BTF置1 { num++; __NOP(); if (num > 72000) { return 1; } } return 0; }
虽然没有直接调用函数,但是本质还是经过了事件并且清除
I2C_SendData(I2C1, data); 为标准库函数,发送一字节数据
3.接收数据并决定是否应答
// 接收一字节数据并决定是否发送应答 返回接收到的数据 0 ack 1nack u8 IIC1_EV6_1_contflag = 1;//EV6_1事件控制标志位 非常关键 u8 IIC1_Receive_data(u8 ack) { u8 data = 0; uint32_t num = 0; //EV6_1 恰好在EV6之后(即清除了ADDR之后),要清除响应和停止条件的产生位。 if(IIC1_EV6_1_contflag) { I2C1->CR1 &= ~(1<<11); I2C1->CR1 &= ~(1<<9); IIC1_EV6_1_contflag = 0; } if (ack) { //EV7_1 I2C1->DR;//读DR寄存器清除该事件 I2C_AcknowledgeConfig(I2C1, DISABLE); //如果非应答 在数据之前需要读一下DR寄存器 I2C_GenerateSTOP(I2C1, ENABLE); } else { I2C_AcknowledgeConfig(I2C1, ENABLE); printf("ACK"); } //EV7 在应答这个位之后 while (I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED) != SUCCESS) // 等待事件 { num++; if (num > 10000) { return 1; } } data = I2C_ReceiveData(I2C1); return data; }
这个的步骤就比较多而且每一步都不能少,特别是EV6_1事件只会在每一次接收出现一次,如果是多字节接收,标志位可以在后续不执行这个事件
还有一个重点是非应答的处理,如果是非应答,在接收最后一个字节之前需要提前生成停止信号以及消除对应事件的影响
4.停止信号
// 生成停止信号 void IIC1_STOP(void) { u8 num = 0; //等EV8_2的事件 没有的话为主接收器的停止位 while(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED) != SUCCESS) // 等待事件 { if (num > 250) { break; } num++; __NOP();__NOP();__NOP();__NOP();__NOP();__NOP(); } I2C_GenerateSTOP(I2C1, ENABLE); IIC1_EV6_1_contflag = 1;//还原 for(u8 i = 0;i < 72 * 2;i++) __NOP(); I2C_GenerateSTOP(I2C1, DISABLE); }
这里停止信号特殊的地方在于EV8_2事件的处理以及停止信号失能处理。
如果是单独写而不接收的话,会出现EV8_2事件,如果有接收数据则不会出现这个事件,需要为了兼容需要进行处理,以及最重要的失能停止位,在调试过程中发送,如果以及读取过一次数据,在发起第二次的时候发现起始信号无法产出且无法响应主模式,这里的话其实是和接收数据发生了冲突,在非应答的接收中,前面我们以及启动了停止信号,所以已经产生停止信号且被硬件消除控制,然后在这个函数中又被产生,但是总线不会在发生停止信号,所以这个控制位不会被硬件消除,导致发生起始位的时候检测到停止位也有,所以发生了错误,所以在这延时后失能停止信号就可以了。
上述硬件IIC框架只限于stm32的硬件IIC控制器,解决了其中的BUG,并且兼容主模式下所有的情况,测试后就可以正常使用,且速度符合设置参数。
附初始化函数
// 宏定义 IO_IIC 表示使用软件模拟IIC 注释表示使用硬件IIC
// #define IO_IIC 1
#define I2C1_Delay 6 // 软件IIC的延时 可以到0 500K 1 300K 2 200k 3 150k 6 100K
void IIC1_Init(void)
{
// CLOCK
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);
// GPIO
GPIO_InitTypeDef GPIO_InitStruct = {0};
#ifdef IO_IIC
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_OD;
#else
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_OD;
#endif
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStruct);
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_5;
GPIO_Init(GPIOB, &GPIO_InitStruct);
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_8;
GPIO_Init(GPIOB, &GPIO_InitStruct);
#ifdef IO_IIC
IIC_SCL_H(2);
IIC_SDA_H(2);
#else
I2C_InitTypeDef I2C_InitStruct = {0};
I2C_InitStruct.I2C_ClockSpeed = 100000; // 100K
I2C_InitStruct.I2C_Mode = I2C_Mode_I2C; // IIC模式
I2C_InitStruct.I2C_DutyCycle = I2C_DutyCycle_2; // 快速模式下的占空比
I2C_InitStruct.I2C_OwnAddress1 = 0x0a; // 自身设备地址
I2C_InitStruct.I2C_Ack = I2C_Ack_Enable; // 启用应答 在接收到一个字节后返回一个应答(匹配的地址或数据)
I2C_InitStruct.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit; // 7位地址
I2C_Init(I2C1, &I2C_InitStruct);
I2C_Cmd(I2C1, ENABLE);
#endif
MPU6050_ADDR_L; // 地址固定0
}