微信搜索:ReCclay,也可阅读博主蓝桥系列所有文章,后台回复“代码”即可获取蓝桥所有备赛代码!关注博主公众号,还可拥有加入博主粉丝群实时沟通技术难题、免费下载CSDN资源等多项福利,还在等什么呢?快快扫码关注,学习才不会迷路。
这里再向各位同学推荐一个CSDN博主 ReRrain 的蓝桥备赛博客,博主秉持初学者思路,向你讲述自己蓝桥备赛的心路历程,娓娓道来蓝桥备赛经验,个人觉得非常不错,值得细细品读。
导读:《蓝桥杯单片机组》专栏文章是博主2018年参加蓝桥杯的单片机组比赛所做的学习笔记,在当年的比赛中,博主是获得了省赛一等奖,国赛二等奖的成绩。成绩虽谈不上最好,但至少问心无愧。如今2021年回头再看该系列文章,仍然感触颇多。为了能更好地帮助到单片机初学者,今年特地抽出时间对当年的文章逻辑和结构进行重构,以达到初学者快速上手的目的。需要指出的是,由于本人水平有限,如有错误还请读者指出,非常感谢。那么,接下来让我们一起开始愉快的学习吧。
不积跬步无以至千里,不积小流无以成江海。
前言
一叶障目不见泰山,个人认为只有把握事物的整体思路脉络,才能做到知己知彼百战百胜。同样,对于蓝桥杯比赛也是如此,本篇文章着重介绍如何布局蓝桥备赛的各个阶段,相信我,你如果真的从头至尾这么做了,成绩肯定不会坏到哪里去,至于能好到什么程度去,只能看你个人本事了。加油,电子人!
一、模块攻略
省赛基础模块:
- 【蓝桥杯单片机组模块】1、硬件电路基础知识 与 蜂鸣器模块上手
- 【蓝桥杯单片机组模块】2、以 LED 流水灯为例,熟悉 CT107D 外设驱动套路
- 【蓝桥杯单片机组模块】3、上手 CT107D 外设驱动之数码管模块
- 【蓝桥杯单片机组模块】4、按键模块
- 【蓝桥杯单片机组模块】5、EEPROM模块
- 【蓝桥杯单片机组模块】6、AD/DA转换模块
- 【蓝桥杯单片机组模块】7、DS18B20温度传感器模块
- 【蓝桥杯单片机组模块】8、DS1302时钟模块
国赛进阶模块:
其他拓展模块:
- 【蓝桥杯单片机组模块】12、LCD1602 模块
- 【蓝桥杯单片机组模块】13、NEC 红外通信 - vs1838B
- 【蓝桥杯单片机组模块】14、STC15定时器2的PWM使用
- 【蓝桥杯单片机组模块】15、PWM学习(总结版)
- 【蓝桥杯单片机组】两种外设访问方式:IO编程和MM编程
二、实战攻略
实战模拟:
- 【蓝桥杯单片机组实战】1、简易计算器
- 【蓝桥杯单片机组实战】2、高级计算器
- 【蓝桥杯单片机组实战】3、秒表计数器
- 【蓝桥杯单片机组实战】4、呼吸灯(PWM)
- 【蓝桥杯单片机组实战】5、交通灯
- 【蓝桥杯单片机组实战】6、模拟定时炸弹
省赛国赛:
备赛总结:
- 【蓝桥杯单片机组】国赛易错底层总结
- 【蓝桥杯单片机组】备赛实战问题记录
- 【蓝桥杯单片机组省赛】省赛前临门一脚
- 【蓝桥杯单片机组省赛】第九届走出考场所写
- 【蓝桥杯单片机组省赛】第九集省赛结果
- 【蓝桥杯单片机组国赛】国赛前最后一更
- 【蓝桥杯单片机组国赛】第九届国赛感悟
三、编程技巧
- 1、到4归0,也就是模4归0,好几种写法:
①:if(x == 4)
x=0;
②:x %= 4;
③:x &= 3;
当然了,相比之下,当然是第三种效率高了。
- 2、交换a b的值,除了利用中间变量tmp的方法,还可以:
a ^= b;
b ^= a;
a ^= b;
难以理解的话,举个例子便知道了。
-
3、移位比乘方快! 比如 a = a * 4 ----> a <<= 2
-
4、一个特别重要的是,编程规范。代码的阅读体验,好的代码读起来如诗如歌,糟糕的代码连呕带泄。愚以为有四点,做到即可大不一样!
- ①、关于括号的位置,个人更倾向于另起一行的写法。
- ②、while/if/switch/for 与 后面的大括号直接记得加上空格。
- ③、双目运算符左右两端分别加上空格。
- ④、逗号后也记得加上空格,不要吝惜空格!
- (当然了,这个只是所谓的规定,能有自己的一套编程规范也未尝不可!)
-
5、两个程序的写法:
for(i=0; i<8; i++)
{
buf[i] = dat % 10;
dat /= 10;
}
do{
buf[i++] = dat % 10;
dat /= 10;
}while(dat > 0);
真的能等效替换吗?看着似乎可以,其实仔细想想,如果用在数码管的显示中,第一个是完全可以实现高位自动填充0的,而第二个就不行了,是系统初始垃圾值!
- 6、数码管的高位为0不显示,负号跟随有效最高位前面的写法
void ShowNumber(long dat)
{
bit flag = 0; //1-负数; 0-正数
char i = 0;//注意这里是有符号i
u8 buf[10];
if(dat < 0)
{
dat = -dat;
flag = 1;
}
else
{
flag = 0;
}
for(i=0; i<8; i++)
{
buf[i] = dat % 10;
dat /= 10;
}
for(i=7; i>0; i--)
{
if(buf[i] == 0)
{
LedBuff[i] = 0xFF;
}
else
{
break;
}
}
if(flag)
{
if(i<7)
LedBuff[i+1] = 0xBF;
}
for(; i>=0; i--)
{
LedBuff[i] = LedChar[buf[i]];
}
}
- 7、有符号长整型转换为字符串的方法 — 方便LCD或者串口打印!
u8 LongToString(u8 *str, long dat)//返回最终总长度
{
char i = 0; //注意这里是有符号i
u8 len = 0; //对应的最终总长度
u8 buf[10];
if(dat < 0)//dat负数时进行处理
{
dat = -dat;
len++;
*str++ = '-';
}
do{
buf[i++] = dat%10;
dat /= 10;
}while(dat > 0)
len += i;
while(i-- > 0) //注意while(i-- > 0) <和0比较的是i未自减前的值,参与运算时i自减后的值>
{
*str++ = buf[i] + '0'; //别忘了对应数字转换成字符是加 '0'
}
*str = '\0';//字符串结尾标志
return len;
}
注意6、7里面的i都是char型的,如果7不小心写成u8虽然没有任何影响,但是毫无疑问i值溢出变成255了!这对于后面的程序处理是一个隐患,我们尽量不要让它溢出,但是前者一旦溢出永远出不来for循环了!
还有一个问题,注意里面的do,while
循环,可以直接用while
循环呢?它们又有什么区别呢?
当然是可以用while
循环的,但是while
循环不能处理0的情况。记住:do,while
是比while
要多执行一次的!!!
- 8、数据转化为十六进制格式的字符串
void MemToStr(u8 *str, u8 *src, u8 len)
{
u8 tmp;
while(len--)
{
tmp = *src >> 4;
if(tmp <= 9)
*str++ = tmp + '0';
else
*str++ = tmp - 10 + 'A';
tmp = *src & 0x0F;
if(tmp <= 9)
*str++ = tmp + '0';
else
*str++ = tmp - 10 + 'A';
*str++ = ' ';
src++;
}
*str = '\0';
}
- 9、将以字符串整理成16字节的固定长度输出
void TrimString16(u8 *out, u8 *in)
{
u8 i = 0;
while(*in != '\0')
{
*out++ = *in++;
i++;
if(i >= 16)
break;
}
for( ; i<16; i++)//注意这里是 < 号
{
*out++ = ' ';
}
*out = '\0';
}
- 10、对比一个有意思的东西
-
11、关于u8 u16 u32的定义尽量避免使用
define
,而是使用typedef
。 -
12、禁止573使能最好这样写
P2 = P2 & 0x1F;
-
13、两个u8合并成一个u16,可以直接
c = a << 8 | b;
或者c = (a << 8) | b
再或者c = ((u16)a << 8) + b;
四、驱动底层编写技巧及注意事项
config.h
- u8、u16、u32的定义最好用typedef定义,因为一旦涉及到指针相关的,宏定义会有意外的错误!并且难以找到!
lcd1602.c
IO定义有:
- LCD1602_DB
- LCD1602_RS
- LCD1602_RW
- LCD1602_E
主要函数有:
- void LcdWaitready()
- void LcdWriteData(u8 data)
- void LcdWriteCmd(u8 cmd)
- void LcdSetCursor(u8 x, u8 y)
- void LcdShowStr(u8 x, u8 y, u8 *str)
- void LcdAreaClear(u8 x, u8 y, u8 len)
- void LcdFullClear()
- void InitLcd1602()
注意事项:
- 获取忙状态之前,记得先把LCD1602_DB 赋值为0xFF!
- 写数据和指定之前记得先判断忙状态!!!(又一次忘了这个!)
- LcdAreaClear部分清除的时候我们写入的是单引号括起来的空格而不是双引号因为双引号自加\0,这并不是我们想要的!和LcdShowStr一样也是先定位指针,然后LcdWriteDat写数据。
- 常用的几条指令:0x38 - 1602固定。0x0C - 显示器开,光标关闭。 0x0F - 显示器开,光标打开。 0x01 - 清屏。
- 写数据和写指令的时候,最后是一个高脉冲。就是先把数据在总线上准备好然后把E引脚先拉高再拉低!
- 显示0:LcdShowStr(15, 1, “0”) 比 LcdShowStr(15, 1, ‘0’);更好!
i2c.c
IO定义有:
- I2C_SCL
- I2C_SDA
- I2CDelay()
主要函数有:
- void I2CStart()
- void I2CStop()
- bit I2CWrite(u8 dat)
- u8 I2CReadACK()
- u8 I2CReadNAK()
注意事项:
- 主机读之前记得先让释放数据总线。主机写完之后也记得释放数据总线,以接收从机的应答位!
- 写完之后会有一个从机的应答位,但是应答位是反逻辑的,0-应答,1-非应答。最好取反一下!
- 不管是I2C读还是DS1302的单字节读,有一个简便写法,就是data初始值赋为0,只需要一个if分支,判断是否等于1,进行赋值即可!无需再赋值0!
- I2CStop注意是先都拉低之后,先拉高SCL,再拉高SDA,这样才能做到 - -在SCL高电平期间拉高SDA!
- I2CWrite有一个返回的应答位!
- I2CReadACK或者I2CReadNAK 读之前务必先释放数据总线!
eeprom.c
主要函数:
- E2Write(u8 *buf, u8 addr, u8 len);
- E2Read(u8 *buf, u8 addr, u8 len);
注意事项:
- 不管是读还是写,都要最前面用
do,while
的形式对从机的相应进行判别,响应了再往下执行! - 读的步骤比较繁琐,需要两次启动信号!但是读是可以连续字节读的,还要注意读的最后一个应答位是
NAK
! - 写的步骤虽然较简,但是它不能连续字节写!要想实现连续字节写,需要使用页写的方式,注意不能跨页写!所以里外都有一层
while(len > 0)
ds1302.c
IO定义:
- DS1302_CE
- DS1302_CK
- DS1302_IO
主要函数有:
- void DS1302ByteWrite(u8 dat)
- u8 DS1302ByteRead()
- void DS1302SingleWrite(u8 reg, u8 dat)
- u8 DS1302SingleRead(u8 reg)
- void DS1302BurstWrite(u8 *dat)
- void DS1302BurstRead(u8 *dat)
- void GetRealTime(struct sTime* time)
- void SetRealTime(struct sTime* time)
- void InitDS1302()
注意事项:
- DS1302是低位在先,高位在后!
- 硬件
DS1302_IO
并没有作上拉处理,而DS1302_IO
的驱动IO能力并不强,解决办法要不就在DS1302_IO
对应的IO口加上拉电阻处理。要不在程序中读的相关内容后加一句DS1302_IO = 0;
- 注意结构体的封装中
year
是u16
类型! GetRealTime
中, 注意年最后加上0x2000
SetRealTime
中,注意虽然我们只是操作0~6
寄存器,但是写的时候也得写上第七位
为0
。DS1302ByteWrite
写完之后一定要记得释放总线!DS1302BurstWrite()
的时候别忘了是*dat++
ds18b20
IO定义:
- sbit IO_18B20 = P1^4;
主要函数:
- bit Get18B20Ack();
- void Write18B20(u8 dat);
- u8 Read18B20();
- bit Start18B20();
- bit Get18B20Temp(int *temp);
注意事项:
- 18B20时序操作严格,1位的读写切记不要打断,最好把中断关闭!
五、一些错误集锦
BUG,你尽管来,我不信总结不完,给爷冲!
-
1、P36 – > P42 P37 – > P44因为有个转接板的缘故。记得矩阵键盘那一点的定义别错了!
-
2、关于KeyDriver中,backup的定义肯定是static型啊,别忘了。
-
3、关于ShowNumber函数,因为数码管高位隐去的缘故,所以临时变量k得是有符号型,这样次才能判断到最低位。
-
4、定义flag后,时刻问问自己,置位还需要清零吗?
-
5、简直愚蠢,溢出是个大问题,看着只能显示三位,为啥就没想到溢出这个事呢!!!
-
6、别忘了,backup[i] = KeySta[i]更新backup的值,更别忘了它应该放置的正确位置。
-
7、关于配置PWM和定时器的位置注意。
好好看看红框所在部分的正确位置,也不难看出问题,不是嘛?
-
8、模拟定时炸弹的问题
- ①关于定时中断里面的
tmr1s++
是在flagStart == 1
的情况下进行的,不然当然出现定时不准的问题了。 - ②关于溢出,KeyDriver里面的
TimerThr
阈值理应定义为u32啊。。我竟然定义成了,u8,然后出现疯狂增或减的情况。
- ①关于定时中断里面的
-
9、LCD的指针初探,勿忘清空。
-
10、关于定时器0(注意以下几个地方)
-
11、关于数字转化为字符串的时候
- 有时候会不小心忘记了关键的一步,
*str++ = buf[i] + '0';
的+'0'
- LongToString的中间变量i,最好定义成char!
- 有时候会不小心忘记了关键的一步,
-
12、中断定时相关的变量是否是static类型,一定要考虑清楚!
六、日常BUG教训总结
-
1、.h的变量声明 要记得加extern
-
2、ifndef 后面对应相应的文件名字,切记!
-
3、18B20读写前后记得关开EA ! 18B20返回的检测脉冲是低电平!~
-
4、背会数码管真值表,很有用!!!
-
5、有点傻,既然温度不会用数组传指针那样,为何不设置一个中间变量呢?(DS18B20)
-
6、既然显示存储的温度的时候,主函数while(1)会一直自动刷新,为何不在keyaction里面搞呢!
-
7、还有温度,Get18B20Temp明明咱是有返回值的,为何想不起来用呢???
-
8、I2C时序中,写之后记得释放总线!
-
9、E2PROM中是连续读分页写的,然后还需要注意一个东西就是无论读还是写完buf的一个字节后要记得len的更新!
-
10、E2PROM写完之后立即读会出错,可以加入适当的延时,或者干些其他事情。
-
11、看图
-
注意
I2C
的write
和Read
。write
注意,先改变数据引脚状态,然后拉高拉低信号线即可。因为默认I2C_SCL
是低电平的,我们无需多管。然后read
的时候,先确保数据总线已经被释放。然后在循环里拉高scl
,进行数据读取,然后拉低表示一位读取完毕! -
12、对于BCD码,我们必须移位或者与的方式进行读取,一般普通的直接除法或者求余即可!
-
13、ADC后面的转换以及重新读的start怎么能忘了!!!
-
14、注意I2C写的时候倒是无所谓,读(应答和非应答)都需要先确保主机被释然后才能继续!!!
结语:以上就是本篇文章的全部内容啦,希望大家可以多多支持我的原创文章。如有错误,请及时指正,非常感谢。
微信搜索:ReCclay,即可免费阅读博主蓝桥系列所有文章,后台回复“代码”即可获取蓝桥所有备赛代码!关注博主公众号,还可拥有加入博主粉丝群实时沟通技术难题、免费下载CSDN资源等多项福利,还在等什么呢?快快扫码关注,学习才不会迷路。