【STM32】| 02——常用外设 | I2C

这篇具有很好参考价值的文章主要介绍了【STM32】| 02——常用外设 | I2C。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

系列文章目录
【STM32】| 01——常用外设 | USART
【STM32】| 02——常用外设 | I2C


失败了也挺可爱,成功了就超帅。

前言

本文详细介绍 I2C协议及 MCU I2C配置使用

1. 简介

I2C是一种常用的串行通信总线,由串行数据线SDA 和串线时钟线SCL组成。I2C是一种多主机控制总线,由飞利浦公司为了让主板、嵌入式系统或手机用以连接低速周边设备而发展。I2C 通讯协议(Inter-Integrated Circuit)是由于它引脚少,硬件实现简单,可扩展性强,不需要外部收发设备,被广泛地使用在系统内多个集成电路(IC)间的通讯。
I2C支持 0KHZ-5MHZ设备通信(hz相当于bps)。有如下几种模式

  • 普通模式 ——100kHz

  • 快速模式——400kHz

  • 快速模式——1MHz

  • 高速模式——3.4MHz

  • 超高速模式——5MHz

    我们常用400KHZ 在此基础上 Inter提出了SMBUS系统总线管理 该规范限制了通信速率10K-100KHZ.

I2C是一种主从通信 支持多主多从的总线。

2. I2C协议

2.1 I2C物理连接

【STM32】| 02——常用外设 | I2C,单片机MCU,# STM32,stm32,嵌入式硬件,单片机
如图 可以看到索引 I2C 设备都通过 SDA/SCL 连接到总线 总线接有上拉电阻(后面讲原因)
I2C特性如下:

  • 总线只需两条线路:一条串行数据线 SDA 一条串行时钟线 SCL并利用电阻将电位上拉
  • 每个连接到总线的器件都可以通过唯一的地址和一直存在的简单的主机/从机关系软件设定地址,主机可以作为主机发送器或主机接收器
  • 它是一个真正的多主机总线 如果两个或更多主机同时初始化数据传输可以通过冲突检测仲裁机制可防止数据被破坏
  • 串行的 8位双向数据传输位速率在标准模式下可达 100kbit/s 快速模式下可达 400kbit/s 高速模式下可达 3.4Mbit/s 单向传输可以高达5Mbit/s
  • 片上的滤波器可以滤去总线数据线上的毛刺波保证数据完整
  • 连接到相同总线的 IC 数量只受到总线的最大电容 400pF 限制

SCL和SDA都是双向的通过上拉电阻连接电源 总线在空闲时都输出高电平 总线具有 线与功能。

2.2 I2C通信协议

该协议约定了通信的起始、停止信号以及数据有效性、响应、仲裁同步、地址广播等。

2.2.1 起始和停止信号

【STM32】| 02——常用外设 | I2C,单片机MCU,# STM32,stm32,嵌入式硬件,单片机

起始信号:SCL高电平时,SDA由高电平转换为低电平
停止信号:SCL高电平时,SDA由低电平转换为高电平

起始信号和停止信号一般都是主机发出,当有起始信号时,总线就会处于被占用状态,当有停止信号时,总线处于空闲状态。

2.2.2 数据有效性

【STM32】| 02——常用外设 | I2C,单片机MCU,# STM32,stm32,嵌入式硬件,单片机
SDA数据线在 SCL 的每个时钟周期传输一位数据。传输时, SCL 为高电平的时候 SDA 表示的数据有效,即此时的 SDA 为高电平时表示数据“1”,为低电平时表示数据“0”。当 SCL为低电平时, SDA 的数据无效,一般在这个时候 SDA 进行电平切换(数据位切换高/低),为下一位要传输的数据做好准备(即下一位要传1 SDA切换为高电平反之低平)。

2.2.3 数据传输格式

【STM32】| 02——常用外设 | I2C,单片机MCU,# STM32,stm32,嵌入式硬件,单片机

SDA 线上的每个字节必须为 8 位 每次传输可以发送的字节数量不受限制 每个字节后必须跟一个响应位 首先传输的是数据的最高位 MSB 如果从机要完成一些其他功能后(例如一个内部中断服务程序) 才能接收或发送下一个完整的数据字节 可以使时钟线 SCL 保持低电平迫使主机进入等待状态 当从机准备好接收下一个数据字节并释放时钟线 SCL 后 数据传输继续。

2.2.4 从机地址/数据方向(R/W位)

MSB:代表高位
如图所示,为一个7位地址,I2C 总线上的每个设备都有自己的独立地址,主机发起通讯时,通过 SDA 信号线发送设备地址(SLAVE_ADDRESS)来查找从机。 I2C 协议规定设备地址可以是 7 位或 10 位。
设备地址后面的一个数据位R/W用来表示数据传输方向,数据方向位为“1”时表示主机由从机读数据,该位为“0”时表示主机向从机写数据
【STM32】| 02——常用外设 | I2C,单片机MCU,# STM32,stm32,嵌入式硬件,单片机

下图是完整的传输时序
【STM32】| 02——常用外设 | I2C,单片机MCU,# STM32,stm32,嵌入式硬件,单片机

2.2.5 响应与不响应(ACK/NACK)

【STM32】| 02——常用外设 | I2C,单片机MCU,# STM32,stm32,嵌入式硬件,单片机
I2C的数据和地址传输都带响应。响应包括“应答(ACK)”和“非应答(NACK)”两种信号。
数据传输时主机产生时钟,在第 9 个时钟时,数据发送端会释放 SDA 的控制权,由数据接收端控制 SDA,若 SDA 为高电平,表示非应答信号(NACK),低电平表示应答信号(ACK)。

