RK3568平台(PWM篇)PWM驱动

一.PWM基础知识

PWM 全称为 Pulse Width Modulation,翻译成中文为脉冲宽度调制,它是一种数字信号控 制模拟电路的技术,可以通过改变高/低电平的占空比来控制平均电压或功率,从而达到对模拟 量的控制目的。

周期(T):指一个完整的高低电平循环所需要的时间,而频率为周期的倒数,指在 1 秒钟有多 少个周期,单位为 Hz,例如一个周期是 20ms,那么一秒钟就有 50 次 PWM 周期。

占空比(Duty Cycle):是指高电平时间与周期的比例,通常以百分比表示,例如周期为 20ms, 高电平所占的时间为 10ms,那占空比就是 50%。

硬件PWM和软件PWM:

硬件 PWM:

(1)实现方式:硬件 PWM 是由专门的 PWM 硬件模块实现 PWM 输出的方式。

(2)优点: ·CPU 占用低,PWM 输出由硬件模块自动完成,无需 CPU 介入。 ·PWM 输出频率和分辨率高,可以达到高达 MHz 级的频率和 ns 级的分辨率。 ·输出波形稳定可靠,不易受 CPU 负载的影响。

(3)缺点: ·需要专门的硬件 PWM 模块,成本较高。 ·PWM 输出引脚受限,只能在预定义的引脚上输出。

软件 PWM:

(1)实现方式:软件 PWM 是通过软件编程实现 PWM 输出的方式。利用定时器中断或者 循环计数的方式,在软件中控制输出引脚的高低电平切换时间,从而生成 PWM 波形。

(2)优点: ·灵活性强,可以在任意 GPIO 引脚上生成 PWM 波形。 ·成本低,不需要额外的硬件 PWM 模块。

(3)缺点: ·CPU 占用较高,因为需要在中断服务程序或者循环中实时控制引脚电平。 ·PWM 输出的频率和分辨率受 CPU 主频和中断响应时间的影响,无法高频高分辨率。 ·对 CPU 性能和实时性要求较高。

二.PWM 子系统框架

 PWM 子系统被划分为了三个层次,分别为用户空间、内核空间和硬件层,内核 空间包括 PWM 设备驱动层、PWM 核心层和 PWM 适配器驱动层。

pwm 子系统 API 讲解:

pwm_config 函数:

函数原型: int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns);

函数作用: pwm_config 用于改变 PWM 的配置,包括占空比和周期。这是 PWM 子系统中最常用的函 数之一,用于调整 PWM 信号的关键参数。

函数参数: struct pwm_device *pwm: 指向 PWM 设备结构体的指针。 int duty_ns: 占空比,单位是纳秒,表示高电平持续的时间。 int period_ns: 周期,单位是纳秒,表示 PWM 信号的总周期。

pwm_set_polarity 函数:

函数原型:int pwm_set_polarity(struct pwm_device *pwm, enum pwm_polarity polarity);

函数作用: 设置 PWM 信号的极性。PWM 信号可以是正极性或负极性,这决定了信号的高低电平的 定义。

函数参数: struct pwm_device *pwm: 指向 PWM 设备结构体的指针。 enum pwm_polarity polarity: 极性参数,可以是 PWM_POLARITY_NORMAL(正极性)或 PWM_POLARITY_INVERSED(负极性)。

pwm_enable 函数:

函数原型:int pwm_enable(struct pwm_device *pwm);

函数作用: 使能 PWM 信号,即开始输出 PWM 信号。

函数参数: struct pwm_device *pwm: 指向 PWM 设备结构体的指针。

pwm_disable 函数:

函数原型:void pwm_disable(struct pwm_device *pwm);

函数作用: 关闭 PWM 信号,即停止输出 PWM 信号。

函数参数: struct pwm_device *pwm: 指向 PWM 设备结构体的指针。

devm_of_pwm_get 函数

函数原型:struct pwm_device *devm_of_pwm_get(struct device *dev, struct device_node *np, const char *con_id);

