目录

本文学习I2C总线通信协议,使用STM32F103完成基于I2C协议的AHT20温湿度传感器的数据采集,并将采集的温度-湿度值通过串口输出。



要求

(1) 学习I2C总线通信协议,使用STM32F103完成基于I2C协议的AHT20温湿度传感器的数据采集,并将采集的温度-湿度值通过串口输出。具体任务:

(2)解释什么是“软件I2C”和“硬件I2C”? (阅读野火配套教材的第23章“I2C–读写EEPROM”原理章节)

(3)阅读AHT20数据手册,编程实现:每隔2秒钟采集一次温湿度数据,并通过串口发送到上位机(win10)。


一、I2C协议详解

1.I2C是什么

I2C总线是Philips公司在八十年代初推出的一种串行、半双工的总线,主要用于近距离、低速的芯片之间的通信;I2C总线有两根双向的信号线,一根数据线SDA用于收发数据,一根时钟线SCL用于通信双方时钟的同步;I2C总线硬件结构简单,简化了PCB布线,降低了系统成本,提高了系统可靠性,因此在各个领域得到了广泛应用。

I2C总线是一种多主机总线,连接在 I2C总线上的器件分为主机和从机。主机有权发起和结束一次通信,从机只能被动呼叫;当总线上有多个主机同时启用总线时,I2C也具备冲突检测和仲裁的功能来防止错误产生;每个连接到I2C总线上的器件都有一个唯一的地址(7bit),且每个器件都可以作为主机也可以作为从机(但同一时刻只能有一个主机),总线上的器件增加和删除不影响其他器件正常工作;I2C总线在通信时总线上发送数据的器件为发送器,接收数据的器件为接收器。

I2C总线可以通过外部连线进行在线检测,便于系统故障诊断和调试,故障可以立即被寻址,软件也有利于标准化和模块化,缩短开发时间。

I2C总线上可挂接的设备数量受总线的最大电容400pF限制。

串行的8位双向数据传输速率在标准模式下可达100Kbit/s,快速模式下可达400Kbit/s,高速模式下可达3.4Mbit/s。

总线具有极低的电流消耗,抗噪声干扰能力强,增加总线驱动器可以使总线电容扩大10倍,传输距离达到15m;兼容不同电压等级的器件,工作温度范围宽。

在这里插入图片描述

2.通信过程

  1. 主机发送起始信号启用总线
  2. 主机发送一个字节数据指明从机地址和后续字节的传送方向
  3. 被寻址的从机发送应答信号回应主机
  4. 发送器发送一个字节数据
  5. 接收器发送应答信号回应发送器
  6. … (循环步骤4、5)
  7. 通信完成后主机发送停止信号释放总线

第4步和第5步用的是发送器和接收器,不是主机和从机,这是由第一个字节的最后一位决定主给从发,还是从给主发。

也就是说,第一个字节和最后的停止信号一定是主机发给从机,但中间就不一定了。

发送数据过程中不允许改变发送方向(除非重启一次通信,详见后文典型I2C时序(3)部分)。

2.寻址方式

I2C总线上传送的数据是广义的,既包括地址,又包括真正的数据。

主机在发送起始信号后必须先发送一个字节的数据,该数据的高7位为从机地址,最低位表示后续字节的传送方向,‘0’表示主机发送数据给->从机,‘1’表示从机发送数据给->主机。

总线上所有的从机接收到该字节数据后都将这7位地址与自己的地址进行比较,如果相同,则认为自己被主机寻址,然后再根据第8位将自己定为发送器或接收器。

4.5种速率

I2C协议可以工作在以下5种速率模式下,不同的器件可能支持不同的速率。

  • 标准模式(Standard):100kbps
  • 快速模式(Fast):400kbps
  • 快速模式+(Fast-Plus):1Mbps
  • 高速模式(High-speed):3.4Mbps
  • 超快模式(Ultra-Fast):5Mbps(单向传输)

bps:bit/s,即SCL的频率
其中超快模式是单向数据传输,通常用于LED、LCD等不需要应答的器件,和正常的I2C操作时序类似,但是只进行写数据,不需要考虑ACK应答信号。

在这里插入图片描述

5.4种信号

I2C协议最基础的几种信号:起始、停止、应答和非应答信号。

起始信号

I2C协议规定,SCL处于高电平时,SDA由高到低变化,这种信号是起始信号。