作为数据接收端时,当设备(主/从机)接收到 I2C 传输的一个字节数据或地址后,
若希望对方继续发送数据,则需要向对方发送“应答(ACK)”信号,发送方会继续发送下
一个数据;若接收端希望结束数据传输,则向对方发送“非应答(NACK)”信号,发送方接
收到该信号后会产生一个停止信号,结束信号传输。

2.2.6 同步与仲裁机制

总线上的设备可以抽象为节点。在多主通信中,总线上会有很多节点,它们都有自己的寻址地址,可以作为从节点被别的节点访问,同时它们都可以作为主节点向其他的节点发送控制字节和传送数据。但是如果有两个或两个以上的节点都向总线上发送请求时,这样就形成了冲突。要解决这种冲突,就要进行同步/仲裁,这就是I 2C总线上的同步/仲裁。
同步指:SCL同步
仲裁指:SDK仲裁

1. SCL同步

【STM32】| 02——常用外设 | I2C,单片机MCU,# STM32,stm32,嵌入式硬件,单片机
SCL同步是由于总线具有线“与”的逻辑功能。
1、只要有一个节点发送低电平时,总线上就表现为低电平。
2、当所有节点都发送高电平时,总线上就表现为高电平。

2. SDA仲裁

SDA线的仲裁也是建立在总线具有线“与”逻辑功能上的。节点在发送1位数据后,比较SDA线上所呈现的数据与自己发送的是否一致。是,继续发送;否则,退出竞争。SDA线的仲裁可以保证I2C总线系统在多个主节点同时企图控制总线时通信正常进行并且数据不丢失。总线系统通过仲裁只允许一个主节点可以继续占据总线
【STM32】| 02——常用外设 | I2C,单片机MCU,# STM32,stm32,嵌入式硬件,单片机
上图是以两个节点为例的仲裁过程。DATA1和DATA2分别是主节点向总线所发送的数据信号,SDA为总线上所呈现的数据信号,SCL是总线上所呈现的时钟信号。
当主节点1、2同时发送起始信号时,两个主节点都发送了高电平信号。这时总线上呈现的信号为高电平,两个主节点都检测到总线上的信号与自己发送的信号相同,继续发送数据。
第2个时钟周期,2个主节点都发送低电平信号,在总线上呈现的信号为低电平,仍继续发送数据。
在第3个时钟周期,主节点1发送高电平信号,而主节点2发送低电平信号。根据总线的线“与”的逻辑功能,总线上的信号为低电平,这时主节点1检测到总线上的数据和自己所发送的数据不一样,就断开数据的输出级,转为从机接收状态。这样主节点2就赢得了总线,而且数据没有丢失,即总线的数据与主节点2所发送的数据一样,而主节点1在转为从节点后继续接收数据,同样也没有丢掉SDA线上的数据。因此在仲裁过程中数据没有丢失。

总结:SDA仲裁和SCL时钟同步处理过程没有先后关系,而是同时进行的。

2.3 I2C读写操作过程

下面通过主机到从机的读写操作进行介绍

主机产生起始信号后,所有从机就开始等待主机紧接下来 广播 的从机地址信号
(SLAVE_ADDRESS)。 在 I2C 总线上,每个设备的地址都是唯一的, 当主机广播的地址与
某个设备地址相同时,这个设备就被选中了,没被选中的设备将不回接受之后的数据信号。
根据 I2C 协议,从机地址可以是 7 位或 10 位。
在地址位之后,是(RW位)传输方向的选择位,该位为 0 时,表示后面的数据传输方向是由主机传输至从机,即主机向从机写数据。该位为 1 时,则相反,即主机由从机读数据。
从机接收到匹配的地址后,主机或从机会返回一个应答(ACK)或非应答(NACK)信号,
只有接收到应答信号后,主机才能继续发送或接收数据。

2.3.1 Master向7位地址Savle写数据

【STM32】| 02——常用外设 | I2C,单片机MCU,# STM32,stm32,嵌入式硬件,单片机

斜线:主机传输至从机 S :传输开始信号 SLAVE_ADDRESS: 从机地址
空白:从机传输至主机 A/A:应答(ACK)或非应答(NACK)信号

I2C主机设备 向一个具有7位地址的I2C从机设备写入N个字节数据的数据帧格式:主机先发送开始信号+7位地址+1位R/W位+响应位(从机响应ACK)继续传输+[N字节数据+从机ACK]
如果从机不想接收了 回应NACK 停止传输 主机发送停止信号

2.3.2 Master从7位地址Savle读取数据

【STM32】| 02——常用外设 | I2C,单片机MCU,# STM32,stm32,嵌入式硬件,单片机
斜线:主机传输至从机 S :传输开始信号 SLAVE_ADDRESS: 从机地址
空白:从机传输至主机 A/A:应答(ACK)或非应答(NACK)信号 DATA数据

若数据方向位配置为“1读数据”方向, 即如图所示, 主机发送起始位+广播完地址,等待接收到从机应答信号后, 从机开始向主机返回数据(DATA),数据包大小也为 8 位,从机每发送完一个数据,都会等待主机的应答信号(ACK),重复这个过程,可以返回 N 个数据,这个 N 也没有大小限制。当主机希望停止接收数据时,就向从机返回一个非应答信号(NACK),则从机自动停止数据传输。

2.3.3 Master向7位地址Savle写/读取数据 (组合通信)

【STM32】| 02——常用外设 | I2C,单片机MCU,# STM32,stm32,嵌入式硬件,单片机
除了单独的读和写, I2C 通讯更常用的是复合格式,即如图所示,
该传输过程有两次起始信号(S)。一般在第一次传输中,主机通过 SLAVE_ADDRESS 寻找到从设备后,发送一段“数据”,这段数据通常用于表示从设备内部的寄存器或存储器地址(注意区分它与SLAVE_ADDRESS 的区别);在第二次的传输中,对该地址的内容进行读或写。也就是说,第一次通讯是告诉从机,主机要读写从机的地址,第二次则是读写的实际内容。

2.3.4 Master向/从10位地址Savle写/读取数据

10为地址用作扩展 大多数都是7位地址 暂时不详细说啦 后面遇到了在填充

Master向10位地址Savle写数据过程
【STM32】| 02——常用外设 | I2C,单片机MCU,# STM32,stm32,嵌入式硬件,单片机
TODO

3. MCU的I2C外设

各种信号MCU大差不差 这里以stm32说明
stm32 i2c外设它提供多主机功能,控制所有I2C总线特定的时序、协议、仲裁和定时。支持标准和快速两种模式,与SMBus 2.0兼容。具备状态错误检测标志及中断、可DMA等特点
详细功能、寄存器等描述 看参考手册

I2C功能框图
【STM32】| 02——常用外设 | I2C,单片机MCU,# STM32,stm32,嵌入式硬件,单片机

四种模式
● 从发送器模式
● 从接收器模式
● 主发送器模式
● 主接收器模式

中断事件类型
【STM32】| 02——常用外设 | I2C,单片机MCU,# STM32,stm32,嵌入式硬件,单片机

4. I2C驱动函数

一般我们i2c与其他设备通信 所以 I2C是一个工具 配置好I2C 发送/接收后 还需要根据从设备做一些具体操作

旧============================================
我这里有个 I2C接口的 温湿度传感器 AHT10 这里用这个演示
先配置单片机 I2C 读写
~~
修改===========================================
这里用OLED来演示 I2C通信 AHT10不知道什么原因 发送设备地址不响应

4.1 硬件I2C

硬件I2C指 MCU自带的I2C外设 有固定引脚
HAL库提供了很多接口 作为主机的收发 作为从机的收发 存储设备读写 以及对应的3种方式(阻塞、中断、DMA) 这里不全部介绍了 只说下用到的

4.1.1 轮询(阻塞)式

1、Cubemx配置

【STM32】| 02——常用外设 | I2C,单片机MCU,# STM32,stm32,嵌入式硬件,单片机
I2C1 默认功能引脚是 PB6/7 它会自动配置我们不用管
从设备地址 根据从设备数据手册 看查 我这里AHT10传感器的 设备地址为 0X38

2、MCU作主机发送数据给从机

因为我们要让OLED显示我们想要的内容 所以要给OLED写命令/数据给它 OLED如何配置这些最后在说 暂时只演示 收发接口
这里我们用主机的收发API

HAL_StatusTypeDef HAL_I2C_Master_Transmit(I2C_HandleTypeDef *hi2c,
											uint16_t DevAddress,
											uint8_t *pData,
											uint16_t Size,
											uint32_t Timeout);

【STM32】| 02——常用外设 | I2C,单片机MCU,# STM32,stm32,嵌入式硬件,单片机
可以看到信号正常发出去了 只是没有应答 如果设备地址正确 从设备会响应
【STM32】| 02——常用外设 | I2C,单片机MCU,# STM32,stm32,嵌入式硬件,单片机
设备地址正确 从设备响应 就可以通信了 可以看到写的数据也可以写进去了

3、MCU作主机读取从机数据
HAL_StatusTypeDef HAL_I2C_Master_Receive(I2C_HandleTypeDef *hi2c,
											uint16_t DevAddress,
											uint8_t *pData,
											uint16_t Size,
											uint32_t Timeout);

【STM32】| 02——常用外设 | I2C,单片机MCU,# STM32,stm32,嵌入式硬件,单片机

4.1.2 中断式

1、Cubemx配置

【STM32】| 02——常用外设 | I2C,单片机MCU,# STM32,stm32,嵌入式硬件,单片机

2、MCU作主机发送数据给从机

和阻塞使用方法一样 只是调中断的接口

HAL_StatusTypeDef HAL_I2C_Master_Transmit_IT(I2C_HandleTypeDef *hi2c,
											uint16_t DevAddress,
											uint8_t *pData,
											uint16_t Size,
											uint32_t Timeout)
如果传输完成调用发送完成回调函数
void HAL_I2C_MasterTxCpltCallback(I2C_HandleTypeDef *hi2c)
{
    /* 如果传输完成会进入这里 */
    if(hi2c1.Instance==I2C1)
    {
        
    }
}
3、MCU作主机读取从机数据
HAL_StatusTypeDef HAL_I2C_Master_Receive_IT(I2C_HandleTypeDef *hi2c,
											uint16_t DevAddress,
											uint8_t *pData,
											uint16_t Size,
											uint32_t Timeout);
如果接收完成调用接收完成回调函数
void HAL_I2C_MasterRxCpltCallback(I2C_HandleTypeDef *hi2c)
{
    /* 如果接收完成会进入这里 */
    if(hi2c1.Instance==I2C1)
    {
        
    }
}

4.1.3 DMA

1、Cubemx配置

【STM32】| 02——常用外设 | I2C,单片机MCU,# STM32,stm32,嵌入式硬件,单片机

2、MCU作主机发送数据给从机

和阻塞使用方法一样 只是调中断的接口

HAL_StatusTypeDef HAL_I2C_Master_Transmit_DMA(I2C_HandleTypeDef *hi2c,
											uint16_t DevAddress,
											uint8_t *pData,
											uint16_t Size,
											uint32_t Timeout)
