【RK3568 PWM 子系统(SG90)驱动开发详解】
RK3568 PWM 子系统(SG90)驱动开发详解
- 一、PWM 基础知识
-
- 1. 基本概念
- 2. 应用场景
- 二、Linux PWM 子系统架构
-
- 1. 架构层次图
- 2. 各层次详细说明
- 三、以 SG90 舵机为例的驱动实现
-
- 1. SG90 舵机基本原理
- 2. 硬件连接
- 3. 设备树配置
- 4. 驱动代码实现
- 四、注意事项
- 五、总结
PWM(脉冲宽度调制)是一种常用的模拟控制技术,广泛应用于 LED 调光、电机控制、电源管理等场景。本文将深入探讨 RK3568 平台的 PWM 子系统,包括基础知识、子系统架构、驱动开发实践以及 SG90 舵机控制实例。
一、PWM 基础知识
1. 基本概念
- PWM 信号: 一种方波信号,通过调节 “占空比” 来控制平均电压
- 频率: 每秒完成的周期数(Hz)
- 占空比: 高电平时间占整个周期的比例(0%-100%)
2. 应用场景
- LED 调光: 调节 LED 亮度
- 电机控制: 控制电机转速和方向
- 电源管理: DC-DC 转换器中的开关控制
- 音频输出: 数字音频转换为模拟信号
二、Linux PWM 子系统架构
Linux PWM 子系统采用分层设计,将用户空间与硬件实现分离,通过标准化接口实现对不同 PWM 控制器的统一管理。
1. 架构层次图
┌─────────────────────────────────────────────────────────────┐│ 用户空间 ││ ┌───────────────┐ ┌────────────────┐ ┌────────────────┐ ││ │ 应用程序 │ │ sysfs接口 │ │ libpwm库 │ ││ │ (控制舵机、 │ │ (直接操作文件) │ │ (高级API封装) │ ││ │ 调节LED亮度) │ └────────────────┘ └────────────────┘ │└───────────────────┬─────────────────────────────────────────┘ │ ▼┌─────────────────────────────────────────────────────────────┐│ 内核空间 ││ ┌───────────────────────────────────┐ ┌─────────────────┐ ││ │ PWM核心层 │ │ 设备树/ACPI │ ││ │ (pwm_core.c, pwm_sysfs.c) │ │ (硬件描述) │ ││ └───────────────────────────┬───────┘ └─────────────────┘ ││ │ ││ ▼ ││ ┌─────────────────────────────────────────────────────┐ ││ │ PWM控制器驱动层 │ ││ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ ││ │ │ 通用驱动 │ │ 平台特定 │ │ 厂商驱动 │ │ ││ │ │ (pwm-xilinx)│ │ (pwm-rk3568)│ │ (pwm-stm32) │ │ ││ │ └─────────────┘ └─────────────┘ └─────────────┘ │ ││ └─────────────────────────────────────────────────────┘ ││ │ ││ ▼ │└─────────────────────────────────────────────────────────────┘ │ ▼┌─────────────────────────────────────────────────────────────┐│ 硬件层 ││ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌────┐ ││ │ PWM控制器 │ │ GPIO PWM │ │ 定时器PWM │ │... │ ││ │ (专用模块) │ │ (软件模拟) │ │ (复用定时器) │ │ │ ││ └─────────────┘ └─────────────┘ └─────────────┘ └────┘ │└─────────────────────────────────────────────────────────────┘
2. 各层次详细说明
用户空间
用户空间提供了与 PWM 子系统交互的接口:
- 应用程序:直接使用 PWM 功能的软件,如 LED 调光控制程序、舵机控制程序
- sysfs 接口:Linux 内核提供的文件系统接口,位于
/sys/class/pwm/
导出 PWM 通道:echo N > /sys/class/pwm/pwmchipN/export
设置周期:echo period_ns > /sys/class/pwm/pwmchipN/pwmM/period
设置占空比:echo duty_ns > /sys/class/pwm/pwmchipN/pwmM/duty_cycle
启用 / 禁用:echo 1/0 > /sys/class/pwm/pwmchipN/pwmM/enable
- libpwm 库:对 sysfs 接口的封装,提供更高级的 API
内核空间 - PWM 核心层
PWM 核心层提供统一的框架和 API,负责:
- PWM 设备管理:注册、注销 PWM 控制器
- 抽象接口定义:定义标准的 PWM 操作函数集
- sysfs 接口实现:创建和管理 PWM 相关的 sysfs 文件
- 资源分配:管理 PWM 通道的分配和释放
关键数据结构:
struct pwm_chip { struct device *dev; /* 关联的设备 */ const struct pwm_ops *ops; /* 操作函数集 */ unsigned int npwm; /* PWM通道数量 */ struct list_head list; /* 内核中的PWM控制器链表 */ /* 其他字段... */};struct pwm_ops { int (*request)(struct pwm_chip *chip, struct pwm_device *pwm); void (*free)(struct pwm_chip *chip, struct pwm_device *pwm); int (*config)(struct pwm_chip *chip, struct pwm_device *pwm, int duty_ns, int period_ns); int (*enable)(struct pwm_chip *chip, struct pwm_device *pwm); void (*disable)(struct pwm_chip *chip, struct pwm_device *pwm); /* 其他可选操作... */};
内核空间 - PWM 控制器驱动层
这一层实现具体硬件的 PWM 控制器驱动,将核心层的抽象操作映射到实际硬件:
- 通用驱动:适用于多种平台的通用 PWM 控制器驱动
- 平台特定驱动:针对特定 SOC 平台的 PWM 控制器驱动(如 RK3568、Xilinx 等)
- 厂商驱动:特定厂商芯片的 PWM 控制器驱动(如 STM32、TI 等)
硬件层
PWM 功能的物理实现:
- 专用 PWM 控制器:独立的 PWM 硬件模块,通常包含多个通道
- GPIO PWM:通过软件控制 GPIO 引脚模拟 PWM 信号(精度较低)
- 定时器 PWM:复用系统定时器实现 PWM 功能
三、以 SG90 舵机为例的驱动实现
SG90 是一款常用的小型舵机,通过 PWM 信号控制角度。下面基于 RK3568 的 PWM 子系统,实现完整的 SG90 舵机驱动。
1. SG90 舵机基本原理
- 控制信号:标准 PWM 频率 50Hz(周期 20ms)
- 角度控制:通过调整 PWM 占空比控制舵机角度
0.5ms 脉冲 → 约 0 度
1.5ms 脉冲 → 约 90 度
2.5ms 脉冲 → 约 180 度
2. 硬件连接
3. 设备树配置
准备工作:
查看底板原理图与数据手册得知:GPIO4_C5
可以做串口uart9_TX_M1
也可以做PWM12_M1
引脚复用定义在kernel/arch/arm64/boot/dts/rockchip/rk3568-pinctrl.dtsi
编写sg90设备树节点
注意:需要先将uart9禁用
sg90_servo: sg90-servo {compatible = \"sg90-servo\";pwms = <&pwm12 0 20000000 1>; /* PWM12, 周期20ms(20000000ns) */min-pulse-width = <500000>; /* 最小脉冲宽度0.5ms(500000ns) */max-pulse-width = <2500000>; /* 最大脉冲宽度2.5ms(2500000ns) */min-angle = <0>; /* 最小角度0度 */max-angle = <180>; /* 最大角度180度 */initial-angle = <90>; /* 初始角度90度 */};
&pwm12 {status = \"okay\";pinctrl-names = \"active\";pinctrl-0 = <&pwm12m1_pins>;};
使用sysfs文件系统测试pwm是否正常工作:
cd /sys/class/pwmcat /sys/kernel/debug/pwm //查看pwm信息cd pwmchip3/echo 0 > export //导出pwm12cd pwm0echo 20000000 > period //设置周期echo 2000000 > duty_cycle //设置高电平时间echo normal > polarity //设置极性,有normal或inversedecho 1 > enable //打开pwmecho 0 > enable //关闭pwm
通过上述指令能够看到sg90角度发生变化,表示已经配置成功了。
4. 驱动代码实现
#include #include #include #include #include #include #include #include #include #include #include /* 驱动名称和设备ID */#define SG90_NAME \"sg90_servo\"#define SG90_CLASS \"sg90\"/* 角度转脉冲宽度的计算公式 */#define ANGLE_TO_PULSE(angle, min_p, max_p, max_a) \\ (min_p + ((angle) * ((max_p) - (min_p))) / (max_a))struct sg90_servo { struct device *dev; struct pwm_device *pwm; int period_ns; /* PWM周期(纳秒) */ int min_pulse_ns; /* 最小脉冲宽度(纳秒) */ int max_pulse_ns; /* 最大脉冲宽度(纳秒) */ int min_angle; /* 最小角度(度) */ int max_angle; /* 最大角度(度) */ int current_angle; /* 当前角度(度) */ struct cdev cdev; /* 字符设备 */ dev_t devt; /* 设备号 */ struct class *class; /* 设备类 */};static struct sg90_servo *sg90_dev;/* 设置舵机角度 */static int sg90_set_angle(struct sg90_servo *servo, int angle){ int pulse_width_ns; int ret; /* 角度范围检查 */ if (angle < servo->min_angle || angle > servo->max_angle) { dev_err(servo->dev, \"Angle %d out of range [%d, %d]\\n\", angle, servo->min_angle, servo->max_angle); return -EINVAL; } /* 计算对应的脉冲宽度 */ pulse_width_ns = ANGLE_TO_PULSE(angle, servo->min_pulse_ns, servo->max_pulse_ns, servo->max_angle); printk(KERN_INFO \"sg90_set_angle pulse_width_ns = %d\", pulse_width_ns); dev_dbg(servo->dev, \"Setting angle %d degrees, pulse width %d ns\\n\", angle, pulse_width_ns); /* 设置PWM占空比 */ ret = pwm_config(servo->pwm, pulse_width_ns, servo->period_ns); if (ret) { dev_err(servo->dev, \"Failed to configure PWM: %d\\n\", ret); return ret; } /* 更新当前角度 */ servo->current_angle = angle; return 0;}/* 文件操作:打开设备 */static int sg90_open(struct inode *inode, struct file *filp){ struct sg90_servo *servo = container_of(inode->i_cdev,struct sg90_servo, cdev); filp->private_data = servo;printk(KERN_INFO \"sg90_open\"); return 0;}/* 文件操作:释放设备 */static int sg90_release(struct inode *inode, struct file *filp){printk(KERN_INFO \"sg90_release\"); return 0;}/* 文件操作:读取当前角度 */static ssize_t sg90_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos){ struct sg90_servo *servo = filp->private_data; char buffer[20]; int len; len = snprintf(buffer, sizeof(buffer), \"%d\\n\", servo->current_angle); if (count < len) return -EINVAL; if (copy_to_user(buf, buffer, len)) return -EFAULT; *f_pos += len; return len;}/* 文件操作:设置角度 */static ssize_t sg90_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos){printk(KERN_INFO \"sg90_write buf = %s\", buf); struct sg90_servo *servo = filp->private_data; char buffer[20]; int angle, ret; if (count >= sizeof(buffer)) return -EINVAL; if (copy_from_user(buffer, buf, count)) return -EFAULT; buffer[count] = \'\\0\'; /* 解析角度值 */ if (sscanf(buffer, \"%d\", &angle) != 1) return -EINVAL; /* 设置角度 */ ret = sg90_set_angle(servo, angle); if (ret) return ret; return count;}/* 文件操作表 */static const struct file_operations sg90_fops = { .owner = THIS_MODULE, .open = sg90_open, .release = sg90_release, .read = sg90_read, .write = sg90_write,};/* 驱动探测函数 */static int sg90_probe(struct platform_device *pdev){ struct device *dev = &pdev->dev; struct sg90_servo *servo; int ret; int initial_angle; /* 分配并初始化驱动数据结构 */ servo = devm_kzalloc(dev, sizeof(*servo), GFP_KERNEL); if (!servo) return -ENOMEM; servo->dev = dev; platform_set_drvdata(pdev, servo);printk(KERN_INFO \"sg90_probe\"); /* 从设备树获取PWM配置 */ servo->pwm = devm_of_pwm_get(dev, pdev->dev.of_node, NULL); if (IS_ERR(servo->pwm)) { ret = PTR_ERR(servo->pwm); if (ret != -EPROBE_DEFER) dev_err(dev, \"Failed to get PWM: %d\\n\", ret); return ret; } /* 获取PWM周期 */ servo->period_ns = 20000000; /* 默认20ms (50Hz) */ of_property_read_u32(dev->of_node, \"period-ns\", &servo->period_ns); /* 从设备树获取脉冲宽度范围 */ ret = of_property_read_u32(dev->of_node, \"min-pulse-width\", &servo->min_pulse_ns); if (ret) { dev_warn(dev, \"Using default min-pulse-width (500000ns)\\n\"); servo->min_pulse_ns = 500000; /* 默认0.5ms */ } ret = of_property_read_u32(dev->of_node, \"max-pulse-width\", &servo->max_pulse_ns); if (ret) { dev_warn(dev, \"Using default max-pulse-width (2500000ns)\\n\"); servo->max_pulse_ns = 2500000; /* 默认2.5ms */ } /* 从设备树获取角度范围 */ ret = of_property_read_u32(dev->of_node, \"min-angle\", &servo->min_angle); if (ret) { dev_warn(dev, \"Using default min-angle (0)\\n\"); servo->min_angle = 0; /* 默认0度 */ } ret = of_property_read_u32(dev->of_node, \"max-angle\", &servo->max_angle); if (ret) { dev_warn(dev, \"Using default max-angle (180)\\n\"); servo->max_angle = 180; /* 默认180度 */ } /* 从设备树获取初始角度 */ ret = of_property_read_u32(dev->of_node, \"initial-angle\", &initial_angle); if (ret) { dev_warn(dev, \"Using default initial-angle (90)\\n\"); initial_angle = 90; /* 默认90度 */ } dev_info(dev, \"SG90 servo initialized: period=%d ns, min_pulse=%d ns, max_pulse=%d ns, angle_range=[%d,%d]\\n\", servo->period_ns, servo->min_pulse_ns, servo->max_pulse_ns, servo->min_angle, servo->max_angle); /* 注册字符设备 */ ret = alloc_chrdev_region(&servo->devt, 0, 1, SG90_NAME); if (ret < 0) { dev_err(dev, \"Failed to allocate char device region\\n\"); return ret; } /* 创建设备类 */ servo->class = class_create(THIS_MODULE, SG90_CLASS); if (IS_ERR(servo->class)) { ret = PTR_ERR(servo->class); dev_err(dev, \"Failed to create class: %d\\n\", ret); goto err_unregister_chrdev; } /* 初始化cdev结构 */ cdev_init(&servo->cdev, &sg90_fops); servo->cdev.owner = THIS_MODULE; /* 添加字符设备 */ ret = cdev_add(&servo->cdev, servo->devt, 1); if (ret) { dev_err(dev, \"Failed to add char device: %d\\n\", ret); goto err_destroy_class; } /* 创建设备节点 */ device_create(servo->class, NULL, servo->devt, NULL, SG90_NAME); pwm_config(servo->pwm, servo->min_pulse_ns, servo->max_pulse_ns);pwm_set_polarity(servo->pwm, PWM_POLARITY_NORMAL);pwm_enable(servo->pwm); /* 设置初始角度 */ ret = sg90_set_angle(servo, initial_angle); if (ret) { dev_err(dev, \"Failed to set initial angle: %d\\n\", ret); goto err_destroy_device; } dev_info(dev, \"SG90 servo driver initialized, initial angle: %d degrees\\n\", initial_angle); sg90_dev = servo; /* 保存全局引用 */ return 0; err_destroy_device: device_destroy(servo->class, servo->devt); cdev_del(&servo->cdev); err_destroy_class: class_destroy(servo->class); err_unregister_chrdev: unregister_chrdev_region(servo->devt, 1); return ret;}/* 驱动移除函数 */static int sg90_remove(struct platform_device *pdev){ struct sg90_servo *servo = platform_get_drvdata(pdev);pwm_config(servo->pwm, servo->min_pulse_ns, servo->max_pulse_ns);pwm_free(servo->pwm); /* 销毁设备节点 */ device_destroy(servo->class, servo->devt); /* 删除字符设备 */ cdev_del(&servo->cdev); /* 销毁类 */ class_destroy(servo->class); /* 释放设备号 */ unregister_chrdev_region(servo->devt, 1);printk(KERN_INFO \"sg90_remove\"); return 0;}/* 设备树匹配表 */static const struct of_device_id sg90_of_match[] = { { .compatible = \"sg90-servo\" }, { }};MODULE_DEVICE_TABLE(of, sg90_of_match);/* 平台驱动结构体 */static struct platform_driver sg90_driver = { .probe = sg90_probe, .remove = sg90_remove, .driver = { .name = SG90_NAME, .of_match_table = sg90_of_match, .owner = THIS_MODULE, },};module_platform_driver(sg90_driver);MODULE_DESCRIPTION(\"SG90 Servo Driver for RK3568\");MODULE_AUTHOR(\"cmy\");MODULE_LICENSE(\"GPL\");
将sg90模块拷贝到开发板进行验证:
四、注意事项
- 角度限制: 实际舵机可能无法达到理论的 0-180 度范围,可通过设备树调整min-angle和max-angle
- 驱动调整: 如果舵机转动方向相反,可修改设备树中的pwms属性的 flags 参数
- 频率匹配: 确保 PWM 频率为 50Hz,这是 SG90 舵机的标准控制频率
五、总结
本文详细介绍了 RK3568 平台的 PWM 子系统,包括基础知识、软件框架和驱动开发。通过这个驱动,你可以在 RK3568 上轻松实现 PWM 控制功能,应用于 LED 调光、电机控制等场景。
在实际开发中,你可能需要根据具体硬件配置调整寄存器定义和初始化参数。同时,建议通过设备树配置 PWM 参数,以提高系统的可维护性和可扩展性。