第十届省赛真题代码部分
前言
本文是对蓝桥杯第十届省赛真题的代码题做的解题笔记,只记录了代码的思路,大模板用的是B站西风老师的2024年版大模板,具体内容就不再重复记录了。
思路参考西风第十五讲内容(资料链接已经在下方给出)
https://www.bilibili.com/video/BV1TR4y1k7iz?p=23\&vd_source=e2191f89c557f5ac44bb6c7aa3967c7c
关于蓝桥杯第十届省赛真题可以在官网查看(资料链接已经在下方给出)
https://www.lanqiao.cn/courses/2786/learning/?id=100643\&compatibility=false
赛题代码思路笔记
竞赛板配置
根据赛题要求完成竞赛板的配置
内部振荡器频率设定
在stc中更改输入用户程序运行时的IRC频率为12MHz
键盘工作模式跳线
配置为BTN按键模式
扩展方式跳线
配置为IO模式
连接频率测量功能的跳线帽
建立模板
根据赛题中的硬件框图确定本赛题的框架
如图,在Driver文件夹里建立LED、数码管、按键、iic、onewire模块,在User文件夹中建立主函数模块(大模板参考西风老师的2024版大模板)。
明确初始状态
![](https://i-blog.csdnimg.cn/direct/79920ead63ef4b95a2b8684c44b96d26.png)
显示功能部分
由题可知,数码管显示一共两个界面。
可以引入一个变量Seg_Disp_Mode来标记当前显示界面,因为只有两个界面,所以可以定义为bit型而不是unsigned char型以此来节省空间。
c
bit Seg_Disp_Mode; //数码管显示模式变量 0-频率显示界面 1-电压显示界面
再在信息处理函数的数码管部分中对当前显示模式进行判断。
判断完成后在各部分写入供该界面显示的代码。
c
if(Seg_Disp_Mode == 0)//频率显示界面
{
}
else//电压显示界面
{
}
频率显示界面
![](https://i-blog.csdnimg.cn/direct/af83f6e7c9084c9e8ec81c5fbd9376c0.png)
由题目可知,数据界面由三部分组成:
- 第一部分为提示符部分,是数码管的第一位,在该界面下全程显示字母F。
- 第二部分为熄灭部分,是数码管的第2到4位,在该界面下全程熄灭。
- 第三部分为频率显示部分,是数码管的第5到8位,在该界面下根据实时监测的频率不同显示数字实时变动。
在Driver文件夹下的Seg模块中的显示数组中加入用于表示熄灭的0xff,表示字母,表示字母C的0xc6,表示字母P的0x8c。
c
code unsigned char seg_dula[] = {0xc0, 0xf9, 0xa4, 0xb0, 0x99, 0x92, 0x82, 0xf8, 0x80, 0x90, 0xff, 0x8e,0xc1};//0-9,10-熄灭,11-F,12-U
更改主模块中对应数码管控制列表中的值来控制数码管的显示情况。
第一部分
标识部分对应的数码管为第一位,更改主模块中对应数码管控制列表Seg_Buf中第一位对应的数,使其对应上Seg模块中的显示数组中的字母F。
c
Seg_Buf[0] = 11;//F
第二部分
熄灭部分,是数码管的第2到4位,全程为熄灭状态,在创建主函数中的Seg_Buf列表时其中的元素已经置为对应Seg模块中的显示数组中的熄灭,所以不要再做额外的更改。
第三部分
关于实时频率部分,模板已经给出,不再赘述。
在数码管中显示,只需要对实时频率数据进行取余和取整操作即可。
c
Seg_Buf[3] = Freq / 10000 % 10;
Seg_Buf[4] = Freq / 1000 % 10;
Seg_Buf[5] = Freq / 100 % 10;
Seg_Buf[6] = Freq / 10 % 10;
Seg_Buf[7] = Freq % 10;
![](https://i-blog.csdnimg.cn/direct/37450f24274f4c1aa600d37baf504084.png)
除此以外,仍需要注意的是,显示数据高位为0时,做熄灭处理。
设置一个循环,对频率显示部分进行逐位获取,该位为0是熄灭处理,直到获取到第一个非零数据时,不再熄灭。
按照上面的思路,当获取的频率值为0时,数码管全部熄灭,不行。要让频率部分最小也能显示一位,那就直接跳过最后一位,只获取前7位。
c
unsigned char i=3;
while(Seg_Buf[i] == 0)
{
Seg_Buf[i] = 10;
if(++i ==7) break;
}
电压显示界面
由题目可知,参数设置界面由三部分组成:
- 第一部分为标识部分,是数码管的第1位,在该界面下全程显示字母U。
- 第二部分为熄灭部分,是数码管的第2到5位,在该界面下全程熄灭。
- 第三部分为电压显示部分,是数码管的第6到8位。
第一部分
标识部分对应的数码管为第一位,更改主模块中对应数码管控制列表Seg_Buf中第一位对应的数,使其对应上Seg模块中的显示数组中的字母P。
c
Seg_Buf[0] = 12;//U
第二部分
在上一个界面中第4到8位数码管点亮,在此界面中,第6到8位数码管点亮。为了避免界面切换时第4位和第5位数码管仍处于点亮状态,可以直接对第4位和第5位数码管进行操作。
c
Seg_Buf[3] = 10;
Seg_Buf[4] = 10;
第三部分
建立一个变量Voltage存储获取的实时电压数据。
c
float Voltage; //实时电压数据
电压值为模拟量,显示值为数字量,调用AD转换相关函数读取对应位置的电压值。
需要注意的是,AD读取出来的是以255为长度量化的电压值,所以需要进行转换。将0-255的范围转算成0-5的范围,需要除以51。
c
Voltage = Ad_Read(0x43) / 51.0;
显示数据是小数点前有一位数的两位小数。
实时电压数据存储在float型的Voltage中,小数前只有一位,将Voltage强制类型转换成unsigned char型可以直接取出。
unsigned char型的Voltage小数点后的数直接被抹去,要取出小数点后的数,可以将float型的Voltage转换成unsigned int型,再进行乘除的运算依次取出需要用的位置的数。
c
Seg_Buf[5] = (unsigned char)Voltage;
Seg_Buf[6] = (unsigned int)(Voltage * 100) /10 % 10;
Seg_Buf[7] = (unsigned int)(Voltage * 100)% 10;
小数点在Seg_Point对应位置为1时点亮,Seg_Disp_Mode在1时要求小数点点亮,所以可以直接用Seg_Disp_Mode给Seg_Point对应位置赋值。
c
Seg_Point[5] = Seg_Disp_Mode;
按键功能部分
![](https://i-blog.csdnimg.cn/direct/3e8674ef79c14dacb7e765da7f48a8cf.png)
在大模板主模块的按键处理函数中,用Key_Down判断按键按下的值。因为题目中按键涉及到4、5、6、7,所以可以添加一个switch对当前按下的按键进行一个判断。
判断完成后在各部分写入供该按键需要实现的功能的代码即可。
c
void Key_Proc()
{
if (Key_Flag)return;
Key_Flag = 1; // 设置标志位,防止重复进入
Key_Val = Key_Read(); // 读取按键值
Key_Down = Key_Val & (Key_Old ^ Key_Val); // 检测下降沿
Key_Up = ~Key_Val & (Key_Old ^ Key_Val); // 检测上升沿
Key_Old = Key_Val; // 更新按键状态
switch(Key_Down)
{
case 4:
break;
case 5:
break;
case 6:
break;
case 7:
break;
}
S4:"显示界面切换"按键
![](https://i-blog.csdnimg.cn/direct/e1f5dc7c808541a091e5bece9946b664.png)
该按键按下后实现了界面的切换。
在显示功能部分,我们已经定义了一个变量Seg_Disp_Mode来标记当前显示的界面,当Seg_Disp_Mode为0时是数据界面,为1时是参数设置界面。
因为变量Seg_Disp_Mode是bit型数据,只在0和1之间变换,所以可以直接用异或来运算。
c
Seg_Disp_Mode ^= 1;
S5:"输出模式切换"按键
![](https://i-blog.csdnimg.cn/direct/6d9f2d34c5a5431baac4607e71889c5d.png)
该按键按下后实现了输出模式的切换。
由题可知,输出模式只有两个,可以定义一个bit型的变量来标记当前输出模式。
c
bit OutPut_Mode; //输出模式 0-固定电压 1-变化电压
和S4的Seg_Disp_Mode的变换类似,按下按键后OutPut_Mode只在0和1之间变换,可以直接用异或来运算。
c
OutPut_Mode ^= 1;
再定义一个float型变量存储输出电压值。
c
float Voltage_Output; //输出电压
S6:"LED指示灯功能控制"按键
![](https://i-blog.csdnimg.cn/direct/ac6b696559c64776aabea1d162aa46f2.png)
该按键按下后实现了LED指示灯的开关。
只有可和关两个功能,可以定义一个bit型的变量Led_Enable_Flag来标记当前状态。
c
bit Led_Enable_Flag = 1; //指示灯功能控制标志 1-点亮 0-熄灭
按键按下后Led_Enable_Flag在0和1之间切换,可以直接用异或运算。
c
Led_Enable_Flag ^= 1;
在调用LED显示相关的函数时,添加前置条件。
c
if(Led_Enable_Flag == 1)
Led_Disp(Slow_Down % 8, ucLed[Slow_Down % 8]); // 更新LED显示
else
Led_Disp(Slow_Down % 8,0);
S7:"数码管显示功能控制"按键
![](https://i-blog.csdnimg.cn/direct/82de44daa3234bc28621c2525ab5b240.png)
S7与S6类似,不再赘述。
c
bit Seg_Enable_Flag = 1; //数码管功能控制标志 1-点亮 0-熄灭
c
Seg_Enable_Flag ^= 1;
c
if(Seg_Enable_Flag == 1)
Seg_Disp(Slow_Down % 8, Seg_Buf[Slow_Down % 8], Seg_Point[Slow_Down % 8]); // 更新数码管显示
else
Seg_Disp(Slow_Down % 8, 10, 0);
LED指示灯功能部分
![](https://i-blog.csdnimg.cn/direct/fbe0c0fdd5ca442b948eaeb79d5cabd7.png)
L1和L2
![](https://i-blog.csdnimg.cn/direct/b4672fdc2afb4a38a6e5b73cf7c241c3.png)
L1和L2的点亮状态和当前处于电压测试还是频率测试相关。在显示功能部分,我们已经给这两个显示界面一个变量Seg_Disp_Mode来标记(Seg_Disp_Mode为0是频率显示界面,为1时是电压显示界面)。
所以,当Seg_Disp_Mode为0是L1熄灭,L2点亮;Seg_Disp_Mode为1是L1点亮,L2熄灭。
且L1和L2互斥,不存在两个同时点亮的情况。
可以建立一个循环,对Led显示数据存放数组ucLed中的0位(L1)和1位(L2)进行扫描。
由题目得,当Seg_Disp_Mode为0时,要求ucLed[0]为0,ucLed[1]为1;当Seg_Disp_Mode为1时,要求ucLed[0]为1,ucLed[1]为0。可知,Seg_Disp_Mode和ucLed(在0-1的范围内)索引值相等时,置0;反之置为1。
根据上述建立条件判断和赋值语句。
c
unsigned char i;
for(i=0;i<2;i++) ucLed[i] = (i != Seg_Disp_Mode);
L3
![](https://i-blog.csdnimg.cn/direct/de4cfe12925b4e0a98bcd90e3b7527d2.png)
L3点亮(ucLed[2]为1)的条件只有两个,即实时电压值(Voltage) 大于等于1.5且小于等于2.5 或 大于等于3.5 时。
根据条件写出条件判断语句((Voltage >= 1.5 && Voltage < 2.5) || (Voltage >= 3.5)),当逻辑为真时,给ucLed[2]赋值为1,点亮L3。
c
ucLed[2] = ((Voltage >= 1.5 && Voltage < 2.5) || (Voltage >= 3.5));
L4
![](https://i-blog.csdnimg.cn/direct/f9e7a2d0109c4d94ada608b49e1f9921.png)
与L3类似,不再赘述。
c
ucLed[3] = ((Freq >= 1000 && Freq < 5000) || (Freq >= 10000));
L5
![](https://i-blog.csdnimg.cn/direct/f461141b6e9349e2bab8ccbbd9683765.png)
L5与输出模式(OutPut_Mode)相关。
当输出固定电压(OutPut_Mode为0)时,L5熄灭(ucLed[4]为0);当输出实时电压(OutPut_Mode为1)时,L5点亮(ucLed[4]为1)。
c
ucLed[4] = OutPut_Mode;
最终代码
User文件
main.c
c
/*头文件声明区域*/
#include <STC15F2K60S2.H>
#include "Init.h"
#include "Key.h"
#include "Seg.h"
#include "Led.h"
#include "iic.h"
/*变量声明区域*/
unsigned char Key_Val,Key_Down,Key_Up,Key_Old;
unsigned char Seg_Buf[8] = {10, 10, 10, 10, 10, 10, 10, 10}; // 数码管显示数据
unsigned char Seg_Point[8] = {0, 0, 0, 0, 0, 0, 0, 0}; // 数码管小数点数据
unsigned char Seg_Pos; // 数码管扫描位置
unsigned char ucLed[8] = {0, 0, 0, 0, 0, 0, 0, 0}; // LED显示数据
unsigned int Slow_Down; // 减速计数器
bit Seg_Flag, Key_Flag; // 数码管和按键的标志位
unsigned int Time_1s; // 1秒钟计数器
unsigned int Freq; // 频率计算变量
bit Seg_Disp_Mode; //数码管显示模式变量 0-频率显示界面 1-电压显示界面
float Voltage; //实时电压数据
bit OutPut_Mode; //输出模式 0-固定电压 1-变化电压
float Voltage_Output; //输出电压
bit Seg_Enable_Flag = 1; //数码管功能控制标志 1-点亮 0-熄灭
bit Led_Enable_Flag = 1; //指示灯功能控制标志 1-点亮 0-熄灭
/*按键处理函数*/
void Key_Proc()
{
if (Key_Flag)return;
Key_Flag = 1; // 设置标志位,防止重复进入
Key_Val = Key_Read(); // 读取按键值
Key_Down = Key_Val & (Key_Old ^ Key_Val); // 检测下降沿
Key_Up = ~Key_Val & (Key_Old ^ Key_Val); // 检测上升沿
Key_Old = Key_Val; // 更新按键状态
switch(Key_Down)
{
case 4:
Seg_Disp_Mode ^= 1;
break;
case 5:
OutPut_Mode ^= 1;
break;
case 6:
Led_Enable_Flag ^= 1;
break;
case 7:
Seg_Enable_Flag ^= 1;
break;
}
}
/* 信息处理函数 */
void Seg_Proc()
{
unsigned char i=3;
if (Seg_Flag)return;
Seg_Flag = 1; // 设置标志位
Voltage = Ad_Read(0x43) / 51.0;
if(OutPut_Mode == 0) Voltage_Output = 2;
else Voltage_Output = Voltage;
Seg_Point[5] = Seg_Disp_Mode;
if(Seg_Disp_Mode == 0)//频率显示界面
{
Seg_Buf[0] = 11;//F
Seg_Buf[3] = Freq / 10000 % 10;
Seg_Buf[4] = Freq / 1000 % 10;
Seg_Buf[5] = Freq / 100 % 10;
Seg_Buf[6] = Freq / 10 % 10;
Seg_Buf[7] = Freq % 10;
while(Seg_Buf[i] == 0)
{
Seg_Buf[i] = 10;
if(++i ==7) break;
}
}
else//电压显示界面
{
Seg_Buf[0] = 12;//U
Seg_Buf[3] = 10;
Seg_Buf[4] = 10;
Seg_Buf[5] = (unsigned char)Voltage;
Seg_Buf[6] = (unsigned int)(Voltage * 100) /10 % 10;
Seg_Buf[7] = (unsigned int)(Voltage * 100)% 10;
}
}
/* 其他显示函数 */
void Led_Proc()
{
unsigned char i;
/*DA转换*/
Da_Write(Voltage_Output * 51);
/*LED相关*/
for(i=0;i<2;i++) ucLed[i] = (i != Seg_Disp_Mode);
ucLed[2] = ((Voltage >= 1.5 && Voltage < 2.5) || (Voltage >= 3.5));
ucLed[3] = ((Freq >= 1000 && Freq < 5000) || (Freq >= 10000));
ucLed[4] = OutPut_Mode;
}
/* 定时器0初始化函数 */
void Timer0_Init(void)
{
AUXR &= 0x7F; // 设置定时器时钟12T模式
TMOD &= 0xF0; // 设置定时器模式为16位定时器
TMOD |= 0x05;
TL0 = 0; // 设置定时器初始值
TH0 = 0; // 设置定时器初始值
TF0 = 0; // 清除TF0标志位
TR0 = 1; // 启动定时器
}
/* 定时器1初始化函数 */
void Timer1_Init(void)
{
AUXR &= 0xBF; // 设置定时器时钟12T模式
TMOD &= 0x0F; // 设置定时器模式为16位定时器
TL1 = 0x18; // 设置定时器初始值
TH1 = 0xFC; // 设置定时器初始值
TF1 = 0; // 清除TF1标志位
TR1 = 1; // 启动定时器
ET1 = 1; // 使能定时器1中断
EA = 1; // 开启全局中断
}
/* 定时器1中断服务函数 */
void Timer1_Isr(void) interrupt 3
{
/*数码管400ms的减速*/
if (++Slow_Down == 400)
{
Seg_Flag = Slow_Down = 0; // 更新数码管显示标志位
}
/*按键10ms的减速*/
if (Slow_Down % 10 == 0)
{
Key_Flag = 0; // 更新按键处理标志位
}
/*1s的计数*/
if (++Time_1s == 1000)
{
Time_1s = 0; // 重置1秒钟计数器
Freq = TH0 << 8 | TL0; // 计算频率
TH0 = 0; // 重置定时器0的值
TL0 = 0;
}
/*信息处理*/
if(Seg_Enable_Flag == 1)
Seg_Disp(Slow_Down % 8, Seg_Buf[Slow_Down % 8], Seg_Point[Slow_Down % 8]); // 更新数码管显示
else
Seg_Disp(Slow_Down % 8, 10, 0);
if(Led_Enable_Flag == 1)
Led_Disp(Slow_Down % 8, ucLed[Slow_Down % 8]); // 更新LED显示
else
Led_Disp(Slow_Down % 8,0);
}
/* 主函数 */
// 系统初始化,设置定时器和串口,然后进入主循环。
void main()
{
Sys_Init(); // 系统初始化
Timer1_Init(); // 初始化定时器1
Timer0_Init();
while (1)
{
Key_Proc(); // 处理按键
Seg_Proc(); // 更新数码管显示
Led_Proc(); // 更新LED显示
}
}
Driver文件
Init.c
c
#include "init.h"
void Sys_Init()
{
P0 = 0xff;
P2 = P2 & 0x1f | 0x80;
P2 &= 0x1f;
P0 = 0x00;
P2 = P2 & 0x1f | 0xA0;
P2 &= 0x1f;
}
LED.c
c
#include "LED.h"
void LED_Disp(unsigned char addr,enable)
{
static unsigned char temp = 0x00;
static unsigned char temp_old = 0xff;
if(enable) temp |= 0x01 << addr;
else temp &= ~(0x01 << addr);
if(temp != temp_old)
{
P0 = ~temp;
P2 = P2 & 0x1f | 0x80;
P2 &= 0x1f;
temp_old = temp;
}
}
Seg.c
c
#include "Seg.h"
code unsigned char seg_dula[] = {0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90,0xff,0x88,0xc6,0x8c};//0-9为数字,10为熄灭,11为A,12为C,13为P
code unsigned char seg_wela[] = {0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80};
void Seg_Disp(unsigned char wela,dula,point)
{
P0 = 0xff;
P2 = P2 & 0x1f | 0xe0;
P2 &= 0x1f;
P0 = seg_wela[wela];
P2 = P2 & 0x1f | 0xc0;
P2 &= 0x1f;
P0 = seg_dula[dula];
if(point) P0 &= 0x7f;
P2 = P2 & 0x1f | 0xe0;
P2 &= 0x1f;
}
Key.c
c
#include "Key.h"
unsigned char Key_Read()
{
unsigned char temp = 0;
P44 = 0;P42 = 1;P35 = 1;P34 = 1;
if(P30 == 0) temp = 7;
if(P31 == 0) temp = 6;
if(P32 == 0) temp = 5;
if(P33 == 0) temp = 4;
P44 = 1;P42 = 0;P35 = 1;P34 = 1;
if(P30 == 0) temp = 11;
if(P31 == 0) temp = 10;
if(P32 == 0) temp = 9;
if(P33 == 0) temp = 8;
P44 = 1;P42 = 1;P35 = 0;P34 = 1;
if(P30 == 0) temp = 15;
if(P31 == 0) temp = 14;
if(P32 == 0) temp = 13;
if(P33 == 0) temp = 12;
P44 = 1;P42 = 1;P35 = 1;P34 = 0;
if(P30 == 0) temp = 19;
if(P31 == 0) temp = 18;
if(P32 == 0) temp = 17;
if(P33 == 0) temp = 16;
return temp;
}
iic.c
c
#include "iic.h"
#include "intrins.h"
#define DELAY_TIME 10
sbit scl = P2 ^ 0;
sbit sda = P2 ^ 1;
//
static void I2C_Delay(unsigned char n)
{
do
{
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
} while (n--);
}
//
void I2CStart(void)
{
sda = 1;
scl = 1;
I2C_Delay(DELAY_TIME);
sda = 0;
I2C_Delay(DELAY_TIME);
scl = 0;
}
//
void I2CStop(void)
{
sda = 0;
scl = 1;
I2C_Delay(DELAY_TIME);
sda = 1;
I2C_Delay(DELAY_TIME);
}
//
void I2CSendByte(unsigned char byt)
{
unsigned char i;
for (i = 0; i < 8; i++)
{
scl = 0;
I2C_Delay(DELAY_TIME);
if (byt & 0x80)
{
sda = 1;
}
else
{
sda = 0;
}
I2C_Delay(DELAY_TIME);
scl = 1;
byt <<= 1;
I2C_Delay(DELAY_TIME);
}
scl = 0;
}
//
unsigned char I2CReceiveByte(void)
{
unsigned char da;
unsigned char i;
for (i = 0; i < 8; i++)
{
scl = 1;
I2C_Delay(DELAY_TIME);
da <<= 1;
if (sda)
da |= 0x01;
scl = 0;
I2C_Delay(DELAY_TIME);
}
return da;
}
//
unsigned char I2CWaitAck(void)
{
unsigned char ackbit;
scl = 1;
I2C_Delay(DELAY_TIME);
ackbit = sda;
scl = 0;
I2C_Delay(DELAY_TIME);
return ackbit;
}
//
void I2CSendAck(unsigned char ackbit)
{
scl = 0;
sda = ackbit;
I2C_Delay(DELAY_TIME);
scl = 1;
I2C_Delay(DELAY_TIME);
scl = 0;
sda = 1;
I2C_Delay(DELAY_TIME);
}
unsigned char Ad_Read(unsigned char addr)
{
unsigned char temp;
I2CStart();
I2CSendByte(0x90);
I2CWaitAck();
I2CSendByte(addr);
I2CWaitAck();
I2CStart();
I2CSendByte(0x91);
I2CWaitAck();
temp = I2CReceiveByte();
I2CSendAck(1);
I2CStop();
return temp;
}
void Da_Write(unsigned char dat)
{
I2CStart();
I2CSendByte(0x90);
I2CWaitAck();
I2CSendByte(0x41);
I2CWaitAck();
I2CSendByte(dat);
I2CWaitAck();
I2CStop();
}