北京信息科技大学信控中心,北京 100192
输液泵在临床中常用于高危患者的治疗,以达到降低医疗风险、改善患者的治疗效果的目的[1-2]。而输液泵属于嵌入式系统的一种,其组成中负责控制功能的微处理器往往是8位或16位的单片机。此类设备运算能力和内部资源都有很大的局限性,如何保证利用有限的硬件实现系统的功能需求和实时性要求是设计人员的首要问题。
目前,很多输液泵都采用了基于指令功能的软件设计方法[3-4]。这种方法具有简单、节省资源的特点,但往往实时性较差。
本文采用多任务型软件架构,加入了任务的优先级调度机制,更加合理的利用了系统资源,同时使系统的实时性和可靠性得到的明显提高。
嵌入式软件的开发往往针对特定的系统,根据硬件平台的不同,“因地制宜”地围绕系统的具体需求而展开[5]。而不同的系统,其需求各不相同,因此嵌入式软件千差万别。
输液泵系统是以单片机为控制核心,通过对电机等机械装置的控制,实现药液输注的典型嵌入式系统。其软件的体系结构与一般嵌入式系统相较而言,有相同之处。总体而言,可以将其划分为如图1所示的三层:底层、中间层和顶层。底层包括系统引导程序和监控程序;中间层包括操作系统、板级支持包(BSP)、处理器支持包(PSP);顶层包括设备驱动程序、用户应用程序。在程序的设计过程中,顶层软件是设计的重点。
设备驱动程序是上层应用程序与硬件设备交互的桥梁。MQX操作系统中已经提供了对于通用硬件的支持,如GPIO、ADC等。但输液泵装置中的存在大量的专用硬件,还需要根据各个硬件的功能需求开发专用的设备驱动程序。
用户应用程序是完成输液泵业务逻辑的主要程序,是开发的最主要部分。不同的输液泵系统根据具体功能的不同,硬件结构的不同,实现方式的不同,其用户应用程序也各不相同。但总体而言,可以将用户应用程序划分为两大部分 :实时控制程序和安全保障程序。
实时控制程序:是完成输液泵主要功能的主体程序,实现了图形显示、电机控制、响应系统输入、数据处理等输液泵的核心功能;
安全保障程序:是对输液泵系统状态进行检测和保护的程序,可以在一定程度上保障了输液泵安全、可靠的运行。主要实现了压力检测、电机状态检测、气泡检测、数据流控制等与安全相关的功能。
由于输液泵系统属于对实时性、可靠性要求较高的嵌入式系统,因此在设计软件时需要特别考虑程序的运行效率。本设计采用功能模块化的设计方法,利用嵌入式操作系统MQX对多个任务模块进行管理的方式。这种设计方式可以极大地提高输液泵系统中程序执行的并发性,从而保证了系统实时性的要求。
按照输液泵系统的功能需求,将系统划分为5个任务模块,如图2所示。
(1)主循环模块:系统初始化、用户事件响应;
(2)按键处理模块:处理键盘输入;
(3)显示处理模块:实时更新LCD上的系统信息;
(4)周期检测任务模块:根据系统状态,周期检测各个异常状态;
(5)中断检测任务模块:运行状态下,检测电机相关状态。
每个任务的优先级及调用的任务函数各不相同,具体设置如表1所示。中断任务与普通任务的设置方式略有不同,需要使用“_int_install_isr()”系统函数在MQX操作系统中注册,在此不做具体说明。
表1 任务优先级及调用的函数说明
MQX系统的任务调度,采用优先级抢占CPU的方式实现。当前正在运行的任务,实际上并“不知道”系统中存在其他的任务,而是完全占用CPU资源独立运行[6]。只有当前任务运行结束、调用任务阻塞函数或任务延时函数时,其他处于就绪状态的任务根据优先级的高低,由优先级最高的任务抢占CPU,进入运行状态。在本系统中,周期检测任务在各个系统任务中的优先级最高,系统会首先检查该任务是否就绪,就绪则立刻转入并执行。如果未就绪,则会检测下一优先级的任务“主循环任务”,以此类推的直到系统对所有任务都进行了一次遍历。然后系统会重新检查周期检测任务是否就绪,开始下一次循环。具体调用流程如图3所示。
其中,比较特殊的是中断检测任务,其调度方式与一般系统任务不同,具有更高的优先级。在MQX系统中,中断任务一旦设置好触发条件和任务函数,只要满足了触发条件,无论当前哪个任务正在运行,都会保存当前任务运行的位置,并立即转入中断任务。
由于采用多任务抢占的设计模式,各个任务对数据进行处理后,不仅需要任务之间数据的通信,还需要进行任务间同步,保证数据的一致性。本设计采用了三种方式,全局变量、互斥量、轻量级消息队列。
全局变量“SysCore”是自定义的结构体变量,为了方便任务之间的通信,将与输液泵系统相关的几乎所有参数都包含在内,如系统状态、当前界面、返回界面、运行参数、硬件驱动接口。
互斥量主要设置在电机、LED等硬件驱动的模块中。由于驱动模块可能会被多个任务调用,当一个任务调用硬件驱动时,使用锁定互斥量独占资源,避免发生意外错误。
轻量级消息队列用于实现任务间的数据交换。实现方式是:由某一任务发送数据给轻量级消息队列,再由其他任务从消息队列中读取。
主循环任务是系统初始化完成后自动进入的第一个任务模块。在开始主循环之前,系统需要对硬件设备进行初始化,并利用MQX的任务列表和“_task_create()”函数创建各个任务。因此,主循环模块又分为了三个子模块:硬件初始化模块、任务初始化模块、循环响应模块。模块流程图如图4所示。
硬件初始化模块:主要功能是对各个设备的控制、检测端口进行初始化,主要包括LCD、电源、FLASH、键盘、蜂鸣器、LED、电机等。通过调用与各个硬件相对应的驱动函数,对硬件的初始状态进行设置,并初始化负责控制各个硬件的驱动结构体。
任务初始化模块:主要功能是创建除“main_task”以外的其他任务。由于系统初始化和任务初始化只有在系统启动时执行一次,如果失败的初始化或错误的初始化没能被系统识别,则可能对后续的程序运行带来无法预知的后果。因此,需要通过检查返回值的方式对任务创建的结果进行检查,即检查“_task_create()”函数的返回值是否为对应任务的“task_id”。一旦检查结果异常,则立即阻塞当前任务,系统进入异常状态,无法通过按键等进行操作。
循环响应模块:负责读取由按键处理任务传递的按键状态,并通过判断系统当前的状态调用对应的函数响应用户的输入。由于采用模块化的设计方法,系统的参数设置、输液模式选择、启停等控制函数都相互独立并存放于“ObjectOperationPool[]”数组中。该数组的类型为自定义的结构体“OBJECT_OP_STRUCT”,即数组的每个元素都是一个结构体。结构体包含的元素为“UP”、“DOWN”、“LEFT”、“RIGHT”、“OK”、“START”等按键响应的函数。再根据循环响应模块读取的系统状态和按键状态值,调用数组中哪个元素的哪个函数。
按键处理模块的主要功能是正确无误的读取用户向系统输入的控制指令,并将指令通过轻量级消息队列发送给相应的任务模块。从硬件角度看,外部输入键盘共有10个按键,分别为:“OK”、“Start”、“Stop”、“Mode”、“↑”、“↓”、“→”、“←”、“Bolus”、“Clear”。按键处理模块实现的具体功能为:
(1)键盘任务模块通过键盘驱动模块提供的“dev_kb_read()”函数读取键盘按键值,查询各个按键按下状态,即获取按下的是哪个按键的信息;
(2)根据按键状态判断用户的输入意图,并将用户输入的有效按键信息读入,如读入按键的初次按下状态、短按下状态、持续按下状态;
(3)将按键信息(键值和状态)通过轻量级消息队列的方式发送给主循环任务和显示处理任务。
按键处理模块的流程图如图5所示。首先进行初始化设置,对该任务所需要的变量和消息队列进行初始化,并读入系统数据,进入循环。然后,执行读取和发送按键信息的处理。利用键盘驱动模块提供的读取函数,将键盘对应的硬件状态值赋值到变量VALUE_CUR中。再将判断后的按键状态发送到消息队列中,完成一次循环。
显示处理模块的主要功能是读取系统信息,并利用LCD显示驱动控制LCD显示屏显示并实时更新提示信息。显示处理模块在系统启动后需要持续更新显示信息,因此需要使用while(1)循环,每执行完一次循环调用_time_delay()函数,等待下一次调用。在循环体内部,根据功能需求可以分为三个子功能模块:图形建立、报警界面切换、数据更新。显示处理模块的具体流程如图6所示。
图形建立模块的功能是根据系统状态,在LCD屏幕上创建一些提示性图标,如低温、电池电量等;
报警界面切换模块在每次调用时,会首先检查系统状态字信息,如果处于异常或故障状态,则会根据状态字调用相应的界面创建函数,进行界面报警处理;
数据更新模块负责调用信息显示历程,以遍历的方式逐条调用各个显示信息的更新函数,对系统各个显示信息进行更新。
周期检测任务模块的主要功能是在系统启动后,根据系统的当前运行状态,读取不同的传感器信号,用以检测系统当前状态是否正常。一旦检测函数发现系统处于异常或故障的状态,则会根据系统的状态触发不同类型、级别的报警并调用相关处理函数。由于需要在输液泵系统使用期间持续的对各类异常状态进行检测,因此需要使用while(1)循环,并在循环结束后调用_time_delay()函数,交出CPU占用权,等待下一次抢占。周期检测任务模块在循环体内部,按照功能的不同,划分为三个子模块,分别为:报警恢复检测、传感器信息读取、系统状态检测处理。周期检测任务模块的具体流程如图7所示。
报警恢复检测模块:主要起到一个计时器的作用。输液泵在使用时可能会出现一些需要用户处理的特定情况,如:待机超时、输液接近完成、电池电量过低。在出现这类不影响用户正常使用的情况时,需要发出可消除的报警信号,提醒用户进行处理。报警恢复检测模块的主要功能就是检测这些特定情况是否被工作人员及时处理。如果检测到在指定时间内,仍未解除特定情况,则会再次触发报警。
传感器信息读取模块:主要功能是正确的读取输液泵系统中传感器的状态信息。输液泵系统中共包括6种不同功能的传感器,分别实现泵门开启检测、输液管路气泡检测、输液管路压力检测、温度检测、电源状态检测、电机状态检测。设计时,考虑到电机相关状态的优先级较高,将其设置成中断形式的检测外,其他传感器信号的读取都在本模块中实现。传感器信号的读取需要考虑到传感器失效的情况。以负责气泡检测的超声波传感器为例,通过比对使能时的传感器数值和禁用后的传感器数值,可以判断传感器本身是否处于故障状态,进而判断读取信息的正确性。
系统状态检测处理模块:主要是根据读取的传感器信息,对系统状态进行逻辑上的判断。输液泵系统状态的检测分为系统故障状态的检测和系统异常状态的检测。系统故障状态的检测主要是对与安全性高度相关的硬件设备的检查,如电机状态、气泡传感器、压力传感器,目的在于及时发现硬件的失效,并及时触发相应的报警。系统异常状态的检测主要是是根据各类传感器的数值,判断输液泵是否出现了异常状态。系统状态检测处理的流程是:根据系统当前的运行状态,选择性地执行系统故障检测和系统异常检测中的某些函数,以判定当前系统是否正常运行。如果判定系统出现故障或异常,则启动声、光报警,停止电机运转并将系统状态置为当前的故障或异常的状态。输液泵运行状态与调用检测函数的详细情况如表2所示。
表2 输液泵运行状态与调用检测函数的详细情况表
中断检测任务模块主要是负责电机运行状态相关的检测,例如转动方向、转动速度。输液泵电机采用步进角为1.8°的两相步进电机,并使用16细分控制。电机每转一圈的脉冲数由公式(1)计算得3200。控制端每次发出脉冲信号为一次中断,电机中断函数为电机反馈计数器加一,作为反馈中断函数的判断依据。
其中,N—脉冲数;
ρ—电机旋转的角度;
ω—电机的步进角;
d—控制电机步进的细分数。
图8为输液泵电机状态监测的示意图,运行过程中电机每转一圈,图8中的两个光电开关分别被电机轴上的挡光片遮挡一次。以光电开关1的信号变化作为中断条件,调用反馈中断函数。
反馈中断函数流程图如图9所示,该函数读取两个光电开关的信号,进而由遮挡情况判断电机的转向,并判断计数器的值。如果计数值超过或低于允许的误差范围(±5%),则认为电机故障。反馈中断函数还会重置电机中断函数中的电机反馈计数器。
由于输液泵在实际使用中,精度受到输液器影响较大。因此,首先需要对输液器进行性能测试。选取市场上较为常见的三种输液器,在同样的设定流速下进行精度测试。测试结果如表2所示。可以看出在持续运行24小时后,输液器精度均有所下降,但哈娜好输液器的精度下降最少,适合作为测试用输液器。
表2 输液器精度下降测试结果
选择哈娜好输液器,分别采用不同的流速运行4小时,对输液泵进行精度测试。测试结果如表3所示。
表3 输液泵精度测试结果
实验结果表明除1ml/h和5ml/h两种极低流速情况外,但是在常规的流速状况下,输液泵可以满足5%的精度要求。
本文设计了应用于输液泵系统的多任务嵌入软件,给出了软件的体系结构、任务模块的划分、任务间通信以及任务模块的实现方法。这种设计方法,在一定程度上减少了系统资源的浪费,并且易于改进和扩充。系统功能模块化,在后期维护中具有很好的可读性和可理解性。