基于单片机的自行车码表
最近毕业设计中接了别人几个项目,忙完一阵是时候做一些总结。
项目名:基于单片机的自行车码表设计
功能需求: 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 "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的电容。