停止信号

I2C协议规定,SCL处于高电平,SDA由低到高变化,这种信号是停止信号。

在这里插入图片描述

数据有效性

I2C协议对数据的采样发生在SCL高电平期间,除了起始和停止信号,在数据传输期间,SCL为高电平时,SDA必须保持稳定,不允许改变,在SCL低电平时才可以进行变化。

在这里插入图片描述

应答信号

I2C最大的一个特点就是有完善的应答机制,从机接收到主机的数据时,会回复一个应答信号来通知主机表示“我收到了”。

应答信号出现在1个字节传输完成之后,即第9个SCL时钟周期内,此时主机需要释放SDA总线,把总线控制权交给从机,由于上拉电阻的作用,此时总线为高电平,如果从机正确的收到了主机发来的数据,会把SDA拉低,表示应答响应。

在这里插入图片描述
使用MCU、FPGA等控制器实现时,需要在第9个SCL时钟周期把SDA设置为高阻输入状态,如果读取到SDA为低电平,则表示数据被成功接收到,可以进行下一步操作。

非应答信号

当第9个SCL时钟周期时,SDA保持高电平,表示非应答信号。

在这里插入图片描述
非应答信号可能是主机产生也可能是从机产生,产生非应答信号的情况主要有以下几种:

  1. I2C总线上没有主机所指定地址的从机设备
  2. 从机正在执行一些操作,处于忙状态,还没有准备好与主机通讯
  3. 主机发送的一些控制命令,从机不支持
  4. 主机接收从机数据时,主机产生非应答信号,通知从机数据传输结束,不要再发数据了

6.读写时序

向指定寄存器地址写入指定数据操作时序:

在这里插入图片描述
从指定寄存器地址读取数据操作时序:
在这里插入图片描述注意,读数据时有两次起始信号。

7.字节传送与应答

I2C总线通信时每个字节为8位长度,数据传送时,先传送最高位,后传送低位,发送器发送完一个字节数据后接收器必须发送1位应答位来回应发送器,即一帧共有9位。

I2C每次发送数据必须是8位。

MSB固定,先发高位,再发低位。
在这里插入图片描述

8.软件IIC和硬件IIC

IIC分为软件IIC硬件IIC

软件IIC
软件IIC通信指的是用单片机的两个I/O端口模拟出来的IIC,用软件控制管脚状态以模拟I2C通信波形,软件模拟寄存器的工作方式。

直接使用 CPU 内核按照 I2C 协议的要求控制 GPIO 输出高低电平,从而模拟I2C。
使用: 需要在控制产生 I2C 的起始信号时,控制作为SCL 线的 GPIO 引脚输出高电平,然后控制作为 SDA 线的 GPIO 引脚在此期间完成由高电平至低电平的切换,最后再控制SCL线切换为低电平,这样就输出了一个标准的 I2C 起始信号。

硬件IIC:
一块硬件电路,硬件I2C对应芯片上的I2C外设,有相应I2C驱动电路,其所使用的I2C管脚也是专用的,硬件(固件)I2C是直接调用内部寄存器进行配置。

直接利用 STM32 芯片中的硬件 I2C 外设。
使用: 只要配置好对应的寄存器,外设就会产生标准串口协议的时序。在初始化好 I2C 外设后,只需要把某寄存器位置 1,此时外设就会控制对应的 SCL 及 SDA 线自动产生 I2C 起始信号,不需要内核直接控制引脚的电平。

硬件I2C的效率要远高于软件的,而软件I2C由于不受管脚限制,接口比较灵活。

二、STM32基于I2C协议的温湿度传感器的数据采集

1.任务要求

每隔2秒钟采集一次温湿度数据,并通过串口发送到上位机

2.连接硬件

AHT20的SCL,GND,SDA,VCC分别对应接stm32f103的B6,GND,B7,5V。(GND和5V任意模块都可以)

在这里插入图片描述

3.创建项目

(1)打开USART2,用于一会通过串口查看数据

在这里插入图片描述

(2)点开I2C1,将其配置为标准IIC模式,配置保持默认不用调节
在这里插入图片描述

在这里插入图片描述

4.实现AHT20采集温湿度程序

1.了解AHT20芯片的相关信息
具体信息请到官方下载对应产品介绍文档,资料链接如下
http://www.aosong.com/class-36.html

