【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. 硬件连接
SG90 | 引脚 | RK3568 |
---|---|---|
橙色 | (信号) | PWM输出引脚 |
红色 | (电源) | 5V电源 |
棕色 (信号) | (地) | GND |
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/pwm
cat /sys/kernel/debug/pwm //查看pwm信息
cd pwmchip3/
echo 0 > export //导出pwm12
cd pwm0
echo 20000000 > period //设置周期
echo 2000000 > duty_cycle //设置高电平时间
echo normal > polarity //设置极性,有normal或inversed
echo 1 > enable //打开pwm
echo 0 > enable //关闭pwm
通过上述指令能够看到sg90角度发生变化,表示已经配置成功了。
4. 驱动代码实现
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/pwm.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/delay.h>/* 驱动名称和设备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 参数,以提高系统的可维护性和可扩展性。