title: PWM与看门狗 tags: [嵌入式Linux, Linux应用编程, PWM, sysfs, 看门狗, watchdog, ioctl, IMX6ULL] created: 2026-09-18 updated: 2026-09-18
💡 关联知识:[[03-外设与高级IO编程/02-GPIO与LED应用编程]]、[[03-外设与高级IO编程/04-FrameBuffer与LCD应用编程]]、[[03-外设与高级IO编程/06-摄像头串口与音频]];延伸阅读:[[嵌入式Linux驱动开发实战/05-Linux外设驱动实战/06-RTC与PWM驱动]]、[[嵌入式Linux驱动开发实战/05-Linux外设驱动实战/01-LED与蜂鸣器驱动]]
本篇讲两个外设:PWM(脉冲宽度调制,用于背光调光、蜂鸣器发声、电机调速)和看门狗(Watchdog,用于系统异常时自动复位)。二者在应用层的操作方式很不一样——PWM 通过 sysfs 属性文件读写,看门狗通过 ioctl 控制,但都属于"把内核驱动暴露的接口用好"这一类应用编程。
PWM(Pulse Width Modulation,脉冲宽度调制)是一种通过数字信号占空比来模拟不同输出的技术。一个 PWM 波形由三个要素描述:
| 要素 | 含义 | 单位 |
|---|---|---|
周期 period |
一个完整波形的时间长度 | ns(纳秒) |
频率 f |
周期的倒数,f = 1 / period |
Hz |
占空比 duty_cycle |
一个周期内高电平的时间长度 | ns(纳秒) |
极性 polarity |
高电平有效(normal)或低电平有效(inversed) | — |
占空比比例 = duty_cycle / period。例如周期 500000 ns = 500 us,对应频率 1 / 500us = 2 kHz;占空比 250000 ns 占周期一半,即 50%。
flowchart LR
subgraph 一个周期 period
H["高电平 duty_cycle"] --> L["低电平 period - duty_cycle"]
end
P["period = 500000ns (500us)"] --> F["频率 f = 1/period = 2kHz"]
H --> D["占空比 = duty_cycle/period"]
PWM 的典型用途:LCD 背光调光(改变占空比即改变平均亮度)、无源蜂鸣器发声(改变频率即改变音调)、电机调速等。I.MX6U 共有 8 个 PWM 控制器(PWM1~PWM8),每个控制器只有 1 路 PWM 输出。
与 LED 一样,PWM 通过 sysfs 操控。进入 /sys/class/pwm 目录,可以看到 8 个以 pwmchipX(X 为 0~7)命名的文件夹,分别对应 I.MX6U 的 8 个 PWM 控制器:
ls /sys/class/pwm
# pwmchip0 pwmchip1 ... pwmchip7
进入某个控制器目录(如 pwmchip0),重点关注 npwm、export、unexport:
| 属性文件 | 读写 | 说明 |
|---|---|---|
npwm |
只读 | 该 PWM 控制器下共有几路 PWM 输出 |
export |
只写 | 导出 PWM,写入编号(从 0 开始) |
unexport |
只写 | 删除已导出的 PWM,写入编号与 export 对应 |
I.MX6U 每个 PWM 控制器只有 1 路输出,因此 npwm 读出来是 1,导出时编号都用 0。
echo 0 > /sys/class/pwm/pwmchip0/export # 导出 PWM1
echo 0 > /sys/class/pwm/pwmchip1/export # 导出 PWM2
# ... pwmchip2~pwmchip5 同理,编号都是 0
导出成功后会生成一个 pwm0 目录。
注意:开发板出厂系统中,PWM1 已被用作 LCD 背光控制,应用层不能直接控制;其它 PWM 因 I/O 资源不够也均不能使用。若要用 PWM1 测试,需要先禁用 LCD 与 backlight(见第 4 节)。
ls /sys/class/pwm/pwmchip0/pwm0
# duty_cycle enable period polarity
| 属性文件 | 读写 | 说明 |
|---|---|---|
enable |
可读写 | 写 1 使能 PWM 输出,写 0 禁止;读可获取当前状态 |
polarity |
可读写 | normal 普通极性、inversed 反转极性 |
period |
可读写 | 配置 PWM 周期,单位 ns |
duty_cycle |
可读写 | 配置 PWM 占空比,单位 ns |
echo 0 > enable # 禁止 PWM 输出
echo 1 > enable # 使能 PWM 输出(通常配置好后再使能)
echo normal > polarity # 默认极性
echo inversed > polarity # 极性反转
echo 10000 > period # PWM 周期设置为 10us(10 * 1000ns)
echo 5000 > duty_cycle # 占空比设置为 5us
很多 SoC 的 PWM 外设在硬件上并不支持极性配置,对应驱动未实现该接口,应用层自然无法通过
polarity配置。ALPHA/Mini I.MX6U 出厂系统便如此。
程序接收 3 个参数:PWM 编号、周期(ns)、占空比(ns)。先确保 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);
}
逐段解释:
| 位置 | 代码 | 说明 |
|---|---|---|
pwm_config() |
通用属性写函数 | 拼接 pwm_path/attr 路径,以只写方式打开并写入值,是操作 sysfs 属性的标准写法 |
| 传参校验 | 4 != argc |
需传入 id、period、duty 三个参数 |
sprintf(pwm_path, ...) |
构造 pwm0 路径 |
如 id=0 时为 /sys/class/pwm/pwmchip0/pwm0 |
access(pwm_path, F_OK) |
判断是否已导出 | 目录不存在说明未导出,需要写 export |
写 export |
write(fd, "0", 1) |
导出编号 0 的 PWM |
pwm_config("period", ...) |
配置周期 | 单位 ns |
pwm_config("duty_cycle", ...) |
配置占空比 | 单位 ns |
pwm_config("enable", "1") |
使能输出 | 配置完成后最后一步 |
编译:
arm-linux-gnueabihf-gcc -o testApp pwm.c
scp testApp root@192.168.1.10:/home/root/
出厂系统无法直接使用 PWM,因为 PWM1 被 LCD 背光占用。要想测试,需要对设备树做配置、禁用 LCD 和 backlight 设备(status 属性设置为 disabled),再重新编译设备树、用生成的 dtb 替换开发板启动文件中的 dtb。也可以参考《I.MX6U 嵌入式 Linux 驱动开发指南》自行配置 PWM。
这里介绍一个不用重新编译设备树的简单方法:重启开发板进入 u-boot 命令行模式,u-boot 启动时会把内核设备树(dtb)拷贝到内存,我们直接查看、修改内存中的那份设备树,把 LCD 和 backlight 的 status 改为 disabled。
SD/eMMC 启动方式,依次执行:
setenv disable_lcd 'fdt addr ${fdt_addr}; fdt set /backlight status disable; fdt set /soc/aips-bus@02100000/lcdif@021c8000 status disable'
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}'
然后执行 boot 启动内核。系统启动后 PWM1 就腾出来了,此时 LCD 被禁用。
Tips:这种修改方式只对本次启动生效,因为我们改的是内存中的设备树;下一次重启会恢复原状。
开发板出厂系统已将 PWM1 输出绑定到 GPIO1_IO08 引脚(即 LCD 背光引脚),该引脚已通过扩展口引出,可用示波器测量。
./testApp 0 500000 250000
# PWM config: id<0>, period<500000>, duty<250000>
本实验测试 PWM1。用示波器检测 GPIO1_IO08 引脚,应看到周期 500us(即 500000ns)、对应频率 2kHz、占空比 50% 的 PWM 波形,与配置一致。
| 现象 | 可能原因 | 排查手段 |
|---|---|---|
open error: No such file or directory |
没进 u-boot 禁用 LCD/backlight,或路径/编号写错 | 确认 ls /sys/class/pwm/pwmchip0/pwm0 |
| 写属性报错 | 未先导出、或写入格式不对 | 先 echo 0 > export,写入的值不带单位 |
| 引脚无波形 | PWM 未使能 / 被其它功能占用 | cat enable 应为 1;确认 PWM1 已腾出 |
| 占空比大于周期 | 参数不合法 | duty_cycle 必须不超过 period |
无 pwm0 目录 |
未导出 | 程序会自动导出,也可手动 echo 0 > export |
⚠️ 来源说明:本节不属于《I.MX6U嵌入式Linux C应用编程指南》内容,为扩展知识。
duty_cycle / period 即可改变平均亮度。运行 ./testApp 0 500000 100000(20% 占空比)与 ./testApp 0 500000 400000(80% 占空比),可对比亮度差异。pwm.c 改变 period 产生不同音调,例如 period=1000000ns 对应 1kHz。有源蜂鸣器自带振荡电路,只需电平驱动即可,不需要 PWM。在产品化的嵌入式系统中,为使系统在异常情况下能自动复位,一般都需要引入看门狗。看门狗其实就是一个可以在一定时间内被复位(重置)的计数器(看门狗计时器/定时器):
有些看门狗也可以只产生中断信号而不使系统复位。
flowchart LR
START["看门狗启动/喂狗<br/>计数器清零"] --> COUNT["计数器递减/递增"]
COUNT --> Q{"计时期内<br/>是否喂狗?"}
Q -->|"是"| START
Q -->|"否(超时溢出)"| RST["产生复位信号<br/>系统重启"]
I.MX6UL/I.MX6ULL SoC 集成了两个看门狗定时器(WDOG):WDOG1 和 WDOG2。其中 WDOG2 用于安全目的,WDOG1 则是一个普通看门狗,支持产生中断信号以及复位 CPU。
Linux 系统中注册的看门狗外设,都会在 /dev/ 下生成设备节点,名称通常为 watchdogX(X 为编号 0、1、2…),如 /dev/watchdog0、/dev/watchdog1。
ls /dev/watchdog*
# /dev/watchdog /dev/watchdog0
watchdog0 就是 I.MX6U 的 WDOG1 对应的设备节点。而 /dev/watchdog 名称不带数字编号,它代表系统默认的看门狗设备,通常指的就是 watchdog0——即这两个节点代表同一个硬件外设。
应用层控制看门狗通过 ioctl() 实现。程序中需要包含头文件:
#include <linux/watchdog.h>
该头文件定义了各种 ioctl 指令宏,每个指令表示向设备请求不同操作:
#define WDIOC_GETSUPPORT _IOR(WATCHDOG_IOCTL_BASE, 0, struct watchdog_info)
#define WDIOC_GETSTATUS _IOR(WATCHDOG_IOCTL_BASE, 1, int)
#define WDIOC_GETBOOTSTATUS _IOR(WATCHDOG_IOCTL_BASE, 2, int)
#define WDIOC_GETTEMP _IOR(WATCHDOG_IOCTL_BASE, 3, int)
#define WDIOC_SETOPTIONS _IOR(WATCHDOG_IOCTL_BASE, 4, int)
#define WDIOC_KEEPALIVE _IOR(WATCHDOG_IOCTL_BASE, 5, int)
#define WDIOC_SETTIMEOUT _IOWR(WATCHDOG_IOCTL_BASE, 6, int)
#define WDIOC_GETTIMEOUT _IOR(WATCHDOG_IOCTL_BASE, 7, int)
#define WDIOC_SETPRETIMEOUT _IOWR(WATCHDOG_IOCTL_BASE, 8, int)
#define WDIOC_GETPRETIMEOUT _IOR(WATCHDOG_IOCTL_BASE, 9, int)
#define WDIOC_GETTIMELEFT _IOR(WATCHDOG_IOCTL_BASE, 10, int)
常用指令:
| ioctl 指令 | 作用 |
|---|---|
WDIOC_GETSUPPORT |
获取看门狗支持哪些功能 |
WDIOC_SETOPTIONS |
开启或关闭看门狗 |
WDIOC_KEEPALIVE |
喂狗操作 |
WDIOC_SETTIMEOUT |
设置看门狗超时时间 |
WDIOC_GETTIMEOUT |
获取看门狗超时时间 |
int fd;
fd = open("/dev/watchdog", O_RDWR);
if (0 > fd)
fprintf(stderr, "open error: %s: %s\n", "/dev/watchdog", strerror(errno));
ioctl(int fd, WDIOC_GETSUPPORT, struct watchdog_info *info);
需要传入一个 struct watchdog_info *,ioctl 会把获取到的数据写入该对象:
struct watchdog_info {
__u32 options; /* Options the card/driver supports */
__u32 firmware_version; /* Firmware version of the card */
__u8 identity[32]; /* Identity of the board */
};
options:设备支持哪些功能或选项;firmware_version:固件版本号;identity:描述性字符串。options 字段可取以下任意一个值或多值的位或:
#define WDIOF_OVERHEAT 0x0001 /* Reset due to CPU overheat */
#define WDIOF_FANFAULT 0x0002
#define WDIOF_EXTERN1 0x0004
#define WDIOF_POWERUNDER 0x0010
#define WDIOF_CARDRESET 0x0020
#define WDIOF_POWEROVER 0x0040
#define WDIOF_SETTIMEOUT 0x0080 /* Set timeout (in seconds) */
#define WDIOF_MAGICCLOSE 0x0100 /* Supports magic close char */
#define WDIOF_PRETIMEOUT 0x0200 /* Pretimeout (in seconds), get/set */
#define WDIOF_ALARMONLY 0x0400 /* Watchdog triggers alarm, not a reboot */
#define WDIOF_KEEPALIVEPING 0x8000 /* Keep alive ping reply */
常见的值有 WDIOF_SETTIMEOUT(设备支持设置超时时间)与 WDIOF_KEEPALIVEPING(设备支持"喂狗",即重置计时器)。
struct watchdog_info info;
if (0 > ioctl(fd, WDIOC_GETSUPPORT, &info)) {
fprintf(stderr, "ioctl error: WDIOC_GETSUPPORT: %s\n", strerror(errno));
return -1;
}
printf("identity: %s\n", info.identity);
printf("version: %u\n", info.firmware_version);
if (0 == (WDIOF_KEEPALIVEPING & info.options))
printf("设备不支持喂狗操作\n");
if (0 == (WDIOF_SETTIMEOUT & info.options))
printf("设备不支持设置超时时间\n");
ioctl(int fd, WDIOC_GETTIMEOUT, int *timeout); // 获取
ioctl(int fd, WDIOC_SETTIMEOUT, int *timeout); // 设置
超时时间以秒为单位。设置时不可超过其最大值,否则 ioctl 调用会失败。
int timeout;
if (0 > ioctl(fd, WDIOC_GETTIMEOUT, &timeout)) {
fprintf(stderr, "ioctl error: WDIOC_GETTIMEOUT: %s\n", strerror(errno));
return -1;
}
printf("current timeout: %ds\n", timeout);
timeout = 10; //10 秒钟
if (0 > ioctl(fd, WDIOC_SETTIMEOUT, &timeout)) {
fprintf(stderr, "ioctl error: WDIOC_SETTIMEOUT: %s\n", strerror(errno));
return -1;
}
ioctl(int fd, WDIOC_SETOPTIONS, int *option);
option 指向一个 int 变量,可取:
#define WDIOS_DISABLECARD 0x0001 /* Turn off the watchdog timer */
#define WDIOS_ENABLECARD 0x0002 /* Turn on the watchdog timer */
int option = WDIOS_ENABLECARD; //开启
//int option = WDIOS_DISABLECARD; //停止
if (0 > ioctl(fd, WDIOC_SETOPTIONS, &option)) {
fprintf(stderr, "ioctl error: WDIOC_SETOPTIONS: %s\n", strerror(errno));
return -1;
}
重要:当调用
open()打开看门狗设备时,即使程序中没有开启看门狗计时器,close()关闭设备时看门狗也会自动启动。所以打开设备后,需要先用WDIOC_SETOPTIONS指令(WDIOS_DISABLECARD)停止计时,等所有设置完成之后再开启。
ioctl(int fd, WDIOC_KEEPALIVE, NULL);
计时器启动后,必须在超时之前"喂狗",否则计时器溢出将导致系统复位或产生中断:
if (0 > ioctl(fd, WDIOC_KEEPALIVE, NULL)) {
fprintf(stderr, "ioctl error: WDIOC_KEEPALIVE: %s\n", strerror(errno));
}
程序打开看门狗后先停止计时,设置超时时间,再开启计时并在超时前不断喂狗。
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <string.h>
#include <linux/watchdog.h>
#define WDOG_DEV "/dev/watchdog"
int main(int argc, char *argv[])
{
struct watchdog_info info;
int timeout;
int time;
int fd;
int op;
if (2 != argc) {
fprintf(stderr, "usage: %s <timeout>\n", argv[0]);
exit(EXIT_FAILURE);
}
/* 打开看门狗 */
fd = open(WDOG_DEV, O_RDWR);
if (0 > fd) {
fprintf(stderr, "open error: %s: %s\n", WDOG_DEV, strerror(errno));
exit(EXIT_FAILURE);
}
/* 打开之后看门狗计时器会开启、先停止它 */
op = WDIOS_DISABLECARD;
if (0 > ioctl(fd, WDIOC_SETOPTIONS, &op)) {
fprintf(stderr, "ioctl error: WDIOC_SETOPTIONS: %s\n", strerror(errno));
close(fd);
exit(EXIT_FAILURE);
}
timeout = atoi(argv[1]);
if (1 > timeout)
timeout = 1;
/* 设置超时时间 */
printf("timeout: %ds\n", timeout);
if (0 > ioctl(fd, WDIOC_SETTIMEOUT, &timeout)) {
fprintf(stderr, "ioctl error: WDIOC_SETTIMEOUT: %s\n", strerror(errno));
close(fd);
exit(EXIT_FAILURE);
}
/* 开启看门狗计时器 */
op = WDIOS_ENABLECARD;
if (0 > ioctl(fd, WDIOC_SETOPTIONS, &op)) {
fprintf(stderr, "ioctl error: WDIOC_SETOPTIONS: %s\n", strerror(errno));
close(fd);
exit(EXIT_FAILURE);
}
/* 喂狗 */
time = (timeout * 1000 - 100) * 1000;//喂狗时间设置us微秒、在超时时间到来前100ms喂狗
for ( ; ; ) {
usleep(time);
ioctl(fd, WDIOC_KEEPALIVE, NULL);
}
}
逐段解释:
| 位置 | 代码 | 说明 |
|---|---|---|
| 参数校验 | 2 != argc |
需传入看门狗超时时间(秒) |
open(WDOG_DEV, O_RDWR) |
打开看门狗 | 以读写方式打开 /dev/watchdog |
WDIOC_SETOPTIONS + WDIOS_DISABLECARD |
先停止计时 | 因为打开设备后计时器会开启 |
atoi(argv[1]) |
解析超时时间 | 小于 1 秒则强制为 1 |
WDIOC_SETTIMEOUT |
设置超时 | 单位秒,不能超过最大值 |
WDIOC_SETOPTIONS + WDIOS_ENABLECARD |
开启计时 | 开始看门狗工作 |
time = (timeout*1000 - 100)*1000 |
计算喂狗间隔 | usleep 以微秒为单位,在超时前 100ms 喂狗 |
WDIOC_KEEPALIVE |
循环喂狗 | 不断重置计时器,避免系统复位 |
编译:
arm-linux-gnueabihf-gcc -o testApp watchdog_test.c
scp testApp root@192.168.1.10:/home/root/
把可执行文件拷贝到开发板 /home/root,执行测试程序,例如启动看门狗、设置超时时间为 2 秒:
./testApp 2
第一次执行可能会报错:
open error: /dev/watchdog: Device or resource busy
这是因为看门狗默认已被其他功能(出厂系统的看门狗脚本)使用。解决方法是手动关闭它:打开 /etc/init.d/watchdog.sh 文件,在开头加一行 exit 0,把整个文件(后续逻辑)跳过。
vi /etc/init.d/watchdog.sh
# 在文件开头加入:
# exit 0
然后重启开发板,再执行程序,看门狗计时器就已启动,程序会不断喂狗、重置计时器,保证不重启:
./testApp 2
# timeout: 2s
程序运行中按 Ctrl + C 结束程序。结束程序意味着停止喂狗了,但看门狗计时器并没有停止,于是计时器会溢出、发生复位重启:
./testApp 2
timeout: 2s
^C
watchdog watchdog0: watchdog did not stop! <- 内核打印:计时器仍在计时、未停止
(随后系统复位重启)
内核打印的 watchdog watchdog0: watchdog did not stop! 表示看门狗计时器还正在计时、未被停止。
测试完毕后,把 /etc/init.d/watchdog.sh 文件中的 exit 0 去掉,让系统重新使用看门狗。
| 现象 | 可能原因 | 排查手段 |
|---|---|---|
Device or resource busy |
看门狗已被系统占用 | 在 /etc/init.d/watchdog.sh 开头加 exit 0 后重启 |
| 设置超时失败 | 超过设备支持的最大值 | 用 WDIOC_GETTIMEOUT 读取,或改小参数 |
| 程序退出后系统立即复位 | close 会自动启动看门狗,且退出后无人喂狗 |
正常现象,用于验证;测试完恢复 watchdog.sh |
| 设备不支持喂狗/设置超时 | 硬件/驱动不支持 | 用 WDIOC_GETSUPPORT 检查 options 位 |
open 失败无权限 |
非 root 运行 | 用 root 执行 |
⚠️ 来源说明:本节不属于《I.MX6U嵌入式Linux C应用编程指南》内容,为扩展知识。
| 维度 | I.MX6ULL(本教程) | STM32(裸机/RTOS) | RK3568 |
|---|---|---|---|
| PWM 接口 | sysfs /sys/class/pwm/pwmchipN |
定时器寄存器 / HAL_TIM_PWM | sysfs,接口一致 |
| PWM 配置 | 写 period/duty_cycle/enable(ns) |
配置 ARR/PSC/CCR 寄存器 | 写 period/duty_cycle/enable(ns) |
| 看门狗接口 | /dev/watchdog + ioctl |
IWDG/WWDG 寄存器直接喂狗 | /dev/watchdog + ioctl,接口一致 |
| 看门狗喂狗 | WDIOC_KEEPALIVE |
写 IWDG_KR 重装载键 | WDIOC_KEEPALIVE |
| 交叉工具链 | arm-linux-gnueabihf-gcc |
arm-none-eabi-gcc |
aarch64-linux-gnu-gcc |
I.MX6ULL 与 RK3568 的 sysfs PWM 与 ioctl 看门狗用法基本通用;STM32 裸机没有 sysfs 与设备节点,需直接操作寄存器。
⚠️ 来源说明:本节不属于《I.MX6U嵌入式Linux C应用编程指南》内容,为扩展知识。
答:周期是波形一个完整循环的时间长度,频率是周期的倒数(f = 1/period),占空比是一个周期内高电平的时间长度,占空比比例 = duty_cycle / period。在 sysfs 中,先 echo 0 > /sys/class/pwm/pwmchipN/export 导出,再向 pwm0/period 与 pwm0/duty_cycle 写入纳秒值,最后 echo 1 > pwm0/enable 使能。例如 period=500000、duty_cycle=250000 表示 2kHz、50%。
答:进入 /sys/class/pwm/pwmchipN,用 npwm 查看路数;向 export 写入编号(I.MX6U 为 0)导出,生成 pwm0 目录;在 pwm0 下配置 period、duty_cycle(纳秒),可选配置 polarity;最后写 enable=1 使能;用完向 unexport 写入编号删除。程序中通常先 access() 判断是否已导出,未导出则先写 export。
答:看门狗是一个可在一定时间内被复位的计数器,启动后自动计数;若在超时前未被复位(喂狗),计数器溢出就会对 CPU 产生复位信号使系统重启。系统正常时程序按时喂狗,程序跑飞则不喂狗、系统自动复位,从而提高可靠性。至于 open 之后要先关闭:因为打开看门狗设备后即使程序没开启计时器,close() 关闭设备时看门狗也会自动启动,所以打开后应先用 WDIOC_SETOPTIONS + WDIOS_DISABLECARD 停止计时,待一切设置完成后再开启,避免在配置过程中意外触发。
答:WDIOC_GETSUPPORT 获取设备支持的功能(如是否支持设置超时、是否支持喂狗);WDIOC_SETOPTIONS 配合 WDIOS_ENABLECARD/WDIOS_DISABLECARD 开启或关闭看门狗计时器;WDIOC_SETTIMEOUT/WDIOC_GETTIMEOUT 设置/获取超时时间(秒);WDIOC_KEEPALIVE 执行喂狗、重置计时器。这些指令都需要先用 open("/dev/watchdog", O_RDWR) 得到文件描述符。
Device or resource busy?答:启动看门狗并设置超时(如 2 秒),程序正常运行时会不断喂狗。按 Ctrl+C 结束程序,即停止喂狗,但看门狗计时器仍在计时,超时后就会复位系统;内核会打印 watchdog watchdog0: watchdog did not stop!。提示 Device or resource busy 是因为看门狗默认已被系统功能(/etc/init.d/watchdog.sh)占用,可在该脚本开头加 exit 0 并重启开发板后再测试,测试完记得去掉。
内容来源:《I.MX6U嵌入式Linux C应用编程指南》第二十四章 PWM应用编程、第二十七章 看门狗应用编程;例程源码 24_pwm/pwm.c、27_watchdog/watchdog_test.c