RK3568 PWM 子系统(SG90)驱动开发详解
- [一、PWM 基础知识](#一、PWM 基础知识)
-
- [1. 基本概念](#1. 基本概念)
- [2. 应用场景](#2. 应用场景)
- [二、Linux PWM 子系统架构](#二、Linux PWM 子系统架构)
-
- [1. 架构层次图](#1. 架构层次图)
- [2. 各层次详细说明](#2. 各层次详细说明)
- [三、以 SG90 舵机为例的驱动实现](#三、以 SG90 舵机为例的驱动实现)
-
- [1. SG90 舵机基本原理](#1. SG90 舵机基本原理)
- [2. 硬件连接](#2. 硬件连接)
- [3. 设备树配置](#3. 设备树配置)
- [4. 驱动代码实现](#4. 驱动代码实现)
- 四、注意事项
- 五、总结
PWM(脉冲宽度调制)是一种常用的模拟控制技术,广泛应用于 LED 调光、电机控制、电源管理等场景。本文将深入探讨 RK3568 平台的 PWM 子系统,包括基础知识、子系统架构、驱动开发实践以及 SG90 舵机控制实例。
一、PWM 基础知识
1. 基本概念
- PWM 信号: 一种方波信号,通过调节 "占空比" 来控制平均电压
- 频率: 每秒完成的周期数(Hz)
- 占空比: 高电平时间占整个周期的比例(0%-100%)
2. 应用场景
- LED 调光: 调节 LED 亮度
- 电机控制: 控制电机转速和方向
- 电源管理: DC-DC 转换器中的开关控制
- 音频输出: 数字音频转换为模拟信号
二、Linux PWM 子系统架构
Linux PWM 子系统采用分层设计,将用户空间与硬件实现分离,通过标准化接口实现对不同 PWM 控制器的统一管理。
1. 架构层次图
bash
┌─────────────────────────────────────────────────────────────┐
│ 用户空间 │
│ ┌───────────────┐ ┌────────────────┐ ┌────────────────┐ │
│ │ 应用程序 │ │ 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 通道的分配和释放
关键数据结构:
c
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禁用
c
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度 */
};
c
&pwm12 {
status = "okay";
pinctrl-names = "active";
pinctrl-0 = <&pwm12m1_pins>;
};
使用sysfs文件系统测试pwm是否正常工作:
c
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. 驱动代码实现
c
#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 参数,以提高系统的可维护性和可扩展性。