目录

一 前言

二 iic通信

1 i2c的底层知识

 2 数据传输

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

同时还需要解释一件事:这里传输的addr和data是什么?

3 IIC的初始化配置

1 这里为什么用复用开漏输出呢?

 2 I2C_DutyCycle_2是什么东西?

 3 为什么要写这行代码I2C_DeInit(I2C1);?

把之前 I2C1 配置过的东西全清空,重新开始配置

三 总结代码


一 前言

我最近在学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代表低电平

IIC引脚
引脚 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_2 1:1 高电平 = 低电平(标准对称方波)
I2C_DutyCycle_16_9 16: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总线
}

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