一些前置知识

IIC的基本原理

IIC串行总线有两根信号线,一根是双向的数据线SDA,另一根是时钟线SCL。两条线都接上拉电阻,以确保总线空闲时刻为高电平,其中时钟信号是由主控器件产生。IIC总线支持多设备连接,允许多主机存在,对于并联在一条总线上的每个IIC设备都有唯一的地址**(即靠地址来区分每一个IIC设备)。**

IIC

起始信号

当时钟线(SCL)为高平时,数据线(SDA)从高电平跳变到低电平。

终止信号

当时钟线(SCL)为高平时,数据线(SDA)从低电平跳变到高电平。

应答信号

主机每发送一个字节(8个bit),就在第9个时钟脉冲期间释放数据线(SDA),由从机反馈一个应答信号。

  • 应答信号**(SDA)为低电平**时,规定为有效应答位(ACK,简称应答位),表示从机成功地接收了该字节。

  • 应答信号**(SDA)为高电平**时,规定为非应答位(NACK),一般表示从机接收该字节没有成功。

IIC的三种模式

类似串口,iic同时也有相应的三种通信模式。

轮询模式

从上至下一次为速度模式(标准100000,快速400000),其余一般不动。

轮询模式

主要用到的HAL库代码

1
2
3
4
5
HAL_I2C_Master_Receive(&hic1,AHT20_ADDRESS,&readBUffer,3,HAL_MAX_DELAY);  
//接收函数,第一个是iic的地址指针,第二个是获取数据的地址,第三个为接收的数组地址,第四个是接收数据的长度,第五个是最长等待时间

HAL_I2C_Master_Transmit(&hic1,AHT20_ADDRESS,&sendBUffer,3,HAL_MAX_DELAY);
//接收函数,第一个是iic的地址指针,第二个是将发送数据的地址,第三个为发送的数组地址,第四个是发送数据的长度,第五个是最长等待时间

实践一:AHT20的读取串口发送

首先,移植相应的AHT20驱动,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  ...
AHT20_Init();//初始化AHT20
float temperature = 0.0, humidity = 0.0;
char message[50];//定义相关的变量、数组名称

...
while (1) {
AHT20_Measure();//读取AHT20
temperature = AHT20_Tempurature();
humidity = AHT20_Humidity();
sprintf(message, "Temperature: %.2f, Humidity: %.2f%%\r\n", temperature, humidity);
//拼接字符串
HAL_UART_Transmit(&huart2, (uint8_t *)message, strlen(message), HAL_MAX_DELAY);
HAL_Delay(1000);
/* USER CODE END WHILE */

/* USER CODE BEGIN 3 */
}

中断模式

状态机的概念

在实际对一个模块的操作中,我们需要将其完成的工作模块化,这样方便我们对于它运行的状态进行判定。

状态机

中断模式代码

开启中断模式

中断模式

所用到的HAL库函数

1
2
3
4
5
6
7
8
9
10
11
12
13
__weak void HAL_I2C_MasterRxCpltCallback(I2C_HandleTypeDef *hi2c){
//接收完成中断
}

__weak void HAL_I2C_MasterTxCpltCallback(I2C_HandleTypeDef *hi2c){
//发送完成中断
}

HAL_I2C_Master_Receive_IT(&hic1,AHT20_ADDRESS,&readBUffer,3,HAL_MAX_DELAY);
//接收函数,第一个是iic的地址指针,第二个是获取数据的地址,第三个为接收的数组地址,第四个是接收数据的长度,第五个是最长等待时间

HAL_I2C_Master_Transmit_IT(&hic1,AHT20_ADDRESS,&sendBUffer,3,HAL_MAX_DELAY);
//接收函数,第一个是iic的地址指针,第二个是将发送数据的地址,第三个为发送的数组地址,第四个是发送数据的长度,第五个是最长等待时间

实践一:AHT20的读取串口发送

i2c.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14

/* USER CODE BEGIN 0 */

