今天和大家分享一下2019年第十届蓝桥杯单片机决赛的程序题,我在做这道题的时候属实有被难到,今天就和大家分享一下;先看题目:
4、功能描述
4.1功能概述
1)使用超声波测距单元完成测距功能。
2) 通过DS18B20温度传感器完成温度测量功能。
3)通过 PCF8591 D/A 转换芯片完成模拟电压输出功能。
4)通过AT24C02E2PROM存储器完成参数变动次数记录功能。
5)通过竞赛板上的USB 转串口模块实现串口收发功能。
6) 通过键盘、数码管、LED指示灯等完成人机交互操作。
7) 温度(T)、 距离(S)测量结果刷新时间要求
●温度(T)≤0.5秒。
距离(S)≤1秒。
8)距离(S)测量说明
测量范围要求: 10cm - 50cm。
●声音在空气中的传播速度: 340米/秒。
4.2显示功能
1) 数据界面
一 温度数据显示
温度数据界面如图2所示.显示内容包括提示符C和温度值。温度数据单位为摄氏度C,数据保留小数点后两位有效数字,占用4位数码管。

●距高数据显示
距离数据界面如图3所示,显示内容包括提示符L和距离数据。距离数据单位为cm,可显示距离范围0cm-99cm,占用两位数码管。

变更次数显示
显示内容包括提示符n和参数变动次数。参数变动次数记录范围为0-65535次,占用5位数码管,数据长度不足5位时,高位数码管熄灭。

2) 参数界面
●温度参数
显示内容包括提示符P、参数编号l和温度参数,温度参数可调整范围0-99。

●距离参数
显示内容包括提示符只、参数编号2和距离参数,距离参数可调整范围0-99。

好,所有的显示功能都已经在这里了,那么我们就先把这些显示功能的函数写下来,在写显示函数之前,我们要先把准备工作做好,比如关闭继电器、蜂鸣器等,准备好数码管的段选数组:
typedef unsigned int u16;
typedef unsigned char u8;
u8 code smgduan[17]=
{0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,
0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71};//共阴显示0~F的值
void delayms(u16 i) //延迟函数
{
u16 aa;
while(i--)
{
for(aa=0;aa<845;aa++);
}
}
void InitHC138(u8 add) //Y4、Y5、Y6、Y7通道选择函数
{
switch(add)
{
case(4):
P2 = ( P2 & 0X1F ) | 0X80;break;
case(5):
P2 = ( P2 & 0X1F ) | 0Xa0;break;
case(6):
P2 = ( P2 & 0X1F ) | 0Xc0;break;
case(7):
P2 = ( P2 & 0X1F ) | 0Xe0;break;
}
}
void InitHC573() //关闭蜂鸣器、继电器、LED函数
{
InitHC138(5);
P0 = 0X00;
InitHC138(4);
P0 = 0XFF;
}
void DigDisplay(u8 add,u8 dat) //数码管段选、位选函数
{
InitHC138(6);
P0 = 0X01 << add;
InitHC138(7);
P0 = dat;
}
void GetDatPro() //温度获取
{
distance_get = distance;
temp_get = Tempget();
temp_get = temp_get*100;
}
void wave_rec(void) //超声波距离获取
{
u8 num = 10;
TX = 0;
TL0 = 0xF4; //设置定时初值
TH0 = 0xFF; //设置定时初值
TR0 = 1; //定时器0打开
while(num--)
{
while(!TF0);
TF0 = 0;
TX ^= 1;
}
TR0 = 0; //定时器0关闭
TL0 = 0; //设置定时初值
TH0 = 0; //设置定时初值
TR0 = 1; //定时器0打开
while(RX && !TF0);
TR0 = 0;
if(TF0)
{
TF0 = 0;
distance = 99;
}
else
{
distance = ((TH0 << 8) | TL0) * 0.017;
if(distance > 99) distance = 99;
}
}好,准备工作做好后,就先把显示函数写出来:
u16 distance_get,distance_setup = 35;//定义距离变量和参数
u16 temp_get ,temp_setup = 30; //定义温度变量和参数
u8 setup_count; //设置参数
//===================温度数据显示========================
void DisplayTempGet()
{
DigDisplay(0,~smgduan[12]);
delayms(1);
DigDisplay(4,~smgduan[temp_get/1000]);
delayms(1);
DigDisplay(5,~(smgduan[temp_get/100%10] | 0x80));
delayms(1);
DigDisplay(6,~smgduan[temp_get/10%10]);
delayms(1);
DigDisplay(7,~smgduan[temp_get%10]);
delayms(1);
}
//===================距离数据显示========================
void DisplayDistanceGet()
{
DigDisplay(0,0xc7);
delayms(1);
DigDisplay(6,~smgduan[distance_get/10]);
delayms(1);
DigDisplay(7,~smgduan[distance_get%10]);
delayms(1);
}
//===================参数数据变动显示========================
void DisplaySetupCount()
{
DigDisplay(0,0xc8);
delayms(1);
DigDisplay(7,~smgduan[setup_count%10]);
delayms(1);
if(setup_count>9)
{
DigDisplay(6,~smgduan[setup_count/10%10]);
delayms(1);
}
if(setup_count>99)
{
DigDisplay(5,~smgduan[setup_count/100%10]);
delayms(1);
}
if(setup_count>999)
{
DigDisplay(4,~smgduan[setup_count/1000%10]);
delayms(1);
}
if(setup_count>9999)
{
DigDisplay(3,~smgduan[setup_count/10000]);
delayms(1);
}
}
//===================温度参数显示========================
void DisplayTempSetup()
{
DigDisplay(0,0x8c);
delayms(1);
DigDisplay(3,~smgduan[1]);
delayms(1);
DigDisplay(6,~smgduan[temp_setup/10]);
delayms(1);
DigDisplay(7,~smgduan[temp_setup%10]);
delayms(1);
}
//===================距离参数显示========================
void DisplayDistanceSetup()
{
DigDisplay(0,0x8c);
delayms(1);
DigDisplay(3,~smgduan[2]);
delayms(1);
DigDisplay(6,~smgduan[distance_setup/10]);
delayms(1);
DigDisplay(7,~smgduan[distance_setup%10]);
delayms(1);
}接下来,我们来看按键模块:
4.3按键功能
1) “短按键”功能说明
●S13:定义为“界面”按键,按下SI3按键,切换数据界面和参数界面,按键S13切换模式如下图所示:

