前言
本文是对蓝桥杯第十二届省赛真题的代码部分做的解题笔记,只记录了代码的思路,大模板用的是B站西风老师的2024年版大模板,具体内容就不再重复记录了。
思路参考西风第十三讲内容(资料链接已经在下方给出)
https://www.bilibili.com/video/BV1TR4y1k7iz?p=22&vd_source=e2191f89c557f5ac44bb6c7aa3967c7c
关于蓝桥杯第十二届省赛真题可以在官网查看(资料链接已经在下方给出)
https://www.lanqiao.cn/courses/2786/learning/?id=264528&compatibility=false
赛题代码思路笔记
竞赛板配置
根据赛题要求完成竞赛板的配置
内部振荡器频率设定
在stc中更改输入用户程序运行时的IRC频率为12MHz
键盘工作模式跳线
配置为KBD键盘模式
扩展方式跳线
配置为IO模式
建立模板
根据赛题中的硬件框图确定本赛题的框架
如图,在Driver文件夹里建立LED、数码管、按键、iic模块(DA输出),onewire模块(DS18B20),在User文件夹中建立主函数模块(大模板参考西风老师的2024版大模板)。
明确初始状态
显示功能部分
由题可知,数码管显示一共由三个界面。
可以引入一个变量来标记当前显示界面。
unsigned char Seg_Disp_Mode;//数码管显示模式变量 0-温度显示界面 1-参数设置界面 2-DAC输出界面
- 1
再在信息处理函数的数码管部分中添加一个switch对显示模式进行一个判断。判断完成后在各部分写入供该界面显示的代码即可完成界面的显示。
void Seg_Proc()
{
/*数码管减速*/
if(Seg_Flag) return;
Seg_Flag = 1;
/*信息获取区域*/
/*数码管显示区域*/
switch(Seg_Disp_Mode)
{
case 0:
break;
case 1:
break;
case 2:
break;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
温度显示界面
下面来完成第一个界面的编写。
由题目可知,温度显示界面由三部分组成:
- 第一部分为标识部分,是数码管的第一位,在该界面下全程显示字母C。
- 第二部分为熄灭部分,是数码管的第2到4位,在该界面下全程熄灭。
- 第三部分为实时温度显示界面,是数码管的第5到8位,在该界面下根据实时监测的温度不同显示数字实时变动。
在Driver文件夹下的Seg模块中的显示数组中加入用于表示熄灭的0xff,表示字母A的0x88,表示字母C的0xc6,表示字母P的0x8c。
//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
- 1
更改主模块中对应数码管控制列表中的值来控制数码管的显示情况。
第一部分
标识部分对应的数码管为第一位,更改主模块中对应数码管控制列表Seg_Buf中第一位对应的数,使其对应上Seg模块中的显示数组中的字母C。
Seg_Buf[0] = 12;//C
- 1
第二部分
熄灭部分,是数码管的第2到4位,全程为熄灭状态,在创建主函数中的Seg_Buf列表时其中的元素已经为置为对应Seg模块中的显示数组中的熄灭,所以不要再做额外的更改。
第三部分
数码管显示的后四位为实时的温度数据,可以引入一个变量Temperature来存储当前获取的温度值。
float Temperature; //实时温度变量
- 1
调用已经写好的温度获取函数来获取实时温度值。
Temperature = rd_temperture();
- 1
实时温度值是小数点前后各两位的小数,数码管的后四位各显示一位数,只要分别取出各位,再加上小数点的点亮就可以完成。
因为Temperature是带有小数点的两位小数,为了方便取用,可以先将其转化为char型的两位数,再用整除10和取余10的形式来取出前两位。
对于小数点后两位的数的获取,可以将Temperature的小数点向由移动两位(即乘100后抹去小数点),再用同样的方法取出后两位。这里需要注意的是,unsigned char型数据最大为255,而乘上100后的Temperature是四位数,无法放入,所以在此可以转化为unsigned int类型。
Seg_Buf[4] = (unsigned char)Temperature / 10 % 10;
Seg_Buf[5] = (unsigned char)Temperature % 10;
Seg_Buf[6] = (unsigned int)(Temperature * 100) / 10 % 10;
Seg_Buf[7] = (unsigned int)(Temperature * 100) % 10;
- 1
- 2
- 3
- 4
小数点的显示在大模板中Seg模块已经编写完成,1为使能,即点亮。只要更改主模块中对应数码管小数点控制列表Seg_Point中对应的位置就行。
Seg_Point[5] = 1;
- 1
调试时发现的问题
在调试中发现,上电时并不是立刻显示实时温度值,而是会先显示85.00,要去掉85.00的干扰,可以在上电后先读取温度覆盖掉默认的85.00,再调用信息处理函数实现显示。
需要注意的是,单独调用温度读取函数时要再加一个750ms的延迟,因为DS18B20实现温度读取一次是750ms,如果不用延迟的话,无法实现完整的读取。
rd_temperture();
Delay750ms();
- 1
- 2
参数设置界面
参数设置界面与温度设置界面类似。
由题目可知,参数设置界面由三部分组成:
- 第一部分为标识部分,是数码管的第1位,在该界面下全程显示字母P。
- 第二部分为熄灭部分,是数码管的第2到6位,在该界面下全程熄灭。
- 第三部分为参数部分,是数码管的第7和8位,默认显示值为25。
第一部分
和温度显示界面类似,不再赘述。
Seg_Buf[0] = 13;//P
- 1
第二部分
这里需要注意的是,初始处于温度显示界面,2到4位为熄灭,5和6位为显示。而参数设置界面中的2到6位都是熄灭的。所以,切换后为了第二部分处于熄灭状态,2到4位仍然不更改,而5到6位要更改为熄灭状态。
还需要注意的一点是,第6位的小数点也要调整为熄灭的状态。
Seg_Buf[4] = 10;
Seg_Buf[5] = 10;
Seg_Point[5] = 0;
- 1
- 2
- 3
第三部分
注意:题目涉及到一个可更改大小的参数十一般需要设置两个变量来标识此参数,一个用于实时的大小更改,一个用于实现控制
为什么要用两个变量,而不用一个?
答:如果只用一个变量,那么该参数再更改时候同时会对它所要控制的原件产生影响,对实际的功能有干扰。例如:当前设置值为24,需要设置的值为26,而蜂鸣器触发条件为25,则当前设置值在更改时会触发蜂鸣器,但是这并不是外部条件触发的,所以是不必要的干扰。
设置两个变量用于温度参数的显示和控制。
unsigned char Temp_Disp = 25;
unsigned char Temp_Ctrol = 25;
- 1
- 2
DAC输出界面
由题目可知,参数设置界面由三部分组成:
- 第一部分为标识部分,是数码管的第1位,在该界面下全程显示字母A。
- 第二部分为熄灭部分,是数码管的第2到5位,在该界面下全程熄灭。
- 第三部分为电压显示部分,是数码管的第6到8位。
该界面和温度显示界面没有太大的不同,不做过多解释。
Seg_Buf[0] = 11;//A
- 1
float V_Output;
- 1
Seg_Buf[5] = (unsigned char)V_Output;
Seg_Buf[6] = (unsigned int)(V_Output*100) / 10 % 10;
Seg_Buf[7] = (unsigned int)(V_Output*100) % 10;
Seg_Point[5] = 1;
- 1
- 2
- 3
- 4
按键功能部分
在大模板主模块的按键处理函数中,用Key_Down判断按键按下的值。因为题目中按键涉及到4、8、9、5,所以可以添加一个switch对当前按下的按键进行一个判断。判断完成后在各部分写入供该按键需要实现的功能的代码即可。
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 8:
break;
case 9:
break;
case 5:
break;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
S4:“界面”按键
该按键按下后实现了界面的切换。
在显示功能部分,我们已经定义了一个变量Seg_Disp_Mode来标记当前显示的界面,当Seg_Disp_Mode为0时是温度显示界面,为1时是参数设置界面,为2时是DAC输出界面。
由所给时序图可知,界面切换可以看作Seg_Disp_Mode在进行0->1->2->0->…的变化。
按下按键一次,Seg_Disp_Mode加1。
用自增运算符,先自增1再经行判断,当自增加后Seg_Disp_Mode为3时,超出界面编号的范围,则将Seg_Disp_Mode重新赋值为0回到第一个界面。
if(++Seg_Disp_Mode == 3) Seg_Disp_Mode = 0;
- 1
当Seg_Disp_Mode为1时,处于参数设置界面。在该界面中,刚完成切换时显示的是当前用于控制相关原件的温度参数的值,即Temp_Ctrol的值,但是Temp_Ctrol在该界面完成温度参数的设置前一直保持不变,不能对Temp_Ctrol经行更改。而要对显示变量Temp_Disp进行更改,且使得刚切换时候显示的是控制值,所以要将控制值赋给显示值。
if(Seg_Disp_Mode == 1) Temp_Disp = Temp_Ctrol;
- 1
当Seg_Disp_Mode为1时,处于DAC输出界面,关于DAC输出显示的部分已经在显示功能部分编写好了,所以这里不需要再对DAC的输出值做处理。
但是,由于题目给出信息,如上图,所以要使温度参数设置界面生效即设置好的温度参数可以是实现对应原件的控制功能。因为温度参数的大小更改是对显示变量Temp_Disp的更改,而控制变量Temp_Ctrol才是对原件的控制,所以,要将更改好的Temp_Disp的值赋给Temp_Ctrol。
if(Seg_Disp_Mode == 2) Temp_Ctrol = Temp_Disp;
- 1
S8:“减”按键
该按键是在参数设置界面下才有用的,所以,要对当前显示的界面先进行判断,当Seg_Disp_Mode为1是,即处于参数设置界面,可以实现减法。
且在该界面下,需要实现温度参数的变化,即对Temp_Disp进行更改。
需要注意的是,Temp_Disp为无符号char型,最大值为255,最小值为0,当Temp_Disp为0时再减1就会变成255,但是255超出了显示范围,且不符合当前情境下的逻辑。所以,当Temp_Disp自减1时,给它重新赋值为0,实现的是,减到0之后不会再变小。
if(Seg_Disp_Mode ==1)
{
if(--Temp_Disp == 255) Temp_Disp = 0;
}
- 1
- 2
- 3
- 4
S9:“加”按键
加法思路和减法类似,不再赘述。
if(Seg_Disp_Mode ==1)
{
if(++Temp_Disp == 100) Temp_Disp = 99;
}
- 1
- 2
- 3
- 4
S5:“模式”切换按键
该按键实现了模式1和模式2的切换,用两种方式实现了实时温度值对电压输出值的控制。定义一个变量V_Output存储电压输出值。
float V_Output;
- 1
因为只有两个模式,所以可以定义一个二进制变量Output_Mode,用0表示模式1,1表示模式2。
bit Output_Mode;
- 1
按键按下实现模式切换,即实现Output_Mode在0和1之间切换。
Output_Mode ^= 1;
- 1
在对模式具体功能经行编写时,先判断当前模式。
if(Output_Mode == 0)
{
}
else
{
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
模式1
实时温度Temperature和温度参数的控制变量Temp_Ctrol进行比较。Temperature小,电压输出值V_Output为0;反之为5。
if(Temperature < Temp_Ctrol) V_Output = 0 ;
else V_Output = 5;
- 1
- 2
模式2
只用上述方式可以实现输出,但是要注意的是题目说的是DAC输出,我们至此都还没用到,所以还要再对DA输出相关的函数Da_Write进行调用才行。 例如:在该题背景下,DAC输出范围为0-5,当前V_Output为5,即DAC要输出最大值5,调用Da_Write函数,直接写入参数V_Output,会得到 由上述可知,要找出0-5和0-255的关系,对V_Output当前值进行放大后再作为参数写入就能解决输出偏小的问题。易得0-5放大51倍为0-255,所以V_Output要乘上51。 不同界面点亮不同的LED可以用互斥点亮。三个界面对应的ucLed中的2位,3位,4位,对应Seg_Disp_Mode的0,1,2。可以用i在0-2间变化的for循环,通过比较i是否与当前的Seg_Disp_Mode相等实现对ucLed中对应位置的更改。 例如:当前为温度显示界面,即Seg_Disp_Mode为0,只有当i为0时, 至此本试题代码部分的笔记就完成了,有错误的地方感谢大家指出。
实时温度Temperature小于等于20时,电压输出值V_Output为1;
实时温度Temperature大于等于40时,电压输出值V_Output为4;
实时温度Temperature在20和40之间时,计算该斜线的函数解析式为
DAC输出 = 0.15*环境温度 - 2 (20if(Temperature <= 20) V_Output = 1;
else if (Temperature >= 40) V_Output = 4;
else V_Output = 0.15*Temperature - 2;
注意
因为V_Output是float型,而Da_Write()括号中的参数为unsigned char型,所以将V_Output以参数的形式写入Da_Write()函数时会进行强制类型转换。但是而unsigned char型最大为255,最小为0,而当前两个模式下写入显示的V_Output最大为5,最小为0。如果直接将V_Output写入的话,DAC输出会偏小。
Da_Write(V_Output)
即Da_Write(5)
。由于Da_Write函数参数unsigned char型最大为255,所以要想Da_Write函数输出最大值应当写成Da_Write(255)
。所以V_Output直接作为参数写入的话,最终得到的输出值会偏小。Da_Write(V_Output * 51);
LED指示灯功能
LED的部分比较简单,实现点亮只要对主模块中的Led显示数据存放数组ucLed中的数进行更改即可。
当前处于模式1时,Output_Mode为0,需要更改ucLed的0位为1,可以实现L1点亮;当前不处于模式1时,Output_Mode为1,需要更改ucLed的0位为0,可以实现L1熄灭。Output_Mode和ucLed中个位的值都只在0和1之间变化,所以可以用逻辑运算符!实现。ucLed[0] = !Output_Mode;
i == Seg_Disp_Mode
才为真,输出1,实现ucLed[1] = 1
的赋值,实现L2点亮。unsigned char i;
for(i=0;i<3;i++) ucLed[1+i] = (i == Seg_Disp_Mode);
最终代码
User文件
main.c
/*头文件声明区*/
#include
Driver文件
Init.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
#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
#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
#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;
}
onewire.c
#include "onewire.h"
sbit DQ = P1 ^ 4;
//
void Delay_OneWire(unsigned int t)
{
unsigned char i;
while(t--){
for(i=0;i<12;i++);
}
}
//
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);
}
//
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;
}
//
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;
}
float rd_temperture(void)
{
unsigned char low,high;
init_ds18b20();
Write_DS18B20(0xcc);
Write_DS18B20(0x44);
init_ds18b20();
Write_DS18B20(0xcc);
Write_DS18B20(0xbe);
low = Read_DS18B20();
high = Read_DS18B20();
return ((high << 8) | low) / 16.0;
}
iic.c
#define DELAY_TIME 10
#include "iic.h"
#include "intrins.h"
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);
}
void Da_Write(unsigned char dat)
{
I2CStart();
I2CSendByte(0x90);
I2CWaitAck();
I2CSendByte(0x41);
I2CWaitAck();
I2CSendByte(dat);
I2CWaitAck();
I2CStop();
}
结语
评论记录:
回复评论: