先前要使用rock5a rk3588s芯片中的pwm通道作为两个电调的输入信号使用。
查阅资料,在rsetup中打开相对应的pwm-gpio后,发现可以通过用户空间直接操控pwm频率,占空比。
其用作电调的控制是足够的,但是想更进一步,把pwm编写为字符设备驱动程序。
esc-pwm驱动程序编写
这里简要说明一下,该处编写的esc-pwm程序分为左右两个,驱动程序中实现的有
映射pwmchip到字符设备文件
向字符设备写入pwm高电平持续时间(1000-2000us)即可
pwm周期为20ms即50Hz
字符设备释放后,pwm高电平持续时间自动复位1.5ms(1500us)
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/pwm.h>
#include <linux/uaccess.h>
#include <linux/device.h>
#define DEVICE_NAME "esc_left_pwm"
#define PWM_CHIP "pwmchip0" // PWM 控制器名称
#define PWM_CHANNEL 0 // PWM 通道号
#define PWM_PERIOD 20000000
#define PWM_DUTY_CYCLE 1500000
static struct pwm_device *esc_left_pwm;
static int major_number;
static struct class *esc_left_pwm_class;
static int device_open(struct inode *inode, struct file *file) {
printk(KERN_INFO "ESC LEFT PWM device opened\n");
return 0;
}
static int device_release(struct inode *inode, struct file *file) {
printk(KERN_INFO "ESC LEFT PWM device closed\n");
pwm_config(esc_left_pwm, PWM_DUTY_CYCLE, PWM_PERIOD); // 初始占空比为 1.5ms
return 0;
}
static ssize_t device_write(struct file *file, const char __user *buffer, size_t length, loff_t *offset) {
char data[32];
unsigned long duty_cycle_us;
if (copy_from_user(data, buffer, length))
return -EFAULT;
data[length] = '\0';
duty_cycle_us = simple_strtoul(data, NULL, 10);
// 设置 PWM 占空比,将微秒转换为纳秒
pwm_config(esc_left_pwm, duty_cycle_us * 1000, PWM_PERIOD); // 输入为微秒,转换为纳秒
return length;
}
static struct file_operations fops = {
.open = device_open,
.release = device_release,
.write = device_write,
};
static int __init pwm_init(void) {
// 注册字符设备
major_number = register_chrdev(0, DEVICE_NAME, &fops);
if (major_number < 0) {
printk(KERN_ALERT "ESC LEFT PWM: Failed to register device\n");
return major_number;
}
// 创建设备类
esc_left_pwm_class = class_create(THIS_MODULE, "esc_left_pwm_class");
if (IS_ERR(esc_left_pwm_class)) {
printk(KERN_ALERT "ESC LEFT PWM: Failed to create device class\n");
unregister_chrdev(major_number, DEVICE_NAME);
return PTR_ERR(esc_left_pwm_class);
}
// 创建设备文件
device_create(esc_left_pwm_class, NULL, MKDEV(major_number, 0), NULL, DEVICE_NAME);
printk(KERN_INFO "ESC LEFT PWM device registered with major number %d\n", major_number);
// 初始化 PWM 设备
esc_left_pwm = pwm_request(PWM_CHANNEL, PWM_CHIP);
if (IS_ERR(esc_left_pwm)) {
printk(KERN_ALERT "ESC LEFT PWM: Failed to request PWM\n");
device_destroy(esc_left_pwm_class, MKDEV(major_number, 0));
class_destroy(esc_left_pwm_class);
unregister_chrdev(major_number, DEVICE_NAME);
return PTR_ERR(esc_left_pwm);
}
// 设置 PWM 周期为 20ms
pwm_config(esc_left_pwm, PWM_DUTY_CYCLE, PWM_PERIOD); // 初始占空比为 15ms
pwm_set_polarity(esc_left_pwm, PWM_POLARITY_NORMAL);
pwm_enable(esc_left_pwm);
printk(KERN_INFO "ESC LEFT PWM: PWM device initialized\n");
return 0;
}
static void __exit pwm_exit(void) {
// 禁用 PWM
pwm_disable(esc_left_pwm);
pwm_free(esc_left_pwm);
// 删除设备文件
device_destroy(esc_left_pwm_class, MKDEV(major_number, 0));
class_destroy(esc_left_pwm_class);
unregister_chrdev(major_number, DEVICE_NAME);
printk(KERN_INFO "PWM device unregistered\n");
}
module_init(pwm_init);
module_exit(pwm_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("DeepSeek");
MODULE_DESCRIPTION("A simple PWM character device driver");
同理稍微改动就是对应侧的驱动
驱动程序编译成ko内核模块
编写Makefile文件
obj-m += esc_left_pwm_driver.o
obj-m += esc_right_pwm_driver.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
编译
make
加载内核模块
sudo insmod pwm_driver.ko
查看字符设备是否正常出现
ls /dev/esc_left_pwm
ls /dev/esc_right_pwm
编写测试程序验证驱动可行性
#include <iostream>
#include <fstream>
#include <cstdlib>
#include <fcntl.h>
#include <unistd.h>
#include <cstring>
#include <cmath>
#include <iomanip>
#define MIN_PULSE_US 1000 // 最小脉冲宽度(us)
#define MAX_PULSE_US 2000 // 最大脉冲宽度(us)
// 将0-1的油门值转换为脉冲宽度(us)
int throttle_to_pulse(float throttle) {
if(throttle < 0) throttle = 0;
if(throttle > 1) throttle = 1;
return MIN_PULSE_US + throttle * (MAX_PULSE_US - MIN_PULSE_US);
}
int main() {
std::ofstream fd_left("/dev/esc_left_pwm");
std::ofstream fd_right("/dev/esc_right_pwm");
std::string buf;
float t = 0;
float throttle;
if (!fd_left.is_open()) {
std::cerr << "无法打开左侧ESC设备" << std::endl;
return -1;
}
if (!fd_right.is_open()) {
std::cerr << "无法打开右侧ESC设备" << std::endl;
return -1;
}
std::cout << "开始PWM输出测试..." << std::endl;
// 生成动态变化的油门值
while(true) {
// 使用正弦函数生成0-1之间变化的油门值
throttle = (std::sin(t) + 1) / 2.0;
// 转换为脉冲宽度并写入设备
int pulse_width = throttle_to_pulse(throttle);
fd_left << pulse_width;
fd_left.flush();
fd_right << pulse_width;
fd_right.flush();
std::cout << "当前油门值: " << std::fixed << std::setprecision(4)
<< throttle << ", 脉冲宽度: " << pulse_width << " us" << std::endl;
t += 0.1; // 增加时间步长
usleep(100000); // 延时100ms
}
// 文件流会在析构时自动关闭
return 0;
}
将内核模块导入内核模块目录
将
.ko
文件复制到内核模块目录:
sudo cp esc_left_pwm_driver.ko /lib/modules/$(uname -r)/kernel/drivers/pwm/esc_pwm/
更新模块依赖关系:
sudo depmod
创建模块加载配置文件:
echo "esc_left_pwm_driver" | sudo tee /etc/modules-load.d/esc_left_pwm_driver.conf
重启系统
esc_right_pwm_driver(O) esc_left_pwm_driver(O)后面会有(O):未签名的模块,非官方模块,不影响使用