记一次STM32使用I2C PinRemap引脚重映射出现卡死现象
在移植WouoUI到STMF103C8 BluePill board时,发现会出现上电无法正常初始化屏幕,debug检查发现卡死在I2C检查函数(如下图)
I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)
本人遇到的现象:在习惯使用的(SWI2C / HWI2C)@(PB8->SCL PB9->SDA)连接OLED的情况下,大多数情况使用江科大的SWI2C,一切正常。
今天跑某开源基于u8g2库的UI框架 WouoUI(HWI2C)@(PB6->SCL PB7->SDA) 遇到了一旦连接OLED的正负极跳线就会导致STM32卡死的现象。意识到可能这就是传说中的标准库 I2C Bug,网上搜寻方案后成功解决,但是I2C的通信速率不能调的和非重映射的一样高,原因未知。
主要原因:加入的OLED供电正负极跳线会导致PB6 PB7被动受到外部电平影响,STM32芯片硬件I2C接口支持多主设备同时使用,内核检测到当出现非自己发出的电平变化等情况后,芯片判定为有其他的主机在操作总线,这样STM32的BUSY(总线忙标志)则会置位,只有在检查到一次I2C协议的停止位后才会硬件清除该标志,此时总线当然只有该芯片单个主机,不会有对应的停止位信号到来,故卡死在等待函数中。
解决方法:在初始化之前使用软件复位,或更换 HAL 库
步骤:
① 重映射配置
在GPIO的RCC使能函数下,加入如下语句:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
GPIO_PinRemapConfig(GPIO_Remap_I2C1, ENABLE); //重映射到PB8/PB9
② 在所有I2C1有关的配置寄存器写入任何数据之前,加入如下语句:
I2C1->CR1 |= (1 << 15);
I2C1->CR1 &= ~(1 << 15);
完整的I2C初始化代码如下:
#define IIC_OLED_SCL_Pin GPIO_Pin_8
#define IIC_OLED_SCL_GPIO GPIOB
#define IIC_OLED_SCL_GPIO_CLK RCC_APB2Periph_GPIOB
#define IIC_OLED_SDA_Pin GPIO_Pin_9
#define IIC_OLED_SDA_GPIO GPIOB
#define IIC_OLED_SDA_GPIO_CLK RCC_APB2Periph_GPIOB
/*引脚初始化*/
void OLED_I2C_Init(void)
{
I2C_InitTypeDef I2C_InitStructure = {0};
GPIO_InitTypeDef GPIO_InitStructure = {0}; //定义GPIO结构体
I2C1->CR1 |= (1 << 15);
I2C1->CR1 &= ~(1 << 15); // 防止重定义之前,V与G连接PB6 PB7导致的I2C卡死
RCC_APB2PeriphClockCmd(IIC_OLED_SDA_GPIO_CLK, ENABLE); //开启GPIO模块的时钟
RCC_APB2PeriphClockCmd(IIC_OLED_SCL_GPIO_CLK, ENABLE); //开启GPIO模块的时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
GPIO_PinRemapConfig(GPIO_Remap_I2C1, ENABLE); //重映射到PB8/PB9需要加这两行(无关位置)
I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;
I2C_InitStructure.I2C_OwnAddress1 = 0x00;
I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;
I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
I2C_InitStructure.I2C_ClockSpeed = 285 * 1000; // N*1000
I2C_Init(I2C1, &I2C_InitStructure);
I2C_Cmd(I2C1, ENABLE);
GPIO_InitStructure.GPIO_Pin = IIC_OLED_SDA_Pin; //配置SDA端口
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD; // 设置GPIO的模式为输出模式
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(IIC_OLED_SDA_GPIO, &GPIO_InitStructure); // 初始化GPIO为高速开漏输出模式
GPIO_InitStructure.GPIO_Pin = IIC_OLED_SCL_Pin; //配置SCL端口
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD; // 设置GPIO的模式为输出模式
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 配置 I/O 口的速度为:50MHz,还可选:GPIO_Speed_2MHz,GPIO_Speed_10MHz
GPIO_Init(IIC_OLED_SCL_GPIO, &GPIO_InitStructure); // 初始化GPIO为高速开漏输出模式
}