通过风扇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
  
posted @   轻轻的吻  阅读(5404)  评论(0编辑  收藏  举报
编辑推荐:
· 基于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)
点击右上角即可分享
微信分享提示