如果传输完成调用发送完成回调函数
void HAL_I2C_MasterTxCpltCallback(I2C_HandleTypeDef *hi2c)
{
    /* 如果传输完成会进入这里 */
    if(hi2c1.Instance==I2C1)
    {
        
    }
}
3、MCU作主机读取从机数据
HAL_StatusTypeDef HAL_I2C_Master_Receive_DMA(I2C_HandleTypeDef *hi2c,
											uint16_t DevAddress,
											uint8_t *pData,
											uint16_t Size,
											uint32_t Timeout);
如果接收完成调用接收完成回调函数
void HAL_I2C_MasterRxCpltCallback(I2C_HandleTypeDef *hi2c)
{
    /* 如果接收完成会进入这里 */
    if(hi2c1.Instance==I2C1)
    {
        
    }
}

4.1.4 HAL库I2C接口

接口很多大差不差

HAL_StatusTypeDef HAL_I2C_Master_Transmit(I2C_HandleTypeDef *hi2c,
											uint16_t DevAddress,
											uint8_t *pData,
											uint16_t Size,
											uint32_t Timeout);
											
// 一般用作写EEPROM 存储设备方便
HAL_StatusTypeDef HAL_I2C_Mem_Write(I2C_HandleTypeDef*hi2c, 
										uint16_t DevAddress, 
										uint16_t MemAddress,// 相当于从设备寄存器地址/命令
										uint16_t MemAddSize,
										uint8_t *pData, // 要写的数据
										uint16_t Size, 
										uint32_t Timeout)
  #define dev_addr 0x78
  /* 第一个数据 比如从机的某个寄存器/命令 */
  /* 第2/3个数据  要写入的数据 */
  uint8_t data[3]={0x31,0x10,0x11};
  
  /* 以下两个等价 */
  HAL_I2C_Master_Transmit(&hi2c1,dev_addr,data,3,0xffff);
  HAL_I2C_Mem_Write(&hi2c1,dev_addr,data[0],1,&data[1],2,0xffff);

【STM32】| 02——常用外设 | I2C,单片机MCU,# STM32,stm32,嵌入式硬件,单片机

4.2 IO模拟I2C

使用IO口去模拟 I2C 不需要固定引脚 灵活
根据I2C协议 实现I2C读写功能

/* 1、定义引脚并配置IO模式 */
#define I2C_WR	        0		/* 写控制bit */
#define I2C_RD	        1		/* 读控制bit */

#define I2C_GPIO_CLK_ENABLE()               __HAL_RCC_GPIOB_CLK_ENABLE()
#define I2C_GPIO_PORT                       GPIOB   
#define I2C_SCL_PIN                         GPIO_PIN_6
#define I2C_SDA_PIN                         GPIO_PIN_7

#define I2C_SCL_HIGH()                      HAL_GPIO_WritePin(I2C_GPIO_PORT,I2C_SCL_PIN,GPIO_PIN_SET)    // 输出高电平
#define I2C_SCL_LOW()                       HAL_GPIO_WritePin(I2C_GPIO_PORT,I2C_SCL_PIN,GPIO_PIN_RESET)  // 输出低电平
#define I2C_SDA_HIGH()                      HAL_GPIO_WritePin(I2C_GPIO_PORT,I2C_SDA_PIN,GPIO_PIN_SET)    // 输出高电平
#define I2C_SDA_LOW()                       HAL_GPIO_WritePin(I2C_GPIO_PORT,I2C_SDA_PIN,GPIO_PIN_RESET)  // 输出低电平
#define I2C_SDA_READ()                      HAL_GPIO_ReadPin(I2C_GPIO_PORT,I2C_SDA_PIN)

void sw_i2c_init(void)
{
  GPIO_InitTypeDef GPIO_InitStruct;
  
  /* 打开GPIO时钟 */
  I2C_GPIO_CLK_ENABLE();

  GPIO_InitStruct.Pin = I2C_SCL_PIN|I2C_SDA_PIN;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_MEDIUM;
  HAL_GPIO_Init(I2C_GPIO_PORT, &GPIO_InitStruct);
}
/* 2、I2C起始信号/停止信号 */
static void I2C_Delay(void)
{
	uint8_t i;
	for (i = 0; i < 10; i++);
}
void I2C_Start(void)
{
	/* SCL高电平时 SDA由高变低 */
	I2C_SDA_HIGH();
	I2C_SCL_HIGH();
	I2C_Delay();
	I2C_SDA_LOW();
	I2C_Delay();
	I2C_SCL_LOW();
	I2C_Delay();
}
void I2C_Stop(void)
{
	/* SCL高电平时,SDA由低变高 */
	I2C_SDA_LOW();
	I2C_SCL_HIGH();
	I2C_Delay();
	I2C_SDA_HIGH();
}
/* 3、I2C应答信号/不应答/等待检测应答信号 */
void I2C_Ack(void)
{
	I2C_SDA_LOW();	/* CPU驱动SDA = 0 */
	I2C_Delay();
	I2C_SCL_HIGH();	/* CPU产生1个时钟 */
	I2C_Delay();
	I2C_SCL_LOW();
	I2C_Delay();
	I2C_SDA_HIGH();	/* CPU释放SDA总线 */
}
void I2C_NAck(void)
{
	I2C_SDA_HIGH();	/* CPU驱动SDA = 1 */
	I2C_Delay();
	I2C_SCL_HIGH();	/* CPU产生1个时钟 */
	I2C_Delay();
	I2C_SCL_LOW();
	I2C_Delay();	
}
uint8_t I2C_WaitAck(void)
{
	uint8_t re;

	I2C_SDA_HIGH();	/* CPU释放SDA总线 */
	I2C_Delay();
	I2C_SCL_HIGH();	/* CPU驱动SCL = 1, 此时器件会返回ACK应答 */
	I2C_Delay();
	if (I2C_SDA_READ())	/* CPU读取SDA口线状态 */
	{
		re = 1;
	}
	else
	{
		re = 0;
	}
	I2C_SCL_LOW();
	I2C_Delay();
	return re;
}
/* 4、写/读一个字节数据 */
void I2C_Write_One_Byte(uint8_t Byte)
{
	uint8_t i;

	/* 先发送字节的高位bit7 */
	for (i = 0; i < 8; i++)
	{		
		if (Byte & 0x80)
		{
			I2C_SDA_HIGH();
		}
		else
		{
			I2C_SDA_LOW();
		}
		I2C_Delay();
		I2C_SCL_HIGH();
		I2C_Delay();	
		I2C_SCL_LOW();
		if (i == 7)
		{
			I2C_SDA_HIGH(); // 释放总线
		}
		Byte <<= 1;	/* 左移一个bit */
		I2C_Delay();
	}
}
uint8_t I2C_Read_One_Byte(void)
{
	uint8_t i;
	uint8_t value;

	/* 读到第1个bit为数据的bit7 */
	value = 0;
	for (i = 0; i < 8; i++)
	{
		value <<= 1;
		I2C_SCL_HIGH();
		I2C_Delay();
		if (I2C_SDA_READ())
		{
			value++;
		}
		I2C_SCL_LOW();
		I2C_Delay();
	}
	return value;
}

看查波形 验证IO模拟 发送一个字节

  I2C_Start();
  I2C_Write_One_Byte(dev_addr);
  while (I2C_WaitAck() == 0) break;
  I2C_Stop();

【STM32】| 02——常用外设 | I2C,单片机MCU,# STM32,stm32,嵌入式硬件,单片机
【STM32】| 02——常用外设 | I2C,单片机MCU,# STM32,stm32,嵌入式硬件,单片机
可以看到是没问题 SCL时钟频率 222KHZ 这个和我们 Delay延时函数有关 我们用的软件 延时 变量=10 空循环10次 在系统时钟72Mhz下 频率 222KHZ 测试得i=29 100khz左右 i=3 400K左右
【STM32】| 02——常用外设 | I2C,单片机MCU,# STM32,stm32,嵌入式硬件,单片机

看查波形 验证IO模拟 应答和不应答

  I2C_Start();
  I2C_Write_One_Byte(dev_addr);
  //I2C_NAck();
  I2C_Ack();
  I2C_Stop(); 

【STM32】| 02——常用外设 | I2C,单片机MCU,# STM32,stm32,嵌入式硬件,单片机
【STM32】| 02——常用外设 | I2C,单片机MCU,# STM32,stm32,嵌入式硬件,单片机


5. 使用I2C与从设备通信

以上介绍了 硬件I2C / 软件IO模拟I2C 驱动函数 后面我们讲解如何使用I2C 去和从设备通信

5.1 驱动i2c接口的OLED屏幕

5.1.1 了解我们的屏幕

我们了解3个信息 驱动芯片是那个 用什么方式驱动 屏幕分辨率
购买的时候 商品名称后缀会有 SSD1306等等 这指这个屏幕的 驱动芯片 这是模块内置的 我要用这个屏幕显示内容 就得去控制SSD1306这个OLED驱动芯片 给他什么命令还是什么内容 它才会让屏幕显示。
一般OLED屏幕有 I2C、SPI两种接口 如果我买了I2C的话 那就通过MCU I2C 和驱动SSD1306 SPI就用SPI

一般商家也会准备资料 资料里包含测试代码 屏幕产品手册 SSD1306驱动芯片手册 没的话就百度找下 不管驱动什么模块 用什么 看它的数据手册 是最好的资料
这是我手里的 0.96的OLED I2C的

【STM32】| 02——常用外设 | I2C,单片机MCU,# STM32,stm32,嵌入式硬件,单片机
从卖家给模块资料 可以了解到 我们屏幕 128x64个像素点

它的原理图
没什么东西 就是我们通过I2C去控制 SSD1306 如何驱动就是看它手册
【STM32】| 02——常用外设 | I2C,单片机MCU,# STM32,stm32,嵌入式硬件,单片机
【STM32】| 02——常用外设 | I2C,单片机MCU,# STM32,stm32,嵌入式硬件,单片机

5.1.2 阅读SSD1306手册

打开一看 60多页 还是英文 这看起来有点空难欸 不需要全看 找对我们有用的 那些有用呢
打开目录
【STM32】| 02——常用外设 | I2C,单片机MCU,# STM32,stm32,嵌入式硬件,单片机

1、获取MCU I2C与SSD2306交互的帧格式

我们看 MCU I2C接口章节 除了一些介绍 I2C相关的 发现一个最有用的 通信帧格式

【STM32】| 02——常用外设 | I2C,单片机MCU,# STM32,stm32,嵌入式硬件,单片机

2、看查命令表及详细介绍

第九章命令表 第10章命令详细描述
这些内容可以先不详细看 后面用到再查 我这里就梳理下用到的几个
有五类命令 :基本命令、滚动显示、显示地址设置、屏幕硬件配置、显示时钟频率

【STM32】| 02——常用外设 | I2C,单片机MCU,# STM32,stm32,嵌入式硬件,单片机
看到了D/C位的定义 这样的话
控制字节为 0X40表示写数据 0X00表示写命令
【STM32】| 02——常用外设 | I2C,单片机MCU,# STM32,stm32,嵌入式硬件,单片机

3、显示原理及寻址模式

刚看命令表 发现几个不懂的概念COM、SEG、 页地址、页寻址等 这时候我们在翻翻手册看看
【STM32】| 02——常用外设 | I2C,单片机MCU,# STM32,stm32,嵌入式硬件,单片机

寻址模式

第10章 设置寻址模式命令的详细描述 给出了解释
三种寻址模式:按页、水平、垂直

【STM32】| 02——常用外设 | I2C,单片机MCU,# STM32,stm32,嵌入式硬件,单片机

显示原理

【STM32】| 02——常用外设 | I2C,单片机MCU,# STM32,stm32,嵌入式硬件,单片机
128x64分辨率 放大了就是格子 我们想让OLED显示扫描内容 只需要按照要显示的内容
【STM32】| 02——常用外设 | I2C,单片机MCU,# STM32,stm32,嵌入式硬件,单片机

取模

通过取模软件 生成我们想要的数据
我演示一下
【STM32】| 02——常用外设 | I2C,单片机MCU,# STM32,stm32,嵌入式硬件,单片机
【STM32】| 02——常用外设 | I2C,单片机MCU,# STM32,stm32,嵌入式硬件,单片机

00H 00H 10H 10H F8H 00H 00H 00H 00H 00H 20H 20H 3FH 20H 20H 00H;“1”
【STM32】| 02——常用外设 | I2C,单片机MCU,# STM32,stm32,嵌入式硬件,单片机
这只是一个字符当我们要用常用字符呢 就需要生成字库了 汉字的字库太大
一般我们用ACSII英文字符的字库 和 用到那几个汉字 取模那几个汉字 后面会用到
【STM32】| 02——常用外设 | I2C,单片机MCU,# STM32,stm32,嵌入式硬件,单片机

4、初始化流程

看到一个 初始化流程及示例代码 具体操作查询下命令看看什么意思
【STM32】| 02——常用外设 | I2C,单片机MCU,# STM32,stm32,嵌入式硬件,单片机
这个官方SSD1306手册上的
【STM32】| 02——常用外设 | I2C,单片机MCU,# STM32,stm32,嵌入式硬件,单片机
下面这个是模块手册上的
【STM32】| 02——常用外设 | I2C,单片机MCU,# STM32,stm32,嵌入式硬件,单片机
【STM32】| 02——常用外设 | I2C,单片机MCU,# STM32,stm32,嵌入式硬件,单片机

5.1.4 编写OLED驱动代码

硬件I2C

先定义OLED.H


#ifndef __OLED_H
#define __OLED_H

#ifdef __cplusplus
extern "C" {
#endif

/* Includes ------------------------------------------------------------------*/
#include "stm32f1xx_hal.h"

#define OLED_DEVICE_ADDR          0X78
/* OLED控制字节 */
#define OLED_CMD                          0X00
#define OLED_DATA                         0X40
/* 命令好多我这里就不详细定义了 不定义也可以用的时候查一下什么意思就行 */
/* 基本命令  ------------------------------------------------------------------*/
#define Set_Contrast_Control      0X81        /* 设置对比度 范围1-256 */
#define Entire_Display_ON         0xA4        /* 全局显示关 */
#define Entire_Display_OFF        0xA5        /* 全局显示开 */
#define Set_Normal_Display        0xA6        /* 正常显示 */
#define Set_Inverse_Display       0xA7        /* 反向显示 */
#define Set_Display_ON            0xAE        /* 显示开 */
#define Set_Display_OFF           0XAF        /* 显示关 */
/* 地址设置命令  --------------------------------------------------------------*/
#define Set_Lower_Column(x)    ((x<=0x0F)?x:0)                  /* 设置列的起始地址低位 范围0x00-0x0F */
#define Set_Higher_Column(x)   ((x>=0x10&&x<=0x1F)?x:0x10)      /* 设置列的起始地址高位 范围0x10-0x1F */
#define Set_Page_Start_Address(x) ((x>=0xB0&&x<=0xB7)?x:0XB0)   /* 设置显示页起始地址 */


/**
 * [oled_init OLED初始化]
 */
void oled_init(void);
/**
 * [oled_show_string OLED显示字符串]
 * @param x    [X坐标]
 * @param y    [y坐标]
 * @param str  [字符串]
 */
void oled_show_char(uint8_t x,uint8_t y,uint8_t ch);
/**
 * [oled_show_string OLED显示字符串]
 * @param x    [X坐标]
 * @param y    [y坐标]
 * @param ch   [ch]
 */
void oled_show_string(uint8_t x,uint8_t y,uint8_t *str);
/**
 * [oled_show_num OLED显示数字]
 * @param x   [X坐标]
 * @param y   [X坐标]
 * @param num [显示的数]
 */
void oled_show_num(uint8_t x,uint8_t y,uint32_t num);



#ifdef __cplusplus
}
#endif

#endif

OLED.c
好多内容没保存 想哭死呜呜呜 我闲的点关机呜呜呜 哎明天再搞以下了呜呜呜

给OLED写一个字节命令/数据
void oled_write_one_byte(uint8_t ctl, uint8_t data)
{
    HAL_I2C_Mem_Write(&hi2c1, OLED_DEVICE_ADDR, ctl, 1, &data, 1, 0xff);
#if 0
    uint8_t temp[2] = {0};
    temp[0] = ctl;
    temp[1] = data;
    HAL_I2C_Master_Transmit(&hi2c1, OLED_DEVICE_ADDR, temp, 2, 0xff);
#endif
}

【STM32】| 02——常用外设 | I2C,单片机MCU,# STM32,stm32,嵌入式硬件,单片机

给OLED写多个字节数据
void oled_write_byte(uint8_t* data, uint8_t size)
{
    HAL_I2C_Mem_Write(&hi2c1, OLED_DEVICE_ADDR, OLED_DATA, 1, data, size, 0xff); }

【STM32】| 02——常用外设 | I2C,单片机MCU,# STM32,stm32,嵌入式硬件,单片机

