通过风扇FG脚检测风扇转速
1、概述
通过风扇FG脚获取风扇转速。
2、分析
根据风扇规格书可知风扇风速=60/(2*脉冲周期),周期T=1/频率。那么我们需要获取FG脚上的脉冲频率,即可获取风扇风速。
3、解决方法
利用边沿触发中断利用定时器获取1s进入中断的次数即可获取脉冲频率。
(1)注册检测脚
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | gpio-pwms { compatible = "gpio-pwms" ; pinctrl-names = "default" ; pwm1 { label = "pwm1" ; gpios = <&pio 0 6 GPIO_ACTIVE_HIGH>; gpios-fg = <&pio 0 17 GPIO_ACTIVE_HIGH>; }; pwm2 { label = "pwm2" ; gpios = <&pio 6 9 GPIO_ACTIVE_HIGH>; gpios-fg = <&pio 0 3 GPIO_ACTIVE_HIGH>; }; pwm3{ label = "pwm3" ; gpios = <&pio 6 11 GPIO_ACTIVE_HIGH>; gpios-fg = <&pio 0 21 GPIO_ACTIVE_HIGH>; }; pwm4{ label = "pwm4" ; gpios = <&pio 6 12 GPIO_ACTIVE_HIGH>; gpios-fg = <&pio 0 20 GPIO_ACTIVE_HIGH>; }; }; |
(2)编写驱动
-
解析dts文件,获取fg脚
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | for_each_child_of_node(node, fg) { enum of_gpio_flags flagsfg; if (!of_find_property(fg, "gpios-fg" , NULL)) { pdata->npwms--; printk( "Fail to find gpios-fg\n" ); continue ; } pwm = &pdata->pwms[i++]; pwm->gpio_fg = of_get_named_gpio_flags(fg, "gpios-fg" , 0, &flagsfg); printk( "pwm->gpio-fg=%d,flags=%d" ,pwm->gpio_fg,flagsfg); if (pwm->gpio_fg < 0) { error = pwm->gpio_fg; if (error != -ENOENT) { if (error != -EPROBE_DEFER) dev_err(dev, "Failed to get gpio-fg flags, error: %d\n" , error); return ERR_PTR(error); } } } |
-
申请中断
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | switch (gpiofg) { case 17: error= devm_gpio_request(dev, gpiofg, "fan1_FG" ); break ; case 3: error= devm_gpio_request(dev, gpiofg, "fan2_FG" ); break ; case 21: error= devm_gpio_request(dev, gpiofg, "fan3_FG" ); break ; case 20: error= devm_gpio_request(dev, gpiofg, "fan4_FG" ); break ; default : break ; } if (error){ printk( "unable to request gpio %u, err=%d\n" , gpiofg, error); } gpwm->irq_fg= gpio_to_irq(gpiofg); //获取一个gpio对应的中断号 if (gpwm->irq_fg < 0) { printk( "return irq number error!" ); } switch (gpiofg) { case 17: pin1FGirq = gpwm->irq_fg; INIT_WORK(&gpwm->gpiofg_work, fan1_speed); //初始化工作队列 irq_set_irq_type(gpwm->irq_fg, IRQ_TYPE_EDGE_FALLING); //设置触发类型 error = devm_request_irq(&pdev->dev, gpwm->irq_fg, get_fan_speed_irq_handler, IRQF_SHARED, "fan1_FG" , gpwm); //申请中断设置中断类型为 共享中断 break ; case 3: pin2FGirq = gpwm->irq_fg; INIT_WORK(&gpwm->gpiofg_work, fan2_speed); error = devm_request_irq(&pdev->dev, gpwm->irq_fg, get_fan_speed_irq_handler, IRQF_SHARED, "fan2_FG" , gpwm); break ; case 21: pin3FGirq = gpwm->irq_fg; INIT_WORK(&gpwm->gpiofg_work, fan3_speed); error = devm_request_irq(&pdev->dev, gpwm->irq_fg, get_fan_speed_irq_handler, IRQF_SHARED, "fan3_FG" , gpwm); break ; case 20: pin4FGirq = gpwm->irq_fg; INIT_WORK(&gpwm->gpiofg_work, fan4_speed); error = devm_request_irq(&pdev->dev, gpwm->irq_fg, get_fan_speed_irq_handler, IRQF_SHARED, "fan4_FG" , gpwm); break ; default : break ; } if (error) { printk( "failed to request irq, err=%d\n" , error); } disable_irq(gpwm->irq_fg); //默认关闭中断 } |
-
中断服务程序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | static irqreturn_t get_fan_speed_irq_handler( int irq, void *dev_id) { struct pwm_chip *gpiofg_data = dev_id; schedule_work(&gpiofg_data->gpiofg_work); //schedule_work(work)来通知内核线程,然后中断结束后,再去继续执行work对应的func函数 return IRQ_HANDLED; } 注意: //中断服务程序的返回值必须为IRQ_HANDLED /** * enum irqreturn * @IRQ_NONE interrupt was not from this device or was not handled * @IRQ_HANDLED interrupt was handled by this device * @IRQ_WAKE_THREAD handler requests to wake the handler thread */ enum irqreturn { IRQ_NONE = (0 << 0), IRQ_HANDLED = (1 << 0), IRQ_WAKE_THREAD = (1 << 1), }; |
中断服务程序有三个返回值,三个值代表不同意思,如果返回值为IR_NONE,系统会认为这个中断没有被处理(但是中断程序执行了),当 未处理中断次数超过100000次时,系统会disable掉这个中断。系统会认为中断卡死了,这是共享中断的特性,会根据中断服务程序的返回值判断中断程序是否被处理。
当一个中断号上有多个中断共享的时候,该中断来的时候,内核会依次调用共享该中断号的各个中断处理函数,如果中断处理函数检测到该中断不是自己的中断时就会返回IRQ_NONE,这时内核就会调用下一个中断处理函数,而这些中断处理函数中必须至少有一个返回IRQ_HANDLED告知内核该中断是自己的中断,已经正常处理,若内核依次调用完所有该中断号的中断处理函数仍未得到IRQ_HANDLED的返回值,内核就会报告上述错误,并在该中断出现一定次数后关闭该中断。即只有中断处理函数返回 IRQ_HANDLED ,这个中断才是被正确完成的。
中断卡死的处理过程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | //Linux-4.14.25/kernel/irq/spurious.c irq = irq_desc_get_irq(desc); if (unlikely(try_misrouted_irq(irq, desc, action_ret))) { int ok = misrouted_irq(irq); if (action_ret == IRQ_NONE) desc->irqs_unhandled -= ok; } desc->irq_count++; if (likely(desc->irq_count < 100000)) return ; desc->irq_count = 0; if (unlikely(desc->irqs_unhandled > 99900)) { /* * The interrupt is stuck */ __report_bad_irq(desc, action_ret); /* * Now kill the IRQ */ printk(KERN_EMERG "Disabling IRQ #%d\n" , irq); desc->istate |= IRQS_SPURIOUS_DISABLED; desc->depth++; irq_disable(desc); mod_timer(&poll_spurious_irq_timer, jiffies + POLL_SPURIOUS_IRQ_INTERVAL); } desc->irqs_unhandled = 0; } |
查看中断信息:
-
工作队列的任务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | static void fan1_speed( struct work_struct *ws) { pinFG1_frequency++; } static void fan2_speed( struct work_struct *ws) { pinFG2_frequency++; } static void fan3_speed( struct work_struct *ws) { pinFG3_frequency++; } static void fan4_speed( struct work_struct *ws) { pinFG4_frequency++; } |
工作队列的介绍
在中断处理中,经常用到工作队列,这样便能缩短中断处理时的时间
//工作队列初始化函数
INIT_WORK(work, func);
中断中通过调用schedule_work(work)来通知内核线程,然后中断结束后,再去继续执行work对应的func函数
示例
当中断来了,立马调用schedule_work(work),然后退出.
中断结束后,内核便会调用_work对应的func函数,最后才来读取按键值,上报按键值,这样就大大缩短了中断处理时间
-
定时器初始化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | static void fan1_init_timer( void ) { fan1timer.expires = jiffies+100; //设定 超时时间,100代表1秒? timer_setup(&fan1timer, fan1_timer, 0); add_timer(&fan1timer); //添加定时器,定时器开始生效 enable_irq(pin1FGirq); } static void fan2_init_timer( void ) { fan2timer.expires = jiffies+100; //设定 超时时间,100代表1秒 timer_setup(&fan2timer, fan2_timer, 0); //准备timer,并设置超时时执行的函数。 add_timer(&fan2timer); //添加定时器,定时器开始生效 enable_irq(pin2FGirq); } static void fan3_init_timer( void ) { fan3timer.expires = jiffies+100; //设定 超时时间,100代表1秒 timer_setup(&fan3timer, fan3_timer, 0); add_timer(&fan3timer); //添加定时器,定时器开始生效 enable_irq(pin3FGirq); } static void fan4_init_timer( void ) { fan4timer.expires = jiffies+100; //设定 超时时间,100代表1秒 timer_setup(&fan4timer, fan4_timer, 0); add_timer(&fan4timer); //添加定时器,定时器开始生效 enable_irq(pin4FGirq); } |
-
定时器超时处理函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | static void fan1_timer( struct timer_list *t) { pinFG_frequency[0] = pinFG1_frequency; pinFG1_frequency = 0; mod_timer(&fan1timer,jiffies+100); // 修改定时器的expire } static void fan2_timer( struct timer_list *t) { pinFG_frequency[1] = pinFG2_frequency; pinFG2_frequency = 0; mod_timer(&fan2timer,jiffies+100); } static void fan3_timer( struct timer_list *t) { pinFG_frequency[2] = pinFG3_frequency; pinFG3_frequency = 0; mod_timer(&fan3timer,jiffies+100); } static void fan4_timer( struct timer_list *t) { pinFG_frequency[3] = pinFG4_frequency; pinFG4_frequency = 0; mod_timer(&fan4timer,jiffies+100); } |
-
read函数(应用层read会调用到这个函数)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | ssize_t pwm_drv_read ( struct file *filp, char __user *userbuf, size_t count, loff_t *fpos) { int ret=0, i = 0,j=0; unsigned char tmp[8] ={0}; //应用层从内核读取数据时,只能一个字节一个字节读,所以将频率short型数据要分成两个单字节数据读。 while (i<8) { tmp[i] = pinFG_frequency[j]>>8 ; tmp[i+1] = pinFG_frequency[j]; i+=2; j++; } ret= copy_to_user(userbuf, tmp, sizeof (tmp)/ sizeof (tmp[0])); if (ret==1) { printk( "copy data error!\n" ); ret = -1; } return ret; |
(3)应用层获取数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | void fan_get_rotating_speed(uint16_t *arg,uint8_t len) { int fd=-1,ret=-1,i=0,j=0; uint8_t recv_buff[8]={0}; uint16_t pinFG_Freqency[4]={0}; printf ( "fan_get_rotating_speed\n" ); fd = open(dev_fan[0].description,O_RDWR ); if (fd < 0) { printf ( "failed to open pwm0 failed!\n" ); } //读取数据 ret = read(fd,recv_buff,len*2); if (ret<0) { printf ( "get fan rotating speed error!" ); } //将8个字节的数据合成4个short型数据 while (i<8) { pinFG_Freqency[j] = (unsigned short )recv_buff[i]<<8|recv_buff[i+1]; i+=2; j++; } //计算转速 for (i=0;i<len;i++) { arg[i]=(uint16_t)((60*pinFG_Freqency[i])/2); } close(fd); } |
driver-ipollo.c中去调用
1 2 3 4 5 6 | else if (strcasecmp(option, "getallstats" ) == 0) { char tmp_str[64] = { 0 }; uint16_t fan_speed[4]={0}; fan_get_rotating_speed(fan_speed, sizeof (fan_speed)/ sizeof (fan_speed[0])); sprintf (tmp_str, "\"fanspeed[0:%d]:[1:%d][2:%d][3:%d]\"" ,fan_speed[0],fan_speed[1],fan_speed[2],fan_speed[3]); strcat (replybuf, tmp_str); |
可通过命令去获取风速:
1 |
1 | echo -n "ascset|0,getallstats" | nc 192.168.1.100 4028 && echo |
1 |
1 |
1 | |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)