双水平呼吸机算法怎么写?(其实是记录自己写呼吸的心得)

双水平正压呼吸机是什么?

  • 市面上的双水平呼吸机,就是包含有双水平模式的呼吸机,其中一般也会包含单水平模式。
  • 其中正压的意思,就是抬高呼吸的压力基线,使吸气顺畅一些。

CPAP

呼吸机硬件参考

不能给太详细,就给个名字,具体的自己设计。

压力传感器、压力差传感器、数字型电机驱动器、带霍尔的涡轮风机(和驱动器配合一起,最好找一家同时生产涡轮风机和驱动器的厂商)等(其他的额外的东西就不重要啦!)。

呼吸机模式有哪些

就拿鱼跃的YH-820呼吸机来说,其:

  • 单水平模式有CPAP
  • 双水平模式有S、T、ST、VGPS模式。

CPAP模式介绍(主要写的算法1)

CPAP诞生的历史是最早,沙利文博士搞的第一台呼吸机的就是这模式。

我的写法是按照《双水平呼吸机控制逻辑策略的设计与实现》这个论文的说法来写的,以下摘抄自论文双水平呼吸机控制逻辑策略的设计与实现。

CPAP模式下,呼吸机在整个呼吸过程中,给患者的气道提供恒定的压力支持。患者进行呼吸切换时,呼吸机将动态调整内部涡轮风机的转速,抵抗患者呼吸动作造成的干扰,维持恒定的压力。由于呼气和吸气时,呼吸机提供相同的压力,患者在呼气时需要克服压力做功,舒适度一般。

虽然都叫CPAP,但是网上的CPAP各有各的说法,比如我的师兄,写的CPAP是固定气道压力的CPAP,还是有点不一样(笑)。

APAP模式介绍(主要写的算法2,加一条)

APAP叫 Auto CPAP,自动的CPAP,哪里自动了?原来是在人呼吸阻塞的时候,给气道加个压呀!
本来呼吸的好好的,突然压力没有变化了!为啥?原来是人不呼吸了,加压!必须马上加压!

然后等人正常呼吸后,再恢复原本的压力。

S模式介绍(主要写的算法3)

双水平模式来喽!双水平是飞利浦第一个搞的。牛!

其和单水平的区别在于呼气和吸气时压力不一致!上图!
在这里插入图片描述

如何做到呢?先知道如何识别呼吸吧(算法部分再讲)!

T模式简单介绍

就是用户自己设定一个呼吸周期,吸气占呼气比例多少,然后病人被动的跟着呼吸机呼吸。

ST模式简单介绍

就是把S模式和T模式结合起来了,根据病人的呼气周期、吸气占比,来动态的调整呼气和吸气的时间。

VGPS 不-介绍

算法涉及到哪些内容?

气道压力对应电机速度、延时升压、呼吸罩穿戴检测、呼吸阻塞检测、阻塞升压、呼吸切换检测、呼气吸气切换时间安排、pid动态调压等。

  • 气道压力对应电机速度:输入一个想要控制的气道压力值,输出电机速度,使气道达到该压力值。
  • 延时升压:这个时基于患者舒适度考虑的,设定一个初始压力,在初始压力的基础上,不断升压,在设定时间内升压到设定压力
  • pid动态调压:
    • 在单水平模式和双水平模式的非呼吸切换状态下,根据气道的压力,动态调节涡轮风机的转速,使气道压力动态稳定
    • 在双水平的呼吸切换状态下,快速切换呼气和吸气的气道压力
  • 呼吸切换检测:检测患者切换呼吸状态;
  • 呼吸阻塞检测:检测患者呼吸受阻;
  • 呼气吸气切换时间安排:在双水平模式下,切换两种气道压力时的切换时间,呼气到吸气为上升时间,吸气到呼吸为下降时间;
  • 阻塞升压:在APAP模式下,当患者呼吸受阻时,自动提升气道的压力,直到患者恢复正常呼吸;
  • 呼吸面罩的穿戴检测:检测人是否带上呼吸面罩。

算法实现

气道压力对应电机速度

我的控制电机是通过中间驱动器的,我对驱动器输入多少速度,电机就会自己达到那个速度,所以对于电机速度上,我不用管(你们要不要搞一个电机速度和电流的关系就不关我事了嘿嘿)。

如何做到电机速度对应压力呢?最简单的办法就是作拟合曲线。

上吧!我的 Excel !

