学习目标

  1. 了解PWM基础概念和工作原理
  2. 学习如何在STC8H上配置PWMA
  3. 掌握PWMA的各个配置
  4. 学习如何使用PWMA控制LED亮度
  5. 掌握调试PWM的方法

    学习内容

    PWM基础概念

    PWM全称是脉宽调制(Pulse Width Modulation),是一种通过改变信号的脉冲宽度来控制电路输出的技术。PWM技术在工业自动化、电机控制、LED调光等领域广泛应用。
    PWM是一种将数字信号转换为模拟信号的技术,它通过改变信号的占空比来控制输出的电平。在STC8H中,PWM输出的频率和占空比可以由程序控制,因此可以用来控制各种电机、灯光和其他设备的亮度、速度等参数。

    STC8H芯片

    STC8H 系列的单片机内部集成了8 通道 16 位高级PWM 定时器,分成两周期可不同的 PWM,分别命名为 PWMA 和PWMB ,可分别单独设置。
    第一组 PWMA 可配置成4 组互补/对称/死区控制的PWM 或捕捉外部信号。
    第二组 PWMB 可配置成4 路PWM 输出或捕捉外部信号。
    两组 PWM 的时钟频率可分别独立设置。
    微信截图_20221207094909.png
    PWM与引脚对应关系如下图:
PWM PWM通道 对应引脚
PWMxP PWMxN
PWMA PWM1P & PWM1N P1.0 P1.1
P2.0 P2.1
PWM2P & PWM2N P5.4 P1.3
P2.2 P2.3
PWM3P & PWM3N P1.4 P1.5
P2.4 P2.5
PWM4P & PWM4N P1.6 P1.7
P2.6 P2.7
P3.4 P3.3
PWMB PWM5 P0.0
P1.7
P2.0
PWM6 P0.1
P2.1
P5.4
PWM7 P0.2
P2.2
P3.3
PWM8 P0.3
P2.3
P3.4

PWMA应用

控制引脚P2.7实现LED灯1的呼吸效果。

  1. 拷贝所需库文件(其他必备库请自行准备)
    1. STC8H_PWM.c``STC8H_PWM.h
    2. NVIC.c``NVIC.h
    3. Switch.h
  2. 导入头文件,初始化宏及全局变量 ```c

    include “Config.h”

    include “GPIO.h”

    include “Delay.h”

    include “NVIC.h”

    include “Switch.h”

    include “STC8H_PWM.h”

define LED_SW P45

define LED1 P27

define LED2 P26

define LED3 P15

define FREQ 1000

define PERIOD (MAIN_Fosc / FREQ) // 周期

PWMx_Duty dutyA;

  1. 3. 配置GPIO
  2. ```c
  3. void GPIO_config(void) {
  4. GPIO_InitTypeDef GPIO_InitStructure; //结构定义
  5. // LED_SW
  6. GPIO_InitStructure.Pin = GPIO_Pin_5; //指定要初始化的IO,
  7. GPIO_InitStructure.Mode = GPIO_OUT_PP; //指定IO的输入或输出方式,GPIO_PullUp,GPIO_HighZ,GPIO_OUT_OD,GPIO_OUT_PP
  8. GPIO_Inilize(GPIO_P4, &GPIO_InitStructure);//初始化
  9. // P2
  10. GPIO_InitStructure.Pin = GPIO_Pin_6 | GPIO_Pin_7; //指定要初始化的IO,
  11. GPIO_InitStructure.Mode = GPIO_PullUp; //指定IO的输入或输出方式,GPIO_PullUp,GPIO_HighZ,GPIO_OUT_OD,GPIO_OUT_PP
  12. GPIO_Inilize(GPIO_P2, &GPIO_InitStructure);//初始化
  13. }
  1. 配置PWM

    1. void PWM_config(void)
    2. {
    3. PWMx_InitDefine PWMx_InitStructure;
    4. // 配置PWM4
    5. PWMx_InitStructure.PWM_Mode = CCMRn_PWM_MODE2; //模式, CCMRn_FREEZE,CCMRn_MATCH_VALID,CCMRn_MATCH_INVALID,CCMRn_ROLLOVER,CCMRn_FORCE_INVALID,CCMRn_FORCE_VALID,CCMRn_PWM_MODE1,CCMRn_PWM_MODE2
    6. PWMx_InitStructure.PWM_Duty = 0; //PWM占空比时间, 0~Period
    7. PWMx_InitStructure.PWM_EnoSelect = ENO4P | ENO4N; //输出通道选择, ENO1P,ENO1N,ENO2P,ENO2N,ENO3P,ENO3N,ENO4P,ENO4N / ENO5P,ENO6P,ENO7P,ENO8P
    8. PWM_Configuration(PWM4, &PWMx_InitStructure);
    9. // 配置PWMA
    10. PWMx_InitStructure.PWM_Period = PERIOD - 1; //周期时间, 0~65535
    11. PWMx_InitStructure.PWM_DeadTime = 0; //死区发生器设置, 0~255
    12. PWMx_InitStructure.PWM_MainOutEnable= ENABLE; //主输出使能, ENABLE,DISABLE
    13. PWMx_InitStructure.PWM_CEN_Enable = ENABLE; //使能计数器, ENABLE,DISABLE
    14. PWM_Configuration(PWMA, &PWMx_InitStructure); //初始化PWM通用寄存器, PWMA,PWMB
    15. // 切换PWM4选择PWM4_SW_P26_P27
    16. PWM4_SW(PWM4_SW_P26_P27); //PWM4_SW_P16_P17,PWM4_SW_P26_P27,PWM4_SW_P66_P67,PWM4_SW_P34_P33
    17. // 初始化PWMA的中断
    18. NVIC_PWM_Init(PWMA,DISABLE,Priority_0);
    19. }
  2. 编写Main函数 ```c

void main() { char direction = 1; u8 duty_percent = 0;// 0 -> 100

  1. EAXSFR(); /* 扩展寄存器访问使能, 必写! */
  2. GPIO_config();
  3. PWM_config();
  4. EA = 1;
  5. // 总开关
  6. LED_SW = 0;
  7. LED1 = 0; // P2.7 PWM4
  8. LED2 = 0;
  9. LED3 = 0;
  10. // 循环之前,设置一次pwm(可选)
  11. dutyA.PWM4_Duty = PERIOD * duty_percent / 100;
  12. UpdatePwm(PWM4, &dutyA);
  13. // 0 -> 100
  14. while(1) {
  15. duty_percent += direction;
  16. // 让duty_percent一直在0-100来回往返
  17. if(duty_percent >= 100) {
  18. duty_percent = 100;
  19. direction = -1;
  20. } else if(duty_percent <= 0) {
  21. duty_percent = 0;
  22. direction = 1;
  23. }
  24. // 修改PWM4的duty
  25. dutyA.PWM4_Duty = PERIOD * duty_percent / 100;
  26. UpdatePwm(PWM4, &dutyA);
  27. delay_ms(10);
  28. }

}

  1. <a name="SF5Zj"></a>
  2. ### PWM配置详解
  3. <a name="XxFAe"></a>
  4. #### 周期
  5. 系统主频:1秒钟计数多少次。<br />代码中的PWM周期(PWM Period),指的是按N等份切分1秒钟,每个等份的计数值。<br />![55.png](https://cdn.nlark.com/yuque/0/2023/png/21441195/1679389225162-842d3b09-27f5-474b-a07b-3a4d4310461a.png#averageHue=%23f7f4f4&clientId=u604ca304-275c-4&from=ui&id=u48a40661&originHeight=471&originWidth=2396&originalType=binary&ratio=1&rotation=0&showTitle=false&size=44177&status=done&style=none&taskId=u4d054ca3-0a2e-4c65-89fa-121e61cb967&title=)<br />例如上图,我们按照8等份切分1秒钟的总计数值`MAIN_Fosc`(主频),每个PWM周期的计数值为:<br />`PWM_Period = MAIN_Fosc / 8 = 24M / 8 = 3M = 3 000 000` 单位为次。<br />即如果将这个`3M`作为`Period`参数,可以得到PWM方波每个周期的时长为:<br />`1 / 8 = 0.125s`
  6. 代码中的配置:
  7. ```c
  8. #define PERIOD (MAIN_Fosc / FREQ) // 周期
  9. PWMx_InitStructure.PWM_Period = PERIOD - 1;

