版权声明:本文为博主原创文章,转载请附上原文出处链接。
本次讲解STC8A8K64S4A12系列PWM脉冲宽度调制的原理;8个PWM外设相关寄存器配置及程序设计。
PWM (全称是Pulse Width Modulation)脉冲宽度调制是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术,广泛应用于测量、通信到功率控制与变换的许多领域中。
下面以一个简单有趣的例子和大家分析PWM的作用。下图是使用9V电池来给一个电灯供电,这是个模拟电路,电路使用开关来控制电灯工作状态。
■ 开关状态与电灯工作状态之间的关系:
根据常识我们知道开关按下,则电灯两端有压降,电灯会被点亮;如果开关松开,则电灯两端没有压降,形成不了回路,电灯就是灭的。那请分析下下面几种情况:
分析:
☆注:呼吸灯的原理,如果我们控制PWM输出频率达到83HZ以上,那么PWM引脚连接的指示灯是看不到闪烁的,是一种常亮的状态,但亮度又不像直接给个低电平那么亮,如果频率不变,不断控制占空比,那么被点亮的指示灯的亮度就是不断变化的,像“呼吸”一样。
■ 关于频率和占空比的理解:
上面的实例中已经在使用频率和占空比的知识点,频率是相对于信号的周期而言,通过波形知道了周期就可以算出频率,频率是周期的倒数。而占空比是指高电平的时间占整个周期的比例。
■ 使用PWM信号控制模拟电路的优势:
根据上面的实例,我们知道可以通过PWM信号的频率和占空比控制电灯的状态和亮度。那么使用PWM信号控制模拟电路的优势有哪些呢?下面分析:
■ PWM脉冲宽度调制还有哪些应用呢?
☆注:占空比的设置:理论上说,通过控制占空比(为0%或者100%)可以实现输出纯粹的低电平或者高电平,但实际使用要结合所选单片机特点进行设计。
STC8A8K64S4A12系列单片机集成了一组增强型PWM波形发生器,该PWM波形发生器内部有一个15位的PWM计数器供8路相互独立的PWM使用,即PWM0、PWM1、PWM2、PWM3、PWM4、PWM5、PWM6和PWM7。另外,用户可设置每路PWM的初始电平,也可以通过对每路PWM的两个控制波形翻转的计数器的配置实现每路PWM高低电平宽度的调节。
由于8路PWM是各自独立的,且每路PWM的初始状态可以自行设定,所以用户可以将其中的任意两路配合起来使用,从而实现互补对称输出及死区控制等特殊用途。
STC8A8K64S4A12系列单片机PWM波形发生器还设计了对外部异常事件进行监控的功能,可用于紧急关闭PWM输出,避免出现严重后果。
STC8A8K64S4A12系列单片机每一路PWM都有3个IO引脚供选择使用,如下表。
☆注:独立GPIO表示开发板没有其他的电路使用这个GPIO,非独立GPIO说明开发板有其他电路用到了该GPIO。针对非独立GPIO使用时需特别注意。
STC8A8K64S4A12系列单片机PWM外设的理解主要是对其PWM波形发生器的内部结构框图的解析,下面给出该结构图。
☆注:从该图不难看出,虽然8路PWM相互独立,但因为共用同一个计数器,所以8路PWM若多个同时使用则其频率都是一样的。
针对STC8A8K64S4A12系列单片机有8路PWM外设,软件的配置过程如下:
☆注:实验例程即是按照上述配置步骤操作寄存器的相关位,后有详述。
STC8A8K64S4A12系列单片机操作PWM时会用到57个寄存器,如下表所示:
☆注:8路相互独立的PWM都有计数器T1、T2、控制寄存器和电平保持控制寄存器,只需把一路PWM对应的寄存器搞清楚就可以了。
PWM配置寄存器PWMCFG的B6位为PWM计数与硬件自动触发ADC转换的关联控制位,该ETADC位应用于比较特殊的场合,一般配置为0即可。PWMCFG寄存器的B7位为PWM计数器归零中断标志位(硬件实现自动置位,不受ECBI位影响)。
PWM控制寄存器PWMCR的B6位为PWM计数器归零中断使能位,B7位为PWM波形发生器使能位,在对相应PWM口配置完成后需使能该位。
PWM中断标志寄存器PWMIF的C0IF~C7IF位的操作: PWM相应通道的中断标志位。C0IF~C7IF位在当PWM发生翻转时,硬件自动置1,需软件清零。
PWM外部异常控制寄存器PWMFDCR的ENFD位为PWM外部异常检测功能控制位,需要用到PWM外部异常检测功能时置1。PWMFDCR寄存器的EFDI位为PWM异常检测中断使能位。该寄存器其他位请参考下图。
☆注:PWMFDCR寄存器是对PWM外部异常控制有需求时使用,一般应用可对相应位设置为0即可。
PWM时钟选择寄存器PWMCKS的SELT2位为PWM时钟源选择位,该时钟源选择位为0时,即选择PWM时钟源为系统时钟提供,那么设置PS[3:0]才有意义。PWMCKS寄存器的PS[3:0]位用于对系统时钟分频后提供PWM时钟源。
☆注:PWMCKS是在扩展RAM区,这就要求在访问这些寄存器时需将P_SW2寄存器的EAXSFR位置1。
PWM触发ADC计数器寄存器分触发ADC计数值高8位寄存器TADCPH和触发ADC计数值低8位寄存器TADCPL,其中触发ADC计数值高8位寄存器TADCPH的B7位为保留位,这样{TADCPH, TADCPL}可组成一个15位的寄存器。
☆注:在ETADC=1且ADC_POWER=1时,15位寄存器{TADCPH, TADCPL}值与PWM内部计数值相等时,硬件会自动触发AD转换。
PWM2有2个15位的翻转计数器PWM2T1和PWM2T2,因为是8位单片机,寄存器都是8位的,所以PWM2实际拥有4个用于翻转的寄存器,分别是PWM2T1H、PWM2T1L、PWM2T2H和PWM2T2L。
PWMn控制寄存器PWMnCR的ENCnO位为PWMn的输出使能位,CnINI位为PWM输出端口的初始电平设置,Cn_S[1:0]位为输出引脚选择(每一路PWM都有3个IO引脚供选择,所以需要寄存器2位实现)。PWMnCR寄存器的ECnI位的操作,置1可保证CnIF被硬件置1时,程序进入相应中断入口。关于ECnT2SI和ECnT1SI位的操作,置1使能T1或T2翻转时中断,切记此处T1或T2不是单片机本身的定时器T1或T2,而是PWMn里面的翻转计数器。
PWMn电平保持控制寄存器可实现PWM通道输出PWM信号时,通过对该寄存器相应位操作强制将输出信号置为高电平或低电平。这在有些场合很实用。
本例需要用到的c文件如下表所示,工程需要添加下表中的c文件。
序号 | 文件名 | 后缀 | 功能描述 |
---|---|---|---|
1 | pwm | .c | 外部PWM有关的用户自定义函数。 |
2 | delay | .c | 包含用户自定义延时函数。 |
■ 需要引用的头文件
#include "delay.h"
#include "pwm.h"
■ 需要包含的头文件路径
本例需要包含的头文件路径如下表:
序号 | 路径 | 描述 |
---|---|---|
1 | …\ Source | pwm.h和delay.h头文件在该路径,所以要包含。 |
2 | …\User | STC8.h头文件在该路径,所以要包含。 |
MDK中点击魔术棒,打开工程配置窗口,按照下图所示添加头文件包含路径。
首先,在pwm.c文件中编写PWM6的初始化函数PWM6_Configuration,代码如下。
程序清单:PWM6初始化函数
/*************************************************************************
功能描述:对PWM6进行初始化
入口参数:无
返回值:无
*************************************************************************/
void PWM6_Configuration(void)
{
PWMCFG &= 0xBF; //将ETADC位置0,即PWM计数不关联触发ADC转换
PWMCFG &= 0x7F; //将CBIF位置0,PWM计数器归零中断标志位,需软件清零
P_SW2 |= 0x80; //将EAXSFR位置1,以访问PWM在扩展RAM区的特殊功能寄存器
//对PWM6的初始化部分
PWM6CR &= 0xE7; //将C6_S[1:0]位置00,选择PWM6的输出引脚是P2.6
PWM6CR |= 0x80; //将ENC6O位置1,PWM6的端口为PWM输出口,受PWM波形发生器控制
PWM6CR &= 0xBF; //将C6INI位置0,设置PWM6输出端口的初始电平为低电平
PWMIF &= 0xBF; //将C6IF位置0,PWM6中断标志位,需软件清零
PWM6CR |= 0x04; //将EC6I位置1,使能PWM6中断
PWM6CR &= 0xFD; //将EC6T2SI位置0,关闭T2翻转时中断
PWM6CR &= 0xFE; //将EC6T1SI位置0,关闭T1翻转时中断
//对PWM6翻转计数器赋初值
PWM6T1 =1; //赋值PWM6第一次翻转计数器值
PWM6T2 = 0x00FA; //赋值PWM6第二次翻转计数器值
//对PWM波形发生器时钟源进行初始化
PWMCKS |= 0x10; //将SELT2位置1,PWM时钟源为定时器2溢出脉冲
PWMC = 0x00FA; //PWM计数器赋值(同时对PWMCH和PWMCL进行了赋值)
AUXR |= 0x04; //定时器2时钟为Fosc,即1T
T2L = 0xE0; //设定定时初值
T2H = 0xFE; //设定定时初值
AUXR |= 0x10; //启动定时器2
P_SW2 &= 0x7F; //将EAXSFR位置0,恢复访问XRAM
//PWM外部异常控制寄存器的操作
PWMFDCR &= 0xDF; //将ENFD位置0,关闭PWM外部异常检测功能
PWMFDCR &= 0xF7; //将EFDI位置0,关闭PWM异常检测中断
PWMFDCR &= 0xFB; //将FDCMP位置0,比较器与PWM无关
PWMFDCR &= 0xFD; //将FDIO位置0,P3.5的状态与PWM无关
PWMFDCR &= 0xFE; //将FDIF位置0,PWM异常检测中断标志位,需软件清零
IP2 |= 0x40; //将PPWM位置1,使能PWM中断为最高优先级中断
//使能PWM波形发生器
PWMCR |= 0x80; //将ENPWM位置1,使能PWM波形发生器,PWM计数器开始计数
PWMCR &= 0xBF; //将ECBI位置0,禁止PWM计数器归零中断
}
然后,编写PWM中断服务函数,一旦进入中断则软件清除相应中断标志位,代码如下。
程序清单:中断服务函数
/***************************************************************************
* 描 述 : PWM中断服务函数
* 入 参 : 无
* 返回值 : 无
**************************************************************************/
void PWM(void) interrupt 22 using 1
{
PWMCFG &= 0x7F; //将CBIF位置0,PWM计数器归零中断标志位,需软件清零
PWMIF &= 0xBF; //将C6IF位置0,PWM6中断标志位,需软件清零
}
最后,在主函数中调用PWM6初始化函数,开启总中断,在主循环中不断改变PWM6翻转计数器T1和T2值实现对PWM6输出信号占空比的调节。
代码清单:主函数
int main()
{
uint8 flag=1;
uint16 ledpwmval=0;
PWM6_Configuration(); //初始化PWM6口
EA = 1; //允许总中断
while(1)
{
delay_ms(30); //延迟每次指示灯亮度的时间,更方便观察实验现象
if(flag) //如果标识符为1则递增变量ledpwmval
ledpwmval++;
else //如果标识符为0则递减变量ledpwmval
ledpwmval--;
if(ledpwmval>248) //如果变量ledpwmval递增到一定值则控制标识符为0,以实现ledpwmval递减
flag=0;
if(ledpwmval==1) //如果变量ledpwmval递减到一定值则控制标识符为1,以实现ledpwmval递增
flag=1;
P_SW2 |= 0x80; //将EAXSFR位置1,以访问PWM在扩展RAM区的特殊功能寄存器
PWM6T1 =(uint16)ledpwmval; //赋值PWM6第一次翻转计数器值(不断变化值)
PWM6T2 = 0x00FA; //赋值PWM6第二次翻转计数器值(定值)
P_SW2 &= 0x7F; //将EAXSFR位置0,恢复访问XRAM
}
}
本实验需要使用短路帽将J29端子的P26与D1短接,如下图所示。
首先,在pwm.c文件中编写PWM7的初始化函数PWM7_Configuration,代码如下。
程序清单:PWM7初始化函数
/*************************************************************************
功能描述:对PWM7进行初始化
入口参数:无
返回值:无
**************************************************************************/
void PWM7_Configuration(void)
{
PWMCFG &= 0xBF; //将ETADC位置0,即PWM计数不关联触发ADC转换
PWMCFG &= 0x7F; //将CBIF位置0,PWM计数器归零中断标志位,需软件清零
P_SW2 |= 0x80; //将EAXSFR位置1,以访问PWM在扩展RAM区的特殊功能寄存器
//对PWM7的初始化部分
PWM7CR &= 0xE7; //将C7_S[1:0]位置00,选择PWM7的输出引脚是P2.7
PWM7CR |= 0x80; //将ENC7O位置1,PWM7的端口为PWM输出口,受PWM波形发生器控制
PWM7CR &= 0xBF; //将C7INI位置0,设置PWM7输出端口的初始电平为低电平
PWMIF &= 0x7F; //将C7IF位置0,PWM7中断标志位,需软件清零
PWM7CR |= 0x04; //将EC7I位置1,使能PWM7中断
PWM7CR &= 0xFD; //将EC7T2SI位置0,关闭T2翻转时中断
PWM7CR &= 0xFE; //将EC7T1SI位置0,关闭T1翻转时中断
//对PWM7翻转计数器赋初值
PWM7T1 =1; //赋值PWM7第一次翻转计数器值
PWM7T2 = 0x00FA; //赋值PWM7第二次翻转计数器值
//对PWM波形发生器时钟源进行初始化
PWMCKS |= 0x10; //将SELT2位置1,PWM时钟源为定时器2溢出脉冲
PWMC = 0x00FA; //PWM计数器赋值(同时对PWMCH和PWMCL进行了赋值)
AUXR |= 0x04; //定时器2时钟为Fosc,即1T
T2L = 0xE0; //设定定时初值
T2H = 0xFE; //设定定时初值
AUXR |= 0x10; //启动定时器2
P_SW2 &= 0x7F; //将EAXSFR位置0,恢复访问XRAM
//PWM外部异常控制寄存器的操作
PWMFDCR &= 0xDF; //将ENFD位置0,关闭PWM外部异常检测功能
PWMFDCR &= 0xF7; //将EFDI位置0,关闭PWM异常检测中断
PWMFDCR &= 0xFB; //将FDCMP位置0,比较器与PWM无关
PWMFDCR &= 0xFD; //将FDIO位置0,P3.5的状态与PWM无关
PWMFDCR &= 0xFE; //将FDIF位置0,PWM异常检测中断标志位,需软件清零
IP2 |= 0x40; //将PPWM位置1,使能PWM中断为最高优先级中断
//使能PWM波形发生器
PWMCR |= 0x80; //将ENPWM位置1,使能PWM波形发生器,PWM计数器开始计数
PWMCR &= 0xBF; //将ECBI位置0,禁止PWM计数器归零中断
}
然后,编写PWM中断服务函数,一旦进入中断则软件清除相应中断标志位,代码如下。
程序清单:中断服务函数
/***************************************************************************
* 描 述 : PWM中断服务函数
* 入 参 : 无
* 返回值 : 无
**************************************************************************/
void PWM(void) interrupt 22 using 1
{
PWMCFG &= 0x7F; //将CBIF位置0,PWM计数器归零中断标志位,需软件清零
PWMIF &= 0x7F; //将C7IF位置0,PWM7中断标志位,需软件清零
}
最后,在主函数中调用PWM7初始化函数,开启总中断,在主循环中不断改变PWM7翻转计数器T1和T2值实现对PWM7输出信号占空比的调节。
代码清单:主函数
int main()
{
uint8 flag=1;
uint16 ledpwmval=0;
PWM7_Configuration(); //初始化PWM7口
EA = 1; //允许总中断
while(1)
{
delay_ms(30); //延迟每次指示灯亮度的时间,更方便观察实验现象
if(flag) //如果标识符为1则递增变量ledpwmval
ledpwmval++;
else //如果标识符为0则递减变量ledpwmval
ledpwmval--;
if(ledpwmval>248) //如果变量ledpwmval递增到一定值则控制标识符为0,以实现ledpwmval递减
flag=0;
if(ledpwmval==1) //如果变量ledpwmval递减到一定值则控制标识符为1,以实现ledpwmval递增
flag=1;
P_SW2 |= 0x80; //将EAXSFR位置1,以访问PWM在扩展RAM区的特殊功能寄存器
PWM7T1 =(uint16)ledpwmval; //赋值PWM7第一次翻转计数器值(不断变化值)
PWM7T2 = 0x00FA; //赋值PWM7第二次翻转计数器值(定值)
P_SW2 &= 0x7F; //将EAXSFR位置0,恢复访问XRAM
}
}
本实验需要使用短路帽将J29端子的P27与D2短接,如下图所示。
首先,在pwm.c文件中编写PWM6和PWM7的初始化函数PWM6PWM7_Configuration,代码如下。
程序清单:PWM6和PWM7初始化函数
/***************************************************************************
功能描述:对PWM6和PWM7进行初始化
入口参数:无
返回值:无
**************************************************************************/
void PWM6PWM7_Configuration(void)
{
PWMCFG &= 0xBF; //将ETADC位置0,即PWM计数不关联触发ADC转换
PWMCFG &= 0x7F; //将CBIF位置0,PWM计数器归零中断标志位,需软件清零
P_SW2 |= 0x80; //将EAXSFR位置1,以访问PWM在扩展RAM区的特殊功能寄存器
//对PWM6的初始化部分
PWM6CR &= 0xE7; //将C6_S[1:0]位置00,选择PWM6的输出引脚是P2.6
PWM6CR |= 0x80; //将ENC6O位置1,PWM6的端口为PWM输出口,受PWM波形发生器控制
PWM6CR &= 0xBF; //将C6INI位置0,设置PWM6输出端口的初始电平为低电平
PWMIF &= 0xBF; //将C6IF位置0,PWM6中断标志位,需软件清零
PWM6CR |= 0x04; //将EC6I位置1,使能PWM6中断
PWM6CR &= 0xFD; //将EC6T2SI位置0,关闭T2翻转时中断
PWM6CR &= 0xFE; //将EC6T1SI位置0,关闭T1翻转时中断
//对PWM7的初始化部分
PWM7CR &= 0xE7; //将C7_S[1:0]位置00,选择PWM7的输出引脚是P2.7
PWM7CR |= 0x80; //将ENC7O位置1,PWM7的端口为PWM输出口,受PWM波形发生器控制
PWM7CR &= 0xBF; //将C7INI位置0,设置PWM7输出端口的初始电平为低电平
PWMIF &= 0x7F; //将C7IF位置0,PWM7中断标志位,需软件清零
PWM7CR |= 0x04; //将EC7I位置1,使能PWM7中断
PWM7CR &= 0xFD; //将EC7T2SI位置0,关闭T2翻转时中断
PWM7CR &= 0xFE; //将EC7T1SI位置0,关闭T1翻转时中断
//对PWM6和PWM7翻转计数器赋初值
PWM6T1 =1; //赋值PWM6第一次翻转计数器值
PWM6T2 = 0x00FA; //赋值PWM6第二次翻转计数器值
PWM7T1 =1; //赋值PWM7第一次翻转计数器值
PWM7T2 = 0x00FA; //赋值PWM7第二次翻转计数器值
//对PWM波形发生器时钟源进行初始化
PWMCKS |= 0x10; //将SELT2位置1,PWM时钟源为定时器2溢出脉冲
PWMC = 0x00FA; //PWM计数器赋值(同时对PWMCH和PWMCL进行了赋值)
AUXR |= 0x04; //定时器2时钟为Fosc,即1T
T2L = 0xE0; //设定定时初值
T2H = 0xFE; //设定定时初值
AUXR |= 0x10; //启动定时器2
P_SW2 &= 0x7F; //将EAXSFR位置0,恢复访问XRAM
//PWM外部异常控制寄存器的操作
PWMFDCR &= 0xDF; //将ENFD位置0,关闭PWM外部异常检测功能
PWMFDCR &= 0xF7; //将EFDI位置0,关闭PWM异常检测中断
PWMFDCR &= 0xFB; //将FDCMP位置0,比较器与PWM无关
PWMFDCR &= 0xFD; //将FDIO位置0,P3.5的状态与PWM无关
PWMFDCR &= 0xFE; //将FDIF位置0,PWM异常检测中断标志位,需软件清零
IP2 |= 0x40; //将PPWM位置1,使能PWM中断为最高优先级中断
//使能PWM波形发生器
PWMCR |= 0x80; //将ENPWM位置1,使能PWM波形发生器,PWM计数器开始计数
PWMCR &= 0xBF; //将ECBI位置0,禁止PWM计数器归零中断
}
然后,编写PWM中断服务函数,一旦进入中断则软件清除相应中断标志位,代码如下。
程序清单:中断服务函数
/***************************************************************************
* 描 述 : PWM中断服务函数
* 入 参 : 无
* 返回值 : 无
**************************************************************************/
void PWM(void) interrupt 22 using 1
{
PWMCFG &= 0x7F; //将CBIF位置0,PWM计数器归零中断标志位,需软件清零
PWMIF &= 0xBF; //将C6IF位置0,PWM6中断标志位,需软件清零
PWMIF &= 0x7F; //将C7IF位置0,PWM7中断标志位,需软件清零
}
最后,在主函数中调用PWM6和PWM7初始化函数,开启总中断,在主循环中不断改变PWM6和PWM7翻转计数器T1和T2值实现对PWM6和PWM7输出信号占空比的调节。
代码清单:主函数
int main()
{
uint8 flag=1;
uint16 ledpwmval=0;
PWM6PWM7_Configuration(); //初始化PWM6和PWM7口
EA = 1; //允许总中断
while(1)
{
delay_ms(30); //延迟每次指示灯亮度的时间,更方便观察实验现象
if(flag) //如果标识符为1则递增变量ledpwmval
ledpwmval++;
else //如果标识符为0则递减变量ledpwmval
ledpwmval--;
if(ledpwmval>248) //如果变量ledpwmval递增到一定值则控制标识符为0,以实现ledpwmval递减
flag=0;
if(ledpwmval==1) //如果变量ledpwmval递减到一定值则控制标识符为1,以实现ledpwmval递增
flag=1;
P_SW2 |= 0x80; //将EAXSFR位置1,以访问PWM在扩展RAM区的特殊功能寄存器
PWM6T1 =(uint16)ledpwmval; //赋值PWM6第一次翻转计数器值(不断变化值)
PWM6T2 = 0x00FA; //赋值PWM6第二次翻转计数器值(定值)
PWM7T1 =(uint16)ledpwmval; //赋值PWM7第一次翻转计数器值(不断变化值)
PWM7T2 = 0x00FA; //赋值PWM7第二次翻转计数器值(定值)
P_SW2 &= 0x7F; //将EAXSFR位置0,恢复访问XRAM
}
}
本实验需要使用短路帽将J29端子的P26与D1短接、P27与D2短接,如下图所示。