#更新通知:2023-09-06 STM32L151 固件库 使用I2C 太难了,又宕机了,建议不要在固件库版本上尝试硬件IIC 了,一般人真用不了,直接使用软件模拟的,或者不要使用固件库了,用HAL 库吧,据说HAL 库没这么多问题,不死心的我还是死心了,等有空再研究吧
1. STM32L151C8T6 硬件IIC 控制OLED 屏,OLED 驱动IC CH1116G, 查阅OLED 数据手册
2. STM32 硬件IIC 初始化,用的标准库,固件库
// stm32l151c8t6 as master, oled control ic (CH1116G) as slave, and communicate by master iic2
void STM32L151C8T6_IIC_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
I2C_InitTypeDef I2C_InitStruct;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOB, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2, ENABLE);
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStruct.GPIO_OType = GPIO_OType_PP; // GPIO_OType_OD, GPIO_OType_PP
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_400KHz;
GPIO_Init(GPIOB, &GPIO_InitStruct); // IIC2 SCL - PB10, SDA - PB11
GPIO_ResetBits(GPIOB, GPIO_Pin_11);
delay_xms(20);
GPIO_PinAFConfig(GPIOB, GPIO_PinSource10, GPIO_AF_I2C2); // set PB10 as IIC2 SCL
GPIO_PinAFConfig(GPIOB, GPIO_PinSource11, GPIO_AF_I2C2); // set PB11 as IIC2 SDA
I2C_InitStruct.I2C_Ack = I2C_Ack_Enable;
I2C_InitStruct.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
I2C_InitStruct.I2C_ClockSpeed = iic_clockSpeed_400Khz; // must be less than 100 Khz
I2C_InitStruct.I2C_DutyCycle = I2C_DutyCycle_2;
I2C_InitStruct.I2C_Mode = I2C_Mode_SMBusHost; // 这里很重要
I2C_InitStruct.I2C_OwnAddress1 = IIC2_NOT_USE_OWN_ADDR; // do not use own address
I2C_Init(I2C2, &I2C_InitStruct);
I2C_Cmd(I2C2, ENABLE);
I2C_AcknowledgeConfig(I2C2, ENABLE);
}
3. GPIO 引脚速率要和 I2C 速率匹配,这很重要
3.1 I2C模式,我这里选的是主机模式,选其它模式就会出问题
4. OLED 硬件I2C 发送函数封装,给OLED发送的东西分两种:①发的是数据,②发的是命令
// master (STM32L151C8T6) send cmd instruction to oled screen control ic (CH1116G)
void OLED_SendCmd(uint8_t cmd)
{
WaitFor_IIC_ReadyToWorking();
I2C_GenerateSTART(I2C2, ENABLE); // iic start signal
IIC_SendStartSignal_CheckEvent();
I2C_Send7bitAddress(I2C2, OLED_ADDRESS, I2C_Direction_Transmitter); // send device addr and write bit
I2C_SendDeviceAddrWaitAck();
IIC_SendByteToOLED(iic_transmitType_Cmd);
IIC_Delay(IIC_TIMEOUT_COUNTER);
I2C_SendData(I2C2, cmd);
I2C_SendByteDataWaitAck();
I2C_GenerateSTOP(I2C2, ENABLE);
IIC_Delay(IIC_TIMEOUT_COUNTER);
}
5. IIC 发送数据之前先检查I2C 是否有空,STM32 为了规避飞利浦的发明专利,把硬件I2C 搞的很复杂,导致固件库会有卡死的问题,据说HAL 库解决了这问题,本人没用过HAL库
static void WaitFor_IIC_ReadyToWorking(void)
{
while (I2C2->SR2 & 0x02)
{
INFO_LOG("[WaitFor_IIC_ReadyToWorking] i2c2 is busy\r\n");
}
}
6. 发送完起始信号后,要检查起始信号事件, 不要用固件库检查事件函数,会卡死的
static void IIC_SendStartSignal_CheckEvent(void)
{
while (!((uint16_t)(I2C2->SR1) & (uint16_t)(0x0001)))
{
printf("[IIC_SendStartSignal_CheckEvent][] I2C_SR1=0x%04x, I2C2_SR2=0x%04x\r\n", I2C2->SR1, I2C2->SR2);
}
while (!((uint16_t)(I2C2->SR2) & (uint16_t)(0x0003)) == 0x0003)
{
printf("[IIC_SendStartSignal_CheckEvent][] I2C_SR1=0x%04x, I2C2_SR2=0x%04x\r\n", I2C2->SR1, I2C2->SR2);
}
}
7. 发送完设备地址也要检查发送设备地址这个事件,同样不能用固件库函数
static void I2C_SendDeviceAddrWaitAck(void)
{
while (!((uint16_t)(I2C2->SR1) & (uint16_t)(0x0082)) == 0x0082)
{
}
while (!((uint16_t)(I2C2->SR2) & (uint16_t)(0x0007)) == 0x0007)
{
}
}
8. 发送命令给OLED 屏,这里要发两次,我也暂时没弄明白为什么,这个地方搞死我了,好惨
static void IIC_SendByteToOLED(uint8_t mode)
{
IIC_Delay(IIC_TIMEOUT_COUNTER);
I2C_SendData(I2C2, mode); // 0x00, high 8-bits, cmd code, iic_transmitType_Cmd
I2C_SendByteDataWaitAck();
IIC_Delay(IIC_TIMEOUT_COUNTER);
I2C_SendData(I2C2, mode); // 0x00, low 8-bits, cmd code
I2C_SendByteDataWaitAck();
}
9. 发送完命令后要检查标志位, while 里面可以啥都不写,空转,死等,也可以加一个超时退出,但是超时退出只能解决卡死的问题,并不能解决I2C busy 卡死导致发不出去数据的问题,治标不治本,这一点被别人说的坑死了,网上有人说超时退出可以解决卡死问题,但仅仅只解决了卡死问题,I2C 还是没有工作起来,我们的最终目的是让I2C 工作起来
static void I2C_SendByteDataWaitAck(void)
{
while (!((uint16_t)(I2C2->SR1) & (uint16_t)(0x0080)) == 0x0080)
{
}
while (!((uint16_t)(I2C2->SR2) & (uint16_t)(0x0007)) == 0x0007)
{
}
}
10. OLED 初始化,不同的驱动IC 会有所区别,但是可以先用我这个套用一下试试,我是在STM32L151C8T6 芯片上还跑了FreeRTOS 实时操作系统的
void OLED_Init(void)
{
delay_xms(20); // oled startup slowly than stm32l151c8t6
INFO_LOG("[OLED_Init] init start\r\n");
OLED_SendCmd(0xAE); // display off
OLED_SendCmd(0x02); // set colum start address
OLED_SendCmd(0x10); // set colum end address
OLED_SendCmd(0x40); // set start line (first row)
OLED_SendCmd(0xB0); // set page address
OLED_SendCmd(0x81); // set contrast ratio
OLED_SendCmd(0xCF); // 128
OLED_SendCmd(0xA1); // set segment remapping, from right to left
OLED_SendCmd(0xA6); // forward display, normal or reverse
OLED_SendCmd(0xA8); // multiple reuse rate, multiple ratio
OLED_SendCmd(0x3F); // duty = 1 / 64
OLED_SendCmd(0xAD); // set charge pump enable
OLED_SendCmd(0x8B); // enable DC-DC
OLED_SendCmd(0x33); // set VPP = 10V
OLED_SendCmd(0xC8); // set output scan direction, COM[N - 1] to COM[0], COM scan direction
OLED_SendCmd(0xD3); // set display offset
OLED_SendCmd(0x00); // 0x00
OLED_SendCmd(0xD5); // set internal clock frequence, set osc frequency
OLED_SendCmd(0xC0);
OLED_SendCmd(0xD9); // set pre-charge period
OLED_SendCmd(0x1F); // 0x22
OLED_SendCmd(0xDA); // set COM pins, pin layout
OLED_SendCmd(0x12);
OLED_SendCmd(0xDB); // set electrical level, set VCOMH
OLED_SendCmd(0x40);
OLED_SendCmd(0xAF); // enable display, display on
INFO_LOG("[OLED_Init] init complete\r\n");
}
11. OLED 测试函数封装,B 站博主keysking
void OLED_Test(void)
{
OLED_SendCmd(0xB0); // page 0
OLED_SendCmd(0x00); // colume 0 low 4-bits
OLED_SendCmd(0x10); // colume 0 high 8-bits
OLED_SendCmd(0x40);
OLED_SendCmd(0xAA);
}
12. 主函数调用OLED 初始化函数和测试函数,先延时一会再初始化,因为STM32 比OLED的控制IC 起来的快很多,CH1116G
int main(void)
{
delay_xms(1000);
OLED_Init();
OLED_Test();
}
13. IIC_Delay 函数
#define IIC_TIMEOUT_COUNTER 0x1000 // iic transmit timeout
static void IIC_Delay(uint32_t delay_time)
{
uint32_t delayTime;
for (delayTime = 0; delayTime < delay_time; delayTime++)
{
}
}