从蜂鸣器到电机:Linux PWM驱动开发实战指南

在嵌入式系统中,PWM(脉冲宽度调制)技术就像一位隐形的魔术师,通过精确控制脉冲宽度,让硬件设备"活"起来——从蜂鸣器发出不同音调,到电机实现无级变速,再到LED灯完成平滑调光。本文将带你深入Linux内核,从设备树配置到驱动开发,构建一个完整的PWM控制解决方案。

1. PWM技术基础与Linux框架

PWM本质上是通过调节脉冲信号的占空比(高电平时间占总周期的比例)来模拟不同电压水平的技术。在Linux内核中,PWM子系统提供了一套标准化的接口,使得驱动开发者可以专注于业务逻辑而非硬件差异。

关键参数解析:

  • period:完整周期时间(纳秒),如1000000ns对应1kHz频率
  • duty_cycle:有效电平持续时间(纳秒),决定占空比
  • polarity:信号极性(normal/inversed),控制有效电平是高还是低
  • enable:启用/禁用PWM输出
// 典型PWM波形配置示例
struct pwm_state {
    u64 period;
    u64 duty_cycle;
    enum pwm_polarity polarity;
    bool enabled;
};

注意:不同SoC厂商的PWM控制器实现可能有差异,但内核API保持统一,这是Linux设备驱动的核心优势。

2. 硬件描述:设备树配置详解

设备树(Device Tree)是连接硬件与软件的桥梁。以NXP i.MX6ULL处理器为例,配置PWM控制器的典型节点如下:

pwm: pwm@02080000 {
    compatible = "fsl,imx6ul-pwm", "fsl,imx27-pwm";
    reg = <0x02080000 0x4000>;
    interrupts = <GIC_SPI 83 IRQ_TYPE_LEVEL_HIGH>;
    clocks = <&clks IMX6UL_CLK_PWM1>,
           <&clks IMX6UL_CLK_PWM1>;
    clock-names = "ipg", "per";
    #pwm-cells = <2>;
    status = "okay";
};

beeper {
    compatible = "pwm-beeper";
    pwms = <&pwm 0 50000 0>;  // 使用PWM0通道,周期50ms
    pinctrl-names = "default";
    pinctrl-0 = <&pwm0_pins>;
};

关键配置项:

属性说明示例值
compatible驱动匹配字符串"fsl,imx6ul-pwm"
reg寄存器物理地址范围<0x02080000 0x4000>
#pwm-cells指定PWM说明符单元格数2(通道号+周期)
pwms消费者设备引用PWM<&pwm 0 50000 0>

提示:使用dtc -I dtb -O dts -o output.dts /proc/device-tree可反编译当前运行的设备树进行调试。

3. 内核配置与框架集成

确保内核已启用PWM子系统支持:

# 在Linux源码目录执行
make menuconfig

导航至:

Device Drivers  --->
    [*] Pulse-Width Modulation (PWM) Support  --->
        <*>   Freescale i.MX PWM support
        [*]   PWM sysfs interface

关键配置选项:

  • CONFIG_PWM=y:启用PWM框架核心
  • CONFIG_PWM_SYSFS=y:提供用户空间sysfs接口
  • SoC特定驱动(如CONFIG_PWM_IMX)

编译并更新内核后,可在/sys/class/pwm/看到PWM控制器:

# 查看可用PWM通道
ls /sys/class/pwm/pwmchip0/
# 导出PWM通道0
echo 0 > /sys/class/pwm/pwmchip0/export

4. PWM驱动开发实战

下面实现一个支持ioctl控制的混杂设备驱动:

#include <linux/module.h>
#include <linux/pwm.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>

#define PWM_CMD_SET		_IO('P', 0)
#define PWM_CMD_DISABLE	_IO('P', 1)

struct pwm_device *pwm;
static int pwm_major;

struct pwm_cmd {
    u32 period_ns;
    u32 duty_ns;
};

static long pwm_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
    struct pwm_cmd param;
    
    switch (cmd) {
    case PWM_CMD_SET:
        if (copy_from_user(&param, (void __user *)arg, sizeof(param)))
            return -EFAULT;
        
        pwm_config(pwm, param.duty_ns, param.period_ns);
        pwm_enable(pwm);
        break;
        
    case PWM_CMD_DISABLE:
        pwm_disable(pwm);
        break;
        
    default:
        return -ENOTTY;
    }
    return 0;
}

static const struct file_operations pwm_fops = {
    .owner = THIS_MODULE,
    .unlocked_ioctl = pwm_ioctl,
};

static struct miscdevice pwm_misc = {
    .minor = MISC_DYNAMIC_MINOR,
    .name = "pwm_ctrl",
    .fops = &pwm_fops,
};

static int __init pwm_drv_init(void)
{
    pwm = pwm_get(NULL, "pwm0");
    if (IS_ERR(pwm)) {
        pr_err("Failed to get PWM device\n");
        return PTR_ERR(pwm);
    }
    
    return misc_register(&pwm_misc);
}

static void __exit pwm_drv_exit(void)
{
    pwm_disable(pwm);
    pwm_put(pwm);
    misc_deregister(&pwm_misc);
}

module_init(pwm_drv_init);
module_exit(pwm_drv_exit);
MODULE_LICENSE("GPL");

关键API解析:

  • pwm_get():获取PWM设备引用
  • pwm_config():配置周期和占空比
  • pwm_enable/disable():启停PWM输出
  • pwm_put():释放PWM设备

5. 用户空间控制与调试

驱动加载后,可通过字符设备或sysfs接口控制PWM:

字符设备控制示例:

#include <fcntl.h>
#include <sys/ioctl.h>

int main() {
    int fd = open("/dev/pwm_ctrl", O_RDWR);
    
    struct {
        u32 period;
        u32 duty;
    } param = {
        .period = 1000000, // 1ms周期
        .duty = 300000     // 30%占空比
    };
    
    ioctl(fd, PWM_CMD_SET, &param);
    sleep(5);
    ioctl(fd, PWM_CMD_DISABLE, 0);
    
    close(fd);
    return 0;
}

sysfs调试技巧:

# 实时监控PWM状态
watch -n 0.1 'cat /sys/class/pwm/pwmchip0/pwm0/{period,duty_cycle,enable}'

# 动态调整占空比
echo 700000 > /sys/class/pwm/pwmchip0/pwm0/duty_cycle

6. 进阶:多设备管理与性能优化

在实际项目中,可能需要管理多个PWM设备:

struct pwm_manager {
    struct pwm_device *motor;
    struct pwm_device *led;
    struct mutex lock;
};

static int pwm_group_set(struct pwm_manager *mgr, 
                        u32 motor_duty, u32 led_duty)
{
    int ret;
    
    mutex_lock(&mgr->lock);
    
    ret = pwm_config(mgr->motor, motor_duty, 1000000);
    if (ret) goto out;
    
    ret = pwm_config(mgr->led, led_duty, 500000);
    if (ret) goto out;
    
    pwm_enable(mgr->motor);
    pwm_enable(mgr->led);
    
out:
    mutex_unlock(&mgr->lock);
    return ret;
}

性能优化建议:

  1. 使用pwm_apply_state()替代分开的配置函数
  2. 对于高频PWM,考虑DMA传输波形数据
  3. 启用硬件加速(如某些SoC支持PWM波形缓存)

在最近的一个智能风扇项目中,我们通过精确控制4路PWM输出,实现了风速的无级调节和噪音优化。实际测试表明,Linux PWM框架在响应延迟和精度上完全满足工业级应用需求,关键是要合理配置硬件定时器分频参数。

更多推荐