在實際情況中很多傳感器并不會用到很復雜的通信協議,反而簡單的數據傳輸機制能夠大大節省成本且滿足實際需要。紅外傳感器和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設為中斷模式,下降沿和上升沿均可觸發中斷。第一次檢測到下降沿后觸發中斷并檢查是否是下降沿且是否達到要求的時長,如果滿足等待第二次上升沿觸發,檢查是否是上升沿且是否達到要求的時長。之后的每一個波形都會觸發兩次中斷檢測時間,用來確定是邏輯1還是邏輯0,連續檢測64次
實驗步驟:
1.配置RCC
2.配置PG8為外部中斷模式
3.配置中斷
4.編寫代碼
//main.c
int 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 0xEE
extern __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中斷服務程序
*參數: 無
*返回: 無
*************************************************************************/
// 檢測脈沖寬度最長為5ms
const 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("-");//??ê??oo?
temperature=-temperature;//×a?a?yêy
}
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) //數據端口PG6
uint8_t DS18B20_Init(void); //初始化DS18B20
short 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); //檢測是否存在DS18B20
void 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;
//時間超過或等于要延時的時間,則退出.
}
}
}
//復位DS18B20
void 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/0
uint8_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<
評論