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

2024-01-29 14:48

本文主要是介绍基于 I2C 协议使用 AHT20 温湿度传感器采集数据,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

  • 一、I2C 协议简介
  • 二、AHT20 温湿度传感器
  • 三、实验步骤
    • 1)实验准备
    • 2)程序代码
    • 3)程序烧录
  • 四、总结
  • 五、参考资料

本文内容:学习 I2C 总线通信协议,完成基于 I2C 硬件协议的 AHT20 温湿度传感器的数据采集,并将采集的温度-湿度值通过串口输出。具体任务:

  1. 解释什么是 “ 硬件I2C ” 和 “ 软件I2C ” ? (阅读野火配套教材的第 23 章 “ I2C–读写EEPROM ” 原理章节)
  2. 阅读 AHT20 数据手册,编程实现:每隔2秒钟采集一次温湿度数据,并通过串口发送到上位机(win10)。

一、I2C 协议简介

  • IIC 通讯协议 (Inter-Integrated Circuit,又叫 I2CI²C ) 是由 Phiilps 公司开发的,由于它引脚少,硬件实现简单,可扩展性强,不需要 USART、CAN 等通讯协议的外部收发设备,现在被广泛地使用在系统内多个集成电路 ( IC ) 间的通讯。

1、I2C 物理层

  • I2C 通讯设备之间的常用连接方式如下图:
    在这里插入图片描述
  • 它的物理层有如下特点:
    1. 它是一个支持设备的总线。“总线”指多个设备共用的信号线。在一个 I2C 通讯总线中,可连接多个 I2C 通讯设备,支持多个通讯主机及多个通讯从机
    2. 一个 I2C 总线只使用两条总线线路,一条双向串行数据线(SDA) ,一条串行时钟线(SCL)。数据线即用来表示数据,时钟线用于数据收发同步
    3. 每个连接到总线的设备都有一个独立的地址,主机可以利用这个地址进行不同设备之间的访问
    4. 总线通过上拉电阻接到电源。当 I2C 设备空闲时,会输出高阻态,而当所有设备都空闲,都输出高阻态时,由上拉电阻把总线拉成高电平
    5. 多个主机同时使用总线时,为了防止数据冲突,会利用仲裁方式决定由哪个设备占用总线
    6. 具有三种传输模式:标准模式传输速率为 100kbit/s ,快速模式为 400kbit/s ,高速模式下可达 3.4Mbit/s,但目前大多 I2C 设备尚不支持高速模式
    7. 连接到相同总线的 IC 数量受到总线的最大电容 400pF 限制

2、I2C 协议层

  • I2C 的协议定义了通讯的起始和停止信号、数据有效性、响应、仲裁、时钟同步和地址广播等环节
    I2C 基本读写过程:
  • 写数据 ——
    在这里插入图片描述

