本文主要是介绍基于 I2C 协议使用 AHT20 温湿度传感器采集数据,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
目录
- 一、I2C 协议简介
- 二、AHT20 温湿度传感器
- 三、实验步骤
- 1)实验准备
- 2)程序代码
- 3)程序烧录
- 四、总结
- 五、参考资料
本文内容:
学习 I2C 总线通信协议,完成基于 I2C 硬件协议的 AHT20 温湿度传感器的数据采集,并将采集的温度-湿度值通过串口输出。具体任务:
- 解释什么是 “ 硬件I2C ” 和 “ 软件I2C ” ? (阅读野火配套教材的第 23 章 “ I2C–读写EEPROM ” 原理章节)
- 阅读 AHT20 数据手册,编程实现:每隔2秒钟采集一次温湿度数据,并通过串口发送到上位机(win10)。
一、I2C 协议简介
- IIC 通讯协议 (Inter-Integrated Circuit,又叫 I2C 、I²C ) 是由 Phiilps 公司开发的,由于它引脚少,硬件实现简单,可扩展性强,不需要 USART、CAN 等通讯协议的外部收发设备,现在被广泛地使用在系统内多个集成电路 ( IC ) 间的通讯。
1、I2C 物理层
- I2C 通讯设备之间的常用连接方式如下图:
- 它的物理层有如下特点:
- 它是一个支持设备的总线。“总线”指多个设备共用的信号线。在一个 I2C 通讯总线中,可连接多个 I2C 通讯设备,支持多个通讯主机及多个通讯从机
- 一个 I2C 总线只使用两条总线线路,一条双向串行数据线(SDA) ,一条串行时钟线(SCL)。数据线即用来表示数据,时钟线用于数据收发同步
- 每个连接到总线的设备都有一个独立的地址,主机可以利用这个地址进行不同设备之间的访问
- 总线通过上拉电阻接到电源。当 I2C 设备空闲时,会输出高阻态,而当所有设备都空闲,都输出高阻态时,由上拉电阻把总线拉成高电平
- 多个主机同时使用总线时,为了防止数据冲突,会利用仲裁方式决定由哪个设备占用总线
- 具有三种传输模式:标准模式传输速率为 100kbit/s ,快速模式为 400kbit/s ,高速模式下可达 3.4Mbit/s,但目前大多 I2C 设备尚不支持高速模式
- 连接到相同总线的 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 通信波形
- 区别:
- 硬件 I2C 的效率要远高于软件的;软件 I2C 由于不受管脚限制,接口比较灵活
- 硬件 I2C 是直接调用内部寄存器进行配置;软件 I2C 是通过 GPIO ,软件模拟寄存器的工作方式
- 硬件 I2C 配置了 IO 口的功能(I2C 功能);软件 I2C 没有配置 IO 口的功能
- 硬件 I2C 的 I2C 写函数,有调用现成的函数或者给某个寄存器赋值;软件 I2C 传输数据的方式是一个 bit 一个 bit 模拟发生送的,肯定用到了循环
- 根据代码量判断,硬件 I2C 的代码量肯定比软件 I2C 的代码量小
- 硬件 I2C 用法比较复杂,软件 I2C 的流程更清楚一些
- 硬件 I2C 的速度比软件 I2C 的速度快,并且硬件 I2C 可以用 DMA
- 硬件 I2C 只能在固定管脚上;模拟 I2C 可以在任何管脚上
- 软件 I2C 是程序员使用程序控制 SCL、SDA 线输出高低电平,模拟 I2C 协议的时序,一般较硬件 I2C 稳定,但是程序较为繁琐,但不难
- 硬件 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 转串口的驱动,需要安装一下,步骤如下:- 提取安装包的链接:https://pan.baidu.com/s/1gLE0pYw_YvismeQz7jCKOw
提取码:4tj5 - 点击 “ 安装 ” ,安装完后弹窗。
- 提取安装包的链接:https://pan.baidu.com/s/1gLE0pYw_YvismeQz7jCKOw
硬件连接
- 首先用杜邦线连接传感器的四个引脚,注意看引脚。
- 这里注意下,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 温湿度传感器采集数据的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!