在 Luckfox RV1103 上玩转 PWM —— LED呼吸灯效果
在嵌入式开发中,PWM(脉宽调制) 是一个非常常见的功能。无论是控制 LED 的亮度、电机的转速,还是蜂鸣器的音调,PWM 都能派上用场。今天我们就以 Luckfox RV1103 为例,写一个小程序来体验 PWM 的魅力。
什么是 PWM?
PWM 的核心思想很简单:通过快速地“开关”电压,并调节 占空比(高电平时间与周期的比例),来控制设备的平均功率。
- 占空比 0% → 完全关闭
- 占空比 50% → 一半亮度
- 占空比 100% → 全亮
环境准备
在 Linux 系统中,PWM 通常通过 sysfs 接口 暴露出来。路径类似于:
/sys/class/pwm/pwmchipX/pwmY
其中:
pwmchipX表示某个 PWM 控制器pwmY表示该控制器下的某个通道
在 Luckfox RV1103 上,常见的 PWM 控制器编号是 10 或 11,而通道通常是 0。
核心代码解析
下面是一段完整的 C++ 程序,它演示了如何在 RV1103 上控制 PWM:
1. 配置控制器与通道
const int PWM_CHIP_NUM = 10; // 可改为 11
const int PWM_CHANNEL = 0; // 通道0
const string PWM_CHIP_PATH = "/sys/class/pwm/pwmchip" + to_string(PWM_CHIP_NUM);
const string PWM_PATH = PWM_CHIP_PATH + "/pwm" + to_string(PWM_CHANNEL);2. 导出通道
Linux 默认不会自动启用 PWM 通道,需要手动导出:
writeSysFS(PWM_CHIP_PATH + "/export", to_string(PWM_CHANNEL));3. 设置周期与占空比
这里我们设置一个 1kHz 的周期:
const int PERIOD_NS = 1000000; // 1ms = 1kHz
writeSysFS(PWM_PATH + "/period", to_string(PERIOD_NS));
writeSysFS(PWM_PATH + "/duty_cycle", "0"); // 初始占空比为04. 固定亮度测试
先来一个 50% 占空比的 LED 测试:
writeSysFS(PWM_PATH + "/duty_cycle", "500000"); // 50%
writeSysFS(PWM_PATH + "/enable", "1");
sleep(3); // 保持3秒5. 呼吸灯效果
通过循环调整占空比,就能实现 LED 的渐亮渐暗:
int duty = 0;
int step = PERIOD_NS / 100; // 每次调整1%
int direction = 1;
while (running) {
duty += step * direction;
if (duty >= PERIOD_NS) { duty = PERIOD_NS; direction = -1; }
if (duty <= 0) { duty = 0; direction = 1; }
writeSysFS(PWM_PATH + "/duty_cycle", to_string(duty));
usleep(50000); // 每50ms更新一次
}效果就是一个柔和的呼吸灯,按下 Ctrl+C 即可停止。
调试与常见问题
- 通道导出失败:检查是否已被占用,或者是否有 root 权限。
- 占空比无效:确认
enable已经设置为1。 - 频率异常:检查
period的单位是否为纳秒。
总结
通过这段代码,我们成功在 Luckfox RV1103 上实现了 PWM 控制,从最简单的固定亮度,到更炫酷的呼吸灯效果。PWM 的应用非常广泛,你可以继续扩展到电机控制、蜂鸣器音效等更多场景。
源代码
#include <iostream>
#include <fstream>
#include <string>
#include <unistd.h>
#include <signal.h>
using namespace std;
// ====== 核心配置:请根据诊断结果修改这里的数字! ======
const int PWM_CHIP_NUM = 10; // 改为 10 (PWM10) 或 11 (PWM11)!
const int PWM_CHANNEL = 0; // Luckfox PWM通常使用通道0
// ==============================================
const string PWM_CHIP_PATH = "/sys/class/pwm/pwmchip" + to_string(PWM_CHIP_NUM);
const string PWM_PATH = PWM_CHIP_PATH + "/pwm" + to_string(PWM_CHANNEL);
const int PERIOD_NS = 1000000; // 1kHz周期
volatile bool running = true;
void signalHandler(int sig) {
running = false;
cout << "\n停止信号已接收。" << endl;
}
bool writeSysFS(const string& path, const string& value) {
ofstream file(path);
if (!file.is_open()) {
cerr << "错误:无法写入文件 " << path << endl;
return false;
}
file << value;
return true;
}
int main() {
cout << "=== PWM LED 测试程序 ===" << endl;
cout << "目标控制器: pwmchip" << PWM_CHIP_NUM << endl;
cout << "目标通道: " << PWM_CHANNEL << endl;
cout << "========================" << endl;
• signal(SIGINT, signalHandler);
• // 1. 检查PWM控制器是否存在
• if (system(("test -d " + PWM_CHIP_PATH + " && echo '找到PWM控制器' || echo '错误:PWM控制器不存在'").c_str())) {}
•
• // 2. 导出PWM通道
• cout << "导出PWM通道..." << endl;
• if (!writeSysFS(PWM_CHIP_PATH + "/export", to_string(PWM_CHANNEL))) {
• cerr << "导出失败!请检查:\n1. 通道是否已被占用\n2. 权限 (需sudo)\n3. 硬件配置" << endl;
• return 1;
• }
• usleep(500000); // 等待导出完成
• // 3. 配置PWM
• cout << "配置PWM参数..." << endl;
• writeSysFS(PWM_PATH + "/period", to_string(PERIOD_NS));
• writeSysFS(PWM_PATH + "/duty_cycle", "0"); // 初始为0
• writeSysFS(PWM_PATH + "/polarity", "normal");
• // 4. 固定亮度测试 (50%)
• cout << "\n[测试1] 固定50%亮度 (500000 ns)" << endl;
• writeSysFS(PWM_PATH + "/duty_cycle", "500000");
• writeSysFS(PWM_PATH + "/enable", "1");
• sleep(3); // 保持3秒
• // 5. 呼吸灯测试
• cout << "\n[测试2] 呼吸灯效果 (按Ctrl+C停止)" << endl;
• int duty = 0;
• int step = PERIOD_NS / 100; // 1%步进
• int direction = 1;
• while (running) {
• duty += step * direction;
• if (duty >= PERIOD_NS) { duty = PERIOD_NS; direction = -1; }
• if (duty <= 0) { duty = 0; direction = 1; }
• writeSysFS(PWM_PATH + "/duty_cycle", to_string(duty));
• // 简单进度显示
• int percent = (duty * 100) / PERIOD_NS;
• cout << "\r占空比: " << percent << "% [" << string(percent/2, '=') << ">" << string(50-percent/2, ' ') << "]";
• cout.flush();
• usleep(50000); // 50ms
• }
• // 6. 清理
• cout << "\n\n清理资源..." << endl;
• writeSysFS(PWM_PATH + "/enable", "0");
• writeSysFS(PWM_PATH + "/duty_cycle", "0");
• usleep(100000);
• writeSysFS(PWM_CHIP_PATH + "/unexport", to_string(PWM_CHANNEL));
• cout << "完成。" << endl;
• return 0;
}