在 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"); // 初始占空比为0

4. 固定亮度测试

先来一个 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;
}
最后修改:2026 年 01 月 02 日
如果觉得我的文章对你有用,请随意赞赏