蓝桥杯单片机学习16——十一届省赛题详解
十一届省赛题
前言
上期我们介绍了十二届省赛题,这其我们来学习一下十一届省赛题
任务要求
1.基本要求
2.竞赛板配置要求
3.硬件框图
4.功能描述
以上就是十一届省赛题的全部要求,下面我来讲一下我的实现思路。
实现思路
看完题目后,我个人的思路还是挺明确的:
- 首先先写一遍LS138相关外设控制,讲蜂鸣器,继电器关闭,通过两个数组控制LED/数码管的显示内容,写一个矩阵按键扫描函数,实现基本的功能函数。
- 完成前期的准备工作,初始化定时器,再去编写对应的页面函数,以及定义相关的变量、标志位、计数位。
- 编写ADC和AT24C02的使用函数,完成所有函数的编写。测试各模块是否能正常工作
- 根据任务要求,编写相关的逻辑,优化代码。
任务难点
本次省赛题的难点,我认为有如下两点:
我的实现思路是定义一个VIN3和VIN3_Pre,通过比较VIN3和VIN4_Pre的大小就可以比较出VIN3的变化趋势,然后再将VIN3和Vp进行比较就可以实现对计数值加一。
我的实现思路是这样的:我有一个按键扫描函数,当有按键按下时,无效按键次数加一,然后再对应的按键处理逻辑部分讲无效按键次数清零,如果无效按键次数大于3,将L3点亮,反之则熄灭。
不过我发现这样的方法是不行的,具体原因还没找出来,等我找到了再说吧,我们先看下面的代码。
代码实现
我这次的代码旗本框图上和十二届省赛题是一致的,所以这次我不会将全部的代码贴上,只会展示我认为需要的代码。
1.变量定义部分
#include <STC15F2K60S2.H>
#include "Key.h"
#include "LS138.h"
#include "iic.h"
#include "interrupt.h"
unsigned char SEG[8] = {33,33,33,33,33,33,33,33}; //显示数码管数组,33为不亮
unsigned char LED[8] = {0,0,0,0,0,0,0,0}; //LED显示数组
int VIN3 = 324; //采集电压 增加 不变 减小
int VIN3_Pre =0; //VIN3变化趋势 VIN3_Pre < VIN3 VIN3_Pre = VIN3 VIN3_Pre > VIN3
int Vp = 300; //参考电压
unsigned int Count = 0; //计数值
unsigned int Vin_State =0; //为1时VIN3变化趋势为下降
unsigned int Page_Num = 1; //当前界面的值
unsigned int Key_Num = 0; //按键的值
int Mistake_Num = 0; //无效按键的次数
unsigned char Vin_Flag = 0; //VIN3 < Vp标志位
unsigned char Show_Flag = 0; //数码管更新标志。每10ms更新一次
2.main函数
main()
{
Init(); //初始化
while(1)
{
Show_Task(); //各种功能函数
Key_Task();
Logic_Task();
Data_Task();
}
}
是的,就是这这么简介,哈哈哈
3.初始化函数
//初始化函数
void Init(void)
{
unsigned char dat = 0;
dat = AT24C02_Read_Byte(0x00);
if(dat != 0xFF) //判断不为第一次上电,则读取EEPROM内的Vp值,反之则不读取
{
Vp = dat *10;
}
LS_Init(); //LS138部分外设初始化
Timer1_Init(); //定时器1初始化,16位自动重装载,1ms产生一次中断
}
初始化函数负责调用各个外设,中断的初始化,以及判断AT24C02是否读取等任务
4.数码管显示任务函数
//数据显示页面
void SEG_Page1(void)
{
SEG[0] = 30;
SEG[1] = 33;
SEG[2] = 33;
SEG[3] = 33;
SEG[4] = 33;
SEG[5] = VIN3/100%10 + 10;
SEG[6] = VIN3/10%10;
SEG[7] = VIN3%10;
}
//参数设置页面
void SEG_Page2(void)
{
SEG[0] = 29;
SEG[1] = 33;
SEG[2] = 33;
SEG[3] = 33;
SEG[4] = 33;
SEG[5] = Vp/100%10 + 10;
SEG[6] = Vp/10%10;
SEG[7] = Vp%10;
}
//计数页面
void SEG_Page3(void)
{
SEG[0] = 28;
SEG[1] = 33;
SEG[2] = 33;
SEG[3] = 33;
SEG[4] = 33;
SEG[5] = 33;
SEG[6] = Count/10%10;
SEG[7] = Count%10;
}
//数码管/LED显示相关内容函数
void Show_Task(void)
{
switch(Page_Num) //根据当前界面的值确定需要显示的页面
{
case 1: SEG_Page1(); //显示数据页面
break;
case 2: SEG_Page2(); //显示参数页面
break;
case 3: SEG_Page3(); //显示1计数页面
break;
}
if(Show_Flag) //每10ms更新一次数码管/LED显示
{
SEG_Control(SEG); //显示数码管
LED_Control(LED); //显示LED
Show_Flag= 0; //取消显示标志位
}
}
包括对应的界面函数以及通过Pag_Num变量决定需要显示的界面,每10ms刷新一次数码管显示。
5.按键矩阵扫描函数
//按键扫描函数
void Key_Task(void)
{
unsigned int temp = 0;
temp = KeyScan();
if(temp != 0) //若有按键按下则读取按键的值
{
Key_Num = temp;
Mistake_Num++; //有按键按下则无效按键数量加一
}
}
当有按键按下时,才会获取对应的按键值
6.逻辑处理函数
//处理逻辑相关函数
void Logic_Task(void)
{
if(Key_Num == 12) //按键十二按下,切换显示界面
{
Mistake_Num = 0; //此次按键是有效按键,将其清零
Page_Num++;
AT24C02_Write_Byte(0x00,Vp*0.1); //将Vp存入EEPROM中
if(Page_Num >= 4)
{
Page_Num = 1;
}
}
if(Key_Num == 13 && Page_Num == 3) //按键十三按下
{
Mistake_Num = 0; //此次按键是有效按键,将其清零
Count = 0; //Count清零
}
if(Key_Num == 16 && Page_Num == 2) //按键十六按下
{
Mistake_Num = 0; //此次按键是有效按键,将其清零
Vp += 50; //Vp加0.5V
if(Vp > 500)
{
Vp = 0; //大于5V,则等于0V
}
}
if(Key_Num == 17 && Page_Num == 2) //按键十七按下
{
Mistake_Num = 0; //此次按键是有效按键,将其清零
Vp -= 50; //Vp减0.5V
if(Vp < 0)
{
Vp = 500; //小于0V,则等于5V
}
}
if(Mistake_Num >=3) //当个无效按键超过三次,L3亮起
{
LED[2] = 1;
}
else //反之则熄灭
{
LED[2] = 0;
}
if(VIN3 < Vp) //VIN3 < Vp,将对应标志位置一
{
Vin_Flag = 1;
}
else
{
Vin_Flag = 0; //反之则置0
LED[0] = 0; //熄灭L1
}
if(Count %2 == 1) //Count为奇数时,L2点亮
{
LED[1] = 1;
}
else
{
LED[1] = 0; //为偶数时熄灭
}
}
包括对不同按键按下的逻辑处理代码,以及判条件控制LED改变的相关代码
7.数据处理相关函数
//初始数据相关的函数
void Data_Task(void)
{
VIN3_Pre = VIN3; //将当前的VIN3记为之前的值
VIN3 = PCF8591_AD_Conversion(3); //读取当前的VIN3电压
if(VIN3_Pre > VIN3 )
{
Vin_State = 1; //VIN3变化趋势为下降
}
}
完成对电压采集,Count加一等内容。
8.定时器函数
//定时器1初始化函数,1ms进来一次
void Timer1_Init(void)
{
TMOD |= 0x00; //设置定时器1工作模式0,16位自动重装载
// TMOD |= 0x10; //设置定时器1工作模式1
// TMOD |= 0x20; //设置定时器1工作模式2,8位自动重装载
TL1 = 0x18; //设置定时器溢出时间
TH1 = 0xFC;
ET1 = 1; //开启定时器0中断允许
EA=1; //开启总中断允许
TR1 = 1; //开启定时器0
PT1 = 0; //设置优先级为低优先级,为1时设置为高优先级
}
//定时器1服务函数
void External_Hander2() interrupt 3
{
static unsigned int Timer1_Count = 0; //定时器计数变量
static unsigned char Vin_Count =0; //VIN3 < Vp 对应计数变量
Timer1_Count++;
if(Vin_State == 1) //判断Count是否需要++
{
if(Vp == VIN3)
{
Count++;
}
Vin_State =0;
}
if(Timer1_Count %10 == 0) //10ms更新一次数码管
{
Show_Flag = 1;
}
if(Timer1_Count >= 1000) //1s触发一次
{
if(Vin_Flag)
{
Vin_Count++;
if(Vin_Count >=5)
{
LED[0] = 1; //VIN3 < Vp 5s后点亮LED1
Vin_Count = 0;
}
}
Timer1_Count = 0;
}
}
定时器1初始化,10ms更新一次数码管/LED显示,判断Count++条件,以及进行计时工作。
9.AD转换函数
//PCF8591AD转换函数,channel为模拟输入的通道
int PCF8591_AD_Conversion(unsigned char channel )
{
int dat = 0;
IIC_Start(); //iic通信开始
IIC_SendByte(PCF8591_Write_Addr); //写入从机PCF8591的写地址,
if(IIC_WaitAck()) //等待从机应答,为1表示不应答,为0表示应答
{
IIC_Stop(); //如果不应答,则结束通信
return 0;
}
IIC_SendByte(0x00 | channel); //写入控制指令,单端输入,AD转换,选择通道channel
IIC_WaitAck(); //这一步不能少,否则读出来一直是255
IIC_Stop(); //结束本次通信,改变收发方向
IIC_Start(); //iic通信开始
IIC_SendByte(PCF8591_Read_Addr); //写入从机PCF8591的读地址,
if(IIC_WaitAck()) //等待应答
{
IIC_Stop(); //如果不应答,则结束通信
return 0;
}
dat = IIC_RecByte(); //读取AD转换出的数字量
IIC_SendAck(1); //不应答
IIC_Stop(); //结束通信
//这里读到了数据,可以进行数据处理。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
dat = dat*1.96 ;
return dat;
}
负责AD采集工作
10.EEPROM读取/写入函数
//AT24C02写字节函数,Addr为数据写入的地址,dat为需要写入的数据
void AT24C02_Write_Byte(unsigned char Addr,unsigned char dat)
{
IIC_Start(); //开始信号
IIC_SendByte(AT24C02_WriteByte); //开始寻址,选择为写数据
IIC_WaitAck(); //等待应答
IIC_SendByte(Addr); //写入数据存放的地址
IIC_WaitAck(); //等待应答
IIC_SendByte(dat); //发送数据
IIC_WaitAck(); //等待应答
IIC_Stop(); //结束通信
}
//AT24C02读字节函数,返回值为读出的数据,Addr为数据读出的地址
unsigned char AT24C02_Read_Byte(unsigned char Addr)
{
unsigned char dat = 0; //定义变量,存放读出的数据
IIC_Start(); //开始通信
IIC_SendByte(AT24C02_WriteByte);//开始寻址,选择为写数据
IIC_WaitAck(); //等待应答
IIC_SendByte(Addr); //写入数据读出的地址
IIC_WaitAck(); //等待应答
IIC_Start(); //开始下一次通信,因为IIC通信每改变一次数据传输方向,必需重新开始通信
IIC_SendByte(AT24C02_ReadByte); //开始寻址,选择为读数据
IIC_WaitAck(); //等待应答
dat = IIC_RecByte(); //读出数据
IIC_SendAck(1); //发送不应答,(发1表示不应答)
IIC_Stop(); //结束通信
return dat; //返回读出的数据
}
负责EEPROM读取/写入函数
总结
看到这里,这一期省赛题目的分享也应该时接近尾声了,后续的话,应该还会继续分析两期左右的省赛题,比较也快到比赛的日子了。还有就是,我可能也会更新一两期的客观题,比较三十分也是很香的。
最后,附上代码逻辑的思维导图,需要自取。
作者:不想写代码的我