戴上面罩的压力数据以及对应的电机速度
测出带上面罩的压力数据以及对应的电机速度数据,对这两列建立散点图。
建立散点图
然后右键图像曲线,把这两列框上即可。
选择数据1
选择数据2
选择数据3

选择好后确认即可。

生成拟合曲线:

先添加趋势线,然后选择合适的趋势线选项,并且显示趋势线公式还有R2(R2越接近1说明拟合效果越好)
添加趋势线

选择趋势线

显示公式
上面的公式是:公式1
只要我想让气道输出一个固定的压力,比如4cmH2O,带入这个公式,算出的电机速度,直接发给驱动器就可以了。

验证一下:
计算器验证
是不是很相近了!

具体代码相关函数:

/*********************************************************************************************************
* 函数名称:PressureToMotoSpeed
* 函数功能:输入气压参数控制电机转速
* 输入参数:cmH2O:4.0 - 20.0 cmH2O
* 输出参数:void
* 返 回 值:void
* 创建日期:2023年11月16日
* 注    意:根据公式1:y = 0.06573 x3 - 3.93066 x2 + 126.67006 x + 40.14498, R2 = 0.99997 ,这一条是电机对面罩输出气压
*********************************************************************************************************/
void PressureToMotoSpeed(double cmH2O)
{
	unsigned int realSpeed = 0;
	
	realSpeed = (unsigned int)(0.06573*cmH2O*cmH2O*cmH2O - 3.93066*cmH2O*cmH2O + 126.67*cmH2O + 40.14498);
	
	CtrlMotoSpeed(realSpeed);
}

这是一个函数,传入参数是厘米水柱,没有输出值。函数的作用是把厘米水柱的值,直接转换成电机速度,然后调用电机速度控制函数,直接控制电机。

延时升压

延时升压,经过一段时间,从初始压力逐渐上升到设定压力。

有几个参数需要注意:初始压力、设定压力、升压时间。

  • 初始压力:刚启动时的压力;
  • 设定压力:经过延时升压最终达到的压力;
  • 升压时间:升压需要的时间。

根据鱼跃的YH-820呼吸机来看,他的延时升压是以0.1分钟为单位的,也就是每过0.1分钟,就上升一次压力,也就是我们得根据这个0.1分钟来细分升压的压力,例子如下:

初始压力 == 5 cmH2O
设定压力 == 10 cmH2O
升压时间 == 5min

我们先把 5min 乘上 10 == 50,也就是需要进行50次升压。
用 设定压力 - 初始压力 = 10 - 5 = 5 cmH2O,也就是总共需要升高5cmH2O的压力。

用 5cmH2O / 50次 = 0.1 cmH2O,也就是说每过 0.1分钟,需要升高 0.1cmH2O。

具体代码片段:

// 延时升压,当(s_boostPressureCnt2 > s_boostPressureCnt)时,延时升压结束。
if( s_boostPressureCnt2 <= s_boostPressureCnt )
{
	if( e_iTimCnt >= s_DelayBoostStartTime + 6000*s_boostPressureCnt2 )
	{
		s_boostPressureCnt2++;
		
		ctrlPressure += (Monitor_Set_Pressure-Monitor_Initial_Pressure) / (DelayedBoost*10);
		// 延时升压的最后一轮升压,直接变成设定的压力,防止浮点计算出现的误差。
		if( s_boostPressureCnt2 == s_boostPressureCnt )
		{
			ctrlPressure = Monitor_Set_Pressure;
		}
	}
}

以上参数解释:

  • s_boostPressureCnt:总升压次数,如上例中的50次;
  • s_boostPressureCnt2:当前升压的计数,每升一次压加1,直到大于总升压次数;
  • e_iTimCnt:系统计时时间,每过1ms加1;
  • s_DelayBoostStartTime:起始升压的时间,进入呼吸模式时记录的系统时间;
  • 其中在系统计时中,6000 == 0.1min;
  • ctrlPressure:当前压力;
  • Monitor_Set_Pressure:设定压力;
  • Monitor_Initial_Pressure:初始压力;
  • DelayedBoost:延时升压的时间,如上例中的5min。

算法解释:

当前升压计数 <= 总升压次数,且升压最小时间到达,则升压一次,当最后一轮升压时,直接赋值设定压力。

呼吸罩穿戴检测

如何检测呼吸面罩是否穿戴?有的人用硬件解决这个问题(专门搞个模块检测的),有的人用软件解决这个问题(用 压力 或 压力差 传感器来看),我属于后者。

压力 传感器怎么看

有了上面的带上面罩的压力速度曲线后,咱们再加一条没带上面罩的压力数据曲线。
数据
两个压力的曲线
再对两个压力作拟合曲线,这样就可以知道在没戴面罩时对应的压力是多少了。
例如:
我设定一个气道压力维持在4.48,此时是我带上面罩应该有的气道压力,但是我实际检测到气道压力小于等于1.55(这里可以加一个常数偏大一点,因为脱面罩不会老是在这个值位置,可以会偏一点),这个时候我就可以认定是没带面罩了。

具体代码片段:

miniPressure = 0.0266*(double)ctrlPressure + 1.4685;
// 带着面罩
if( measurePressure > miniPressure )
{
	WearingMaskCnt2 = 0;
	if( WearingMaskCnt1++ > 10 )
	{
		WearingMaskCnt1 = 0;
		// 清除未戴面罩标志位
		WearingMaskFla = 0;
	}
}
else
{
	WearingMaskCnt1 = 0;
	if( WearingMaskCnt2++ > 10 )
	{
		WearingMaskCnt2 = 0;
		// 置位未戴面罩标志位
		WearingMaskFla = 1;
	}
}

参数解释:

  • measurePressure:这是实际测量的气道压力值。
  • ctrlPressure:这是实际应该要输出的戴面罩的压力值。
  • miniPressure:这是对应压力下未带面罩时应该有的压力值。

算法解释:
当实测压力值大于未戴面罩压力值,且持续一段时间,则清除 未带面罩标志位(此时就相当于戴上了面罩),否则进入else一段时间,则为置位 未带面罩标志位。

压力差 传感器怎么看

压力差传感器可以相当于流量传感器的作用,当流量大于一定阈值时,就是没戴上面罩,反之则戴上了。

因为带上面罩气流值基本很小了,没带上面罩时,就算电机转的很慢,气流也会很大。

呼吸切换检测

呼气吸气怎么检测?

哎呀直接上图:

气道压力变化图
这个是我实测的气道压力变化图。
我只要一呼气,气道压力迅速上升,而后缓慢下降;
我只要一吸气,气道压力迅速下降,然后缓慢上升;

其中有两个点很好捕捉的,就是迅速上升和迅速下降的两个点。

那就可以用 判断趋势 + 判断阈值 两重办法来弄。

代码片段如下:

// 上升	+ 阈值 = 呼气
if( measurePressure > LastPressure )
{
	exhaleeCnt++;
	if( exhaleeCnt >= ExhalationTriggerS )
	{
		if( measurePressure > ExhalationThreshold )
		{
			// 呼气计数清零
			exhaleeCnt = 0;
			// 吸气计数清零
			inhaleCnt = 0;
			// 呼气
			printf("呼气\r\n");
		}
	}
}
else
{
	exhaleeCnt = 0;
}

以上只有呼气部分,是因为吸气部分和呼气的内容大差不差,就简略掉了。

参数解释:

  • measurePressure:当前气道压力;
  • LastPressure:上次检测气道压力;
  • exhaleeCnt:呼气上升计数;
  • ExhalationTriggerS:呼气触发敏感度;
  • ExhalationThreshold:呼气阈值;
  • inhaleCnt:吸气下降计数;

算法解释:
当连续上升一定次数时,并且当前的气道气压大于设置的阈值时,就判定为呼气;一旦在上升期间出现下降,则清空上升计数。

注意:
ExhalationTriggerS 和 ExhalationThreshold 参数都得自己耐心调试得到哦!

呼吸阻塞检测

呼吸阻塞检测简单呀,当一段时间不呼气也不吸气就是阻塞了,这个时候直接判定为阻塞。

代码如下:

if( breathe == 0 )
{
	// 阻塞判断
	if( NotBreathingCnt++ > NotBreathingVal )
	{
		NotBreathingCnt = 0;
		// 陷入阻塞
		printf("阻塞\r\n");
	}
}
else
{
	NotBreathingCnt = 0;
}

参数解释:
breathe:不呼吸为0,呼吸为1;
NotBreathingCnt:不呼吸的持续计数值;
NotBreathingVal:不呼吸多久的认为设定参数值,需要自己调试得到。

算法解释:
当不呼吸且持续了一段时间,则陷入阻塞,否则清除不呼吸计数。

阻塞升压

在APAP模式中,陷入阻塞后逐渐上升压力。

// 不阻塞
if( TrappedInBlockage == 0 )
{
	maintainCnt -= 10;
	if( maintainCnt < -60 )
	{
		maintainCnt = 0;
		if( ctrlPressure > SetPressure + (float)0.1 )
		{
			maintainPIDValCnt = 0;
			ctrlPressure -= (float)0.5;
		}
	}
}
// 陷入阻塞
else if( TrappedInBlockage == 1 )
{
	if( maintainCnt++ > 300 )
	{
		maintainCnt = 0;
		
		ctrlPressure += (float)0.5;
		if( ctrlPressure > (float)25.0 )
		{
			ctrlPressure = 25;
		}
		else if( ctrlPressure > SetPressureApap + AutopressureVal + (float)0.1 )
		{
			ctrlPressure = SetPressureApap + AutopressureVal;
		}
	}
}

参数解释:

  • TrappedInBlockage:陷入阻塞标志位;
  • maintainCnt:维持状态的计数值;
  • ctrlPressure:当前的控制的气道压力;
  • SetPressure:设置的气道压力;
  • 阻塞一定时间就上升一次0.5cmH2O;
  • 最大的气道压力限制在25cmH2O;
  • AutopressureVal:最大阻塞升压值,阻塞后最大能达到的压力就是(SetPressureApap + AutopressureVal)。

算法解释:
陷入阻塞一段时间后上升一定的压力,持续这个动作,直到压力上升达到极限。反之当不阻塞时,迅速减小压力,直到回归原本的压力。

呼气吸气切换时间安排

对于双水平中,有上升时间和下降时间的概念,意思就是说,当人呼吸切换时,气道压力切换需要的时间。
例如:呼气切换到吸气,气道压力需要上升到吸气需要的压力,这个时候,给个一定的逐步上升时间,可以给人一定的舒适感。

具体代码内容放到双水平S模式讲解。

pid动态调压

对于pid不了解的可以看看这个文章:PID超详细教程——PID原理+串级PID+C代码+在线仿真调参

对于pid动态调压,最难的点就是pid参数的设置,其他都很简单,但是我这里不讲pid参数怎么调节啦!大家自己想办法吧(hhh)!

代码如下:

// 呼吸压力变化的情况下稳定压力。
pressureStabilityPID.target = ctrlPressure;
pid_calculate(&pressureStabilityPID, measurePressure);
if( pressureStabilityPID.output < 0 )
{
	PressureToMotoSpeed(0);
}
else if( pressureStabilityPID.output > 25 )
{
	PressureToMotoSpeed(25);
}
else
{
	PressureToMotoSpeed(pressureStabilityPID.output);
}

// pid 输出过高,一般是取下面罩导致的
if( (pressureStabilityPID.output - ctrlPressure) > 3 )
{
	// 进入稳定区域
}
// 气压过低,也是取下面罩导致的
if( measurePressure < miniPressure )
{
	// 进入稳定区域
}

参数解释:

  • pressureStabilityPID:pid结构体,包含pid的一些参数,如p、i、d参数、target目标值、output输出值等。
  • ctrlPressure:当前的控制的气道压力;
  • measurePressure:气压传感器读取的气道气压值。
  • pid_calculate(&pressureStabilityPID, measurePressure):对目标值和实际值之间做pid计算,计算出的值给到 pressureStabilityPID.output;
  • 不能输出负数的气压值,小于0就让他等于0;
  • 限制气压最大在25cmH2O以下;
  • pid的输出最大不要超过ctrlPressure一定值,自己设定一个合适值即可;
  • miniPressure:未戴面罩应该有的压力值。

算法解释:
对目标气压值和实际气压值之间做pid运算,输出的气压值做一个限制,在一个范围内。当压力过小时,或者pid输出过大时(相当于没戴面罩),减小电机转速进入未戴面罩的稳定区域。

具体呼吸机模式编写

不管是什么呼吸机模式,都可以给他拆分为多个状态,如常见的几种状态:正常运行模式,未带面罩时的低风速模式,过度到低风速模式的中间模式等

CPAP

CPAP可以分为:戴上面罩的正常运行模式、戴上面罩向没有戴面罩的风机转速过度模式、未戴上面罩的低风速模式、未戴面罩向戴上面罩的风机转速过度模式。

为什么要有过度模式,因为我的电机转速变化太快会停转,所以加了一个过度模式。

戴上面罩的正常运行模式(状态0,overvoltageStopFlag == 0)

要求:pid需要正常运行、实时控制电机转速、要检测未戴面罩;

代码如下:

// 呼吸压力变化的情况下稳定压力。
pressureStabilityPID.target = ctrlPressure;
pid_calculate(&pressureStabilityPID, measurePressure);
if( pressureStabilityPID.output < 0 )
{
	PressureToMotoSpeed(0);
}
else if( pressureStabilityPID.output > 25 )
{
	PressureToMotoSpeed(25);
}
else
{
	PressureToMotoSpeed(pressureStabilityPID.output);
}

// pid 输出过高
if( (pressureStabilityPID.output - ctrlPressure) > 3 )
{
	overvoltageStopFlag = 2;
}
// 气压过低
if( measurePressure < miniPressure )
{
	overvoltageStopFlag = 2;
}

以上代码有没有和pid动态调压部分很像!其实就是一个东西hh。

戴上面罩向没有戴面罩的风机转速过度模式(状态2,overvoltageStopFlag == 2)

要求:要延时一段时间稳定电机转速、清除pid的缓存值;

if( overvoltageStopCnt++ > 80 )
{
	overvoltageStopCnt = 0;
	overvoltageStopFlag = 3;
	pid_clear(&pressureStabilityPID);
}
// 输出风机转速
PressureToMotoSpeed(ctrlPressure/2);

控制电机转速降低一半,也可以逐步降低,看自己怎么写。

未戴上面罩的低风速模式(状态3,overvoltageStopFlag == 3)

要求:检测呼吸。

// 检测到呼吸
if( ABS(LastBreathePressureVal - measurePressure ) > 0.06 )
{
	LastBreathePressureVal = measurePressure;
	
	BreathePressureCnt += 5;
	if( BreathePressureCnt > 10 )
	{
		BreathePressureCnt = 0;
		
		// 呼气启动
		overvoltageStopFlag = 1;
	}
}
else
{
	if( BreathePressureCnt > -10 )
	{
		BreathePressureCnt--;
	}
}

解释:

  • ABS是取绝对值。
  • 检测呼吸的判断就是,上次压力比这次压力差值大于一个自己调试出来的参数值(0.06),连续好几次就是呼吸了;

未戴面罩向戴上面罩的风机转速过度模式(状态1,overvoltageStopFlag == 1)

要求:提前稳定pid、电机转速进入过度速度。

if( overvoltageStopCnt++ > 130 )
{
	overvoltageStopCnt = 0;
	overvoltageStopFlag = 0;
}
pressureStabilityPID.target = ctrlPressure;
pid_calculate(&pressureStabilityPID, measurePressure);
// 输出风机转速
PressureToMotoSpeed(ctrlPressure/2);

控制电机转速为实际输出的一半,也可以逐步上升,看自己怎么写。
过程中一直计算pid,使到达状态0。

APAP

APAP和CPAP差不多,只加入了阻塞升压的功能,具体内容直接翻看上面的阻塞升压部分,这部分只在状态0生效。

双水平S模式

双水平的状态就多喽,不过也没多到哪里去,也就9种状态,呼气3种,吸气3种,戴没戴面罩切换2种,没戴面罩低风速1种。

呼气3种状态(状态1,3,5,overvoltageStopFlag == 1或3或5)

呼气正常pid调节状态(pid正常运算,判断pid过大,判断没戴面罩,判断吸气)、吸气上升(逐步上升输出气道压力、pid运算)、等待pid达到气道压力(pid控制电机、pi的稳定判断、判断pid过大)

呼气正常pid调节状态(状态1,overvoltageStopFlag == 1)

pid正常运算,判断pid过大,判断没戴面罩,判断吸气

// 呼吸压力变化的情况下稳定压力。
pressureStabilityPID.target = ctrlPressure;
pid_calculate(&pressureStabilityPID, measurePressure);
if( pressureStabilityPID.output < 0 )
{
	PressureToMotoSpeed(0);
}
else if( pressureStabilityPID.output > 24 )
{
	PressureToMotoSpeed(24);
}
else
{
	PressureToMotoSpeed(pressureStabilityPID.output);
}
// 下降	+ 阈值 = 吸气
if( LastPressure > measurePressure )
{
	inhaleCnt++;
	if( inhaleCnt >= InspiratoryTriggerS )
	{
		if( measurePressure < ctrlPressure )
		{
			inhaleCnt = 0;
			exhaleeCnt = 0;
			// 吸气
			printf("吸气\r\n");
			
			// 吸气上升
			overvoltageStopFlag = 3;
			
			// 设置新的pid参数
			pid_init(&pressureStabilityPID, PID_Position, P2, I2, D2);
		}
	}
}
else
{
	inhaleCnt = 0;
}
// pid 输出过高
if( (pressureStabilityPID.output - ctrlPressure) > 3 )
{
	overvoltageStopFlag = 6;
}
// 气压过低
if( measurePressure < miniPressure )
{
	overvoltageStopFlag = 6;
}

这些东西其实之前都介绍过了。
就是把他们杂糅起来而已。

其中这里设置了不同的pid参数:

pid_init(&pressureStabilityPID, PID_Position, P2, I2, D2);

为什么这样设置?
我们的pid参数实际上分为两种,一个是稳定状态下的pid动态调压,一个是呼吸切换时的快速pid切换调压,这里就是切换到快速pid调压。

吸气上升(状态3,overvoltageStopFlag == 3)

逐步上升输出气道压力、pid运算。

// pid计算
pressureStabilityPID.target = SuctionPressureS;
pid_calculate(&pressureStabilityPID, measurePressure);
// 逐渐上升
ctrlPressure += riseTimeMiniInterval;
// 输出风机转速
PressureToMotoSpeed(ctrlPressure);

if( overvoltageStopCnt++ >= 20*RiseTimeS )
{
	overvoltageStopCnt = 0;
	// 稳定吸气压力pid
	overvoltageStopFlag = 5;
	
	// 设置输出吸气压
	ctrlPressure = SuctionPressureS;
}

这一段是吸气上升吸气上升阶段,包含了电机逐步上升,pid跟随上升,吸气上升结束时切换到等待pid稳定状态。

等待pid达到气道压力

pid控制电机、pi的稳定判断、判断pid过大;

// pid计算
pressureStabilityPID.target = ctrlPressure;
pid_calculate(&pressureStabilityPID, measurePressure);
if( pressureStabilityPID.output < 0 )
{
	PressureToMotoSpeed(0);
}
else if( pressureStabilityPID.output > 24 )
{
	PressureToMotoSpeed(24);
}
else
{
	PressureToMotoSpeed(pressureStabilityPID.output);
}

// pid稳定
if( ABS(ctrlPressure - pressureStabilityPID.output) < (float)0.1 )
{
	overvoltageStopFlag = 1;
	// 恢复pid参数
	pid_init(&pressureStabilityPID, PID_Position, P1, I1, D1);
}

// pid 输出过高
if( (pressureStabilityPID.output - ctrlPressure) > 6 )
{
	overvoltageStopFlag = 6;
}

恢复正常运行状态下的pid参数:

pid_init(&pressureStabilityPID, PID_Position, P1, I1, D1);

这里的pid过高阈值设置的比较高,是因为这个参数的pid比较暴躁(应该是我没调好参数)。

吸气3种状态(状态0,2,4,overvoltageStopFlag == 0或2或4)

和呼气没太大区别,不写了!

戴面罩到没戴面罩过程中的2种状态(状态6,7,overvoltageStopFlag == 6或7)

电机稳定到设定气道压力的一半
if( overvoltageStopCnt++ > 80 )
{
	overvoltageStopCnt = 0;
	overvoltageStopFlag = 7;
	pid_clear(&pressureStabilityPID);
	pressureStabilityPID.output = ctrlPressure*2;
}
// 输出风机转速
PressureToMotoSpeed(ctrlPressure/2);
电机稳定到一个较小值
if( overvoltageStopCnt++ > 80 )
{
	overvoltageStopCnt = 0;
	overvoltageStopFlag = 8;
}
// 输出风机转速
PressureToMotoSpeed(miniPressure);

没戴面罩低风速1种(状态8,overvoltageStopFlag == 8)

// 检测到呼吸

if( ABS(LastBreathePressureVal - measurePressure) > (float)0.06 )
{
	LastBreathePressureVal = measurePressure;
	
	BreathePressureCnt += 5;
	if( BreathePressureCnt > 10 )
	{
		BreathePressureCnt = 0;
		
		// 呼气启动
		overvoltageStopFlag = 1;
		ctrlPressure = ExpiratoryPressureS;
		// 恢复pid参数
		pid_init(&pressureStabilityPID, PID_Position, P1, I1, D1);
		exhaleeCnt = 0;
		inhaleCnt = 0;
		overvoltageStopCnt = 0;
	}
}
else
{
	if( BreathePressureCnt > -10 )
	{
		BreathePressureCnt--;
	}
}

总结

大概就这些啦,其中代码都是简略贴上来的,部分耦合思路还得自己去想啦,就提供个大概方向,尼玛写的真累!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/233434.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

机械中常用的一些术语

目录 一、OEMSOP:SOP编写指南 WI(标准作业指导书):标准作业程序 &#xff08;SOP&#xff09;:SOP和WI的区别&#xff1a;一、PFC、FMEA、PCP、WIPPAP、PSW&#xff1a; 一、OEM 1.OEM&#xff1a; 原始设备制造商OEM&#xff08;Original Equipment Manufacturer&#xff09;…

从零开始的C++(二十一)

C11 1.列表初始化&#xff1a; //允许以下代码正确运行int a[]{1,2,3};//效果与int a[]{1,2,3}一致 即允许省略等于号。同时&#xff0c;允许用花括号对所有自定义类型和内置类型进行初始化&#xff0c;而非以前花括号只能对数组进行初始化。利用花括号对自定义类型初始化时…

数据结构和算法-单链表

数据结构和算法-单链表 1. 链表介绍 链表是有序的列表&#xff0c;但是它在内存中是存储如下 图1 单链表示意图 小结: 链表是以节点的方式存储每个节点包含data域&#xff0c;next域&#xff0c;指向下一个节点。如图&#xff1a;发现链表的各个节点不一定是连续存储。比如地…

C语言函数详解

# 函数的概念 对于函数&#xff0c;我想大家应该并不陌生&#xff0c;在数学中就存在函数的概念&#xff0c;比如&#xff1a;一次函数 ykxb &#xff0c;k和b都是常数&#xff0c;给⼀个任意的x&#xff0c;就能得到⼀个y值。 在C语言中也有函数的概念&#xff0c;函数也被称为…

unity 模型生成PNG图片并导出(可以任意控制方向和大小,本文提供三种方案)

提示&#xff1a;文章有错误的地方&#xff0c;还望诸位大神不吝指教&#xff01; 文章目录 前言一、插件RuntimePreviewGenerator&#xff08;方案一&#xff09;二、unity 官方提供的接口&#xff08;方案二&#xff09;三、方法三&#xff0c;可以处理单个模型&#xff0c;也…

STM32基于USB串口通信应用开发

✅作者简介&#xff1a;热爱科研的嵌入式开发者&#xff0c;修心和技术同步精进&#xff0c; 代码获取、问题探讨及文章转载可私信。 ☁ 愿你的生命中有够多的云翳,来造就一个美丽的黄昏。 &#x1f34e;获取更多嵌入式资料可点击链接进群领取&#xff0c;谢谢支持&#xff01;…

git自动更新功能

确认权限 因为一般Linux系统网页用的www 或 www-data用户和用户组,所以要实现自动来去,首先要在www用户权限下生成ssh密钥,不然没有权限,其次就是,要把用root用户拉去的代码,批量改成www用户 1. 给www权限 vi /etc/sudoers www ALL=(ALL) NOPASSWD:/bin/chow…

整体式雨水收集pp模块可根据需求承重可达30到60吨每平方米

整体式雨水收集pp模块的承重能力主要取决于其设计和制造工艺&#xff0c;以及所使用的材料。一般来说&#xff0c;模块的尺寸越大&#xff0c;承重能力也越大。同时&#xff0c;模块的设计和制造工艺也会影响其承重能力。 在设计和制造整体式雨水收集pp模块时&#xff0c;需要…

unity 2d 入门 飞翔小鸟 小鸟碰撞 及死亡(九)

1、给地面&#xff0c;柱体这种添加2d盒装碰撞器&#xff0c;小鸟移动碰到就不会动了 2、修改小鸟的脚本&#xff08;脚本命名不规范&#xff0c;不要在意&#xff09; using System.Collections; using System.Collections.Generic; using UnityEngine;public class Fly : Mo…

CompletableFuture异步多任务最佳实践

