STC15单片机-ADC获取环境温度(NTC热敏电阻)
ADC獲取環境溫度
STC15L2K32S2 ADC結構
STC15系列單片機ADC由多路選擇開關、比較器、逐次比較寄存器、10位DAC、轉換結果寄存器(ADC_RES和ADC_RESL)以及ADC_CONTR構成。
STC15系列單片機的ADC是逐次比較型ADC。逐次比較型ADC由一個比較器和D/A轉換器構成,通過逐次比較邏輯,從最高位(MSB)開始,順序地對每一輸入電壓與內置D/A轉換器輸出進行比較,經過多次比較,使轉換所得的數字量逐次逼近輸入模擬量對應值。逐次比較型A/D轉換器具有速度高,功耗低等優點。
ADC的使用也是通過配置寄存器來實現的,簡單來說該結構對應的寄存器是ADC_CONTR,每一位對應的功能如下:
ADC_POWER:電源控制位,置1則打開A/D轉換器電源,置0則關閉轉換器電源
SPEED1和SPEED0:模數轉換器轉換速度選擇位,兩位對應4種選項,分別是90個時鐘,180個時鐘,360個時鐘,540個時鐘轉換1次,速度可根據需要選擇,本次實驗選用180個時鐘轉換1次
ADC_FLAG:模數轉換器轉換結束標志位,當A/D轉換完成后,ADC_FLAG = 1,要由軟件清0。不管是A/D轉換完成后由該位申請產生中斷,還是由軟件查詢該標志位A/D轉換是否結束,當A/D轉換完成后,ADC_FLAG = 1,一定要軟件清0
ADC_START:模數轉換器(ADC)轉換啟動控制位,設置為“1”時,開始轉換,轉換結束后為0。
CHS2/CHS1/CHS0:模擬輸入通道選擇,因為該型號單片機的整個P1口都帶有ADC的功能,那使用時總得選1個吧,所以這3位就是用來選擇8個通道中的其中1個的
ADC相關寄存器
P1ASF也是用來選擇通道的,ADC_CONTR上面有介紹,ADC_RES和ADC_RESL是用來存放轉換結果的,CLK_DIV(PCON2)寄存器只用到了第5位ADRJ,
ADRJ置1時:ADC_RES[1:0]存放高2位ADC結果,ADC_RESL[7:0]存放低8位ADC結果;
ADRJ置0時:ADC_RES[7:0]存放高8位ADC結果,ADC_RESL[1:0]存放低2位ADC結果;
IE是控制ADC中斷的,IP是設置ADC優先級的,這次實驗中沒有用到中斷
更詳細介紹只能看手冊了
NTC熱敏電阻
NTC是英文Negative Temperature Coefficient的縮寫。其含義為負溫度系數,它的電阻值隨溫度的升高而降低。利用這一特性既可制成測溫、溫度補償和控溫組件,又可以制成功率型組件,抑制電路的浪涌電流。這是由于NTC熱敏電阻器有一個額定的零功率電阻值,當其串聯在電源回路中時,就可以有效地抑制開機浪涌電流。并且在完成抑制浪涌電流作用以后,利用電流的持續作用,將NTC熱敏電阻器的電阻值下降非常小的程度。
本次實驗使用到的NTC熱敏電阻電氣特性,因為這涉及到了硬件選型,了解一下,主要注意B值,10千歐,3950,精度1%這幾個標簽
開發時特別要注意看熱敏電阻溫度阻值對照表,即多少阻值對應多少的溫度值的表,一般在產品介紹手冊中廠家會給出,如下面這只是一部分,不同的熱敏電阻的測溫范圍也不同,最小值、中心值和最大值的意思是有誤差,在最小和最大范圍內的都可認為是同一溫度
數據計算
NTC電阻值 ——> 輸入到ADC電壓值
溫度特性表阻值對應的溫度已經知道了,但光知道熱敏電阻的阻值是沒法進行ADC轉換的,因為引腳輸入的是電壓,所以要把這些阻值轉換為對應的引腳輸入電壓值,通過阻值計算電壓就要根據實際的硬件電路來算,本次實驗所用到的硬件電路圖如下
輸入到ADC的電壓值 = (RT1/(RT1+10K)) * 3.3
RT1就是溫度特性表中每一個溫度值對應的電阻值,分別計算出最小、中間、最大的電壓值
輸入到ADC的電壓值 ——> ADC轉換結果(采集值)
然后就是經過ADC轉換后的采集值,這個采集值是單片機計算出來的,放到了ADC_RES[1:0]和ADC_RES[7:0]中,數據手冊中有給出計算公式,Vin就是上一步計算出的輸入到ADC的電壓值,VCC為單片機工作電壓3.3V,因為該ADC是10位的,所以乘以1024
全部計算在一張execl表中
可將pdf的溫度特性表轉為execl表,然后用公式算出電壓值,然后再根據電壓值算出ADC的轉換結果,也就是采集值,因為這些采集值是浮點型的,而單片機處理浮點型的數據是非常耗時間的,所以經過四舍五入后,得到整型的采集值;
后續在程序中,就要將整形的采集值做成一個二位數組,判斷ADC的轉換結果在哪一個范圍內,對應的溫度值就清楚了
程序
文件結構
main.c ->主函數文件,包含main函數等;
Public.c ->公共函數文件,包含Delay 延時函數等;
Sys_init.c ->系統初始化函數,包含GPIO初始化函數等;
ADC.c->ADC初始化,采集 ADC值等;
NTC.c ->NTC外設函數,包含查表,獲取環境溫度等;
實驗結果
間隔1000ms 通過ADC獲取NTC傳感器的電壓
通過查表,獲取環境溫度,并打印到串口上
ADC.h
主要是ADC轉換速度宏定義,標志位,以及結構體類型
#ifndef __ADC_H_ #define __ADC_H_//宏定義轉換速度 #define ADC_SPEED_90 0x60 #define ADC_SPEED_180 0x40 #define ADC_SPEED_360 0x20 #define ADC_SPEED_540 0x00#define ADC_POWER 0x80 //ADC電源控制位 #define ADC_FLAG 0X10 //ADC完成標志位,需要軟件清零 #define ADC_START 0X08 //ADC啟動標志位,置'1'開始轉換 //定義結構體類型 typedef struct {uint16_t ADC_Value; //ADC采集值void (*ADC_Init)(); //ADC初始化uint16_t (*ADC_Result)(); //ADC轉換結果uint16_t (*ADC_Filter_Result)(); //ADC轉換濾波后的結果 }ADC_t;/* extern variables-----------------------------------------------------------*/ extern ADC_t idata ADC; /* extern function prototypes-------------------------------------------------*/ #endif /********************************************************End Of File ********************************************************/ADC.c
實現ADC初始化函數,實現轉換并獲取轉換結果函數,為了確保獲取數據的穩定性,實現一個多次讀值取平均值的濾波算法
/* Includes ------------------------------------------------------------------*/ #include <main.h>/* Private define-------------------------------------------------------------*/ #define NTC_CHAN 0x04 //NTC傳感器輸入通道(CHS2、CHS1、CHS0) /* Private variables----------------------------------------------------------*/ static void ADC_Init(); static uint16_t ADC_Result(); static uint16_t ADC_Filter_Result(); /* Public variables-----------------------------------------------------------*/ ADC_t idata ADC = {0,ADC_Init,ADC_Result,ADC_Filter_Result }; /* Private function prototypes------------------------------------------------*//* * @name ADC_Init * @brief ADC初始化 * @param None * @retval None */ static void ADC_Init() {//端口1模擬功能配置寄存器P1ASF = BIT4; //將P1.4口設置為模擬功能A/D使用//A/D轉換控制寄存器 1100 0100ADC_CONTR = ADC_POWER|ADC_SPEED_180|NTC_CHAN;Public.Delay_ms(2); //等待ADC電源穩定//ADRJ置‘1’,ADC_RES[1:0]存放高2位ADC結果,ADC_RESL[7:0]存放低8位ADC結果CLK_DIV |= 0x20; }/* * @name ADC_Result * @brief ADC轉換結果 * @param None * @retval 返回ADC轉換結果 */ static uint16_t ADC_Result() {uint16_t ADC_Result = 0;//清零轉換結果ADC_RES = 0;ADC_RESL = 0;//啟動ADCADC_CONTR |= ADC_START;_nop_(); //手冊建議添加四個延時,等待ADC轉換完成_nop_();_nop_();_nop_();while(!(ADC_CONTR & ADC_FLAG)); //查詢方式等待ADC轉換結果//清除ADC轉換完成標志位ADC_CONTR &= (~ADC_FLAG);//獲取轉換結果 :ADC_RES高2位+ADC_RESL低8位ADC_Result = (ADC_RES & 0x03); //取出ADC_RES[1:0]存放的高2位數據ADC_Result <<= 8; //左移8位,將取出的2位高位數據放到第9,第10位ADC_Result += ADC_RESL; //加上低8位的數據,組合成一個16位的數據return ADC_Result; }/* * @name ADC_Filter_Result * @brief ADC轉換濾波后的結果(濾波函數,以后也可以借鑒使用) * @param None * @retval 返回ADC轉換結果 */ static uint16_t ADC_Filter_Result() {//經過濾波算法后返回的結果就會很準確,遵循產品的穩定性原則/*循環讀取AD值16次后求平均值并返回結果,而這16次里每一次又是8次讀取后取的平均值*/uint16_t ADC_min,ADC_max,ADC_temp,ADC_result,ADC_return;uint8_t i,j;ADC_return = 0;for(i=0;i<16;i++){ADC_result = 0;ADC_min = ADC_max = ADC.ADC_Result();for(j=0;j<8;j++){ADC_temp = ADC.ADC_Result();if(ADC_temp < ADC_min){ADC_result += ADC_min;ADC_min = ADC_temp;}else if(ADC_temp > ADC_max){ADC_result += ADC_max;ADC_max = ADC_temp;}else{ADC_result += ADC_temp;}}ADC_result /= 8;ADC_return += ADC_result;}ADC_return /= 16;Run_LED.Run_LED_Flip();return ADC_return; //返回經過濾波后的采集值 } /********************************************************End Of File ********************************************************/NTC.h
#ifndef __NTC_H_ #define __NTC_H_//定義結構體類型 typedef struct {void (*Get_NTC_Voltage)(); //獲取NTC電壓值float fNTC_Voltage; //NTC電壓void (*Get_Temperature_Value)(); //獲取溫度值float fTemperature; //溫度 }NTC_t;/* extern variables-----------------------------------------------------------*/ extern NTC_t idata NTC; /* extern function prototypes-------------------------------------------------*/ #endif /********************************************************End Of File ********************************************************/NTC.c
主要是把上面execl表中的整型采集值做成一個二維數組,這需要點時間,而這里的數據又將之前的數據細分了,分出了0.5°來,二維數組里的數據只要大概在表中某一溫度值對應的采集值范圍內即可,不要超過最大采集值和最小采集值
示例代碼到二位數組刪減了,放不了這么多
而程序是直接得出轉換后的采集值的,那如果得到NTC熱敏電阻的電壓呢,就要通過采集值倒推出電壓值了,計算公式:電壓值=(3.3 * ADC采集值)/1024;分別獲取到ADC采集值和電壓值后,就可打印到串口上顯示
Get_Temperature_Value函數就是通過二分法,在二維數組中找采集值處于二維數組中的哪個下標位置,因為二位數組下標為0的對應溫度是-30℃,下標為1是-29.5℃,依次類推,所以獲取到下標值后,可以通過找數組下標與溫度值的規律,得出計算公式,最后打印溫度值
/* Includes ------------------------------------------------------------------*/ #include <main.h>/* Private define-------------------------------------------------------------*/ #define SW_NTC P13 //MOS開關驅動引腳 #define SW_NTC_ON (bit)1 //打開MOS開關 #define SW_NTC_OFF (bit)0 //關閉MOS開關/* Private variables----------------------------------------------------------*/ /*NTC型號:B3950 10K 1%溫度表,對應溫度-30℃ ~ 70℃Note:如果采集值 > 966,則溫度低于-30℃;采集值 < 145,則溫度高于70℃*/ const uint16_t xdata NTC_Table[201][2] = { //溫度 -30 -29.5 -29 -28.5 -28 -27.5 -27 -26.5 -26 -25.5 {964,966},{962,963},{960,961},{958,959},{957,957},{955,956},{953,954},{951,952},{949,950},{947,948},{945,946},{943,944},.......{169,170},{166,168},{164,165},{162,163},{160,161},{158,159},{155,157},{152,154},{147,151} };static void Get_NTC_Voltage(); static void Get_Temperature_Value(); /* Public variables-----------------------------------------------------------*/ NTC_t idata NTC = {Get_NTC_Voltage,0.0,Get_Temperature_Value,0.0 }; /* Private function prototypes------------------------------------------------*//* * @name Get_NTC_Voltage * @brief 獲取NTC電壓值 * @param None * @retval None */ static void Get_NTC_Voltage() {//打開MOS開關SW_NTC = SW_NTC_ON;//等待電壓穩定Public.Delay_ms(1);//獲取ADC采集值ADC.ADC_Value = ADC.ADC_Filter_Result();//條件編譯#ifdef Monitor_Run_Codeprintf("The ADC Value is: %d\r\n",ADC.ADC_Value);#endif//通過獲取到的ADC采集值,倒推出NTC的電壓值//VCC:3.3伏//10位ADC結果:ADC.ADC_Value//求VinNTC.fNTC_Voltage = (3.3 * ADC.ADC_Value)/1024;#ifdef Monitor_Run_Codeprintf("The NTC Voltage is: %.2fV\r\n",NTC.fNTC_Voltage);#endif//關閉MOS開關,NTC電源斷開,停止工作SW_NTC = SW_NTC_OFF; }/* * @name Get_Temperature_Value * @brief 獲取溫度值 * @param None * @retval None */ static void Get_Temperature_Value() {static uint8_t Temp;//獲取采集值,計算溫度等NTC.Get_NTC_Voltage();//臨界溫度處理if(ADC.ADC_Value < 147) //溫度最高70℃{ADC.ADC_Value = 147;#ifdef Monitor_Run_Codeprintf("Temperature is higher than 70℃\r\n");#endif}if(ADC.ADC_Value > 966) //溫度最低-30℃{ADC.ADC_Value = 966;#ifdef Monitor_Run_Codeprintf("Temperature is below than -30℃\r\n");#endif }//二分法查表if(ADC.ADC_Value > NTC_Table[101][1]){//查詢數組下標0 - 100,對應-30℃至 20℃for(Temp=0;Temp<=100;Temp++){//如果采集值在這些一維數組的范圍內,則表示找到對應溫度值,用break跳出循環if((ADC.ADC_Value >= NTC_Table[Temp][0]) && (ADC.ADC_Value <= NTC_Table[Temp][1])){break;}}}else{//查詢數組下標101 - 201,對應20℃至70℃for(Temp=101;Temp<=200;Temp++){if((ADC.ADC_Value >= NTC_Table[Temp][0]) && (ADC.ADC_Value <= NTC_Table[Temp][1])){break;}}}//計算溫度//下標(Temp) 溫度// 0 -30// 1 -29.5// 2 -29// 3 -28.5// 4 -28//通過比較數組和下標的關系,找出計算溫度值的規律NTC.fTemperature = (Temp/2.0)-30;#ifdef Monitor_Run_Codeprintf("Temperature is: %.1f ℃\r\n\r\n",NTC.fTemperature);#endif } /********************************************************End Of File ********************************************************/main.c
程序中獲取到數據的過程:最底層的ADC_Result()獲取到采集值,ADC_Filter_Result()濾波算法得到更精確的采集值,Get_NTC_Voltage()通過采集值倒算出電壓值,Get_Temperature_Value()在數組中找到采集值對應的溫度,最后將溫度通過串口打印
函數調用關系:main() -> Get_Temperature_Value() -> Get_NTC_Voltage() -> ADC_Filter_Result() -> ADC_Result()
/* * @name main * @brief 主函數 * @param void * @retval int */ int main(void) {//系統初始化Hradware.Sys_Init();//串口1發送初始化信息#ifdef Monitor_Run_Codeprintf("Initialization completed,system startup!\r\n\r\n");#endif//系統主循環while(1){NTC.Get_Temperature_Value();Public.Delay_ms(1000);} }看教程時需要注意的點
NTC部分的CMFB103F3950FANT是熱敏電阻,名稱是NTC熱敏電阻,25度情況下,阻值是10千歐
R6是一個分壓電阻,阻值與RT1相同,為了讓兩者中間的采集電壓不能太小,R6精度是1%
C7電容,起濾波作用,穩定電壓,C8電容也是起穩定電壓作用
因為要求低功耗,所以要有一個MOS開關U4,控制電源,關閉時后MCU_3V3就不會有電流流向RT1,熱敏電阻不工作
文件結構中將ADC和NTC分開編程,因為ADC是芯片內部的資源,NTC是外部的,下次使用的可能不是NTC,可能是別的傳感器,所以分開處理比較好,易于移植
需要根據手冊制作execl表,選擇-30度到70度,電壓值計算公式:RT1/(RT1+10K)*3.3V,采集值計算公式:1024 *(Vin/Vcc),Vin就是前面計算的電壓值,Vcc是3.3V,然后再將浮點型的采集值四舍五入轉化為整型值,最后在程序中判斷在這些整型值的哪個范圍,就可以推出溫度是多少
示例代碼中將整型值寫成一個二維數組,共201個,每個一維數組表示的溫度范圍是0.5度
上電串口輸出初始化信息添加宏定義,調試時打開,出產品時注釋掉
與硬件掛鉤的定義,比如引腳定義,盡量用宏定義,方便移植更改
P13口設置為推挽輸出,P14設置為高阻輸入,Sys_Init()方法中記得加ADC初始化
可能出現的故障:
如果采集的電壓一直是3.3V,那可能的故障是,MOS開關沒有打開,或者虛焊,或者NTC傳感器沒接,導致一直是MCU_3.3V流進P14口
如果采集的一直是0V,那R6電阻可能虛焊
如果采集的電壓不穩定,那R7電阻可能虛焊或沒接,導致線路懸空不穩定
需要得到的數據:采集值((電壓值*1024)/3.3)-> ADC_RES,ADC_RESL,電壓值((電阻值/(電阻值+10)) * 3.3),溫度
遇到的問題
問題1
編寫好ADC和NTC的程序后,主函數調用NTC.Get_Temperature_Value()函數,里面有用 printf 函數重定向串口打印采集值、熱敏電阻電壓和溫度,但打開串口卻看不到任何信息
解決方法:
1.要在串口初始化函數Init()中,將串口切換寄存器AUXR1的S1_S1(BIT7)和S1_S0(BIT6)位都清零,才能將串口1切換到P3.0和P3.1口,因為串口的CH340芯片接的就是單片機P3.0和P3.1引腳
2.要在系統初始化文件Sys_Init.c的GPIO初始化函數中,將之前做RS-485通信時,P3.0和P3.1口初始化為0的操作刪除
P3 &= (~(BIT0|BIT1|BIT4|BIT5)); //RS-485項目P3口初始化原來寫法 P3 &= (~(BIT4|BIT5)); //修改為不對BIT0和BIT1初始化為0問題2
解決第一個問題后,串口能輸出ADC采集值、NTC電壓和溫度,在串口助手查看到單位℃出現亂碼情況,而且輸出一輪換行時本來兩次 \r\n 就可實現空出一行,現在卻要三次 \r\n 才能實現,如下圖所示
printf("Temperature is: %.1f ℃\r\n\r\n\r\n",NTC.fTemperature); //三次\r\n才能實現空一行的效果,本來兩次就能實現℃單位亂碼和兩次 \r\n 無法空一行的情況:
解決思路及過程
一開始先看波特率是否對應,發現是正確的,再一想既然其他打印信息都能正確輸出,那波特率是沒有問題的;然后去查看代碼是否有寫錯,串口通信的全部函數也檢查了一遍,發現沒有問題,并且也拿之前寫過的串口通信程序對照著檢查了一遍,程序一摸一樣;但我拿教程的代碼燒錄后,發現串口通信是正常的,℃能正常打印,使用的是兩次 \r\n ,而自己寫的串口這一部分代碼是與教程一樣的,為什么代碼一樣,串口輸出會有亂碼現象呢?頭一次遇到這種問題
然后想著去查看STC-ISP串口助手那一塊是否有可以設置的地方,猜測自己的串口助手與教程的某項設置不一樣,導致亂碼,后來完全沒發現STC-ISP燒錄工具有可以設置串口相關參數的地方,除了波特率,校驗位和停止位,其他沒得設置,波特率對應了,亂碼的問題與校驗位和停止位關系應該也不大,如果校驗位和停止位有錯,應該整個通信都有問題,而不只是這一個℃單位
后來猜測到,既然燒錄教程的代碼沒問題,燒錄自己的代碼有問題,而內容卻一樣,那我把教程代碼串口打印輸出這一塊的全部內容復制到自己的代碼中,是否還有亂碼現象呢?若沒有亂碼,說明就是自己的代碼還有問題;若還有亂碼,那可想到這部分代碼在教程的文件中沒有問題,而把內容復制到我自己的文件中后卻出現了問題,那這個文件是不是有點問題?
為了進一步驗證是不是文件的鍋,因為打印的信息都在NTC.c源文件里,就先把教程的NTC.c里的內容全部覆蓋到自己本來的NTC.c中,然后編譯燒錄,發現仍然出現亂碼,這就說明問題了,這些代碼在教程的文件中沒出問題,在我自己創建的文件中就出現亂碼(因為是亂碼現象,所以確定邏輯功能都是正確的);后續我把教程一整個NTC.c文件拷貝到自己的工程中,編譯燒錄,沒有出現亂碼現象,所以說明文件大概率是有問題的
既然文件可能有問題,那是不是剛開始創建時就不對了呢,然后突然意識到,我從面向對象的代碼開始,用的編譯器是VS Code,而新建文件也是用VC code創建的,不是用keil來創建文件,會不會與這個有關系呢?然后分別做了測試,把這次實驗整個拷貝兩份,一份備注為VS code ,另一份備注為 keil,然后分別把NTC.c文件刪除,一份用VS code打開并新建NTC.c文件,粘貼自己寫的代碼,另一份用keil打開并新建NTC.c文件,也是粘貼自己的代碼,然后都用keil來編譯,最后燒錄看效果
串口打印的關鍵代碼就這樣寫,有℃單位,兩次 \r\n
printf("Temperature is :%.1f℃\r\n\r\n",NTC.fTemperature);VS code創建的NTC.c文件,燒錄后串口輸出情況:
Keil創建的NTC.c文件,燒錄后串口輸出情況:
可看到,用VS code新建NTC.c文件的是會出現亂碼現象,而用keil新建的就不會,這兩個工程代碼完全一樣,就是新建文件時用的軟件不同,居然會出現這種結果
這下問題就解決了,原來是VS code編譯器新建文件時出的問題,這種情況還真第一次遇到,用不同軟件新建的文件都會導致最后運行結果出錯;前面實驗新建文件也是用的VS code軟件,可能并沒有用到串口或沒太注意,直到這次串口輸出信息亂碼時才發現問題所在,至于為什么VS code新建文件會有問題,網上也沒查到,以后還是用keil來新建文件保險一點
在后續串口通信實驗中發現了問題的原因
原來VS code編寫代碼時新建的文件編碼格式與keil軟件的不同,導致串口發送的數據出現亂碼,在用VS code編寫代碼前統一文件編碼格式即可
總結
以上是生活随笔為你收集整理的STC15单片机-ADC获取环境温度(NTC热敏电阻)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: LIN总线知识基础
- 下一篇: DS18B20温度换算