配置的是周期中的计数值。
我们的理解策略:通常我们不关心计数值,关心的是1秒钟执行多少次(即频率Hz),也就是一秒钟多少个周期。
因此在代码MAIN_Fosc / 1000中的1000表示的是1秒钟多少个周期(即频率Hz)。
MAIN_Fosc / 1000表示的是每个周期的计数值。那为什么要-1呢?因为计数器是从0开始计数的。

占空比

在一个PWM的周期计数中,高电平的计数时长百分比。
56.png

模式

  • 冻结: CCMRn_FREEZE
  • 匹配时设置通道 n 的输出为有效电平: CCMRn_MATCH_VALID
  • 匹配时设置通道 n 的输出为无效电平: CCMRn_MATCH_INVALID
  • 翻转: CCMRn_ROLLOVER
  • 强制为无效电平: CCMRn_FORCE_INVALID
  • 强制为有效电平: CCMRn_FORCE_VALID
  • PWM 模式 1: CCMRn_PWM_MODE1
  • PWM 模式 2: CCMRn_PWM_MODE2

常用的为PWM 模式 1``PWM 模式 2
PWM 模式 1和PWM 模式 2是反向的,一个占空比越大越亮,一个是越小越亮。

使能PWM

  1. PWMx_InitStructure.PWM_MainOutEnable= ENABLE; //主输出使能, ENABLE,DISABLE
  2. PWMx_InitStructure.PWM_CEN_Enable = ENABLE; //使能计数器, ENABLE,DISABLE
  3. PWM_Configuration(PWMA, &PWMx_InitStructure); //初始化PWM通用寄存器, PWMA,PWMB

引脚配置

  1. PWM4_SW(PWM4_SW_P26_P27);

使能配置成功后,pwm才能工作。
如果运行中pwm想停止掉,也可以通过配置使能来停止。

EAXSFR扩展寄存器

由于PWM的配置相关特殊功能寄存器位于扩展RAM区域,访问这些寄存器,需先将P_SW2的BIT7设置为1,才可正常读写。

  1. EAXSFR(); /* 扩展寄存器访问使能 */

详细可参见STC8手册:

  • 3.1.2 外设端口切换控制寄存器 2(P_SW2)
  • 8.2.8 扩展 SFR 使能寄存器 EAXFR 的使用说明

练习题

  1. 使用PWMA实现8路呼吸灯
  2. 串口控制灯的明暗程度