LED 呼吸灯(PWM 实现,PWMA 版)
一、学习目标- 理解 PWM 的基础概念和工作原理。
- 学会在 STC8H 上配置 PWMA。
- 熟悉 PWMA 的关键配置项(周期、占空比、模式、引脚切换等)。
- 会用 PWMA 控制 LED 亮度,实现“呼吸灯”效果。
- 掌握用 波形&概念图 来理解和调试 PWM。
二、PWM 基础概念回顾1. 什么是 PWMPWM:Pulse Width Modulation,脉宽调制。
核心思想:
频率基本固定,通过改变“每个周期内高电平所占的时间比例”(占空比)来调节“平均输出能量”。
- 如果控制的是 LED:占空比越大 → 平均电流越大 → 越亮。
- 如果控制的是 电机:占空比越大 → 平均电压越高 → 转得更快/力道更大。
所以可以把 PWM 看成一种“用数字信号(高低电平)去模拟模拟量”的手段。
2. STC8H 的 PWM 模块概览STC8H 内部有 8 通道 16 位高级 PWM 定时器,分成两组:
- PWMA:4 组互补 / 对称 / 死区控制的 PWM,可做电机驱动、互补输出等;
- PWMB:4 路普通 PWM 输出,通常用来做简单调光、震动马达之类。
两组 PWM 的时钟频率可以 独立配置。
数据手册中还有一张 “PWM 通道 vs 引脚” 对照表,你可以在富文本里用表格或图片插入原图。文件里已经给出了精简版表格,这里只强调和呼吸灯相关的一行:
- 本例使用 PWMA 的 PWM4 通道
- 选择输出脚:PWM4_SW_P26_P27
- 即 PWM4P → P2.6,PWM4N → P2.7(本例重点用 P2.7 做 LED1)
三、PWMA 应用:呼吸灯效果设计目标:
- 用 P2.7(LED1) 做一颗呼吸灯;
- 通过 PWM4 的占空比从 0% → 100% → 0% 往返变化,模拟 LED “渐亮—渐灭”的呼吸效果。
1. 工程准备在工程中(除了基础库)需要拷贝以下文件:
- STC8H_PWM.c / STC8H_PWM.h
- NVIC.c / NVIC.h
- Switch.h
2. 头文件与宏定义- #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) - 1) // 周期
-
- PWMx_Duty dutyA;
复制代码 说明:
- LED_SW:整组 LED 的电源开关(类似上一节 LED 灯设计中的三极管开关)。
- LED1/LED2/LED3:对应不同引脚的 LED。这里呼吸灯主要用 LED1 = P27。
- FREQ = 1000:目标 PWM 频率为 1kHz。
- PERIOD = (MAIN_Fosc / FREQ) - 1:
- 如果主频是 24MHz,则 PERIOD ≈ 24000 - 1;
- 对应每秒 1000 个 PWM 周期,即 1kHz。
- PWMx_Duty dutyA:保存 PWMA 各通道占空比的结构体,本例用其中的 PWM4 字段。
3. GPIO 配置- void GPIO_config(void) {
- GPIO_InitTypeDef GPIO_InitStructure; // 结构定义
-
- // LED_SW 总开关:P4.5
- GPIO_InitStructure.Pin = GPIO_Pin_5;
- GPIO_InitStructure.Mode = GPIO_OUT_PP; // 推挽输出
- GPIO_Inilize(GPIO_P4, &GPIO_InitStructure);
-
- // P2.6 / P2.7:对应 LED2 / LED1,引脚后面会被 PWM4 占用
- GPIO_InitStructure.Pin = GPIO_Pin_6 | GPIO_Pin_7;
- GPIO_InitStructure.Mode = GPIO_PullUp; // 上拉 / 准双向
- GPIO_Inilize(GPIO_P2, &GPIO_InitStructure);
- }
复制代码
- P4.5 用来控制 LED 整体电源;
- P2.7 在本例分配给 PWM4N 用来驱动 LED1 做呼吸灯。
4. PWM 配置(PWMA + PWM4)- void PWM_config(void)
- {
- PWMx_InitDefine PWMx_InitStructure;
-
- // 1)配置 PWM4 通道
- PWMx_InitStructure.PWM_Mode = CCMRn_PWM_MODE2; // 模式:PWM 模式 2
- PWMx_InitStructure.PWM_Duty = 0; // 初始占空比 0
- PWMx_InitStructure.PWM_EnoSelect = ENO4P | ENO4N; // 使能 PWM4P 与 PWM4N 输出
- PWM_Configuration(PWM4, &PWMx_InitStructure);
-
- // 2)配置 PWMA 公共部分
- PWMx_InitStructure.PWM_Period = PERIOD; // 计数周期
- PWMx_InitStructure.PWM_DeadTime = 0; // 死区时间 0(本例非互补驱动,设 0 即可)
- PWMx_InitStructure.PWM_MainOutEnable = ENABLE; // 主输出使能
- PWMx_InitStructure.PWM_CEN_Enable = ENABLE; // 计数器使能
- PWM_Configuration(PWMA, &PWMx_InitStructure); // 初始化 PWMA
-
- // 3)引脚切换:把 PWM4 映射到 P2.6 / P2.7
- PWM4_SW(PWM4_SW_P26_P27); // 可选:PWM4_SW_P16_P17, PWM4_SW_P26_P27, PWM4_SW_P66_P67, PWM4_SW_P34_P33
-
- // 4)PWMA 中断(本例用不到中断,直接禁用)
- NVIC_PWM_Init(PWMA, DISABLE, Priority_0);
- }
复制代码 关键点:
- CCMRn_PWM_MODE2:PWM 模式 2(和模式 1 的占空比“高电平逻辑”相反)。
- PWM_EnoSelect = ENO4P | ENO4N:同时启用 PWM4 的正/负两路输出;
- PWM_Period = PERIOD:周期计数值,用来决定 PWM 基本频率;
- PWM_MainOutEnable / PWM_CEN_Enable:不开这两个,PWM 不会真正输出。
- PWM4_SW(PWM4_SW_P26_P27):一定要切到正确的引脚,否则即便 PWM 配置正确,外部也看不到波形。
5. 主函数:实现“呼吸”曲线- void main() {
- char direction = 1;
- u8 duty_percent = 0; // 0 -> 100
-
- EAXSFR(); // 扩展寄存器访问使能,必须先开!
- GPIO_config();
- PWM_config();
- EA = 1;
-
- // 总开关:使能 LED 供电
- LED_SW = 0;
- LED1 = 0; // P2.7 PWM4
- LED2 = 0;
- LED3 = 0;
-
- // 循环之前,预先设置一次 PWM4 占空比(可选)
- dutyA.PWM4_Duty = PERIOD * duty_percent / 100;
- UpdatePwm(PWM4, &dutyA);
-
- while (1) {
- duty_percent += direction;
-
- // 让 duty_percent 在 0 ~ 100 来回往返
- if (duty_percent >= 100) {
- duty_percent = 100;
- direction = -1;
- } else if (duty_percent <= 0) {
- duty_percent = 0;
- direction = 1;
- }
-
- // 根据百分比更新 PWM4 占空比
- dutyA.PWM4_Duty = PERIOD * duty_percent / 100;
- UpdatePwm(PWM4, &dutyA);
-
- delay_ms(10); // 平滑度控制:10ms 调整一次占空比
- }
- }
复制代码 逻辑解释(可以在富文本里配几行“伪代码”说明):
- duty_percent:0~100,代表占空比百分数;
- direction:
- 为 +1 时:占空比逐步 递增(LED 渐亮);
- 为 -1 时:占空比逐步 递减(LED 渐灭)。
- 每 10ms 更新一次占空比,100 次变化 ≈ 1 秒,感受到一个比较顺滑的“呼吸”过程。
四、PWM 配置进一步理解1. 周期(Period)- 系统主频 MAIN_Fosc:表示 1 秒内时钟跳动次数(例如 24MHz → 1 秒 2400 万次)。
- 我们希望一秒有多少个 PWM 周期?这就是 频率 FREQ,单位 Hz。
公式:
PWM_Period = MAIN_Fosc / FREQ举例:
- 主频 24MHz,FREQ = 8Hz:
- PWM_Period = 24,000,000 / 8 = 3,000,000
- 周期时间 = 1 / 8 = 0.125 秒
本例中:
- #define FREQ 1000
- #define PERIOD (MAIN_Fosc / FREQ)
- PWMx_InitStructure.PWM_Period = PERIOD - 1;
复制代码
- 这里的 1000 就是指 每秒 1000 个 PWM 周期,也就是 1kHz;
- PERIOD - 1 是因为计数器是从 0 开始数(0 ~ PERIOD-1 一共 PERIOD 次)。
2. 占空比(Duty)在“一个周期”的计数过程中,高电平持续的计数步数 / 总步数 = 占空比。
- 占空比 0%:LED 一直关;
- 占空比 50%:LED 半亮(肉眼是均匀的中等亮度);
- 占空比 100%:LED 常亮。
代码里通过:
dutyA.PWM4_Duty = PERIOD * duty_percent / 100;把百分比换成对应的比较值(计数步数)。
3. 模式(PWM Mode)常见模式:
- CCMRn_FREEZE:冻结输出
- CCMRn_MATCH_VALID:匹配时输出有效电平
- CCMRn_MATCH_INVALID:匹配时输出无效电平
- CCMRn_ROLLOVER:翻转
- CCMRn_FORCE_INVALID:强制无效
- CCMRn_FORCE_VALID:强制有效
- CCMRn_PWM_MODE1:PWM 模式 1
- CCMRn_PWM_MODE2:PWM 模式 2
实际开发里:
- 最常用的是 PWM_MODE1 和 PWM_MODE2;
- 二者的“占空比高电平含义”是相反的:
- 一个是“占空比越大越亮”;
- 另一个是“占空比越大越暗”。
本例使用 CCMRn_PWM_MODE2,并在软件中让 duty_percent 从 0→100→0 往返,最终实现的是“亮-灭”的呼吸效果。
4. 使能 PWM & 引脚切换 & EAXSFR几个关键配置别忘:
- PWMx_InitStructure.PWM_MainOutEnable = ENABLE; // 主输出
- PWMx_InitStructure.PWM_CEN_Enable = ENABLE; // 计数器
- PWM_Configuration(PWMA, &PWMx_InitStructure);
-
- PWM4_SW(PWM4_SW_P26_P27); // 把 PWM4 切到 P2.6 / P2.7
-
- EAXSFR(); // 扩展寄存器访问使能(访问 PWMA 相关寄存器前必须调用)
复制代码
- PWM_MainOutEnable & PWM_CEN_Enable 没开 → PWM 不工作;
- PWM4_SW(...) 没配对 → 引脚不输出波形;
- EAXSFR() 没调用 → PWMA 的扩展 SFR 不能正确访问(STC8 系列特有)。
详细说明可查数据手册:
- 3.1.2 《外设端口切换控制寄存器 2(P_SW2)》
- 9.2.8 《扩展 SFR 使能寄存器 EAXFR 的使用说明》
|