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

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.驱动读取寄存器并计算为纳秒值
配置代码如下:
#includestruct 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#include #include 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=yCONFIG_GPIO_IR_RECEIVER=m # 或 yCONFIG_RC_DECODERS=y # 包含 NEC、RC5、RC6 等解码器CONFIG_RC_MAP=y
创建/etc/rc_keymaps/my_remote.map,配置代码如下:
# 按键映射表格式:scancode keycode# scancode: 遥控器发送的原始 NEC 码# keycode: 映射到的 Linux 按键0x12ff01 KEY_POWER0x12ff02 KEY_VOLUMEUP0x12ff03 KEY_VOLUMEDOWN0x12ff04 KEY_UP0x12ff05 KEY_DOWN0x12ff06 KEY_LEFT0x12ff07 KEY_RIGHT0x12ff08 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/event2
RK3576的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 # 或 m
IR 发射使用 7 字节缓冲区,格式如下:
txbuf[0]: NEC 引导码低电平时间 (μs),通常 9000txbuf[1]: NEC 引导码高电平时间 (μs),通常 4500txbuf[2]: 重复码高电平时间 (μs),通常 2250txbuf[3]: 逻辑 "0" 和 "1" 低电平时间 (μs),通常 560txbuf[4]: 逻辑 "0" 高电平时间 (μs),通常 560txbuf[5]: 逻辑 "1" 高电平时间 (μs),通常 1690txbuf[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#include #include 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=--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:0x12ff01ir-keytable -t # 在另一终端中观察
要点 | 说明 |
CONFIG_RC_CORE 必须启用 | IR 发射依赖 rc-core 子系统 |
PWM 输出和 IR 互斥 | IR 发射时 PWM 不能处于输出模式 |
pinctrl 配置 | 确保 PWM 引脚连接到 IR LED |
载波频率 | 硬件自动使用 PWM 配置的载波频率(通常 38KHz) |
超时处理 | 驱动自动等待发射完成,超时 3 倍帧时间 |
产品简介