OLED清空
uint8_t OLED_GRAM[128][16]; 
void oled_clear(void)
{
    uint8_t i, n;
    for(i = 0; i < 8; i++) {
        for(n = 0; n < 128; n++) {
            /* 清空数据 */
            OLED_GRAM[n][i] = 0;
        }
    }
    /* 更新显存 */
    for(i = 0; i < 8; i++) {
        /* 设置行起始地址 */
        oled_write_one_byte(OLED_CMD, 0xb0 + i);
        /* 设置列起始地址低位 */
        oled_write_one_byte(OLED_CMD, 0x00);
        /* 设置列起始地址高位 */
        oled_write_one_byte(OLED_CMD, 0x10);
        /* 按行写数据到显存 */
        oled_write_byte(&OLED_GRAM[0][0], 128);
    }
}

OLED显示一个字符

void oled_show_char(uint8_t x,uint8_t y,uint8_t ch)
{
	uint8_t page = y;
    uint8_t col  = 8;

    if(y > 7 || x > 15)
        return;
	oled_write_one_byte(OLED_CMD, 0xB0 + page&0x0f);
	oled_write_one_byte(OLED_CMD,0x00+col&0x0f);
    oled_write_one_byte(OLED_CMD, 0x10+col>>4);
	oled_write_byte((uint8_t*)&ascii_font_8x16[ch][0], 8);
	oled_set_pos(page + 1, col);
	oled_write_byte((uint8_t*)&ascii_font_8x16[ch][8], 8);
}
OLED显示字符串
void oled_show_string(uint8_t x,uint8_t y,uint8_t *str)
{
uint8_t i=0;
	while(str[i])
	{
	    oled_put_char(x, y, str[i]);
	    x++;
		if(x > 15) {
		x = 0;
		y += 2;
		}
		i++;
	}
}
OLED显示数字
void oled_show_num(uint8_t x,uint8_t y,uint32_t num)
{
   uint8_t str[16]={0};
   sprintf((char *)&str,"%d",num);
   oled_put_string(x, y, str);
}
OLED初始化
void oled_init(void)
{
    /* 1、初始化I2C */
    MX_I2C1_Init();
	/* 2、配置OLED */
    /* OLED Demo里 */
    oled_write_one_byte(OLED_CMD, 0xAE); //--turn off oled panel
    oled_write_one_byte(OLED_CMD, 0x00); //---set low column address
    oled_write_one_byte(OLED_CMD, 0x10); //---set high column address
    oled_write_one_byte(OLED_CMD, 0x40); //--set start line address  Set Mapping RAM Display Start Line (0x00~0x3F)
    oled_write_one_byte(OLED_CMD, 0x81); //--set contrast control register
    oled_write_one_byte(OLED_CMD, 0xCF); // Set SEG Output Current Brightness
    oled_write_one_byte(OLED_CMD, 0xA1); //--Set SEG/Column Mapping     0xa0左右反置 0xa1正常
    oled_write_one_byte(OLED_CMD, 0xC8); // Set COM/Row Scan Direction   0xc0上下反置 0xc8正常
    oled_write_one_byte(OLED_CMD, 0xA6); // 正常模式
    oled_write_one_byte(OLED_CMD, 0xA8); //--set multiplex ratio(1 to 64)
    oled_write_one_byte(OLED_CMD, 0x3f); //--1/64 duty
    oled_write_one_byte(OLED_CMD, 0xD3); //-set display offset   Shift Mapping RAM Counter (0x00~0x3F)
    oled_write_one_byte(OLED_CMD, 0x00); //-not offset
    oled_write_one_byte(OLED_CMD, 0xd5); //--set display clock divide ratio/oscillator frequency
    oled_write_one_byte(OLED_CMD, 0x80); //--set divide ratio, Set Clock as 100 Frames/Sec
    oled_write_one_byte(OLED_CMD, 0xD9); //--set pre-charge period
    oled_write_one_byte(OLED_CMD, 0xF1); //Set Pre-Charge as 15 Clocks & Discharge as 1 Clock
    oled_write_one_byte(OLED_CMD, 0xDA); //--set com pins hardware configuration
    oled_write_one_byte(OLED_CMD, 0x12);
    oled_write_one_byte(OLED_CMD, 0xDB); //--set vcomh
    oled_write_one_byte(OLED_CMD, 0x40); // Set VCOM Deselect Level
    oled_write_one_byte(OLED_CMD, 0x20); //-Set Page Addressing Mode (0x00/0x01/0x02)
    oled_write_one_byte(OLED_CMD, 0x02); //
    oled_write_one_byte(OLED_CMD, 0x8D); //--set Charge Pump enable/disable
    oled_write_one_byte(OLED_CMD, 0x14); //--set(0x10) disable
    oled_write_one_byte(OLED_CMD, 0xA4); // Disable Entire Display On (0xa4/0xa5)
    oled_write_one_byte(OLED_CMD, 0xA6); // Disable Inverse Display On (0xa6/a7)
    oled_write_one_byte(OLED_CMD, 0xAF);
    oled_write_one_byte(OLED_CMD, 0xA6); //正常显示
    oled_write_one_byte(OLED_CMD, 0xC8); //正常显示
    oled_write_one_byte(OLED_CMD, 0xA1); //正常显示
    oled_clear();
}
软件IO模拟I2C

只需要替换 前面 硬件I2C初始化、读写为软件I2C就行

