蓝桥杯单片机组第十三届初赛试题-程序题(第2批)
题目到官网看即可,有点久了有些细节记不清了,可能以前发的帖子解释详细一点。
这是我单片机初学的时候写的,像代码结构什么的肯定有可以提升的地方,多多包涵,将就看一下。
i2c文件使用官方的,pcf8591函数声明没有摆出来。
main.c
#include <system.H>
// 定义超声波测距相关参数,初始值为3.0和1.5
float hc = 3.0, lc = 1.5;
// 定义电压上下限,初始值为3.0和1.5
float highc = 3.0, lowc = 1.5;
// 定义变量v用于存储DAC输出的电压值,voltage用于存储ADC采集的电压值
float v, voltage = 0;
// sonicflag:超声波测距触发标志;adc:ADC采集结果;waveflag:测距启动标志;ledflag:LED闪烁标志
unsigned char sonicflag = 0, adc = 0, waveflag = 0, ledflag = 0;
// 定义距离变量,初始值为50
unsigned int distance = 50;
// 该函数用于设置P2和P0端口的值
// p2:要设置到P2端口高5位的值
// p0:要设置到P0端口的值
void P2_P0(unsigned char p2, unsigned char p0)
{
P0 = p0;
// 清除P2端口高5位
P2 = P2 & 0x1F;
// 设置P2端口高5位
P2 = p2 | P2;
P2 = P2 & 0x1F;
}
void Delay25us(void)
{
unsigned char data i;
_nop_();
_nop_();
i = 72;
while (--i);
}
void Timer1_Isr(void) interrupt 3
{
// 静态变量,用于记录不同定时任务的计数
static unsigned int T0 = 0, T1 = 0, T2 = 0, T3 = 0;
T0++;
T1++;
T2++;
T3++;
// 每1ms执行一次数码管扫描
if (T0 > 1)
{
T0 = 0;
smg_loop();
}
// 每20ms执行一次按键扫描
if (T1 > 20)
{
T1 = 0;
key_loop();
}
// 每500ms触发一次超声波测距
if (T2 > 500)
{
T2 = 0;
sonicflag = 1;
}
// 每100ms改变一次LED闪烁标志
if (T3 > 100)
{
T3 = 0;
ledflag++;
ledflag = ledflag % 2;
}
}
void Timer1_Init(void)
{
// 定时器时钟1T模式
AUXR |= 0x40;
TMOD &= 0x0F;
// 设置定时初始值
TL1 = 0x20;
// 设置定时初始值
TH1 = 0xD1;
// 清除TF1标志
TF1 = 0;
// 定时器1开始计时
TR1 = 1;
// 使能定时器1中断
ET1 = 1;
// 全局中断使能,两个定时器初始化有一个有这一句就行
EA = 1;
}
void Timer0_Init(void)//定时器0给超声波计时间用,不用在初始化的时候打开
{
// 定时器时钟12T模式
AUXR &= 0x7F;
// 设置定时器模式
TMOD &= 0xF0;
// 设置定时初始值
TL0 = 0;
// 设置定时初始值
TH0 = 0;
// 清除TF0标志
TF0 = 0;
}
// 该函数用于根据不同的显示模式更新数码管显示内容
// i:显示模式,0表示电压显示,1表示测距显示,2表示参数显示
void view(unsigned char i)
{
switch (i)
{
case 0: // 电压显示模式
{
// 显示特定符号
smg_set(0, 18);
smg_set(1, 17);
smg_set(2, 17);
smg_set(3, 17);
smg_set(4, 17);
// 显示电压整数部分
smg_set(5, (unsigned int)(voltage * 100) / 100 + 20);
// 显示电压小数点后第一位
smg_set(6, (unsigned int)(voltage * 100) % 100 / 10);
// 显示电压小数点后第二位
smg_set(7, (unsigned int)(voltage * 100) % 10);
break;
}
case 2: // 参数显示模式
{
smg_set(0, 19);
smg_set(1, 17);
smg_set(2, 17);
// 显示上限参数整数部分
smg_set(3, (unsigned char)hc + 20);
// 显示上限参数小数部分
smg_set(4, (unsigned char)(hc * 10) % 10);
smg_set(5, 17);
// 显示下限参数整数部分
smg_set(6, (unsigned char)lc + 20);
// 显示下限参数小数部分
smg_set(7, (unsigned char)(lc * 10) % 10);
break;
}
case 1: // 测距显示模式
{
smg_set(0, 15);
smg_set(1, 17);
smg_set(2, 17);
smg_set(3, 17);
smg_set(4, 17);
// 根据测距启动标志显示特定字符或距离数据
smg_set(5, waveflag == 0 ? 10 : 17);
smg_set(6, waveflag == 0 ? 10 : distance / 100);
smg_set(7, waveflag == 0 ? 10 : distance % 100 / 10);
break;
}
}
}
// 超声波测距函数
void untrasonic()
{
unsigned char i = 0;
// 当超声波测距触发标志置位时
if (sonicflag == 1)
{
// 清除触发标志
sonicflag = 0;
// 发送8个25us的超声波脉冲
for (i = 0; i < 8; i++)
{
tx = 1;
Delay25us();
tx = 0;
Delay25us();
}
// 启动定时器0开始计时
TR0 = 1;
// 等待回波信号或定时器0溢出
while ((rx == 1) && (TF0 == 0));
// 停止定时器0计时
TR0 = 0;
// 如果定时器0未溢出
if (TF0 == 0)
{
// 读取定时器0的高8位
distance = TH0;
// 左移8位
distance <<= 8;
// 读取定时器0的低8位
distance |= TL0;
// 根据定时器计数值计算距离
distance = (unsigned int)(distance * 0.017);
}
else
{
// 清除定时器0溢出标志
TF0 = 0;
// 距离值置为0
distance = 0;
}
}
}
// DAC输出函数,根据测距结果设置DAC输出电压
void dac()
{
// 当测距未启动时
if (waveflag == 0)
{
// DAC输出0V
pcf8591_dac(0);
v = 0;
}
else
{
// 当距离在20到80之间时
if (distance > 20 && distance <= 80)
{
// 计算DAC输出电压
v = distance / 15.0 - (1 / 3.0);
// 设置DAC输出电压
pcf8591_dac(v);
}
// 当距离在0到20之间时
else if (distance > 0 && distance <= 20)
{
v = 1.0;
pcf8591_dac(v);
}
else
{
v = 5.0;
pcf8591_dac(v);
}
}
}
void main()
{
// 按键值
unsigned char value = 0;
// 显示模式
unsigned char mode0 = 0;
// 参数设置模式
unsigned char mode1 = 0;
// 临时变量,用于参数交换
float vtemp = 0;
Timer1_Init();
Timer0_Init();
// 初始化LED灯,所有灯熄灭,仅LED1点亮
led.hex = 0xFF;
led.b.b0 = 0;
// 设置P2和P0端口,点亮LED1
P2_P0(0x80, led.hex);
// 关闭蜂鸣器和继电器
P2_P0(0xA0, 0);
while (1)
{
// 获取按键值
value = key_get();
// 按键4按下,切换显示界面
if (value == 4)
{
mode0++;
mode0 = mode0 % 3;
if (mode0 == 1)
{
// 测距界面,设置LED状态
led.b.b0 = 1;
led.b.b1 = 0;
led.b.b2 = 1;
}
if (mode0 == 2)
{
// 参数设置界面,设置LED状态
led.b.b0 = 1;
led.b.b1 = 1;
led.b.b2 = 0;
// 默认设置上限
mode1 = 0;
}
if (mode0 == 0) // 退出参数设置,更新上下限参数
{
highc = hc;
lowc = lc;
// 电压显示界面,设置LED状态
led.b.b0 = 0;
led.b.b1 = 1;
led.b.b2 = 1;
}
// 更新LED显示
P2_P0(0x80, led.hex);
}
// 按键5按下,选择设置上限还是下限
if (value == 5)
{
// 仅在参数设置界面有效
if (mode0 == 2)
{
mode1++;
mode1 = mode1 % 2;
}
}
// 按键6按下,参数值加0.5
if (value == 6)
{
// 上限设置
if (mode1 == 0)
{
hc = hc + 0.5;
if (hc > 5.0) hc = 0.5;
// 越界后上下限交换
if (hc < lc)
{
vtemp = hc;
hc = lc;
lc = vtemp;
}
}
// 下限设置
if (mode1 == 1)
{
lc = lc + 0.5;
// 下限不能高于上限
if (lc > hc) lc = hc;
if (lc > 5.0) lc = 0.5;
}
}
// 按键7按下,参数值减0.5
if (value == 7)
{
// 上限设置
if (mode1 == 0)
{
hc = hc - 0.5;
if (hc < 0.5) hc = 5.0;
}
// 下限设置
if (mode1 == 1)
{
lc = lc - 0.5;
// 越界后上下限交换
if (lc < 0.5)
{
lc = 5.0;
if (hc < lc)
{
vtemp = hc;
hc = lc;
lc = vtemp;
}
}
// 下限不能低于上限
if (hc < lc) hc = lc;
}
}
// 根据显示模式更新数码管显示
view(mode0);
// 启动ADC采集通道3的电压值
pcf8591_adc(0x03);
// 读取ADC采集结果
adc = pcf8591_read();
// 计算采集到的电压值
voltage = 5.0 * (adc / 255.0);
// 当电压值在上下限之间时
if ((voltage < highc) && (voltage > lowc))
{
// 启动测距
waveflag = 1;
// 执行超声波测距
untrasonic();
// 设置DAC输出电压
dac();
// 根据LED闪烁标志控制LED灯
if (ledflag == 1)
{
led.b.b7 = 0;
P2_P0(0x80, led.hex);
}
else
{
led.b.b7 = 1;
P2_P0(0x80, led.hex);
}
}
else
{
// 停止测距
waveflag = 0;
}
}
}
主函数是程序的入口,主要完成以下工作:
- 初始化定时器 1 和定时器 0。
- 初始化 LED 灯和关闭蜂鸣器、继电器。
- 进入无限循环,不断读取按键值,根据按键值进行显示模式切换、参数设置等操作。
- 调用
view
函数更新数码管显示。 - 进行 ADC 电压采集,根据采集到的电压值判断是否启动超声波测距和 DAC 输出。
- 根据
ledflag
标志位控制 LED 灯的闪烁。
arrkeys.c
#include <STC15F2K60S2.H>
// 该函数用于读取独立按键的状态,返回按下按键对应的编号
// 按键编号对应关系为:P30 -> 7, P31 -> 6, P32 -> 5, P33 -> 4
unsigned key_read()//独立按键
{
// 用于存储检测到的按键编号,初始值为 0 表示无按键按下
unsigned char key = 0;
// 设置按键扫描的端口状态,P44 置 0,P42、P35、P34 置 1
P44 = 0;
P42 = 1;
P35 = 1;
P34 = 1;
// 检测 P30 引脚是否为低电平,如果是则表示按键 7 被按下
if (P30 == 0)
key = 7;
if (P31 == 0)
key = 6;
if (P32 == 0)
key = 5;
if (P33 == 0)
key = 4;
// 返回检测到的按键编号
return key;
}
// 该函数用于对按键进行消抖处理,并返回有效的按键编号
// 这个可能是状态机,通过比较当前和上一次按键状态来判断是否为有效按键按下
unsigned char key_loop()
{
// 静态变量,用于记录上一次按键的状态,初始值为 0
static unsigned char nowstate = 0, laststate = 0;
// 用于存储最终有效的按键编号,初始值为 0 表示无有效按键按下
unsigned char keynum = 0;
// 将当前按键状态保存为上一次按键状态
laststate = nowstate;
// 调用 key_read 函数获取当前实际的按键状态
nowstate = key_read();
// 判断上一次无按键按下,且当前检测到按键 7 被按下,则认为按键 7 是有效按下
if (laststate == 0 && nowstate == 7)
keynum = 7;
if (laststate == 0 && nowstate == 6)
keynum = 6;
if (laststate == 0 && nowstate == 5)
keynum = 5;
if (laststate == 0 && nowstate == 4)
keynum = 4;
// 返回有效的按键编号
return keynum;
}
// 该函数用于获取经过消抖处理后的有效按键编号
// 调用 key_loop 函数来完成按键消抖和判断,并将结果返回
unsigned char key_get()
{
// 临时变量,用于存储 key_loop 函数返回的有效按键编号
unsigned char temp = 0;
// 调用 key_loop 函数获取有效按键编号
temp = key_loop();
// 返回有效按键编号
return temp;
}
//这是一个必要的函数,好像是如果直接通过上面函数获得键值,就会一直返回按的结果好像一直在按
1. key_read 函数
该函数用于读取独立按键的状态。
首先对按键相关的端口进行初始化设置(P44 = 0; P42 = 1; P35 = 1; P34 = 1;)。
然后依次检测 P30 - P33 引脚的电平状态,如果相应引脚为低电平,则将对应的按键编号赋值给 key 变量。
最后返回按键编号。
2. key_loop 函数
该函数实现了按键消抖功能,使用状态机的思想。
定义两个静态变量 nowstate 和 laststate 分别记录当前按键状态和上一次按键状态。
调用 key_read 函数获取当前按键状态,并更新 nowstate。
通过比较 laststate 和 nowstate,当 laststate 为 0(无按键按下)且 nowstate 为特定按键编号时,将该按键编号赋值给 keynum。
最后返回有效按键编号。
3. key_get 函数
该函数调用 key_loop 函数获取有效按键编号,并将其返回。
smg.c
#include <STC15F2K60S2.H>
// 定义数码管段码表,存放在程序存储器中
// 依次对应 0 - 9、A、b、C、d、E、J、-、无显示、U、P 等字符的段码
// 后面还有一组是带小数点的数字
code unsigned char Seg_Table[] =
{
0xc0, //0
0xf9, //1
0xa4, //2
0xb0, //3
0x99, //4
0x92, //5
0x82, //6
0xf8, //7
0x80, //8
0x90, //9
0x88, //A 10
0x83, //b 11
0xc6, //C 12
0xa1, //d 13
0x86, //E 14
0xc3, //J 15 11000011
0xBF, //- 16
0xFF, //none 17
0xC1, //U 18
0x8C,//P 19 1000,1100
0x40,0x79,0x24,0x30,0x19,0x12,0x02,0x78,0x00,0x10,0x08,0x03,0x46,0x21,0x06,0x0e
};
// 定义数码管显示缓冲区数组,初始值全为 17(对应无显示状态)
unsigned char smg_word[8]={17,17,17,17,17,17,17};
// 该函数用于设置 P2 端口高 5 位和 P0 端口的值
// p2: P2 端口高 5 位要设置的值
// p0: P0 端口要设置的值
void P2andP0(unsigned char p2, unsigned char p0)//在main.c里解释过了
{
P0 = p0;
P2 = P2 & 0x1F;
P2 = p2 | P2;
P2 = P2 & 0x1F;
}
// 该函数用于设置数码管显示缓冲区中指定位置的显示内容
// pos: 数码管的位置(0 - 7)
// word: 要显示的字符在段码表中的索引
void smg_set(unsigned char pos, unsigned char word)
{
// 将指定位置的显示内容设置为 word
smg_word[pos] = word;
}
// 该函数用于实现数码管的动态扫描显示
void smg_loop()
{
// 静态变量,用于记录当前扫描到的数码管位置,初始值为 0
static unsigned char i = 0;
// 选中第 i 个数码管进行显示,0xc0 用于选中数码管位选锁存器
P2andP0(0xc0, 0x01 << i);
// 将 smg_word[i] 对应的段码值通过段码表查找出来并显示
P2andP0(0xe0, Seg_Table[smg_word[i]]);
// 将当前位置的显示内容设置为无显示状态,避免余晖影响
smg_word[i] = 17;
// 扫描位置加 1
i++;
// 当扫描到第 8 个数码管后,将扫描位置重置为 0,开始下一轮扫描
if (i >= 8) i = 0;
}
system.h
// 防止头文件被重复包含,这是一种常见的预处理器技巧
// 如果 _system_h_ 未被定义,则执行下面的代码并定义 _system_h_
#ifndef _system_h_
#define _system_h_
#include <STC15F2K60S2.H>
#include <pcf8591.H>
#include <intrins.H>
//结构体和联合体部分是给led和嗡鸣器用的,详细的解释在以前帖子有
// 定义一个位域结构体 bits
// 该结构体将一个字节拆分为 8 个独立的位,分别用 b0 - b7 表示
typedef struct
{
unsigned char b0:1; // 第 0 位
unsigned char b1:1; // 第 1 位
unsigned char b2:1; // 第 2 位
unsigned char b3:1; // 第 3 位
unsigned char b4:1; // 第 4 位
unsigned char b5:1; // 第 5 位
unsigned char b6:1; // 第 6 位
unsigned char b7:1; // 第 7 位
}bits;
// 定义一个联合体 hextobin
// 联合体的特点是所有成员共享同一块内存空间
// 这里的成员包括前面定义的位域结构体 b 和一个无符号字符型变量 hex
typedef union
{
bits b;
unsigned char hex;
}hextobin;
// 定义两个 hextobin 类型的变量 led 和 buzz
// led 控制 LED 灯,buzz 控制蜂鸣器
hextobin led,buzz;
// 该函数用于获取按键的值
unsigned char key_get();
// 该函数可能用于按键扫描和消抖处理
unsigned char key_loop();
// 该函数用于数码管的动态扫描显示
void smg_loop();
// 该函数用于设置数码管指定位置显示的内容
// pos: 数码管的位置(0 - 7)
// word: 要显示的字符在段码表中的索引
void smg_set(unsigned char pos, unsigned char word);
// 定义两个位变量 tx 和 rx
// tx 连接到 P1 端口的第 0 位,可能用于发送信号
// rx 连接到 P1 端口的第 1 位,可能用于接收信号
sbit tx=P1^0;
sbit rx=P1^1;
// 该函数用于数码管的显示设置
// loc: 数码管的位置
// word: 要显示的内容
void smg_view(unsigned char loc, unsigned char word);
#endif
pcf8591.h
#include <iic.H>
// 定义一个无符号字符型变量 ack,用于存储 I2C 通信中的应答信号
unsigned char ack = 0;
// 该函数用于通过 I2C 总线向 PCF8591 芯片的 DAC(数模转换)通道写入数据
// word: 要写入 DAC 通道的数据,范围是 0 - 255
void pcf8591_dac(unsigned char word)
{
I2CStart();
// 发送 PCF8591 芯片的写地址(0x90),表示接下来要向芯片写入数据
I2CSendByte(0x90);
// 等待 PCF8591 芯片返回应答信号
I2CWaitAck();
// 发送控制字节 0x40,选择 DAC 输出功能
I2CSendByte(0x40);
// 等待 PCF8591 芯片返回应答信号
I2CWaitAck();
// 发送要写入 DAC 通道的具体数据
I2CSendByte(word);
// 等待 PCF8591 芯片返回应答信号
I2CWaitAck();
I2CStop();
}
// 该函数用于通过 I2C 总线从 PCF8591 芯片读取 ADC(模数转换)数据
// 返回值: 读取到的 ADC 数据,范围是 0 - 255
unsigned char pcf8591_read()
{
// 定义一个无符号字符型变量 datas,用于存储读取到的 ADC 数据
unsigned char datas = 0;
I2CStart();
// 发送 PCF8591 芯片的读地址(0x91),表示接下来要从芯片读取数据
I2CSendByte(0x91);
// 等待 PCF8591 芯片返回应答信号,并将应答信号存储在 ack 变量中
ack = I2CWaitAck();
// 从 PCF8591 芯片接收 ADC 数据
datas = I2CReceiveByte();
// 向 PCF8591 芯片发送非应答信号(1),表示不再接收数据
I2CSendAck(1);
I2CStop();
return datas;
}
// 该函数用于通过 I2C 总线向 PCF8591 芯片发送 ADC 通道选择命令
// add: 要选择的 ADC 通道地址
void pcf8591_adc(unsigned char add)
{
I2CStart();
// 发送 PCF8591 芯片的写地址(0x90),表示接下来要向芯片写入数据
I2CSendByte(0x90);
// 等待 PCF8591 芯片返回应答信号,并将应答信号存储在 ack 变量中
ack = I2CWaitAck();
// 发送要选择的 ADC 通道地址
I2CSendByte(add);
// 等待 PCF8591 芯片返回应答信号,并将应答信号存储在 ack 变量中
ack = I2CWaitAck();
I2CStop();
}