3. 车载字符设备驱动注册与文件操作实现
任务目标
在车载Linux环境中实现一个PWM蜂鸣器驱动,支持通过ioctl设置PWM频率与占空比,从而控制蜂鸣器音调与音量。驱动基于Linux PWM子系统,适用于带有PWM控制器(如SoC内置PWM或外部PWM芯片)的硬件平台。
环境与依赖
- Linux内核版本:4.19+(使用标准PWM子系统API)
- 硬件:任意PWM输出通道连接蜂鸣器驱动电路(需有源蜂鸣器或通过H桥驱动无源蜂鸣器)
- 设备树:需在设备树中声明PWM蜂鸣器节点,并指定使用的PWM通道、默认频率与占空比
核心实现
驱动通过标准字符设备接口暴露给用户空间,注册miscdevice以简化设备节点管理。核心功能包括:open时初始化PWM并启动输出;ioctl支持设置频率(以Hz为单位)与占空比(以百分比表示,0~100);release时停止PWM并释放资源。PWM频率通过pwm_config的周期参数控制,占空比通过duty_cycle控制。
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/pwm.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/slab.h>
#define PWM_BUZZER_IOC_MAGIC 'B'
#define PWM_BUZZER_SET_FREQ _IOW(PWM_BUZZER_IOC_MAGIC, 1, unsigned int)
#define PWM_BUZZER_SET_DUTY _IOW(PWM_BUZZER_IOC_MAGIC, 2, unsigned int)
struct pwm_buzzer_dev {
struct pwm_device *pwm;
unsigned int freq; /* Hz */
unsigned int duty; /* 0~100 */
};
static struct pwm_buzzer_dev *buzzer_dev;
static void pwm_buzzer_update(struct pwm_buzzer_dev *dev)
{
unsigned int period_ns;
unsigned int duty_ns;
if (!dev->pwm)
return;
if (dev->freq == 0) {
pwm_disable(dev->pwm);
return;
}
period_ns = NSEC_PER_SEC / dev->freq;
duty_ns = (period_ns / 100) * dev->duty;
pwm_config(dev->pwm, duty_ns, period_ns);
pwm_enable(dev->pwm);
}
static int pwm_buzzer_open(struct inode *inode, struct file *file)
{
if (!buzzer_dev || !buzzer_dev->pwm)
return -ENODEV;
file->private_data = buzzer_dev;
return 0;
}
static long pwm_buzzer_ioctl(struct file *file, unsigned int cmd,
unsigned long arg)
{
struct pwm_buzzer_dev *dev = file->private_data;
unsigned int val;
if (copy_from_user(&val, (void __user *)arg, sizeof(val)))
return -EFAULT;
switch (cmd) {
case PWM_BUZZER_SET_FREQ:
if (val == 0 || val > 100000) /* 限制范围:1Hz~100kHz */
return -EINVAL;
dev->freq = val;
break;
case PWM_BUZZER_SET_DUTY:
if (val > 100)
return -EINVAL;
dev->duty = val;
break;
default:
return -ENOTTY;
}
pwm_buzzer_update(dev);
return 0;
}
static int pwm_buzzer_release(struct inode *inode, struct file *file)
{
struct pwm_buzzer_dev *dev = file->private_data;
if (dev->pwm)
pwm_disable(dev->pwm);
return 0;
}
static const struct file_operations pwm_buzzer_fops = {
.owner = THIS_MODULE,
.open = pwm_buzzer_open,
.unlocked_ioctl = pwm_buzzer_ioctl,
.release = pwm_buzzer_release,
};
static struct miscdevice pwm_buzzer_misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = "pwm_buzzer",
.fops = &pwm_buzzer_fops,
};
static int pwm_buzzer_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
struct pwm_buzzer_dev *dev;
u32 freq = 1000, duty = 50;
dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL);
if (!dev)
return -ENOMEM;
dev->pwm = devm_of_pwm_get(&pdev->dev, np, NULL);
if (IS_ERR(dev->pwm))
return PTR_ERR(dev->pwm);
of_property_read_u32(np, "default-frequency", &freq);
of_property_read_u32(np, "default-duty", &duty);
dev->freq = freq;
dev->duty = duty;
buzzer_dev = dev;
pwm_buzzer_update(dev);
return misc_register(&pwm_buzzer_misc);
}
static int pwm_buzzer_remove(struct platform_device *pdev)
{
misc_deregister(&pwm_buzzer_misc);
if (buzzer_dev && buzzer_dev->pwm)
pwm_disable(buzzer_dev->pwm);
return 0;
}
static const struct of_device_id pwm_buzzer_of_match[] = {
{ .compatible = "vendor,pwm-buzzer" },
{ }
};
MODULE_DEVICE_TABLE(of, pwm_buzzer_of_match);
static struct platform_driver pwm_buzzer_driver = {
.probe = pwm_buzzer_probe,
.remove = pwm_buzzer_remove,
.driver = {
.name = "pwm_buzzer",
.of_match_table = pwm_buzzer_of_match,
},
};
module_platform_driver(pwm_buzzer_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("PWM Buzzer Driver");
设备树配置示例
/ {
pwm_buzzer {
compatible = "vendor,pwm-buzzer";
pwms = &pwm0, 0; /* 使用pwm0通道0 */
default-frequency = <2000>; /* 默认2kHz */
default-duty = <50>; /* 默认50%占空比 */
};
};
编译与加载
- 将驱动源码放入内核源码树
drivers/misc/目录,并添加Makefile条目:obj-$(CONFIG_PWM_BUZZER) += pwm_buzzer.o - 在内核配置中启用
CONFIG_PWM_BUZZER=y(或=m) - 编译内核或模块:
make modules或make - 加载模块(若编译为模块):
insmod pwm_buzzer.ko
用户空间测试程序
#include <stdio.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#define PWM_BUZZER_IOC_MAGIC 'B'
#define PWM_BUZZER_SET_FREQ _IOW(PWM_BUZZER_IOC_MAGIC, 1, unsigned int)
#define PWM_BUZZER_SET_DUTY _IOW(PWM_BUZZER_IOC_MAGIC, 2, unsigned int)
int main(void)
{
int fd = open("/dev/pwm_buzzer", O_RDWR);
if (fd < 0) {
perror("open");
return 1;
}
unsigned int freq = 3000; /* 3kHz */
unsigned int duty = 30; /* 30% 音量 */
if (ioctl(fd, PWM_BUZZER_SET_FREQ, &freq) < 0)
perror("set freq");
if (ioctl(fd, PWM_BUZZER_SET_DUTY, &duty) < 0)
perror("set duty");
sleep(2); /* 发声2秒 */
freq = 1000;
duty = 80;
ioctl(fd, PWM_BUZZER_SET_FREQ, &freq);
ioctl(fd, PWM_BUZZER_SET_DUTY, &duty);
sleep(2);
close(fd);
return 0;
}
验证方法
- 加载驱动后检查设备节点:
ls -l /dev/pwm_buzzer,应存在该字符设备。 - 编译并运行测试程序,应能听到蜂鸣器先以3kHz、30%占空比发声2秒,随后变为1kHz、80%占空比发声2秒。音调变化对应频率改变,音量变化对应占空比改变。
- 使用示波器连接PWM输出引脚,可观察到频率与占空比与设置值一致。
- 关闭设备文件后(测试程序退出),蜂鸣器应停止发声,表明
release中已禁用PWM输出。
常见问题
- 设备节点未创建:确认设备树节点匹配
compatible属性,且PWM控制器驱动已加载。 - ioctl返回EINVAL:检查传入的频率是否在1~100000范围内,占空比是否在0~100范围内。
- 蜂鸣器无声:确认PWM通道硬件连接正确,且蜂鸣器驱动电路支持所设置的频率范围(无源蜂鸣器需在音频范围内)。