蓝桥杯单片机笔记
---山东科技大学电子信息工程22级杰小韦

前言
本人14届蓝桥杯单片机组国二
蓝桥杯编程题85分,选择题15分,选择题包括的知识点太广了,对于大部分同学来说,很难去学完,不过省赛里,编程接近满分就是省一,国赛编程题接近满分就是国二,所以选择题我们完全可以不看。
要想拿省一,我们需要把所有外设学完,背代码,写至少5道真题。刚学完写第一套题的时候会很困难,后面就很顺利了(我第一套题写了5天)
1.入门

2.LED

3.蜂鸣器和继电器

4.数码管




5.初始化函数封装

6.按键
独立按键

矩阵键盘

代码:

7.中断系统
外部中断
通过引脚接收外部的高低电平,来触发中断
内部中断
通过内部的信号源触发中断(如定时中断)


中断服务号

开关(TRx:定时/计数器启动位 1启动)


TCON:中断标志寄存器
SCON寄存器用于串口通讯
中断初始化函数:
一个普通的函数,函数名任取,就是把中断的设置代码放到一个函数里
简单外部中断0的初始化函数:
void init(){
IT0=1; //设置为下降沿触发
EX0=1; //小开关闭合
EA=1; //大开关闭合
}
中断服务函数:
void 函数名() interrupt 中断号
其中,函数名任意,中断号为图中对应中断服务号

注意,中断服务函数不需要被调用,会自动触发
外部中断时,
外部中断0的复合io口为P32,即按下独立按键s5,触发中断
外部中断1的复合io口为P33,即按下独立按键s4,触发中断
需要将跳帽接到独立键盘一边,而且复合的io口无法更改,即不能指定按键触发中断
代码:

8.定时器/计数器
原理:晶振产生波形信号,每个周期计数一次,从某个起点开始计数,直到记满产生溢出信号
16位计数器最大值为65535
晶振为12Mhz,选择12分频,进入计数器的脉冲就变为1Mhz,周期就为1μs
我们要设置计数起点,才能达到定时/计数的效果,设置的方法为:
设我们要定时x μs,则赋值:
TH0=(65536-x)/ 256 高8位
TL0=(65536-x) % 256 低8位

TMOD:模式寄存器
定时设置时,相关模式如下图内容所示
高4位-计数器1,低四位-计数器2
定时器编程思路: (总思路:计数+中断)
先配置TMOD模式寄存器,再根据要定时的时间计算计数初值并配置,最后配置计数器的中断功能
注意:使用16位计数器时,不具备自动重装功能,中断服务函数必须重置计数初值
代码:

9.IO口错乱问题解决
在同时综合运用多个模块时,比如要在led亮或灭时,蜂鸣器叫一下,代码如下:

上诉代码为用定时器实现led7每秒闪烁,并且在闪烁时,蜂鸣器叫一下,逻辑上没有问题。但在实际运行时,led的闪烁发生了错误。原因在于最后:
P0=0x00;//灭蜂鸣器
csh(4);//打开Y4
在打开Y4的瞬间,上面一句对P0的赋值影响了led的状态,所有的灯都亮了
即使在最后添加P0=P0 | 0x7e ,来熄灭中间的灯,两边的灯状态不变,但led7的闪烁依旧不正确
正确处理方法:现场保护法
定义一个变量a,在调用csh()之前,用a保存P0的值,当再次打开之前的Yx时,P0=a 即可
改进代码如下:

其他问题:无法关闭无关外设和数码管混乱


这段代码,实现的是在数码管上显示25.6这个数
而实际运行过程中,数码管其他5个数字也会亮,原因在于screen函数中,csh(6)前,P0有值,打开Y6通道的一瞬间就使得其他管段被点亮。所以,我们在前面给了赋值,P0=0x00;解决了数码管其他管不正常的问题。
但是,又出现了led被点亮,无法关闭的问题。原因在于,在main函数中,csh(4);P0=0xff;下一句代码就是screen中的第一句P0=0x00;点亮了led
解决办法:main函数初始化最后写csh(0);(要自己在csh函数里加上0的情况,即都给0)
类似地,如果蜂鸣器乱响,也是这样的情况
10.PWM
在高级单片机中,如32单片机,产生pwm波是一个固定的功能,而在51单片机中,只能用较为原始的方法来产生pwm(用定时器实现电平取反)。
pwm脉宽调式:即方波,有高电平,低电平,通过调节高电平的占空比,实现调节功率(直观地说,就是调节灯泡的亮度,电机的转速等)
编程思想步骤:
总的来说,就是在定时器的代码上进行修改
细说:
先确定pwm波的频率,算出周期。如100Hz,周期就为0.01s,即10ms。
再将10ms分成100份,即0.1ms,也是100μs,这个就是我们需要定时的时间。
再定义一个变量 char k=0;
在中断服务函数中,(每定时一次)k++;
如果我们需要亮度为70%,则k在0~70时,点亮led,k在70~100时,熄灭led。k>100时,进行重置,即k=0并且点亮led
代码:

