记一次STM32使用I2C PinRemap引脚重映射出现卡死现象

在移植WouoUI到STMF103C8 BluePill board时,发现会出现上电无法正常初始化屏幕,debug检查发现卡死在I2C检查函数(如下图)

I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)

image

本人遇到的现象:在习惯使用的(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为高速开漏输出模式
}
posted @ 2024-08-05 21:19  月舞纱雨日  阅读(68)  评论(1编辑  收藏  举报