main函数代码:

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * <h2><center>&copy; Copyright (c) 2021 STMicroelectronics.
  * All rights reserved.</center></h2>
  *
  * This software component is licensed by ST under BSD 3-Clause license,
  * the "License"; You may not use this file except in compliance with the
  * License. You may obtain a copy of the License at:
  *                        opensource.org/licenses/BSD-3-Clause
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "dma.h"
#include "i2c.h"
#include "usart.h"
#include "gpio.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */

#include<stdio.h>
#include "AHT20-21_DEMO_V1_3.h" 

void SystemClock_Config(void);


int fputc(int ch,FILE *f)
 
{
    HAL_UART_Transmit(&huart1,(uint8_t *)&ch,1,0xFFFF);    
		//等待发送结束	
		while(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_TC)!=SET){
		}		

    return ch;
}



int main(void)
{
  /* USER CODE BEGIN 1 */
	uint32_t CT_data[2]={0,0};
	volatile int  c1,t1;
	
	Delay_1ms(500);

	HAL_Init();

	SystemClock_Config();

	MX_GPIO_Init();
	MX_DMA_Init();
	MX_USART1_UART_Init();
	
	//初始化AHT20
	AHT20_Init();
	Delay_1ms(500);

  while (1)
  { 
    /* USER CODE END WHILE */
		AHT20_Read_CTdata(CT_data);       //不经过CRC校验,直接读取AHT20的温度和湿度数据    推荐每隔大于1S读一次
		//AHT20_Read_CTdata_crc(CT_data);  //crc校验后,读取AHT20的温度和湿度数据 
	

		c1 = CT_data[0]*1000/1024/1024;  //计算得到湿度值c1(放大了10倍)
		t1 = CT_data[1]*2000/1024/1024-500;//计算得到温度值t1(放大了10倍)
		printf("正在检测");
		HAL_Delay(100);
		printf(".");
		HAL_Delay(100);
		printf(".");
		HAL_Delay(100);
		printf(".");
		HAL_Delay(100);
		printf(".");
		HAL_Delay(100);
		printf(".");
		HAL_Delay(100);
		printf(".");
		HAL_Delay(100);
		printf(".");
		HAL_Delay(100);
		printf(".");
		HAL_Delay(100);
		printf(".");
		HAL_Delay(100);
		printf(".");
		printf("\r\n");
		HAL_Delay(1000);
		printf("温度:%d%d.%d",t1/100,(t1/10)%10,t1%10);
		printf("湿度:%d%d.%d",c1/100,(c1/10)%10,c1%10);
		printf("\r\n");
		printf("等待");
		HAL_Delay(100);
		printf(".");
		HAL_Delay(100);
		printf(".");
		HAL_Delay(100);
		printf(".");
		HAL_Delay(100);
		printf(".");
		HAL_Delay(100);
		printf(".");
		HAL_Delay(100);
		printf(".");
		HAL_Delay(100);
		printf(".");
		HAL_Delay(100);
		printf(".");
		HAL_Delay(100);
		printf(".");
		HAL_Delay(100);
		printf(".");
		printf("\r\n");
		HAL_Delay(1000);
  /* USER CODE END 3 */
	}
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }
  /** Initializes the CPU, AHB and APB buses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSI;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK)
  {
    Error_Handler();
  }
}

/* USER CODE BEGIN 4 */

/* USER CODE END 4 */

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */
  __disable_irq();
  while (1)
  {
  }
  /* USER CODE END Error_Handler_Debug */
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/


三、结果

while循环每两秒左右读取一次温湿度并且通过串口打印在这里插入图片描述

总结

通过本次实验,我深入了解了I2C通信协议以及如何使用STM32F103进行基于I2C协议的AHT20温湿度传感器数据采集。通过配置I2C外设和编写相关的代码,我成功读取了AHT20传感器的温湿度数据,并通过串口输出到上位机。本次实验帮助我熟悉了I2C通信协议的使用和串口通信的配置,同时也加深了对温湿度传感器的理解。

参考

一文看懂I2C协议

I2C总线协议详解

【嵌入式15】I2C总线通信协议及实操stm32通过I2C实现温湿度(AHT20)采集

STM32F103基于I2C协议的AHT20温湿度传感器的数据采集

Logo

技术共进,成长同行——讯飞AI开发者社区

更多推荐