如何为树莓派实现边缘计数器/时钟检测器及低CPU占用的频率检测内核驱动
嘿,刚好做过类似的树莓派边缘计数/频率检测驱动,完全匹配你的低CPU占用和文件接口需求,给你详细捋一遍实现思路和代码:
核心实现思路
要满足低CPU占用,绝对不能用用户空间轮询的方式——必须靠内核GPIO中断+定时器统计的组合:
- 用GPIO边缘触发中断,每次信号跳变时仅做原子计数(中断上下文尽量轻量化)
- 用内核定时器定期读取计数值,计算频率后重置计数器
- 通过sysfs提供文件接口,让用户空间直接读取计算好的频率
1. 驱动核心代码实现
以下是可直接参考的关键代码片段,注释已经写得很清楚:
头文件与全局变量定义
#include <linux/gpio.h> #include <linux/interrupt.h> #include <linux/timer.h> #include <linux/sysfs.h> #include <linux/kobject.h> #include <linux/module.h> // 原子计数器:避免中断与定时器的并发冲突 static atomic_t edge_count = ATOMIC_INIT(0); // 存储当前计算出的频率值 static unsigned int current_freq = 0; // 内核定时器 static struct timer_list freq_timer; // 要使用的GPIO引脚(根据你的硬件连接修改,比如GPIO18) static int gpio_pin = 18; // 定时器间隔(单位ms,可根据精度需求调整) static unsigned int timer_interval_ms = 100; // sysfs节点对象 static struct kobject *freq_kobj;
中断处理函数(仅做计数)
static irqreturn_t edge_interrupt_handler(int irq, void *dev_id) { // 原子递增计数器,避免并发问题 atomic_inc(&edge_count); return IRQ_HANDLED; }
定时器回调(计算频率)
static void freq_timer_callback(struct timer_list *t) { // 读取当前计数值 unsigned int count = atomic_read(&edge_count); // 计算频率:频率 = 计数 / 时间间隔(秒) current_freq = (count * 1000) / timer_interval_ms; // 重置计数器 atomic_set(&edge_count, 0); // 重新启动定时器,实现周期性统计 mod_timer(&freq_timer, jiffies + msecs_to_jiffies(timer_interval_ms)); }
Sysfs文件接口实现
// 用户读取文件时的回调,返回当前频率 static ssize_t freq_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { return sprintf(buf, "%u\n", current_freq); } // 定义sysfs属性:只读权限,对应/sys/kernel/edge_counter/frequency static struct kobj_attribute freq_attr = __ATTR(frequency, 0444, freq_show, NULL);
驱动初始化与退出函数
static int __init edge_counter_init(void) { int irq; int ret; // 校验并申请GPIO引脚 if (!gpio_is_valid(gpio_pin)) { pr_err("Invalid GPIO pin %d\n", gpio_pin); return -EINVAL; } ret = gpio_request(gpio_pin, "edge_counter"); if (ret) { pr_err("Failed to request GPIO %d\n", gpio_pin); return ret; } gpio_direction_input(gpio_pin); // 申请GPIO对应的中断(这里用上升沿触发,可根据VCO信号修改) irq = gpio_to_irq(gpio_pin); ret = request_irq(irq, edge_interrupt_handler, IRQF_TRIGGER_RISING, "edge_counter_irq", NULL); if (ret) { pr_err("Failed to request IRQ %d\n", irq); gpio_free(gpio_pin); return ret; } // 初始化定时器 timer_setup(&freq_timer, freq_timer_callback, 0); mod_timer(&freq_timer, jiffies + msecs_to_jiffies(timer_interval_ms)); // 创建sysfs节点 freq_kobj = kobject_create_and_add("edge_counter", kernel_kobj); if (!freq_kobj) { pr_err("Failed to create kobject\n"); free_irq(irq, NULL); gpio_free(gpio_pin); del_timer(&freq_timer); return -ENOMEM; } ret = sysfs_create_file(freq_kobj, &freq_attr.attr); if (ret) { pr_err("Failed to create sysfs file\n"); kobject_put(freq_kobj); free_irq(irq, NULL); gpio_free(gpio_pin); del_timer(&freq_timer); return ret; } pr_info("Edge counter driver initialized on GPIO %d\n", gpio_pin); return 0; } static void __exit edge_counter_exit(void) { int irq = gpio_to_irq(gpio_pin); // 清理资源 sysfs_remove_file(freq_kobj, &freq_attr.attr); kobject_put(freq_kobj); del_timer(&freq_timer); free_irq(irq, NULL); gpio_free(gpio_pin); pr_info("Edge counter driver exited\n"); } module_init(edge_counter_init); module_exit(edge_counter_exit); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("Raspberry Pi Edge Counter/Frequency Detector Driver"); MODULE_AUTHOR("Your Name");
2. 编译与使用步骤
编写Makefile
obj-m += edge_counter.o KDIR := /lib/modules/$(shell uname -r)/build PWD := $(shell pwd) all: $(MAKE) -C $(KDIR) M=$(PWD) modules clean: $(MAKE) -C $(KDIR) M=$(PWD) clean
编译与加载驱动
- 先安装树莓派内核头文件:
sudo apt install raspberrypi-kernel-headers - 编译驱动:
make - 加载驱动:
sudo insmod edge_counter.ko - 查看当前频率:
cat /sys/kernel/edge_counter/frequency - 卸载驱动:
sudo rmmod edge_counter
3. 优化与注意事项
- CPU占用控制:整个驱动几乎不占用CPU——只有中断触发时执行1行原子计数,定时器每隔固定时间执行一次轻量计算,比用户空间轮询高效几个数量级
- 精度调整:如果需要更高精度,可将
timer_interval_ms调小(比如10ms);对精度要求不高时,调大到1秒能进一步降低CPU占用 - 边缘触发选择:根据VCO输出信号,可将中断触发方式改为
IRQF_TRIGGER_FALLING(下降沿)或IRQF_TRIGGER_BOTH(双边沿) - GPIO引脚选择:尽量避开树莓派的特殊功能引脚(如I2C、SPI引脚),避免硬件冲突
内容的提问来源于stack exchange,提问作者Stefan Jaritz
相关产品推荐
相关产品推荐

