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

相关推荐
虾..9 小时前
Linux 软硬链接和动静态库
linux·运维·服务器
Evan芙9 小时前
Linux常见的日志服务管理的常见日志服务
linux·运维·服务器
村里小码农11 小时前
Android APP之间共享数据
android·contentprovider·contentresolver·android app数据共享
hkhkhkhkh12311 小时前
Linux设备节点基础知识
linux·服务器·驱动开发
Jerry11 小时前
Navigation 最佳实践
android
Just_Paranoid11 小时前
【Android UI】Android 颜色的表示和获取使用指南
android·ui·theme·color·attr·colorstatelist
louisgeek12 小时前
Android Charles Proxy 抓包
android
HZero.chen12 小时前
Linux字符串处理
linux·string
张童瑶12 小时前
Linux SSH隧道代理转发及多层转发
linux·运维·ssh
汪汪队立大功12312 小时前
什么是SELinux
linux