函数作用: 从设备树节点获取 PWM 设备。 (3)函数参数: struct device *dev: 设备结构体。 struct device_node *np: 设备节点。 const char *con_id: 连接 ID。

三.PWM驱动舵机驱动程序

#include <linux/module.h>
#include <linux/init.h>
#include <linux/moduleparam.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/uaccess.h>
#include <linux/pwm.h>

dev_t dev_num;
struct cdev cdev_test;
struct class *class;
struct device *device;

struct pwm_device *sg90_pwm_device;

// 打开设备时的回调函数
static int cdev_test_open(struct inode *inode, struct file *file) {
    printk("This is cdev test open\n");
    pwm_config(sg90_pwm_device, 500000, 20000000);    // 配置 PWM 参数:脉冲宽度为 50000000 纳秒, 周期为 20,000,000 纳秒
    pwm_set_polarity(sg90_pwm_device, PWM_POLARITY_NORMAL);    // 设置 PWM 极性为正常极性
    pwm_enable(sg90_pwm_device);    // 启动 PWM
    return 0;
}

// 写入设备时的回调函数
static ssize_t cdev_test_write(struct file *file, const char __user *buf, size_t size, loff_t *off) {
    int ret;
    unsigned char data[1];
    printk("This is cdev test write\n");
    // 从用户空间拷贝数据到内核空间
    ret = copy_from_user(data, buf, size);
    if (ret) {
        printk("copy_from_user failed\n");
        return -EFAULT;
    }
    // 更新 PWM 参数:脉冲宽度根据用户输入的数据调整
    pwm_config(sg90_pwm_device, 500000 + data[0] * 100000 / 9, 20000000);
    return size;
}

// 释放设备时的回调函数
static int cdev_test_release(struct inode *inode, struct file *file) {
    printk("This is cdev test release\n");

    pwm_config(sg90_pwm_device, 500000, 20000000);    // 回到初始的 PWM 参数配置
    pwm_disable(sg90_pwm_device);    // 停止 PWM
    return 0;
}

// 定义字符设备操作函数集合
static struct file_operations cdev_test_ops = {
    .owner = THIS_MODULE,
    .open = cdev_test_open,
    .write = cdev_test_write,
    .release = cdev_test_release,
};

// 设备探测函数
static int sg90_probe(struct platform_device *pdev) {
    int ret;

    // 获取 PWM 设备
    sg90_pwm_device = devm_pwm_get(&pdev->dev, NULL);
    if (IS_ERR(sg90_pwm_device)) {
        printk("Failed to get PWM device\n");
        return PTR_ERR(sg90_pwm_device);
    }

    // 申请设备号
    ret = alloc_chrdev_region(&dev_num, 0, 1, "alloc_name");
    if (ret < 0) {
        printk("alloc_chrdev_region is error\n");
        return ret;
    }
    printk("alloc_chrdev_region is ok\n");

    // 初始化字符设备
    cdev_init(&cdev_test, &cdev_test_ops);
    cdev_test.owner = THIS_MODULE;

    ret = cdev_add(&cdev_test, dev_num, 1);// 添加字符设备
    class = class_create(THIS_MODULE, "test");    // 创建设备类
    device = device_create(class, NULL, dev_num, NULL, "sg90");    // 创建设备

    printk("sg90_probe successful\n");
    return 0;
}

// 设备移除函数
static int sg90_remove(struct platform_device *pdev) {

    device_destroy(class, dev_num);    // 删除设备
    class_destroy(class);    // 删除设备类
    cdev_del(&cdev_test);    // 删除字符设备
    unregister_chrdev_region(dev_num, 1);    // 释放设备号
    printk("sg90_remove successful\n");
    return 0;
}

// 设备树匹配表
static const struct of_device_id sg90_of_device_id[] = {
    {.compatible = "sg90"},
    {},
};
MODULE_DEVICE_TABLE(of, sg90_of_device_id);

