下面以笔者课题组开发的12导同步心电图机为例,介绍以普通52单片机为主控芯片应用串行热敏打印机实现多种方式的心电图形打印,并重点描述了12导联同步打印方式的程序实现方案。
1 系统硬件设计
系统配置了一个内置式数字打印机,它主要由热敏打印头(w216-qs)和步进电机组成。w126-qs点阵式热敏打印头打印数据采用串行输入,其内部不仅包含有由c-mos集成芯片构成的1728位移位寄存器,还包含借助高密度厚膜工艺制成的加热元件。这些加热元件通过锁存和切换晶体管驱动,可在热敏打印纸上产生1728个点,对应的打印宽度为216mm,分辨率为8dot/mm。热敏打印头所需的打印数据为串行数据,数据传输遵循spi口的通信协议。系统采用了口线模拟spi的工作方式与打印头通信,电路如图1所示。
考虑到52单片机内部令有256字节的内部ram,系统还外扩1片hm628128存储12导心电数据和中间转换结果。
2 系统软件编写
数字打印实现的两个关键问题:①如何将心电数据转换成打印数据;②如果将数据输出到数字打印机。通常采用的方法是转换数据同时将其输出到打印机打印。这样节省了存储器空间;缺点是程序实现复杂,通用性差(不同打印方式的数据输出程序不同),系统功能不易扩展,数据转换和输出都要考虑打印点位置,并且每输出一点的数据都要调用一次程序,加大了系统开销。系统软件中没有采有这种方式,而是在内存中开辟216字节打印缓冲区,将热敏打印头1728个点与216×8位数据相对应,每次将要打印的一线数据都转换完再输出。这样只需在数据转换时考虑打印位置和方式,输出程序只需将216字节的数据按位输出即可,并且每打印一线数据只需调用一次输出子程序,字节了系统开销。缺点是占用系统资源,这一点在12导同步打印表现得尤为明显。
系统程序实现三种打印方式:分两次打印12导联数据,每次打印6导、12导同步打印、纵向打印(打印效果如图2)。在每一种打印程序中实现模拟spi口将打印数据送至数字打印机的子程序共用,不同处在于如何将心电数据转换成打印数据。
2.1 i/o口线模拟spi口
spi(serial peripheral interface)总线串口是由motorola公司提出的一种同步串行外设接口,通过四根线进行通信:时钟线(spkclk)、数据输出线(spimiso)、数据输出线(spimosi)、片选线(cs),内部通过spidat寄存器完成串-并/并-串转换。它主要工作在主从式系统中,一个主器件可以带多个从器件,主器件通过片选线控制总线冲突,使同一时刻只有一个从器件与从器件交换数据。
系统应用的串行热阵式打印机数据传输采用spi时序,但普通52单片机无spi口,所以采用i/o口线模拟spi时序。考虑到系统中mcu作为主器件总是发送数据,而数字打印机作为唯一从器件又总是接收数据,所以只需用口线模拟spi口的时钟线(spikclk)、数据输出线(spimosi),程序模拟spidat完成并-串转换即可。如前所提到打印头打印的数据点数为1728点,分辨率为8mm/mv,对应216字节的数据,为此从内部ram中分配出216字节的空间作为打印缓冲区,程序从缓冲区依次读数据,在模拟时钟线的控制下将并行数据转换成的串行数据按位依次送至打印机的移位寄存器中,结束后送latch锁存信号和打印头加热脉冲strobe,从而在热敏打印纸打印出一线心电图形,驱动步进电机向前走纸即可以连续打印。spi口模拟程序如下:
output:
现场保护
lcall intram ;初始化内部打印缓冲区
mov r0,#dat_buff ;初始化r0为缓冲区末位地址
dat_out:
mov a,@r0 ;从缓冲区读数据
mov r7,#08h ;初始化r7控制并/串数据转换
cont_chg:
rrc a ;对acc循环右移实现并-串转换
mov p1.3,c 将串行数据送至打印机
setb p1.1 ;模拟spi时钟
nop
clr p1.1
djnz r7,cont_chg ;判断1字节数据是否转换完
dec r0 ;寻址下一字节
cjne r0,#15h,dat_out;判断数据是否全部转换完
clr p1.2 ;产生数据锁存信号
nop
setb p1.2
nop
clr p1.0 ;产生加热脉冲
lcall heatdly ;调用加热延时程序
setb p1.1
lcall motor_run ;步进电机走纸
恢复现场
ret
2.2 打印算法
数字打印机实质上实现了数据与打印点的对应,也就是说8位心电数据数值范围为0~255,对应于热敏打印纸上的256点,通过加热敏单元使纸上某点变黑显示数据的大小。这就需要将表征实际心电大小的数据(以下称为原始数据)转换成能够指示加热点位置的数据(以下称为位置数据),通过位置数据的控制将心电数据对应的点依次打印出来,就可获得心电图。但是由于系统模数转换器获得的心电数据是离散的,如果仅将它们对应的点打印出来,得到只是一些离散的点,要想获得连续的心电图形,需要将相邻的离散点按照一定的算法将它们连接起来,对于纵向打印方式和横向打印方式,离散点连线算法是不同的。限于篇幅,在下面介绍打印方式的实现中,仅对横向12导同步打印和纵向打印进行详细阐述,而对6导联打印仅介绍其实现思想。
2.2.1 横向6导联打印
心电图纸长度为216mm,为每一导联心电信号分配32mm,对应于打印缓冲区中连续的32字节,打印数据转换后的位置数据存储于这32个字节中。12导联的心电数据被分为两大组,当一组打印完成再打印剩下的6导联的数据。具体的实现过程可参考横向12导联打印方式。
2.2.2 横向12导联同步打印
在6导联打印方式实现过程中,注意到在大多情况下一个完事心电波形中仅qrs波主峰较高可以点满整个空间外,其他波段幅值都较小占用空间很少,而这些波段可以提供更多的信息,此外将12导联分开打印,不利于医生对比同一时刻不同导联的心电波形。而采用12导联同步打印,虽然会出现波形部分重叠,但是在一些心脏疾病诊断中影响不大,且可以得取更直观的效果。
12导联同步打印程序实现的基本思想与6导联同步打印相同,不同在于将12导联的数据同时在216mm宽的打印纸上打印出来,不可避免地出现不同导联心电图形重叠的现象,对应的内存单元也会出现复用的情况。如果简单地套用6导联打印程序,那么前一导联的数据会被相邻导联数据冲掉,从而使图形无法正确显示。图3列出了为各个导联分配的热敏打印纸空间与缓冲内存单元(这里假定缓存地址为0x1dh~0xedh)。从图3中可以看出除了i导联前16mm空间和v6导联的后16mm空间没有被复用,打印纸的其它空间都是被两个导联共用。内部ram使用情况也与之类似。为此在外部ram开辟一个内部打印缓冲区的影像区(大小为216字节,单元地址的低8位与内存相应单元相同,如内部ram 0x1dh单元与外部ram 0xxx1dh相对应),将12导联分为两组:一组(i,iii,avl,v1,v3,v5)仍然存储于内部存储器,而另一组(ii,avf,v2,v4,v6)存储于外部影像区,在12导联一线数据转换完要打印时,将两部分按照对应单元相与即可。这样做不但可以解决上述问题,而且节省了内部资源、降低程序编写的难度。
如前所述不同导联所占的打印空间不同,所以,对于某导联心电信号,先要确定其打印区间,然后再确定打印数据在该区间的相对位置。
假定某导联所占打印空间的起始字节为第n个字节,而要打印的心电数据为m,将m除以8,得商k,余数为1,则此心电数据对应点对数(n-k)字节的第1位。即该心电数据对应的位置数据为第(n-k)字节(该字节的1位置1,其它位清零)。因此,打印此心电数据时,该导联所要传送的32字节打印数据中只有第(n-k)字节的第1位为1,其他都为0。
与液晶显示相类似,对于一个导联的心电信号,要实现心电图打印,必须将盯连两个心电数据用线连接。即对于一条心电曲线,起始显示数据点在起始列只显示1点;从第二个数据点开始,要在下一列显示上一数据点到此次数据点之间的线段。在热敏打印纸上表现为将两数据点之间的点都加热,对应于内存则是将两点之间的数据都置1。
对于—导联的心电信号,先读出第一个心电数据,将其转换成32字节位置数据直接打印。从第二个心电数据开始,除了要转换成位置数据外,还要与上一个数据相比较,用大数对应的位置数据减去小数对应的,然后结果与大数的位置数据相加,结果即为此心电数据应送打的32字节数据,也即完成了与上一心电数据连线的操作。分析发现连线算法只影响相连两个位置数据中非零字节之间的数据,为了简化计算,只需对这些字节进行减法操作,而不必计算所有的32个字节。对于加法,也只需将大数对应位置数据中的非零字节进行加法,即进行单字节加法。
举例说明,对于导联v6心电信号,前一个数据37h,下一个数据为55h,导联v6分配的打印区间为1dh~3dh,按照位置数据转换算法,37h的位置数据为第37h字节,该字节内容为80h,其他字节都为00h;55h的位置数据为第33h字节,该字节内容为20h,其他字节为00h。由于37h〈55h,因此应是心电数据55h的位置数据减去33h的位置数据,计算如图4所示。
2.3 纵向打印程序
纵向打印能够实现将12导联数据无重叠同步显示。这样医生可以参考比较同一时刻的各个波形的变化趋势,为疾病的诊断提供方便。
热敏打印纸宽度为216mm,分辨率为8dot/mm,这样最多能够打印1728点,将这些点与某一导联连续的1728个心电数据相对应,也就是说导联顺序第n个数据对应于一线图形中的第n个点。与模向打印相比纵向打印仍然要解决将离散的点连接起来的问题;但与横向顺序打印方式,即打印点按时间顺序打印相比,其难点在于需要将不同时刻同一幅值的多个点同时打印出来,即打印点按空间顺序打印。
首先定义一个内存单元存储扫描值,使其从当前通道数据最大值变化到0,依次与通道的每一个数据进行比较:相同则对应点被描记;小则不被描记;大则需要将当前心电数据相邻的两点与扫描值相比较,只要其中一个比扫描值大,则对应点被描记,要描记的噗将其内存对应的数据位置1。
举例说明,如果打印内存缓冲区的首字节为n,大小为216字节,而当前某一导联顺序第m个心电数据是v,而此时的扫描值为w:①v>w,则继续比较第m+1个数据;②v=w,则对应点需要被描记;③v