
清空记录
历史记录
取消
清空记录
历史记录

PWM的基本概率与工作原理
PWM(Pulse Width Modulation,脉冲宽度调制)是一种数字信号技术,通过改变脉冲的占空比来控制平均电压。
参数 | 说明 | 典型应用 |
周期 (T) | 一个完整脉冲的时间 | 控制精度 |
占空比 | 高电平时间占比 | LED 亮度、电机速度 |
频率 | 每秒脉冲数 | 避免人耳可闻噪声 |
•Fan(风扇):PWM 调速
•Backlight(背光):PWM 调光
•Speaker:PWM 音频(部分实现)
•IR Remote(红外遥控):PWM 红外发射 / GPIO 红外接收
•pwm0 ~ pwm7:常规PWM(支持输出、Capture、IR发射)
•pwm1_6ch_*:6通道PWM(用于背光等)
•高级功能:波形发生器、频率计、Counter 输入、Capture 模式、IR发射
不同占空比直观展示:
•25% 占空比:██░░░░░░
•50% 占空比:████░░░░
•75% 占空比:██████░░
•100% 占空比:████████
用户空间通过ioctl/write操作PWM子系统,PWM子系统配置控制器寄存器,控制器输出波形至外部设备;外部设备在Capture模式下可将信号参数回传给控制器,GPIO IR接收头则通过rc-core子系统处理信号。附整体架构图
PWM设备树配置
需编辑触觉智能Purple Pi OH的SDK的ido-evb7609-v1a-mipi.dtsi,配置代码如下:
&pwm1_6ch_5 {
status = "okay";
};配置代码如下:
/ {
/* 背光设备 */
backlight: backlight {
compatible = "pwm-backlight";
pwms = <&pwm1_6ch_5 0 25000 0>; /* PWM, channel, period(ns), polarity */
/* 亮度等级表 */
brightness-levels = <
0 20 20 21 21 22 22 23
23 24 24 25 25 26 26 27
/* ... 更多等级 ... */
248 249 250 251 252 253 254 255
>;
default-brightness-level = <200>;
/* 电源控制 */
enable-gpios = <&gpio4 RK_PA1 GPIO_ACTIVE_HIGH>;
};
/* 风扇 PWM 控制 */
fan: pwm-fan {
compatible = "pwm-fan";
pwms = <&pwm2 0 40000 0>; /* 25KHz PWM 频率 */
/* 温度与转速曲线 */
cooling-levels = <0 100 150 200 255>;
#cooling
-cells = <2>;
};
};配置代码如下:
&pinctrl {
pwm1 {
pwm1m0_pins: pwm1m0-pins {
rockchip,pins =
<1 RK_PB6 2 &pcfg_pull_none>;
};
};
pwm2 {
pwm2m0_pins: pwm2m0-pins {
rockchip,pins =
<1 RK_PB7 2 &pcfg_pull_none>;
};
};
};
PWM脉冲捕获
PWM Capture 模式将PWM引脚从输出切换为输入,用于捕获外部信号的周期和占空比,是 PWM 控制器的双向能力。
RK3576的PWM控制器(v4版本)实现Capture流程:
1.将 PWM 引脚配置为输入模式(PWM_IN_SEL)
2.使能边沿检测中断(CAP_LPR_INT 低电平周期中断、CAP_HPR_INT 高电平周期中断)
3.硬件自动将高/低电平时间分别写入 HPC(High Period Counter)和 LPC(Low Period Counter)寄存器
4.驱动读取寄存器并计算为纳秒值
1.将 PWM 引脚配置为输入模式(PWM_IN_SEL)
2.使能边沿检测中断(CAP_LPR_INT 低电平周期中断、CAP_HPR_INT 高电平周期中断)
3.硬件自动将高/低电平时间分别写入 HPC(High Period Counter)和 LPC(Low Period Counter)寄存器
4.驱动读取寄存器并计算为纳秒值
配置代码如下:
#include
<linux/pwm.h>
struct pwm_capture {
unsigned int period; /* 捕获到的周期 (ns) */
unsigned int duty_cycle; /* 捕获到的占空比 (ns) */
};
/* 捕获外部 PWM 信号 */
int pwm_capture(struct pwm_device *pwm, struct pwm_capture *result, unsigned long timeout);配置代码如下:
#include <linux/pwm.h>
#include <linux/module.h>
#include <linux/platform_device.h>
struct my_pwm_capture_dev {
struct pwm_device *pwm;
struct device *dev;
};
static int my_capture_probe(struct platform_device *pdev)
{
struct my_pwm_capture_dev *priv;
int ret;
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->dev = &pdev->dev;
/* 获取 PWM 设备(与 PWM 输出共用节点) */
priv->pwm = devm_pwm_get(&pdev->dev, NULL);
if (IS_ERR(priv->pwm)) {
ret = PTR_ERR(priv->pwm);
if (ret != -EPROBE_DEFER)
dev_err(&pdev->dev, "Failed to get PWM: %d\n", ret);
return ret;
}
platform_set_drvdata(pdev, priv);
dev_info(&pdev->dev, "PWM capture device initialized\n");
return 0;
}
/* 读取捕获结果 */
static int my_capture_read(struct my_pwm_capture_dev *priv,
unsigned int *period_ns,
unsigned int *duty_ns)
{
struct pwm_capture result;
int ret;
/* 捕获外部信号,超时 100ms */
ret = pwm_capture(priv->pwm, &result, 100);
if (ret) {
dev_err(priv->dev, "Failed to capture PWM: %d\n", ret);
return ret;
}
*period_ns = result.period;
*duty_ns = result.duty_cycle;
dev_info(priv->dev, "Captured: period=%uns, duty=%uns\n",
result.period, result.duty_cycle);
return 0;
}
/* 计算频率和占空比百分比 */
static void my_capture_calc_freq_duty(unsigned int period_ns, unsigned int duty_ns,
unsigned int *freq_hz, unsigned int *duty_pct)
{
if (period_ns == 0)
return;
*freq_hz = 1000000000 / period_ns;
*duty_pct = (duty_ns * 100) / period_ns;
}
static int my_capture_remove(struct platform_device *pdev)
{
return 0;
}
static const struct of_device_id my_capture_of_match[] = {
{ .compatible = "mycompany,my-pwm-capture", },
{ }
};
static struct platform_driver my_capture_driver = {
.probe = my_capture_probe,
.remove = my_capture_remove,
.driver = {
.name = "my-pwm-capture",
.of_match_table = my_capture_of_match,
},
};
module_platform_driver(my_capture_driver);
MODULE_LICENSE("GPL");要点 | 说明 |
PWM 不能同时输出和捕获 | Capture模式下PWM必须处于禁用状态 |
超时设置 | 建议 100ms,过短可能导致无中断触发而超时 |
时钟精度 | 捕获精度取决于PWM 控制器的输入时钟频率 |
pinctrl 复用 | Capture 复用同一 PWM 引脚,无需额外pinctrl 配置 |
RK3576 支持 | PWM v3/v4 均支持Capture 模式 |
红外遥控使用 NEC 协议传输数据,载波频率通常为 38KHz。NEC 协议帧结构:引导码(9ms高+4.5ms低) + 地址码(8bit) + 地址反码(8bit) + 命令码(8bit) + 命令反码(8bit)
逻辑 | 编码方式 |
逻辑 "0" | 560μs 高 + 560μs 低(总 1.12ms) |
逻辑 "1" | 560μs 高 + 1.69ms 低(总 2.25ms) |
重复码 | 9ms 高 + 2.25ms 低 + 560μs 高 |
Linux 通过 rc-core 子系统统一管理红外遥控,支持 NEC、NEC32、RC-5、RC-6、Sony SIRC、JVC、Sharp、XMP 等协议。
设备树配置代码如下:
/ {
/* GPIO 红外接收头 */
ir_receiver: ir-receiver {
compatible = "gpio-ir-receiver";
gpios = <&gpio0 RK_PA5 GPIO_ACTIVE_LOW>;
linux,rc-map-name = "rc-rc6-mce"; /* 使用预定义的按键映射 */
linux,autosuspend-period = <125>; /* 自动挂起延迟 (ms) */
};
};属性 | 说明 | 必填 |
compatible | 必须为"gpio-ir-receiver" | 是 |
gpios | 红外接收头连接的 GPIO | 是 |
linux,rc-map-name | 按键映射表名称 | 否(默认使用rc-empty-map) |
linux,autosuspend-period | 自动挂起延迟(ms) | 否 |
确保以下内核配置已启用:
CONFIG_RC_CORE=y
CONFIG_GPIO_IR_RECEIVER=m # 或 y
CONFIG_RC_DECODERS=y # 包含 NEC、RC5、RC6 等解码器
CONFIG_RC_MAP=y创建/etc/rc_keymaps/my_remote.map,配置代码如下:
# 按键映射表格式:scancode keycode
# scancode: 遥控器发送的原始 NEC 码
# keycode: 映射到的 Linux 按键
0x12ff01 KEY_POWER
0x12ff02 KEY_VOLUMEUP
0x12ff03 KEY_VOLUMEDOWN
0x12ff04 KEY_UP
0x12ff05 KEY_DOWN
0x12ff06 KEY_LEFT
0x12ff07 KEY_RIGHT
0x12ff08 KEY_ENTER加载映射表:
ir-keytable -c # 清除现有映射
ir-keytable -p NEC -p RC-6 # 启用解码协议
ir-keytable -w /etc/rc_keymaps/my_remote.map # 加载映射配置代码如下:
# 查看红外设备
ir-keytable
# 输出示例:
# Found /sys/class/rc/rc0/ with:
# Name: gpio_ir_recv
# Driver: gpio-ir-recv
# Default keymap: rc-rc6-mce
# Input device: /dev/input/event2
# Enabled protocols: nec
# 实时监控按键事件
ir-keytable -t
# 输出示例:
# 1234.567890: event type EV_MSC, evscancode = 0x12ff01
# 1234.567891: event type EV_KEY, KEY_POWER(116) pressed
# 1234.567891: event type EV_KEY, KEY_POWER(116) released
# 查看原始事件
cat /dev/input/event2 | hexdump
# 使用 evtest 测试
evtest /dev/input/event2RK3576的PWM 控制器内置硬件IR发射引擎,支持NEC协议的自动调制,无需CPU干预时序。IR 硬件引擎工作流程如图所示:
1.用户写入 NEC 码数据(7 字节格式)
2.PWM 控制器通过 IR_TRANS_CTRL0 寄存器启动发射
3.硬件自动产生 38KHz 载波并按 NEC 协议调制
4.发射完成后触发IR_TRANS_END_INT 中断
1.用户写入 NEC 码数据(7 字节格式)
2.PWM 控制器通过 IR_TRANS_CTRL0 寄存器启动发射
3.硬件自动产生 38KHz 载波并按 NEC 协议调制
4.发射完成后触发IR_TRANS_END_INT 中断
IR 相关寄存器:
寄存器 | 偏移 | 功能 |
IR_TRANS_ARBITER | 0x180 | 通道仲裁 |
IR_TRANS_CTRL0 | 0x184 | 发射控制 |
IR_TRANS_PRE | 0x188 | 引导码预装值 |
IR_TRANS_SPRE | 0x18C | 重复码预装值 |
IR_TRANS_LD | 0x190 | 数据低电平周期 |
IR_TRANS_HD | 0x194 | 数据高电平周期 |
IR_TRANS_DATA_VALUE | 0x1A0 | NEC 码数据值 |
INT_EN | - | 中断使能 |
设备树配置代码如下:
&pwm2 {
status = "okay";
pinctrl-0 = <&pwm2m0_pins>;
rockchip,pwm-ir-transmit; /* 使能 IR 发射功能 */
};属性 | 说明 | 必填 |
rockchip,pwm-ir-transmit | 使能 PWM 硬件 IR 发射 | 是 |
内核配置代码如下:
CONFIG_RC_CORE=y # 必须启用(IR 发射依赖 rc-core)
CONFIG_PWM_ROCKCHIP=y # 或 mIR 发射使用 7 字节缓冲区,格式如下:
txbuf[0]: NEC 引导码低电平时间 (μs),通常 9000
txbuf[1]: NEC 引导码高电平时间 (μs),通常 4500
txbuf[2]: 重复码高电平时间 (μs),通常 2250
txbuf[3]: 逻辑 "0" 和 "1" 低电平时间 (μs),通常 560
txbuf[4]: 逻辑 "0" 高电平时间 (μs),通常 560
txbuf[5]: 逻辑 "1" 高电平时间 (μs),通常 1690
txbuf[6]: 32位 NEC 数据32位数据格式(txbuf[6]):
bit[31:24] bit[23:16] bit[15:8] bit[7:0]
命令反码 命令码 地址反码 地址码例如:地址 0x01,命令 0x10
•地址码 = 0x01
•地址反码 = 0xFE(~0x01)
•命令码 = 0x10
•命令反码 = 0xEF(~0x10)
•txbuf[6] = 0xEF10FE01
配置代码如下:
#include <linux/pwm.h>
#include <linux/rc-core.h>
#include <linux/module.h>
struct my_pwm_ir_dev {
struct rc_dev *rcdev;
struct platform_device *pdev;
};
/* NEC 协议红外发射 */
static int my_pwm_ir_tx(struct rc_dev *dev, unsigned int *txbuf, unsigned int count)
{
struct my_pwm_ir_dev *priv = dev->priv;
struct pwm_device *pwm;
int ret;
if (count < 2)
return -EINVAL;
pwm = dev->priv;
if (!pwm)
return -ENODEV;
/* 通过 PWM IR 硬件发射 */
/* 注意:实际发射由 pwm-rockchip 驱动的
* rockchip_pwm_ir_transmit 处理 */
ret = pwm_ir_transmit(pwm, txbuf, count);
if (ret < 0)
dev_err(&dev->dev, "IR transmit failed: %d\n", ret);
return ret;
}
static int my_pwm_ir_probe(struct platform_device *pdev)
{
struct my_pwm_ir_dev *priv;
struct rc_dev *rcdev;
int ret;
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
/* 注册 rc_dev */
rcdev = devm_rc_allocate_device(&pdev->dev, RC_DRIVER_IR_RAW_TX);
if (!rcdev)
return -ENOMEM;
rcdev->driver_name = "my-pwm-ir";
rcdev->device_name = "PWM IR Transmitter";
rcdev->tx_ir = my_pwm_ir_tx;
rcdev->priv = priv;
rcdev->allowed_protocols = RC_BIT_NEC | RC_BIT_NECX | RC_BIT_NEC32;
ret = devm_rc_register_device(&pdev->dev, rcdev);
if (ret) {
dev_err(&pdev->dev, "Failed to register rc device: %d\n", ret);
return ret;
}
priv->rcdev = rcdev;
priv->pdev = pdev;
dev_info(&pdev->dev, "PWM IR transmitter registered\n");
return 0;
}
static const struct of_device_id my_pwm_ir_of_match[] = {
{ .compatible = "mycompany,my-pwm-ir", },
{ }
};
static struct platform_driver my_pwm_ir_driver = {
.probe = my_pwm_ir_probe,
.driver = {
.name = "my-pwm-ir",
.of_match_table = my_pwm_ir_of_match,
},
};
module_platform_driver(my_pwm_ir_driver);
MODULE_LICENSE("GPL");配置代码如下:
# 使用 ir-ctl 发送 NEC 码
# 格式:ir-ctl --transmit=<device> --send="protocol:scancode"
# 发送 NEC POWER 键(地址 0x01,命令 0x10)
ir-ctl -d /dev/lirc0 --send=nec:0x12ff01
# 查看 IR 设备
ir-ctl -i
# 输出示例:
# lirc0: PWM IR Transmitter
# driver: my-pwm-ir
# Features: send
# Allowed protocols: NEC NECX NEC32
# 发送重复码(长按效果)
ir-ctl -d /dev/lirc0 --send=nec:0x12ff01 --send=nec:0x12ff01 --send=nec:0x12ff01
# 使用 evtest 验证(发射 + 接收回路测试)
ir-ctl -d /dev/lirc0 --send=nec:0x12ff01
ir-keytable -t # 在另一终端中观察要点 | 说明 |
CONFIG_RC_CORE 必须启用 | IR 发射依赖 rc-core 子系统 |
PWM 输出和 IR 互斥 | IR 发射时 PWM 不能处于输出模式 |
pinctrl 配置 | 确保 PWM 引脚连接到 IR LED |
载波频率 | 硬件自动使用 PWM 配置的载波频率(通常 38KHz) |
超时处理 | 驱动自动等待发射完成,超时 3 倍帧时间 |
产品简介