// 定义平台驱动
static struct platform_driver sg90_platform_driver = {
    .driver = {
        .name = "sg90",
        .of_match_table = sg90_of_device_id,
    },
    .probe = sg90_probe,
    .remove = sg90_remove,
};

// 模块初始化函数
static int __init modulecdev_init(void) {
    int ret;

    // 注册平台驱动
    ret = platform_driver_register(&sg90_platform_driver);
    if (ret) {
        printk("platform_driver_register is error\n");
        return ret;
    }
    printk("platform_driver_register is ok\n");

    return 0;
}

// 模块退出函数
static void __exit modulecdev_exit(void) {
    // 注销平台驱动
    platform_driver_unregister(&sg90_platform_driver);
    printk("bye bye\n");
}

// 声明模块许可证和作者
module_init(modulecdev_init);
module_exit(modulecdev_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("topeet");
#include <fcntl.h>  
#include <unistd.h>  
#include <stdlib.h> 
#include <stdio.h> 

int main(int argc, char *argv[]) {
    // argc 表示命令行参数的数量,包括程序名本身
    // argv 是一个字符串数组,存储了各个命令行参数

    int fd;  // 文件描述符,用于标识打开的设备文件
    unsigned char buf[1];  // 存储要写入设备的单个字节数据

    // 如果命令行参数个数小于2,说明缺少要写入的值,打印用法并返回错误
    if (argc < 2) {
        printf("Usage: %s <value>\n", argv[0]);
        return -1;
    }

    // 以只写方式打开设备文件"/dev/sg90"
    fd = open("/dev/sg90", O_WRONLY);
    if (fd < 0) {  // 打开设备文件失败,输出错误信息并返回错误
        perror("open");
        return -1;
    }

    // 将命令行参数转换为整数,存储在buf[0]中
    buf[0] = (unsigned char)atoi(argv[1]);
    // 将buf中的1个字节数据写入打开的设备文件
    if (write(fd, buf, 1) != 1) {  // 写入失败,输出错误信息,关闭文件并返回错误
        perror("write");
        close(fd);
        return -1;
    }

    // 延迟3秒,模拟设备操作
    sleep(3);
    // 关闭设备文件
    close(fd);

    return 0;  // 程序执行成功
}

  • 17
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
对于RK3568来说,它支持PWM输入,并且可以使用Linux内核中的“pwm-input”驱动程序来进行配置和使用。以下是通用的指令配置: 1. 首先,确认系统中已经加载了“pwm-input”驱动程序。可以通过运行以下命令来检查: ``` lsmod | grep pwm_input ``` 如果输出结果为空,则需要加载该驱动程序: ``` modprobe pwm_input ``` 2. 然后,找到PWM输入的GPIO引脚编号。在RK3568中,可以通过查看设备树来确定该引脚编号。例如,如果PWM输入信号连接到GPIO2_D0引脚,则可以在设备树中找到以下节点: ``` &pwm_input { status = "okay"; pinctrl-names = "default"; pinctrl-0 = <&pwm_input>; phandle = <0x1a>; gpios = <&gpio2 RK_PD0 GPIO_ACTIVE_LOW>; }; ``` 其中,“gpios”属性指定了该PWM输入信号所连接的GPIO引脚编号,即“gpio2 RK_PD0”。 3. 最后,使用sysfs接口来配置PWM输入。在RK3568中,可以通过以下sysfs路径来进行配置: ``` /sys/class/pwm/pwmchipX/pwmY/ ``` 其中,“X”和“Y”分别是PWM输入所连接的GPIO引脚编号。例如,在上面的例子中,可以使用以下命令来配置GPIO2_D0引脚上的PWM输入: ``` echo 0 > /sys/class/pwm/pwmchip2/pwm0/polarity echo 1 > /sys/class/pwm/pwmchip2/pwm0/enable ``` 以上命令将PWM输入极性设置为正极性,并启用PWM输入。 希望这可以帮助你进行PWM输入的配置。请注意,具体的配置命令可能会因不同的硬件平台和设备树而有所不同。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

嵌入式_笔记

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值