第十一届蓝桥杯电子类国赛总结回顾

2020年11月14日,蓝桥杯电子类国赛终于结束了,准备了挺久的。决赛没考超声波和串口,有点意外,题目较为简单这也意味着今年的竞争会格外激烈了,祝愿自己能有个好成绩吧哈哈。已经大三了,这应该是我唯一一次参加电子类了。下次还有机会参加蓝桥杯的话应该回去玩玩C语言组,虽然自己C语言菜的不谈哈哈,整理一下资料,写个总结吧,希望对之后参加这个比赛的同学能有一点点帮助。
备赛期间学习的是电子设计工坊的资料(可以上淘宝搜索即可-所以看过他们视频的同学应该会对我整理的概念会比较清楚)。

省赛和国赛真题和模拟题的源码放在之后的博客里吧,不然太长了,大家有需要的去看一下就行。省赛的不全(重做了一次电脑,没了)。

冲鸭!
tips: 1. shift+tab 选中的代码块整体左移
2.有时会出现程序实际上没有烧录进去,此时要重新生成一下hex文件

组合按键时候使用(替代if)
#define SetKeyBoard(x) P4= (x>>3) | (x>>4); P3=x //规律:上下对称结构,上面右移3,4;下面左移3,4
#define GetKeyBoard() ((P4&0x10)<<3) | ((P4&0x04)<<4) | (P3&0x3f)

注意组合按键时候给出任意两个按键的组合,根据电路图判断出对应键值(多多练习)

1.一定要把底层驱动的.h文件复制到c文件所在文件夹才能生效
矩阵按键键值对应码的确定:分别令横向和纵向为低电平,得到两组数据,在相“或”得到最终结果

ds1302写入和读出的数据均为BCD码
EEPROM:是一种掉电数据不丢失的存储芯片

3.第十届蓝桥杯比赛驱动代码:
只有单总线协议onewire需要更改(延时扩大12倍,用for循环)

4.用定时器计算器得到的定时器初始化代码要自己补充ET0=1和EA=1.

  1. PCF8591写:0x90, 读:0x91
    AT24C02写:0xa0, 读:0xa1

6.定时器中时间的判断可用大于,而不用等于
读取温度时候要先关闭定时器ET0=0,读完温度后再打开ET0=1.(iic读电压等也要用此方式,保护时序)

7.当既有蜂鸣器控制,又有继电器控制时,可以设置两个标志位,在循环中整体分四种情况讨论(具体见第五届模拟题灌溉系统代码)

8.尽量不用浮点型(见自动售水机)

9.超声波发送与接收引脚定义:#define TX P10
#define RX P11

10.LED微亮情况的解决:先送P0信号,防止在打开锁存器的瞬间P0处于不确定状态(见省赛彩灯控制器)

11.写AT24C02要有延时10ms(用delay)(可在开头先写一次,排除先前别人写入的干扰,即在while(1)之前写一次,然后再删去该部分程序即可)

12.ds18b20温度读取一开始的85消除方法

