前言
单片机专栏断更了几天,直到看到省赛还有41天才想起来该刷题了,在4T平台随便挑了一套模拟题,做了大概一个小时左右才做完,几天没刷题就可以明显的感觉到做题的效率变低了,阅读能力也有点下降(可能是熬夜打游戏导致的),比如在旋钮模式下,按下S8后通过pcf8591芯片的通道3的电压转换后的数值控制参数,题目的意思是按下S8后旋转旋钮可以直接控制参数,我理解成了每按下S8读取一次电压参数。。。导致第一次提交只有60多分,第二次提交才正确。所以,越接近比赛越不能放松,要多刷题保持手感。
附件:蓝桥杯单片机组第十五届4T模拟赛三
一、分析题目
完整看一遍题目,可以得到以下信息:
- 数码管显示中出现了
R
,P
,E
,M
,-
,需自行添加这些符号的段码表。 - 数码管显示页面有三个页面,可以定义
unsigned char
型变量SegMode
来控制页面流转。 - 在参数控制界面下有两种方式可以控制参数,记得分类讨论。
分析好后,先从main
函数入手,写完main
函数的初始模板后再写Led,Key,Seg,Wave,pcf8591
模块的底层代码,上面这些模块的搭建在蓝桥杯模块专栏已经说明了,点击传送门快速传送:
传送门:蓝桥杯单片机模板搭建
二、数码管模块
1.测距页面
当SegMode的值为0时,数码管显示测距界面。
由于测距界面需要使用超声波模块,超声波模块可以使用定时器或者PCA,在这边使用PCA去实现超声波。
(1)超声波底层代码
Wave.h
#ifndef __Wave_H__
#define __Wave_H__
unsigned char Wave();
#endif
Wave.c
#include <STC15F2K60S2.H>
#include <intrins.h>
sbit Tx = P1^0;
sbit Rx = P1^1;
void Delay12us(void) //@12.000MHz
{
unsigned char data i;
_nop_();
_nop_();
i = 33;
while (--i);
}
void WaveInit()
{
unsigned char i;
for(i = 0; i < 8; i++)
{
Tx = 1;
Delay12us();
Tx = 0;
Delay12us();
}
}
unsigned char Wave()
{
unsigned int time;
CMOD = 0x00;
CH = CL = 0;
WaveInit();
CR = 1;
while((Rx == 1) && !CF);
CR = 0;
if(CF)
{
CF = 0;
return 0;
}
else
{
time = (CH << 8) | CL;
return time * 0.017;
}
}
(2)调用超声波模块
方法一:
直接在数码管处理函数中读取超声波测量的距离
unsigned char wave_cm;
void SegProc()
{
wave_cm = Wave();
switch(SegMode)
{
case 0:
SegBuf[0] = 12;//A
SegBuf[1] = 10;
SegBuf[2] = 10;
SegBuf[3] = 10;
SegBuf[4] = 10;
SegBuf[5] = wave_cm / 100;
SegBuf[6] = wave_cm / 10 % 10;
SegBuf[7] = wave_cm % 10;
break;
}
}
方法二:
在main.c函数先定义函数void WaveProc()
void WaveProc()
功能:处理一切和超声波有关的数据
void WaveProc()
{
wave_cm = Wave();
}
void SegProc()
{
switch(SegMode)
{
case 0:
SegBuf[0] = 12;//A
SegBuf[1] = 10;
SegBuf[2] = 10;
SegBuf[3] = 10;
SegBuf[4] = 10;
SegBuf[5] = wave_cm / 100;
SegBuf[6] = wave_cm / 10 % 10;
SegBuf[7] = wave_cm % 10;
break;
}
}
2.参数界面
当SegMode的值为1时,进入参数界面。
下面为数码管参数界面显示需要使用到的参数的定义
/*参数定义区*/
typedef unsigned char u8;
typedef unsigned int u16;
/*u8*/
idata u8 wave_min = 10;//参数下限,初值为10
idata u8 wave_max = 60;//参数上限,初值为60
/*bit*/
idata bit SetMode;//参数模式标志位 0-按键模式 1-旋钮模式
void SegProc()
{
switch(SegMode)
{
case 1:
SegBuf[0] = 13;
SegBuf[1] = !SetMode ? 1 : 2;
SegBuf[2] = 10;
SegBuf[3] = wave_min / 10;
SegBuf[4] = wave_min % 10;
SegBuf[5] = 11;
SegBuf[6] = wave_max / 10;
SegBuf[7] = wave_max % 10;
break;
}
}
3.距离界面
当SegMode的值为2时,进入距离界面。
先不考虑什么情况下报警次数会加,只考虑显示。
报警次数小于10时正常显示,大于等于10时显示-,使用三目运算符做最简单。
idata unsigned char warn;//报警次数
void SegProc()
{
switch(SegMode)
{
case 2:
SegBuf[0] = 14;
SegBuf[1] = 10;
SegBuf[2] = 10;
SegBuf[3] = 10;
SegBuf[4] = 10;
SegBuf[5] = 10;
SegBuf[6] = 10;
SegBuf[7] = (warn > 9) ? 11 : warn;
break;
}
}
4.数码管完整代码
void SegProc()
{
switch(SegMode)
{
case 0:
SegBuf[0] = 12;
SegBuf[1] = 10;
SegBuf[2] = 10;
SegBuf[3] = 10;
SegBuf[4] = 10;
SegBuf[5] = wave_cm / 100;
SegBuf[6] = wave_cm / 10 % 10;
SegBuf[7] = wave_cm % 10;
break;
case 1:
SegBuf[0] = 13;
SegBuf[1] = !SetMode ? 1 : 2;
SegBuf[2] = 10;
SegBuf[3] = wave_min / 10;
SegBuf[4] = wave_min % 10;
SegBuf[5] = 11;
SegBuf[6] = wave_max / 10;
SegBuf[7] = wave_max % 10;
break;
case 2:
SegBuf[0] = 14;
SegBuf[1] = 10;
SegBuf[2] = 10;
SegBuf[3] = 10;
SegBuf[4] = 10;
SegBuf[5] = 10;
SegBuf[6] = 10;
SegBuf[7] = (warn > 9) ? 11 : warn;
break;
}
}
三、按键模块
1.S4按键
S4的控制页面流转是老朋友了,直接无脑敲就行了。
typedef unsigned char u8;
typedef unsigned int u16;
/* 参数定义区 */
idata u8 KeyVal, KeyDown, KeyUp, KeyOld;
idata u8 SegMode;
void KeyProc()
{
KeyVal = KeyDisp();
KeyDown = KeyVal & ~KeyOld;
KeyUp = ~KeyVal & KeyOld;
KeyOld = KeyVal;
switch(KeyDown)
{
case 4:
if(++SegMode == 3)
SegMode = 0;
break;
}
}
2.S5按键
S5按键有两种作用
- 参数界面:进行参数模式切换
- 记录界面:清零报警值
switch(KeyDown)
{
case 5:
if(SegMode == 1)
SetMode ^= 1;
else if(SegMode == 2)
warn = 0;
break;
}
3.S8、S9
先实现按键模式,按钮模式等全部写完再实现。
/*参数定义区*/
typedef unsigned char u8;
typedef unsigned int u16;
/*u8*/
idata u8 wave_min = 10;//参数下限,初值为10
idata u8 wave_max = 60;//参数上限,初值为60
/*bit*/
idata bit SetMode;//参数模式标志位 0-按键模式 1-旋钮模式
按键模式对参数的设置只有每次按下S8、S9数值会加10,下限值加到50,再按下回到最小值0,上限值加到90,再按下回到最小值50.
switch(KeyDown)
{
case 9:
if(SegMode == 1)//按键模式
{
if(!SetMode)
{
wave_max += 10;
if(wave_max == 100)
wave_max = 50;
}
}
break;
case 8:
if(SegMode == 1)
{
if(!SetMode)//按键模式
{
wave_min += 10;
if(wave_min == 50)
wave_min = 0;
}
}
break;
}
由于按键模块还需要修改(添加旋钮模式),完整代码在后面会给出。
四、报警次数
上文在写数码管的时候,只实现了报警次数的显示,报警次数是怎么加的还没有实现。
也就是说,只有前一次的距离值处于参数下限值和参数上限值之间,下一次的距离值小于参数下限值或者大于参数上限值,报警次数才会增加,而且只有当距离值再次回到参数下限值和参数上限值之间,并且下一次距离值小于参数下限值或者大于参数上限值,报警次数才会再次增加。
可以直接在WaveProc
函数中判断
idata bit WarnFlag;
void WaveProc()
{
wave_cm = Wave();
//第一次测量数据处于二者之间,计数标志位激活
if(wave_cm >= wave_min && wave_cm <= wave_max)
WarnFlag = 1;
//如果测量的数据不处于二者之间
else
{
//如果计数标志位激活(第一次测量数据处于二者之间)
if(WarnFlag)
{
warn++;//警告次数增加
WarnFlag = 0;//标志位失效
}
}
}
五、AD、旋钮
接下来实现旋钮模式
阅读两张图,可以得出:
根据电压转换后的上限的值 = 根据电压转换后的下限的值 + 50
而且根据电压转换后的下限值和电压的对应关系是:
RB2 = AD() / 51 * 10;
- AD的取值范围为
0~255
,题目中的电压值的取值范围为0~5
,所以要先除以51,然后电压值为0~1
时对应的参数下限是0
,1 ~ 2
对应的参数下限是10
,2 ~ 3
对应的参数下限是20
,所以可以通过将AD转换后的值去整除51再乘以10得到下限值。
例如:- AD值为52,52/51=1, 1 * 10=10
- AD值为151,151/51=2 ,2 * 10 = 20
idata unsigned char ParaMode;//0-失效 1-下限参数激活 2-上限参数激活
void KeyDown()
{
switch(KeyDown)
{
case 9:
if(SegMode == 1)
{
if(!SetMode)
{
wave_max += 10;
if(wave_max == 100)
wave_max = 50;
}
else
ParaMode = 2;//上限参数激活
}
break;
case 8:
if(SegMode == 1)
{
if(!SetMode)
{
wave_min += 10;
if(wave_min == 50)
wave_min = 0;
}
else
ParaMode = 1;//下限参数激活
}
break;
}
}
void ADProc()
{
RB2 = AD() / 51 * 10;
if(RB2 > 40)//防止数据越界
RB2 = 40;
//按钮模式下参数值直接等于AD转换后的值
if(SegMode == 1)//处于参数界面
{
if(ParaMode == 1)//下限参数激活
wave_min = RB2;
else if(ParaMode == 2)//上限参数激活
wave_max = RB2 + 50;
}
}
由于上面的
ADProc
中对上限参数、下限参数的赋值是由ParaMode为1或2决定的,这样子如果切回按键模式,会导致按键模式失灵,因此要在切换模式的时候顺便让上限、下限参数失效。
按键S5需要作出以下改变:
case 5:
if(SegMode == 1)
{
if(SetMode == 1)
{
ParaMode = 0;//上限参数、下限参数均失效
SetMode = 0;
}
else
SetMode = 1;
}
else if(SegMode == 2)
warn = 0;
break;
六、Led灯
Led就考的很简单了,这边不做过多讲解,只考到互斥点灯和Led闪烁而已。
idata unsigned char Time_100ms;//定时100ms
idata LedFlag;//Led闪烁标志位
void LedProc()
{
unsigned char i;
for(i = 0; i < 3; i++)
ucLed[i] = (i == SegMode);
ucLed[7] = (WarnFlag) ? 1 : LedFlash;
LedDisp(ucLed);
}
//定时器中断函数
void Timer1_Isr(void) interrupt 3
{
systick++;
if(++SegPos == 8) SegPos = 0;
SegDisp(SegPos, SegBuf[SegPos], SegPoint[SegPos]);
if(!WarnFlag)
{
if(++Time_100ms == 100)
{
Time_100ms = 0;
LedFlash ^= 1;
}
}
}
七、完整代码
代码1可自行添加退出函数即可实现,代码2可直接使用。
1.本篇文章所使用的伪代码(调度器部分省略)
#include <STC15F2K60S2.H>
#include "Init.h"
#include "Key.h"
#include "Seg.h"
#include "Led.h"
#include "Wave.h"
#include "pcf8591.h"
typedef unsigned char u8;
typedef unsigned int u16;
typedef unsigned long int u32;
idata u8 wave_cm;
idata u8 SegPos;
idata u8 SegMode;
idata u8 wave_max = 60, wave_min = 10;//距离上限默认60,距离下限默认10
idata u8 warn;
idata u8 KeyVal, KeyDown, KeyUp, KeyOld;
idata u8 RB2;
idata u8 Time_100ms;
idata u8 ParaMode;//按钮模式的距离上下限模式
pdata u8 SegBuf[8] = {10,10,10,10,10,10,10,10};
pdata u8 SegPoint[8] = {0,0,0,0,0,0,0,0};
pdata u8 ucLed[8] = {0,0,0,0,0,0,0,0};
idata u32 systick;
idata bit SetMode;//0-按键模式 1-按钮模式
idata bit WarnFlag;
idata bit LedFlash;
void KeyProc()
{
KeyVal = KeyDisp();
KeyDown = KeyVal & ~KeyOld;
KeyUp = ~KeyVal & KeyOld;
KeyOld = KeyVal;
switch(KeyDown)
{
case 4:
if(++SegMode == 3)
SegMode = 0;
break;
case 5:
if(SegMode == 1)
{
if(SetMode == 1)
{
ParaMode = 0;
SetMode = 0;
}
else
SetMode = 1;
}
else if(SegMode == 2)
warn = 0;
break;
case 9:
if(SegMode == 1)
{
if(!SetMode)
{
wave_max += 10;
if(wave_max == 100)
wave_max = 50;
}
else
ParaMode = 2;
}
break;
case 8:
if(SegMode == 1)
{
if(!SetMode)
{
wave_min += 10;
if(wave_min == 50)
wave_min = 0;
}
else
ParaMode = 1;
}
break;
}
}
void SegProc()
{
switch(SegMode)
{
case 0:
SegBuf[0] = 12;
SegBuf[1] = 10;
SegBuf[2] = 10;
SegBuf[3] = 10;
SegBuf[4] = 10;
SegBuf[5] = wave_cm / 100;
SegBuf[6] = wave_cm / 10 % 10;
SegBuf[7] = wave_cm % 10;
break;
case 1:
SegBuf[0] = 13;
SegBuf[1] = !SetMode ? 1 : 2;
SegBuf[2] = 10;
SegBuf[3] = wave_min / 10;
SegBuf[4] = wave_min % 10;
SegBuf[5] = 11;
SegBuf[6] = wave_max / 10;
SegBuf[7] = wave_max % 10;
break;
case 2:
SegBuf[0] = 14;
SegBuf[1] = 10;
SegBuf[2] = 10;
SegBuf[3] = 10;
SegBuf[4] = 10;
SegBuf[5] = 10;
SegBuf[6] = 10;
SegBuf[7] = (warn > 9) ? 11 : warn;
break;
}
}
void LedProc()
{
unsigned char i;
for(i = 0; i < 3; i++)
ucLed[i] = (i == SegMode);
ucLed[7] = (WarnFlag) ? 1 : LedFlash;
LedDisp(ucLed);
}
void WaveProc()
{
wave_cm = Wave();
if(wave_cm >= wave_min && wave_cm <= wave_max)
WarnFlag = 1;
else
{
if(WarnFlag)
{
warn++;
WarnFlag = 0;
}
}
}
void ADProc()
{
RB2 = AD() / 51 * 10;
if(RB2 > 40)
RB2 = 40;
if(SegMode == 1)
{
if(ParaMode == 1)
wave_min = RB2;
else if(ParaMode == 2)
wave_max = RB2 + 50;
}
}
void Timer1_Init(void) //1毫秒@12.000MHz
{
AUXR &= 0xBF; //定时器时钟12T模式
TMOD &= 0x0F; //设置定时器模式
TL1 = 0x18; //设置定时初始值
TH1 = 0xFC; //设置定时初始值
TF1 = 0; //清除TF1标志
TR1 = 1; //定时器1开始计时
ET1 = 1; //使能定时器1中断
EA = 1;
}
void Timer1_Isr(void) interrupt 3
{
systick++;
if(++SegPos == 8) SegPos = 0;
SegDisp(SegPos, SegBuf[SegPos], SegPoint[SegPos]);
if(!WarnFlag)
{
if(++Time_100ms == 100)
{
Time_100ms = 0;
LedFlash ^= 1;
}
}
}
void main()
{
SystemInit();
Timer1_Init();
SchedulerInit();//调度器初始化
while(1)
{
SchedulerRun();//调度器运行
}
}
2.完整代码
#include <STC15F2K60S2.H>
#include "Init.h"
#include "LED.h"
#include "Key.h"
#include "Seg.h"
#include "iic.h"
#include "Wave.h"
/* 变量声明区 */
unsigned char Key_Slow; //按键减速变量 10ms
unsigned char Key_Val, Key_Down, Key_Up, Key_Old; //按键检测四件套
unsigned int Seg_Slow; //数码管减速变量 500ms
unsigned char Seg_Buf[] = {10,10,10,10,10,10,10,10,10,10};//数码管缓存数组
unsigned char Seg_Pos;//数码管缓存数组专用索引
unsigned char Seg_Point[8] = {0,0,0,0,0,0,0,0};//数码管小数点使能数组
unsigned char ucLed[8] = {0,0,0,0,0,0,0,0};//LED显示数据存放数组
unsigned char Seg_Mode;
unsigned char s;
bit Change_Mode;//0-按键模式 1-按钮模式
unsigned char Para_Mode;//按钮模式控制上下限
unsigned char S_MAX = 60, S_MIN = 10, S_MAX_OLD, S_MIN_OLD, S_Old;
float AD_RB2;
unsigned char Count;
bit LED8;
unsigned char Time_100ms;
bit Flash;
/* 按键处理函数 */
void Key_Proc()
{
if(Key_Slow) return;
Key_Slow = 1; //按键减速
Key_Val = Key();
Key_Down = Key_Val & ~Key_Old;
Key_Up = ~Key_Val & Key_Old;
Key_Old = Key_Val;
switch(Key_Down)
{
case 4:
if(++Seg_Mode == 3)
Seg_Mode = 0;
if(Seg_Mode == 0)
Change_Mode = 0;
break;
case 5:
if(Seg_Mode == 1)
{
Change_Mode = !Change_Mode;
if(!Change_Mode)
Para_Mode = 0;
}
if(Seg_Mode == 2)
Count = 0;
break;
}
if(Seg_Mode == 1)
{
if(!Change_Mode)
{
if(Key_Down == 9)
{
S_MAX += 10;
if(S_MAX == 100)
S_MAX = 50;
}
if(Key_Down == 8)
{
S_MIN += 10;
if(S_MIN == 50)
S_MIN = 0;
}
}
else
{
if(Key_Down == 9)
Para_Mode = 1;
if(Key_Down == 8)
Para_Mode = 2;
}
}
}
/* 信息处理函数 */
void Seg_Proc()
{
unsigned char i = 5, j;
if(Seg_Slow) return;
Seg_Slow = 1; //数码管减速
s = Wave();
AD_RB2 = AD_Read(0x43) / 51.0;
if(Seg_Mode == 1)
{
if(Para_Mode == 1)
S_MAX = 50 + (unsigned char)AD_RB2 * 10;
if(Para_Mode == 2)
S_MIN = (unsigned char)AD_RB2 * 10;
}
if(S_MIN > s || s > S_MAX)
{
if(S_MAX_OLD != S_MAX || S_MIN_OLD != S_MIN || S_Old != s)
Count++;
S_MAX_OLD = S_MAX;
S_MIN_OLD = S_MIN;
S_Old = s;
LED8 = 1;
}
else
LED8 = 0;
switch(Seg_Mode)
{
case 0:
Seg_Buf[0] = 11;
Seg_Buf[1] = s / 100;
Seg_Buf[6] = s / 10;
Seg_Buf[7] = s % 10;
while(!Seg_Buf[i])
{
Seg_Buf[i] = 10;
i++;
}
break;
case 1:
Seg_Buf[0] = 12;
Seg_Buf[1] = (unsigned char)Change_Mode + 1;
Seg_Buf[3] = S_MIN / 10;
Seg_Buf[4] = S_MIN % 10;
Seg_Buf[5] = 13;
Seg_Buf[6] = S_MAX / 10;
Seg_Buf[7] = S_MAX % 10;
break;
case 2:
Seg_Buf[0] = 14;
for(j = 1; j < 7; j++)
Seg_Buf[j] = 10;
(Count > 9) ? (Seg_Buf[7] =13) : (Seg_Buf[7] = Count);
break;
}
}
/* 其他显示函数 */
void Led_Proc()
{
unsigned char i;
for(i = 0; i < 3; i++)
ucLed[i] = (i == Seg_Mode);
LED8 ? (Flash ? (ucLed[7] = 1) : (ucLed[7] = 0)) : (ucLed[7] = 1);
}
/* 定时器0初始化函数 */
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;
}
/* 定时器0中断服务函数 */
void Timer0_Server() interrupt 1
{
if(++Key_Slow == 10) Key_Slow = 0; //按键延迟
if(++Seg_Slow == 100) Seg_Slow = 0; //数码管延迟
if(++Seg_Pos == 8) Seg_Pos = 0; //数码管显示
Seg_Disp(Seg_Pos,Seg_Buf[Seg_Pos],Seg_Point[Seg_Pos]);
LED_Disp(Seg_Pos,ucLed[Seg_Pos]);
if(++Time_100ms == 100)
{
Time_100ms = 0;
Flash = !Flash;
}
}
void main()
{
Init();
Timer0_Init();
while(1)
{
Key_Proc();
Seg_Proc();
Led_Proc();
}
}