在实际情况中很多传感器并不会用到很复杂的通信协议,反而简单的数据传输机制能够大大节省成本且满足实际需要。红外传感器和DS18B20是典型的单总线传感器,本期通过这两类传感器的工作原理和操作实例来认识单总线。
单总线能够极大程度节约管脚资源,只用一根管脚即可进行通讯。这种通讯方式在传感器中运用较多,传感器采集到的数据通过一根数据线直接传到单片机。
先导知识:
【资料图】
红外线经常使用在红外遥控中,可以通过非接触的方式来控制家中的一些电气设备,既然可以通过遥控控制设备,中间一定存在数据传输。遥控中的发射器将内部按键的数值和命令通过红外线发射出去,电器中有红外接收器,接收器接收到后可以解析命令进行不同的动作。
红外线是光的一种,光的本质是电磁波,其传播本质上是一种粒子振动。广义上,光是指所有的电磁波谱。狭义上的光是人类眼睛可以看见的一种电磁波,也称可见光。
1.光的波长:是指波在一个振动周期内传播的距离。光的波长由光的频率以及传播的介质决定,光通过不同介质的时候,频率不变而波长发生改变。
2.光的颜色:是由它的波长来决定的,各种颜色有各自的波长,人的眼睛能看到的可见光按波长从长到短排列,依次为红、橙、黄、绿、青、蓝、紫。红光到紫光,波长逐渐变小,红外线是比红光波长还长的非可见光。
高于绝对零度(-273.15℃)的物质都可以产生红外线。现代物理学称之为热射线。我们把红光之外的辐射叫做红外线(紫光之外是紫外线),人的肉眼不可见。
3.无线远程遥控技术:又称为遥控技术,是指实现对被控目标的遥远控制,在工业控制、航空航天、家电领域应用广泛。
4.红外遥控:是一种无线、非接触控制技术,具有抗干扰能力强,信息传输可靠,功耗低,成本低,易实现等显著优点,被诸多电子设备特别是家用电器广泛采用,并越来越多的应用到计算机和手机系统中。
5.红外通讯:就是通过红外线传输数据。发射器发出红外信号,接收器接收到信号进行解析。
6.红外遥控器:遥控器是利用一个红外发光二极管,以红外光为载体来将按键信息传递给接收端的设备。红外光对于人眼是不可见的,因此使用红外遥控器不会影响人的视觉(可以打开手机摄像头,遥控器对着摄像头按,可以看到遥控器发出的红外光)。
7.信号调制:日常生活环境中有很多红外光源,太阳、蜡烛火光、白炽灯、甚至是我们的身体。这些红外光源都可能会对我们的接收设备产生干扰,为了屏蔽干扰,只接收有效信息,我们就需要用到调制。通过调制我们可以把指定的数字信号转换为特定频率的红外光进行发送,调制载波频率一般在30khz到60khz之间,大多数使用的是38kHz。
8.红外接受器:红外线接收器是一种可以接收红外信号并能独立完成从红外线接收到输出与TTL电平信号兼容的器件,体积和普通的塑封三极管差不多,适合于各种红外线遥控和红外线数据传输。
9.信号解调:解调就是将模拟信号(光信号)转换成数字信号。红外接收器接收到外部发射器传过来的红外信号后,会按照固定的协议去解析信号,并转换成数字信号输出。
NEC协议详解:
NEC协议是典型的红外协议,其实红外协议有很多,如ITT、NRC17(Nokia)、Sharp等,NEC协议使用的最多的一种。在传出过程中主要传输8 位地址码和8 位命令码,地址码主要标识遥控可以解析的设备,不同的遥控器控制不同的设备,不同发命令码对应不同的按键;该协议会完整发射两次地址码(第一次地址码,第二次是地址反码,这里起到了一个校验的作用,两次命令码同理)和命令码,以提高可靠性;脉冲时间长短可调制;典型的为38KHz 载波频率;位时间 1.12ms(表示逻辑0) 或 2.25ms(表示逻辑1)
红外遥控实例讲解
电路分析:整个红外传感器只使用PG8与MCU进行传输
实验目的:按下遥控按键,主机通过红外接收器接收到信号(模拟信号)并解码(数字信号),识别出按键的命令码,打印出对应的按键符号
将PG8设为中断模式,下降沿和上升沿均可触发中断。第一次检测到下降沿后触发中断并检查是否是下降沿且是否达到要求的时长,如果满足等待第二次上升沿触发,检查是否是上升沿且是否达到要求的时长。之后的每一个波形都会触发两次中断检测时间,用来确定是逻辑1还是逻辑0,连续检测64次
实验步骤:
1.配置RCC
2.配置PG8为外部中断模式
3.配置中断
4.编写代码
//main.cint main(void){ HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_USART1_UART_Init(); /*配置systick为100us中断一次 */ /*原来默认是1ms触发一次中断,现在要100us中断一次*/ /*所以只需将计数值缩小10倍即可,因此除数值由1000变为了10000 */ HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq()/10000); HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK); HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0); printf("nr"); printf("nr-------------------------------------------------"); printf("nr FS-STM32开发板 IR红外接收是u按程序"); printf("nr 请将红外接收头连接到开发板对应的接口"); printf("nr 然后用红外遥控进行控制,注意串口输出"); printf("nr-------------------------------------------------"); printf("nr----------------- 协议如下 ----------------------"); printf("nr 首先是引导码:开始拉低9ms,接着一个4.5ms的高脉冲"); printf("nr 引导码的作用是通知接收器准备接收数据"); printf("nr 引导码之后是4个字节的二进制码,其中前两个字节是:"); printf("nr 遥控识别码ID,第一个为正码,第二个为反码,"); printf("nr 后两字节是键值,第一个为正码,第二个为反码."); printf("nr 最后有可能持续按键,上述数据发送完后则发送"); printf("nr 9ms低,2ms高的脉冲"); printf("nr---------------- 载波为38kHz --------------------"); printf("nr传输一个逻辑1需要2.25ms(560us低电平+1680us高电平)"); printf("nr传输一个逻辑0需要1.125ms(560us低电平+560us高电平)"); printf("nr-------------------------------------------------"); printf("nr----- 本实验在中断中检测接收IR红外线数据 ------nr"); while (1) { Remote_Infrared_KeyDeCode(); }}void HAL_SYSTICK_Callback(void){ if(GlobalTimingDelay100us != 0) { GlobalTimingDelay100us--; }}void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin){ //中断处理函数,上升沿和下降沿均可触发 //中断检测时间用于确定每一个脉冲代表的含义 Remote_Infrared_KEY_ISR();}
//RemoteInfrared.h#include "stm32f4xx_hal.h"#defineRemote_Infrared_DAT_INPUT HAL_GPIO_ReadPin(GPIOG, GPIO_PIN_8)typedef struct _Remote_Infrared_data_struct //定义红外线接收到的数据结构体类型{ uint8_t bKeyCodeNot; //按键的ASIIC码值——反码 uint8_t bKeyCode; //shift键按下标志 uint8_t bIDNot; //断码标志位——反码 uint8_t bID; //新键标志位}Remote_Infrared_data_struct;typedef union _Remote_Infrared_data_union //定义红外线接收到的数据联合体类型{ Remote_Infrared_data_struct RemoteInfraredDataStruct; uint32_t uiRemoteInfraredData; }Remote_Infrared_data_union;void Remote_Infrared_KEY_ISR(void);uint8_t Remote_Infrared_KeyDeCode(void);
//RemoteInfrared.c#include "RemoteInfrared.h"#define REPEAT_KEY 0xEEextern __IO uint32_t GlobalTimingDelay100us;extern __IO uint32_t GlobalTimingDelay100usTx;__IO uint32_t FlagGotKey = 0;__IO Remote_Infrared_data_union RemoteInfrareddata;/************************************************************************//红外处理接收-------------------------协议--------------------------开始拉低9ms,接着是一个4.5ms的高脉冲,通知器件开始传输数据了接着是发送4个8位二进制码,第一二个是遥控器识别码(REMOTE_ID),第一个为正码(0),第二个为反码(255),接着两个数据是键值,第一个为正码,第二个为反码,发送完后40ms,遥控器再发送一个9ms低,2ms高的脉冲,表示按键的次数,出现一次则证明只按下了一次,如果出现多次,则可以认为是持续按下该键*名称: Remote_Infrared_KEY_ISR(INT11_vect ) *功能: INT0中断服务程序 *参数: 无 *返回: 无 *************************************************************************/// 检测脉冲宽度最长为5msconst uint32_t TIME_DELAY_6MS = 60;const uint32_t TIME_DELAY_10MS = 100;void Remote_Infrared_KEY_ISR(void){ static __IO uint8_t bBitCounter = 0; //记录中断进入的次数 static __IO uint32_t bKeyCode = 0; bBitCounter++; if(bBitCounter == 1) //为1表示第一次进来,就需要检测波形 { if(Remote_Infrared_DAT_INPUT) //如果读管脚是高电平,值为真,则无效 //因为先进来的必须是9ms的低电平 { bBitCounter = 0; } else { GlobalTimingDelay100us = TIME_DELAY_10MS; //将GlobalTimingDelay100us值设为100,因为该值每100us减1 //100us触发一次中断,减1,从100减到0刚好10ms //当然正常情况下不会减到0,因为9ms左右就会再次触发中断 } } · else if(bBitCounter == 2) // 4.5ms高脉冲 { if(Remote_Infrared_DAT_INPUT)//高脉冲为真 { //高脉冲触发中断,先检查上次GlobalTimingDelay100us剩余值 //理想状态下为10,当然不可能那么精确 //若以我们给定了一个范围,剩余值在这个范围就认为满足时间 if((GlobalTimingDelay100us > 2) && (GlobalTimingDelay100us < 18)) { //之后就可以GlobalTimingDelay100us赋新值60检查高脉冲时间 GlobalTimingDelay100us = TIME_DELAY_6MS; } else { bBitCounter = 0; //printf("."); } } else { bBitCounter = 0; } } else if(bBitCounter == 3) // 4.5ms的高脉冲 { if(Remote_Infrared_DAT_INPUT) { bBitCounter = 0; } else { //正常情况下GlobalTimingDelay100us值剩余15 //给定范围是5-20,则满足4.5ms的高脉冲 if((GlobalTimingDelay100us > 5) && (GlobalTimingDelay100us < 20)) //起始码 4.5ms { //到这里,引导码(起始码)满足条件,正确 GlobalTimingDelay100us = TIME_DELAY_6MS; } else if((GlobalTimingDelay100us > 32) && (GlobalTimingDelay100us < 46)) //重复码 2.25ms { //范围在32-46,则高电平满足了重复码的时间 bBitCounter = 0;//自动持续动作,进入次数变为0 RemoteInfrareddata.uiRemoteInfraredData = bKeyCode; //RemoteInfrareddata.uiRemoteInfraredData = REPEAT_KEY; bBitCounter = 0; FlagGotKey = 1; } else { bBitCounter = 0; //既不是起始码也不是重复码,回5到初始值 //printf("%d&", GlobalTimingDelay100us); } } } else if(bBitCounter > 3 && bBitCounter < 68) //接收32位数据,共检测64次 { //在高电平中断中检测低电平是否合格,低电平中断中检测高电平是否合格 if(Remote_Infrared_DAT_INPUT)//检测数据脉冲低电平的时间 //不管逻辑0还是逻辑1,前半段脉冲都是560us低电平 { if((GlobalTimingDelay100us > 50) && (GlobalTimingDelay100us < 58)) { GlobalTimingDelay100us = TIME_DELAY_6MS;//低电平合格,为GlobalTimingDelay100us重新赋值 } else { bBitCounter = 0; //printf("#"); } } else //低电平中断中检测高电平 { if((GlobalTimingDelay100us > 50) && (GlobalTimingDelay100us < 58)) // "0" 0.56ms×óÓÒ { GlobalTimingDelay100us = TIME_DELAY_6MS; bKeyCode < <= 1; // MSB First bKeyCode += 0x00; } else if((GlobalTimingDelay100us > 40) && (GlobalTimingDelay100us < 48)) //"1" 1.685ms×óÓÒ { GlobalTimingDelay100us = TIME_DELAY_6MS; bKeyCode < <= 1; // MSB First bKeyCode += 0x01; } else { bBitCounter = 0; } } if(bBitCounter == 67) { RemoteInfrareddata.uiRemoteInfraredData = bKeyCode; bBitCounter = 0; FlagGotKey = 1; //printf("KeyCode = 0x%X", bKeyCode); } } else { bBitCounter = 0; //printf("KeyCode = 0x%X", bKeyCode); } }/*************************************************************************名称: unsigned char Remote_Infrared_KeyDeCode(unsigned char bKeyCode) *功能: PS2键盘解码程序 *参数: bKeyCode 键盘码*返回: 按键的ASIIC码 ************************************************************************/uint8_t Remote_Infrared_KeyDeCode(void){ //uint8_t Key = 0xFF; if (FlagGotKey == 1)//通码,为1时表示有按键按下 { FlagGotKey = 0;//还原为0以便下次判断 //检查地址码和命令码的反码取反后是否与原来相同 if((RemoteInfrareddata.RemoteInfraredDataStruct.bID == (uint8_t)~ RemoteInfrareddata.RemoteInfraredDataStruct.bIDNot) && (RemoteInfrareddata.RemoteInfraredDataStruct.bKeyCode == (uint8_t)~ RemoteInfrareddata.RemoteInfraredDataStruct.bKeyCodeNot)) { printf("nr IR Receive KeyCode = 0x%02X, ", RemoteInfrareddata.RemoteInfraredDataStruct.bKeyCode); switch(RemoteInfrareddata.RemoteInfraredDataStruct.bKeyCode) { case 0: printf("ERROR "); break; case 0xA2: printf("CH- "); break; case 0X62: printf("CH "); break; case 0XE2: printf("CH+ "); break; case 0X22: printf("|< < "); break; case 0X02: printf(" >>| "); break; case 0XC2: printf("PLAY/PAUSE"); break; case 0XE0: printf("VOL- "); break; case 0XA8: printf("VOL+ "); break; case 0X90: printf("EQ "); break; case 0X98: printf("100+ "); break; case 0XB0: printf("200+ "); break; case 0x68: printf("0 "); break; case 0x30: printf("1 "); break; case 0x18: printf("2 "); break; case 0x7A: printf("3 "); break; case 0x10: printf("4 "); break; case 0x38: printf("5 "); break; case 0x5A: printf("6 "); break; case 0x42: printf("7 "); break; case 0x4A: printf("8 "); break; case 0x52: printf("9 "); break; default: printf("Unknown key!"); } } else { printf("nr ERR 0x%08X", RemoteInfrareddata.uiRemoteInfraredData); } } return RemoteInfrareddata.RemoteInfraredDataStruct.bKeyCode;}
DS18B20温度传感器
独特的单总线接口方式,DS18B20在与微处理器连接时仅需要一条口线即可实,现微处理器与DS18B20的双向通讯。大大提高了系统的抗干扰性。测温范围 -55℃~+125℃,精度为±0.5℃。支持多点组网功能,多个DS18B20可以并联在唯一的三线上,最多只能并联8个,实现多点测温,如果数量过多,会使供电电源电压过低,从而造成信号传输的不稳定。工作电源: 3.05.5V/DC(可以数据线寄生电源)。在使用中不需要任何外围元件。测量结果以912位数字量方式串行传送。
DS18B20硬件连接
DS18B20通信类型
单总线是一种半双工通信方式。DS18B20共有6种信号类型:复位脉冲、应答脉冲、写0、写1、读0和读1。所有这些信号,除了应答脉冲以外,都由主机发出同步信号。并且发送所有的命令和数据都是字节的低位在前。单片机对传感器操作的时候首先会给传感器发送一个复位脉冲,传感器会恢复一个应答脉冲,如果得不到这样一个应答脉冲,传感器就认为它坏了或者不存在,每一个脉冲都有规定的格式。
1.复位脉冲
单总线上的所有通信都是以初始化序列开始。主机输出低电平,保持低电平时间至少480 us,一般是480 us-960us之间,以产生复位脉冲。接着主机释放总线,4.7K的上拉电阻将单总线拉高,延时15~60 us,并进入接收模式(Rx)等待传感器回复。接着DS18B20拉低总线60~240 us,以产生低电平应答脉冲。
2.写时序
写时序包括写0时序和写1时序。所有写时序至少需要60us,且在2次独立的写时序之间至少需要1us的恢复时间,两种写时序均起始于主机拉低总线。
a.写0时序:主机输出低电平,延时60us,然后释放总线,延时2us。
b.写1时序:主机输出低电平,延时2us,然后释放总线,延时60us。
3.读时序
单总线器件仅在主机发出读时序时,才向主机传输数据,所以,在主机发出读数据命令后,必须马上产生读时序,以便从机能够传输数据。所有读时序至少需要60us,且在2次独立的读时序之间至少需要1us的恢复时间。每个读时序都由主机发起,至少拉低总线1us。主机在读时序期间必须释放总线,并且在时序起始后的15us之内采样总线状态。
典型的读时序过程为:主机输出低电平延时2us,然后主机转入输入模式延时12us,然后读取单总线当前的电平,然后延时50us。
DS18B20温度读取过程
①复位
②发SKIP ROM命令(0XCC)
③发开始转换命令(0X44)
④复位
⑤发送SKIP ROM命令(0XCC)
⑥发读存储器命令(0XBE)
⑦连续读出两个字节数据(即温度)
⑧结束。
DS18B20温度数据存储
转化后得到的11位数据,存储在18B20的两个8比特的RAM中, MSB的前面5位是符号位,如果测得的温度大于0, 这5位为0,只要将测到的数值乘于0.0625即可得到实际温度;如果温度小于0,这5位为1,测到的数值需要取反再乘于0.0625即可得到实际温度。
例如+125℃的数字输出为07D0H(000000111 11010000),-25.0625℃的数字输出为FE6FH
DS18B20温度采集实验
实验步骤:
1.配置RCC
2.配置PG6管脚为输出模式
3编写代码
//main.c#include "ds18b20.h"int mian(){ int16_t temperature; HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_USART1_UART_Init(); MX_TIM6_Init(); printf("this is DS18B20 testn"); if(!DS18B20_Init()) {printf(" DS18B20 is heren"); }else{printf(" DS18B20 is not heren"); } while(1){ temperature = DS18B20_Get_Temp(); if(temperature< 0){ printf("-");//ÏÔʾ¸ººÅ temperature=-temperature;//תΪÕýÊý} printf("temperature = %d.%dn",temperature/10,temperature%10); HAL_Delay(1000); }}
//ds18b20.h#ifndef __DS18B20_H#define __DS18B20_H#include "stm32f4xx_hal.h"//IO方向设置#define DS18B20_IO_IN() {GPIOG- >MODER&=~(3< <(6*2));GPIOG- >MODER|=0< <(6*2);} //PG6输入模式#define DS18B20_IO_OUT() {GPIOG- >MODER&=~(3< <(6*2));GPIOG- >MODER|=1< <(6*2);} //PG6输出模式//操作函数 #defineDS18B20_OUT_LOW HAL_GPIO_WritePin(GPIOG, GPIO_PIN_6, GPIO_PIN_RESET) //数据端口PG6#defineDS18B20_OUT_HIGH HAL_GPIO_WritePin(GPIOG, GPIO_PIN_6, GPIO_PIN_SET) //数据端口PG6#defineDS18B20_DQ_IN HAL_GPIO_ReadPin(GPIOG, GPIO_PIN_6) //数据端口PG6uint8_t DS18B20_Init(void); //初始化DS18B20short DS18B20_Get_Temp(void); //获取温度void DS18B20_Start(void); //开始温度转换void DS18B20_Write_Byte(uint8_t dat); //写入一个字节uint8_t DS18B20_Read_Byte(void); //读出一个字节uint8_t DS18B20_Read_Bit(void); //读出一个位uint8_t DS18B20_Check(void); //检测是否存在DS18B20void DS18B20_Reset(void); //复位DS18B20 #endif
//ds18b20.c#include "ds18b20.h"uint32_t usctick = 0;uint32_t time_delay = 0;extern TIM_HandleTypeDef htim6;//延时nus//nus为要延时的us数//nus:0~190887435(最大值即2^32/fac_us@fac_us=168)static uint8_t fac_us = 168; //主时钟为168M, 在1us内ticks会减168次void delay_us(uint32_t nus){ uint32_t ticks; uint32_t told,tnow,tcnt=0; uint32_t reload=SysTick- >LOAD;//LOAD的值 ticks=nus*fac_us; //nus需要的节拍数 told=SysTick- >VAL; //刚进入时的计数器的值 while(1) { tnow=SysTick- >VAL; if(tnow!=told) { if(tnow< told) //计数器递减 tcnt+=told-tnow; else tcnt+=reload-tnow+told; told=tnow; if(tcnt >=ticks)break; //时间超过或等于要延时的时间,则退出. } }}//复位DS18B20void DS18B20_Reset(void) { DS18B20_IO_OUT(); //设置为输出模式 DS18B20_OUT_LOW ; //拉低 delay_us(650); //延时650us DS18B20_OUT_HIGH ; //拉高 delay_us(20); //延时20us}//等待DS18B20的回应//返回1:未检测到DS18B20存在//返回0:存在uint8_t DS18B20_Check(void) { uint8_t retry=0; DS18B20_IO_IN(); //设置为输入模式 //等待DS18B20拉低总线回应,如果超过200us未拉低,则认为未回应 //DS18B20_DQ_IN == 1表示管脚仍为高电平 while ((DS18B20_DQ_IN == 1) && (retry< 200)) { retry++; delay_us(1); }; if(retry >=200) return 1; //DS18B20超时未拉低总线 else retry=0; //DS18B20及时拉低总线 while ( (DS18B20_DQ_IN == 0 ) && ( retry < 240) ) //测试拉低总线的时间是否在240us内{ retry++; delay_us(1); }; if(retry >=240) return 1; //超多240us错误 return 0; //正确回应}//从DS18B20读取一个位//返回值:1/0uint8_t DS18B20_Read_Bit(void) {uint8_t data; DS18B20_IO_OUT(); //设置为输出 DS18B20_OUT_LOW ; //输出低电平 delay_us(3); //延时3us DS18B20_OUT_HIGH ; //拉高DQ DS18B20_IO_IN(); //设置为输入 delay_us(12); //延时12us if(DS18B20_DQ_IN) data=1; else data=0; delay_us(50); return data;}//从DS18B20读取一个字节//返回值:读到的数据,先读数据的低位uint8_t DS18B20_Read_Byte(void) { uint8_t i,j,dat;dat=0;for(i=0;i< 8;i++) { j=DS18B20_Read_Bit(); dat=(j<