简介 CompletableFuture相比于Java8的并行流&#xff0c;对于处理并发的IO密集型任务有着得天独厚的优势&#xff1a; 在流式编程下&#xff0c;支持构建任务流时即可执行任务。CompletableFuture任务支持提交到自定义线程池&#xff0c;调优方便。 本文所有案例都会基于这样…

向日葵远程控制鼠标异常的问题

​ 在通过向日葵进行远程控制的时候&#xff0c;可能会遇到鼠标位置异常的问题。此时&#xff0c;不管怎么移动鼠标&#xff0c;都会停留在屏幕最上方&#xff0c;而无法点击到正确的位置。如图&#xff1a; 此时&#xff0c;如果启用了“被控端鼠标”功能&#xff0c;可以正…

ChatGLM3-6B和langchain阿里云部署

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、ChatGLM3-6B部署搭建环境部署GLM3 二、Chatglm2-6blangchain部署三、Tips四、总结 前言 提示&#xff1a;这里可以添加本文要记录的大概内容&#xff1a; …

回顾2023 亚马逊云科技 re_Invent,创新AI,一路同行

作为全球云计算龙头企业的亚马逊云科技于2023年11月27日至12月1日在美国拉斯维加斯举办了2023 亚马逊云科技 re:Invent&#xff0c;从2012年开始举办的亚马逊云科技 re:Invent 全球大会,到现如今2023 亚马逊云科技 re:Invent&#xff0c;回顾历届re:Invent大会&#xff0c;亚马…

imutils库介绍及安装学习

目录 本机环境 安装 函数及属性 列举imutils库信息 属性和函数介绍及使用 属性 常用函数 方法使用 图像平移 图像缩放 图像旋转 骨架提取 通道转换 OPenCV版本的检测 综合测试 介绍 imutils 是一个用于图像处理和计算机视觉任务的 Python 工具包。它提供了一系…

Python爬虫-实现批量抓取王者荣耀皮肤图片并保存到本地

前言 本文是该专栏的第12篇,后面会持续分享python爬虫案例干货,记得关注。 本文以王者荣耀的英雄皮肤为例,用python实现批量抓取“全部英雄”的皮肤图片,并将图片“批量保存”到本地。具体实现思路和详细逻辑,笔者将在正文结合完整代码进行详细介绍。注意,这里抓取的图片…

centos7中的计划任务

一次调度执行-----at 安装&#xff1a; [rootzaotounan ~]# yum -y install at ​ 启动&#xff1a; [rootzaotounan ~]# systemctl start atd ​ 开机自启动&#xff1a; [rootzaotounan ~]# systemctl enbale atd ​ 语法&#xff1a; at <时间规格> 时间规格参数&…

Linux下C++动态链接库的生成以及使用

目录 一.前言二.生成动态链接库三.使用动态链接库 一.前言 这篇文章简单讨论一下Linux下如何使用gcc/g生成和使用C动态链接库&#xff08;.so文件&#xff09;。 二.生成动态链接库 先看下目录结构 然后看下代码 //demo.h#ifndef DEMO_H #define DEMO_H#include<string&…

键盘打字盲打练习系列之矫正坐姿——4

一.欢迎来到我的酒馆 盲打&#xff0c;矫正坐姿&#xff01; 目录 一.欢迎来到我的酒馆二.继续练习二.矫正坐姿1.键盘与鼠标2.椅子 三.改善坐姿建议 二.继续练习 前面的章节&#xff0c;我们重点向大家介绍了主键盘区指法和键盘键位。经过一个系列的教程学习&#xff0c;相信大…

C语言数据结构-双向链表

文章目录 1 双向链表的结构2 双向链表的实现2.1 定义双向链表的数据结构2.2 打印链表2.3 初始化链表2.4 销毁链表2.5 尾插,头插2.6 尾删,头删2.7 根据头次出现数据找下标2.8 定点前插入2.9 删除pos位置2.10 定点后插入 3 完整代码3.1 List.h3.2 Lish.c3.3 test.c 1 双向链表的结…

redis中缓存雪崩,缓存穿透,缓存击穿等

缓存雪崩 由于原有缓存失效&#xff08;或者数据未加载到缓存中&#xff09;&#xff0c;新缓存未到期间&#xff08;缓存正常从Redis中获取&#xff0c;如下图&#xff09;所有原本应该访问缓存的请求都去查询数据库了&#xff0c;而对数据库CPU和内存造成巨大压力&#xff0c…