蓝桥杯单片机第十届决赛
感受:难,比起第8 ,第9,第11套决赛来说,出题人无所不用其极。它也得到了我的尊重,我特地写篇文章总结总结。
难点在于:
(1)功能要求较多:
代码比其他的赛题要多,细节也更多,刚开始写怀疑按键和串口会冲突,试着用一个锁去调度公用的P30和P31资源,结果根本不需要,因为按键只用到了13,12,16,17,刚好避开了P30和P31,所以按键的动态扫描只针对这四列就行。
(2)资源占用多
(3)对外设的细节把握较多
解题步骤:
数码管动态刷新
按键动态扫描(只扫描按键所需的4行,就屏蔽了P30和P31)
led动态刷新
按键长按短按判断(状态机实现)
外设定时使用(超声波,温度,epprom)
串口消息收发(状态机加串口中断)
全工程如下
main函数如下
#include "sys.h"
u8 s=0;
u8 keytime=0;
u8 smgtime=0;
u16 temp;//温度
u8 ctemp=30;//温度参数
u8 dis=0;//距离
u8 cdis=35;//距离参数
u16 cge=0;//参数变动次数
u8 cmode=1;//参数编号 1 :温度数据 2:距离数据
// 3:温度参数 4:距离参数 5:变更次数
u16 tempt=0;//读取温度间隔时间
bit uic=0;
u16 dist=0;//读取距离间隔时间
u8 longf=0;
u16 longt=0;
bit cdac=0; // 0:dac输出 1:dac关闭
u8 ledflush=0;
u16 dact=0;//dac输出时间
u8 savef=0;//参数变更保存到epprom标志
u8 bstate=0;//串口指令状态
u8 xdata sendbuf[18]={'\0'};
//dac电压输出
void DacOut(){
if(cdac){
Pcf_W_Dac(20);
}
else{
if(dis<=cdis) Pcf_W_Dac(102);
else
Pcf_W_Dac(204);
}
}
//判断LED的状态
void Judgement(){
if(temp<(ctemp*100))LEDDT[0]=0;
else LEDDT[0]=1;
if(dis<cdis)LEDDT[1]=2;
else LEDDT[1]=0;
if(!cdac)LEDDT[2]=3;
else LEDDT[2]=0;
}
//判断按键长按与短按状态
void los(){
static int keys=0;
if(key_v==12)keys=12;
else if(key_v==13)keys=13;
if(!keys)return;
switch(longf){
case 0:
longt=0;
if(key_v==keys)longf=1;
break;
case 1:
if(key_v==keys){
if(longt++>1000){//达到长按临界值执行长按的瞬间性操作
if(keys==12)cge=0;//长按12清0
else if(keys==13)cdac=~cdac;
longf=2;
}
}
//按键松开
else {
if(longt<=1000){ //执行短按
if(keys==12){
if(!uic){//数据界面显示
if(cmode==1)cmode=2;
else if(cmode==2)cmode=5;
else cmode=1;
}
else {//参数界面切换
if(cmode==3)cmode=4;
else cmode=3;
}
}
else if(keys==13){
if(!uic){
uic=1;
cmode=3;
}
else {
uic=0;
cmode=1;
}
}
keys=0;
longf=0;
}
}
break;
case 2:
if(key_v==keys){//执行长按的连续性操作
//没有操作空
}
else {
longf=0;
keys=0;
}
break;
}
}
void UISetting(){
if(cmode==1){//温度数据显示
DT[0]=21;
DT[1]=10;
DT[2]=10;
DT[3]=10;
DT[4]=temp/1000%10;
DT[5]=temp/100%10+11;
DT[6]=temp/10%10;
DT[7]=temp%10;
}
else if(cmode==2){//距离数据显示
DT[0]=22;
DT[1]=10;
DT[2]=10;
DT[3]=10;
DT[4]=10;
DT[5]=10;
DT[6]=dis/10%10;
DT[7]=dis%10;
}
else if(cmode==3){//温度参数显示
DT[0]=24;
DT[1]=10;
DT[2]=10;
DT[3]=1;
DT[4]=10;
DT[5]=10;
DT[6]=ctemp/10%10;
DT[7]=ctemp%10;
}
else if(cmode==4){//距离参数显示
DT[0]=24;
DT[1]=10;
DT[2]=10;
DT[3]=2;
DT[4]=10;
DT[5]=10;
DT[6]=cdis/10%10;
DT[7]=cdis%10;
}
else {//变更次数显示
DT[0]=23;
DT[1]=10;
DT[2]=10;
DT[3]=cge>=10000?cge/10000:10;
DT[4]=cge>=1000?cge/1000%10:10;
DT[5]=cge>=100?cge/100%10:10;
DT[6]=cge>=10?cge/10%10:10;
DT[7]=cge%10;
}
}
void KeyConsole(){
if(keytime>8){
keytime=0;
key_scan();
if(rkey){
switch(rkey){
case 16:
//参数次数变动
if(cmode==3){
cge++;
if(ctemp==0)return;
ctemp-=2;
}
else if(cmode==4){
cge++;
if(cdis==0)return;
cdis-=5;
}
savef=1;
break;
case 17:
if(cmode==3){
ctemp+=2;
cge++;
}
else if(cmode==4){
cdis+=5;
cge++;
}
savef=1;
break;
default:
break;
}
}
}
}
void main(){
UartInit();//定时器2的串口初始化
Timer1Init();
sys_init();
Timer0Init();
while(1){
//按键扫描
KeyConsole();
//界面显示
if(smgtime>48){
smgtime=0;
UISetting();
Judgement();
if(savef){
if(savef==1){//将改变次数保存到epprom ,分两次保存
EppromW(0,cge/256);
savef=2;
}
else if(savef==2){
EppromW(1,cge%256);
savef=0;
}
}
}
if(dact>479){
dact=0;
DacOut();
}
//测温
if(tempt>540){
//
tempt=0;
temp=ReadTemp()*100;
}
//超声波测距
if(dist>=898){
dist=0;
dis=get_dis();
}
}
}
void TIME1() interrupt 3{//
smg_play(DT[s],s++);
if(s>7)s=0;
smgtime++;
keytime++;
dist++;
tempt++;
dact++;
los();
if(ledflush++==10){
ledflush=0;
LedRunning();
}
}
void UART1() interrupt 4{
ReceiveDate();
}
void ReceiveDate(){
u8 rec;
if(RI==1){ //每次接受完字符后判断
RI=0;
rec=SBUF;
switch(bstate){
case 0:
if(rec=='S')bstate=1;
else if(rec=='P')bstate=5;
else bstate=13;
break;
case 1:
if(rec=='T')bstate=2;
else bstate=13;
break;
case 2:
if(rec=='\r')bstate=3;
else bstate=13;
break;
case 3:
if(rec=='\n')bstate=4;
else bstate=13;
break;
case 5:
if(rec=='A')bstate=6;
else bstate=13;
break;
case 6:
if(rec=='R')bstate=7;
else bstate=13;
break;
case 7:
if(rec=='A')bstate=8;
else bstate=13;
break;
case 8:
if(rec=='\r')bstate=9;
else bstate=13;
break;
case 9:
if(rec=='\n')bstate=10;
else bstate=13;
break;
}
if(bstate==4){//查询数据指令
sendbuf[0]='$';
sendbuf[1]=dis/10%10+48;
sendbuf[2]=dis%10+48;
sendbuf[3]=',';
sendbuf[4]=temp/1000%10+48;
sendbuf[5]=temp/100%10+48;
sendbuf[6]='.';
sendbuf[7]=temp/10%10+48;
sendbuf[8]=temp%10+48;
sendbuf[9]='\r';
sendbuf[10]='\n';
sendbuf[11]='\0';
SendString(sendbuf);
bstate=0;
}
//查询参数指令
//返回当前的距离参数和温度参数
else if(bstate==10){
sendbuf[0]='#';
sendbuf[1]=cdis/10%10+48;
sendbuf[2]=cdis%10+48;
sendbuf[3]=',';
sendbuf[4]=ctemp/10%10+48;
sendbuf[5]=ctemp%10+48;
sendbuf[6]='\r';
sendbuf[7]='\n';
sendbuf[8]='\0';
SendString(sendbuf);
bstate=0;
}
if(bstate==13){
bstate=0;
//发送错误
SendString("ERROR\r\n");
}
}
}
sys.c
#include "sys.h"
u8 key_state=0;
u8 rkey=0;
u8 key_v=0;
u8 DT[]={10,10,10,10,10,10,10,10};
u8 code SMGINDEX[]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90,0xff,
0xc0&0x7f,0xf9&0x7f,0xa4&0x7f,0xb0&0x7f,0x99&0x7f,0x92&0x7f,
0x82&0x7f,0xf8&0x7f,0x80&0x7f,0x90&0x7f,
0xc6,0xc7,0xc8,0x8c //C L n p
};
u8 code LED[]={0xff,0xfe,0xfd,0xfb};
u8 LEDDT[]={0,0,0};
void LedRunning(){
P0=0xff;
P0=LED[LEDDT[0]]&LED[LEDDT[1]]&LED[LEDDT[2]];
lock(4);
}
void sys_init(){
P0=0xff;
lock(4);
P0=0;
lock(5);
P0=0;
lock(6);
P0=0xff;
lock(7);
}
void smg_play(u8 du,u8 we){
P0=0xff;
lock(7); //消影
P0=0;
P0=1<<we;
lock(6);
P0=0xff;
P0=SMGINDEX[du];
lock(7);
}
void key_scan(){
switch(key_state){
case 0:
key_v=0;
P32=1;
P33=1;
P34=0;
P35=0;
if(P33!=1||P32!=1)key_state=1;
break;
case 1:
P32=1;
P33=1;
P34=0;
P35=0;
if(P32!=1||P33!=1){
if(!P32)key_v=5;
else if(!P33)key_v=4;
P34=1;
P35=1;
P32=0;
P33=0;
if(!P34)key_v+=12;
else if(!P35)key_v+=8;
rkey=key_v;
key_state=2;
}
else
key_state=0;
break;
case 2:
P32=1;
P33=1;
P34=0;
P35=0;
rkey=0;
if(P32==1&&P33==1)key_state=0;
break;
default:
break;
}
}
void Timer1Init(void) //1毫秒@12.000MHz
{
AUXR &= 0xBF; //定时器时钟12T模式
TMOD &= 0x0F; //设置定时器模式
TL1 = 0x18; //设置定时初始值
TH1 = 0xFC; //设置定时初始值
TF1 = 0; //清除TF1标志
TR1 = 1; //定时器1开始计时
EA=1;
ET1=1;
}
sys.h
#ifndef __SYS_H_
#define __SYS_H_
#include "mystc.h"
#define lock(x) P2=P2&0x1f|(x<<5); P2=P2&0x1f;
typedef unsigned char u8;
typedef unsigned int u16;
void sys_init();
void smg_play(u8 du,u8 we);
void key_scan();
void Timer1Init(void);
float ReadTemp();
u16 get_dis();
void Timer0Init(void);
void LedRunning();
void Pcf_W_Dac(u8 dat);
void EppromW(u8 word,u8 dat);
void SendString(u8 *str);
void Send_char(u8 q);//发送单个字符
void UartInit(void);
u8 DT[];
void ReceiveDate();
u8 LEDDT[];
extern u8 rkey;
extern u8 key_v;
#endif
其他的是外设驱动文件,需要用的函数都在sys.h里进行声明就行
onewire.c
/*
程序说明: 单总线驱动程序
软件环境: Keil uVision 4.10
硬件环境: CT107单片机综合实训平台(外部晶振12MHz) STC89C52RC单片机
日 期: 2011-8-9
*/
#include "sys.h"
sbit DQ = P1^4; //单总线接口
//单总线延时函数
void Delay_OneWire(unsigned int t) //STC89C52RC
{
u8 i=0;
while(t--){
for(i=0;i<10;i++);
}
}
//通过单总线向DS18B20写一个字节
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);
}
//从DS18B20读取一个字节
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;
}
//DS18B20设备初始化
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 ReadTemp(){
u8 low,high;
u16 temp=0;
init_ds18b20();
Write_DS18B20(0xcc);
Write_DS18B20(0x44);
init_ds18b20();
Write_DS18B20(0xcc);
Write_DS18B20(0xbe);
low=Read_DS18B20();
high=Read_DS18B20();
temp=high<<8|low;
return temp*0.0625;
}
sonnic.c(用定时器1进行定时算超声波距离)
#include "sys.h"
#include "intrins.h"
//用定时器0来计算时间
sbit RX=P1^1;
sbit TX=P1^0;
void Delay12us() //@12.000MHz
{
unsigned char i;
_nop_();
_nop_();
i = 33;
while (--i);
}
void Timer0Init(void) //100微秒@12.000HZ 定时器0做超声波计时器
{
AUXR &= 0x7F; //定时器时钟12T模式
TMOD &= 0xF0; //设置定时器模式
TL0 = 0x00; //设置定时初始值
TH0 = 0x00; //设置定时初始值
TF0 = 0; //清除TF0标志
TR0 = 0; //定时器0开始计时
}
void csb_start(){
u8 i=8;
EA=0;
while(i--){
TX=1;
Delay12us();
TX=0;
Delay12us();
}
EA=1;
}
u16 get_dis(){
u16 dis;
RX=1;
csb_start();
TR0=1;
while(RX==1&&TF0==0);
TR0=0;
if(!TF0){
dis=TH0<<8|TL0;
dis=dis*0.017;
if(dis<=2||dis>=400)dis=999;
}
else {
dis=999;
}
TH0=TL0=0;
return dis;
}
iic.c
/*
程序说明: IIC总线驱动程序
软件环境: Keil uVision 4.10
硬件环境: CT107单片机综合实训平台 8051,12MHz
日 期: 2011-8-9
*/
#include "sys.h"
#include "intrins.h"
#define DELAY_TIME 5
#define SlaveAddrW 0xA0
#define SlaveAddrR 0xA1
//总线引脚定义
sbit SDA = P2^1; /* 数据线 */
sbit SCL = P2^0; /* 时钟线 */
void IIC_Delay(unsigned char i)
{
do{_nop_();}
while(i--);
}
//总线启动条件
void IIC_Start(void)
{
SDA = 1;
SCL = 1;
IIC_Delay(DELAY_TIME);
SDA = 0;
IIC_Delay(DELAY_TIME);
SCL = 0;
}
//总线停止条件
void IIC_Stop(void)
{
SDA = 0;
SCL = 1;
IIC_Delay(DELAY_TIME);
SDA = 1;
IIC_Delay(DELAY_TIME);
}
//发送应答
void IIC_SendAck(bit ackbit)
{
SCL = 0;
SDA = ackbit; // 0:应答,1:非应答
IIC_Delay(DELAY_TIME);
SCL = 1;
IIC_Delay(DELAY_TIME);
SCL = 0;
SDA = 1;
IIC_Delay(DELAY_TIME);
}
//等待应答
bit IIC_WaitAck(void)
{
bit ackbit;
SCL = 1;
IIC_Delay(DELAY_TIME);
ackbit = SDA;
SCL = 0;
IIC_Delay(DELAY_TIME);
return ackbit;
}
//通过I2C总线发送数据
void IIC_SendByte(unsigned char byt)
{
unsigned char i;
for(i=0; i<8; i++)
{
SCL = 0;
IIC_Delay(DELAY_TIME);
if(byt & 0x80) SDA = 1;
else SDA = 0;
IIC_Delay(DELAY_TIME);
SCL = 1;
byt <<= 1;
IIC_Delay(DELAY_TIME);
}
SCL = 0;
}
//从I2C总线上接收数据
unsigned char IIC_RecByte(void)
{
unsigned char i, da;
for(i=0; i<8; i++)
{
SCL = 1;
IIC_Delay(DELAY_TIME);
da <<= 1;
if(SDA) da |= 1;
SCL = 0;
IIC_Delay(DELAY_TIME);
}
return da;
}
void EppromW(u8 word,u8 dat){
IIC_Start();
IIC_SendByte(0xa0);
IIC_WaitAck();
IIC_SendByte(word);
IIC_WaitAck();
IIC_SendByte(dat);
IIC_WaitAck();
IIC_SendAck(1);
IIC_Stop();
}
void Pcf_W_Dac(u8 dat){
IIC_Start();
IIC_SendByte(0x90);
IIC_WaitAck();
IIC_SendByte(0x40);
IIC_WaitAck();
IIC_SendByte(dat);
IIC_WaitAck();
IIC_SendAck(1);
IIC_Stop();
}
uart.c
#include "sys.h"
void UartInit(void) //4800bps@12.000MHz
{
SCON = 0x50; //8位数据,可变波特率
AUXR |= 0x01; //串口1选择定时器2为波特率发生器
AUXR |= 0x04; //定时器时钟1T模式
T2L = 0x8F; //设置定时初始值
T2H = 0xFD; //设置定时初始值
AUXR |= 0x10; //定时器2开始计时
ES=1;
}
void Send_char(u8 q){
ES=0;
SBUF=q;
while(!TI);
TI=0;
ES=1;
}
void SendString(u8 *str){
while(*str!='\0'){
Send_char(*str);
str++;
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?