11.初始化函数的高级写法

代码如图所示
以Y4为例进行解释:
打开Y4,则 P27=1;P26=0;P25=0;
也可以直接对P2进行操作
P27 26 25 24 23 22 21 20
1 0 0 0 0 0 0 0 → 0x80
即P2=0x80;
但是直接这样写,会影响到其他io口,即P24,P23,P22……所以,我们要直接对P2进行操作,还不影响P2其他的io口,就需要 &| 操作
&0 必为0 &1 不变
|1 必为1 |0 不变
P2=(P2&0x1f) | 0x80;
括号中 (P2&0x1f)是固定的,作用是前三位清0,且不影响其他位
再 | 0x80 作用是将前3位变为100,且不影响其他位
其他的类似,规律:(P2&0x1f)不变,|后面的数刚好是Yx中x的两倍+0
12.MM模式(没用,不考)
——io扩展技术与存储器映射扩展
io模式:直接通过io口控制某些元件,前面我们一直用的都是io模式,比如点灯P00=0;
mm模式:通过存储地址控制

引入头文件“absacc.h”,j13跳帽接到mm模式
然后就可以通过XBYTE关键字进行操作了
具体:
XBYTE[0x8000]=0x00 就相当于
csh(4);P0=0x00;
(注:csh(4)是自己编写的初始化函数,作用是打开Y4)
XBYTE[0xa000]=0xff 就相当于
csh(5);P0=0xff;
其他类似
规律:地址为Yx中x的两倍+000
代码:

13.温度传感器DS18B20
使用温度传感器,需要用到官方提供的底层驱动程序,里面有一些进行相应操作的函数,我们调用里面的函数即可
注意:官方提供的驱动程序中,可能会挖坑,比如,他给的头文件.h文件中,缺少一些函数的声明,需要我们手动添加;
还有,文件夹中给了驱动程序的说明,里面有驱动运行的环境,如果一些参数和我们的环境不一致,我们就需要进行相应的修改。比如驱动环境为12T
而蓝桥杯板子的时钟是1T,我们就需要将官方提供的驱动.c文件中,函数里的延时时间都扩大到原来的12倍
代码思路步骤:
因为要显示出温度,所以要先写好数码管的代码
根据题目的要求,如保留几位小数,做一些修改,具体视具体情况而定
再写温度传感器的代码

这些操作,都是用官方提供的低层驱动程序实现的
ROM:64位空间,保存了设备的ID号,蓝桥杯板子上,单总线上只有DS18B20,所以不需要进行ID匹配,可以直接跳过ROM指令
延时700~900ms时需要同时扫描数码管
延时delay(1000)即可
代码:

数据处理:

注意:
分辨率为0.0625,整数部分不需要×分辨率
小数部分需要×0.0625
需求:保留一位小数,并且在数码管上显示
代码:

解释:读取数据时,先读取的是低8位,后读取的是高8位。
int temp是一个32位的数,足够容纳16位数据
整合时,先temp=h8,左移8位,h8成为temp的高8位,低8位全为0,再 | l8 ,完成整合
因为我们需要显示在一位小数,在数码管显示中,我们需要将比如26.5这个数扩大为265,才方便写代码
所以,先判断高5位(符号位)是否都为0,是则为正数,再将temp右移4位(小数位);再把小数加回来,小数位只是l8的低4位
temp=(temp+(l8 &0x0f)*0.0625)*10
注意:整数部分不需要 * 分辨率
图中代码只是开括号把10乘了进去
数码管显示部分代码:

14.模块化设计-----头文件
当一个工程的代码很多,函数很多时,把所有代码放在main函数里,就会很混乱,不方便调试。
所以,我们需要自己编写头文件,把各个模块的代码分到不同的文件中,如数码管、按键、温度传感器等不同模块放在不同的.c文件中。
头文件编写步骤:
在工程中添加.c和.h文件,它们的名字必须一致
.c文件正常写,.h文件格式如下

