一.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; // 程序执行成功
}