蓝桥杯单片机省赛 第七届(代码+分析)

  • Post author:
  • Post category:其他

 首先,先讲一下我编写代码的流程(后以此为例,不再重复):

  无论是省赛还是国赛,都会发现数码管显示及矩阵按键是必不可少的,所以我的第一步是还原动态数码管的内容,通过给org数组赋值,让数码管显示我想要显示的内容。确保这部分无误后,还原矩阵按键模块,通过按键改变org数组内的内容,改变数码管显示,如与预期相同,在进行下一步。我对我完成这道省赛的时间做一个展示,希望能对你起到帮助。

分析题目,是否需要数码管和矩阵按键 约2分钟
让数码管显示1234.5678 约20分钟
通过矩阵按键改变数码管的显示内容 约20分钟
阅读题目,理解系统需求 约10分钟
针对S4按键,修改模式标志 约2分钟
针对S5按键,修改时间,开启倒计时(完成编写后出现两个BUG:1.逻辑错误,时间直接从0变为2分钟,从未出现1分钟的显示 2.修改完1问题后矩阵按键按下一次识别多次,加入delay降低识别速度) 约30分钟(大部分为修改BUG)
针对S9按键,完成清除时间功能 约2分钟
完成不同模式下的LED灯亮灭(完成编写后出现一个BUG:LED等有闪烁现象,原因经分析为中断抢占P2的使用。这个问题我从底层分析,说起来容易,实际上很多人根本找不到原因,必须要很理解原理,不断临时废除部分代码才能找出,具体内容我们下面再说) 约40分钟(绝大部分为修改BUG)
完成PWM输出(此处还增加了LED,因为周围没有示波器,又要现象可被观察) 约15分钟
针对S8,增加温度模块(完成编写后出现一个BUG:按键有失灵的情况,经过分析,读取温度在delay参数的控制之外,又放慢了矩阵按键的识别速度,我选择让读取温度在delay参数的控制之内) 约30分钟(大部分为修改BUG)
再次审题,检测修饰功能(比如count=0这样的小细节,使得系统更加精准) 约10分钟

这样的时间还是挺可以的,剩余时间超过1小时,客观题时间再长,我们时间也充足。一个一个功能的实现,立刻烧录检测是否存在问题,这样的习惯可以更好地解决一些问题。但与同学们交流以后发现很多同学交流后发现,他们习惯于将整体代码全部书写完毕以后直接烧录,然后进行代码的改错、修饰,但是我想说很多BUG是你预测不到的,如果一起写之后再修改,除非你对很多问题已经非常熟悉,又或者你的分析能力超群,否则一定会陷入迷茫。个人感觉自己的水平在中上吧,点对点的修改BUG时间都占了编程的约一半时间。初学者,个人认为,一步步脚踏实地,最好。

下面我们进入代码编程,整体看完后,我在讲讲我BUG的修改思路(函数后有“/*相同*/”代表与之前模块的内容几乎相同【此处默认你是跟着我写的文章一步步学的】。因为自己在写系统时不会看之前的模块,所以参数名字会有区别,其他几乎相同。):

#include "STC15F2K60S2.H"
#include "stdio.h"
#include "onewire.h"/*与我写的模块相同,与官方不同*/

typedef unsigned int u16;
typedef unsigned char u8;

u8 org[10],tran[9],wei,old,new,mode=1,time,dis,led,delay,flag,count1,low,high,*low1,*high1;
u16 count,te;/*需要16位的数字用无符号整型!!!*/
sfr clock = 0x8f;/*为打开PWM输出做准备*/

void lighten(u8 led)/*相同*/
{
  P0=0XFF;
  P2=P2&0X1F|0X80;
  P2=P2&0X1F;
  P0=~led;
  P2=P2&0X1F|0X80;
  P2=P2&0X1F;
}

void close()/*相同*/
{
  P0=0;
  P2=P2&0X1F|0XA0;
  P2=P2&0X1F;
  P0=0XFF;
  P2=P2&0X1F|0X80;
  P2=P2&0X1F;
}

void open()/*多打开了一个定时器1*/
{
  EA=1;
  ET0=1;
  ET1=1;
}

void Timer1Init(void)/*相同*/		//1毫秒@12.000MHz
{
	AUXR |= 0x40;		//定时器时钟1T模式
	TMOD &= 0x0F;		//设置定时器模式
	TL1 = 0x20;		//设置定时初值
	TH1 = 0xD1;		//设置定时初值
	TF1 = 0;		//清除TF1标志
	TR1 = 1;		//定时器1开始计时
}

