3. PWM控制LED亮度
任务目标
在嵌入式Linux环境中,通过PWM(脉冲宽度调制)控制LED亮度,实现从暗到亮再到暗的平滑呼吸灯效果。本案例直接操作硬件寄存器,不依赖Linux内核PWM子系统,适用于快速验证和教学演示。
硬件与前置条件
- 开发板:i.MX6ULL(或同系列NXP i.MX处理器)
- LED连接至GPIO1_IO08(对应PWM1输出引脚)
- 交叉编译工具链已安装,目标板可运行Linux
核心实现
i.MX6ULL的PWM1控制器基地址为0x020BC000。通过配置PWM周期寄存器(PWMPR)和占空比寄存器(PWMSAR),即可控制输出波形的占空比,从而调节LED亮度。以下代码直接映射物理地址并操作寄存器,实现完整的呼吸灯效果:
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <stdint.h>
// PWM1寄存器偏移(i.MX6ULL参考手册)
#define PWM_BASE 0x020BC000
#define PWMCR_OFF 0x00 // 控制寄存器
#define PWMSR_OFF 0x04 // 状态寄存器
#define PWMPR_OFF 0x08 // 周期寄存器
#define PWMSAR_OFF 0x0C // 占空比寄存器
// PWMCR关键位定义
#define PWMCR_EN (1 << 0) // PWM使能
#define PWMCR_CLKSRC (2 << 2) // 时钟源=IPG (66MHz)
#define PWMCR_PRESCALER(x) ((x) << 4) // 预分频
#define PWMCR_POUTC (1 << 9) // 输出极性:高电平有效
// 映射物理地址到用户空间
static volatile uint32_t *pwm_regs;
static int pwm_init(void) {
int fd = open("/dev/mem", O_RDWR | O_SYNC);
if (fd < 0) {
perror("open /dev/mem");
return -1;
}
pwm_regs = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE,
MAP_SHARED, fd, PWM_BASE);
close(fd);
if (pwm_regs == MAP_FAILED) {
perror("mmap");
return -1;
}
return 0;
}
static void pwm_set_freq_duty(uint32_t freq_hz, uint32_t duty_percent) {
uint32_t period, duty;
uint32_t pwmcr_val;
// 时钟源66MHz,预分频64 → PWM时钟≈1.03MHz
// 周期 = PWM时钟频率 / 期望频率
period = 1031250 / freq_hz;
if (period > 0xFFFF) period = 0xFFFF;
if (period < 2) period = 2;
// 占空比 = 周期 × 百分比 / 100
duty = period * duty_percent / 100;
if (duty == 0) duty = 1; // 避免完全关闭
// 先关闭PWM再修改寄存器
pwm_regs[PWMCR_OFF / 4] &= ~PWMCR_EN;
pwm_regs[PWMPR_OFF / 4] = period - 1;
pwm_regs[PWMSAR_OFF / 4] = duty;
// 配置并启动:预分频64,输出高电平有效
pwmcr_val = PWMCR_EN | PWMCR_CLKSRC | PWMCR_PRESCALER(6) | PWMCR_POUTC;
pwm_regs[PWMCR_OFF / 4] = pwmcr_val;
}
int main(void) {
int i;
if (pwm_init() != 0) {
return -1;
}
printf("呼吸灯启动,按Ctrl+C退出\n");
// 呼吸灯循环:频率1kHz,占空比从1%递增到99%再递减
while (1) {
// 渐亮
for (i = 1; i <= 99; i += 2) {
pwm_set_freq_duty(1000, i);
usleep(15000); // 15ms
}
// 渐暗
for (i = 99; i >= 1; i -= 2) {
pwm_set_freq_duty(1000, i);
usleep(15000);
}
}
return 0;
}
编译与运行
# 交叉编译(以arm-linux-gnueabihf为例)
arm-linux-gnueabihf-gcc -o pwm_led pwm_led.c
# 拷贝到目标板并运行(需要root权限)
./pwm_led
验证方法
- 程序运行后,观察LED亮度应呈现从暗到亮、再从亮到暗的周期性平滑变化,每个完整周期约3秒。
- 使用示波器或万用表测量GPIO1_IO08引脚,应看到频率约1kHz、占空比周期性变化的方波信号。
- 按Ctrl+C终止程序后,LED应停止发光(PWM输出关闭)。
关键说明
- 预分频值设为6(即2^6=64分频),实际PWM时钟为66MHz/64≈1.03MHz,周期寄存器值对应约1kHz输出。
- 若LED亮度变化不明显,可检查LED是否连接至正确的PWM输出引脚,以及是否串联了合适的限流电阻(建议100Ω~330Ω)。
- 本代码直接操作物理地址,仅适用于教学验证;实际产品开发应使用内核PWM子系统或设备树配置。