void main()                 //先读取10次
{
  u8 keyvalue;
  P2=0x80;P0=0xff;P2=0;
  P2=0xa0;P0=0;P2=0;
  Timer0Init();
  set_sfm(23,59,50);
  for(chuli_i=0;chuli_i<10;chuli_i++)
  {
    ET0=0;
    temp_chuli=(u8)rd_temperature_f();
    ET0=1;
  }
  while(1)

13.对LED的操作可以设各个灯的代表比如led1,led2,led3等等,最后用led1| led2 | led3的形式总和即可

14.ADC: A表示模拟信号,D表示数字信号
注意看DAC的输出函数(记忆):电压跟随效果

15.用PCF8591电压值模拟湿度的时候,读取的数值应该除以255.f(256.f)整除的话得到的一直是0

16.要用到定时器和计数器时候,定时器的配置直接复制STC,计数器的配置自己写,不用配置AURX(详见第十届省赛代码部分)
比如用定时器0当做计数器,则直接写TMOD & =0xf0 //保留高四位(之前对定时器1的配置),将第四位置0便于下一行对计数器的配置

TMOD | = 0x04;
TH0=0;
TL0=0;
ET0=1;
TR0=1;
EA=1;
***注意***1.IAP15单片机M1M0=0016位自动重装载,不用在中断中再次进行赋初值。
     2.NE555频率的读取要从超声波模块取下一个连接子,接到P34和SIGNAL上。
     3.计数器读取频率50ms比较合适,之后乘上20即可。
     4.TR0对于定时器0的定时和计数功能都有影响。
     5.T0设置为计数器时,P3^4每一个下降沿都会进入中断,寄存器值加1
  1. IAP15的超声波使用关键:
    (1).定时器T0直接复制STC代码即可,T1的使用要么不加AUXR的赋值,要么只能用12T模式(用1T会出错,数值会很大,与实际不符)。
    (2)超声波的定时器1中要把中断使能ET1关闭(ET1=0),定时开关TR1关闭(TR1=0)。用标志位手动清零的方式,这样能实现显示999状态。

18.判断密码相同,串口发送相同,可以定义一个函数来判断(return 0 or return 1)

19.同时读取PCF8591的0x03和0x01时,有可能出现数据串位现象,(此时交换下0x01和0x03即可)

20.串口发送的是ASCII码值,所以如果是数字,要加 ’ 0 ',进行转换

21.定义二维数组前面加 xdata (因为数组太大会越界)

22.led灯的操作最好是给每种状态设flag,最后统一对P0赋值

23.最高位的零不显示的方法:
以前使用判断位数,然后分别赋值的方法,过于繁琐。
简便方法如下:
在正常的8位数组赋值之后再加一个for循环:

for(i=0;fre_display[i]==smg_duan[0];i++)
{
   display[i]=0x00;  //数码管如果是显示数字0,就赋值位0x00(不显示)
}

24.自己写头文件的时候要注意最后要加#endif
注意extern的用法,在头文件中声明(不赋值),在对应的c文件中定义

25.蓝桥杯第十一届模拟题总结:
待解决:左移操作的便捷方法

删除操作:

for(j=6;j>6-input_index;j--)    //每按下一次删除键,删除一位,从左向右移动一次
 {
    modi_code_display[j+1]=modi_code_display[j];
 }
 modi_code_display[7-input_index]=0x00;
 input_index--;         

26.注意单总线协议的延时补充代码

void Delay_OneWire(unsigned int t) 
{
  unsigned char i;       //特别注意:for循环要放在while循环里面,否则会出错
  while(t--)
  {
    for(i=0;i<12;i++);
  }
}

27.再访串口:(2020.4.2)
ES=1 串行口中断标志位
ET1 是中断允许标志位 在串口得初始化中不能开启ET1 (因为串口已经有中断了,再开启定时器中断会产生干扰)
TR1 是定时器开启标志

void UartInit(void)  //1200bps@11.0592MHz
{
    SCON = 0x50; //8位数据,可变波特率
    AUXR |= 0x40; //定时器1时钟为Fosc,即1T
    AUXR &= 0xFE; //串口1选择定时器1为波特率发生器
    TMOD &= 0x0F;  //设定定时器1为16位自动重装方式
    TL1 = 0x00;  //设定定时初值
    TH1 = 0xF7;  //设定定时初值           
    ET1 = 0; //禁止定时器1中断  不要开启定时器1的中断标志位ET1
    TR1 = 1;  //启动定时器1      
     
    ES=1;     //串行口中断标志位 从该行开始需要自己写
    EA=1;
    SendString("STC15F2K60S2\r\nUart Test !\r\n");
}

28.关于EEPROM。
写EEPROM的时候要注意写入的数据是否大于256,如果是则要做相应的除法处理。

29.长短按,双击中,key_return要手动赋初值为0:(u8 key_return=0;)

30.关于NE555
要用到P34,所以要把定时器0作为计数器,P34为定时器0的计数器外部脉冲口

  1. 关于三位一体综合键盘
    (1) 三位一体矩阵键盘出现按一下跳动很多时候,考虑时候是返回值写成了key_val,要改成key_return。
    (2) 双击函数中出现只有第一次双击有效,后面只能执行单击,考虑是否为没有写key_time_1=0在相应位置。
    (3)如果出现长按和组合按键实现不了,单击和双击能实现,则检查是否在key_long_short_double_click()函数中的case KEY_STATE_0中没有写else{key_return=key_val_1)},只写了if(key_val_1>0 && key_val_1<50)
    (4)组合按键出现按下组合键时候,连续加两次值,表明没有松手检测,去检查有没有在key_state_4里转到松手检测的key_state_3, 有可能误写成转到 key_state_0了。
    (5)检查底层有没有修改 复制后的各个temporary的值。
    (6) 出现任何按键都没有反应时,可能是switch中的。key_state=key_state_1误写成了key_state_0,(key_long_short_click和key_long_short_double_click()这两个函数的switch都需要检查)。
    (7)key_long_short_double_click()中最后是key_return=key_prev_1(不是key_val_1),因为此时处于第一次单击后的等待时间,是无按键状态,超过500ms后此时是无按键状态。

33.NE555频率读取出现数码管数值一直增加,到65536后清零又重新累加的情况分析

if(fre_flag)
 {
   fre_flag=0;
   TR0=0;       //计数器关闭
   fre=TH0*256+TL0;//在计数器关闭和重新打开之间要对TH0和TL0清零处理                    
   TH0=TL0=0;
   TR0=1;
   fre*=20;
   ...
  }   

34.DAC输出代码

void SetDACOut(unsigned char val)          //set DAC
{
  IIC_Start();
  IIC_SendByte(0x90);               //写操作的地址
  IIC_WaitAck();
  IIC_SendByte(0x40);               //DAC
  IIC_WaitAck(); 
  IIC_SendByte(val);                //传值
  IIC_WaitAck();
  IIC_Stop();
}

35.矩阵键盘长按不松手数值一直加(没有松手检测)

case key_state_2:
    if(key_val==NO_KEY)
    {
      key_state=key_state_0;
      key_return=key_prev;    //short
    }
    else
    {
      key_time++;
      if(key_time>=50)
      {
        key_state=key_state_3;             //此处没有到key_state_3中,则没有松手检测
        key_return=key_val+100;  //long    //key_state_3是松手检测
      }
    }
    break;

36.串口之extern
1.extern用在变量声明中常常有这样一个作用,你在*.c文件中声明了一个全局的变量,这个全局的变量如果要被引用,就放 在*.h中并用extern来声明。

2.如果函数的声明中带有关键字extern,仅仅是暗示这个函数可能在别的源文件里定义,没有其它作用。即下述两个函数声明没有区别:

uart.c中定义为 unsigned char rec_table[6]
unsigned char rec_index=0;

uart.h中声明时才要加extern:
extern unsigned char rec_table[6]
extern unsigned char rec_index;

37.关于驱动代码 : 一个_nop_()等于一个机器周期
iic: 延时上的不同写法(核心:要延时10个机器周期以上)

写法一:(第十届资源数据包)
#define DELAY_TIME 5

void IIC_Delay(unsigned char i)
{
    do{_nop_();}
    while(i--);        
}

void IIC_Start(void)//主要看SDA和SCL后的延时方法即可得知延时方法
{
    SDA = 1;
    SCL = 1;
    IIC_Delay(DELAY_TIME);
    SDA = 0;
    IIC_Delay(DELAY_TIME);
    SCL = 0;    
}
//------------------------------------------------------------

写法二:(用somenop作延时)
#define somenop {_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();} 
                                //10个机器周期以上 
void IIC_Start(void)
{
    SDA = 1;
    SCL = 1;
    somenop;               
    SDA = 0;
    somenop;
    SCL = 0;    
}


38.串口中16进制发送转换成10进制

u8 hextoDec(u8 hex)  
  /*16进制->10进制,用于显示到数码管上(字符也可转换,如FF->255) 1111   1111  = 2 ^ 8   */   
{                                                                                                       
    u8 sum=0;  
    u8 mul=1;
   u8 count=0;
     u8 i;
     u8 r;
     do{
         r=hex%16;
         for(i=0;i<count;i++)
    		 mul*=16;
         mul*=r;
         sum+=mul;
         mul=1;
         count++;
       }while(hex/=16);
    return sum;             //最后的十进制数
}

39 .单片机ISP下载软件串口助手的hex模式和文本模式的区别 首先hex模式是十六进制模式,当我们用电脑以hex模式给单片机USART口发数据时,发的是十六进制,单片机接收的也是十六进制;如果要转换为10进制数,要用相应的算法进行处理。 当我们用电脑以文本模式给单片机发数据时,只能发字母(0-9,a-z,A-Z等其他符号),单片机收到的也是字母!总结:不论你发的是什么,单片机收到的都是ASCII码。2.注意: 向ds1302直接输入时分秒时不能直接写入,必须先把十进制的数据转化为BCD码,这时候写入才是有效的数据。
BCD码:用4位二进制数来表示十进制数中的0~9这十个数码的编码形式,简称为BCD码。
0000 ~ 1001

void set_sfm(unsigned char shi,unsigned char fen,unsigned char miao)
/*该写法是将10进制转换成BCD码写入的方法,区别于串口发送中的将16进制
  转换成10进制*/
 {
     Write_Ds1302_Byte(0x8e,0);    
     Write_Ds1302_Byte(0x80,miao/10*16+miao%10);   /
     Write_Ds1302_Byte(0x82,fen/10*16+fen%10);
     Write_Ds1302_Byte(0x84,shi/10*16+shi%10);
     Write_Ds1302_Byte(0x8e,0x80);
 }

4.串口发送为ASCII码值,所以如果单片机给电脑发数字的话程序中要加上 ’ 0 ’

'\r’是回车,使光标移到行首,(carriage return)0X0D
'\n’是换行,使光标下移一格,(line feed)0x0A

   Windows系统里面,每行结尾是“<回车><换行>”,即“\r\n”;

5.当出现读取NE555频率,连接P34和SIGNAL时,矩阵键盘要用普通形式,不能用移位形式,否则会有干扰,没有松手检测效果
但是矩阵键盘的普通形式无法实现组合按键。组合按键必须使用移位法矩阵键盘写法

#define NO_KEY 0                         //因为分两步写时,key_val得到的是0,所以NO_KEY要为0,而不是0xff

