通过风扇FG脚检测风扇转速

摘要:
然后,我们需要获得FG引脚上的脉冲频率,以获得风扇风速。注册检测引脚gpio pwms{compatible=“gpio pwm”;pinctrl names=“default”;pwm 1{label=“pwm 1”;gpios=;gpios fg=;};pwm2{label=“pwm2”;gpios=;gpios fg=;};pwm3{label=“pwm3”;gpios=;gpios fg=;};pwm4{label=“pwm4”;gpios=;gpios fg=;};};编写驱动器解析dts文件以获取节点{enumof_gpio_flagsflagsfg;if(!

1、概述

    通过风扇FG脚获取风扇转速。

2、分析

通过风扇FG脚检测风扇转速第1张

    根据风扇规格书可知风扇风速=60/(2*脉冲周期),周期T=1/频率。那么我们需要获取FG脚上的脉冲频率,即可获取风扇风速。

3、解决方法

    利用边沿触发中断利用定时器获取1s进入中断的次数即可获取脉冲频率。

(1)注册检测脚

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脚
	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
");
			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
",
						error);
				return ERR_PTR(error);
			}
		} 
		
	}

  

  • 申请中断
 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
",
				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
", error);
		}
	  disable_irq(gpwm->irq_fg);   //默认关闭中断
	}
 
  • 中断服务程序
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 ,这个中断才是被正确完成的。

中断卡死的处理过程:

//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
", 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;
} 

查看中断信息:

通过风扇FG脚检测风扇转速第2张

通过风扇FG脚检测风扇转速第3张

  • 工作队列的任务
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函数,最后才来读取按键值,上报按键值,这样就大大缩短了中断处理时间

  • 定时器初始化
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);
}
  • 定时器超时处理函数
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会调用到这个函数)
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!
");
		ret = -1;
	}
 	  return ret;

  

(3)应用层获取数据 

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
");
	
	fd = open(dev_fan[0].description,O_RDWR );	 
	if(fd < 0)
	{			
		printf("failed to open pwm0 failed!
");
	}
	//读取数据
	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中去调用

 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);

可通过命令去获取风速:

echo -n "ascset|0,getallstats" | nc 192.168.1.100 4028 && echo
通过风扇FG脚检测风扇转速第4张
 

免责声明:文章转载自《通过风扇FG脚检测风扇转速》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇linux 常见音乐、视频播放器简介VIO(2)—— IMU传感器测量模型、误差模型、运动模型(预积分模型)下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

随便看看

基于智能网卡(Smart Nic)的Open vSwitch卸载方案简介

SmartNic技术的初衷是以比普通CPU低得多的成本支持各种虚拟化功能,如sriov、overlay/decap和卸载一些vSwitch处理逻辑。目前,业界还没有完美的SmartNic解决方案来解决传统的vSwitch性能瓶颈,每种解决方案的实施方式也各不相同。没有统一的解决方案。图1.不同SmartNic架构的比较。2.基于SmartNic的OVS卸载方...

Vue跨层级传递slot的方法

但是我需要通过插槽在父组件中指定一个模板,而B组件引用C组件。组件C的部分模板需要在组件A中配置。模板引用A组件:{{node.text}}&lt;模板引用B组件:spanslot=“nodeMenu”slot scope=“{node}”&gt;node=“node”&gt;/span&gt;/div&gt;2.2如...

jenkins之部署、启动、关闭

jenkins可以通过内置的应用服务器或者借助其他应用服务器启动目录1、启动jenkins2、关闭jenkins3、重启jenkins4、重新加载jenkins配置信息前言:部署jenkins应用,是要安装java的,最新版本的jenkins是需要按照1.8版本的jdk,不然启动不了。...

Java 实体类转json对象,属性名转key后首字母强制被转成小写的解决方案

  2.原因分析我们知道在Java规范中,实体类的属性名的命名规范需要遵循首字母小写的驼峰命名法,既是规范也是约束也是牢笼。如果不喜欢这种实现方式,还可以迭代json取值,将key的首字母转大写,也是可以哒。...

应用程序-特定 权限设置并未向在应用程序容器 不可用 SID (不可用)中运行的地址 LocalHost (使用 LRPC) 中的用户 NT AUTHORITYSYSTEM SID (S-1-5-18)授予针对 CLSID 为 {D63B10C5-BB46-4990-A94F-E40B9D520

此安全权限可以使用组件服务管理工具进行修改。根据APPID为{9CA88EE3-ACB7-47C8-AFC4-AB702511C276}在注册表中找到HKEY_CLASSES_ROOTAppID{9CA88EE3-ACB7-47c8-AFC4-AB702511C276}右键选择权限:加入SYSTEM用户并赋予完全控制权限:如果在注册表中没有权限添加用户,则需...

获取用户当前位置信息的两种方法——H5、微信

在之前调用百度地图API的总结中,我使用H5获取了当前位置信息。事实上,微信还提供了一种获取用户地理位置的方法。现在我们将发布这两种方法,并根据情况选择使用它们。varspeed=响应。速度;//速度,单位:米/秒varaccuracy=res精度;//位置精度}});3、 从地理位置和地图显示中获得的经度和纬度可以使用百度地图或高德地图显示,也可以在微信内...