嵌入式Linux C应用编程——输出PWM

PWM 应用编程

概述

PWM(Pulse Width Modulation,脉冲宽度调制)是一种通过调节脉冲的宽度(占空比)来实现模拟信号控制的技术,常用于 LED 调光、电机调速、蜂鸣器控制等场景。

本文学习如何在应用层通过 sysfs 文件系统 操控 I.MX6U 开发板上的 PWM 外设。

应用层如何操控 PWM

与 LED、GPIO 设备一样,PWM 也是通过 sysfs 方式 进行操控,对应的路径为 /sys/class/pwm

/sys/class/pwm 目录结构

进入 /sys/class/pwm 目录,可以看到 8 个以 pwmchipX 命名的文件夹(X 为数字 0~7):

/sys/class/pwm/
├── pwmchip0/   → PWM1
├── pwmchip1/   → PWM2
├── pwmchip2/   → PWM3
├── pwmchip3/   → PWM4
├── pwmchip4/   → PWM5
├── pwmchip5/   → PWM6
├── pwmchip6/   → PWM7
└── pwmchip7/   → PWM8

I.MX6U 共有 8 个 PWM 控制器,每个控制器只有 1 路 PWM 输出

注意: 开发板出厂系统中,PWM1(pwmchip0)已被用作 LCD 背光控制,应用层不能直接对它进行控制。其它 PWM 因 I/O 资源不足,出厂系统中均不能使用。如需测试 PWM,需要先禁用 LCD 和 backlight 设备(见下文测试章节)。

pwmchipX 目录下的属性文件

pwmchip0 为例,其目录下包含三个关键属性文件:

/sys/class/pwm/pwmchip0/
├── export       # 只写:导出 PWM(写入编号 0)
├── npwm         # 只读:读取该控制器下有多少路 PWM 输出
└── unexport     # 只写:删除已导出的 PWM(写入编号 0)

npwm(只读)

读取可得知该 PWM 控制器下共有几路 PWM 输出:

cat /sys/class/pwm/pwmchip0/npwm
# 输出: 1

I.MX6U 每个 PWM 控制器只有 1 路,所以读取结果均为 1

export(只写,无读权限)

在使用 PWM 之前,需要先将其导出:

# 导出 PWM1(pwmchip0 下的编号 0)
echo 0 > /sys/class/pwm/pwmchip0/export

# 导出 PWM2
echo 0 > /sys/class/pwm/pwmchip1/export

# 导出 PWM3
echo 0 > /sys/class/pwm/pwmchip2/export

注意: 每个 pwmchipX 下的编号都从 0 开始。因为 I.MX6U 每个控制器只有一路 PWM,所以都只能使用编号 0。

导出成功后,会在 pwmchipX 目录下生成一个名为 pwm0 的子目录。

unexport(只写,无读权限)

使用完 PWM 后,需要将导出的 PWM 删除:

echo 0 > /sys/class/pwm/pwmchip0/unexport

写入 unexport 的编号必须与导出时写入 export 的编号对应。因为 I.MX6U 每个控制器只有一路 PWM,所以编号只能是 0。

pwm0 目录下的属性文件

导出后生成的 pwm0 目录包含以下属性文件:

/sys/class/pwm/pwmchipX/pwm0/
├── enable       # 可读可写:使能/禁止 PWM
├── period       # 可读可写:配置 PWM 周期(单位:ns)
├── duty_cycle   # 可读可写:配置 PWM 占空比(单位:ns)
└── polarity     # 可读可写:配置 PWM 极性(可选,部分 SoC 不支持)

enable

写入 "0" 禁止 PWM 输出,写入 "1" 使能 PWM 输出。通常先配置好 periodduty_cycle,最后再使能 PWM。

echo 0 > enable   # 禁止 PWM 输出
echo 1 > enable   # 使能 PWM 输出

period

配置 PWM 周期,以 纳秒(ns) 为单位。

# 配置 PWM 周期为 10us(10 × 1000ns)
echo 10000 > period

duty_cycle

配置 PWM 占空比,以 纳秒(ns) 为单位。

# 配置占空比为 5us(占空比 50%,周期 10us)
echo 5000 > duty_cycle

polarity

设置 PWM 极性,可写入的值:

说明
"normal" 普通极性(默认)
"inversed" 极性反转
echo normal > polarity     # 默认极性
echo inversed > polarity   # 极性反转

注意: 很多 SoC 的 PWM 外设在硬件上并不支持极性配置,驱动中未实现此接口,因此应用层无法通过 polarity 属性文件配置极性。ALPHA/Mini I.MX6U 开发板出厂系统便是如此。

编写应用程序

程序功能

通过命令行传参接收 PWM 编号、周期和占空比,然后:

  1. 导出 PWM
  2. 配置周期
  3. 配置占空比
  4. 使能 PWM

完整代码

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

static char pwm_path[100];

static int pwm_config(const char *attr, const char *val)
{
    char file_path[100];
    int len;
    int fd;

    sprintf(file_path, "%s/%s", pwm_path, attr);
    if (0 > (fd = open(file_path, O_WRONLY))) {
        perror("open error");
        return fd;
    }

    len = strlen(val);
    if (len != write(fd, val, len)) {
        perror("write error");
        close(fd);
        return -1;
    }

    close(fd);  // 关闭文件
    return 0;
}