给OLED写一个字节命令/数据
void oled_write_one_byte(uint8_t ctl, uint8_t data)
{
	I2C_Start();
    I2C_Write_One_Byte(OLED_DEVICE_ADDR);
    I2C_WaitAck();
    if(ctl == OLED_DATA) {
        I2C_Write_One_Byte(OLED_DATA);
    } else {
        I2C_Write_One_Byte(OLED_CMD);
    }
    I2C_WaitAck();
    I2C_Write_One_Byte(data);
    I2C_WaitAck();
    I2C_Stop();
}
给OLED写多个字节数据
void oled_write_byte(uint8_t* data, uint8_t size)
{
    uint8_t i = 0;
    I2C_Start();
    I2C_Write_One_Byte(OLED_DEVICE_ADDR);
    I2C_WaitAck();
    I2C_Write_One_Byte(OLED_DATA);
    I2C_WaitAck();
    while(i < size) {
        I2C_Write_One_Byte(*data);
        I2C_WaitAck();
        data++;
        i++;
    }
    I2C_Stop();
}

替换这两个接口就好文章来源地址https://www.toymoban.com/news/detail-804782.html

到了这里,关于【STM32】| 02——常用外设 | I2C的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【STM32】STM32学习笔记-I2C通信外设(34)

    I2C(Inter-Integrated Circuit)总线 是一种由NXP(原PHILIPS)公司开发的两线式串行总线,用于连接微控制器及其外围设备。多用于主控制器和从器件间的主从通信,在小数据量场合使用,传输距离短,任意时刻只能有一个主机等特性。 串行的 8 位双向数据传输位速率在标准模式下可

    2024年01月17日
    浏览(64)
  • STM32--MPU6050与I2C外设

    在51单片机专栏中,用过I2C通信来进行实现AT24C02的数据存储; 里面介绍的是 利用程序的编程来实现I2C的时序 ,进而实现AT24C02与单片机之间的关系连接; 本章将介绍使用I2C的硬件外设来实现I2C通信,和介绍MPU6050,利用I2C通信实现STM32对MPU6050的控制. I2C通信软件实现程序链接

    2024年02月11日
    浏览(58)
  • 十三、51单片机之EEPROM(I2C)

    (1)存储设备类型:ROM、RAM、PROM(可编程ROM)、EPROM(可擦除ROM)、EEPROM(电可擦除ROM)。 (2)为什么需要EEPROM? 某些数据内容我们需要掉电不丢失且在程序运行中可以修改这些数据内容,这就需要用到EEPROM。 (3)EEPROM和flash(闪存)的区别。 EEPROM是按功能分类的一种存储设备类型;flash是存

    2023年04月27日
    浏览(60)
  • 单片机第一季:零基础12——I2C和EEPROM

    目录 1,EEPROM 2,I2C  2.1,I2C物理层  2.2,I2C协议层  3,AT24C02介绍  4,代码  为什么需要EEPROM? 单片机内部的ROM只能在程序下载时进行擦除和改写,但是程序运行本身是不能改写的。单片机内部的RAM中的数据程序运行时可以改,但是掉电就丢失了。有时候我们有一些数据要

    2024年02月14日
    浏览(50)
  • 【STM32学习】——STM32-I2C外设&硬件读写MPU6050&软硬件读写波形对比

    目录 前言 一、I2C外设 二、硬件I2C操作流程 1.主机发送时序 3.其他时序

    2024年02月10日
    浏览(49)
  • 【51单片机】AT24C20数据帧(I2C总线)

    🎊专栏【51单片机】 🍔喜欢的诗句:更喜岷山千里雪 三军过后尽开颜。 🎆音乐分享【Love Story】 🥰大一同学小吉,欢迎并且感谢大家指出我的问题🥰 小吉先向大家道个歉,因为最近在期末突击,所以文章久久没有更新,也请大家多多见谅😥 目录   🎁I2C总线 🏳️‍🌈

    2024年02月08日
    浏览(72)
  • STM32-I2C通信在AT24C02的应用

    AT24C02是一种失去电源供给后依旧能保持数据的储存器,常用来储存一些配置信息,在系统重新上电之后也可以加载。它的容量是2k bit的EEPROM存储器,采用I2C通信方式。 AT24C02支持两种写操作:字节写操作和页写操作。本实验中我们采用的是字节写操作,就是一个地址一个数据

    2024年02月09日
    浏览(53)
  • STM32基于HAL工程硬件I2C读写AT24C02/04/08数据

    ✨申明:本文章仅发表在CSDN网站,任何其他网站,未注明来源,见此内容均为盗链和爬取,请多多尊重和支持原创! 🍁对于文中所提供的相关资源链接将作不定期更换。 相关篇针对AT24C32及以上容量《STM32基于STM32-HAL工程硬件I2C读取AT24Cxx数据》 🎯本工程使用STM32F103VE+AT24C02实

    2023年04月11日
    浏览(55)
  • 【单片机】UART、I2C、SPI、TTL、RS232、RS422、RS485、CAN、USB、SD卡、1-WIRE、Ethernet等常见通信方式

    在单片机开发中,UART、I2C、RS485等普遍在用,这里做一个简单的介绍 UART口指的是一种物理接口形式(硬件)。 UART是异步(指不使用时钟同步,依靠帧长进行判断),全双工(收发可以同时进行)串口总线。它比同步串口复杂很多。有两根线,一根TXD用于发送,一根RXD用于接收

    2024年02月11日
    浏览(43)
  • 【STM32 CubeMX】I2C层次结构、I2C协议

    在STM32 CubeMX环境中,I2C(Inter-Integrated Circuit)是一种常用的串行通信协议,广泛应用于连接各种外设和传感器。理解I2C的层次结构、协议和硬件结构对于STM32微控制器的开发至关重要。通过STM32 CubeMX提供的图形化配置工具,我们能够更轻松地理解和配置I2C通信,同时深入了解

    2024年02月22日
    浏览(81)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包