最近畢業設計中接了別人幾個項目,忙完一陣是時候做一些總結。
項目名:基於單片機的自行車碼表設計
功能需求: 1可以測量當前的溫度和濕度並顯示出來。
2時間顯示。
3實時速度和行馳里程顯示。
4設定里程提醒。
根據功能需求決定硬件:單片機使用STC125A60S2具有足夠的資源來進行開發(使用51系列的單片也可行,並且價格更低),顯示使用LCD1602,速度使用磁力傳感器,溫度和濕度使用DHT11傳感器。時間計數應該使用外部的時鍾芯片(由於沒有芯片只能使用單片內部的定時器實現^^誤差較大)。提醒聲音使用了無源蜂鳴器(PWM波控制).
整體硬件設計圖
IO腳定義
#ifndef _CURRENCY_H_ #define _CURRENCY_H_ #include "stc12c5a60s2.h " //currency.h typedef unsigned char unchar; typedef unsigned int unint; sbit Data=P3^5; sbit rs=P2^7; sbit rw=P2^6; sbit e =P2^5; sbit buzzer = P3^4;//蜂鳴器 #endif
主函數部分
/******************************************************************************* * 描述: * * 1602字符型LCD顯示演示程序 * * 在第一行顯示 里程 時間 * * 在第二行顯示 速度 溫度 * * * ********************************************************************************/ #include <stc12c5a60s2.h> #include "lcd1602.h" #include "DHT11.h" #include "delay.h" #include "currency.h" unchar test[10]; unsigned long time_ms; unsigned long last_time; unsigned long distance_cm; //厘米 unsigned int speed; //百米/時 unsigned int tempr; //0.1度 unsigned int n ; unsigned char tm ; unsigned int distance; bit menu=0; bit clear_flag; unsigned long time_menu; void Timer0Init(void) //10毫秒@12.000MHz { AUXR &= 0x7F; //定時器時鍾12T模式 TMOD &= 0xF0; //設置定時器模式 TMOD |= 0x01; //設置定時器模式 TL0 = 0xF0; //設置定時初值 TH0 = 0xD8; //設置定時初值 TF0 = 0; //清除TF0標志 TR0 = 1; ET0 = 1; //定時器0開始計時 EA = 1; } //-------------------------------------------------------- //算法實現 //-------------------------------------------------------- void show() { unsigned char c; unsigned char h,m,s; unsigned int time; lcd_pos(0); distance = distance_cm / 10000; c = (distance / 1000) % 10 + '0'; //百位 write_data(c); //寫入數據 c = (distance / 100) % 10 + '0'; //十位 write_data(c); c = (distance / 10) % 10 + '0'; //個位 write_data(c); write_data('.'); c = distance % 10 + '0'; //小數一位 write_data(c); //進行位提取 write_data('k'); write_data ('m'); time = time_ms/1000; h = time / 3600; m = (time % 3600) / 60; s = time % 60; lcd_pos(9); c = h % 10 + '0'; write_data(c); write_data(':'); c = (m / 10) + '0'; write_data(c); c = (m % 10) + '0'; write_data(c); write_data(':'); c = (s / 10) + '0'; write_data(c); c = (s % 10) + '0'; write_data(c); //時間提取 lcd_pos(0x40); //定位第二行 c = (speed / 100) % 10 + '0'; //取百位 write_data(c); c = (speed / 10) % 10 + '0'; //取十位 write_data(c); write_data('.'); c = speed % 10 + '0'; //取個位 write_data(c); write_data('k'); write_data('m'); write_data('/'); write_data('h'); //實時提取 } //開始 void main() { int i; init_1602(); //lcd1602初始化 delay_ms(1500); //DHT11上電后要等待1.5S以越過不穩定狀態在此期間不能發送任何指令 Timer0Init(); IT0=1; //外部中斷0下降沿觸發 EX0=1; IT1=1; //外部中斷1下降沿觸發 EX1=1; //外部中斷開啟動 distance_cm = 0; time_ms = 0; last_time = 0; tempr = 324; while(1) { if((time_ms - last_time) > 5000) speed = 0; P1 = 0xff; if((P1 & 0x02) == 0) { distance_cm = 0; time_ms = 0; } if(menu == 0) //菜單0顯示 { if(clear_flag==1) { write_com(0x01); clear_flag = 0; } show(); if(distance ==15) //單位是百米 { Buzzer_Alert(); } } else if(menu == 1) { if(clear_flag==0) { wrire_com(0x01); clear_flag = 1; } ET0 = 0; DHT11_receive(); } } } void T0_Interrupt(void) interrupt 1 //3定時器1的中斷號 1定時器0的中斷號 0外部中斷1 2外部中斷2 4串口中斷 { TL0 = 0xF0; //設置定時初值 TH0 = 0xD8; //設置定時初? time_ms += 10; // if(menu) // { // time_menu+= 10; //test: // if(time_menu==10000) // { // menu=0; // time_menu=0; // } // } } //比較重要------------------------------------ void counter(void) interrupt 0 { unsigned int intervel = 0; // static unsigned char cnt = 0; EX0=0; distance_cm+=218; //一圈218厘米 cnt++; if(last_time == 0) { last_time = time_ms; cnt = 0; } else if(cnt >= 5) { intervel = time_ms - last_time; last_time = time_ms; speed = 360 * 5 * 218 / intervel; //實時速度統計 if(speed > 350) else cnt = 0; } EX0=1; } void key_menu(void) interrupt 2 { menu=~menu; ET0 = 1; }
LCD1602模塊函數
#include "lcd1602.h" #include "currency.h" #include "delay.h" #include "intrins.h" /***********************lcd1602寫命令函數************************/ void write_com(unchar com) { e=0; rs=0; rw=0; P0=com; delay_unint(3); e=1; delay_unint(25); e=0; } /***********************lcd1602寫數據函數************************/ void write_data(unchar dat) { e=0; rs=1; rw=0; P0=dat; delay_unint(36); e=1; delay_unint(300); e=0; } /***********************lcd1602寫數據函數************************/ void write_data_1(unchar dat) { e=0; rs=1; rw=0; P0=dat+48; delay_unint(432); e=1; delay_unint(3600); e=0; } /*********************光標控制***********************/ void lcd1602_guanbiao(unchar open_off,unchar add) { if(open_off == 1) //開光標 { write_com(0x80+add); //將光標移動到秒個位 write_com(0x0f); //顯示光標並且閃爍 } else { write_com(0x0c); //關光標 } } /***********************lcd1602上顯示兩位十進制數************************/ void write_sfm2(unchar hang,unchar add,unchar date) { unchar shi,ge; if(hang==1) write_com(0x80+add); else write_com(0x80+0x40+add); shi=date%100/10; ge=date%10; write_data(0x30+shi); write_data(0x30+ge); } /***********************lcd1602上顯示這字符函數************************/ void write_string(unchar hang,unchar add,unchar *p) { if(hang==1) write_com(0x80+add); else write_com(0x80+0x40+add); while(1) { if(*p == '\0') break; write_data(*p); p++; delay_unint(600); } } /***********************lcd1602上顯示這字符函數************************/ void write_string_1(unchar hang,unchar add,unchar *p) { if(hang==1) write_com(0x80+add); else write_com(0x80+0x40+add); while(1) { if(*p == '\0') break; write_data_1(*p); p++; delay_unint(600); } } /***********************lcd1602初始化設置************************/ void init_1602() { write_com(0x38); // write_com(0x0c); write_com(0x06); delay_unint(12000); //write_string(1,0," Welcome to use "); //write_string(2,0," Bicycle speed "); //lcd1602_guanbiao(1,7+0x40); //開光標 } unchar Lcd1602_ReadBusy() //判斷lcd1602是否處於忙的狀態,即讀忙 { unchar temp; rs=0; rw=1; _nop_(); P0=0xff; //讀某IO口數據前,先將該口置為1 /*原因:電路中存在的一個普遍的現象:高電平很容易被低電平拉低,而低電平一般不可能被高電平拉高。所以在讀數據之前將單片機IO口拉高才不會影響原來數據線上的數據!*/ _nop_(); e=1; _nop_(); temp=P0; //讀取此時lcd1602的狀態字 _nop_(); e=0; return (temp&0x80); //如果忙 /*狀態字為temp(8位2進制數)的最高位,最高位為1表示禁止讀寫,為0表示允許讀寫,即temp&0x80得1表示忙,得0表示不忙*/ } void Lcd1602_WriteData(unchar dat) //寫數據 { rs=1; //數據 rw=0; //寫 _nop_(); P2=dat; _nop_(); e=1; _nop_(); _nop_(); e=0; _nop_(); _nop_(); } void lcd_pos(unchar pos) { write_com(pos | 0x80); }
由於DHT11需要比較准確的延時誤差在5%
delay.c延時模塊
#include "delay.h" #include "intrins.h" #include "DHT11.h" //-------------------------------------------------------- //延時1ms 實際0.99ms //-------------------------------------------------------- void delay_ms(unsigned int x) { while(x--) { unsigned char i, j; _nop_(); _nop_(); i = 9; j = 120; do { while (--j); } while (--i); } } void delay_unint(unsigned int q) { while(q--); } //-------------------------------------------------------- //延時60s 實際63us //-------------------------------------------------------- void Delay60us() //@12.000MHz { unsigned char i, j; _nop_(); _nop_(); i = 1; j = 180; do { while (--j); } while (--i); } //-------------------------------------------------------- //延時80s 實際87us //-------------------------------------------------------- void Delay80us() //@12.000MHz { unsigned char i, j; _nop_(); _nop_(); i = 1; j = 255; do { while (--j); } while (--i); } //-------------------------------------------------------- //延時30s 實際31us //-------------------------------------------------------- void Delay30us() //@12.000MHz { unsigned char i; i = 87; while (--i); } void Delay50us() //@12.000MHz { unsigned char i, j; _nop_(); _nop_(); i = 1; j = 145; do { while (--j); } while (--i); }
DHT11.C 溫度采集模塊
#include "DHT11.h" #include "currency.h" #include "stc12c5a60s2.h" #include "delay.h" #include "lcd1602.h" #include <intrins.h> //------------------------------------ //function:rec_dat數據組清零 //------------------------------------ unchar rec_dat[9]; //------------------------------------ //function:DHT11啟動 //------------------------------------ void DHT11_start() { Data=1; Delay30us(); Data=0; delay_ms(25); //延時18ms以上 Data=1; Delay30us(); } //------------------------------------ //function:DHT11接收一個字節 //------------------------------------ unchar DHT11_rec_byte() //接收一個字節 { unchar i,dat=0; for(i=0;i<8;i++) //從高到低依次接收8位數據 { while(!Data); ////等待50us低電平過去 Delay60us(); //延時60us,如果還為高則數據為1,否則為0 dat<<=1; //移位使正確接收8位數據,數據為0時直接移位 if(Data==1) //數據為1時,使dat加1來接收數據1 dat+=1; while(Data); //等待數據線拉低 } return dat; } //------------------------------------ //function: 接收DHT11的40位的數據 //------------------------------------ void DHT11_receive() //接收40位的數據 { unchar i, R_H,R_L,T_H,T_L,RH,RL,TH,TL,revise; DHT11_start(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); if(Data==0) { while(Data==0); //等待拉高 Delay80us(); //拉高后延時80us R_H=DHT11_rec_byte(); //接收濕度高八位; R_L=DHT11_rec_byte(); //接收濕度低八位 T_H=DHT11_rec_byte(); //接收溫度高八位 T_L=DHT11_rec_byte(); //接收溫度低八位 revise=DHT11_rec_byte(); //接收校正位 Delay30us(); //結束 if((R_H+R_L+T_H+T_L)==revise) //校正 { RH=R_H; RL=R_L; TH=T_H; TL=T_L; } /*數據處理,方便顯示*/ rec_dat[0]='0'+(RH/10); rec_dat[1]='0'+(RH%10); rec_dat[2]='R'; rec_dat[3]='H'; rec_dat[4]=' '; rec_dat[5]=' '; rec_dat[6]='0'+(TH/10); rec_dat[7]='0'+(TH%10); rec_dat[8]='C'; lcd_pos(40); //定位第二行的第一個 for(i=0;i<9;i++) { write_data(rec_dat[i]); } } }
buzzer,c蜂鳴器模塊
#include "buzzer.h" #include "currency.h" #include "delay.h" void Buzzer_Alert() //PWM 500hz { long int i=2000; while(i--) { buzzer=~buzzer; delay_ms(1); } }
代碼難度不大,具體注釋想必有點單片機的基礎都可以看懂^^
DHT11的硬件
注:這個上拉電阻主要是普通的單片機的上拉能力不強,當數據進行長距離傳輸時容易有較大的寄生電容造成RC放電,所以要加上 上拉電阻。短距離不用加也可以。
DHT11的操作比較簡單,就是時序操作,沒有什么要注意的。
總結:
按鍵最好要消抖,一般有兩種做法,軟件消抖和硬件消抖兩種方法,在單片機資源足夠充裕並且系統對實時性要求不高時,建議使用軟件消抖,延時20ms后進行判斷。不然利用硬件消抖,一般在按鍵端接入4.7K電阻加0.1uF的電容,在有時間后加上GPS定位功能。