说明:
阴影部分 —— 数据由主机传输至从机
白色部分 —— 数据由从机传输至主机
S —— 传输开始信号
SLAVE_ADDRESS —— 从机地址
R/W —— 传输方向选择位,1 为读,0 为写
A/A —— 应答(ACK)或非应答(NACK)信号
P —— 停止传输信号

  • ①主机广播从机地址
  • ②主机接收到指定从机的应答信号
  • ③主机开始正式向该从机传输数据(DATA)(说明:数据包的大小为 8 位,主机每发送完一个字节数据,都要等待该从机的应答信号(ACK)
  • ④重复步骤③,可以向该从机传输 N 个数据,这个 N 没有大小限制
  • ⑤当数据传输结束时,主机向该从机发送一个停止传输信号(P),表示不
    再传输数据
  • 读数据 ——
    在这里插入图片描述
  • ①主机广播从机地址
  • ②主机接收到指定从机的应答信号
  • ③从机开始向主机返回数据(DATA)(说明:数据包大小也为 8 位,从机每发送完一个数据,都会等待主机的应答信号(ACK)
  • ④重复步骤③,可以返回 N 个数据,这个 N 也没有大小限制
  • ⑤当主机希望停止接收数据时,就向从机返回一个非应答信号(NACK),则从机自动停止数据传输
  • 读和写数据 ——
    在这里插入图片描述
  • 除了基本的读写,I2C 通讯更常用的是复合格式,该传输过程有两次起始信号(S)
  • 在第一次传输中:
  • 主机通过 SLAVE_ADDRESS 寻找到从设备后
  • 发送一段 “ 数据 ” (说明:这段数据通常用于表示从设备内部的寄存器或存储器地址(注意区分它与 SLAVE_ADDRESS 的区别)
  • 在第二次的传输中:
  • 对该地址的内容进行读或写
  • 也就是说,第一次通讯是告诉从机读写地址,第二次则是读写的实际内容

3、硬件 I2C 和 软件 I2C

  • 硬件(固件) I2C 对应芯片上的 I2C 外设,有相应 I2C 驱动电路,其所使用的 I2C 管脚也是专用的
  • 软件(模拟) I2C 一般是用 GPIO 管脚,用软件控制管脚状态以模拟 I2C 通信波形
  • 区别:
  1. 硬件 I2C 的效率要远高于软件的;软件 I2C 由于不受管脚限制,接口比较灵活
  2. 硬件 I2C 是直接调用内部寄存器进行配置;软件 I2C 是通过 GPIO ,软件模拟寄存器的工作方式
  3. 硬件 I2C 配置了 IO 口的功能(I2C 功能);软件 I2C 没有配置 IO 口的功能
  4. 硬件 I2C 的 I2C 写函数,有调用现成的函数或者给某个寄存器赋值;软件 I2C 传输数据的方式是一个 bit 一个 bit 模拟发生送的,肯定用到了循环
  5. 根据代码量判断,硬件 I2C 的代码量肯定比软件 I2C 的代码量小
  6. 硬件 I2C 用法比较复杂,软件 I2C 的流程更清楚一些
  7. 硬件 I2C 的速度比软件 I2C 的速度快,并且硬件 I2C 可以用 DMA
  8. 硬件 I2C 只能在固定管脚上;模拟 I2C 可以在任何管脚上
  9. 软件 I2C 是程序员使用程序控制 SCL、SDA 线输出高低电平,模拟 I2C 协议的时序,一般较硬件 I2C 稳定,但是程序较为繁琐,但不难
  10. 硬件 I2C 程序员只要调用 I2C 的控制函数即可,不用直接的去控制SCL、SDA 高低电平的输出,但是有些单片机的硬件 I2C 不太稳定,调试问题较多

更多详细内容,请在 B 站观看:https://www.bilibili.com/video/BV1ps411M7Dr?p=22

二、AHT20 温湿度传感器

在这里插入图片描述
详细参数请参考:

  • AHT20产品手册a2.pdf
  • 链接:qlo6

三、实验步骤

1)实验准备

硬件——

  • 野火 stm32 指南者开发板
  • ST-LINK V2 STM8/STM32仿真器编程器
  • AHT21B_V1.0 温湿度传感器(注意:买到的传感器是没有焊引脚的,需要自己再买排针,用电烙铁焊接上去,我不知道厂家能不能帮忙焊接,我的是班里集体买的,然后再去实验室焊接的)
  • 杜邦线——母对母

软件——

  • Keil5 MDK
  • 野火串口调试助手——提取码:yf99
  • 这里说明一下,如果你的电脑上没有安装 USB 转串口的驱动,需要安装一下,步骤如下:
    1. 提取安装包的链接:https://pan.baidu.com/s/1gLE0pYw_YvismeQz7jCKOw
      提取码:4tj5
    2. 点击 “ 安装 ” ,安装完后弹窗。
      在这里插入图片描述

硬件连接

  • 首先用杜邦线连接传感器的四个引脚,注意看引脚。
    在这里插入图片描述
  • 这里注意下,VCC→3V3,GND→GND,SCL→PB6,SDA→PB7,别接错了。
    在这里插入图片描述
  • 温湿度传感器接入完毕,下面接串口。
  • 使用 USB 线连上 USB 转串口,使用杜邦线连接 SWD ,SWD 怎么连呢?继续往下看。
    在这里插入图片描述
  • 看板子的背面,这里我们只需要用杜邦线依次连接 SWCLK、GND、SWDIO、3V3 就行了。

说明:
NRST:异步复位脚,重置除了 RTC 的寄存器以及后备存储器的寄存器;
SWCLK:时钟开关;
GND:接地;
SWDIO:输入/输出控制。
3V3:电源。

在这里插入图片描述

  • 杜邦线的另一端接 ST_LINK ,根据正面提示的引脚来进行接入,如下图:
    在这里插入图片描述
    在这里插入图片描述
  • 然后将两个 USB 接口插到电脑上,并打开开关(这样,板子就连通了电脑),之后可以进行程序的烧录。

2)程序代码

第一步:工程文件下载

  • 下载工程文件。
  • 链接:https://pan.baidu.com/s/1_9a809N5EZrmR9gaJ9Ijfw
    提取码:sstc
  • 双击打开 I2C_AHT20\Project\RVMDK (uv5)\ 目录下的 BH-F103.uvprojx 文件
    在这里插入图片描述
  • 说明一下:如果打开后,注释显示为乱码,说明 Keil5 的编码格式不是 GB2312,设置编码格式。
  • 点击扳手按钮,Editor 下设置为 Chinese GB2312。
    在这里插入图片描述
  • 设置完成后,关闭工程文件,重新打开即可。
  • 不设置成 GB2312 也没事,只不过设置后,中文注释能成功显示,便于阅读程序。
  • 代码中都有注释,这里就不再说了吧,可以慢慢看,慢慢理解
  • 如果读不太懂,可以了解下 I2C 协议内容以及对照 AHT20 参考手册进行阅读,那就容易得多了
  • 这里就说一下程序的大概流程:
    • 初始化 I2C GPIO 和串口 GPIO
    • 循环调用采集数据函数
      ①初始化传感器系统
      ②触发测量
      ③读取传感器测量数据
      ④将数据计算成十进制并显示
  • 看起来不难,其中发送、接收等等关于引脚的数据传输都是采用高低电平控制的,这就是其中的难点,需要对照手册进行代码阅读才能看得懂。
  • 下面我把代码贴出来,方便查看,说明一下,主要看 bsp_i2c.c 即可。

main.c

#include "stm32f10x.h"
#include "usart.h"
#include "delay.h"
#include "bsp_i2c.h"int main(void)
{	//延时初始化delay_init();//串口初始化uart_init(115200);//IIC_Init();while(1){read_AHT20_once();delay_ms(1500);}
}
/*********************************************END OF FILE**********************/

bsp_i2c.h

#ifndef __BSP_I2C_H
#define __BSP_I2C_H#include "sys.h"
#include "delay.h"
#include "usart.h"#define SDA_IN()  {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)8<<28;}
//CRL = 0000 1111 1111 1111 1111 1111 1111 1111
//8<<28 = 1000 1111 1111 1111 1111 1111 1111 1111
//CRL = 1000 1111 1111 1111 1111 1111 1111 1111 = 0x8fffffff 表示 SDA 输入
#define SDA_OUT() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)3<<28;}
//CRL = 0x3fffffff 表示 SDA 输出#define IIC_SCL    PBout(6) //SCL
#define IIC_SDA    PBout(7) //SDA	 
#define READ_SDA   PBin(7)  //SDA 数据读取 7 管脚void IIC_Init(void);
void  read_AHT20_once(void);
void  reset_AHT20(void);
void  init_AHT20(void);	
void  startMeasure_AHT20(void);
void  read_AHT20(void);
uint8_t  Receive_ACK(void);
void  Send_ACK(void);
void  SendNot_Ack(void);
void I2C_WriteByte(uint8_t  input);
uint8_t I2C_ReadByte(void);	
void  set_AHT20sendOutData(void);
void  I2C_Start(void);
void  I2C_Stop(void);#endif

bsp_i2c.c

#include "bsp_i2c.h"
#include "delay.h"
#include "string.h"uint8_t   ack_status=0;
uint8_t   readByte[6];uint32_t  H1=0;  //Humility
uint32_t  T1=0;  //Temperatureuint8_t  AHT20_OutData[4];/*****************初始化 I2C 函数****************/
void IIC_Init(void)
{GPIO_InitTypeDef GPIO_InitStructure;//启用高速 APB (APB2) 外围时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE );	//GPIO 定义GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP ;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化 SCL(Pin6)高电平IIC_SCL=1;//初始化 SDA(Pin7)高电平IIC_SDA=1;
}/**********************AHT20 数据操作总函数*********************/
void read_AHT20_once(void)
{printf("读取数据中");//延时 10 微妙delay_ms(10);//传输数据前进行启动传感器和软复位reset_AHT20();delay_ms(10);//查看使能位init_AHT20();delay_ms(10);//触发测量startMeasure_AHT20();delay_ms(80);//读数据read_AHT20();delay_ms(5);
}void reset_AHT20(void)
{//数据传输开始信号I2C_Start();//发送数据I2C_WriteByte(0x70);//接收 ACK 信号ack_status = Receive_ACK();//判断 ACK 信号if(ack_status){printf(">");}elseprintf("×");//发送软复位命令(重启传感器系统)I2C_WriteByte(0xBA);//接收 ACK 信号ack_status = Receive_ACK();//判断 ACK 信号if(ack_status)printf(">");elseprintf("×");//停止 I2C 协议I2C_Stop();
}//0x70 —> 0111 0000 前七位表示 I2C 地址,第八位为0,表示 write
//0xE1 —> 看状态字的校准使能位Bit[3]是否为 1
//0x08 0x00 —> 0xBE 命令的两个参数,详见 AHT20 参考手册
void init_AHT20(void)
{//传输开始I2C_Start();//写入 0x70 数据I2C_WriteByte(0x70);//接收 ACK 信号ack_status = Receive_ACK();//判断 ACK 信号if(ack_status)printf(">");elseprintf("×");//写入 0xE1 数据I2C_WriteByte(0xE1);ack_status = Receive_ACK();if(ack_status)printf(">");elseprintf("×");//写入 0x08 数据I2C_WriteByte(0x08);ack_status = Receive_ACK();if(ack_status)printf(">");else printf("×");//写入 0x00 数据I2C_WriteByte(0x00);ack_status = Receive_ACK();if(ack_status) printf(">");else printf("×");//停止 I2C 协议I2C_Stop();
}//0x70 —> 0111 0000 前七位表示 I2C 地址,第八位为0,表示 write
//0xAC —> 触发测量
//0x33 0x00 —> 0xAC 命令的两个参数,详见 AHT20 参考手册
void startMeasure_AHT20(void)
{//启动 I2C 协议I2C_Start();I2C_WriteByte(0x70);ack_status = Receive_ACK();if(ack_status)printf(">");else printf("×");I2C_WriteByte(0xAC);ack_status = Receive_ACK();if(ack_status) printf(">");else printf("×");I2C_WriteByte(0x33);ack_status = Receive_ACK();if(ack_status) printf(">");else printf("×");I2C_WriteByte(0x00);ack_status = Receive_ACK();if(ack_status) printf(">");else printf("×");I2C_Stop();
}void read_AHT20(void)
{uint8_t i;//初始化 readByte 数组for(i=0; i<6; i++){readByte[i]=0;}I2C_Start();//通过发送 0x71 可以获取一个字节的状态字I2C_WriteByte(0x71);ack_status = Receive_ACK();//接收 6 个 8 bit的数据readByte[0]= I2C_ReadByte();//发送 ACK 信号Send_ACK();readByte[1]= I2C_ReadByte();Send_ACK();readByte[2]= I2C_ReadByte();Send_ACK();readByte[3]= I2C_ReadByte();Send_ACK();readByte[4]= I2C_ReadByte();Send_ACK();readByte[5]= I2C_ReadByte();//发送 NACK 信号SendNot_Ack();I2C_Stop();//温湿度的二进制数据处理//0x68 = 0110 1000//0x08 = 0000 1000	if( (readByte[0] & 0x68) == 0x08 ){H1 = readByte[1];//H1 左移 8 位并与 readByte[2] 相或 H1 = (H1<<8) | readByte[2];H1 = (H1<<8) | readByte[3];//H1 右移 4 位H1 = H1>>4;H1 = (H1*1000)/1024/1024;T1 = readByte[3];//与运算T1 = T1 & 0x0000000F;T1 = (T1<<8) | readByte[4];T1 = (T1<<8) | readByte[5];T1 = (T1*2000)/1024/1024 - 500;AHT20_OutData[0] = (H1>>8) & 0x000000FF;AHT20_OutData[1] = H1 & 0x000000FF;AHT20_OutData[2] = (T1>>8) & 0x000000FF;AHT20_OutData[3] = T1 & 0x000000FF;}else{AHT20_OutData[0] = 0xFF;AHT20_OutData[1] = 0xFF;AHT20_OutData[2] = 0xFF;AHT20_OutData[3] = 0xFF;printf("꧰üá?");}printf("完成!\n");printf("----温度:%d%d.%d °C\n",T1/100,(T1/10)%10,T1%10);printf("----湿度:%d%d.%d %%",H1/100,(H1/10)%10,H1%10);printf("\n\n");
}//接收 ACK 信号
uint8_t Receive_ACK(void)
{uint8_t result=0;uint8_t cnt=0;//置 SCL 低电平IIC_SCL = 0;//设置 SDA 为读取数据模式SDA_IN();delay_us(4);//置 SCL 高电平IIC_SCL = 1;delay_us(4);//等待从机发送 ACK 信号,等待时间为 100 个循环while(READ_SDA && (cnt<100)){cnt++;}IIC_SCL = 0;delay_us(4);//如果在等待时间内,则结果为 1if(cnt<100){result=1;}return result;
}//发送 ACK 信号
void Send_ACK(void)
{//设置 SDA 为写数据模式SDA_OUT();IIC_SCL = 0;delay_us(4);//置 SDA 为低电平IIC_SDA = 0;delay_us(4);IIC_SCL = 1;delay_us(4);IIC_SCL = 0;delay_us(4);SDA_IN();
}//发送 NACK 信号
void SendNot_Ack(void)
{//设置 SDA 为写数据模式SDA_OUT();IIC_SCL = 0;delay_us(4);IIC_SDA = 1;delay_us(4);IIC_SCL = 1;delay_us(4);IIC_SCL = 0;delay_us(4);IIC_SDA = 0;delay_us(4);
}//发送一个字节数据
void I2C_WriteByte(uint8_t  input)
{uint8_t  i;//设置 SDA 为写数据模式SDA_OUT();//循环左移发送 8 bit数据for(i=0; i<8; i++){IIC_SCL = 0;delay_ms(5);if(input & 0x80){IIC_SDA = 1;}else{IIC_SDA = 0;}IIC_SCL = 1;delay_ms(5);input = (input<<1);}IIC_SCL = 0;delay_us(4);SDA_IN();delay_us(4);
}	//循环检测 SDA 的电平状态并存储起来
uint8_t I2C_ReadByte(void)
{uint8_t  resultByte=0;uint8_t  i=0, a=0;IIC_SCL = 0;SDA_IN();delay_ms(4);//循环检测for(i=0; i<8; i++){IIC_SCL = 1;delay_ms(3);a=0;if(READ_SDA){a=1;}else{a=0;}resultByte = (resultByte << 1) | a;IIC_SCL = 0;delay_ms(3);}SDA_IN();delay_ms(10);return   resultByte;
}//设置 I2C 协议开始
void I2C_Start(void)
{SDA_OUT();IIC_SCL = 1;delay_ms(4);//SDA 从 1 跳变为 0 的这个过程//表示起始信号IIC_SDA = 1;delay_ms(4);IIC_SDA = 0;delay_ms(4);//SCL 变为 0//表示 SDA 数据无效,此时 SDA 可以进行电平切换IIC_SCL = 0;delay_ms(4);
}//设置 I2C 协议停止
void I2C_Stop(void)
{SDA_OUT();//SCL 高电平,SDA 高电平//停止时序IIC_SDA = 0;delay_ms(4);IIC_SCL = 1;delay_ms(4);//SDA 切换到高电平IIC_SDA = 1;delay_ms(4);
}

usart.h

#ifndef __USART_H
#define __USART_H#include "stdio.h"	
#include "sys.h"  #define USART_REC_LEN  			200
#define EN_USART1_RX 			  1extern u8  USART_RX_BUF[USART_REC_LEN];
extern u16 USART_RX_STA;void uart_init(u32 bound);#endif

usart.c

#include "sys.h"
#include "usart.h"#if SYSTEM_SUPPORT_UCOS#include "includes.h"#endif#if 1#pragma import(__use_no_semihosting)             struct __FILE 
{ int handle; 
}; FILE __stdout;void _sys_exit(int x) 
{ x = x; 
} int fputc(int ch, FILE *f)
{      while((USART1->SR&0X40)==0);USART1->DR = (u8) ch;      return ch;
}#endif #if EN_USART1_RXu8 USART_RX_BUF[USART_REC_LEN];u16 USART_RX_STA=0; /****************串口初始化函数***************/
void uart_init(u32 bound)
{GPIO_InitTypeDef 				GPIO_InitStructure;USART_InitTypeDef			 	USART_InitStructure;// 启用高速 APB (APB2) 外围时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);// 将 USART Tx 的 GPIO 配置为推挽复用模式GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;GPIO_Init(GPIOA, &GPIO_InitStructure);// 将 USART Rx 的 GPIO 配置为浮空输入模式GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;GPIO_Init(GPIOA, &GPIO_InitStructure);  // 配置波特率USART_InitStructure.USART_BaudRate = bound;// 配置 针数据字长USART_InitStructure.USART_WordLength = USART_WordLength_8b;// 配置停止位USART_InitStructure.USART_StopBits = USART_StopBits_1;// 配置校验位USART_InitStructure.USART_Parity = USART_Parity_No;USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;// 配置硬件流控制USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;// 完成串口的初始化配置USART_Init(USART1, &USART_InitStructure);// 使能串口接收中断//USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);// 使能串口USART_Cmd(USART1, ENABLE);
}void USART1_IRQHandler(void)
{u8 Res;
#ifdef OS_TICKS_PER_SECOSIntEnter();    
#endifif(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET){Res =USART_ReceiveData(USART1);if((USART_RX_STA&0x8000)==0){if(USART_RX_STA&0x4000){if(Res!=0x0a)USART_RX_STA=0;elseUSART_RX_STA|=0x8000;}else{	if(Res==0x0d)USART_RX_STA|=0x4000;else{USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ;USART_RX_STA++;if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;}		 }}   		 } 
#ifdef OS_TICKS_PER_SECOSIntExit();  											 
#endif
}#endif	

delay.h

#ifndef __DELAY_H
#define __DELAY_H 			   
#include "sys.h"void delay_init(void);
void delay_ms(u16 nms);
void delay_us(u32 nus);#endif

delay.c

#include "delay.h"
#include "sys.h"#if SYSTEM_SUPPORT_UCOS#include "includes.h" #endifstatic u8  fac_us = 0;
static u16 fac_ms = 0;#ifdef OS_CRITICAL_METHOD
void SysTick_Handler(void)
{				   OSIntEnter();OSTimeTick();         OSIntExit();
}
#endifvoid delay_init()	 
{
#ifdef OS_CRITICAL_METHOD u32 reload;
#endifSysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);fac_us = SystemCoreClock / 8000000;#ifdef OS_CRITICAL_METHOD reload = SystemCoreClock / 8000000;	reload *= 1000000 / OS_TICKS_PER_SEC;fac_ms = 1000 / OS_TICKS_PER_SEC;SysTick->CTRL |= SysTick_CTRL_TICKINT_Msk;SysTick->LOAD = reload;SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk;
#elsefac_ms = (u16)fac_us * 1000;
#endif
}								    #ifdef OS_CRITICAL_METHODvoid delay_us(u32 nus)
{		u32 ticks;u32 told,tnow,tcnt = 0;u32 reload = SysTick->LOAD; ticks = nus * fac_us;  tcnt = 0;told = SysTick->VAL;while(1){tnow = SysTick->VAL;	if(tnow != told){	    if(tnow < told)tcnt += told - tnow;elsetcnt += reload - tnow + told;	    told = tnow;if(tcnt >= ticks)break;}  }; 									    
}void delay_ms(u16 nms)
{	if(OSRunning == TRUE){		  if(nms >= fac_ms){OSTimeDly(nms / fac_ms);}nms %= fac_ms;}delay_us((u32)(nms * 1000));
}
#elsevoid delay_us(u32 nus)
{		u32 temp;	    	 SysTick->LOAD = nus * fac_us;SysTick->VAL = 0x00;SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk ;do{temp = SysTick->CTRL;}while(temp & 0x01 && !(temp &(1<<16)));SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;SysTick->VAL = 0X00;
}void delay_ms(u16 nms)
{	 		  	  u32 temp;		   SysTick->LOAD = (u32)nms * fac_ms;SysTick->VAL = 0x00;SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk ;do{temp = SysTick->CTRL;}while(temp & 0x01 && !(temp& (1<<16)));SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;SysTick->VAL = 0X00;
} 
#endif

sys.h

#ifndef __SYS_H
#define __SYS_H	#include "stm32f10x.h"#define SYSTEM_SUPPORT_UCOS		0#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2)) 
#define MEM_ADDR(addr)  *((volatile unsigned long  *)(addr)) 
#define BIT_ADDR(addr, bitnum)   MEM_ADDR(BITBAND(addr, bitnum)) #define GPIOA_ODR_Addr    (GPIOA_BASE+12) //0x4001080C 
#define GPIOB_ODR_Addr    (GPIOB_BASE+12) //0x40010C0C 
#define GPIOC_ODR_Addr    (GPIOC_BASE+12) //0x4001100C 
#define GPIOD_ODR_Addr    (GPIOD_BASE+12) //0x4001140C 
#define GPIOE_ODR_Addr    (GPIOE_BASE+12) //0x4001180C 
#define GPIOF_ODR_Addr    (GPIOF_BASE+12) //0x40011A0C    
#define GPIOG_ODR_Addr    (GPIOG_BASE+12) //0x40011E0C    #define GPIOA_IDR_Addr    (GPIOA_BASE+8) //0x40010808 
#define GPIOB_IDR_Addr    (GPIOB_BASE+8) //0x40010C08 
#define GPIOC_IDR_Addr    (GPIOC_BASE+8) //0x40011008 
#define GPIOD_IDR_Addr    (GPIOD_BASE+8) //0x40011408 
#define GPIOE_IDR_Addr    (GPIOE_BASE+8) //0x40011808 
#define GPIOF_IDR_Addr    (GPIOF_BASE+8) //0x40011A08 
#define GPIOG_IDR_Addr    (GPIOG_BASE+8) //0x40011E08 #define PAout(n)   BIT_ADDR(GPIOA_ODR_Addr,n)
#define PAin(n)    BIT_ADDR(GPIOA_IDR_Addr,n)#define PBout(n)   BIT_ADDR(GPIOB_ODR_Addr,n)
#define PBin(n)    BIT_ADDR(GPIOB_IDR_Addr,n)#define PCout(n)   BIT_ADDR(GPIOC_ODR_Addr,n)
#define PCin(n)    BIT_ADDR(GPIOC_IDR_Addr,n)#define PDout(n)   BIT_ADDR(GPIOD_ODR_Addr,n)
#define PDin(n)    BIT_ADDR(GPIOD_IDR_Addr,n)#define PEout(n)   BIT_ADDR(GPIOE_ODR_Addr,n)
#define PEin(n)    BIT_ADDR(GPIOE_IDR_Addr,n)#define PFout(n)   BIT_ADDR(GPIOF_ODR_Addr,n)
#define PFin(n)    BIT_ADDR(GPIOF_IDR_Addr,n)#define PGout(n)   BIT_ADDR(GPIOG_ODR_Addr,n)
#define PGin(n)    BIT_ADDR(GPIOG_IDR_Addr,n)void NVIC_Configuration(void);#endif

sys.c

#include "sys.h"void NVIC_Configuration(void)
{NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
}

3)程序烧录

  • 先编译(无错),再烧录。
    在这里插入图片描述
  • 说明:如果你烧录的时候弹窗报错,请继续往下看,没有则跳过。
  • 首先在 keil 软件中,点击 魔术棒 ——> Output ,勾选上 “ Create HEX File ” 选项。
    在这里插入图片描述
  • 进入 “ Debug ” 项,在 Use:中选择 ST-Link Debugger(因为我们使用的是 ST-Link)。
    在这里插入图片描述
  • 然后点击 ST-Link ST-Link Debugger 框的右边 Settings。
  • Port 项勾选为 SW 。
    在这里插入图片描述
  • 然后点击上方的 “ Flash Download ”,勾选上 “ Reset and Run ” ,然后点击 “ Add ” ,选上 STM32F10x High-density Flash ,添加完毕后点击 确定 ——> OK,设置完毕。
    在这里插入图片描述
  • 到这里,烧录完成了。
  • 双击打开野火串口调试助手(就是那个 .exe 应用程序)。
  • 设置波特率为 115200,再打开串口,显示结果如下。
    在这里插入图片描述

