别急着点亮 OLED,先把 STM32 的 I2C 通信搞明白
我最近在学oled查相应的代码时,我发现网上的资源和讲解良莠不齐,我现在分享一下我的思路,我会用大概两篇的文章来说一下oled显示字符串、汉字的原理以及代码如何写,最后也会提供相应的我写的项目工程,这篇文章呢,会先讲解iic的原理以及硬件iic的代码如何写,我会一步步教大家的。由于我使用硬件i2c写的,可能会和网上的大部分人的代码不太一样,因为网上的博主们大部分用的是软件i2c写的,还请大家认真阅
目录
i2c的主发送过程(来自stm32f10x中文参考手册),这里结合代码进行讲解:
一 前言
我最近在学oled查相应的代码时,我发现网上的资源和讲解良莠不齐,我现在分享一下我的思路,我会用大概两篇的文章来说一下oled显示字符串、汉字的原理以及代码如何写,最后也会提供相应的我写的项目工程,这篇文章呢,会先讲解iic的原理以及硬件iic的代码如何写,我会一步步教大家的。
由于我使用硬件i2c写的,可能会和网上的大部分人的代码不太一样,因为网上的博主们大部分用的是软件i2c写的,还请大家认真阅读下去本篇文章,希望大家有不一样的收获。
二 iic通信
通信协议目前来说我自己了解到的有IIC和uart,IIC和uart之间的区别是:uart是两个设备进行信息传递和交互的,iic是多设备之间的交互,我们将这种“支持多设备进行通信的通信协议”称为总线协议;在iic中一共只有三条线:GND、SCL、SDA。在进入下面的各种专有名词讲解前,要有一个理念:一般来说SCL、SDA默认是高电平。
1 i2c的底层知识
(1)线路
GND:只进行接地,是为了保护电路的作用
SCL:负责提供精准的频率,他一直以1、0、1、0的电平进行传递
SDA:进行信息的传递,1代表高电平,0代表低电平
| 引脚 | IIC1 | IIC2 |
| SCL | PB5/PB8(要用重映射) | PB10 |
| SDA | PB6/PB9(要用重映射) | PB11 |
在stm32f10x的i2c引脚,i2c一共有两种模式,常用IIC1模式,同时要说明,在用PB5、PB6和PB8、PB9这两对要进行重映射,重映射的话就说明要给对应的引脚配置成复用xxx
(2)位和字节的关系
位:最小的数据单位,也就是二进制中的一个“0”、“1”,相当于硬件的高电平
字节:由8个位构成的数据单位,我们在之后iic传输命令、数据的时候,都是以一个字节来传输的,也就是发送八个二进制数
(3)起始位、停止位
- 起始位:当SCL是高电平时,SDA从高到低的跳变
- 停止位:当SCL是高电平时,SDA从低到高的跳变
(4)应答位
现在让单片机读取SDA的电平
若SDA=高电平,从设备并没有收到数据;
SDA=低电平,从设备有应答,设备收到了数据。
特别说明一点:在传输数据的时候,要保证传输数据的有效性,当SCL是高电平的时候,必须要保证SDA传输数据的时候电平不能跳
2 数据传输
在数据传输的过程中,对于地址来说有两种,7位、10位,我们这次只讲7位地址的传输。有写和读两个过程,在我们本次oled屏幕中,我们只有到了写过程,但是两个部分都要会,在后续的eeprom、adc、dac等,就需要运用到了,本次呢,主要侧重讲写的过程,主发送。

i2c的主发送过程(来自stm32f10x中文参考手册),这里结合代码进行讲解:

如果在代码里面体现我们就需要写成这样,一步步的分析:
while (I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY));
这一行是检查I2C1模式对应的路线是否繁忙,如果I2C_GetFlagStatus是繁忙的,那也就是返回值是1,while(1);不进行下一行的代码,他就会卡死在这里。
I2C_GenerateSTART(I2C1, ENABLE);
这是发送一个起始信号,这里的代码是在stm32f10x_i2c.h中
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));
这行是检查事件EV5,主机模式已经成功发送了起始信号START,前面I2C_GenerateSTART写完就紧跟一个EV5事件
这里大家不必担心,打开这个I2C_CheckEvent函数,即可看见EV5等所有的事件
这里为什么要在这个函数前面加一个!呢?
while (!“门开了”);,言外之意就是门没打开就一直等,换到上面这个代码上就是EV5没触发就一直等
剩下的步骤请大家自行理解,若不理解,请留言评论区,我再单独给每个人解释。
同时还需要解释一件事:这里传输的addr和data是什么?
addr:这里是寄存器地址,oled在iic的地址是0x3c,但是我们不能这么写,我们要将0x3c向左移动一位,把最后一位给空出来,然后再在最后加一个0(读是1,写是0),最后的呈现的代码OLED_ADDRESS是0x78
data:这里的数据,并不是oled传输的数据,而是你要让oled干什么,oled有命令、数据两种模式
3 IIC的初始化配置

这里我是选择I2C1模式,将SCL接入了PB6,SDA接入了PB7,大家也可以接PB8,PB9,大家记得引脚是复用功能哈
1 这里为什么用复用开漏输出呢?
AF:引脚复用功能,不当普通 GPIO 用,而作为 I2C 功能使用
OD(开漏模式):因为SCL、SDA默认是接入了上拉电阻,为了避免“多个设备同时驱动导致短路”,所有设备只能拉低线,而不能直接输出高电平
2 I2C_DutyCycle_2是什么东西?
Duty Cycle = 占空比,就是 I2C 时钟(SCL)的低电平时间和高电平时间的比例,
如果一个 SCL 周期是 10μs,高电平占了 4μs,低电平 6μs;
那它的占空比就是 6:4,也可以写成 3:2
其中传输速度上有标准模式100k和快速模式400k,下面两种模式都是快速模式下的占空比
配置宏 Tlow:Thigh 占空比含义 I2C_DutyCycle_21:1 高电平 = 低电平(标准对称方波) I2C_DutyCycle_16_916:9 低电平时间略长(兼容某些设备)
一般来说选I2C_DutyCycle_2,这两个没什么速度上太大的区别
3 为什么要写这行代码I2C_DeInit(I2C1);?
把之前 I2C1 配置过的东西全清空,重新开始配置
三 总结代码
关于iic硬件的底层代码,就已经到这里讲完了,关于oled显示的讲解,请看下一篇文章,相关oled.c的i2c底层代码如下:
void I2C_Configuration(void)
{
I2C_InitTypeDef I2C_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);
//PB6--SCL: PB7--SDA
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
I2C_DeInit(I2C1);
I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;
I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
I2C_InitStructure.I2C_ClockSpeed = 400000;
I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;
I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
I2C_InitStructure.I2C_OwnAddress1 = 0x30;
I2C_Init(I2C1, &I2C_InitStructure);
I2C_Cmd(I2C1, ENABLE);
}
void I2C_WriteByte(uint8_t addr, uint8_t data)
{
while (I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY)); //检查I2C总线是否繁忙
I2C_GenerateSTART(I2C1, ENABLE); //开启I2C1
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)); //EV5,主模式
I2C_Send7bitAddress(I2C1, OLED_ADDRESS, I2C_Direction_Transmitter); //发送器件地址
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));
I2C_SendData(I2C1, addr); //寄存器地址
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTING));
I2C_SendData(I2C1, data);
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTING));
I2C_GenerateSTOP(I2C1, ENABLE); //关闭I2C1总线
}
更多推荐




所有评论(0)