void HAL_I2C_MasterTxCpltCallback(I2C_HandleTypeDef *hi2c){
if(hi2c == &hi2c1){
aht20_State == 2;
}//发送信息完成中断

void HAL_I2C_MasterRxCpltCallback(I2C_HandleTypeDef *hi2c){
if(hi2c == &hi2c1){
aht20_State == 4;
}//接收信息完成中断

/* USER CODE END 0 */

main.h

1
2
3
4
5
6
/* USER CODE BEGIN EC */

extern uint8_t aht20_State;//提前变量,以便全局使用

/* USER CODE END EC */

main.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
...
AHT20_Init();
float temperature = 0.0, humidity = 0.0;
char message[50];

/* USER CODE END 2 */

...

while (1) {

if(aht20_State == 0){
AHT20_Measure();
aht20_State == 1;
}else if(aht20_State == 2){
HAL_Delay(75);
AHT20_Get();
aht20_State == 3;
}else if(aht20_State == 4){
AHT20_Analysis(&temperature,&humidity);
sprintf(message, "Temperature: %.2f, Humidity: %.2f%%\r\n", temperature, humidity);
HAL_UART_Transmit(&huart2, (uint8_t *)message, strlen(message), HAL_Delay(1000);
aht20_State == 0;
}

/* USER CODE END WHILE */

/* USER CODE BEGIN 3 */
}
...

DMA模式

开启DMA模式

DMA模式

所用到的HAL库函数

1
2
3
4
5
6
7
8
9
10
11
12
13
__weak void HAL_I2C_MasterRxCpltCallback(I2C_HandleTypeDef *hi2c){
//接收完成中断
}

__weak void HAL_I2C_MasterTxCpltCallback(I2C_HandleTypeDef *hi2c){
//发送完成中断
}

HAL_I2C_Master_Receive_DMA(&hic1,AHT20_ADDRESS,&readBUffer,3,HAL_MAX_DELAY);
//接收函数,第一个是iic的地址指针,第二个是获取数据的地址,第三个为接收的数组地址,第四个是接收数据的长度,第五个是最长等待时间

HAL_I2C_Master_Transmit_DMA(&hic1,AHT20_ADDRESS,&sendBUffer,3,HAL_MAX_DELAY);
//接收函数,第一个是iic的地址指针,第二个是将发送数据的地址,第三个为发送的数组地址,第四个是发送数据的长度,第五个是最长等待时间

代码实践

实践一:结合OLED和AHT20制作简易温湿度计

main.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
 /* USER CODE BEGIN Includes */
#include "oled.h"
#include "aht20.h"
#include "stdio.h"
#include "string.h"
/* USER CODE END Includes */

/* USER CODE BEGIN 2 */
HAL_Delay(20);
OLED_Init();
AHT20_Init();
float temperature = 0.0, humidity = 0.0;
char message[50];//初始化相应的的变量和函数
/* USER CODE END 2 */

while (1)
{
/* USER CODE END WHILE */

/* USER CODE BEGIN 3 */

AHT20_Measure();
temperature=AHT20_Temperature();
humidity=AHT20_Humidity();

OLED_NewFrame();//准备新的图像

OLED_DrawImage(15,0,&temperatureImgImg,OLED_COLOR_NORMAL);
//画图,x,y,图像指针,字符颜色
sprintf(message,"%.1f℃",temperature);//拼接字符串
OLED_PrintString(45,15,message,&font16x16,OLED_COLOR_NORMAL);
//打印字符串,x,y,字符串指针地址,字符颜色

OLED_DrawImage(15,30,&humidityImgImg,OLED_COLOR_NORMAL);
sprintf(message,"%.1f%%",humidity);
OLED_PrintString(45,40,message,&font16x16,OLED_COLOR_NORMAL);

OLED_ShowFrame();//展示图像

字体、图片需要用取模助手取模,放在font.c文件中,图片名称需要extern 提前变量在font.h中,以便全局调用。

一些参考资料

1.【STM32入门教程-2024】第12集 IIC通信与温湿度传感器AHT20(DHT20)_哔哩哔哩_bilibili

2.【STM32入门教程-2024】第13集 IIC的中断与DMA以及状态机编程_哔哩哔哩_bilibili

3.【STM32入门教程-2024】第14集 如何在OLED屏幕上挥毫_哔哩哔哩_bilibili

4.【STM32·番外】挑战5分钟制作温湿度计(A/DHT20+OLED)_哔哩哔哩_bilibili

5.STM32之IIC_stm32 iic-CSDN博客