界面切换要求:
1)每次从数据界面进入参数界面,默认当前为温度参数。2)每次从参数界面进入数据界面,默认当前为温度数据。.
●S12:定义为“切换”按键。
在数据界面下,按下S12按键,切换显示温度数据、距离数据和参数变更次数。切换模式如下图所示:

在参数界面下,按下SI2按键,切换显示温度参数和距离参数,切换模式如下图所示:

●S16: 定义为“减”按键。
在温度参数界面下,按下S16,温度参数减少2℃.
在距离参数界面下,按下S16,距离参数减少5cm
●S17: 定义为“加”按键。
在温度参数界面下,按下S17,温度参数增加2℃。
2)“长按键” 功能说明
●任何界面状态下,长按S12按键,可重置参数变动次数记录为0次。
●任何界面状态下,长按S13按键,可切换DAC输出功能,详见4. 6 DAC输出功能。 切换模式如下图所示:

3) 其它要求
长按键功能触发时间要求:按键按下时间超过1秒,触发长按键功能,否则为短按键。
按键应做好消抖处理,避免出现一次按键功能多次触发等问题。按键长按、短按对应的功能和效果不可互相影响。
请严格按照以上要求,定义各按键长按、短按功能。
这个按键是阉割版的矩阵按键,比较好处理,功能要求看起来似乎有点复杂,其实,只要我们把他们的逻辑写出来就会一目了然,S13只有一个切换功能,我们就用位定义一个变量,只有0和1两个状态,再设几个变量,每当按键按下,只是变量的值在变,不要再按键处理函数里调用显示函数,这样太乱:
void KeyScan()
{
R1 = 0;
R2 = 1;
C1 = 1;C2 = 1;
if(C1 == 0) //S13
{
if(turn_dat_setup == 0) //数据界面
{
get_dat++;
}
else if(turn_dat_setup == 1)
{
setup_dat++;
}
DA_flag = 1;
while(!C1);
DA_flag = 0;
}
else if(C2 == 0) //S17
{
switch(setup_dat)
{
case(1):temp_setup = temp_setup+2;break;
case(2):distance_setup = distance_setup+5;break;
}
setup_count++;
while(!C2);
}
R2 = 0;
R1 = 1;
C1 = 1;C2 = 1;
if(C1 == 0) //S12
{
turn_dat_setup = ~turn_dat_setup;
count_flag = 1;
while(!C1);
count_flag = 0;
EEPROM_write(0x01,setup_count/256);
delayms(5);
EEPROM_write(0x02,setup_count%256);
delayms(5);
}
else if(C2 == 0) //S16
{
switch(setup_dat)
{
case(1):temp_setup = temp_setup-2;break;
case(2):distance_setup = distance_setup-5;break;
}
setup_count++;
while(!C2);
}
}
void DisplayDat() //对于按键控制的参数进行显示
{
if(turn_dat_setup == 0)
{
switch(get_dat)
{
case(1):DisplayTempGet();break;
case(2):DisplayDistanceGet();break;
case(3):DisplaySetupCount();break;
}
}
else if(turn_dat_setup == 1)
{
switch(setup_dat)
{
case(1):DisplayTempSetup();break;
case(2):DisplayDistanceSetup();break;
}
}
}
void SetupDatPro() //数据的边界范围处理
{
if(get_dat > 3)
{
get_dat = 1;
}
if(get_dat < 1)
{
get_dat = 3;
}
if(setup_dat > 2)
{
setup_dat = 1;
}
if(setup_dat < 1)
{
setup_dat = 2;
}
if(temp_setup > 99)
{
temp_setup = 0;
}
if(temp_setup < 0)
{
temp_setup = 99;
}
if(distance_setup > 99)
{
distance_setup = 0;
}
if(distance_setup < 0)
{
distance_setup = 99;
}
}
void Timer2() interrupt 12 //这个是定时器2,我们后面会讨论到
{
u16 count_sec_setup,count_sec_AD;
if (DA_flag) //长按键 DA切换
{
count_sec_AD++;
if(count_sec_AD == 500)
{
count_sec_AD = 0;
DA_turn = ~DA_turn;
}
}
if(count_flag == 1) //长按键清零
{
count_sec_setup++;
if(count_sec_setup == 500)
{
setup_count = 0;
count_sec_setup = 0;
}
}
}4.4存储功能
1)数据的存储
每当一次参数设置操作完成(从参数界面退出,切换回数据界面),如果参数发生变化,参数变动次数加1,将参数变动次数保存在E2RPOM存储器,要求可记录范围: 0-65535.
2)数据的重置.
参数变动次数可以通过长按S12重置为0次。
存储功能也已经写在按键扫描函数里了;这个是读取函数:
void GetSetupCount() //读取EEPROM的数值
{
u16 count_h,count_l;
count_h = EEPROM_read(0x01);
count_l = EEPROM_read(0x02);
setup_count = ((count_h * 256) + count_l);
}4.5串口功能
1)通信模式
波特率: 4800 bps
校验位:无校验
停止位: 1位。
2)数据召测功能
通过串口调试软件下发数据召测指令,设备接收到正确指令后,上报数据或参数。召测指令格式要求如下:
①查询数据指令:"ST\r\n”, 接收到指令后,设备返回距离和温度数据,数据格式要求如下:
以字符串形式输出,以’$’开头, "\r\n”结尾。
距离数据(S)和温度数据(T)以’,’间隔。
-格式: $距离数据,温度数据\r\n,
举例: $20,24. 32\r\n
上报的距离数据为整数,温度数据保留小数点后两位有效数字。
②查询参数指令: ”PARA\r\n”,接收到指令后,设备返回当前的距离参数和温度参数。返回数据格式要求如下:
以字符串形式输出,以‘$’开头,” r\n”结尾。
距离参数(S)和温度参数(T)以’,’间隔。
格式: #距离参数,温度参数\r\n
~举例: #35,30\r\n
③如设备接收到错误指令,返回”ERROR\r\n”。
备注:
1) 串口查询参数、数据指令响应时间要求: ≤500ms。
2) 串口逻辑功能错乱,发送乱码、错误数据将被酌情扣分。
3) S12、 S13、 S16、 S17按键扫描过程和串口通讯功能应互不影响。
串口这一模块我一直觉得是最难的一块,因为总是不能够顺畅的打开PC和单片机之间的通信。而这道题的难点在于,超声波已经占据一个定时器资源了,我们还需要一个定时器进行定时工作,这个时候就需要打开第三个定时器了,对于第三个定时器,我们使用的<reg52>的头文件没有包含相关的寄存器,需要我们查阅资料进行配置:
sfr T2H = 0xD6; //定时器2的高八位寄存器
sfr T2L = 0xD7; //定时器2的低八位寄存器
sfr AUXR = 0X8E;
sfr T2MOD = 0XC9;
sfr IE2 = 0XAF;
u8 RevBuf[20]; //数据接收缓冲区
bit rx_flag;
bit send_enable = 0; //发送标志位
u8 rx_tt; //
u8 rx_cnt;
void UartInit(void) //4800bps@12.000MHz
{
SCON = 0x50; //8位数据,可变波特率
AUXR |= 0x40; //定时器1时钟为Fosc,即1T
AUXR &= 0xFE; //串口1选择定时器1为波特率发生器
TMOD &= 0x0F; //设定定时器1为16位自动重装方式
TL1 = 0x8F; //设定定时初值
TH1 = 0xFD; //设定定时初值
ET1 = 0; //禁止定时器1中断
TR1 = 1; //启动定时器1
ES = 1;
}
void Time2Init() //定时器2打开
{
EA = 1;
IE2 = (1<<2);
AUXR &= 0XFB;
T2L = 0X30; //2ms
T2H = 0XF8;
AUXR |= 0X10;
}
void SendByte(u8 dat) //串口发送字节
{
SBUF = dat;
while(!TI);
TI = 0;
}
void SendDat(u8 *str) //串口一位一位的发送需要发送的数据
{
while(*str != '\0')
{
SendByte(*str++);
}
}
void Urat() interrupt 4 //串口接收,接收到的数据暂存缓冲区数组中
{
if(RI == 1)
{
RI = 0;
rx_flag = 1; //表示数据已经接收完毕
RevBuf[rx_cnt++] = SBUF; // 放入暂存区
}
}
void UratPro()
{
if(send_enable) //允许向PC发送数据
{
send_enable = 0;
SendDat(RevBuf);
}
}
void Timer2() interrupt 12
{
if(rx_flag && ++rx_tt >= 50) //如果数据接收完毕
{
rx_flag = 0;
rx_tt = 0;
RevBuf[rx_cnt++] = '\0';
rx_cnt = 0;
if(strcmp(RevBuf,"ST\r\n") == 0) //判断电脑发送的信息是否一致,如
{ //果一致,发送距离数据
send_enable = 1;
sprintf(RevBuf,"$%d,%d\r\n",distance_get,temp_get%100);
}
else if(strcmp(RevBuf,"PARA\r\n") == 0)
{
send_enable = 1;
sprintf(RevBuf,"$%d,%d\r\n",distance_setup,temp_setup);
}
else
{
send_enable = 1;
sprintf(RevBuf,"ERROR\r\n");
}
}
}4.6DAC输出功能
1) 在启动状态下,DAC输出电压值Vout取决于测距数据(S), 对应关系如下表所示

2)在停止状态下,DAC 固定输出0. 4V.
DAC功能看起来比较生疏,但其实将底层驱动完善后只用进行一个简单的判断就可以输出了:
void DAPro()
{
if(DA_turn == 1)
{
if(distance_get <= distance_setup)
{
DA_write(102);
}
else if(distance_get > distance_setup)
{
DA_write(204);
}
}
}4. 7LED指示灯功能
1) 温度指示灯
当温度数据超过温度参数时,指示灯L1点亮,否则熄灭。
2) 距离指示灯
当距离数据小于距离参数时,指示灯L2点亮,否则熄灭。
3)DAC功能指示灯
启动状态下,指示灯L3点亮,停止状态下,指示灯L3熄灭。
每一次试题的最后都要考到LED和蜂鸣器之类的控制,做到这里的时候一定要静下心,必要的时候在纸上写一个逻辑框图就很清晰了:
void LedPro()
{
InitHC138(4);
P0 = 0XFF;
if(temp_get > temp_setup )
{
L1 = 0;
}else L1 = 1;
if(distance_get < temp_setup)
{
L2 = 0;
}else L2 = 1;
if(DA_turn == 1)
{
L3 = 0;
}else L3 = 1;
}4)本试题未涉及的 LED指示灯应处于熄灭状态,不同功能的指示灯状态切换时应互不影响。
1. 8初始状态说明
请严格按照以下要求设计作品的上电初始状态。
1) DAC处于启动状态。
2) 作品上电后。未经任何操作的状态下,数码管处于数据界面下,显示温度
3) 工作参数在每次上电时重置为默认值。
●温度参数: 30℃
●距离参数: 35cm
最后,我们写一个主函数,把核心的函数全部放进去:
void main()
{
InitHC573();
Timer0Init();
UartInit();
GetSetupCount();
Time2Init();
InitHC138(4);
P0 = 0XFF;
while(1)
{
wave_rec();
DisplayDat();
KeyScan();
SetupDatPro();
GetDatPro();
UratPro();
DAPro();
LedPro();
}
}在这里,给大家附上完善过的底层驱动代码以供大家参考:
//=======================================================================//
//==========================温度读取底层驱动============================//
//=====================================================================//
#include "onewire.h"
/****************************单总线延时函数****************************/
void Delay_OneWire(unsigned int t)
{
unsigned char aa;
while(t--)
{
for(aa=0;aa<10;aa++);
}
}
/****************************DS18B20芯片初始化****************************/
bit Init_DS18B20(void)
{
bit initflag = 0;
DQ = 1;
Delay_OneWire(12);
DQ = 0;
Delay_OneWire(80);
DQ = 1;
Delay_OneWire(10);
initflag = DQ;
Delay_OneWire(5);
return initflag;
}
/*******************通过单总线向DS18B20写一个字节****************/
void Write_DS18B20(unsigned char dat)
{
unsigned char i;
for(i=0;i<8;i++)
{
DQ = 0;
DQ = dat&0x01;
Delay_OneWire(5);
DQ = 1;
dat >>= 1;
}
Delay_OneWire(5);
}
/****************************从DS18B20读取一个字节***********************/
unsigned char Read_DS18B20(void)
{
unsigned char i;
unsigned char dat;
for(i=0;i<8;i++)
{
DQ = 0;
dat >>= 1;
DQ = 1;
if(DQ)
{
dat |= 0x80;
}
Delay_OneWire(5);
}
return dat;
}
/****************************温度读取处理函数****************************/
unsigned char Tempget()
{
unsigned char low,high,temp;
Init_DS18B20();
Write_DS18B20(0xcc);
Write_DS18B20(0x44);
Delay_OneWire(20);
Init_DS18B20();
Write_DS18B20(0xcc);
Write_DS18B20(0xbe);
low=Read_DS18B20();
high=Read_DS18B20();
temp=high<<4;
temp|=(low>>4);
return temp;
}
//======================================================================//
//==========================EEPROM底层驱动=============================//
//=====================================================================//
#include <REG52.H>
#include "intrins.h"
#include "iic.h"
#define somenop {_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();}
#define SlaveAddrW 0xA0
#define SlaveAddrR 0xA1
//总线引脚定义
sbit SDA = P2^1; /* 数据线 */
sbit SCL = P2^0; /* 时钟线 */
void DA_write(uchar dat)
{
IIC_Start();
IIC_SendByte(0x90);
IIC_WaitAck();
IIC_SendByte(0x43);
IIC_WaitAck();
IIC_SendByte(dat);
IIC_WaitAck();
IIC_Stop();
}
uchar AD_read(uchar add)
{
uchar temp;
IIC_Start();
IIC_SendByte(0x90);
IIC_WaitAck();
IIC_SendByte(add);
IIC_WaitAck();
IIC_Stop();
IIC_Start();
IIC_SendByte(0x91);
IIC_WaitAck();
temp=IIC_RecByte();
IIC_Stop();
return temp;
}
uchar EEPROM_read(uchar add)
{
uchar temp;
IIC_Start();
IIC_SendByte(0xA0);
IIC_WaitAck();
IIC_SendByte(add);
IIC_WaitAck();
IIC_Stop();
IIC_Start();
IIC_SendByte(0xA1);
IIC_WaitAck();
temp=IIC_RecByte();
IIC_Stop();
return temp;
}
void EEPROM_write(uchar add,uchar dat)
{
IIC_Start();
IIC_SendByte(0xA0);
IIC_WaitAck();
IIC_SendByte(add);
IIC_WaitAck();
IIC_SendByte(dat);
IIC_WaitAck();
IIC_Stop();
}
//总线启动条件
void IIC_Start(void)
{
SDA = 1;
SCL = 1;
somenop;
SDA = 0;
somenop;
SCL = 0;
}
//总线停止条件
void IIC_Stop(void)
{
SDA = 0;
SCL = 1;
somenop;
SDA = 1;
}
//等待应答
bit IIC_WaitAck(void)
{
SDA = 1;
somenop;
SCL = 1;
somenop;
if(SDA)
{
SCL = 0;
IIC_Stop();
return 0;
}
else
{
SCL = 0;
return 1;
}
}
//通过I2C总线发送数据
void IIC_SendByte(unsigned char byt)
{
unsigned char i;
for(i=0;i<8;i++)
{
if(byt&0x80)
{
SDA = 1;
}
else
{
SDA = 0;
}
somenop;
SCL = 1;
byt <<= 1;
somenop;
SCL = 0;
}
}
//从I2C总线上接收数据
unsigned char IIC_RecByte(void)
{
unsigned char da;
unsigned char i;
for(i=0;i<8;i++)
{
SCL = 1;
somenop;
da <<= 1;
if(SDA)
da |= 0x01;
SCL = 0;
somenop;
}
return da;
}
//======================================================================//
//==========================DS1302时钟底层驱动=========================//
//=====================================================================//
#include "ds1302.h"
//---DS1302写入和读取时分秒的地址命令---//
//---秒分时日月周年 最低位读写位;-------//
unsigned char code READ_RTC_ADDR[7] = {0x81, 0x83, 0x85, 0x87, 0x89, 0x8b, 0x8d};
unsigned char code WRITE_RTC_ADDR[7] = {0x80, 0x82, 0x84, 0x86, 0x88, 0x8a, 0x8c};
//---DS1302时钟初始化2016年5月7日星期六23点59分50秒。---//
//---存储顺序是秒分时日月周年,存储格式是用BCD码---//
unsigned char TIME[7] = {50, 59, 23, 0x07, 0x05, 0x06, 0x16};
//从DS1302寄存器读出数据
unsigned char read(unsigned char add)
{
unsigned char i,temp;
unsigned char dat1,dat2;
CE=0;
SCLK=0;
CE=1;
writebyte(add);
for(i=0;i<8;i++)
{
SCLK=0;
temp>>=1;
if(IO)
{
temp|=0x80;
}
SCLK=1;
}
IO=0;
dat1=temp/16;//0XFF 1111 1111
dat2=temp%16;
temp=dat1*10+dat2;
return temp;
}
//写字节
void writebyte(unsigned char dat)
{
unsigned char i;
for(i=0;i<8;i++)
{
SCLK=0;
IO=dat&0x01;
SCLK=1;
dat>>=1;
}
}
//向DS1302寄存器写入数据
void write(unsigned char add,unsigned char dat)
{
unsigned char num;
CE=0;
SCLK=0;
CE=1;
writebyte(add);
num=(dat/10<<4)|(dat%10);// 55
writebyte(num);
CE=0;
}
void Init_DS1302()
{
unsigned char i;
write(0x8e,0x00);
for(i=0;i<7;i++)
{
write(WRITE_RTC_ADDR[i],TIME[i]);
}
write(0x8e,0x80);
}
void readtime()
{
unsigned char i;
write(0x8e,0x00);
for(i=0;i<7;i++)
{
TIME[i]=read(READ_RTC_ADDR[i]);
}
write(0x8e,0x80);
}
我要赚赏金