四、总结

本次实验共用到了两个知识点,一个是串口通讯,一个是 I2C 协议,串口通讯好理解,只不过基于 I2C 协议的 AHT20 温湿度传感器采集温度这个部分就有些难懂了,毕竟涉及到的知识层面是二进制控制管脚的高低电平,需要配合 AHT20 参考手册才能了解清楚。

这篇文章讲述的是硬件 I2C 协议,而软件 I2C 还没有去学一下,时间不太多,后面再学吧,如果你有兴趣,也可以到 B 站中学习,野火的视频很全。

五、参考资料

1、硬件IIC和软件IIC区别
2、B 站野火教学视频链接
3、AHT20温度采集

这篇关于基于 I2C 协议使用 AHT20 温湿度传感器采集数据的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/657264

相关文章

Java function函数式接口的使用方法与实例

《Javafunction函数式接口的使用方法与实例》:本文主要介绍Javafunction函数式接口的使用方法与实例,函数式接口如一支未完成的诗篇,用Lambda表达式作韵脚,将代码的机械美感... 目录引言-当代码遇见诗性一、函数式接口的生物学解构1.1 函数式接口的基因密码1.2 六大核心接口的形态学

使用DeepSeek API 结合VSCode提升开发效率

《使用DeepSeekAPI结合VSCode提升开发效率》:本文主要介绍DeepSeekAPI与VisualStudioCode(VSCode)结合使用,以提升软件开发效率,具有一定的参考价值... 目录引言准备工作安装必要的 VSCode 扩展配置 DeepSeek API1. 创建 API 请求文件2.

