本帖最后由 HonestQiao 于 2022-12-17 19:29 编辑
51单片机迷人的一点,就是能够通过寄存器直接进行系统功能控制,以及外设的控制。新定义MCU作为51系列单片机中的佼佼者,当然少不了这一点。
在官方提供的演示代码中,有关于PWM的演示,具体代码如下:
通过众多寄存器的联合设置,将PWM功能发挥出来。
首先看一下手册,了解一下系统提供的PWM功能:
【此处应配图】
系统总共提供了两类PWM功能,PWM0功能较为丰富,所以下面以PWM0为例说明。
PWM0一共提供了8路PWM,可以独立运行,也可以两两互补运行。
要使用PWM功能,我们首先需要确定一些前置数据:
- 时钟频率:PWM运行的频率
- 工作模式:如果为独立模式,则每一个PWM都单独设置;如果为互补模式,则两两波形相反。
- PWM类型:边沿对齐或者中心对齐,简单来说,就是边沿从头开始计数,达到周期则直接从头开始,而中心对齐则是达到最高点后再递减直到0;后面使用边沿对齐模式
- 是否反向:如果反向,则占空比表示低电平时间
- PWM周期:在指定的时钟频率下,一次PWM完成的周期
- 占空比:高电平时间占比
所谓的PWM类型,看下面的图,能够加深理解:边沿对齐:
中心对齐:
系统的运行频率为32MHz,所以PWM时钟频率,最高为32MHz,而其配置,则使用了两个bit位来控制分频,所以可用分频为2、4、8
结合手册和源码,可以得知PWMCON0中特定的位控制分频,具体如下:
【此处应配图】
因为是PWMCON0的bit4、bit5,所以对应的操作值如下:
- 1分频:0x00 也就是不分频
- 2分频:0x10 bit4为1,对应二进制 0b00 01 00 00
- 4分频:0x20 bit5位1,对应二进制 0b00 10 00 00
- 8分频:0x30 bit4、bit5均为1,对应二进制 0b00 11 00 00
因为是初次初始化PWM0,所以演示代码中,直接设置PWMCON0为0,也就是不分频了。
知道了系统频率,就可以求得周期值。
如果我们需要PWM的频率为1KHz,那么:T=1000/(1/32M)=0x7d00 其他以此类推。
演示代码使用的为0x0c80,为 0x7d00 / 0x0a,也就是10KHz了。
得到了周期值,就可以存放到下面的寄存器了:
【此处应配图】
注意是高位和地位分开存放的。
然后,是否反向,通过下面的寄存器设置:
【此处应配图】
下一步就是设置各路PWM是否输出了,使用下面的寄存器:
【此处应配图】
需要提醒的一点是,上述说明了位编号的,都是通过位操作来进行的,也就是实际使用时,要看其二进制编码状态。
样式代码使用了0x03,对应二进制0b11,表示启用PWM00、PWM01
再然后就是设置占空比了,占空比不能大于周期值。在运行过程中,可以进行改变,从而达到输出变化的结果。
演示代码中,使用如下的代码定义周期值的存储地址:
unsigned int xdata PWMREG[14] _at_ 0x2034; //PWM占空比调节寄存器
为什么是0x2034,这通过手册可以获取:
【此处应配图】
占空比值使用两个8位寄存器来保存,所以上述地址的对应关系:
- 0x2034、0x2035:对应PWM20,PWMREG[0]
- 0x2036、0x2037:对应PWM21,PWMREG[1]
- 0x2038、0x2039:对应PWM30,PWMREG[2]
- 0x203a、0x203b:对应PWM31,PWMREG[3]
- 0x203c、0x203d:对应PWM40,PWMREG[4]
- 0x203e、0x203f:对应PWM41,PWMREG[5]
- 0x2040、0x2041:对应PWM00,PWMREG[6]
- 0x2042、0x2043:对应PWM01,PWMREG[7]
- 0x2044、0x2045:对应PWM02,PWMREG[8]
- 0x2046、0x2046:对应PWM03,PWMREG[9]
- 0x2048、0x2047:对应PWM04,PWMREG[10]
- 0x204a、0x204b:对应PWM05,PWMREG[11]
- 0x204c、0x204d:对应PWM06,PWMREG[12]
- 0x204e、0x204f:对应PWM07,PWMREG[13]
一个有14个,所以使用了一个 unsigned int xdata PWMREG[14] 来定义,方便操作。
PWM00和PWM01则对应使用PWMREG[6]、PWMREG[7],其取值均为0x640,实则位周期值0x0c80的一半,也就是说占空比位50%
通过上面一通的寄存器设置,PWM0做好准备,就等开工了。
现在,要使能PWM0,需要通过下面的寄存器使能:
【此处应配图】
启用时,对应的操作值为0x80。
在main.c中启用PWM_Test(),然后将代码编译烧录后,通过示波器或者逻辑分析仪可以分析输出情况。示波器更好,逻辑分析仪只能简单辅助。
要进行测量,需要先连接开发板的PWM输出引脚,通过原理图可以得到:
【此处应配图】
我手头没有示波器,所以用逻辑分析仪简单分析了一下,具体结果如下:
【此处应配图】
输出的信号,确实如设置所期望的:PWM频率为10KHz,占空比位0%,而PWM00和PWM01刚好是相反的,因为设置了PWM01输出反向。
然后,就可以做一些简单的处理,点亮LED,并使用呼吸灯的模式。
首先进行一些预先的计算:
- 1. PWM运行频率使用1KHz,则其周期值对应之前计算的0x7d00
其他需要计算的就没有了。
然后,我们需要动态的改变占空比,让他从最小值,变化到最大值。这可以通过PWM0中断回调来进行处理,也就是每次达到计数器最大值,自动中断并进行一次回调:
【此处应配图】
演示代码中,使用如下的寄存器进行控制:
【此处应配图】
演示代码设置的为0x02,对应二进制位0b10,刚好通过上述寄存器控制。
在演示代码的最后,有一个PWM0_Interrup(),这个就是终端回调的函数,我们在其中编写自己的代码即可。
具体代码如下:
unsigned int step = 0;
void PWM0_Interrup() interrupt 8
{
if(PWMCON0 & 0x40)
{
PWMCON0 &= ~0x40; //清除中断标志位
P04 = ~P04;
if(step==0) {
if(PWMREG[6]<=0x0c80-0x0a) {
PWMREG[6] += 0x0a;
} else {
step = 1;
}
} else {
if(PWMREG[6]>=0x0a) {
PWMREG[6] -= 0x0a;
} else {
step = 0;
}
}
}
}
因为PWM00的占空比使用PWMREG[6]来控制,所以在每次回调中,修改其值,那么下一个周期中就会生效了。
上述代码中,用status表示要增大还是减小,循环往复。
将P50引脚连接上 LED:
然后编译烧录代码进行测试,效果如下:
写了半天,突然提示文章内容过长,不得不删除大量的图片。
发烧中,人有点迷糊,看东西有点重影,所以等好了再补足图片,另外理解内容和计算也可能不准确,敬请见谅。
|