如何看懂时序图,以DHT21为例
有很多传感器手册给了我们时序图,我们只要按照时序图操作就行了,还有一些是标准接口,例如SPI,IIC,UART,这些可以利用硬件提供的收发器通信,还有一些我们没有足够的接口,或者没有对应的接口与之通信,我们可以按照手册提供的时序图,利用IO来完成读写操作。完成的思路是模块化编程思想,将问题逐个分解。由大化小,实现小的功能。
比如常用的单线协议的温湿度传感器DHT21.
可以看到一共40BIT,并注意到是以8BIT为单位的,因此我们可以先规划成每次读取8BIT,读取5次,完成读取。
开始读取时候,假设传感器是空闲的,那么这个时候传感器就是在高电平,主控想要发起读取,要给传感器一个读取的信号,
这个信号就是先拉低至少500us,然后拉高20到40us.
因此这个时候,主控的IO要处于输出状态,我们可以输出1,也可以输出0,先输出1,然后输出0,将0持续的事件大于500us,然后输出1
保持20us到40us.
为了靠谱,我这里拉低持续2ms,拉高持续30us.
先设置IO的模式为输出模式。
复制
Write_AM2301_PIN_Init();
拉低这个端口,即输出0
复制
RESET_AM2301_PIN();
保持2ms,这样就满足最少500us了。
复制
HAL_Delay(2);
然后拉高它,输出1
复制
SET_AM2301_PIN();
保持30us
复制
rt_hw_us_delay(30);
接下来传感器就该响应这个请求了,这个时候就要让主控读取信号的模式了
读取相应,因为接下来器件会主动拉低总线80us,然后再拉高80us.
我们先切换主控的这个IO到输入模式,进行读取。然后判断
器件准备好的这个拉低拉高信号。
第一步,切断刀输入模式,准备读取IO信号
复制
Read_AM2301_PIN_Init();
Sensor_AnswerFlag=0;
判断是否传感器拉低了总线,拉低表示传感器要发送准备好信号了
复制
if(Read_AM2301_PIN()==GPIO_PIN_RESET)
{
Sensor_AnswerFlag=1;
Sys_CNT=0;
等待准备好的拉低段80us结束,并计数,看看是否超时。
复制
while(Read_AM2301_PIN()==GPIO_PIN_RESET)
{
if(++Sys_CNT>3000)
{
Sensor_ErrorFlag=1;
return 0;
}
}
Sys_CNT=0;
如果准备拉低状态顺利结束,再看看准备信号的拉高状态是否OK
复制
while(Read_AM2301_PIN()==GPIO_PIN_SET)
{
if(++Sys_CNT>3000)
{
Sensor_ErrorFlag=1;
return 0;
}
}
一切OK的话,就该读取实际的传感器输出值了。这个时候要写入到存储传感器40BIT数值的变量里了
每次读取8BIT,一共5此,所以用个循环。方到准备好的变量数组里。
复制
for(i=0;i<5;i++)
{
AM2301_Data = Read_AM2301_Data();
}
接下来我们还要实现什么呢,当然是基本的读取8BIT的操作了。
根据这个时序图,可以看出来什么是1,什么是0.
我们看到总线在传输数据时候,拉低都是50us,只有拉高长短不同,长的表示1,短的表示0.
因此我们读取每一位时候,只要先判断是不是低电平或者高电平,就行了。
在低电平时候我们等待,当高电平到来我们判断是否大于28us,因为26us~28us表示0,70us标志1.
所以我们找一个介于28到70us之间的判断阈值。
比如我以30us作为阈值,当低电平结束后,我延时30us,如果是0,这个时候高电平肯定结束了,
如果是1,高电平还在持续。
因此我通过这个思路判断是0还是1.
因为我要读取是8BIT,因此我用循环8次的操作。
复制
unsigned char Read_AM2301_Data(void)
{
unsigned char i,cnt,buffer,tmp;
//要读取8次
for (i = 0; i < 8; i++)
{
cnt=0;
//判断低电平是否结束
while(!Read_AM2301_PIN())
{
if(++cnt>=3000)
break;
}
//低电平结束后,进入高电平,开始计时30us
rt_hw_us_delay(30);
tmp=0;
//如果此时还是高电平,那么肯定是大于28us,确定是1来了,赋值1
if(Read_AM2301_PIN())
tmp=1;
cnt=0;
//等待高电平结束,号进入下一位的读取
while(Read_AM2301_PIN())
{
if(++cnt>=2000)
break;
}
//移位写入刚刚得到的1个BIT
buffer<<=1;
buffer|=tmp;
}
return buffer;
}
接下来实现什么呢?
实现读取IO状态和写高低电平。
复制
unsigned char Read_AM2301_PIN(void)
{
return HAL_GPIO_ReadPin(AM2301_PORT, AM2301_PIN);
}
void SET_AM2301_PIN(void)
{
HAL_GPIO_WritePin(AM2301_PORT, AM2301_PIN,GPIO_PIN_SET);
}
void RESET_AM2301_PIN(void)
{
HAL_GPIO_WritePin(AM2301_PORT, AM2301_PIN,GPIO_PIN_RESET);
}
这里我直接调用的HAL库函数,其实这么做是方便移植,如果你要去其他芯片下使用,你只需要实现这3个函数以及延时函数就行了。逻辑顺序无需修改。最后奉上源码
复制
#include "stm32f0xx_hal.h"
//读传感器 端口位定义,可修改
//*
#define AM2301_PIN GPIO_PIN_10
#define AM2301_PORT GPIOA
#define AM2301_GPIO_CLK_ENABLE() __HAL_RCC_GPIOA_CLK_ENABLE()
#define AM2301_GPIO_CLK_DISABLE() __HAL_RCC_GPIOA_CLK_DISABLE()
unsigned char Sensor_AnswerFlag; //收到起始标志位
unsigned char Sensor_ErrorFlag; //读取传感器错误标志
unsigned int Sys_CNT;
unsigned char AM2301_Data[5]={0x00,0x00,0x00,0x00,0x00};
void Read_AM2301_PIN_Init(void)
{
AM2301_GPIO_CLK_ENABLE();
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
GPIO_InitStruct.Pin = AM2301_PIN;
HAL_GPIO_Init(AM2301_PORT, &GPIO_InitStruct);
}
void Write_AM2301_PIN_Init(void)
{
AM2301_GPIO_CLK_ENABLE();
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
GPIO_InitStruct.Pin = AM2301_PIN;
HAL_GPIO_Init(AM2301_PORT, &GPIO_InitStruct);
}
unsigned char Read_AM2301_PIN(void)
{
return HAL_GPIO_ReadPin(AM2301_PORT, AM2301_PIN);
}
void SET_AM2301_PIN(void)
{
HAL_GPIO_WritePin(AM2301_PORT, AM2301_PIN,GPIO_PIN_SET);
}
void RESET_AM2301_PIN(void)
{
HAL_GPIO_WritePin(AM2301_PORT, AM2301_PIN,GPIO_PIN_RESET);
}
unsigned char Read_AM2301_Data(void)
{
unsigned char i,cnt,buffer,tmp;
for (i = 0; i < 8; i++)
{
cnt=0;
while(!Read_AM2301_PIN())
{
if(++cnt>=3000)
break;
}
rt_hw_us_delay(30);
tmp=0;
if(Read_AM2301_PIN())
tmp=1;
cnt=0;
while(Read_AM2301_PIN())
{
if(++cnt>=2000)
break;
}
buffer<<=1;
buffer|=tmp;
}
return buffer;
}
unsigned char Read_Sensor(void)
{
unsigned char i;
Write_AM2301_PIN_Init();
RESET_AM2301_PIN();
// rt_thread_mdelay(2);
HAL_Delay(2);
SET_AM2301_PIN();
rt_hw_us_delay(30);
SET_AM2301_PIN();
Read_AM2301_PIN_Init();
Sensor_AnswerFlag=0;
if(Read_AM2301_PIN()==GPIO_PIN_RESET)
{
Sensor_AnswerFlag=1;
Sys_CNT=0;
while(Read_AM2301_PIN()==GPIO_PIN_RESET)
{
if(++Sys_CNT>3000)
{
Sensor_ErrorFlag=1;
return 0;
}
}
Sys_CNT=0;
while(Read_AM2301_PIN()==GPIO_PIN_SET)
{
if(++Sys_CNT>3000)
{
Sensor_ErrorFlag=1;
return 0;
}
}
for(i=0;i<5;i++)
{
AM2301_Data[i] = Read_AM2301_Data();
}
}
else
{
Sensor_AnswerFlag=0;
}
return 1;
}
此文章已获得原创/原创奖标签,著作权归21ic所有,未经允许禁止转载。
---------------------
作者:gaoyang9992006
链接:https://bbs.21ic.com/icview-3027014-1-1.html
来源:21ic.com
此文章已获得原创/原创奖标签,著作权归21ic所有,任何人未经允许禁止转载。
如何看懂时序图,以DHT21为例的更多相关文章
- 看懂UML图
看懂UML类图和时序图 这里不会将UML的各种元素都提到,我只想讲讲类图中各个类之间的关系: 能看懂类图中各个类之间的线条.箭头代表什么意思后,也就足够应对 日常的工作和交流: 同时,我们应该能将类图 ...
- 看懂类图——UML类图基础
类图 要学懂设计模式,就需要先看得懂类图,类与类之间的关系是学习设计模式的基础,而在软件工程中,类与类之间的关系是通过UML中的类图来体现. 这篇笔记包含的不会是类图的所有东西,包含的只是各个类之间的 ...
- (转)看懂类图——UML类图基础
类图 要学懂设计模式,就需要先看得懂类图,类与类之间的关系是学习设计模式的基础,而在软件工程中,类与类之间的关系是通过UML中的类图来体现. 这篇笔记包含的不会是类图的所有东西,包含的只是各个类之间的 ...
- 0160 十分钟看懂时序数据库(I)-存储
摘要:2017年时序数据库忽然火了起来.开年2月Facebook开源了beringei时序数据库:到了4月基于PostgreSQL打造的时序数据库TimeScaleDB也开源了,而早在2016年7月, ...
- 看懂UML类图与时序图
看懂UML类图和时序图 这里不会将UML的各种元素都提到,我只想讲讲类图中各个类之间的关系: 能看懂类图中各个类之间的线条.箭头代表什么意思后,也就足够应对 日常的工作和交流: 同时,我们应该能将类图 ...
- 看懂UML类图和时序图
看懂UML类图和时序图 这里不会将UML的各种元素都提到,我只想讲讲类图中各个类之间的关系: 能看懂类图中各个类之间的线条.箭头代表什么意思后,也就足够应对 日常的工作和交流: 同时,我们应该能将类图 ...
- [转] 看懂UML类图和时序图
PS: 组合关系:实心,一个类A属于另一个类,或多个类,但是类A不能单独存在去使用,A一般是一种抽象的东西 聚合关系:空心,一个类A可以单独存在使用 不论组合聚合,A的方法都会被直接调用. 看懂UML ...
- [转]看懂UML类图
这里不会将UML的各种元素都提到,我只想讲讲类图中各个类之间的关系: 能看懂类图中各个类之间的线条.箭头代表什么意思后,也就足够应对 日常的工作和交流: 同时,我们应该能将类图所表达的含义和最终的代码 ...
- 看懂UML类图
这里不会将UML的各种元素都提到,我只想讲讲类图中各个类之间的关系: 能看懂类图中各个类之间的线条.箭头代表什么意思后,也就足够应对 日常的工作和交流: 同时,我们应该能将类图所表达的含义和最终的代码 ...
随机推荐
- RabbitMQ 消息中间件 的下载与安装【window10】
1.前言 弄了好久,才终于把 rabbitmq装好 ,本来是很简单的,但是,安装有个要求就是路径不能有中文字符, 虽然可以安装,但是无法运行,需要修改路径名为非中文字符后重装rabbitmq才可以运行 ...
- 5大最新云原生镜像构建工具全解析,3个来自Google,你了解几个?
1云原生大背景下的镜像构建在分享开始,我想先跟大家简单聊一下云原生,可能不会详细展开,而是带领大家了解一下云原生对镜像构建方面的影响.第一,在接触云原生相关的技术时,无论是要解决开发.测试环境的问题, ...
- 《剑指offer》面试题56 - I. 数组中数字出现的次数
问题描述 一个整型数组 nums 里除两个数字之外,其他数字都出现了两次.请写程序找出这两个只出现一次的数字.要求时间复杂度是O(n),空间复杂度是O(1). 示例 1: 输入:nums = [4,1 ...
- RabbitMQ 中的分布式,普通 cluster 模式的构建
RabbitMQ 如何做分布式 前言 集群配置方案 cluster 普通模式 镜像模式 federation shovel 节点类型 RAM node Disk node 集群的搭建 1.局域网配置 ...
- 记一次简单的Oracle离线数据迁移至TiDB过程
背景 最近在支持一个从Oracle转TiDB的项目,为方便应用端兼容性测试需要把Oracle测试环境的库表结构和数据同步到TiDB中,由于数据量并不大,所以怎么方便怎么来,这里使用CSV导出导入的方式 ...
- 【解决了一个小问题】alert manager中的cluster.advertise-address参数是什么意思?
如果在启动 alert manager的时候,不填写参数: /usr/bin/alertmanager --config.file=/etc/alert_manager/alertmanager.ya ...
- Cesium入门12 - Camera Modes - 相机模式
Cesium入门12 - Camera Modes - 相机模式 Cesium中文网:http://cesiumcn.org/ | 国内快速访问:http://cesium.coinidea.com/ ...
- Linux下进程线程,Nignx与php-fpm的进程线程方式
1.进程与线程区别 进程是程序执行时的一个实例,即它是程序已经执行到课中程度的数据结构的汇集.从内核的观点看,进程的目的就是担当分配系统资源(CPU时间.内存等)的基本单位. 线程是进程的一个执行流, ...
- promise的队列,宏任务,微任务,同步任务
// promise里面有一个特别的任务,就是微任务 // 同步任务>微任务>宏任务 setTimeout(() => { console.log("setTimeout& ...
- Spring 官宣发布 Spring Boot 3.0 第一个里程碑 M1,从 Java 8 提升到 Java 17!
Spring官方于2022年1月20日发布Spring Boot 3.0.0-M1版本,预示开启了Spring Boot 3.0的里程碑,相信这是通往下一代Spring框架的激动人心的旅程. 接下来一 ...