void translate(u8 org[],u8 tran[])/*相同*/
{
  u8 j,k,mid0;
  for(j=0,k=0;j<8;j++,k++)
  {
    switch(org[k])
	{
	  case'0':mid0=0xc0;break;
	  case'1':mid0=0xf9;break;
	  case'2':mid0=0xa4;break;
	  case'3':mid0=0xb0;break;
	  case'4':mid0=0x99;break;
	  case'5':mid0=0x92;break;
	  case'6':mid0=0x82;break;
	  case'7':mid0=0xf8;break;
	  case'8':mid0=0x80;break;
	  case'9':mid0=0x90;break;
	  case'-':mid0=0xbf;break;
	  case'c':mid0=0xc6;break;
	  default:mid0=0xff;
	}
	if(org[k+1]=='.')
	{
	  mid0&=0x7f;
	  k++;
	}
	tran[j]=mid0;
  }
}

u8 key_translate()/*相同*/
{
  u16 key;
  u8 key_return;
  P34=0;P35=1;P42=1;P44=1;
  key=(key<<4)+(P3&0x0f);
  P34=1;P35=0;P42=1;P44=1;
  key=(key<<4)+(P3&0x0f);
  P34=1;P35=1;P42=0;P44=1;
  key=(key<<4)+(P3&0x0f);
  P34=1;P35=1;P42=1;P44=0;
  key=(key<<4)+(P3&0x0f);
  switch(~key)
  {
    case 0x1000:key_return=19;break;
	case 0x2000:key_return=18;break;
	case 0x4000:key_return=17;break;
	case 0x8000:key_return=16;break;
	case 0x0100:key_return=15;break;
	case 0x0200:key_return=14;break;
	case 0x0400:key_return=13;break;
	case 0x0800:key_return=12;break;
	case 0x0010:key_return=11;break;
	case 0x0020:key_return=10;break;
	case 0x0040:key_return=9;break;
	case 0x0080:key_return=8;break;
	case 0x0001:key_return=7;break;
	case 0x0002:key_return=6;break;
	case 0x0004:key_return=5;break;
	case 0x0008:key_return=4;break;
	default:key_return=0;
  }
  return key_return;
}

void Timer0Init(void)/*相同*/		//100微秒@12.000MHz
{
	AUXR |= 0x80;		//定时器时钟1T模式
	TMOD &= 0xF0;		//设置定时器模式
	TL0 = 0x50;		//设置定时初值
	TH0 = 0xFB;		//设置定时初值
	TF0 = 0;		//清除TF0标志
	TR0 = 1;		//定时器0开始计时
}

void keydo()
{
  new=key_translate();
  if(new!=old&&new!=0)
  {
    if(new==4) 
	{
	  if(++mode==4) mode=1;/*三种模式来回切换*/
	  switch(mode)
	  {
	    case 1:flag=2;count1=0;break;/*flag用来控制占空比*/
		case 2:flag=3;count1=0;break;
		case 3:flag=7;count1=0;break;
	  }
	}
	if(new==5)
	{
	  time+=60;/*调整时间*/
	  if(time>=180) time=0;
	  else if(time>=120) time=120;
	  else time=60;
	  count=0;
	} 
	if(new==9) time=0;/*时间归0*/
	if(new==8) if(++dis==2) dis=0;/*dis区分显示内容*/
  }
  old=new; 
}

void display(u8 tran[],u8 wei)/*相同*/
{
  P0=0xff;
  P2=P2&0X1F|0XE0;
  P2=P2&0X1F; 
  P0=1<<wei;
  P2=P2&0X1F|0XC0;
  P2=P2&0X1F;
  P0=tran[wei];
  P2=P2&0X1F|0XE0;
  P2=P2&0X1F;
}

void disled()/*LED显示*/
{
  if(time==0) led=0;/*时间为0,关闭灯*/
  else
  	  switch(mode)/*根据模式,点亮相应灯。注意同时要熄灭其他灯*/
	  {
	    case 1:led=led&0xf8;led=led|0x01;break;
		case 2:led=led&0xf8;led=led|0x02;break;
		case 3:led=led&0xf8;led=led|0x04;break;
	  }
}

