3.LED灯定时器中断延迟亮灭实验
上一篇教程有提到定时器中断,这篇教程我们就来简单运用一下定时器中断,这里还是老规矩就是创建2个文件Timer.c和Timer.h,基础配置就这里不介绍,我的第一篇教程里有介绍可以先去阅读。
然后整体工程为:
然后就是定时器的配置,这里我是用到了定时器0来实现计时,相关配置可以先了解下手册:
还有就是寄存器配置:
这里的TCON只用到了TR0和TF0,TF0可设置也可以不用设置,因为是硬件置1和0的。
这个是TMOD寄存器,这个寄存器和上面的TCON寄存器有区别是不可位寻址,也就是这个只能一次性初始化所有位,也就是TMOD = 0x00,这里值得注意就是定时器的模式,这里我选择的就是16位自动重装载模式
然后就是计算公式,这里的TH0和TL0就是我们设置16位的高8位和低8位,所以计数的时候我们需要把这两个数和起来计算,然后就是时钟频率,时钟频率是由选择的时钟决定的,所以为了方便计算我是用了12MHz去计算的,所以延迟还是有些误差的,后面我会用逻辑分析仪调试一下。
因为定时器只有16位,而且12MHz,而且机器周期是12MHz/12 = 1us,也就是定时1次就是1us我们计算,所以16位最大也就是2的16次方=65536,也就是65536us=65.536ms,最大就是到65.536ms后进入中断,中断的话就是当定时器到达中断条件时,MCU当前执行的程序就会挂起然后去执行中断里面的程序后再回到原来执行的地方继续执行,这里可以自行百度一下,网上由许多概念可以帮助理解。
我们这里是定时1s,所以就需要实现多次定时然后中断,我们定义一个全局变量的数来记录几次到达1s然后执行灯的亮灭,所以接下来就是看下文件的配置。
timer.c文件的配置:
这里我们可以看到我们配置了TMOD = 0x00;这里我是配置T0在模式0
然后分频就是初始化12分频,分频在手册这个图可以看到(这里我们是初始化的值0),所以就是12分频:
然后我是用12MHz来计算定时一次所用的1/(12MHz/12) = 1us时间,这里代码我们看到TMOD = 0x00,根据上面手册截图我们可以看到我们把T0工作模式设置成模式0:
然后就是TH0是我设置的值的高8位,TL0是我设置的值的低8位,然后后面我们设置64536是因为定时器计数到65536时会硬件把中断位置1,64536-65536刚好就是1000次计数,然后1000us = 1ms,所以这里每次产生中断就是1ms,但是这里并不是12MHz的频率,后面会讲解,然后就是开启TR0定时器的运行中断位:
然后就是开启ET0:
然后就是开启总中断EA:
然后定时器和中断配置基本就已经配置好,等在main函数调用配置即可。
Timer.h文件的配置:
这里只是函数的声明而已。
然后就是main文件的配置:
这里我们看到我们在main函数下又定义了一个函数,这个函数于我们之前的函数定义有所不同就是在函数写完后紧接着interrupt这个是非标准的关键字,这里是51中断函数配置必须添加的,根据手册:
这里可以看到上面我们在interrupt后面添加的数字是根据这个表来配置的,因为我们使用的是定时器0(timer0)所以次序是1,如果我们使用的是外部中断0的话就是0。
然后这个函数我们没有在主函数里调用,这里要讲的是这个函数不需要调用,也就是当定时器计数到65536时会硬件把TF0置1,然后产生中断,然后中断会自动调用我们配置的函数,也就是上面的LED_loop()函数,我们看看LED_loop()函数里的配置,首先我们要实现1s的延迟,而我们定时器最大定时也就约65536us=65.536ms所以我们需要分配一下一个变量来记录记录多少次中断,因为我们是定义1ms,那么我们就需要这个数记录1000次后再执行操作,那就是1s了,而这个变量需要值不会重复初始化,那就要定义成静态变量static,所以可以看到我们用到了static关键字去定义了,然后就是每次进来i++,记录1次,到达1000次时就让LED端口的电平反转一下,从高变为低或者从低变为高,这里就只是用了端口配置P20,然后还得把i清0重新计数,但是有没有看到我们if判断里的i值不是1000,因为每次进入中断程序执行的语句也时需要CPU处理的时间,然后就是开发板的时钟不是12MHz,可能是11.0592MHz,这就是意味着执行一条指令的时间比我们预估的还长,导致定时器计数一次的时间比1us长,所以我们调节if中i的成立条件减小即可,通过逻辑分析仪看高低电平时间就是现在的920时接近于1s,所以这就是不是i == 1000而时i=920的原因了。
好了看一下逻辑分析仪的测试图:
然后就是实物图:
代码:
通过定时器延迟开关灯.zip
(69.65 KB, 下载次数: 433)
4.LED流水灯实验
前面我们已经介绍了软件延迟和定时器中断延迟,也通过控制一个灯的亮灭,接下来我们来实现流水灯,这里我们不借助intrins库里的函数,我们通过简单的移位和位运算来实现,这次我们不需要创建文件,用之前的文件,然后我们操作的是P2的8位端口,所以我们在之前的工程里的led.h里添加一条宏定义:
然后就在主函数里实现我们的编程,首先是使用软件延迟函数:
这里我们需要一个变量来实现移多少位,也就是i为要移动多少位,整体编程思想:
所以如果要实现从下到上亮就是要实现8位的数值如上图的:
所以可以看到我们在while循环里写的代码就是实现这一功能的,这个需要理解下,不算难,主要是要理解移位和位运算
如果从上到下的思路和代码就是:
然后定时器的代码就是main文件不一样,其余一样
从下到上的流水灯:
从上到下的流水灯:
上面流水灯要结合C语言的移位和位运算,希望能把这个知识点学好运用
5.LED呼吸灯软件延迟实验
接下来我们来认识一下pwm,脉冲宽度调制,用数字输出量来实现模拟输出的一种调制方式,这里相关的简介可以自行百度一下,stc片上是自带硬件pwm输出的,但是我想着我们可以通过逻辑先实现一下简单的pwm输出,然后再用硬件pwm输出,这样对于pwm原理会更加深刻的了解,因为我本身也是学生,这些信号输出采集和协议我都是刚接触不久学习不久,所以就出了这些教程可以来回顾我自己学习的情况,如果我写的教程有什么地方讲解错了希望各位可以回复出错的地方,我查阅后修改就会完善该教程。
话不多说,首先逻辑实现pwm,要用到延迟,我之前也通过2种方式实现了延迟,那么接下来这个逻辑实现pwm我也是用了这2种方法来实现,一个是软件延迟方式,另一个就是定时器中断方式,最终的实验结果就是让LED灯逐渐变亮然后逐渐变暗,也就是呼吸灯。
软件延迟方式,我是习惯模块化编程创建了pwm.c和pwm.h文件,这次教程可以不用创建,直接在main文件主函数实现即可,接下来看下相关文件的配置。
pwm.c文件配置:
这里我们看到我们用到的都是之前定义的函数和宏定义,而这里我们需要知道的就是这led_pwm函数里的循环结构的逻辑,首先我们要先了解一下pwm是什么做什么的。
就拿玩具遥控车来说明一下,按照我们的生活常识,我们知道当遥控车的电量不足时,我们通过遥控让车前进就会非常慢,因为驱动能力下降,显然电池电压会降低,电机就会变慢,那么我们知道单片机只能让电平置1或置0,而置1通常就是MCU供电电压,然后0就是0V,如果只是输出1或0的话就是定量的电压,那么就有了pwm宽度调制,也就是在一个周期内,高电平和低电平的占比可以通过这个公式来实现对相关电压的输出。
需要输出的电压=需要的电平(高电平或者低电平)/一个脉冲周期(高电平时间+低电平的时间)。
如果按照正常情况下我们知道我们STC32单片机只能输出5V和0V,那么通过这个pwm脉冲调制我们可以输出3.8V或者其他0-5V电压范围,比如我们要输出2.5V电压,那么我们就一秒在一秒内0.5s是高电平还有0.5s是低电平,这样的1s就是输出2.5V电压,当然时间周期可以缩短,1s我们可以用肉眼看到灯的亮灭,如果1us中0.5us置1和0.5us置0,那眼睛就不能分辨亮灭了,可能能分辨出来的就是亮和暗了。
所以我们就是要在短时间内实现高电平和低电平的占比变化来实现灯灭的时间和亮的时间不同,由于这个变化特别快,只能感觉到灯的亮和暗,来实现灯的呼吸效果。
这里我们看到有2个循环,其实这里的效果就是一个循环是实现灯从暗到亮,另一个是从亮到暗,那么接下来我就只讲一个循环,另一个循环其实就是改变一下参数而已。
这个循环是从暗到亮:
突然想到前面延迟文件里只是配置了ms的延迟,那么我们就在stc-isp软件里查看1us函数为:
然后我在delay文件里的配置是:
这里和ms一样延迟我修改了可以自定义us延迟,这里的方法和ms函数方法一样。
我们看下pwm循环,通过程序我们可以看到:
这里我们可以看到,循环了1000次,然后就是每次循环就是一个周期,然后循环里面首先就是LED0_ON是开启LED灯,这个指令大约1us,然后就是延迟ius,然后就是关闭LED灯,然后就是延迟(1000-i)us,如果把LED0_ON和LED0_OFF这2条语句的处理时间忽略的话就刚好每次循环就是(i+1000-i)us = 1ms,也就是1000次循环就是1000ms = 1s。通过i的增加可以知道LED0_ON延迟的时间就会变长,LED0_OFF的延迟时间就会变短,也就是灯开启在每个周期内的延迟时间会逐渐变长,然后灯关闭的时间会逐渐变短,这样就是实现了灯的由暗变亮的实现。第二个循环就是由亮变暗的过程,可以通过上面的讲解来自行理解一下。
pwm.h文件配置:
这里就是对我们配置的函数进行声明,然后方便其他文件调用。
main文件的设置:
调用我们配置的函数,实现呼吸灯。
由于篇幅太长,后面的定时器实现等下一篇教程介绍。
6.LED呼吸灯定时器中断实验
上一篇教程是用到了用到软件延迟函数来实现呼吸灯的效果,这篇就介绍用定时器中断来实现呼吸灯效果,之前在LED的延迟亮灭已经介绍过定时器0的配置,然后官方管理员有回复说讲解一下定时器24位配置,也就是说明定时的范围会增大,所以我去翻了翻手册介绍,然后看到定时器介绍的这句话:
8位的预分频和16位的计数,也就是16位计数还是不变,只是加了这个预分频,然后接下来就是看预分频寄存器的介绍和作用用法即可,通过翻看手册:
这里我们可以看到官方手册说的就是定时器时钟和分频系数的关系,这里我们需要明确的是其实定时器的计数还是65536这个范围,但是由于时钟频率被我们改变了,所以每计数一次的时间我们可以改变,这里分频我们还可以看到辅助寄存器AUXR也是对CPU时钟的分频,也就是我们可以通过这些频率来实现宏观上计数的位数加长,但实际定时器的计数器也就是16位,65536不变的。一开始我以为是直接一个24位的计数器,结果查阅手册后发现通过这个巧妙的方式给改成的,实际就是计数时间不同,这个要理解一下,就比如我们本来计数是1us的话,那么如果这个预分频系数是给127的话,那么定时器时钟就会变为原来的1/128,那么本来计数是1us,现在是1/(1/128)us = 128us了,这样的话本来计数65536us现在就可以计数65536*128us也就是2的24次方us了,也就相当于比原来扩大到24位计数定时了。
然后就是AUXR这个辅助寄存器,我们看下手册:
然后看下定时器结构图:
这里我们可以看到时钟输出就是要到声明我们讲的8位分频寄存器那,然后配置完我们可以再配置12分频或者不分频,默认是12分频,如果实现1分频(也就是不分频的话),就需要把完成一次计数的时间除以12,也相当于变快了。像上面的如果完成一次计数时间是1us的话,这个AUXR的T0x12设置成1的话完成一次计数就会变成1/12us了,也就是相当于完成一次计数的时间变短了,这里是配置定时器0,其他定时器配置也是这样,只是寄存器操作位不同。
接下来回到我们的呼吸灯实验
上一篇教程已经讲了相关的逻辑了,其实定时器也是一样的,先来看下我们的配置吧
Timer.c文件的配置:
这里我们还是用定时器0,然后这里与之前多了一个预分配配置,这里我是配置了TM0PS = 0xff,这里也就是相当于128分频了,因为计算的时候要加1。AUXR我们就没有配置,也就是12分频默认的。本来我们如果不加分频就是我们可以看到我们TH0和TL0的配置是计数2次也就是2us,现在加了128时钟分频,那么现在就是2*128us = 256us了,也就是按照我们原来不设置预分频的时候我们计数一次是估算1us的话,这个配置2次计数也就是2us进入一次中断,但现在设置了预分频后就是256us进入中断。
然后接下来就是在main文件里配置即可了,首先我们需要定义几个全局变量来记录相关值:
这里简单介绍一下,count是用来调整占空比增加的时间,这里是固定多少时间然后实现占空比的增加或减少,然后就是pwm_count这个就是有效电平和无效电平的边界值,这就是我们调制占空比的值,然后就是i,这个是每个pwm的周期,然后就是flag,这里是用bit来定义的,也就是只有0和1这2个赋值数,这里是可以后面判断是让pwm_count是增加还是减少,实现有效电平的占比多还是少,就能实现灯的亮暗了。
然后就是定时器0中断函数:
这里可以看到我们用了非常多的判断语句,首先就是第一张图里面的2个判断语句就是为了操作pwm周期里的有效电平和无效电平的输出,我们首先可以先看一下,中断函数最开始是count和i的自增操作,这个就是为了计算时间,然后我们看到第一个if语句里的判断条件是当i小于pwm_count这个边界值时就开启LED灯,然后就是第二个if语句,判断条件就是大于pwm_count值时关闭,这个判断里面还嵌套了另一个if语句,这个就是定义pwm周期,这里定义i=15时就重新开始新的周期,而这个i=15是根据4000us=4ms来算的,这里约等于15次中断(因为每条语句也是要有处理时间的,所以我把后面的舍去,这里我们涉及的都是大概的时间值,想要准确的值需要调试和仪器,我手上只有逻辑分析仪,所以只能算个大概):
然后通过调整pwm_count的值就能实现占空比的变化了,这就是中断函数第二张图实现的,首先我们可以看到我们通过一个判断,判断条件是count == 390,这里是因为我想每100ms就实现pwm_count的变化:
然后就是if和else判断语句,判断条件是flag,这就要看里面内容了,如果flag是等于1的话就实现pwm_count自增,这个效果就会让每4ms一个周期的LED灯的亮的时间变长,关的时间变短,然后就是把count的值置0,重新100ms再进入操作,然后就是判断pwm_count是否等于15,因为我们周期的最大也就是15的宽度,超过了也就没意义了,然后就是如果等于15的话我们就把flag的值置为0,那么接下来100ms后就会进入else语句里了,然后我们看下else,其实差不多,不过这个是让pwm_count自减,这个效果也是会让每4ms一个周期的LED灯的亮的时间变短,关的时间变长,然后就是把count的值置0,重新100ms再进入操作,然后就是判断pwm_count是否等于0,因为我们设置为0的话就相当于一直是关灯状态了,然后就是如果等于0的话我们就把flag的值置为1,一直这样就能实现,从暗到亮然后从亮到暗一直反复。
7.PWM输出实现LED呼吸灯实验
今天我们来看一下呼吸灯最后一节,是用片上的硬件pwm外设,这里我只用了一个通道,首先我们得先看下数据手册来了解一下pwm外设
这里23.1简介到23.6中断是介绍相关的PWM配置,建议自己先看一看了解一下,因为这篇是带大家如何配置完成呼吸灯效果,这些可以有兴趣的时候看一看,后面配置寄存器的时候我可能会涉及到的会介绍一下,如果有机会后面我专门写关于PWM硬件配置的详细教程,那么接下来我们代码部分,这次的实验我们首先要用到的就是我们之前定义的定时器0和我们可以定义2个函数,一个是初始化PWM相关寄存器,一个是更新我们设置捕获/比较寄存器。
首先我们来看一下定时器timer.c文件
这里我们还是用到了T0,然后这里不同的是我们定义的时间就是1ms,因为我们由之前的实验我们估算计数1次为1us,那么我们这里64536-65536刚好就是1000次计数,那么就是1000us=1ms所以这里的定时器就是1ms进入一次中断了。
接下来就是pwm.c文件
这里我们可以看到定义了2个函数
这里pwm_Init()是配置寄存器的初始化:
首先我们这个呼吸灯我只用了一个LED实现的(P20的LED),后面的实验如果各位想用其他IO端口的LED灯实现的话就需要自己通过本次实验教程如果去配置相关的寄存器即可。
首先我们看一下:
这里我们配置的是这个捕获/比较使能寄存器,首先就是我们要配置时要清零一下,数据手册:
这里我们和这一条语句一起讲:
这里的0x05 = 0000 0101b,也可以看到我们把CC1NE和CC1E置1然后其余的都置0了,那么我们看下数据手册:
可以看到我们把CC1NP置0就是高电平为有效其实这里需要和后面的比较值写入和pwm模式相结合的,这里我们有一个概念就行,后面结合介绍,然后我们把CC1NE置1,那就是开启比较输出了,也是和后面的相关的寄存器一起相结合的,然后就是CC1P就是配置高电平有效,然后就是CC1E就是开启输入捕获/比较输出使能,然后我们可以看到这条语句:
这里看清楚是CCMR,不是上面的CCER寄存器了,看下数据手册:
这里的0x60 = 0110 0000b
所以就是配置OC1M为110,其余的都是置0,由于这个寄存器的介绍篇幅太长,我们一点一点来看,比如OC1CE置0:
这里我们可以看到当置0时不受ETRF的影响,我通过搜索可以知道:
ETRF为外部触发输入。
然后就是OC1M置为110,我们可以看手册里介绍:
也就是设置PWM模式为模式1,这里我们可以看到我们不管是向上计数还是向下计数的,都是当我们计数器CNT的数小于我们设置的之前逻辑实现时讲到的边界值的数的时候就是输出高电平,大于边界值的时候就是输出低电平了。
然后就是OC1PE置0:
这里我们可以看到这个位是设置是否开启CCR预装载器,这里禁止,也就是我们直接写入,不通过预装载了。
然后就是OC1FE置0:
这里我们可以看到的是快速使能,这里和之前一张介绍图有点联系:
就是可能会直接装载而不像上图一样会过段时间装载
然后就是CC1S也是置为00:
这里就是单纯的输出模式了。
然后就是这2句语句了:
这里我们可以看下这2个寄存器了:
这里我们可以看到这2个寄存器是分别来接收我们配置的高8位和低8位的值,这里应该就是一个16位的数和定时计数器一样分成2个8位寄存器来载入了,这里通过我们之前PWM的基本介绍我们根据这个图:
可以知道这个是配置一个计数周期为多长,我是设置成1000个计数了。至于1000/256和1000%256就是对高8位提取和低8位提取了。
然后就是这个语句:
这里我们看下手册相应的寄存器:
然后我们这里只涉及到通道1,也就是高6位我们可以不用管了,我们看下这2位的手册解析:
可以看出我们是禁止了PWM1N输出然后使能PWM1P输出,到这里我们要和这条语句一起看了:
这个寄存器手册查看:
又是我们只用到了PWM1通道,这为什么呢,我们之前已经讲过我们只驱动了P20上的LED实现PWM输出呼吸灯,那么就是这个寄存器决定的,因为我们需要的引脚就是P20,刚好下面的解析:
在通道1里刚好有一个是P2.0端口了,那我们这里配置这个寄存器这低2位就是01,由于其余的我们不用所以都配置成00了,然后这里我们可以看到P2.0是在PWM1P通道,那么上面的ENO寄存器使能PWM1P通道即可,也就是ENO1P置1其余置0。
然后就是这条语句:
我们通过手册查看寄存器:
这里可以看到我们把MOEA位置1,其余都是0,这里我们看下MOEA位的配置解析:
这里有一个小的问题就是这里的CCIE,这里应该是CCnE位我觉得或者是CC1E。
这里置1是使能OC和OCN,然后我查找手册相关描述:
这里可以理解成输出的信号会经过处理,那么对于信号的稳定性会加强。然后就是AOE位被置0:
这里因为我们是不用更新事件是就MOE位自动置1,因为我们用软件置1了。因为刹车的功能我们没有用到,所以这里不讲配置刹车功能的寄存器,后面如果有用到的话到时再做介绍。然后就是OSSRn和OSSIn都是配置0:
那么当PWM不工作的时候就禁止OC/OCN输出了。
然后就是LOCKn置为00:
这里可以看到我们没有设置级别,也就是无保护模式了。
然后就是初始化的最后一条语句:
我们查看手册描述的寄存器:
这里我们只设置了CENA为1,其余的都是0,那么接下来来看下相关位配置的解析:
首先就是ARPEA置为0:
这里就是因为我们之前也是没有设置预装载的,所以这里设置成没有缓冲,直接写入的。
然后就是CMSA置为00:
这里是边沿对齐模式这里我用PWM介绍里的一张图来看比较容易理解:
看上面图可以看到边沿对齐模式和中间对齐模式有所区别,就是我们只有逐次递增或者逐次递减,到达我们的极限值的时候就会重新加载,也就是我们看到在同一时间的时候会出现一个跳变,整体看起来就是对齐。
这里的向上计数或向下计数是取决于DIR位,那接下来我们看下DIRA位置0:
这里我们可以看到我们设置成向上计数模式。
然后就是OPMA置为0:
这里需要2个图:
这里的单脉冲模式可以看下手册解析,由于篇幅太长,我自己做一下解释,就是当更新事件来的时候会产生一个一定宽度的脉冲,然后上面的OPM位置0是当这个更新事件相应后计数器不会停止,会继续计数下去。
然后就是URSA置0:
这里我们可以看到我们置0后会有三个事件可以产生中断,这里是和下面我们介绍的UDISA相结合的,该位置0:
这里可以看到和上面一样的三个事件,也就是UDIS位置0后就是可以使能这3个事件成为更新源,然后上面一位置0,就是这3个事件可以成为更新源。这2位是相互配合的。
然后就是CENA置1:
这里可以看到我们使能了计数器,这里所有的寄存器初始化就已经结束了。
然后就是pwm_setcount(unsigned int count)函数
我们可以看到这个函数里只有2个语句:
这里我们看下手册:
这里设置的就是我们之前逻辑实现呼吸灯时说的分界值,也就是比较值了,我们上面配置CCMR寄存器的相关位的时候有涉及到:
这里就是这个CCR值了,然后这里也是一个16位的数,寄存器也是分了2个,就是获取低8位和高8位,这些与之前的配置定时器和ARR是一样的,但是这里为啥要有括号然后在前面写unsigned char,其实这个是c语言的强制类型转换,这里就是我们本来count是16位的数,然后我们取高8位后,我们存取在寄存器刚好是8位,所以就刚刚好,这里是为了规范代码,其实这里的括号和unsigned char其实可以去掉,我测试过了,这就是这个函数作用,配置比较值,为什么要定义这个函数,因为我们呼吸灯原理之前已经说过了,要调节这个比较值的大小来实现低电平的占比大小实现灯的亮暗了。所以就需要这个函数来调节,其实这个可以不用写成函数,直接在后面我们配置的时候写这2个语句即可,但是这样规范代码可以让我们了解库函数概念,封装。
那接下来就是main.c文件的配置了:
这里可以看到我们声明了2个全局变量,一个是pwm_count,其实这个就是我们所说的比较值,然后就是一个标志flag,这里和之前的逻辑实现呼吸灯一样,是来判断我们的比较值要自加还是自减。然后就是main函数我们初始化pwm和定时器T0,然后我们看下定时器T0中断函数,这里我们可以看到有一个if-else语句,然后就是调用pwm_setcount函数首先我们看下if-else语句,这里判断条件就是我们flag标志,这里我们知道我们pwm_count和flag一开始分别是0和1,我门看下一开始我们就是进入if语句然后就是pwm_setcount自增,然后就是判断pwm_setcount是否大于等于1000,记得我们设置ARR也就是pwm周期就是1000,然后就是如果大于等于1000就没意义了,那就自减把flag标志变为0,然后就是后面进入定时器中断的时候就变成else,这里面的语句我就不过多介绍了和之前的逻辑实现分析一样,就是自减然后到达0的时候就改变标志然后就是自增了,然后就是调用pwm_setcount函数来更新一下比较值。实现对低电平的占比改变,好了LED的相关实验也到这了,下一次就是按键实验了。
8.三种方法按键实现灯的亮灭
这篇教程我想着用板上给的按键来实现按一次就实现灯的亮,再按一次实现灯的暗。
因为我之前是用过STC89C52的开发板,那时候就只是P3.2端口,然后有2个方式,一个就是普通的IO口判断,然后就是外部中断判断,但是这次写这个STC32教程的时候我一开始以为只有这2个方式,但是查看手册发现STC32和市场上其他架构的32位MCU一样,在每个端口都加了中断,那么这次教程我就一次性讲3个方法吧。
首先讲的是普通IO口检测:
这里我们首先要知道按键消抖,这个就是我们后面会用到一个延迟消抖一个东西,当然还有硬件消抖,那接下来看一下按键为什么要消抖,通过原理图:
我们可以看到当我们按下按键的时候,IO口就是接地的,也就是低电平,我们一般会想当我们按下按键的时候,IO口就会及时的出现稳定的电平信号,但是由于我们硬件影响,会产生一定的抖动,电平变化会像下图:
这里我们可以看到IO端口在按下的时候会在高电平和低电平之间有抖动,这个抖动范围可以在高低电平之间,那么我们要知道的是,如果我们判断IO口的高低电平来实现按键是否按下的时候没有把这个抖动过滤掉的话就会得到在短的时间内持续的抖动会让我们的判断是多次按下按钮,这在一些电器或者工业场景是不允许的,所以我们需要把这个抖动滤掉,这个抖动一般在20ms左右,所以我们需要判断电平按下后再延迟20ms然后再判断电平是否是按下的,如果是的话就是按下按键,然后就可以处理事件了。
那么硬件消抖的话就是加一个电容充放电来实现对抖动的补偿让电信号趋于稳定。硬件消抖网上有许多方案,大家可以自行查询,这里就不过多讲述,因为板上没有硬件消抖,我们用到的都是软件消抖。那么接下来我们来看下文件代码了。首先我们要创建一个key.c和key.h文件。然后基础配置我们上面教程已经讲了,所以这里就不过多讲述,接下来来看下key.c文件:
这里我们可以看到2个函数,第一个是对按键IO端口的初始化,第二个是判断按键是否按下,也就是按键检测函数。
首先就是这个按键初始化函数里面有2条语句,这里2条语句应该不用过多讲述了吧,之前再配置IO端口的模式有讲过,可以回到上面再看一下,然后就是接下来的按键检测函数,我们判断按键按下然后返回一个值,或者没按下时候然后返回一个值。然后根据返回值来实现灯的亮灭。但是我这次写的函数可以涉及到模式,这个是之前我用其他MCU的时候在看教程的时候有涉及到这个按键模式,也就是单次按下检测模式和连续按下检测模式,这里我们简单讲解一下这个函数,首先我们看一下声明了一个全局变量:
这里就是我们选择的模式,如果mode为1时按键按下就会返回1,不然如果mode为0时,key不管按下或者没按下都会返回0,那么我们这个:
函数里有一个参数,这个参数就是我们要来设置成按下不松开一直响应还是按下后不松开只产生一次响应。
这里我们一开始就是if语句判断flag标志是否为1,如果为1的话就让mode为1。那么接下来就是判断按键是否为0,这里我们先看下key.h文件吧,这个key是通过宏定义P3.2的:
至于我为什么选择P32这个端口,其实就是这个端口有外部中断,因为普通端口就是普通IO端口检测和IO口中断,这P32端口就还有外部中断。可以多一种方法讲解。
那继续看,我们if语句判断key是否按下,因为我们知道之前原理图如果按下会显示低电平,那么判断条件就是key==0,然后就是延迟20ms,这里就是我前面讲过的软件消抖,这里就是当我们首先检测到按键按下的时候再延迟20ms等按键的电平稳定然后再判断一次,这一次判断呢我加了&&操作还判断了mode是否为1,如果为1的话就是可以实现按键按下,如果不是的话直接跳过了,这里就是我们前面为什么要一开始判断flag是否为1然后给mode赋值为1,如果为1的话就等于延迟后的判断只考虑按键是否按下。后面的已经看到把mode置0,然后返回1,然后就是另一个if语句,判断按键是否松开,如果松开的话就把mode置1然后返回0。这里我们如果把flag置1的话就是mode一直是1,为什么,因为一开始我们的if判断就是成立的,那么mode一开始就是1,那么就只是判断按键是否按下,如果按下的话就一直返回1,那么这就是按下不松开然后持续响应的效果,然后如果flag是0的话就是按下后mode置为0,然后如果不松手的话就是只判断一次而已,要等松开才让mode置1。
好了上面是普通IO口检测,接下来我们来看下IO端口中断。这个要结合手册的相关的寄存器来配置的,首先我们看下手册的简述:
中断我们之前的定时器中断有涉及到,这里用一张图来粗略了解:
就是一般的话就是正常的程序执行,然后我们如果是设置了中断程序的话,当中断请求来时,程序运行就会跳到中断程序那等执行完中断程序后就会回到刚才中断那继续执行下去。
那么接下来我们来看下文件代码,这里首先讲这个中断函数吧,因为这里涉及到一个文件,首先我们用到的是P32这个IO端口的中断,所以查找手册:
相关的寄存器如图,但是这里我只讲我们配置用到的寄存器,那么接下来来看一下我们的P32在中断列表里是第几:
这里可以看到是第40,这里编号就是interrupt后面用到的数字,就像我们之前用到的定时器T0中断在中断列表了是1:
这里顺便讲一下P32是外部中断0端口,这里可以看原理图:
那么这里我们也把外部中断的列表序号也看一下,因为用外部中断的方法也是可以要用到的。
那么接下来我们先看一下IO端口的中断,其实一开始我看了手册相关章节后也特别懵,太多寄存器了,然后我就看了一下相关的例程,发现不是我想的那么复杂,那就和上一个教程一样通过讲解代码来吧,首先我们看下我们的key.c和key.h文件:
这里的文件我们只看到了一个函数声明,因为我们的这个中断的重点在中断函数那里,所以这里我们来简单的讲解一下,首先就是这2条语句:
这个就是配置P3的所有IO口都为准双向口,这个之前有讲解过可以回头去看看,然后就是这2条语句:
这个是涉及到相关的寄存器,查找手册:
这里就是和上面那2条语句差不多是配置产生中断请求的模式,那么这里也是有4种模式,我这次教程都是用了下降沿触发中断的,因为2个寄存器的P32位都是0,那就是第一种模式,下降沿触发,因为我们按下按键就是从高电平跳变到低电平,所以我选择下降沿触发,下面讲解的外部中断也是下降沿触发的,你可以自己选择自己想要的触发条件然后选择相应的模式。
然后就是这条语句:
这里我们也要参考一下手册:
这里我们可以看到这是开启中断使能的寄存器,这个相当于可以开启上面我们设置模式的中断。为什么这里是0x04,因为这里涉及到P32位,而这里是一个寄存器直接配置,不按位配置的,所以这里的P32位刚好就是**** *X**,就是x这位,那么如果这个位置1其余置0就是0x04了。
然后就是最后一条语句:
这条语句非常重要,为什么呢?这个是中断开关,而且还是所有中断的总开关:
所以所有中断要想开启,这条语句一定要写。
然后我们看下main文件:
首先我们在main函数里初始化了按键配置,然后就是下面的中断函数,但是大家可能注意到一开始我们在前面看的中断列表里有涉及到P3的IO端口中断的序号是40,那么这里为什么是13,这里就要看到手册里的介绍了:
然后跳到相关的章节:
这里由于篇幅太长,我只截取用到的方法:
用到的就是手册讲到的借用13号中断向量,这里我们得创建一个文件,名字是isr.asm文件,然后在文件里编写一些汇编代码:
我也是不懂汇编代码的,但大概也是就是把P3端口的中断编号让13号承接应该就是,这里我自己想的,这里的0143H和006BH可以改的:
上面的是中断序号和中断向量,可以看到只有中断13是空的,所以我们可以用中断13来承接其他的中断,这里看到中断13的向量为006BH,刚刚好就是我们上面文件代码里有出现的,那如果在0-31有出现其他空的中断类型的话就可以改成相应的中断向量然后通过相关的中断序号来实现,然后就是P3口的IO端口中断的中断向量刚好就是0143H,那么如果我们要改成其他P1,P2......的端口中断就把其他端口的中断向量和0143H替换即可,当然:
这里的P3要改成相应的P几端口。
好了看回中断函数,这里首先就是定义了一个变量,然后就是这条语句:
这里我们要看一下P3INTF寄存器:
这里可以看到这个寄存器是来看是否产生中断请求,也就是一个标志,所以我们先是用变量来获取,然后把标志位清零,上面手册说得软件清0就是我们得在代码里自己清0,然后就判断中断是不是P32口的中断标志,这和之前的使能寄存器那个一样就是0x04然后就是在这里把LED灯电平取反。
这就是IO口中断的检测,其实这里这个代码会有一定的误差,因为这里没有把按键的抖动滤除,这里我们可以把代码改成这样即可:
然后最后就是外部中断0来实现了,其实这个和上面的IO端口中断相似,首先我们来看下代码文件吧。
这次我创建了2个文件eint.c和eint.h来初始化外部中断:
这里我们分析一下这个初始化函数里的语句,首先就是:
这个参考手册里:
根据第一个图就是设置0或者1的话就是2个模式的采集请求中断,0的话就是电平跳变就请求中断,也就是不管上升沿还是下降沿都会产生中断,然后就是如果置1就是下降沿触发,上升沿就不触发。我这里是设置位1也就是只有下降沿的时候才触发中断,然后就是:
手册解析:
这条语句和之前的IO端口中断使能寄存器一样,这里也是使能一下外部中断端口。
然后就是:
这条语句之前有讲解过,这个是所有中断的总开关,如果要使用中断这个位必须置1。
然后就是main文件:
可以看到我们在main函数里面已经初始化了外部中断配置。然后就是中断函数,上面有讲解到外部中断0的序号就是0然后就是进入后可以我们可以看到我们延迟了20ms,这里也是我们上面一直强调的软件消抖,然后再判断P32的电平,如果是低电平就是按下了,然后就让LED的电平发生转变由高变低,或者由低变高。
代码:
好了这就是我们板上的按键资源的实验。
9.串口通信实验
这次教程是关于串口方面的,这次我就讲最基本的单片机往电脑发送一个数据,还有就是电脑发送数据单片机接收后把接收的数据赋值在P2端口实现控制LED灯。
首先我们先来实现单片机通过串口向电脑发送数据。
接下来就是我们看一下手册:
这里面讲了关于串口的内容,大家可以先过一遍,我等下还是和之前的一样和代码结合一起来讲比较好我觉得。首先我们这次还用到了我们之前定时器的内容,接下来来看一下代码吧,这次我们创建2个文件uart.c和uart.h:
这里可以看到我们只有一个初始化函数,这里是串口和定时器T1的基础配置,那么接下来看一下每条语句的含义,首先我们看下这2条语句:
这个应该会比较熟悉了,这个就是配置IO端口的模式,这里是把P3的IO口都设置成准双向口。
然后就是这条语句:
这里我给了解释,但为什么我们要把低位置0,我们看下手册:
可以看到最后一位是选择定时器的,而定时器就是我们后面要讲的波特率相关的,那么我们这里是设置成0就是选择定时器1来作为波特率的发射器。
然后就是这个语句:
这里我们看下就是设置成0100 0000,那么接下来来看下手册讲解:
这里的SM0/FE位是因为我们再上面配置PCON时已经把SMOD0配置成0了,所以这位就是SM0,与SM1确定串口工作模式,然后我们看下我们把SM0,SM1分别设置成01,也就是我们把串口设置成工作模式1,这里是设置成可变波特率和8位数据的方式,然后剩下的位配置解析:
首先就是SM2,我们因为只是单片机和电脑通信,没有涉及到多机通信,所以该为置0即可,然后就是REN位,这个是当我们接口接收数据的时候用,我们首先介绍的是单片机向电脑发送数据,没有用到接收,所以就这里我们先置0,然后就是TB8和RB8,上面我们设置串口的工作模式1了,这2位是工作模式2和3才用到,所以这里就不过多介绍,我们没有用到,所以置0,然后就是TI和RI这2位,这2位比较重要,因为到后面我们会涉及到数据寄存器SBUF,当我们发送的时候就是给SBUF赋值,然后看TI值是否等于1,如果等于1的话就是发送完成,同样道理,就是当我们电脑往单片机发送数据的时候,如果RI等于1时就说明可以提取SBUF的值,因为我们电脑发送数据就是由SBUF接收了。
然后就是这条语句:
这里应该会比较熟悉吧,这里的TMOD寄存器:
可以看到我们把T1_M1和T1_M0设置成0,那就是把定时器1设置成模式1:
然后就是这2条语句:
这里就是我们设置波特率的,首先我们来讲下波特率就是每秒传送的字节数,单位就是bps,我这2个实验的波特率都是115200,也就是这个速率就是每秒传输115200个位数据。
手册里有介绍波特率怎么计算:
我们现在配置的就是12T的定时器重装载值,由于11.0592MHz计算起来比较麻烦,我一开始是用12MHz计算,然后发现串口传输的数据有偏差,然后想要用11.0592MHz计算的时候发现原来官方的STC-ISP软件里面是有官方给的装载值,所以就懒得去算了,用官方得装载值即可:
这里画线部分就是我们需要注意得,定时器时钟有没有分频要根据自己设置AUXR的T1x12的位是否有配置,我是默认值就是12分频的,然后就可以复制下面的TL1和TH1的值。要不我们还是来推导一下:
12*4*115200 = 5529600
那么11059200/5529600 = 2
那么65536-2 = 65534
根据下面的二进制可以看出高8位是1111 1111 =0xFF,也就是TH1的值
然后低8位是1111 1110 =0xFE,也就是TL1的值,刚好就是我们在STC-ISP软件里得到的。
所以这里就配置好了接下来就是:
因为我们的定时器是用来作为波特率的发射器的,所以溢出不能产生中断,所以要不允许溢出中断:
然后就是:
这条语句是开启定时器:
然后就是:
这个看一下手册:
这里可以看到这是串口1中断允许位,我们用到的就是串口1,因为我是用到了P30,P31端口,根据:
这里可以看到我是用到了这个串口1。
然后就是:
开启总的中断开关,那,接下来来看下main文件:
这里可以看到我们在mian函数里初始化串口了,然后就是:
这个我们之前说过TI是发送数据时的标志位,当我们知道TI等于0时要么就是还没有发送数据或者数据还在发送中,因为我们在其他地方没有使用串口通信,所以这里一开始就是未发送数据,然后我们就往SBUF数据寄存器写入一个字符A,那单片机就会通过串口发送给电脑了,当发送完成我们上面也有看到TI会被硬件置1然后进入中断:
这里的序号4是根据:
这里可以知道,然后就是1进入中断后我们先判断TI是否是为1,因为我们关闭了接收,所以这里就是TI被硬件置1进入中断了,然后我们TI这个位硬件置1要软件置0,不软件置0的话后面就无法再进入中断了。所以就有了这条语句:
好了这里我们就可以知道我们一直往电脑发送一个字符A,由于要用到串口,我自己用了一个TTL转串口来测试了,用STC-ISP里带的串口助手来测试下:
可以看到测试成功,这里注意的是要勾选文本模式才能显示字符A,如果勾选HEX的话就会显示16进制数。
然后就是串口的电脑发送数据然后单片机接收后赋值给P2口来驱动LED灯了,那接下来来看下代码:
其实这里我们只改了这条语句:
也就是之前这个寄存器的RNE位:
允许串口接收数据了,因为我们需要接收电脑发来的数据。
其余的和上面的串口发送数据给电脑一样,这里就不过多讲述了。
然后就是main文件:
首先我们定义了一个全局变量dat,这个就是来接收数据的,然后就是在while循环里把接收到的数据赋值给P2,使LED灯亮灭。然后就是这个中断函数,这时的中断函数相比上面的中断函数这里触发进入中断的就不只是TI了,还有RI,这里我们先看第2个if语句,就是判断如果RI等于1,我们之前知道当数据SBUF接收到电脑发送过来的数据时RI就会被硬件置1并且进入中断,当然这个也是要软件置0,那么如果RI是等于1的话就是电脑发送数据来了,然后就用dat获取一下数据,然后就是把RI置0,这样下次再发送就会再次进入中断,然后这里有SBUF = dat,这里是我们单片机发送刚才电脑发送过来的数据,这里可以测试,然后就是第1个if语句了,由于我们接收数据后就发送相同数据回去,所以TI就会被硬件置1然后进入中断,我们就要把TI再软件置0。
好了来看下测试,我们发送0xFE也就是1111 1110,也就是P2端口的最低位的LED会点亮也就是P20端口的LED灯会点亮,然后我们再测试一下0x7F也就是0111 1111,最高位的LED灯就会被点亮也就是P27端口的LED灯会点亮。
0xFE测试:
这里都要设置成HEX模式,可以看到数据可以返回,然后看下开发板:
0x7F测试:
开发板测试图:
代码: