赞
踩
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.
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=00为16位自动重装载,不用在中断中再次进行赋初值。
2.NE555频率的读取要从超声波模块取下一个连接子,接到P34和SIGNAL上。
3.计数器读取频率50ms比较合适,之后乘上20即可。
4.TR0对于定时器0的定时和计数功能都有影响。
5.T0设置为计数器时,P3^4每一个下降沿都会进入中断,寄存器值加1。
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的计数器外部脉冲口
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的时候,我们就可以认为数据已经接收完了。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。