void main()
{
  low1=&low;high1=&high;
  clock=clock|0x01;/*打开PWM输出,本系统通过P34,也就是定时器0进行输出,《用户手册》488页*/
  close();
  rd_temperature(low1,high1);
  Timer1Init();
  Timer0Init();
  open();
  while(1)
  {
    disled();
    translate(org,tran);
	if(time) lighten(led);
	if(!delay)
	{
   	  keydo();
	  rd_temperature(low1,high1);	
	  delay=1; 
	}
    if(dis==0)	sprintf(org,"-%1u- %4u",(u16)mode,(u16)time);
	if(dis==1)	
	{
	  te=(low+high*256);
	  sprintf(org,"-4-  %2uc",(u16)(te/16.0));
    }
  }
}

void time0() interrupt 1
{
  if(++wei==8) wei=0;
  display(tran,wei);
  if(time!=0) lighten(led);
  else lighten(0);
  if(++count1==10) count1=0;
  if(time!=0&&count1<=flag)/*1KHZ信号,周期就是1毫秒,自然风为例,就需要300微秒高电平,700微秒低电平,分析完所有模式之后,决定以100微秒为基本单位,进行编程*/
  {
    P34=1;
	led=led|0x80;
  }
  else
  {
    P34=0;
	led=led&0x7f;   
  }
}

void time1() interrupt 3
{
  if(++delay==10) delay=0;
  count++;
  if(count==1000)
  {
   	count=0;
	if(time>0) time--;/*倒计时*/
  }
}

为了方便代码的理解,我将参数的意义再次做成表格:

mode 初值为1,记录的是系统工作模式
time 系统剩余工作时间。为0时代表系统停止工作,PWM输出停止,灯不亮,倒计时停止。所以在多处限制函数运行。
dis 显示内容,决定是显示温度还是工作状态。
delay 降速,判断时间间隔延长可有效防止矩阵按键多次识别(有时例如竞争冒险等还是会影响输出的结果)。
flag 限制PWM(本系统选择P34,定时器0)高低电平每个周期占空比。
count1 依据flag,判断高低电平是否需要改变。
count 倒计时用。

最后我解释BUG的修改过程:

  简单的BUG就不做解释了,跟着自己的代码走就能发现的错误(也就是逻辑错误),完全不足为惧!我选择两个讲解。

  文章开头表格中提到:“针对S8,增加温度模块(完成编写后出现一个BUG:按键有失灵的情况,经过分析,读取温度在delay参数的控制之外,又放慢了矩阵按键的识别速度,我选择让读取温度在delay参数的控制之内)。”怎么发现的呢?当我显示温度后,矩阵按键识别很慢,但显示系统工作状态时矩阵按键又正常,所以一定是按下S8后的问题,在先前的S8中,我有识别温度的函数,也就是每次main函数while(1)执行一次,因为S8按下,都要执行读取温度函数,而温度模块又是单线,相比速度低,导致keydo执行的周期瞬间提高。我选择降低执行读取温度函数频率,受delay控制。当然有很多种方法啦,我只选我最先想到的。

  文章开头表格中提到:“完成不同模式下的LED灯亮灭(完成编写后出现一个BUG:LED等有闪烁现象,原因经分析为中断抢占P2的使用。这个问题我从底层分析,说起来容易,实际上很多人根本找不到原因,必须要很理解原理,不断临时废除部分代码才能找出,具体内容我们下面再说)”

  数码管和led的锁存器最终都是通过P2选择的,所以如果一个位于主函数(lighten函数),一个位于中断(display函数)就会相互干扰。原理可以看:

蓝桥杯单片机模块代码(LED)(代码+注释)_tuygre的博客-CSDN博客

代码里有完整的解释。 

头文件onewire有修改,具体看蓝桥杯单片机模块代码(DS18B20温度测量)(代码+注释)_tuygre的博客-CSDN博客

其实修BUG才是比赛的难度所在,这需要独立分析的能力,有时还需要基础知识,理解原理。不行就多练吧!之后每周至少更新一期省赛,希望关注! 

官方提供的原理图,用户手册等下载地址如下:

链接:https://pan.baidu.com/s/1y8lRYHxLKojL4_r0PZPYRw 
提取码:19so

注释无法插入图片,提到的相关信息读者自己在文件夹中寻找。

南京信息工程大学本科学生学习笔记,供大家参考。

如有错误,联系QQ3182097183。


版权声明:本文为tuygre原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。