int main(int argc, char *argv[])
{
    /* 校验传参 */
    if (4 != argc) {
        fprintf(stderr, "usage: %s <id> <period> <duty>\n",
                argv[0]);
        exit(-1);
    }

    /* 打印配置信息 */
    printf("PWM config: id<%s>, period<%s>, duty<%s>\n",
            argv[1], argv[2],
            argv[3]);

    /* 导出 pwm */
    sprintf(pwm_path, "/sys/class/pwm/pwmchip%s/pwm0", argv[1]);

    if (access(pwm_path, F_OK)) {   // 如果 pwm0 目录不存在,则导出
        char temp[100];
        int fd;

        sprintf(temp, "/sys/class/pwm/pwmchip%s/export", argv[1]);

        if (0 > (fd = open(temp, O_WRONLY))) {
            perror("open error");
            exit(-1);
        }

        if (1 != write(fd, "0", 1)) {   // 导出 pwm
            perror("write error");
            close(fd);
            exit(-1);
        }

        close(fd);  // 关闭文件
    }

    /* 配置 PWM 周期 */
    if (pwm_config("period", argv[2]))
        exit(-1);

    /* 配置占空比 */
    if (pwm_config("duty_cycle", argv[3]))
        exit(-1);

    /* 使能 pwm */
    pwm_config("enable", "1");

    /* 退出程序 */
    exit(0);
}

代码解析

  1. pwm_config() 辅助函数

    • 接收属性名和值,拼装完整路径
    • 以只写方式打开属性文件
    • 写入配置值后关闭文件
    • 简化了多次配置的重复操作
  2. main() 函数流程

    • 校验传参:需要 3 个参数——
      • <id>:PWM 控制器编号(0、1、2、3…,分别对应 PWM1、PWM2、PWM3…)
      • <period>:PWM 周期(单位 ns)
      • <duty>:PWM 占空比(单位 ns)
    • 导出 PWM:通过 access() 判断 pwm0 目录是否存在
      • 存在 → 已导出,跳过
      • 不存在 → 向 export 文件写入 "0" 完成导出
    • 配置周期:调用 pwm_config("period", argv[2])
    • 配置占空比:调用 pwm_config("duty_cycle", argv[3])
    • 使能 PWM:调用 pwm_config("enable", "1")

编译

# 交叉编译(ARM 架构)
arm-linux-gnueabihf-gcc -o testApp pwm.c

运行

# 配置 PWM1,周期 500us(500000ns),占空比 250us(50%)
./testApp 0 500000 250000

在开发板上测试

开发板出厂系统中 PWM1 被 LCD 背光占用,需要先将其释放。有两种方式:

  1. 修改内核源码:配置内核、修改设备树,禁用 LCD 和 backlight 设备后重新编译
  2. u-boot 临时修改(推荐):在 u-boot 命令行中修改内存中的设备树,重启后失效

u-boot 临时修改方法

步骤一:重启开发板,进入 u-boot 命令行模式

开机时按下任意键进入 u-boot 命令行。

img

步骤二:设置环境变量(禁用 LCD 和 backlight)

SD/eMMC 启动方式:

# 定义禁用 LCD 和 backlight 的命令
setenv disable_lcd 'fdt addr ${fdt_addr}; fdt set /backlight status disable; fdt set /soc/aips-bus@02100000/lcdif@021c8000 status disable'

# 修改 mmcboot 命令,在启动内核前先执行 disable_lcd
setenv mmcboot 'echo Booting from mmc ...; run mmcargs; if test ${boot_fdt} = yes || test ${boot_fdt} = try; then if run loadfdt; then run disable_lcd; bootz ${loadaddr} - ${fdt_addr}; else if test ${boot_fdt} = try; then bootz; else echo WARN: Cannot load the DT; fi; fi; else bootz; fi;'

NAND 启动方式:

setenv disable_lcd 'fdt addr ${fdt_addr}; fdt set /backlight status disable; fdt set /soc/aips-bus@02100000/lcdif@021c8000 status disable'

setenv bootcmd 'nand read ${loadaddr} 0x620000 0x800000;nand read ${fdt_addr} ${fdt_offset} 0x20000; run disable_lcd; bootz ${loadaddr} - ${fdt_addr}'

img

步骤三:启动内核

boot

img

这种修改方式只对 本次启动 生效,因为修改的是内存中的设备树文件。重启开发板后会恢复到未修改状态。

步骤四:拷贝并运行测试程序

系统启动后,LCD 已被禁用,PWM1 已腾出。将编译好的可执行文件拷贝到开发板并运行:


# 运行测试程序
./testApp 0 500000 250000

观察 PWM 波形

PWM1 的输出引脚绑定到 GPIO1_IO08(即 LCD 背光引脚),该引脚已通过开发板扩展口引出。使用示波器检测 GPIO1_IO08 引脚,可观察到 PWM 波形。

总结

PWM 应用编程的核心思路与 GPIO 类似——通过 sysfs 文件系统接口,以读写文件的方式操控硬件外设。

posted @ 2026-07-22 19:09  Javenwww  阅读(4)  评论(0)    收藏  举报