概要
链接:https://pan.baidu.com/s/1kyaGEdhLMkj_CHjE-wPmDw
提取码:yyw9
总体结构如下图所示:
达到的任务需求:
不同的计价收费标准
白天 1元/公里 晚上 2元/ 公里 途中等待(30s)1元/30s
等待显示P-
功能切换按键(3个)
启动计价开关 数据复位 白天/晚上/等待模式转换
数码管显示
显示单价,等待模式,公里数,总价格
单价可自定义
整体架构流程
整体结构简洁,使用51单片机的定时器0中断完成速度的模拟,及各价的统计。
硬件整体由三个部分组成:单片机最小系统部分,数码管显示及驱动部分,按键扫描部分。
1、单片机最小系统部分。由于单片机的P0口是开漏输出,数码管驱动部分使用到了,所以应加上上拉电阻。晶振提供系统运行的12mhz的时钟信号。复位电路采用高电平复位,该系列51单片机RST引脚高电平超过4个机器周期则自动复位。
2、数码管显示及驱动部分。采用动态扫描原理,使用74hc138译码器进行位选功能,使用74hc245锁存器提高io口的驱动能力。动态扫描是人眼视觉感知的一种现象,它利用人眼的暂留视觉效应来实现多位数码管等显示设备的连续显示。动态扫描原理:在数码管动态扫描中,每个数码管仅在一个瞬间被激活,并持续一段非常短的时间,随后迅速切换到下一个数码管。这个切换的速度远远快于人眼的感知,所以我们感觉到所有数码管都在同时显示。对于数码管动态扫描,一般的频率范围在几十赫兹到几千赫兹之间。
3、为了简化电路,按键按下直接连接到低电平,使用查询方式检测按键是否按下和使用软件进行按键的消抖。按键消抖是指在按下或释放按钮时,电路会因为机械部件的震动或弹跳而导致短暂的多次触发。这种现象可能会对电路造成误触发或干扰。本设计采用软件滤波方式消抖,在软件层面对按键信号进行滤波处理,通过设置延时时间或使用状态机等方式来确保只接收有效的稳定信号,忽略短暂的抖动信号。
软件整体包含主程序和中断程序两个部分:
主程序原理:单片机开始运行程序之后开启定时器,开启定时器中断。并进行相关的初始化和寄存器的设置工作。然后进入一个循环执行函数。该循环过程主要进行四个工作:
1、检测按键是否按下,如果按键1按下,则让开始/暂停标志位在0-1之间顺序切换。如果按键2按下,则让模式标志位在1-3之间顺序切换。
2、单价的显示。判断当前模式为1-3的任意一种。若模式标志位为1则显示单价1.0,若模式标志位为2则显示单价2.0,若模式标志位为3则显示p-表示为等待模式。
3、路程价格的计算。计算白天行驶的路程和晚上的路程总和,计算三种模式下记录的价格总价。
4、路程和价格显示。通过除和取余计算处每一位该显示的数字,并设置数码管显示数组的值,之后借助中断进行数码管的扫描显示。
中断函数原理:
1、进行按键扫描函数及消抖函数的执行。
2、进行数码管的扫描函数。
3、判断开始标志位。如果为1,则进行下一步的判断。为0,则退出中断。
4、判断模式标志。如果模式标志位为1,则白天相应的相对值加1.则夜晚相应的相对值加1.则等待相应的相对值加1.这些相对值用于主函数循环里的距离和价格计算。
系统基本流程图:
注:详细代码可查看技术细节或链接文件!
技术名词解释
通过使用定时器中断,可以实现定时操作,例如定时延时、周期性任务执行、计时等。定时器中断能够提高系统的实时性和精确性,方便进行时间敏感的操作和监控任务。
- 动态扫描:
- 动态扫描是人眼视觉感知的一种现象,它利用人眼的暂留视觉效应来实现多位数码管等显示设备的连续显示。动态扫描原理:在数码管动态扫描中,每个数码管仅在一个瞬间被激活,并持续一段非常短的时间,随后迅速切换到下一个数码管。这个切换的速度远远快于人眼的感知,所以我们感觉到所有数码管都在同时显示。对于数码管动态扫描,一般的频率范围在几十赫兹到几千赫兹之间。
-
按键消抖:
-
按键消抖是指在按下或释放按钮时,电路会因为机械部件的震动或弹跳而导致短暂的多次触发。这种现象可能会对电路造成误触发或干扰。下面是按键消抖的原因:机械弹跳:当按键被按下或释放时,机械接触部件(如弹簧或金属片)可能会因为弹性或惯性导致短时间内反复接触和分离。这种机械弹跳会引起瞬时断开和闭合,导致短暂的多次触发信号。机械震动:按键在使用过程中可能会受到外界震动或物理振动的影响,导致按键触发信号的误差。这种机械震动也可能会引起按键的短暂弹跳,由此产生消抖问题。电气干扰:电路中存在的电磁干扰或噪音也可能导致按键触发信号的抖动。这些干扰可以干扰信号线路或开关元件的接触,引起不稳定的触发信号。以上原因造成的按键消抖问题可能会对电子系统的精确控制、数据输入和用户体验等方面带来不便和困扰。为了解决按键消抖问题,通常会采取以下方法:软件滤波:在软件层面对按键信号进行滤波处理,通过设置延时时间或使用状态机等方式来确保只接收有效的稳定信号,忽略短暂的抖动信号。硬件滤波:在电路设计中使用外部元件(如电容器或电阻器)来滤除按键信号中的抖动。这些元件可以通过RC滤波电路来减小信号的上升和下降时间,从而防止抖动信号的误触发。使用独立的按键消抖芯片:有一些专门的按键消抖芯片可用于处理按键信号,通过内部的消抖算法来确保稳定触发信号的传递。
-
该设计采用软件滤波方式
- 定时器中断:
在基于51系列单片机的系统中,定时器中断是一种常见的中断方式,可用于实现定时功能。以下是51单片机定时器中断的一般原理:51单片机的定时器:51单片机通常具有一个或多个定时器,例如8051芯片具有定时器/计数器T0和T1。这些定时器是硬件模块,有自己的计数器和控制寄存器。定时器控制寄存器:每个定时器都有一个相应的控制寄存器,用于设置定时器的工作模式、计数值、中断使能等。通过编程控制这些寄存器的值,可以配置定时器的工作方式和定时周期。中断使能和优先级:定时器中断通常需要在中断控制寄存器中使能。此外,还需要设置中断优先级,以确定定时器中断与其他中断的优先级关系。在51单片机中,中断优先级是通过中断优先级寄存器IP进行设置。定时器中断触发:定时器根据设定的定时周期开始计数。当定时器的计数值达到预设的值时,定时器的中断标志位会被置位。此时,如果相应的定时器中断使能位被设置,定时器中断就会触发,进入中断服务程序。中断服务程序:中断服务程序是在定时器中断触发时执行的一段特殊代码。在中断服务程序中,可以执行一系列操作,如更新显示、数据处理、状态改变等。在处理完中断服务程序后,程序会返回到中断被触发时的执行位置,继续执行主程序。
技术细节
详细的工程代码:
主函数代码
#include <STC89C5xRC.H>
#include "Timer0.h"
#include "Key.h"
#include "Nexie.h"
/*********可自己更改单价******/
#define P_DAY 0.01 //白天距离0.01元/10米
#define P_Night 0.02 //晚上0.02元/10米
#define P_wait 0.03 //wait时0.03元/10米
unsigned int D_distance=0,N_distance=0,Wait=0;
unsigned int mode_flag=1; //白天-夜晚-等待--模式标志
unsigned char KeyNum=0,temp;//按键检测相关变量
unsigned char start=0; //开始、暂停标志位
unsigned int distance; //存放总距离数
float price; //存放总价格
/**
* @brief 按键扫描及相关标志位改变
* @param 无
* @retval 无
*/
void keyscan(void)
{
KeyNum=Key();
if(KeyNum)
temp=KeyNum;
switch (temp)
{
case 1:start++;if(start>1){start=0;}temp=0;break;
case 2:mode_flag++;if(mode_flag>3){mode_flag=1;};temp=0;break;
}
}
/**
* @brief 单价及等待模式显示
* @param 无
* @retval 无
*/
void danjia_show()
{
switch (mode_flag)
{
case 1:Nixie_SetBuf(1,11);Nixie_SetBuf(2,0);temp=0;break;
case 2:Nixie_SetBuf(1,12);Nixie_SetBuf(2,0);temp=0;break;
case 3:Nixie_SetBuf(1,20);Nixie_SetBuf(2,21);temp=0;break;//20为显示P,21显示-
}
}
/**
* @brief 公里数显示
* @param 无
* @retval 无
*/
void Distance_show()
{
distance=D_distance+N_distance;
Nixie_SetBuf(3,distance/10000%10);
Nixie_SetBuf(4,distance/1000%10+10);
Nixie_SetBuf(5,distance/100%10);
}
/**
* @brief 价格显示
* @param 无
* @retval 无
*/
void Price_show()
{
unsigned int ge=0,shi=0,xiao=0;//第一次缩放的变量,个位十位小数位
price=D_distance*P_DAY+N_distance*P_Night+Wait*P_wait;
//把float类型的price强制转换为int类型
ge=(unsigned int)price%10; //防止unsigned char类型导致数据丢失
shi=(unsigned int)price/100%10;
xiao=(unsigned int)price/10%10;
Nixie_SetBuf(8,(unsigned char) ge); //显示个位
Nixie_SetBuf(7,(unsigned char) xiao+10); //显示小数位
Nixie_SetBuf(6,(unsigned char) shi); //显示十位
}
void main()
{
Timer0_Init();
while(1)
{
keyscan();
danjia_show();
Distance_show();
Price_show();
}
}
void Timer0_Routine() interrupt 1
{
static unsigned int T0Count1,T0Count2,T0Count3,T0Count4;
TL0 = 0x18 ; //设置定时初始值
TH0 = 0xFC ; //设置定时初始值
T0Count1++;T0Count2++;T0Count3++;T0Count4++;//每1ms进行一次++;
if(T0Count1>=20)//20ms调用一次Key_Loop()函数
{
T0Count1=0;
Key_Loop();
}
if(T0Count2>=2)//2ms调用一次Nixie_Loop()函数
{
T0Count2=0;
Nixie_Loop();
}
if(T0Count3>=100)//100ms加10m
{
T0Count3=0;
if(start==1)
{
if(mode_flag==1)
{
D_distance++;
}
else if(mode_flag==2)
{
N_distance++;
}
}
}
if(T0Count4==500)//0.0333块钱每秒
{
T0Count4=0;
if(start==1)
{
if(mode_flag==3)
Wait++;
}
}
}
数码管扫描代码:
#include <STC89C5xRC.H>
#include "Delay.h"
unsigned char Nixie_Buf[9]={0,10,10,10,10,10,10,10,10};//0是为了照应loop中i=1
unsigned char NixieNumb[]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F,
0xBF,0x86,0xDB,0xCF,0xE6,0xED,0xFD,0x87,0xFF,0x6F,
0x73,0x40};//P和-
/**
* @brief 外部调用对数组Nixie_Buf[9]赋值
* @brief 使得loop函数循环对scan函数赋值时读到相应数据
* @brief 未赋值的数组位即为10;对应NixieNumb[]中的0;
* @param Table,Number在Nixie_Buf数组的下标 赋的值(该值即对应NixieNumb[]中的值)
* @retval 无
*/
void Nixie_SetBuf(unsigned char Table,Number)
{
Nixie_Buf[Table]=Number;
}
/**
* @brief 数码管显示
* @param Table,Number显示的位置 显示的数字
* @param 两参数由定时器按周期扫描时给定
* @retval 无
*/
void Nixie_Scan(unsigned char Table,Number)
{
P0=0x00;//消隐
switch(Table)
{// P2_4=C-4,P2_3=B-2;P2_2=A-1
case 1:P24=1;P23=1;P22=1;break;
case 2:P24=1;P23=1;P22=0;break;
case 3:P24=1;P23=0;P22=1;break;
case 4:P24=1;P23=0;P22=0;break;
case 5:P24=0;P23=1;P22=1;break;
case 6:P24=0;P23=1;P22=0;break;
case 7:P24=0;P23=0;P22=1;break;
case 8:P24=0;P23=0;P22=0;break;
}
P0=NixieNumb[Number];//不用delay再消隐,因为定时器进来有时间间隔
}
/**
* @brief 定时器调用的函数
* @param 无
* @retval 无
*/
void Nixie_Loop(void)//定时器每进一次就调用一次scan函数
{ //使得八个数码管依次快速显示
static unsigned char i=1;
Nixie_Scan(i,Nixie_Buf[i]);
i++;
if(i>=9)i=1;
}
按键扫描代码:
#include <STC89C5xRC.H>
unsigned char Key_KeyNumber;
/**
* @brief 外部调用主函数
* @param 无
* @retval Temp 传出在loop函数中得到的Key_KeyNumber值;
*/
unsigned char Key(void)
{
unsigned char Temp=0;
Temp=Key_KeyNumber;
Key_KeyNumber=0;//对全局变量清0,防止下次中断进来
//没按键按下仍传出上次按键值
return Temp;
}
/**
* @brief 定时器按周期进来一次则检测四个按键是否有按下的
* @param 无
* @retval keyNumber得到键值在loop中调用并返回值给NowState
*/
unsigned char Key_GetState()
{
unsigned char keyNumber=0;
if(P30==0)keyNumber=1;
if(P31==0)keyNumber=2;
return keyNumber;
}
/**
* @brief 定时器周期扫描函数
* @param 无
* @retval 无
*/
void Key_Loop(void)
{
static unsigned char NowState,LastState;//静态变量防止下次进来销毁
LastState=NowState;
NowState=Key_GetState(); //调用按键按下函数,并把按下的值赋给NowState
if(LastState==1 && NowState==0)//如果上次为1按下状态,这次为松开状态0则可以确定1键按下了
{
Key_KeyNumber=1; //把得到的键值从中断中传出,放到全局变量Key_KeyNumber中
}
if(LastState==2 && NowState==0)
{
Key_KeyNumber=2;
}
}
定时器初始化代码:
#include <STC89C5xRC.H>
/**
* @brief 定时器初始化
* @param 无
* @retval 无
*/
void Timer0_Init(void) //1毫秒@12.000MHz
{
AUXR &= 0x7F; //定时器时钟12T模式
TMOD &= 0xF0; //设置定时器模式
TL0 = 0x18; //设置定时初始值
TH0 = 0xFC; //设置定时初始值
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时
ET0=1;
EA=1;
PT0=0;
}
小结
注:本设计仿真使用的是protues的8.15版本,如不能打开请检查版本序号!
该设计基本完成了设计任务的要求,显示单价,等待模式,公里数,总价格都能稳定显示,按键不会误触发,有消抖处理。欢迎大家的讨论交流和指正!也希望能给你带来一些帮助!