本篇内容是观看B站江科大自化协UP主的教学视频所做的笔记,对其中内容有所引用,并结合自己的单片机板块进行了更改调整。
以下笔记内容以一个视频为一个片段(内容较多,可能不适合速食,望见谅)
一些内容涉及前面的知识点,可能需要提前了解(可以翻看本人之前的文章或者去B站看UP主的视频)
单片机上的DS18B20:
拆卸后的DS18B20:
DS18B20是一种常见的数字温度传感器,其控制命令和数据都是以数字信号的方式输入输出,相比较于模拟温度传感器,具有功能强大、硬件简单、易扩展、抗干扰性强等特点
测温范围:-55°C 到 +125°C
通信接口:1-Wire(单总线)
其它特征:可形成总线结构、内置温度报警功能、可寄生供电
(可寄生供电即无需接VCC,只需要接数据线与GND即可完成数据通信,节省线路)
通过外接电路,当温度变化时,热敏电阻两端电压发生改变,产生连续的电压变化(模拟信号),通过AD芯片将模拟信号转换为数字信号,最终将数据反馈出来。
(最终读取的数据只是正比于温度,还需要进行系数配比才能显示出温度)
——因此应用方面不如数字温度传感器。
电路模块:
来自up主数据手册截图:
DS18B20(TO-92分装):
如DS18B20一样,它内部集成了上面模拟温度传感器的部分(集成模拟温度传感器,并在内部实现数据转化),并将数据存储在内部的RAM(随机存储器)中。
——因此无需设计电路,只需要通过引脚与单总线通信协议,将RAM里面的温度转化读取出来即可。
(也就是说,模拟温度传感器的部分都需要利用外界资源,但数字温度传感器自己内部已经集成有了)
up主处部分:
本单片机DS18B20原理图:
up主部分的R1为上拉电阻,与I2C部分的上拉电阻功能相同。(可以参考I2C总线部分笔记对照)
本单片机芯片资料内部结构框图(DS1820,不是DS18B20):
解释作用:这一部分电路能省去VDD,直接采用DQ供电。
①POWER-SUPPLY SENSE负责检测电路是否连接VDD。
②当VDD连接时,电流会正常过来,如下图。
③当VDD没有连接时,上方的DQ会通过二极管流下来进行供电。
这里加入了一个上拉电阻,为了使得寄生供电能满足温度传感器的工作电流要求。
④当为低电平状态时,VDD与DQ都不流入电流,这时为了维持内部系统正常运转,于是需要Cpp(电容)来进行供电。
Cpp在高电平状态时会存储电能,当转为低电平时,将电能释放,以维持系统运转工作。
PS:本单片机并没有接入强上拉电路(上拉电阻部分),因此这里不使用该功能,而采用外部供电。
这里的ROM为只读存储器(真的只能只读),记录器件的地址(ID号),用于总线通信的寻址(不能更改)。
PS:当外部发送正确地址时,才能进入下一部分。(也可以设置指令跳过该部分)
①上面的MEMORY CONTROL LOGIC为内存控制逻辑部分,负责与指令进行交互,进行指令的执行(如写入数据到下方的RAM,或将RAM数据放到单总线上)。
②下面的SCRATCHPAD(暂存器)为RAM,存储着温度等参数,为数据交互的一个寄存器。
①TEMPERATURE SENSOR(温度传感器)相当于内部的模拟传感器集成部分。
当发出指令让其开始温度转换时,该部分就开始工作,并将数据放入RAM中。
②ALARM HIGH TRIGGER REGISTER(TH,报警高触发器)用于存储温度上限阈值,实现温度报警。
存储介质为EEPROM,掉电不丢失数据。
③ALARM LOW TRIGGER REGISTER(TL,报警低触发器)用于存储温度下限阈值,实现温度报警。
存储介质为EEPROM,掉电不丢失数据。
④CONFIGURATION REGISTER(配置寄存器)用于设置分辨率(精度)
——这里出厂默认最高精度为0.0625℃。
可以通过配置寄存器中两位(其他位无效)来降低精度,最低为0.5℃。
当精度降低时,温度转换速率会迅速提升。(相当于处理的数据少了,流程简化,速度就上去了)
存储介质为EEPROM,掉电不丢失数据。
⑤8-BIT CRC GENERATOR(8位CRC生成器)中CRC为校验码,该部分将RAM之前的数据进行校验,并生成一个校验码放于后端,通讯时检测即可判断数据是否正确。(CRC为校正率较高的一种校验码)
本单片机芯片资料图:
这里的EEPROM对应上面存储数据部分的②~④。(因为其他为存在的硬件部分)
温度存储部分:
①Byte 0存储的是温度的最低有效字节,括号内为默认值。
②Byte1存储的是温度的最高有效字节,括号内为默认值。
③两个字节共同组成温度值,在未变动情况下,默认值为85℃。(不同单片机默认值不一样)
配置温度数据部分:
①Byte 2存储为温度上限阈值,Byte 3存储为温度下限阈值,Byte 4存储为精度(分辨率)。
②右边的EEPROM负责存储数据,需要读取与写入数据要先经过RAM部分,然后通过指令将数据存储进EEPROM中(当上电时,EEPROM的数据会自动放入RAM中)
保留部分:
Byte 5~Byte 7为保留部分,并没有使用到,当后续功能升级再添加。
CRC校验部分:
将前面八个字节进行运算,并生成CRC校验码。
可以读取CRC校验码,并对前面八个字节数据进行运算,如果运算结果与CRC相同,那么校验正确,反之数据出错。
单总线(1-Wire BUS)是由Dallas公司开发的一种通用数据总线一根通信线:DQ
异步、半双工
单总线只需要一根通信线即可实现数据的双向传输,当采用寄生供电时,还可以省去设备的VDD线路,此时,供电加通信只需要DQ和GND两根线
DS18B20:
DHT11:
DHT11为温湿度传感器(顾名思义,既可以测温度,也可以测湿度)。
(与I2C总线部分类似,可以去I2C总线部分内容对比了解)
外部供电:
类似于I2C总线原理。
寄生供电:
解释:
这里VDD与GND合并为一条线(即没有VDD)。
Vpu部分有一个电子开关,当输出为高电平时,开关闭合,此时DQ被Vpu部分强上拉进行供电,充当VDD作用。
而当低电平状态时,电子开关断开,此时供电由下方小型的弱上拉Vpu部分进行提供,仅保证工作的电能。
(当进行EEPROM读写等耗电操作时,必须给强上拉才能工作)
这里使用了绝对时间,是因为只有一根线的原因,无法像I2C总线一样利用上升沿或下降沿。
①图中黑粗线Bus master pulling low(主机拉低)是将总线拉低;
②图中黑细线Resistor pullup为上拉电阻将总线上拉(因为是弱上拉,所以利用曲线来表示弱的状态,而不是强上拉一下拉上去)
③当从机存在时,从机会将总线拉低(即图中棕线DS18B20 pulling low)。
④最终上拉电阻拉高,总线恢复空闲状态。
⑤通过以上步骤,可以将过程分为复位与响应两个部分。
①在从机(棕色部分)响应时,检测I/O口即可知道从机是否存在。
②因为中间的操作间隔时间为范围值,且没有参考数据,于是取中间值作为等待间隔时间。
上面的时间片为时序结构。
过程:
可以将上面的部分分为发送0与发送1两部分的时序。
①发送0时拉低超过60us即可,但不能超过120us,否则可能会变为初始化操作(至少480us)
②中间存在总线恢复时间,因此连续发送两位不能低于这个恢复时间。(本单片机操作时间较长,因此这里了解即可)
③图中阴影部分,表示可以在1~15us时释放总线即可,这时电阻都会将总线拉高。
④从机在主线拉低30us后进行操作(如果发送0,就一直保持总线拉低;如果发送1,就在主线拉低后,将总线释放)
操作:
在主机拉低后30us后,从机读取I/O口即可。
过程:
①当主机拉低总线后,从机如果需要发0,那么也拉低总线,当主机在1~15us后释放总线时,因为从机拉低着,因此总线状态为拉低状态(0)。
②当主机拉低总线后,从机如果需要发1,那么就不动总线。当主机经过1~15us后释放总线时,因为没有被拉低,因此总线状态为拉高状态(1)。
操作:
在总机拉低后15us内读取总线电平(I/O口),即可知道从机需要发送的数据。
(之所以主机读取的时间短,是为了给从机留下充足时间,因为从机无法找到其什么时候释放总线恢复为高电平——如图中主机接收0后面的大片阴影部分)
初始化:从机复位,主机判断从机是否响应
ROM操作:ROM指令+本指令需要的读写操作
功能操作:功能指令+本指令需要的读写操作(RAM操作)
①SEARCH ROM(搜寻ROM)
②READ ROM(读取ROM):先调用读取指令,再调用读取时序(接收部分),就可以将ROM读取出来。
③MATCH ROM(匹配ROM):先发送匹配指令,然后发送ROM地址,实现利用该指令匹配与哪个设备进行通信的效果。
④SKIP ROM(跳过ROM):调用跳过指令,直接将ROM部分跳过进入下一部分。
(当总线上只有一个设备时使用。多个设备挂载时,使用这个指令会产生混乱,无法确定是哪个设备)
⑤ALARM SEARCH(报警搜索):当挂载多个设备的情况下,某个设备温度超过设定温度阈值时,会产生报警,利用这个指令即可搜索出超过温度阈值的设备。
①CONVERT T(温度变换):调用该指令时,温度传感器部分会将温度数据的模拟信号转化为数字信号,并将数据放在RAM中(相当于数据更新)。
②WRITE SCRATCHPAD(写暂存器):调用该指令后,再接着写入时序(发送一个字节部分),那么该字节就会被写入RAM中的配置温度数据部分。
③READ SCRATCHPAD(读暂存器):调用该指令后,再接着接收时序(接收一个字节部分部分),那么DS18B20就会依次将RAM里面内容读取出来(直到CRC)。
——下面的实现代码只需要读取前两个字节(温度数据)即可。
④>COPY SCRATCHPAD(复制暂存器):调用该指令后,DS18B20就会把RAM中配置温度数据部分写入EEPROM中(实现掉电不丢失)。
⑤RECALL E2(重调E2暂存器):调用该指令后,DS18B20就会把EEPROM中数据放入RAM中(覆盖之前的数据)。
⑥READ POWER SUPPLY(读取设备供电模式):调用该指令后,紧跟着读取一位时序(接收一位字节部分),就会返回当前供电状态(独立供电或寄生供电)
——使用该指令原因:因为进行操作(如数据变换)时,需要高电能补充;因此在寄生供电状态下,需要调强上拉模式;
如果在独立供电时,就不需要调;
因此出现了这种指令。
①这里的小数部分依靠二分法的机制进行补位,最小分度为0.0625(即每次数据加一,即温度增加0.0625℃)
②这里数据的存储方式以二进制补码的形式存储。
(即正数保持不变,负数则将对应的正数每个位取反,最后加一)
——如上面下方的表格,即为部分温度的二进制与十六进制的数据对照。
这里的P37对应的即为DQ(单总线接口),因为I/O口都连接有上拉电阻(单片机核心原理图部分),因此这里没有显示上拉电阻部分。
手册中的知识为DS1820,不是这里的DS18B20。(DS18B20为DS1820的升级版)
手册中的芯片图:
DS1820与DS18B20的内容基本一样,但存储格式不一样,且分辨率也不一样。
因此,建议去网上找对应的DS18B20的手册。
——英文内容正宗,中文内容更亲切(但是会出现翻译错误)
实现效果:在LCD1602液晶屏显示DS18B20检测的温度。
定义I/O口:
初始化函数:
说明:这里的延时部分,可能与上面的等待时间有所出入,是因为没有建议时间导致的,为了充分等待,因此延时时间较长。
这里的延时语句,需要在STC烧录程序中生成。
以up主的12MHZ晶振为例进行说明:
①从下面可以看到,一条_nop_();语句,延时长度为1us。
②从下面可以看到,调用一次函数,延时长度为4us(本单片机延时长度为5us)。
③从下面可以看到,一个循环语句,延时长度为7us。(本单片机延时长度为8us)
③因此我们无法通过循环的方式,编写一个可调节的微秒函数(因为循环也存在延时)。
——之前毫秒函数可调节是因为毫秒延时长度大,因此可以忽略循环产生的微秒延时误差。
——调用函数时,也会存在延时。
所以需要生成微秒延时语句时,直接在STC烧录程序中生成对应代码即可。
因为初始化函数有返回值,因此可以进行测试。
——边写边测试,养成好习惯。
main.c文件:
OneWire.h文件:
烧录后,发现LCD1602液晶屏显示数字000,说明此时有从机响应;
拔掉DS18B20后,再次检测(可以重启单片机,或者按下复位按键均可),可以发现显示数字变为001,说明此时没有从机响应。
出现上面的显示效果,说明代码没有问题。
发送一位函数:
补充:
①因为上面提到,调用一次函数,都会存在延时(11.0592MHZ晶振为5us延时)。
②因此这里直接生成15us的函数,然后去掉函数结构(也就是去掉调用环节),实现减掉5us的延时,刚好为10us。
(因为11.0595MHZ晶振调用延时为5us延时)
——上面70us没有考虑到调用(其实也是考虑到了),是因为延时较大。
③但是_nop_();语句,添加就得加入
(而且上面也说只要在15us内释放就行,因此少1us影响不大)
接收一位函数:
PS:这里考虑到了函数调用的延时5us,因此生成的是55us延时函数。
发送一个字节函数:
接收一个字节函数:
定义指令:
温度变换函数:
温度读取函数:
补充:
利用浮点数类型,单片机运行速率会变慢,但是这里可以不在乎这一点时间,因此采用了浮点数类型。
PS:这里可以进行更改,使得运行更快。
比如用整形变量存储并通过指针形式返回,显示时再分开显示,再补上小数点的显示就可以达到相同效果。(个人想法)
——事实上后面确实也做了分开处理,因此可以考虑。
说明:LCD_ShowNum(2,6,(unsigned long)(T*10000)%10000,4);
①这里是将小数部分移到整数,然后去掉之前的整数部分,使得能正常显示。
②因为乘以10000后,float类型存储不下这么多字节(float只能存储整数四位与小数四位),因此需要强制类型转换为unsigned long。
烧录后,即可看到LCD1602液晶屏显示温度的效果。
在DS18B20的温度读取函数中添加两条显示二进制代码,即可看到温度对应的二进制数变动,更加直观的知道温度变化的过程。(别忘了包含头文件LCD1602.h)
PS:测试完后记得删除,除非想保留这种效果。
添加划线部分区域,使得上电时不会有一刻出现默认值(本单片机为1℃)
实现效果:在LCD1602液晶屏显示DS18B20检测的温度,同时可以设置温度上下阈值,当温度超过阈值时,会给出显示越界。
可以选择拷贝一份代码一的文件进行更改,也可新建。
这里除了上个程序的模块化代码外,还添加了I2C文件与AT24C02文件,以及Key文件(这个Key文件是未用定时器扫描时的模块化代码)
Key.c文件:
初始显示与准备:
while(1)循环内:
前面部分改动:
while(1)循环内:
初始显示部分:
温度阈值显示部分:
烧录后即可看到预期效果。
原因:因为代码二中的Key具有Delay语句,因此当按住按键时,温度是不会走动的,不符合想要的感觉,因此进行调整。
一、将代码二中Key文件替换为I2C时已经加入定时器模块化的文件。
(更改后的文件代码可以查看I2C部分的内容)
二、添加Timer0文件。
中断函数部分:
初始准备部分:
烧录程序后,发现按住按键,温度显示可以正常走动;
但是温度显示会出现闪烁。
解释:
因为单总线在运行的时候,存在绝对时间,当中断产生时,会打断单总线的运行,使得显示出错。
解决方法:
在单总线运行时,将定时器关闭,使得单总线运行正常。
对OneWire.c文件进行更改:
初始化函数:
发送一位函数:
接收一位函数:
缺点:
因为这里使用的是按键扫描,因此定时器被停止影响不大。
但是如果为计时操作,定时器被停止会产生较大的误差,导致时间不准。
①因为单总线具有严格的运行时间,导致运行时无法被打断(即需要将运行优先级调到最高),所以使用不方便;
可以通过外接模块自行扫描来实现分开运转,保证各自的最高优先级。
②I2C总线因为具有联动性(即主机停止,从机也停止),因此中断对其影响不大。
LCD1602(Liquid Crystal Display)液晶显示屏是一种字符型液晶显示模块,可以显示ASCII码的标准字符和其它的一些内置特殊字符,还可以有8个自定义字符
显示容量:16×2个字符,每个字符为5*7点阵
(实际为5*8的点阵,只是字符占据5*7)
1602是由其能显示的字符数命名的数字。
LCD1602正面图:
LCD1602背面图:
这里黑色的两个圆圈为LCD1602的芯片(里面封装了LCD的控制电路与字模,因此自带扫描电路,无需写入代码扫描——联系单总线最后总结部分)
LCD12864:
12864指的是宽度为128个像素点,高度为64个像素点。
(本单片机也可以加入这个液晶屏,插入第二行排座即可)
一般mp3使用的屏幕:
定制的LCD显示屏:
可以定制特定的字符,对比前面的通用LCD成本低。(但是定制的没法更改一些内容)
彩色LCD显示屏:
本单片机上原理图:
VO:电位器硬件,可以通过转动调节对比度。(对比度太浅,无法显示字符;太深,每个像素点都显示,无法分辨内容)
——这里的电阻尽量选择大一点(千欧级别以上)。
本单片机上电位器位置:
D0~D7(本单片机的DB0~DB7):八个引脚代表一个字节,并行传输模式。
(最好接在同一个寄存器上,如P0,而且最好从低到高排列,否则需要处理数据比较麻烦)
RS:数据/指令选择。当为1时,传输的字节为数据;当为0时,传输的字节为指令(不显示在屏幕上)。
RW:读/写选择。当为1时,代表单片机向LCD读取数据;当为0时,代表向LCD写入数据。
(一般读不出什么数据,因此后面写入代码使用写选择)
E:使能,相当于I2C的SCK。当为1时,将数据或指令传输;当为0时,LCD内部处理指令数据。
A与K(本单片机上的BG VCC与BG GND):前者为背光灯电源正极,后者为背光灯电源负极。控制LCD背光灯亮起来,如果不接,屏幕会显示,但不会发亮(因为屏幕不自带灯光)。
CGRAM+CGROM(Character Generator,字模生成):字模库,其中RAM是可写入的区域(即上面的自定义字符),ROM为不可写入区域(存放着ASCLL标准字符与一些特殊字符)。
DDRAM(Data Display RAM,数据显示RAM):
数据显示区,写入的数据都存储进该存储单元中,
并且通过在字模库中查找,最终显示在屏幕上。
(类似于二极管显示数字,不能直接给数字,而是需要给对应十六进制数进行转换的道理,这里的字模库相当于十六进制存储库)
补充:数据显示区为40*2,而屏幕区为16*2,因此只能显示数据显示器前十六列的数据(如果想显示其他列,需要指令操作)。
控制器部分的AC:光标位置,相当于在数据显示区的特定区域写入内容。(可以通过指令确定AC位置)
上面为数据显示区对应位置的地址。
PS:第一行与第二行的地址不是连续的。
字模库:
ASCLL码表:
写时序:
读时序与写时序类似,但是这里不需要读,只需要写入数据即可。
步骤(只需要看第一次写时序部分即可):
①将RS设置(1为数据,0为指令)。——图中交叉部分代表填写数据。
②将RW置0(为写选择)。
③将DB0~DB7填入字节数据。
④将E拉高,传输数据。
⑤再将E拉低,完成数据传输。
根据1所在的最高位位置,对应不同的指令块。
实现效果:在LCD1602液晶屏显示中显示字符、字符串、无符号数字、有符号数字、十六进制数、二进制数。
单片机核心原理图(部分):
LCD1602原理图:
定义部分:
补充:这里define可以直接用sfr来进行定义,不过定义的名字后面需要接着地址,因此采用define形式。
REGX52.H文件:
写指令函数:
这里根据手册来说,单片机速度没有LCD处理数据的速度快,但是依然会出错,因此加入延时函数。
私有延时函数:
为了后续便于修改(如单片机运行速度更高时,需要调整延时),而不用影响其他文件,因此生成一个延时函数放入其中作为私有延时函数。
与写指令函数过程相同,只需要将RS置一即可。
初始化函数:
显示字符函数:
①Char中可以直接填入对应的字符地址(对照字模库),或者利用单引号加上字符的形式填入(不过如果ASCLL码表字符与字模库字符不一样时,会显示不一样)
②当需要填入特殊字符时,利用反斜杠即可消除特殊字符的效果,从而单引号引用出该字符(如要表示双引号,需要先打入反斜杠\,再加入双引号)
PS:事实上,写到这里已经可以完成所有字符输出了,但是为了显示方便,加入下面的函数
这样就可以省去代码相同的情况,直接调用函数即可。
更改后的显示字符与字符串函数:
为了能将数字每位抽取并显示,需要另建立一个抽取函数。
原理:截自up主
无符号数字函数:
烧录后即可看到预期效果。
利用画面移动指令,即可实现流动效果。
烧录后,即可看到LCD1602显示流动效果。
流动时DDRAM内的数据会向一方移动(根据指令确定移动方向),而到达边界之后,会重新移到末尾处,不会将数据移没。
单片机上的电机:
带扭矩的电机:
带编码器的电机:
本单片机上原理图:
说明:因为电机为功率较大的负载,直接接在单片机上的I/O口会导致无法驱动,且存在损坏单片机的风险;
因此需要加入驱动电路。
常见的驱动电路:大功率器件直接驱动、H桥驱动。
①这种驱动下的电机只能沿一个方向转动。
②这种电路与三极管开关类似,在Q1部分需要加入一个功率较大的器件(常见的为达林顿管与MOS管)。
③Q1的MOS管相当于电子开关的作用,当IN端传入低电平,电路就会导通。
④D1部分的二极管(续流二极管)用于保护电路。
当IN正常赋0时,电流不会通过D1所在的部分,电机正常运转;
而当IN部分控制电子开关断开(变为1)时,因为电机为感性负载元件(电感的特性:感应出很高的电压),会导致在B1处产生很高的电压,
如果没有D1部分作为闭合电路释放掉电流的话,会导致电子开关部分损坏,或者直接损坏IN处的I/O口。(这一部分联系高中的楞次定理)
这种驱动可以控制电机正反转。
①通过控制Q2与Q3断开,Q1与Q4导通,实现电流在B1向右流动,电机正转(假设这个方向为正);
②控制Q1与Q4断开,Q2与Q3导通,实现电流在B1向左流动,电机反转。
③这种电流因为没有续流二极管(上面大功率器件驱动电路里的部分),所以无法消除感抗导致的电压增大,因此要求驱动的Q1~Q4(MOS管或晶体管等)需要有很强的耐压特性,避免被击穿损坏。
如果利用电机驱动芯片的话,其内部自带驱动电路,因此无需自己配置电路设置。
PWM(Pulse Width Modulation)即脉冲宽度调制,在具有惯性的系统中,可以通过对一系列脉冲的宽度进行调制,来等效地获得所需要的模拟参量,常应用于电机控速、开关电源等领域
(因为利用的是数字信号的宽度变量,而不是模拟信号的宽度变量,因此对于单片机之类的物品而言,更容易产生对应宽度变量来控制)
PWM重要参数:
频率 = 1 / TS 占空比 = TON / TS 精度 = 占空比变化步距
补充:
①频率越高,电平变化越快,越能等效出连续的模拟信号曲线。(如果频率较低,应用于电机控制时,会导致电机运行断断续续——因为电平是0跟1之间变化)
②占空比指的是在一个频率段(一个高电平加一个低电平)中,打开的时间(图中凸起地方)占据总频率段时间的百分比;
也可用TON:TOFF来进行表示(但是不常用,因为这种表示的是比值,没有百分比直观)
③精度指的是相邻占空比的变化步距。
——即第一个占空比为1%,第二个为2%,第三个为3%,那么其精度就为1%;
而即第一个占空比为0.1%,第二个为0.2%,第三个为0.3%,那么其精度为0.1%;
前者的精度没有后者高,
精度越高,占空比调节越细致。
说明:因为电机一通电时就会全速运行,因此需要一些手段对其进行调速,于是采用PWM进行控制。
——在电机所在电路直接接一个电位器进行调速不可行,因为电机运行时,电位器只能接入几欧的电阻,而电位器为纯电阻电路部件,因此全部电能转化为热能,最终会导致电位器烧坏(电机不会烧坏,因为其为非纯电阻电路)
PWM控制电机思路:利用PWM控制电机的导通,利用1ms通电,1ms断开,实现电机速度的减半
(中间因为电机无法立即停下来,需要缓冲来缓慢降低速度,因此可以快速变化来将电机卡在缓冲部分,实现降速),
其他速度同理。
解释运行原理:
利用一定时间控制电压的正反来实现模拟连续的正弦线的运转(即图上的黑细线在同一时间对应着紫色的正弦虚线)
实现效果:将第一个LED灯亮灭像呼吸的方式一样缓缓降低亮度再提升亮度。
①这里利用延时函数,使得每次LED亮灭具有一定的间隔时间,且无需手动调节。
②利用for循环,将占空比不断进行调节,呈现递增效果,最终实现数字信号转换为模拟信号的效果
PS:烧录程序后,可以正常看到呼吸灯效果。但是该程序运转时,主函数无法执行其他工作(因为在循环),因此需要改进(利用定时器与PWM)。
PS:该编译思路需要看完代码二部分的内容,才能接到这里。
这里利用了PWM进行编写,通过定时器来处理延时部分,能避免Delay导致的主函数占用问题。
这里通过变量state来对灯的状态进行记录的原因:
因为灯从亮到暗与从暗到亮,对应的波长相同,
只是0与1调换,因此采用state进行取反,赋值给LED,即可省去对亮到暗的判断语句部分。
(这里是因为LED仅为一位二进制,所以非零即一的原则,取反的state并不是1,只是赋值转换为1而已)
这里使用Stop变量的原因:
通过Stop来记录是否进入中断,从而能将Compare的相关的if语句移入主函数中;
也可不用加Stop变量,直接将主函数部分的if语句移到中断函数也可(不过缺点是会导致中断函数内容过长,主函数一无所有)
如果直接将Compare相关的if语句放入主函数且不加入Stop变量的话,会导致下一次中断还没产生时,Counter的值一直保持为99,导致Compare多次++,最终产生错误的效果。
主函数判断为Counter==99而不是Counter==100的原因:
因为当Counter为99时,如果为Counter==100,那么就需要等到下一次中断让Counter++才能进入if,
而Counter在进入中断加到100后,立即被下面的取余语句给刷为0,因此永远无法达到100的值,所以需要设置为Counter==99。
主函数判断为Compare==99而不是Compare==100的原因:
跟④同理,因为Counter是与Compare比较的,如果Compare==100的话,会有一段时间只判断上方的if语句,而绝对不会出现下方的else效果,所以改为Compare==99。
(其实改为Compare==100也没事,且可以达到最大亮度与停止亮灯)
实现效果:通过按下独立按键K1实现电机风扇转速调节,并在数码管第一位显示当前转速等级。
其中Key文件为独立按键的模块化文件(利用Delay进行消抖的),
Timer0文件为定时器0的模块化文件,
Delay文件为延时函数文件,
Nixie为数码管显示文件(利用Delay进行消影的)。
添加到main.c文件
上图即为设置比较值为1时,对应的PWM输出,最终通过不断改变比较值,模拟出连续的模拟信号曲线。
在STC烧录软件中获取100us的重装值,并移动到这里(与下面的定时器中断模板),使得中断产生速度加快。
(PWM越快越稳定,但是太快会导致电机抖动等现象,因此适当速度即可——通常设置为10K~20K频率——单片机设置不到这么快,于是设置为100us,然后每100次中断执行一次,即10ms)。
①这里将比较值设置为固定值,使得电机在对应按键等级下的转速固定。(运行原理参考上一节)
②这里电机端口设置为P1^0端口,到时连接使用该端口即可。
本单片机原理图:
电机连接方式:
由图可见,电机一端连接5V,另一端连接01,而01对应的就是原理图上的P1^0口。
烧录后,即可看到预期效果。
因为电机对应I/O口为高电平时转动(原因参考蜂鸣器部分的ULN2003D部分,1经过非门变为0;再联系上节运行原理即可),
而在刚上电时,I/O口默认为高电平,因此电机会转动一下,直到程序执行后才停止。
该原因为硬件设置问题,因此无法解决(如果自己设计电路板硬件时,可以考虑解决这个问题)