当前位置: 首页 > news >正文

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

http://www.dtcms.com/a/300355.html

相关文章:

  • JavaScript手录06-函数
  • Linux——线程同步
  • KubeKey安装KubeSphere、部署应用实践问题总结
  • 立式加工中心X-Y轴传动机械结构设“cad【6张】三维图+设计说明书
  • 计算机中的单位(详细易懂)
  • 计算机结构-逻辑门、存储器、内存、加法器、锁存器、程序计数器
  • 斐波那契数列加强版 快速矩阵幂
  • 53. 最大子数组和
  • 组合问题(回溯算法)
  • Windows Server容器化应用的资源限制设置
  • 图书管理系统:一个功能完善的图书馆管理解决方案
  • 【C++篇】STL的关联容器:map和set(下篇):用一颗红黑树同时封装出map和set
  • CCFRec-人大高瓴-KDD2025-序列推荐中充分融合协同信息与语义信息
  • Item13:以对象管理资源
  • 人工智能论文辅导:Prompt Engineering(特征工程)
  • 倍思鹿数值仿真-实现各类提示、快捷键功能,提高工作效率
  • Android Jetpack 组件库 ->Jetpack Navigation (下)
  • 通过不同坐标系下的同一向量,求解旋转矩阵
  • 深度学习入门(2)
  • 实验-OSPF多区域
  • 告别Vite脚手架局限!MixOne Beta测试招募:你的需求,我们来实现
  • 【Java】基础概念-构造函数详解
  • [Python] -进阶理解7- Python中的内存管理机制简析
  • 基于springboot的在线数码商城/在线电子产品商品销售系统的设计与实现
  • (二)使用 LangChain 从零开始构建 RAG 系统 RAG From Scratch
  • 7月26号打卡
  • Unity GenericMenu 类详解
  • 技术 — 资本双螺旋:AI 时代的投资浪潮与技术突破
  • 模型训练部署流程
  • 电磁兼容三:电磁干扰三要素详解