第4章 简单应用系统设计 ——控制类 目前,在单片机应用系统中,控制类的应用占到了绝大多数。本章将以6个案例的讲解向读者介绍如何进行单片机的控制系统设计。 本章的所有案例在Proteus中均可仿真通过。这些内容均以实际项目的设计过程来讲解,所有电路和软件代码也均调试通过,读者稍加改造即可应用于自己的设计项目。 4.1 电子抢答器系统设计 抢答器广泛应用于各类抢答比赛活动中,其设计方法也多种多样。本案例以单片机为核心,配以一定的外围器件设计电子抢答器系统。该系统可同时供8位选手使用,具备一般抢答器的基本功能,读者可方便地应用于实际工程项目中。 4.1.1 系统设计要求与方案确定 1. 系统设计要求 ? 以AT89S52单片机为核心元件,设计一个电子抢答器系统。 ? 该抢答器可以供8个选手使用。可以显示30s倒计时时间,显示抢到的选手号并伴有提示音。 ? 主持人控制该系统的使用(选择开始抢答、系统复位等)。 ? 可以调整抢答的时间。 ? 超时报警功能。 2.设计方案确定 (1)硬件方案 硬件电路由6个部分组成,即单片机按键输入电路、单片机时钟电路、复位电路、数码管驱动电路、四位数码管电路、蜂鸣器电路。各部分实现功能如下。 ? 单片机按键输入电路:包括8个选手按键、1个开始键、1个复位键,提供选手的抢答信号和主持人的控制信号。 ? 单片机时钟电路、复位电路:单片机正常工作需要。 ? 数码管驱动及四位数码管电路:显示倒计时时间和抢到的选手号。 ? 蜂鸣器电路:提供超时报警和有选手抢答时的提示音。 (2)软件方案 本系统软件部分的主要完成功能是键盘的按键识别、抢答计时显示及调整、超时报警、显示抢到选手的号码及播放提示音。 根据软件的功能划分软件设计模块结构,如图4.1所示。 其中各个模块的具体任务如下。 ? 程序初始化模块:对按键、数码管、定时器初始设置。 ? 按键驱动模块:对各按键的功能进行简单的定义。 ? 超时报警模块:时间到,无人抢答则报警。 ? LED驱动模块:根据系统需要,进行相应数字的显示。 ? 抢答计时模块:用定时时钟对抢答时间进行限制。 4.1.2 系统硬件电路设计 1.系统硬件框图设计 根据确定的硬件方案设计系统硬件框图,如图4.2所示。 2.系统原理图设计 根据设计要求和硬件框图设计系统原理图,如图4.3所示。 由于在Proteus中做单片机仿真时,单片机的时钟及复位电路可省略不接(由Proteus软件提供),因此在原理图中该部分电路未画出,请读者注意。 图4.3 电子抢答器原理图 单片机的管脚资源分配: ? P0口为数码管的8位数据信号口。 ? P1口为选手按键的输入信号。 ? P2.0、P2.1、P2.2、P2.3 为数码管的位选择信号。 ? P2.4、P2.5 为主持人控制键(开始、复位)信号。 ? P2.6 为蜂鸣器输出信号。 4.1.3 系统软件设计 1.程序流程图 系统程序主程序流程图如图4.4所示。中断服务程序流程图如图4.5所示。 图4.4 电子抢答器主程序流程图 图4.5 电子抢答器中断服务程序流程图 2.应用程序设计 该系统应用程序设计主要包括抢答器主程序、键盘扫描程序和数码管显示程序3大部分。程序由5个文件组成,如表4.1所示。 表4.1 电子抢答器系统程序文件列表 文件名 完成功能 led.h 数码管端口定义及用到的函数声明 Key.h 对键盘端口进行定义和存放 Qiangda.c 系统主文件,电子抢答器的具体流程处理 led.c 根据需要显示的内容进行一定的译码显示 Key.c 实现按键的扫描,取得键值 系统详细软件代码如下。 Key. h头文件: /*************************************************************************** * 文件名称:Key.h * 说明:本文件为键盘驱动头文件 * 功能:对键盘进行端口定义 * 修改: * 版本:1.0.0 * 作者:YuanDong * 时间:2014.5.15 ****************************************************************************/ #include "Include.h" #ifndef KEY_H #define KEY_H 1 #ifndef KEY_GLOBAL #define KEY_EXT extern #else #define KEY_EXT #endif #define READ_KEY(x,name,b) ((x)?(b|=name):(b&=~name) ) //-----------------------------用户设置区----------------------------------- #define CAN_MORE_PRESS 0 // 是否允许多键都按:允许为1;不允许为0 #define CAN_REPEAT 0 //是否允许重复按键:允许为1;不允许为0 #define REPEAT_MODE 1 //重复模式:先长后短为1;相同间隔为0 #define FIRST_TIMES 200 //重复按下时,第一次间隔时间 #define OTHER_TIMES 25 //第二次后间隔时间(重复模式为0时此值无效) #define KEY_DELAY_TIME 20 //键盘扫描时间间隔 XXXus #define KEY_FORCE_VALUE 0 //键盘有效电平,1为高电平,0为低电平 #define IN_PRESS_ONE P1_0 //1号选手按键端口 #define IN_PRESS_TWO P1_1 //2号选手按键端口 #define IN_PRESS_THREE P1_2 //3号选手按键端口 #define IN_PRESS_FOUR P1_3 //4号选手按键端口 #define IN_PRESS_FIVE P1_4 //5号选手按键端口 #define IN_PRESS_SIX P1_5 //6号选手按键端口 #define IN_PRESS_SEVEN P1_6 //7号选手按键端口 #define IN_PRESS_EIGHT P1_7 //8号选手按键端口 /* 键盘重映射 注:当前结构下最大按键数为8; 以二进制中的相应位来标识 */ enum KEY_VALUE{KEY_ONE=0x01,KEY_TWO=0x02, KEY_THREE=0x04, KEY_FOUR=0x08, KEY_FIVE=0x10,KEY_SIX=0x20,KEY_SEVEN=0x40,KEY_EIGHT=0x80,KEY_NULL=0x00}; //---------------------------------------------------------------------------- KEY_EXT void init_key(void); KEY_EXT uchar get_key_value(void); KEY_EXT void delay_us(uint us); KEY_EXT void delay(uint us); #endif led.h文件: /**************************************************************************** * 文件名称:led.h * 说明:本文件为数码管头文件 * 功能:1.数码管端口定义 2.数码管用到的函数说明 * 修改: 无 * 版本:1.0.0 * 作者:YuanDong * 时间:2014.5.15 ***************************************************************************/ #include "Include.h" #ifndef LED_H #define LED_H 1 #ifndef LED_GLOBAL #define LED_EXT extern #else #define LED_EXT #endif #define LED P0 sbit LED_A=P0^6; sbit LED_B=P0^5; sbit LED_C=P0^4; sbit LED_D=P0^3; sbit LED_E=P0^2; sbit LED_F=P0^1; sbit LED_G=P0^0; sbit LED_DP=P0^7; sbit LED_CS1=P2^3; //数码管片选 sbit LED_CS2=P2^2; sbit LED_CS3=P2^1; sbit LED_CS4=P2^0; /*----------------设定时钟在存储区中的位置 -------------------------*/ #define S_L 0 //秒高位所在存储区中的位置 #define S_H 1 //秒低位所在存储区中的位置 #define NO 3 //抢答选手号所在缓冲位置 #define SET_MOD 2 //手动设定抢答时间时的模式 /*------------------------------------------------------------------*/ enum SEG_DISPLAY_MOD{DIGITAL=0x00,CHARACTER=0x01}; /*高电平驱动 */ #ifdef LED_GLOBAL uchar segLedCode[10]={0x7e,0x06,0x6d,0x79,0x33,0x5b,0x5f,0x70,0x7f,0x73}; /*数码管数码译码数组,led6~led0==a~g,code1~10==0~9*/ #endif LED_EXT void init_led(void); LED_EXT void ledWrite(uchar c,uchar n,uchar mod); /*循环显示子函数,segCode为要显示的数据组,segCodeMode为要显示的相应数据模式 */ LED_EXT void ledSweepDisplay(uchar *segCode,uchar segCodeMod,uchar segNumber); #endif Qiangda.c文件: /**************************************************************************** * 文件名称:Qiangda.c * 说明:本文件为电子抢答器主程序 * 功能:实现抢答器的主程序控制 * 修改:无 * 版本:1.0.0 * 作者:YuanDong * 时间:2014.5.15 ****************************************************************************/ #include "Key.h" #include "led.h" #define KEY_VALID_VALUE 0 //主持人按键有效电平 #define MOD_TWO 0x49 //模式显示字符 #define ALARM_COUNT_TIME 1000 //选手回答时报警时间,10s sbit KEY_START=P2^4; sbit KEY_CLEAR=P2^5; sbit ALARM_OUT=P2^6; const uchar displayMode=0x04; uint alarmCountTemp; //选手抢答报警计时 uchar clockCountLTemp=0x00; //抢答时间原始值,为BCD码式的 uchar clockCountHTemp=0x03; uchar displayBuff[4]={'0','0',0x7e,'0'}; //数码管显示缓冲区,低两位为时间,最高位为抢答号 uchar musicFre[4]={0x10,0x20,0x30,0x40}; uchar musicFreTemp; uchar musicFreCount; uchar alarm_can_out; //允许报警输出 uchar game_is_ok; //抢答器状态变量,0x01为抢答开始 uchar time_is_over; //抢答时间计数到变量 uchar key_is_press; uchar clockCountL; //抢答时间计数值低位 uchar clockCountH; //抢答时间计数值高位 uchar timer0Temp; //timer0 中断计数变量 uchar workMode; //抢答器工作模式,0为抢答模式,1为时间调整模式 void init_timer0(void) //定时10ms { TMOD|=0x01; //GATE=0,TR=1运行;C/T=1,counter,0,timer;01十六进制 TH0=0xdc; TL0=0x00; TR0=1; //timer0 控制位,为1时启动timer0 ET0=1; //timer0 中断使能 } void main() { uchar gameKeyScan; init_led(); init_key(); init_timer0(); clockCountL=clockCountLTemp; clockCountH=clockCountHTemp; displayBuff[S_H]=clockCountH+'0'; displayBuff[S_L]=clockCountL+'0'; gameKeyScan=0x00; game_is_ok=0x00; timer0Temp=0x00; musicFreTemp=0x00; time_is_over=0x00; key_is_press=0x00; workMode=0x00; alarm_can_out=0x00; alarmCountTemp=0x00; sei(); while(1) { ledSweepDisplay(displayBuff,displayMode,4); if(KEY_START==KEY_VALID_VALUE) { if(workMode==0x00) //模式1 { if(game_is_ok!=0x01) { clockCountL=clockCountLTemp; clockCountH=clockCountHTemp; displayBuff[S_H]=clockCountH+'0'; displayBuff[S_L]=clockCountL+'0'; displayBuff[NO]='0'; game_is_ok=0x01; //抢答开始 } } Else //模式2 { while(KEY_START==KEY_VALID_VALUE); if(clockCountLTemp<0x09) clockCountLTemp++; else { clockCountLTemp=0x00; if(clockCountHTemp<0x05) clockCountHTemp++; else clockCountHTemp=0x00; } clockCountL=clockCountLTemp; clockCountH=clockCountHTemp; displayBuff[S_H]=clockCountH+'0'; displayBuff[S_L]=clockCountL+'0'; } } else if(KEY_CLEAR==KEY_VALID_VALUE) { if(workMode!=0x00) { workMode=0x00; displayBuff[SET_MOD]=0x7e; } else { if(game_is_ok!=0x01) { delay(100); if(KEY_CLEAR==KEY_VALID_VALUE) { delay(100); if(KEY_CLEAR==KEY_VALID_VALUE) { delay(100); while(KEY_CLEAR==KEY_VALID_VALUE) { workMode=0x01; //进入模式2 displayBuff[SET_MOD]=MOD_TWO; ledSweepDisplay(displayBuff,displayMode,4); } } } } } game_is_ok=0x00; time_is_over=0x00; alarm_can_out=0x00; clockCountL=clockCountLTemp; clockCountH=clockCountHTemp; displayBuff[S_H]=clockCountH+'0'; displayBuff[S_L]=clockCountL+'0'; displayBuff[NO]='0'; } if(game_is_ok==0x01) { if(time_is_over!=0x01) { gameKeyScan=get_key_value(); if(gameKeyScan!=0x00) { game_is_ok=0x00; musicFreCount=musicFre[1]; alarm_can_out=0x02; if(KEY_ONE==gameKeyScan) displayBuff[NO]=0x01+'0'; if(KEY_TWO==gameKeyScan) displayBuff[NO]=0x02+'0'; if(KEY_THREE==gameKeyScan) displayBuff[NO]=0x03+'0'; if(KEY_FOUR==gameKeyScan) displayBuff[NO]=0x04+'0'; if(KEY_FIVE==gameKeyScan) displayBuff[NO]=0x05+'0'; if(KEY_SIX==gameKeyScan) displayBuff[NO]=0x06+'0'; if(KEY_SEVEN==gameKeyScan) displayBuff[NO]=0x07+'0'; if(KEY_EIGHT==gameKeyScan) displayBuff[NO]=0x08+'0'; } } Else //抢答开始后时间到 { musicFreCount=musicFre[0]; alarm_can_out=0x01; } } } } void timer0_overflowing() interrupt 1 using 1 //timer0溢出中断,10ms { TH0=0xdc; TL0=0x00; if((alarm_can_out!=0x01)&&(alarm_can_out!=0x02)) ALARM_OUT=0; else { if(alarm_can_out==0x01) { if(musicFreTemp0x00) clockCountL--; else { if(clockCountH!=0x00) { clockCountL=0x09; clockCountH--; } else time_is_over=0x01; displayBuff[S_H]=clockCountH+'0'; } displayBuff[S_L]=clockCountL+'0'; } } } led.c文件: /*************************************************************************** * 文件名称:led.c * 说明:本文件为数码管驱动显示程序 * 功能:根据键值进行一定的译码显示 * 修改:无 * 版本:1.0.0 * 作者:YuanDong * 时间:2014.5.15 ***************************************************************************/ #define LED_GLOBAL 1 #include "led.h" extern void delay(uint us); //声明延迟函数,实例化在Key.c文件中 extern void delay_us(uint us); void init_led(void) { LED=segLedCode[0]; LED_CS1=0; LED_CS2=0; LED_CS3=0; LED_CS4=0; } void ledWrite(uchar c,uchar n,uchar mod) { if(CHARACTER==mod) { LED=c; } else if(DIGITAL==mod) { if((c-'0')<0x0a) { LED=segLedCode[c-'0']; } else { LED=segLedCode[0]; } } switch(n) { case 0x01: { LED_CS1=0; delay_us(10); LED_CS1=1; break; } case 0x02: { LED_CS2=0; delay_us(10); LED_CS2=1; break; } case 0x03: { LED_CS3=0; delay_us(10); LED_CS3=1; break; } case 0x04: { LED_CS4=0; delay_us(10); LED_CS4=1; break; } default: break; } } void ledSweepDisplay(uchar *segCode,uchar segCodeMod,uchar segNumber) { //循环显示子函数 uchar segCount; for(segCount=0;segCount>1; } } Key.c文件: /**************************************************************************** * 文件名称:Key.c * 说明:本文件为键盘扫描程序 * 功能:实现按键的扫描,取得键值 * 修改:无 * 版本:1.0.0 * 作者:YuanDong * 时间:2014.5.15 *************************************************/ #define KEY_GLOBAL 1 #include "Key.h" uchar keyValueBuff; //读取键值的缓冲区 uchar keyValueOld; //前一次的键值 uchar keyValueTemp; uint stillTimes; //键按下保持次数 uint stillTimesMax; uchar get_data_1_count(uchar number); void read_all_key(uchar *buff); void init_key(void) { keyValueBuff=0x00; keyValueOld=0x00; keyValueTemp=0x00; stillTimes=0; stillTimesMax=FIRST_TIMES; } void read_all_key(uchar *buff) { READ_KEY(IN_PRESS_ONE==KEY_FORCE_VALUE,KEY_ONE,*buff); READ_KEY(IN_PRESS_TWO==KEY_FORCE_VALUE,KEY_TWO,*buff); READ_KEY(IN_PRESS_THREE==KEY_FORCE_VALUE,KEY_THREE,*buff); READ_KEY(IN_PRESS_FOUR==KEY_FORCE_VALUE,KEY_FOUR,*buff); READ_KEY(IN_PRESS_FIVE==KEY_FORCE_VALUE,KEY_FIVE,*buff); READ_KEY(IN_PRESS_SIX==KEY_FORCE_VALUE,KEY_SIX,*buff); READ_KEY(IN_PRESS_SEVEN==KEY_FORCE_VALUE,KEY_SEVEN,*buff); READ_KEY(IN_PRESS_EIGHT==KEY_FORCE_VALUE,KEY_EIGHT,*buff); } uchar get_key_value(void) { keyValueBuff=0x00; read_all_key(&keyValueBuff); keyValueTemp=keyValueBuff; delay_us(KEY_DELAY_TIME); read_all_key(&keyValueBuff); /* 两次读到的键盘值相同则为有效键盘值 */ if( keyValueTemp==keyValueBuff ) { if(keyValueTemp==0) { keyValueOld=0; stillTimes=0; stillTimesMax=FIRST_TIMES; return 0; } else if(keyValueOld !=keyValueTemp) { stillTimes=0; keyValueOld=keyValueTemp; #if CAN_MORE_PRESS return keyValueTemp; #else if( 1==get_data_1_count(keyValueTemp) ) { return keyValueTemp; } else { return 0; } #endif } else if(keyValueOld==keyValueTemp) { #if CAN_MORE_PRESS==0 if( 1 !=get_data_1_count(keyValueTemp) ) { stillTimes=0; return 0; } #endif #if CAN_REPEAT stillTimes++; if(stillTimes>stillTimesMax) { stillTimes=0; #if REPEAT_MODE if(FIRST_TIMES==stillTimesMax) { stillTimesMax=OTHER_TIMES; } #endif return keyValueTemp; } #else stillTimes=0; return 0; #endif } } else { stillTimes=0; return 0; } return 0; } uchar get_data_1_count(uchar number) { register uchar i,j=0; for(i=0; i<8; i++) { if( (number&_BV(i)) ) { j++; } } return j; } void delay_us(uint us) { uchar delayi; while(--us) { for(delayi=0;delayi<10;delayi++); } } void delay(uint us) //1ms { uchar delayi; while(--us) { for(delayi=0;delayi<124;delayi++); } } 4.1.4 设计小结 本案例以单片机为核心设计了一个电子抢答器系统,比起用数字电路实现的电子抢答器系统,具有硬件电路简单、成本低廉、功能丰富及更好的功能扩展性等特点。在软件设计中,应用了定时器技术、按键识别技术、数码管显示技术等。