【RK3568 PWM 子系统(SG90)驱动开发详解】

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 参数,以提高系统的可维护性和可扩展性。

相关推荐
zepcjsj080112 分钟前
简单实现支付密码的页面及输入效果
android
小阳睡不醒1 小时前
小白成长之路-部署Zabbix7(二)
android·运维
mCell1 小时前
从删库到跑路?这50个Linux命令能保你职业生涯
linux·windows·macos
杰克逊的日记1 小时前
GPU运维常见问题处理
linux·运维·gpu
誰能久伴不乏2 小时前
Linux系统调用概述与实现:深入浅出的解析
linux·运维·服务器
程序员学习随笔2 小时前
Linux进程深度解析(2):fork/exec写时拷贝性能优化与exit资源回收机制(进程创建和销毁)
linux·运维·服务器
-SGlow-3 小时前
MySQL相关概念和易错知识点(2)(表结构的操作、数据类型、约束)
linux·运维·服务器·数据库·mysql
代码改变世界ctw3 小时前
Linux内核设计与实现 - 第14章 块I/O层
linux·运维·服务器
你过来啊你5 小时前
Android用户鉴权实现方案深度分析
android·鉴权