本篇内容是观看B站江科大自化协UP主的教学视频所做的笔记,对其中内容有所引用,并结合自己的单片机板块进行了更改调整。
以下笔记内容以一个视频为一个片段(内容较多,可能不适合速食,望见谅)
一些内容涉及前面的知识点,可能需要提前了解(可以翻看本人之前的文章或者去B站看UP主的视频)
蜂鸣器是一种将电信号转换为声音信号的器件,常用来产生设备的按键音、报警音等提示信号
蜂鸣器按驱动方式可分为有源蜂鸣器和无源蜂鸣器
有源蜂鸣器:内部自带振荡源,将正负极接上直流电压即可持续发声,频率固定。
无源蜂鸣器:内部不带振荡源,需要控制器提供振荡脉冲才可发声,调整提供振荡脉冲的频率,可发出不同频率的声音。
——由此可以推断,本开发板上的蜂鸣器为无源蜂鸣器
单片机开发板的蜂鸣器:
蜂鸣器在电路图的符号:
蜂鸣器的符号有时也可能是下面的类型,认准蜂鸣器的Buzzer说明就行——(下图来自于视频截图)
蜂鸣器的模块:
由上面的“低电平触发”可以推断其为有源蜂鸣器。(当接上VCC,并给I/O口赋予低电平即可触发)
因此,需要提前了解蜂鸣器的类型以便于使用。(如问商家、自己测试、研究模块的说明——第一种方法较好)
因为I/O口的输出电路比较小,而蜂鸣器的功率较大,因此加入一个驱动电路,增大驱动能力。
解释工作原理:
这种类型当传入的信号为1时,相当于有一个手将上下开关导通;
当传入信号为0时,相当于有一个手将上下开关断开。
ps:
其中R1为限流电阻,将控制信号的电流减小,驱动能力弱化。
(限流电阻只需要保证三极管能够饱和即可,要求没有模拟电路高)
截图自up主:
工作原理与NPN型同理,只是信号反过来即可。
截图自up主:
单片机不能直接驱动蜂鸣器,经过集成电路中的驱动芯片进行驱动。
原理图上的蜂鸣器:
BEEP连接的芯片:
可见BEEP连接受P2_5这个I/O口控制,通过ULN2003D这个芯片增强驱动能力,实现控制蜂鸣器。
手册中芯片介绍:
手册中电路图:
来自up主视频截图:
上面这种状态由于0经过非门后为1,使得两头均为1,蜂鸣器不响动。
而当最左端输出为1时,经过非门后变为的0能使蜂鸣器响动。
PS:其中二极管作为灯测试,当最左侧为1时,另一侧如果为零(即COM端为0),那么灯就会亮起,从而实现测试效果。
——但是本单片机该芯片接的是VCC(高电平),因此做不了测试。
芯片图:
补充:0经过非门的内部结构后呈现的1没有驱动能力,为高阻态。
来自搜索:
ULN2003D一般用于步进电机的驱动,如果自己制作使用蜂鸣器,只需要用三极管进行驱动即可。(用ULN2003D芯片驱动有点浪费)
①本单片机上使用的为无源蜂鸣器,不能一直通电,否则容易烧坏。(因为线圈电阻很小,长时间用会因为温度高的问题导致烧毁)
②无源蜂鸣器内部为线圈或其他什么原理,需要加入交流震荡才能发声,区别于有源蜂鸣器。
③由于I/O口默认为高电平,经过ULN2003D芯片的内部取反后,使得蜂鸣器默认有电流存在。
(因此如果自己制作的时候一定要注意不要让蜂鸣器一直通电,否则容易烧毁。这里是因为存在限流电阻,所以没事)
需要自己识谱后才能利用蜂鸣器编写谱子演奏,因此需要了解。
其中每组都有八个音,从c起到b落(do到si);
相邻组相同的音之间相差八度。
相邻两个键相差半音。
(如果相邻为黑键的话,白黑相差半音;如果白白相邻,中间没有黑键,白白相差半音)
根据上下一一对应即可将简谱跟音键联系起来。
①从1~7为一组,每升高八度(到下一个C)时数字上面加一个点。
(再加八度就两个点,以此类推)
②每降低八度(到上一个C)时数字下面加一个点。
(再减八度就两个点,以此类推)
通过#(升音符号)与b(降音符号)搭配白键的简谱数字,即可表示对应的黑键。
(
为中央C相邻右边的黑键,
也为中央C相邻右边的黑键,依次类推即可——所以写程序的话可以单用一个升音符号配合简谱数字表示所有按键)
其中第一行的5后面的—表示5占据了两个时长(后面的以此类推),表示为单个时间的二倍。
音符图:
①如果是三十二分音符,在竖线再加一条线即可,其他分音符以此类推。(类似于八分到十六分)
②一般以四分音符为一个时间基准(可以为500ms,200ms等,以节奏快慢决定时间——根据百度查询,四分音符为480tick,1tick为1/12ms,因此四分音符为40ms)
③因此,若四分音符为500ms,二分音符则为1000ms,八分音符则为250ms(其他以此类推)
①在简谱数字(一般为四分音符,以它为例)后加入一条线代表二分音符,加入三条横线代表全音符。(其他情况以此类推,一根线代表一个基准音符)——这种线叫增音线。
如:
②在简谱数字下方加入一条线代表八分音符,加入两条线代表十六分音符。(其他情况以此类推,每次加一条线,音符时长减半)——这种线叫减音线。
①其中4/4为拍号,分子的4表示四分音符为基准音符,分母的4表示每小节有4拍。
②每段竖线代表一个小节的结束,每个小节里有四拍(一拍为一个四分音符)
比如这一个小节,000为三拍,后面67下面有一条横线代表八分音符,两个八分音符时值等于一个四分音符,因此刚好为一拍,合起来就是四拍即一小节。
③在数字右边加一个点表示延时1/2拍。
如下图中第一个
④增音线使用代表按下对应的音直到对应的时间到为止,如
则是按下7这个键不放,直到3个四分音符的时值。
⑤减音线使用代表按下对应的音直到对应的几分音符时值到达后停下,如果类似于这种情况,则按住3这个键到八分音符的时值后松开,然后重新按住3到八分音符的时值松开。
⑥延音线使用则是将对应的几分音符按住不放到延音结束,类似下图的
,这是为了识谱方便,这个地方的效果等同于
C调音符与频率对照表(不全版)
这里C调指的是乐谱图上面的1=C(如果1=D则为D大调)。
ps:C大调表示只有白键,如果为其他调则存在黑键。
Excel中编写对应音符与频率表(部分)
截自up主视频:
可见对应频率连成了一条光滑的曲线。
①在频率排序时是将a作为基准频率(为440HZ),那么A就为220HZ,a1为880HZ。(一个a到下一个a的频率关系为二倍关系)
②而a到下一个a1间的音符以等比数列(2为公比)进行平分得到频率。(包括最开始的a与黑键在内一共12个键,因此又叫12平分率)
如下:
a()的基准频率为440HZ,乘以2的十二分之一次方即可得到下一个键的频率。
将1/频率即可得到周期值。
ps:如果不是12T的晶振周期,那么就需要将晶振频率/12得到的定时器秒数再除以上面的频率,得到的值转化为的重装值才是正确的。
(至于为什么除以12,与定时器部分有关,12分屏内容,可以去定时器部分回看)
因为最终想要利用定时器来实现I/O口的翻转,从而实现震荡效果,因此则需要将周期值除以二得到翻转周期。(翻一次变为0,再一次变为1,完成一次周期)
截自up主视频:
得到的效果(部分):
这里将得到的翻转周期,用65536-翻转周期即可得到对应的重装载值。
原因:将溢出值65536减去翻转周期后,即可得到每次进入中断时的初始值,最终使得到65536产生溢出时的时间刚好为翻转周期,从而产生中断来实现I/O口的翻转。
实现效果:按下独立按键时,蜂鸣器会发出时长为100ms的提示音,同时在数码管第一位显示按下的按键对应数字。
其中Key文件为独立按键的模块化代码,Delay文件为延时函数的模块化代码,Nixie为数码管的模块化代码。
Key.c
Delay.c
Nixie.c
将添加的文件包含到main.c文件中:
PS:因为最终只需要用到数码管的第一位,因此Key.c文件可以删除掉后面消影的操作。
经过测试,可以发现键码显示效果正常,说明代码没问题。(一定要边写边测试,以防最后出错找起来很辛苦)
这里是为了方便知道I/O口的作用,提高代码可读性。
这里需要注意对照自己开发板上的原理图,不同的开发板可能I/O口不一样。(这里的单片机开发板与up主不同,为P2^5口)
①定义一个变量
②写入循环
③加入翻转代码
这里加入Delay(1);使得每隔一ms翻转一次I/O口。(因此周期为2ms)
可以通过双击下图的[Source Group]进入工程目录里,然后将其他的.c或.h文件复制粘贴一份,更改掉名字即可实现快速新建文件。
(也可以通过这种方法直接找之前的文件,无需再打开文件管理器去复制粘贴文件)
然后将复制文件里面的内容删除掉即可。
①在STC烧录软件中生成软件延时微秒的代码。
②将代码添加进Buzzer.c文件中,并include
这里将名称改动,使得该延时函数为蜂鸣器私有的延时函数。
③将翻转I/O口的代码整合为一个函数加入Buzzer.c中。
上面的翻转周期决定了音高。
这里Time里面的数字决定了时值。
烧录后即可看到预期效果。
原因:因为红外接收模块使用的是P3_2的I/O口,与独立按键的K3重复,因此红外接收处会影响单片机独立按键的部分,使得会莫名其妙的按下3(实则没按)。
解决方法:将红外模块拔下即可解决乱跳的问题。
拔下前:
拔下后的红外部分:
拔下后的红外接收管:
Ps:记得当不显示效果后要按回去,以防弄丢。
实现效果:通过单片机蜂鸣器演奏一首曲子。(这里为天空之城)
其中Timer0文件为定时器0的模块化文件,Delay文件为延时函数的文件。
Timer0.c
Delay.c
将添加的文件包含进main.c文件中:
Ps:因为播放音乐的函数与主函数耦合程度大,
且播放音乐的频率比较高(相对这个单片机而言),——因此做完播放音乐就几乎没法做其他事情了。
因此就不进行模块化了。
(上节已完成转换,现在只需要复制过来即可)
(通过索引,能够实现转换到上面数组存放音符对应的重装载值)
乐谱的部分音符对应索引:
(这里仅作测试,后面会进行完善数组)
主函数部分:
将上面程序烧录后发现,当Music相邻的数组单元内数字相同时,蜂鸣器没有过500ms停止响动后再接500ms,而是直接连过去响1000ms,不符合我们想要的抬手动作。
通过关闭定时器并延时5ms,实现抬手动作,将音符分开,而不是连过去。
(这里在每个索引后面紧跟着一个数字为其时长值,实现乐谱中每个音符对应的时值)
补充:由于数组长度仅为256,远远小于一些音符的时长值,因此以16分音符的时长值为基准,其他分音符乘以对应倍数即可解决问题。
(比如四分音符,在索引后面紧跟数字就为4,最后×十六分音符就可以得到四分音符的时值)
从上面可以看出,当125这个十六分音符时值乘以4时,刚好转换为四分音符,也就解释了上面的原理。
首先将基准音符定义为SPEED,以方便后续不同谱子基准音符不同时能进行更改,显得更规范。
然后将延时函数的参数进行更改
作用:方便后续更改时,只需要更改前面的宏定义后面的数字(基准时值)即可。
在乐谱中存在0这种数字,代表休止停顿,因此需要在代码中加入,以实现休止效果。
①重装载值存储函数添加:
这样数组第零位就是休止符。
②索引数组内索引全部加一:
Excel表中的索引值更改:
③修改中断函数内部代码:
①在索引数组中添加终止数
②更改主函数,添加终止时执行内容
这里利用终止数实现乐谱演奏完后关闭定时器并进入死循环(即不再演奏)
Ps:可以更改演奏结束后的操作实现从头开始演奏等操作。
原因:这样就不用每次对照谱子都需要对照Excel表中找索引,只需要直接打入音符即可直接被替换为索引数字。
(这里不用数组的原因,一是数组不能出现字符串,二是数组存储内容太大占用空间)
宏定义对应音符:
①点击上方工具栏的图标
②在弹出的弹框中选择[Replace]替换内容
③在Find框输入想被替换的内容,Replace输入替换的新内容,在Look中选择替换区域(第一个为当前文件,第二个为选中的区域——得提前用左键划取区域,第三个为当前项目)
④输入完内容后,点击[Replace All]完成全部替换。
其中每一行对应一个//部分。
天空之城曲谱:
Ps:这里的数组必须加入code,否则会报错,原因是数组内容太大,RAM(随机寄存器)存储空间不足,导致出错。
(这里的code内容可以看LED点阵屏显示动画的笔记,RAM可以看单片机介绍的笔记)
这里的4左边的符号代表临时升降号,在本小节均有效,后面的音符即使没有升降号,也得升半调。
因此代码后面的4即使没有升降号也要为M4_就是这个原因。
(其他乐谱知识自行了解)
上面的音符对应的频率表转换是C大调的,而“天空之城”为D大调的,因此需要进行移调的操作。
(具体内容自行了解更改即可)
将程序烧录进单片机,即可听到美妙的音乐了。
特点:①存储速度快;②掉电丢失。
类似于D触发器,是上面列举的存储器中最快的一个,
一般用于电脑CPU高速缓存,
但是容量较小,且成本较高。
利用电容进行存储数据(充完电时为高电平,放完电为低电平)。
因为集成度很高,容值特别小。
且因为漏电原因,需要配一个扫描电路,补上因为漏电丢失的电能(动态刷新)。
但DRAM成本比SRAM更低,容量更大。
如电脑的内存条,手机的运行内存,都是动态RAM。
特点:①掉电不丢失;②存储较慢
仅依靠电路存储数据,且只能读不能写(即 烧录 在Mask ROM中的资料永远无法做修改)——ROM因此得名(read only memory)。
仅能写入一次数据,之后数据永远无法更改。(区别于Mask ROM连写都没法写)
既可以写入,又可以擦除数据(但需要紫外线照射30分钟)。
——中间存在一个洞,里面是直接裸露的芯片,上方贴了层黑纸,当需要擦除时直接弄开照射即可。
实现了快速擦除(几毫秒)的功能,但容量较小。——开发板上的ROM。
PS:上面的四种可以说是同一种类型的演变过程。
应用范围广(U盘、内存卡、电脑的固态硬盘、手机的存储容量等)
依靠磁介质存储信息。
早期电脑存在软盘(A盘与B盘),但容量较小,后面被淘汰了。
后面依靠硬盘存储(即C盘开始)。
光盘依靠光信号存储。
横向的为地址总线,纵向的为数据总线。
接上第一行为1(高电平),然后前三列接上1(高电平),那么数据总线就会接收到前三位均为1,后面的行和列因为没有接(可以视为0效果),于是就可以产生对应点数据。
然后给第二行为1,给其他列接1即可实现数据存储。
后面的以此类推即可。
(也就是先第一行为1,存储器存储数据到第一行;然后让第二行为1,存储器存储数据到第二行,以此类推。)
①当存储断开时,即为左边的情况(交错时即为断开,相当于0)。
②当存储接通时,即为右边的情况(使用二极管是为了防止上一行的数据通过列电路来对下一行产生干扰)
③通过上面的显示,可以想到,当给对应行接通(为1)时,有二极管的列就会被传为1,没有接二极管的列就会保持断开(为0),因此就能做到存储数据的作用
——所以一旦做好芯片,由于二极管已经固定的缘故,也就没法进行更改或者写入的操作,这也就是为什么Mask ROM具有其对应特性的原因。
①所有的电路节点都做成左边的状态,这样无论哪头都过不去;
②但是蓝色的二极管在反向电压超过一定数值时,会被击穿(相当于废掉,失效了),因此只需要给行一个较高的电压,列为0,那么二极管就会被击穿,成为右边的状态,即为接通。
③这也就是为什么PROM能写入数据,且只能写入一次的原因。(因为蓝色的二极管被击穿后,就没有用了,因此无法做二次写入)——相当于烧毁二极管。
补充:
①这也就是为什么烧录程序的原因。(早期确实是烧掉了)
②早期需要加一个编程电压(高电压)的原因,就是为了击毁里面的那个蓝色二极管。
③所以这里的所有二极管如果换为保险丝的话,当温度过高时熔断,即可断开,也可以达到类似效果。(即正常时能接通,高温时断开)
如果将连接的部分替换为某种能复原的材料,那么就可以做到多次写入与读取的效果。(EPROM利用紫外线照射30分钟来复原,而E2PROM则用电来复原)
总结:因为每次只能给一行连接为1(高电平),因此地址总线可以接一个138译码器(可以到数码管笔记处了解),来实现减少I/O口的同时,实现存储数据。
AT24C02是一种可以实现掉电不丢失的存储器,可用于保存单片机运行时想要永久保存的数据信息
存储介质:E2PROM
通讯接口:I2C总线
容量:256字节(对于目前的单片机够用,且价格便宜)
实物图:
贴片型:
直插型:
引脚图:
本单片机上原理图:
来自up主的原理图:
WP(即本单片机上的WE):图上的写保护接到了GND,因此没有写保护。(如果需要写保护,可以接到I/O口或开关上)——up主的写保护为高电平有效,本单片机为低电平有效(上面有横线,代表低电平有效)。
A0、A1、A2(即本单片机上的E0、E1、E2):可以接到高电平(1),但这里全部接到GND(为0)了。
SCL、SDA:up主的引出去接了一个上拉电阻,而本单片机直接接的I/O口(因为单片机上每个I/O口都接了上拉电阻)。
本单片机内部结构框图:
来自up主的内部结构框图:
画圈部分为I2C总线部分,暂时不管。
EEPROM部分,与上面的存储器简化模型联系;
X DEC(本单片机为X DECOOER)为译码器部分,将数据传入后传输给地址总线。
经过网格后传出数据总线部分,为输入输出端(既输入又输出数据)。
SERIAL MUX为串行数据选择端,通过Y DEC(本单片机为Y DECOOER)将并行八位变为一位一位的逻辑输出出去。
EEPROM上方的DATA RECOVERY为数据恢复,上面的两个部分用于数据擦除。
⑦DATA WORD ADDR/COUNTER(数据字地址寄存器/计数器):
数据字地址寄存器用于设置地址(存储地址),每写入或读出一个数据,计数器会自动加一。当读出而不指定地址的话,就会默认把计数器的数据传出来。
START STOP LOGIC为开始结束逻辑,
DEVICE ADDRESS COMPARATOR为器件地址比较器,
SERIAL CONTROL LOGIC为串行控制逻辑。
I2C总线(Inter IC BUS)是由Philips公司开发的一种通用数据总线(通信的一种协议,相当于串口的通信协议)
两根通信线:SCL(Serial Clock)—也可叫SCLK、SDA(Serial Data)
同步(因为有同步的时钟线SCL)、半双工(只有SDA一根线,且负责来回通信),带数据应答(当发送一个字节数据后,要求对方在接受到后需要发送一个应答)
通用的I2C总线,可以使各种设备的通信标准统一,对于厂家来说,使用成熟的方案可以缩短芯片设计周期、提高稳定性,对于应用者来说,使用通用的通信协议可以避免学习各种各样的自定义协议,降低了学习和应用的难度
0.96寸的OLED屏幕:
显示模块,像素密度高,为128×64个像素点,利用I2C总线进行通信。
DS3231:
时钟芯片,利用I2C进行通信,精度高于DS1302,但价格较贵。
MPU6050:
陀螺仪传感器(姿态传感器),常用于平衡车、无人机之类,也是利用I2C总线进行通信。
所有I2C设备的SCL连在一起,SDA连在一起
设备的SCL和SDA均要配置成开漏输出模式
上图为开漏输出模式简图,当输出为0的时候,开关闭合,输出0;
当输出为1的时候,开关断开,此时引脚为浮空状态,电平不稳定(极易受外界干扰)。——(可以联系矩阵键盘笔记)
SCL和SDA各添加一个上拉电阻(外接),阻值一般为4.7KΩ左右(最好参考I2C通信协议,有建议什么情况用什么阻值,本单片机为10K)
开漏输出和上拉电阻的共同作用实现了“线与”的功能,此设计主要是为了解决多机通信互相干扰的问题
①假如当CPU与IC1通信时,最好其他设备对其没有影响,这时给那些设备为1(因为1没有驱动能力),这样就能避免干扰。
——而弱上拉模式,总归有上拉电阻的干扰,当挂载设备多的时候,干扰就会比较大(主线并联,上拉能力变强)
②因为1发送不出去,于是在外面加两个电阻进行拉高。当CPU想发0时,就可以发0;当不想发0时,只需要不做操作(相当于松手),即可被外部电阻自动拉到高电平,从而输出1。
①SCLK IN为输入缓存,可检测SCLK的电压进行操作(相连的部分因为输入阻抗很大,所以可以相当于断开)
②SCLKN1 OUT右边的部分为电子开关(低电平导通,高电平断开)
——当电子开关断开时,SCLK部分相当于完全断开(因为与SCLK IN相连部分相当于断开)
③后面的DATAN1部分同理。
PS:将SCL后面再拉低(添加方框外的下降沿),是为了能接上后面的拼图部分。——只有最后停止的时候才将其拉为高电平。
流程:
方框部分为发送一个位的基本结构,
当SDA出现交叉时,就是代表从高电平转为低电平(或低电平转为高电平),
而当SCL为高电平(即图中上升)时,SDA不允许有数据变化(即不能出现交叉),
以此类推,最终完成主机八个位(一个字节)的传输数据(接收对象为从机)。
补充:所有线都由主机(这里是单片机)控制的,而我们编程也只编程主机内容;
从机(这里是24C02)负责监测数据,自动读取数据。
PS:主机在接收数据之前,需要释放SDA(即将SDA置1)。
因为根据上面解释内部结构时,提到当SDA处于0状态时,从机是无法获得控制权的(即此时从机无论发什么都会是0)。
——根据半双工的原因,两方共用一根线,一方抓住不放,另一方无法正常发送数据。
流程:
当主机SDA置1后(即图中SDA黑色部分),此时从机负责放入数据(交错时说明跟之前的数据相反,没交错说明与之前数据相同),
接着SCL拉高进行读取数据,读取后拉低进行下一位填充,
以此类推(后面不用再弄SDA置一),最终完成主机八位(一个字节)的填充读取。
流程:
流程与发送应答一位的过程类通,可以视作接收数据的第九位。
(前面是接收应答的过程,第九位是发送应答的过程)
补充:
这里以0代表应答,即上面的内部结构解释的,
当SDA为0时,将线的控制权交给主机,说明其应答了;
当SDA为1时,意味着线的控制权依然留着,说明没有应答。
流程:
流程与接收应答一位的过程类通。
(前面是发送应答的过程,第九位是接收应答的过程)
补充:
这里0跟1作为应答与非应答的理由同上方发送应答。
PS:
这里要释放一次SDA(即将SDA置1)——与接收应答类通。
这里的色块对应上面的拼图,拼凑成数据帧。
这里的数据帧为基础的,后面有其他发展与延申的需要自行了解。
说明:
①在完成起始后(即图中S,对于起始拼图),后面发送的第一位必须是从机地址加读写位;
②对于发送的第一位,A6~A0为从机地址,最后一位为读写位;
③由图可知,读写位中,W上有一横线,说明低电平有效。即读取时置1,写入时置0。(即分配线的控制权,0给主机,1给从机)
——这里发送将其置0,为写入模式。
④而对于A6~A0,前四位为固定端(这里24C02固定端为1010)
——当芯片公司向公司(制定I2C通讯协议的公司)申请I2C的权利后,公司会给芯片一个固定前缀,这里以此而来;
⑤后面的A2~A0为可变地址位(本单片机全部接为0),当使用多个24C02芯片时,通过调配后面的可变地址位,实现多个通讯。
——如果是同一个地址会互相干扰。
这里为接收应答部分,每次完成一个字节需要进行一次应答,
因为是发送数据,因此接着的是接收应答;
RA为0,说明从机应答。
接着依次将发送拼图与接收应答拼图交替进行,
直到最后完成数据传输时,加入终止拼图,完成发送一帧数据帧。
说明:
与发送一帧数据类通,只是后面数据写入的发送拼图与接收应答拼图替换为接收拼图与发送应答拼图;
最后一个发送应答,可以为1(非应答),也可以为0(应答),
这里一般为1(非应答)。
说明:
这里只是将发送与接收一帧数据帧拼接在一起,
先发送再接受,
然后在发送一帧数据帧的P(终止拼图)替换为S(起始拼图);
接着进行下一个第一位操作,进行接受一帧数据的流程;
最后完成所有操作后,用P(终止拼图)结束。
这里的步骤与发送一帧数据类似,只是第二位写入的字节替换为了写入数据所在的地址。
手册中的图:
这里的步骤与先发送再接受数据帧类似,只是第二位写入的字节被替换为读取数据所在的地址。
手册中的图:
有兴趣可以去研究手册,可以了解更多。
实现效果:
通过按下独立按键K1与K2,实现LCD1602液晶屏的数字加减;
并且通过按下K3,将数字存储(即使关掉单片机重开,数据依旧存在);
通过按下K4,将存储的数字显示出来。
方法与之前一样。
其中LCD1602文件为LCD1602液晶屏的显示函数的模块化代码(可以去LCD1602的笔记参考对应代码),
Delay文件为延时函数的模块化代码,
Key文件为独立按键检测函数的模块化代码。
Delay.c文件
Key.c文件
将文件添加进main.c文件中
起始拼图:
终止拼图:
发送拼图:
PS:
这里将SCL置1后迅速拉低,需要考虑其运行速率,如果运行速率快于操作的反应时间,那么就需要延时。(因为单片机运行效率较慢,所以这里不需要)
——单片机运行效率可以利用晶振进行计算
补充:
手册中各操作的反应时间:
从上图可以看到,除了写周期的操作时间为5ms外,其他都是us级别,因此不需要进行延时。
——到写周期的时候需要进行延时添加。
接收拼图:
发送应答拼图:
补充:这里的参数类型可以换为bit,bit为C51特有的变量类型,位宽只有一位,类似于bool类型。
接收应答拼图:
字节写:
随机读:
每次写完内容,能测试尽量测试,避免后面找问题的痛苦。
main.c文件
烧录后发现LCD1602液晶屏第一行显示Hello,第二行显示066,说明代码没问题。
补充:Delay的原因
①上面在写入发送字节代码的时候,曾提到每个操作都有对应的操作时间,而每次完成一次写字节操作后都需要进行5ms停顿,是因为ROM需要写入时间(这也就是为什么ROM比RAM慢的原因)。
(每次的写字节是从一次的Start开始到Stop结束后算一次写周期)
②如果没有在写入完成时就进行下一次Start,那么就会出错。
③每次读入不需要Delay。
①首先先注释掉读取的代码,只写入不读取
烧录后发现Hello正常显示,但第二行显示为000,说明写入完成。
②接着注释掉写入的代码,只读取不写入
关掉单片机重新烧录,发现第二行显示了147,说明即使中途掉电了,数据也没有丢失。
原因:这个现象说明存储的数据在芯片中而不是在单片机中。
实现效果:
通过独立按键K1,实现数码管秒表的开始与停止;
按下独立按键K2,将秒表清零;
按下独立按键K3,存储当前时间(掉电不丢失);
按下独立按键K4,将存储时间显示。
每次按住独立按键,都不会影响秒表的运行效果。
其中Timer0文件为定时器0的模块化代码,Nixie为数码管的模块化代码,Key为独立按键的模块化代码。
Timer0.c
Nixie.c
Key.c更改后:
Key.h更改后:
该部分负责获取按下的按键,并返回对应的按键键码。
main.c中断函数:
①这里利用消抖的特性,根据下图可知,在按下按键并松开按键那刻,前20ms为低电平状态,现状态为高电平状态。
②因此只需要记录前一次的按键状态是否为0,当前状态是否为1,即可知道是否按下并释放按键按键并解决消抖。
③但是为了知道是哪个按键按下,因此当按住对应按键时,第一部分会检测对应的按键,返回数码值,以这个数码值来代替上面的状态0。
④而根据第一部分的处理,将未按下状态时的1以数码值0作为返回。
⑤这样,只需要先将NowState之前的数码值交给LastState,然后将第一部分的返回值给NowState,最后检测这两个值是否满足按下并放开的条件,即可完成按键传出值效果。
⑥每次调用函数时,都是在中断产生时才调用,因此上一次与当前的按键检测相隔了20ms,实现了消抖效果。
这里利用Temp变量来存储Key_KeyNumber的值,使得Key_KeyNumber能够被清零,不至于在按下一次按键之后,未按下的状态时,Key_KeyNumber依然有值能返回,从而出现错乱的现象。
烧录程序后,发现当按下对应按键时,数码管会显示对应键码,且按住按键时,不会影响主函数运行(即不会卡在一个部分,数码管依然能被扫描显示)
因为原先的数码管扫描存在Delay函数,会使代码运行效率慢,影响显示效果,利用定时器扫描,能更加高效。
Nixie.c更改后:
Nixie.h更改后:
①这里将P0清零显示放到最开始,使得每次调用显示函数时,实现消影的操作。
②存储数字的数组最后加入一个空显示,使得能通过调用数组单元第十位,使得对应位置的数码管不显示。
①这里利用一个数组,记录数码管每个位置的状态,并用函数对数组里面的值进行修改,从而实现更改数码管对应位置的值显示。
②为了让数组下标与数码管位置一一对应,所以将第零位填入0,使其无效,让代码更加易懂。
③将数组初始值填为10,对应第一部分,即为空显示,那么扫描数码管时,所有数码管都不显示东西,只有更改数组值才会显示。
main.c文件中断函数:
①这里利用一个循环,依次将每位数码管中的数字(存储在记录数码管数字的数组)提取出来并显示,实现扫描效果。
②当越界时,i重新为1,实现与第二部分相互配合,让数组下标即为数码管对应位置,更加通俗易懂。
烧录程序后,当按下对应按键时,前三位数码管会显示对应键码数字,说明效果良好。
烧录程序后,当按下对应按键时,会发现并不会卡在某一个地方,数码管依然能正常显示,只是按键按下后对应数码管改变的效果有所延迟而已。
原因:因为数码管与独立按键均通过定时器进行扫描(可以打断主函数的Delay),因此当主函数出现卡顿时,并不影响数码管与独立按键的扫描判定,所以依然能正常检测按键与显示数码管。
①中断函数不要出现延时太长的代码(如Delay之类),否则会出现,刚结束一个中断,下一个中断就出现了,压根没时间进行主函数部分;或者一个中断都没结束,下一个中断就到了的情况。
②尽量不要使用太多中断函数,因为中断可能会互相进行干扰。(当然后面如果更熟悉了之后,可以通过调节中断优先级等方法来解决问题)
main.c文件
Nixie.c文件改动:
添加的模块化代码:
将代码一模块化的I2C与AT24C02文件添加进来。
烧录后即可看到预期效果。