使用TomCat,service输出台出现乱码的解决

《使用TomCat,service输出台出现乱码的解决》本文介绍了解决Tomcat服务输出台中文乱码问题的两种方法,第一种方法是修改`logging.properties`文件中的`prefix`和`... 目录使用TomCat,service输出台出现乱码问题1解决方案问题2解决方案总结使用TomCat,

解决IDEA使用springBoot创建项目,lombok标注实体类后编译无报错,但是运行时报错问题

《解决IDEA使用springBoot创建项目,lombok标注实体类后编译无报错,但是运行时报错问题》文章详细描述了在使用lombok的@Data注解标注实体类时遇到编译无误但运行时报错的问题,分析... 目录问题分析问题解决方案步骤一步骤二步骤三总结问题使用lombok注解@Data标注实体类,编译时

Java中注解与元数据示例详解

《Java中注解与元数据示例详解》Java注解和元数据是编程中重要的概念,用于描述程序元素的属性和用途,:本文主要介绍Java中注解与元数据的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参... 目录一、引言二、元数据的概念2.1 定义2.2 作用三、Java 注解的基础3.1 注解的定义3.2 内

将sqlserver数据迁移到mysql的详细步骤记录

《将sqlserver数据迁移到mysql的详细步骤记录》:本文主要介绍将SQLServer数据迁移到MySQL的步骤,包括导出数据、转换数据格式和导入数据,通过示例和工具说明,帮助大家顺利完成... 目录前言一、导出SQL Server 数据二、转换数据格式为mysql兼容格式三、导入数据到MySQL数据

Java中使用Java Mail实现邮件服务功能示例

《Java中使用JavaMail实现邮件服务功能示例》:本文主要介绍Java中使用JavaMail实现邮件服务功能的相关资料,文章还提供了一个发送邮件的示例代码,包括创建参数类、邮件类和执行结... 目录前言一、历史背景二编程、pom依赖三、API说明(一)Session (会话)(二)Message编程客

C++中使用vector存储并遍历数据的基本步骤

《C++中使用vector存储并遍历数据的基本步骤》C++标准模板库(STL)提供了多种容器类型,包括顺序容器、关联容器、无序关联容器和容器适配器,每种容器都有其特定的用途和特性,:本文主要介绍C... 目录(1)容器及简要描述‌php顺序容器‌‌关联容器‌‌无序关联容器‌(基于哈希表):‌容器适配器‌:(

C#提取PDF表单数据的实现流程

《C#提取PDF表单数据的实现流程》PDF表单是一种常见的数据收集工具,广泛应用于调查问卷、业务合同等场景,凭借出色的跨平台兼容性和标准化特点,PDF表单在各行各业中得到了广泛应用,本文将探讨如何使用... 目录引言使用工具C# 提取多个PDF表单域的数据C# 提取特定PDF表单域的数据引言PDF表单是一

使用Python实现高效的端口扫描器

《使用Python实现高效的端口扫描器》在网络安全领域,端口扫描是一项基本而重要的技能,通过端口扫描,可以发现目标主机上开放的服务和端口,这对于安全评估、渗透测试等有着不可忽视的作用,本文将介绍如何使... 目录1. 端口扫描的基本原理2. 使用python实现端口扫描2.1 安装必要的库2.2 编写端口扫