//-------------------底层------------------------
u8 key_scan()
{
  u8 key_temp,key_temporary=0;
  u8 key1,key2;
  P30=0;P31=0;P32=0;P33=0;P34=1;P35=1;P42=1;P44=1;
  if(P44==0)key1=0x70;
  if(P42==0)key1=0xb0;
  if(P35==0)key1=0xd0;
  if(P34==0)key1=0xe0;
  if((P44==1)&&(P42==1)&&(P35==1)&&(P34==1))key1=0xf0;



  P30=1;P31=1;P32=1;P33=1;P34=0;P35=0;P42=0;P44=0;
  if(P33==0)key2=0x07;
  if(P32==0)key2=0x0b;
  if(P31==0)key2=0x0d;
  if(P30==0)key2=0x0e;
  if((P33==1)&&(P32==1)&&(P31==1)&&(P30==1))key2=0x0f;
  key_temp=key1|key2;
  switch(key_temp)
  {
    case 0x77:key_temporary=4;break;
    case 0x7b:key_temporary=5;break;
    case 0x7d:key_temporary=6;break;
    case 0x7e:key_temporary=7;break;
    
    case 0xb7:key_temporary=8;break;
    case 0xbb:key_temporary=9;break;
    case 0xbd:key_temporary=10;break;
    case 0xbe:key_temporary=11;break;

    case 0xd7:key_temporary=12;break;
    case 0xdb:key_temporary=13;break;
    case 0xdd:key_temporary=14;break;
    case 0xde:key_temporary=15;break;

    case 0xe7:key_temporary=16;break;
    case 0xeb:key_temporary=17;break;
    case 0xed:key_temporary=18;break;
    case 0xee:key_temporary=19;break;
  }
  return key_temporary;
}
//----------------判断层------------------
u8 key_long_short_click()                       //key_val得到的是0,所以NO_KEY要为0,而不是0xff
{
  static unsigned char key_state=key_state_0,key_prev,key_time=0;
  u8 key_val=0,key_return=0;
  key_val=key_scan();
  switch(key_state)
  {
    case key_state_0:
    if(key_val!=NO_KEY)
    {
      key_state=key_state_1;
      key_prev=key_val;
    }
    break;
    case key_state_1:
    if(key_val==NO_KEY)
    {
      key_state=key_state_0;
    }
    else
    {
      key_state=key_state_2;
      key_time=0;
    }
    break;
    case key_state_2:
    if(key_val==NO_KEY)     //short
    {
      key_state=key_state_0;       
      key_return=key_prev;
    }
    else
    {
      key_time++;     //0.8s==800ms  10ms扫描一次  -->80
 /*  要求不松手一直快速增加,所以不进入松手检测key_state_3,key_state一  直是key_state_2,松手后进入switch后的case key_state_2时因为                                                     key_val==NO_KEY, 所以会回到初始状态,系统将重新运行  */
      if(key_time>=80)
      {
        key_return=key_val;       
      }
    break;
  }
  return key_return;
}

定时器2的学习和使用总结
2.定时器2用法

尤其注意定时器T2的初始化过程,是直接对8位寄存器进行操作的,然后还有T2在15系列的中断号 12。定时器2①T2控制寄存器-AUXR
在这里插入图片描述
//---------------------------------------------------------------------------------------
在这里插入图片描述

注意:(1)由于无法用查询法,所以尽量不用到超声波检测中(较复杂)。
(2) 中断允许寄存器 IE2 不可位寻址,所以主程序中想要关中断来保护时许时不能直接对 ET2 操作,要对寄存器IE2整体赋值。

void Timer2Init(void)        //1毫秒@12.000MHz      //用于模板
{
    AUXR |= 0x04;        //定时器时钟1T模式
    T2L = 0x20;        //设置定时初值
    T2H = 0xD1;        //设置定时初值
    AUXR |= 0x10;        //定时器2开始计时
    IE2 |= (1<<2);   // (自己写)  0100 ->开启中断    关闭的写法:IE2 &= (~(1<<2))
    EA = 1;
}

3.串口文件写好后编译若出现很多警告,检查是否将uart.c加入到工程中

4.单片机内部晶振工作频率和波特率初始化函数中的要对应,否则串口通信会出错。
普通51单片机不能修改,使用的晶振为11.0592MHz或12MHz, 而IAP15可以在STC-ISP中设置多种晶振频率。

5.用移位法矩阵键盘对串口会有影响,最好用普通赋值法矩阵键盘

uart通信判断数据接收完成方法——超时检测法
注:这个方法是我在备赛期间从CSDN上的大佬那学习来的(特别好用)
之前一直在想串口中断函数里面怎么判断接收的数据是否收完,其中一种方法可以规定好接收回来的数据的数据格式,比方说固定以换行字符作为结束符号,但是这个方法的问题在于有时候不一定规定得了,换句话说假如单片机和某个芯片模块进行通信,而那个模块发送的数据字节我们则是没办法规定它是以什么结束的,如果是单片机和单片机通信的话就可以。而我后来网上查找资料发现还有一种方法就是超时检测法。
超时检测法其实原理也很简单,就是用定时器去定时扫描,比如:定义一个变量,给这个变量赋一个初值,然后每当进入定时器中断里面,则该变量减一;在串口中断方面,每当进入串口中断,则重新给这个变量赋最初的那个初值。也就是说,如果数据发送完了,那么就不会进入串口中断,因此,当该变量减到为0的时候,我们就可以认为数据已经接收完了。

  • 33
    点赞
  • 86
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值