宏定义名称是随意的,一般是文件名的大写,两个宏定义名称一致即可
随后,在#define和#endif之间放.c文件中函数的声明。
main中,要使用自己编写的头文件,写
#include“xxx.h”即可
在自己写的头文件中使用别的头文件也是一样的
15.实时时钟DS1302
Ds1302有两块寄存器,日历时钟寄存器(记录时间)和静态RAM存储器(用于用户自定义)
关注日历时钟寄存器即可
日历时钟寄存器即保存了年月日时分秒星期

这个表左边两列是重点,分别为:
读秒地址 写秒地址
读分地址 写分地址
读时地址 写时地址
……
0x8e是控制写入保护的地址
BCD码:直接将十进制数写为字面上的十六进制
比如,56直接写0x56,36直接写0x36
在读取数据时得到的也是BCD码,在数码管显示之前要进行转换,公式为:/16得到十位,%16得到个位;或者右移四位得到十位,前四位清零得到个位

控制字和寄存器的地址一样,细节可忽略
上面图片中的读操作地址,就是各读寄存器的地址,按顺序分别对应 秒分时日月 星期 年,写操作同理;
时间数组的内容和读写数组的顺序相对应,分别保存的是初始化的 秒分时日月 星期 年,后面时钟启动,读取新的时间后,数组里的内容会被改变
当然,你也可以修改数组的顺序为,年星期月日时分秒,要改三个数组的顺序都得改(保持一致)
编程操作:先把官方提供的驱动代码添加到工程
即,ds1302.h
代码:

解释:
一个写函数,一个读函数,格式基本固定
写函数就是利用循环把8个时间数据写入ds1302,当i=0时,写函数(秒寄存器地址,秒数据)
当i=1时,写函数(分寄存器地址,分数据)
……
读函数同理,但会修改时间数组的内容(除非另写一个数组)
数码管显示部分代码:


16.AD转换(电压)PCF8591
ad转换,a→模拟量,d→数字量
ad转换用到了iic(读作i方c),iic可以理解为通信协议,是传输数据用的,需要在工程中添加官方提供的驱动程序iic.
原理图:

pcf8591的iic通讯地址(电话号码)
写0x90 读0x91

控制字定义
输出电压0x40
读通道1(光敏)0x01 读通道3(电位器)0x03


代码思路步骤
输出模拟电压

翻译: 打电话→我要向pcf8591交代事情→等待应答(好的,你说)→我要让它输出电压→等待应答(好的,要输出几伏电压)→发送电压值→等待应答(好的)→挂电话
代码:

读取数字电压

翻译:打电话→我要向pcf8591交代事情→等待应答(好的,你说)→我要读取…通道的电压值→等待应答(好的,我现在让它反馈数据)→(给另一个人)打电话→我要读取pcf8591最新反馈的数据→等待应答(好的,那你记一下)→记录数据→数据已发生完毕→挂电话
代码:

数据处理和数码管显示
u不能是char类型,因为最大值超过了256
/51.0 和 /255.0*5 是一样的


最好不在读取和输出函数内处理数据,在外面处理比较好(因为要处理小数)
17.NE555定时器

事实上,NE555定时器是一个硬件设备,功能已经固定,是无法编程的,它的作用是产生某频率的方波信号,考试的题目就是测量它产生信号的频率(可以通过板子上的旋钮调节滑动变阻器的阻值,以调节频率)
而要测量频率,用的是T0和T1两个定时计数器,一个用来定时,另一个用来计数。所以,实际的考点是定时计数器

思路:
将j13的NAL和P34短接
(P34为T0的计数外部引脚)
T0计数(8位自动重装计数模式,最大值255)
T1定时(16位手动重装定时模式)
此时,TMOD=0x16
T0的计数初值给255,每来一个脉冲,就会触发中断,中断服务函数中,让变量a++;
T1定时时间为1s(分段计时),当定时时间到时,
b=a;a=0;
b就是上一秒的频率,数码管显示b即可
代码:

18.AT24C02储存器
实际上,at24c02存储器的写数据和读数据的步骤和pcf8591电压的相关操作一致,只不过iic通讯地址不同
at24c02:
写0xa0,读0xa1
pcf8591:
写0x90,读0x91
数据写入

代码

数据读取

代码

函数调用

19.超声波
原理:用定时器计时超声波发射信号到接收信号的时间,这个时间/2×声速就是距离
步骤:
定义好TX和RX
1.发射信号:每延时10微秒,TX改变状态,循环8次
2.设置初值为0,开启定时器
3.等待接收到信号或定时器溢出(溢出了就是长时间没有收到信号,此时测量无效)
4.判断定时器溢出标志位,如果没溢出,则读取定时器的时间×0.017 就是距离(单位cm)
如果溢出,则清除溢出标志位,此次探测无效
注:定时器溢出时,TF1置1。
超声波模块收到信号时,Rx==0
代码:


超声波会有偏差
注:最佳测距代码:
发送信号时,循环8次。延迟时间10微秒。最后时间加156微秒
20.PCA定时器 (中断号为 7)
国赛中,同一项目里,超声波、频率、脉冲(pwm)、串口等内容通常一起出现,而它们需要都需要用到定时器。明显,两个定时器在国赛里是不够用的,所以,超声波测距,我们通常用PCA定时器(不需要中断)。另外,我们还可以再加一个T2定时器。这样我们就有了4个定时器。
Stc烧录软件不能生成PCA定时器的相关配置代码

都0x00即可
类似TMOD寄存器,PCA模式寄存器为CMOD
类似于TCON寄存器,PCA有CCON寄存器
这两个寄存器的配置记住即可
另外,CF 等同 TF 定时器中断使能
CR 等同 TR 定时器启动位
CH 等同 TH 定时器高8位
CL 等同 TL 定时器低8位
可以发现,各功能器一致,只是T改为了C
T2计时器:中断号为12
stc烧录软件能生成配置代码,相关原理也和T0定时器一致,只是命名不同
直接生成代码即可,简单
注意:如果要写中断服务函数,记得允许中断
即:IE2 |=0x04;
(不可位寻址,所以只能这么写)

21.定时器刷新数码管
之前用delay函数实现动态显示,需要在主循环中一直扫描。这种写法,对大延时的处理方式是,延时的时候扫描数码管,在大部分情况下都行得通,但在使用超声波模块时,出现了问题:
在等待接收信号或定时器溢出的循环中,如果在循环中扫描数码管,测量误差很大;不添加的话,数码管的显示又不稳定。
所以,我们只能用新的解决方法,那就是在定时器的中断函数中扫描数码管(2ms扫描一次)
代码:
smg.c中:

中断服务函数中

后面只需要调用smg_Set()就可以修改显示的内容
22.串口通讯
简单来说串口通讯就是两个机器之间进行数据传输,可以是单片机和电脑,单片机和手机,单片机和单片机。
这里我们学的是单片机和电脑之间进行数据传输
重要相关概念:
波特率:每秒传输的数据的位数
即波特率是传输的速度,双方都以这个标准进行数据的发生或接受,才能正常通讯
波特率就是一个数,这个数可以由定时器产生,定时器溢出一次,串口就传一次数据。如要产生9600的波特率,就让定时器每秒钟溢出9600次
(因为最多能做到1微秒溢出一次,而1秒=10^6微秒,所以理论上定时器能产生的最大波特率为10^6)
串口通讯的数据缓冲区:
缓冲寄存器SBUF,包括了发送寄存器和接收寄存器,它们在物理结构上是独立的,但软件地址是同一个地址99H
数据发送:把数据写入SBUF后,内核会自动发生数据,发送完成后,数据TI标志位会置1
故,数据发送的代码格式为:
unsigned char a=某个值; SBUF=a;
while(TI==0); //等待数据发送完成
TI=0; //手动标志位清零
数据接收:当接收一个完整的数据之后,RI标志位会置1。此时,直接读取SBUF的数据即可
(完整的数据:)
故,数据接收代码格式为:
unsigned char a;
if(RI==1){
a=SBUF;
RI=0;
}
注意:TI和RI标志位都需要软件手动清零
串口的中断号为4
当数据发生完成或接收完成时,都会进入中断,所以要进行对应的操作时,要判断是由数据发生完成产生的中断还是数据接收完成产生的中断。
接收数据(读取SBUF的数据),应该在串口的中断服务函数中实现。
发送数据随意。



串口模式寄存器SCON

8位数据,可变波特率,SCON=0x50
辅助寄存器AUXR(地址为0x8e)
用于选择哪个定时器作为波特率发生器
以及定时器的时钟频率
实际上,寄存器配置和波特率设置,都可以用stc烧录软件的波特率计算器进行生成,步骤就简单了很多

注意系统频率和我们烧录文件时所选频率必须保持一致。
打开串口时,串口号必须和USB一致
波特率也要和我们设置的波特率一致
小细节:
如果系统频率设为12Mhz,定时器不能用8位重装模式,(如9600的波特率)
虽然能生成,但实际有误差,串口通信会乱码
注意:生成时不会打开串口中断,所以需要我们添加代码:
ES=1; //串口小开关
EA=1; //大开关
加油写题吧!

4万+

被折叠的 条评论
为什么被折叠?



