题目 :
蓝桥杯大赛历届真题 - 第十二届蓝桥杯单片机程序设计题 - 蓝桥云课 (lanqiao.cn)
好几个月前写的了,很多东西不记得,整体来说这一套很简单。
按键动作不影响其它操作是非常常见的要求,这就要求按键通过定时器中断扫描。
LED看似简单,但是要各种亮灭不影响其它灯,实现起来很多人反而不会,这里使用结构体和共用体来实现。可以同时实现对位的控制和整个端口的控制,且不同位相互不受影响。
i2c和单总线部分使用官方的驱动程序。
代码部分
main.h
#ifndef _MAIN_H_
#define _MAIN_H_
#include <STC15F2K60S2.H>
unsigned char key_read();
unsigned char key_loop();
void P2andP0(unsigned char p2,p0);
void smg_set(unsigned char position,words,points);
void smg_loop();
float ds18b20_read();
typedef struct
{
unsigned char b0:1;
unsigned char b1:1;
unsigned char b2:1;
unsigned char b3:1;
unsigned char b4:1;
unsigned char b5:1;
unsigned char b6:1;
unsigned char b7:1;
}bits;
typedef union
{
bits b;
unsigned char hex;
}hextobin;
hextobin led,buzz;
void dac(float datas);
#endif
main.c
#include <main.H>
// 定义全局变量
unsigned char mode = 0, mode1 = 0, tflag = 0, dacflag = 0; // 各种标志位
unsigned int heat; // 存储温度值,单位为0.01°C
char para = 20; // 设定值
float t = 0, voltage; // 当前温度和输出电压
// 定时器0中断服务程序
void Timer0_Isr(void) interrupt 1 {
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(); // 读取按键
}
// 每900ms触发一次,用于读取温度
if (T2 > 900) {
T2 = 0;
tflag = 1; // 标记温度读取完成
}
// 每400ms触发一次,用于更新DAC输出
if (T3 > 400) {
dacflag = 1; // 标记DAC需要更新
T3 = 0;
}
}
// 定时器0初始化
void Timer0_Init(void) { // 1毫秒@12.000MHz
AUXR |= 0x80; // 定时器时钟1T模式
TMOD &= 0xF0; // 设置定时器模式
TL0 = 0x20; // 设置定时初始值
TH0 = 0xD1; // 设置定时初始值
TF0 = 0; // 清除TF0标志
TR0 = 1; // 开始计时
ET0 = 1; // 使能定时器0中断
EA = 1; // 开启总中断
}
// 显示更新函数
void view() {
switch (mode) {
case 0: // 显示温度
smg_set(0, 12, 0); // 设置第一位显示内容
smg_set(1, 17, 0); // 设置第二位显示内容
smg_set(2, 17, 0); // 设置第三位显示内容
smg_set(3, 17, 0); // 设置第四位显示内容
smg_set(4, heat / 1000 % 10, 0); // 设置第五位显示内容
smg_set(5, heat / 100 % 10 + 20, 0); // 设置第六位显示内容
smg_set(6, heat / 10 % 10, 0); // 设置第七位显示内容
smg_set(7, heat / 1 % 10, 0); // 设置第八位显示内容
break;
case 1: // 显示设定值
smg_set(0, 19, 0); // 设置第一位显示内容
smg_set(1, 17, 0); // 设置第二位显示内容
smg_set(2, 17, 0); // 设置第三位显示内容
smg_set(3, 17, 0); // 设置第四位显示内容
smg_set(4, 17, 0); // 设置第五位显示内容
smg_set(5, 17, 0); // 设置第六位显示内容
smg_set(6, para / 10, 0); // 设置第七位显示内容
smg_set(7, para % 10, 0); // 设置第八位显示内容
break;
case 2: // 显示电压
smg_set(0, 10, 0); // 设置第一位显示内容
smg_set(1, 17, 0); // 设置第二位显示内容
smg_set(2, 17, 0); // 设置第三位显示内容
smg_set(3, 17, 0); // 设置第四位显示内容
smg_set(4, 17, 0); // 设置第五位显示内容
smg_set(5, (unsigned int)(voltage * 100) / 100 % 10, 0); // 设置第六位显示内容
smg_set(6, (unsigned int)(voltage * 100) / 10 % 10, 0); // 设置第七位显示内容
smg_set(7, (unsigned int)(voltage * 100) % 10, 0); // 设置第八位显示内容
break;
}
}
// 数模转换控制函数
void pcf8591_dac() {
if (mode1 == 0) {
if (t < para) {
voltage = 0; // 如果温度低于设定值,则输出0V
dac(0); // 设置DAC输出为0
} else {
dac(5); // 如果温度高于设定值,则输出5V
voltage = 5; // 设置电压值为5V
}
} else if (mode1 == 1) {
if (t < 20) {
voltage = 1.0; // 如果温度低于20°C,则输出1V
dac(1.0); // 设置DAC输出为1V
} else if (t > 40) {
dac(4.0); // 如果温度高于40°C,则输出4V
voltage = 4.0; // 设置电压值为4V
} else {
dac(0.15 * t - 2); // 在20°C到40°C之间,线性调整输出电压
voltage = 0.15 * t - 2; // 设置电压值
}
}
}
// 主函数
void main() {
unsigned char value = 0, v = 0;
// 初始化定时器0
Timer0_Init();
// 初始化LED和P2及P0端口
led.hex = 0xFF;
P2andP0(0xA0, 0);
// 设置LED的初始状态
led.b.b1 = 0;
led.b.b0 = 0;
// 更新P2和P0端口
P2andP0(0x80, led.hex);
// 主循环
while (1) {
// 读取按键值
value = key_read();
// 按键值为4时改变显示模式
if (value == 4) {
mode++;
mode = mode % 3; // 确保模式在0-2之间
// 根据模式更新LED状态
if (mode == 0) {
led.b.b1 = 0;
led.b.b2 = 1;
led.b.b3 = 1;
P2andP0(0x80, led.hex);
} else if (mode == 1) {
led.b.b1 = 1;
led.b.b2 = 0;
led.b.b3 = 1;
P2andP0(0x80, led.hex);
} else if (mode == 2) {
led.b.b1 = 1;
led.b.b2 = 1;
led.b.b3 = 0;
P2andP0(0x80, led.hex);
}
}
// 按键值为5时改变DAC模式
if (value == 5) {
mode1++;
mode1 = mode1 % 2; // 确保模式在0-1之间
// 根据DAC模式更新LED状态
if (mode1 == 0) {
led.b.b0 = 0;
P2andP0(0x80, led.hex);
} else {
led.b.b0 = 1;
P2andP0(0x80, led.hex);
}
}
// 按键值为8时减小设定值
if (value == 8 && mode == 1) {
para--;
if (para < 0) para = 0; // 确保设定值不低于0
}
// 按键值为9时增加设定值
if (value == 9 && mode == 1) {
para++;
if (para > 99) para = 99; // 确保设定值不高于99
}
// 更新显示
view();
// 如果温度读取完成,更新温度值
if (tflag == 1) {
t = ds18b20_read(); // 读取温度
heat = (unsigned int)(t * 100); // 转换温度值为整数
tflag = 0; // 清除标志位
}
// 如果需要更新DAC输出,调用相应的函数
if (dacflag == 1) {
pcf8591_dac(); // 更新DAC输出
dacflag = 0; // 清除标志位
}
}
}
key.c
#include <STC15F2K60S2.H>
unsigned char key_get()
{
unsigned char keynum=0;
P44=0;P42=1;
if(P33==0)keynum=4;
if(P32==0)keynum=5;
P44=1;P42=0;
if(P33==0)keynum=8;
if(P32==0)keynum=9;
return keynum;
}
unsigned char key_loop()
{
static unsigned char now=0,last=0;
unsigned char keynum=0;
last=now;
now=key_get();
if(last==0&&now==4)keynum=4;
if(last==0&&now==5)keynum=5;
if(last==0&&now==8)keynum=8;
if(last==0&&now==9)keynum=9;
return keynum;
}
unsigned char key_read()
{
unsigned char temp=0;
temp=key_loop();
return temp;
}
smg.c
#include <STC15F2K60S2.H>
void P2andP0(unsigned char p2,p0);
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
0x8e, //F 15
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
};
unsigned char word[8]={17,17,17,17,17,17,17,17},point=0;
void smg_set(unsigned char position,words,points)
{
word[position]=words;
point=points;
}
void smg_loop()
{
static unsigned char i=0;
P2andP0(0xC0,0x01<<i);
// if(point==1)P2andP0(0xE0,word[i]!=17?Seg_Table[word[i]]&0x7F:Seg_Table[word[i]]);
// else
P2andP0(0xE0,Seg_Table[word[i]]);
word[i]=17;
i++;
if(i>7)i=0;
}
P2andP0.c
#include <STC15F2K60S2.H>
void P2andP0(unsigned char p2,p0)
{
P0=p0;
P2=P2&0x1F;
P2=P2|p2;
P2=P2&0x1F;
}
ds18b20.c
void Write_DS18B20(unsigned char dat);
unsigned char Read_DS18B20(void);
bit init_ds18b20(void);
#define skip 0xCC
#define read 0xBE
#define write 0x44
float ds18b20_read()
{
unsigned char i=0;
int t0,tl,th;
float t=0;
i=init_ds18b20();
Write_DS18B20(skip);
Write_DS18B20(write);
i=init_ds18b20();
Write_DS18B20(skip);
Write_DS18B20(read);
tl=Read_DS18B20();
th=Read_DS18B20();
t0=(th<<8)|tl;
t=t0/16.0;
return t;
}
pcf8591.c
void I2CStart(void);
void I2CStop(void);
void I2CSendByte(unsigned char byt);
unsigned char I2CReceiveByte(void);
unsigned char I2CWaitAck(void);
void I2CSendAck(unsigned char ackbit);
void dac(float datas)
{
unsigned char i;
I2CStart();
I2CSendByte(0x90);
i=I2CWaitAck();
I2CSendByte(0x40);
i=I2CWaitAck();
I2CSendByte(datas);
i=I2CWaitAck();
I2CStop();
}
解释部分
主要功能:
1. 定时器0中断服务程序 (Timer0_Isr): 这个函数处理定时器0的中断,用于周期性地执行不同的任务。
2. 定时器0初始化 (Timer0_Init): 设置定时器0的配置参数以实现1毫秒的定时。
3. 显示更新 (view): 根据当前模式更新显示的内容,显示可以是温度、设定值或电压。
4. 数模转换控制 (pcf8591_dac): 根据不同的模式设置DAC输出电压。
5. 主函数 (main): 控制程序的流程,读取按键输入并相应地更新显示和DAC输出。
关键部分解析:
定时器0中断服务程序
使用四个递增的计数器 T0, T1, T2, T3 来调度不同频率的任务。
T0 每1ms触发一次,用于调用 smg_loop() 更新显示。
T1 每20ms触发一次,用于调用 key_loop() 读取按键状态。
T2 每900ms触发一次,用于调用 ds18b20_read() 获取温度值。
T3 每400ms触发一次,用于调用 pcf8591_dac() 更新DAC输出。
显示更新
根据 mode 变量的不同值,更新显示不同的内容。
mode == 0: 显示温度。
mode == 1: 显示设定值 para。
mode == 2: 显示电压 voltage。
数模转换控制
根据 mode1 的值,选择不同的DAC输出策略。
mode1 == 0: 如果温度低于设定值,则输出0V;否则输出5V。
mode1 == 1: 根据温度线性调节输出电压,范围为1V至4V之间。
主函数
初始化定时器0。
无限循环中读取按键,并根据按键值改变模式、调整设定值或更新显示。
调用 view() 和条件判断来更新显示。
调用 ds18b20_read() 和 pcf8591_dac() 来更新温度值和DAC输出。
其他函数
按键读取 (key_read): 用于检测是否有按键按下,并返回相应的按键值。
温度读取 (ds18b20_read): 读取DS18B20温度传感器的温度值。
数模转换 (dac): 向PCF8591 DAC发送数据。
各函数和变量解释
全局变量
mode: 一个无符号字符型变量,表示当前显示模式,取值范围为0到2。
mode1: 一个无符号字符型变量,表示DAC工作模式,取值范围为0到1。
tflag: 一个无符号字符型变量,作为标志位,当温度读取完成时被置为1。
dacflag: 一个无符号字符型变量,作为标志位,当需要更新DAC输出时被置为1。
heat: 一个无符号整型变量,用于存储温度值(单位为0.01°C)。
para: 一个字符型变量,用于存储设定值,范围为0到99。
t: 一个浮点型变量,用于存储当前温度(单位为°C)。
voltage: 一个浮点型变量,用于存储输出电压值。
函数
Timer0_Isr(): 定时器0的中断服务程序。该函数中包含了四个计数器,用于周期性地执行不同的任务:
T0: 每1毫秒递增,达到1时触发 smg_loop() 更新显示。
T1: 每20毫秒递增,达到20时触发 key_loop() 读取按键。
T2: 每900毫秒递增,达到900时设置 tflag 为1,用于触发温度读取。
T3: 每400毫秒递增,达到400时设置 dacflag 为1,用于触发DAC更新。
Timer0_Init(): 初始化定时器0,设置定时器0为1毫秒定时。
view(): 根据 mode 变量的值更新数码管显示内容。
pcf8591_dac(): 根据 mode1 和当前温度 t 控制DAC输出电压。
main(): 主函数,负责程序初始化和主循环,包括初始化定时器0、读取按键、更新显示和控制DAC输出。
key_read(): 读取按键状态,返回对应的按键值。
key_loop(): 连续读取按键状态,过滤按键抖动。
P2andP0(): 设置P2和P0端口的值以控制数码管。
smg_set(): 设置数码管显示内容的位置和数值。
smg_loop(): 循环更新数码管的显示。
ds18b20_read(): 读取DS18B20温度传感器的温度值。
dac(): 通过I2C接口向DAC写入数据,控制输出电压。
辅助定义
Seg_Table[]: 数组,用于存储数码管显示的段码。
word[8]: 数组,用于存储数码管各位置的显示内容。
point: 变量,用于存储数码管小数点的状态。
Write_DS18B20(): 写入DS18B20的命令字。
Read_DS18B20(): 从DS18B20读取数据。
init_ds18b20(): 初始化DS18B20。
I2CStart(), I2CStop(), I2CSendByte(), I2CReceiveByte(), I2CWaitAck(), I2CSendAck(): I2C通信相关的函数。