蓝桥杯单片机组第十五届4T模拟赛三(第一套)

发布于:2025-03-05 ⋅ 阅读:(129) ⋅ 点赞:(0)

前言
单片机专栏断更了几天,直到看到省赛还有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时对应的参数下限是01 ~ 2对应的参数下限是102 ~ 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();
	}
}