自制导纳信号发生器 [原创cnblogs.com/helesheng]

最近正在研制一种经过测量人体导纳,估算体内血液变化率,进而评估心血管系统泵血功能的医疗仪器。为测量人体导纳,咱们设计了一套巧妙的激励信号幅度反馈电路,该电路因为涉及商业机密就不在这里讨论了。这里主要分享一下我本身设计的,用于对导纳测量电路进行调试和幅度定标的重要工具——导纳信号发生器的设计。git

如下原创内容欢迎网友转载,但请注明出处:http://blog.163.com/helesheng算法

1、自制导纳信号发生器的缘由缓存

在研制人体导纳测试仪器的过程当中,我发现很难对仪器进行调试和定标:因为没法买到商品化的导纳信号发生器,只能直接进行人体试验。一方面,每次调试电路都很麻烦;另外一方面,没法对电路增益进行幅度和频率定标。所以自制一种阻值接近人体,变化频率可调的导纳信号发生器就势在必行了。网络

导纳(admittance)的定义是阻抗的倒数,标准量纲为西门子S,1S即1Ω的阻抗对应的导纳.ide

 “导纳信号发生器”从本质上讲也是阻抗信号发生器,但“导纳信号发生器”的静态部分应该能够直接设置导纳值(而非阻值),而动态部分则应该是导纳随时间成正弦变化的。函数

先来看看人体导纳的基本状况:在50KHz交流信号激励下,人体胸腔静态阻抗约30Ω(33mS,33毫西门子),因为血流变化引发的阻抗约为5KΩ(0.2mS)。二者相差较大,为方便调试,我用固定电阻实现导纳信号发生器的静态部分,血流变化引发的动态导纳变化则用数字电位器(Digital Potentiometer)来模拟。“动态”和“静态”两部分电路则采用并联形式。工具

2、静态部分的电路测试

我设计的导纳信号发生器的“静态部分”,是指导纳值能够手动调节,但不会自动变化的部分。其电路以下。ui

图1 导纳信号发生器的静态部分电路spa

其中R1~R10都是阻值为100Ω,精度为1%的电阻,OPT1-OPT10则是导通电阻仅为1Ω的固态继电器KAQY212。导纳信号发生器能够在使用者的控制下,使OPT1~OPT10导通和关闭,每多打开一个固态继电器则测试端Ts1和Ts2之间的导纳就增长10mS(100Ω的倒数)。设上述电路在Ts1和Ts2之间产生的静态导纳为

其中K为打开的固态继电器数量, 是100Ω电阻对应的导纳数值。例如,当打开三个固态继电器后, 为30mS —— 与人体静态导纳至关。

 3、动态部分的电路

 我设计的导纳信号发生器的“动态部分”,是导纳值自动呈正弦性周期变化的部分,它与静态部分并联,以模拟人体的动态导纳变化。我用最大阻值为10KΩ的数字电位器MCP41010来实现动态部分。

 

图2 与静态部分并联的动态部分电路

MCP41010是SPI接口的器件,使用者设置完变化频率后,导纳信号发生器中的MCU经过SPI口定制改变MCP41010的阻值,以使导纳值根据设定的频率成正弦变化。其中,MCP41010的阻值分辨率为最大阻值(10KΩ)的256分之一。

设MCP41010在Ts1和Ts2之间产生的动态导纳为 ,而从外部观测整个导纳信号发生器,其整体导纳能够表示为下式。

4、控制和人机交互电路

个人导纳信号发生器可以设置静态导纳值和动态导纳变化的频率,所以还必须有显示和按键等人机交互设备。为了省事,我使用了一个具备Arduino接口的STM32F103开发板做为个人主控板。扩展Arduino盾板除了有上述的静态导纳和动态导纳电路以外,还有一只四位数码管和四只按键。四位数码管中两位用于显示静态导纳,两位用于显示动态导纳变化的频率;四只按键两只用于调整静态导纳,两只用于调整动态导纳变化的频率。

标准Arduino接口的I/O数量很少,不足以控制这么多外设,所以使用了三只74HC595来扩展I/O口。具体电路以下图所示。

图3 控制和人机交互电路

上面电路右上角为标准Arduino扩展接口,左边顺序串接的三只74HC595扩展产生:数码管的段码驱动D0-D七、数码管的位选通驱动DIG0-DIG3以及静态导纳电路中固态继电器的开关信号SWs1- SWs10。

动态导纳产生电路中的数字电位器MCP41010则由Arduino接口中的SPI接口控制。

由上述电路配置可知,Arduino控制板中的MCU除了要定时显示数码管的各个位以外,还要定时刷新MCP41010的阻值,以及扫描按键。我在实时操做系统uC/OS-II下来开发导纳信号发生器的软件。

5、动态正弦导纳信号的产生算法

如下是本文的核心内容。

1)正弦导纳表格的产生

既然是“导纳信号发生器”,就应该使测试端子Ts+和Ts-之间的动态导纳成正弦变化。但做为一款“数字电位器”,MCP41010是将本身的总阻值(10KΩ±2KΩ)均分为256份。MCU经过指令给MCP41010的数值每增长“1”,其抽头和某一端的阻值就增长约40Ω,即每一个LSB对应的阻值相等,而非导纳相等。

显然当阻值较小时,数字电位器的每一个LSB变化所引发的导纳变化较大。所以在数字电位阻值处,导纳分辨率也较低。经过一段Matlab代码来计算产生正弦导纳所需的数字电位数值。

 

% 本脚本用于产生导纳模拟器数字电位器所需的数值
%数字电位MCP41010的阻值为1-10k欧姆,取中间阻值做为导纳正弦变化的0值
MAX_R = 10*10^3;
MID_R = MAX_R*0.5;%MID_R决定了计算产生的导纳以哪一个值为中心
MID_ADMIT = 1/MID_R;%导纳的平均值(中间值)
MIN_ADMIT = 1/MAX_R;%导纳的最小值等于最大的阻值的倒数
%AMP_ADMIT = (MID_ADMIT - MIN_ADMIT)*0.98;%导纳的变化幅度为最大幅度的98%
AMP_ADMIT = (MID_ADMIT - MIN_ADMIT)*0.8;%只使用了导纳的变化幅度的80%
N = 64;%表格中的数据点数
i = 1 : N;
sig_admit = AMP_ADMIT*sin(2*pi*i/N) + MID_ADMIT;
R_data8_admin = round(sig_admit.^(-1));%计算产生正弦变化的导纳所需的阻值
plot(R_data8_admin,'*-r');grid on;
title('导纳正弦变化所需的阻值(单位欧姆)');
%将上述阻值折算为MCP41010所需的0~255的设定值
R_CODE_MCP4XXXX = round((R_data8_admin/MAX_R)*256);
%反过来计算这些数值所对应的导纳值
admit_true = (R_CODE_MCP4XXXX/256 * MAX_R).^(-1);
figure();
plot(admit_true,'*-b');grid on;
title('MCP41010变化引发的实际导纳变化(单位西门子)');

上面的代码取MCP41010阻值范围的一个值MID_R对应的导纳MID_ADMIT做为要产生的正弦导纳值的平均值。因为正弦导纳信号中高于平均值的部分的导纳值和低于平均值的部分的导纳值是对应相等的,但数字电位器阻值较小的部分所对应的导纳范围显然较宽,所以用正半周内最大导纳值MAX_ADMIT减去平均值MID_ADMIT获得的导纳变化幅度的一部分做为正弦导纳信号的幅度AMP_ADMIT。

AMP_ADMIT = (MID_ADMIT - MIN_ADMIT)*0.8;%使用了导纳的变化幅度的80%

 取MID_ADMIT为MCP41010的中间阻值所对应的导纳,而AMP_ADMIT为可能的最大振幅的80%。

上面代码产生的“导纳正弦变化所需的阻值(单位欧姆)”以下图。

 

图4 导纳正弦变化所需的阻值 

 

从图中可知,在阻值的较小的一半(上图下半截),较小的阻值变化就能引发和阻值较大的一半(上图上半截)相同的导纳变化。所以为产生上下对称的导纳值,上图的下半截被压缩得“较窄”。如前所述,这一现象将致使在阻值较小的下半段,数字电位的分辨率不足。上述Matlab代码对这一现象进行了仿真,获得了下图所示的“MCP41010变化引发的实际导纳变化(单位西门子)”。从图中可知导纳发生器的产生的正弦变化幅度为(2.0±0.5)×10-4S,简单表示为:

 

图5 MCP41010变化引发的实际导纳变化

 能够看到在阻值较小的上半部分,导纳变化的正弦曲线存在明显失真。若感受这种程度的失真没法达到设计要求,则只能更换分辨率更高的数字电位器了。

2)程控导纳变化频率的实现

 人体导纳变化主要由心脏搏动引发,所以变化频率在10Hz之内。为了对不一样频率的导纳变化进行频率定标,须要导纳信号发生器可以产生频率稳定且可调的导纳变化信号。提到频率可调,天然想到DDS算法。DDS算法能够描述为下列公式。

 

其中,fout是算法输出的信号频率,fclk是算法刷新的速度,2N是DDS算法查找表(LUT)的长度,而delta则是算法在查找表中每次跳过的点数。程序只须要修改M就能够产生和delta成正比的输出频率fout。每次在为方便uC/OS-II下的程序设计,我将uC/OS-II的系统时钟设为1KHz,并把刷新MCP41010的阻值的操做放在钩子函数OSTimeTickHook();中,DDS的时钟fclk也就是1KHz。取N为16,即查找表的长度为2N =65536。根据公式(5),fout的分辨率约为0.015Hz——远高于频率定标所需的频率精度。钩子函数中DDS算法的实现代码以下。

 1 void OSTimeTickHook (void)  2 {  3     unsigned char MCP41XXX_1st_byte,MCP41XXX_2nd_byte;//须要经过SPI口发送给MCP41XXXX的两个字节
 4     MCP41XXX_1st_byte = 0X11;//对电位器1执行写数据操做
 5     MCP41XXX_2nd_byte = 0x00;//第二个字节是须要写入的电阻数值
 6     /////如下DDS算法产生频率可调节的正弦导纳值////////////  7     unsigned short delta;//DDS算法的地址表增量
 8     delta = admit_frq*65536/1000;//根据DDS算法,地址表增量等于目标频率admit_frq乘以表格长度L,再除以采样率fs
 9     dds_adder = dds_adder + delta;//累加器增长并天然溢出
10     MCP41XXX_2nd_byte = 256 - sin_admit_tbl[dds_adder>>10];//数值表格只有64个数,也就是6位地址能够覆盖 11     //最后用256减去查表的值是由于电路图画的有问题:输入MCP41XXX的数值是PW和PB之间的值, 12     //但电路图将MCP41XXX链接成变阻器的方法是将PW和PB短路,从而外部获得的是PW和PA的值,因此将数值反转后才能获得正确的值
13     CS_RP = 0; 14  SPIx_ReadWriteByte(MCP41XXX_1st_byte); 15  SPIx_ReadWriteByte(MCP41XXX_2nd_byte); 16     CS_RP = 1; 17 }
钩子函数

 

 sin_admit_tbl[]是上面的Matlab代码产生的长度为64个点的MCP41010数值表,它们对应的导纳值是一个正弦变化的周期。其索引dds_adder长度是16位的,但sin_admit_tbl[]的地址只有6位(64个点),所以将dds_adder右移10位做为sin_admit_tbl[]的地址。admit_frq是DDS算法但愿产生的信号频率,至关于(5)式中的fout。admit_frq做为全局变量,其值是在键盘任务中修改的。

6、uC/OS-II下代码的实现

因为刷新数字电位的任务在钩子函数中完成,uC/OS-II中只须要两个任务:1)“键盘任务”TaskKEY();负责扫描四个按键,并根据输入刷新静态导纳r_sw_num和动态导纳频率admit_frq这两个全局变量。2)“刷新显示和输出任务”TaskFLASH_DIS();负责定时地、逐位地刷新数码管上显示的内容,以及静态导纳电路中须要打开的固态继电器。

键盘任务代码以下所示。

 1 void TaskKEY(void *pdata)  2 {  3     while(1)  4  {  5         OSTimeDly(20);  6         if(KEY1 == 0)  7  {  8             OSTimeDly(20);//消除按键抖动
 9             if(KEY1 == 0) 10  { 11                 if(admit_frq < MAX_FRQ) 12                     admit_frq++; 13                 while(KEY1 == 0)OSTimeDly(10);//一直等待到按键被释放,最后的延时还能消抖动
14  } 15  } 16         if(KEY2 == 0) 17  { 18             OSTimeDly(20);//消除按键抖动
19             if(KEY2 == 0) 20  { 21                 if(admit_frq > MIN_FRQ) 22                     admit_frq--; 23                 while(KEY2 == 0)OSTimeDly(10);//一直等待到按键被释放,最后的延时还能消抖动
24  } 25  } 26         if(KEY3 == 0) 27  { 28             OSTimeDly(20);//消除按键抖动
29             if(KEY3 == 0) 30  { 31                 if(r_sw_num < MAX_SW) 32                     r_sw_num++; 33                 while(KEY3 == 0)OSTimeDly(10);//一直等待到按键被释放,最后的延时还能消抖动
34  } 35  } 36         if(KEY4 == 0) 37  { 38             OSTimeDly(20);//消除按键抖动
39             if(KEY4 == 0) 40  { 41                 if(r_sw_num > 0) 42                     r_sw_num--; 43                 while(KEY4 == 0)OSTimeDly(10);//一直等待到按键被释放,最后的延时还能消抖动
44  } 45  } 46  } 47 }
键盘任务

 

刷新显示和输出任务代码以下所示。其中显示缓存dis_buff[]中对应的是须要显示的每一个数码管位的内容。dis_buff[]的内容须要不断计算、刷新,以防键盘任务在用户操做时修改须要显示的值。变量first_byte,second_byte,third_byte中的值则是须要经过串行口下载到三只74HC595中的。其中既包括当前须要显示的数码管位的字形码D0-D7,也包括显示的位置选通讯号DIG0-DIG3和固态继电器的开关信号SWs1- SWs10,其对应关系请参见图3中电路网络标号。

 1 void TaskFLASH_DIS(void *pdata)  2 {  3     unsigned char dis_buff[4],i;  4     unsigned short sw_ctl_bits=0;//低10位对应100欧姆导纳电阻的开关状态
 5     unsigned char dis_index = 0;//刷新数码管到第几位的计数器
 6     unsigned char bit_sel = 1;//决定哪位被选中显示,必须有一个位为1
 7     unsigned char first_byte,second_byte,third_byte;  8     while(1)  9  { 10         //////////先刷新须要595输出的全部东西的数值///////////
11         dis_buff[3] = r_sw_num/10;//数码管的右半边两个显示打开的100欧姆电阻数目
12         dis_buff[2] = r_sw_num%10; 13         if(dis_buff[3] == 0)//处理十位的消隐问题
14             dis_buff[3] = LED_PATT[10];//消隐显示
15         else
16             dis_buff[3] = LED_PATT[dis_buff[3]];//查字型表
17         dis_buff[2] = LED_PATT[dis_buff[2]];//查字型表
18         dis_buff[1] = admit_frq/10;//数码管的左半边两个显示导纳变化的频率
19         dis_buff[0] = admit_frq%10; 20         if(dis_buff[1] == 0)//处理十位的消隐问题
21             dis_buff[1] = LED_PATT[10];//消隐显示
22         else
23             dis_buff[1] = LED_PATT[dis_buff[1]];//查字型表
24         dis_buff[0] = LED_PATT[dis_buff[0]];//查字型表 25         //由打开的100欧姆电阻数r_sw_num,决定打开的位
26         sw_ctl_bits = 0; 27         if(r_sw_num != 0) 28  { 29             for(i = 0;i < r_sw_num;i++) 30  { 31                 sw_ctl_bits = sw_ctl_bits<<1; 32                 sw_ctl_bits++;//将最低位置一,并左移一位
33  } 34  } 35         bit_sel = 0x01 << dis_index;//刷新到哪一位,就让对应的位选信号为1
36         //////////刷新595输出,每次让一个数码管亮///////////
37         third_byte = dis_buff[dis_index];//第三个送出的字节是字形码,但具体是那个位的,要由当前扫描到的位置决定
38         ///////第二个字节的最高两个位控制100欧姆导纳电阻的最低两个开关,第四位是数码管的位选通讯号
39         second_byte = (sw_ctl_bits & 0x0003);//第二个字节中只去最低两个位
40         second_byte = second_byte << 6;//将最低两个位放到最高两个位置
41         second_byte = second_byte + bit_sel;//第四位是位选通讯号
42         first_byte = (sw_ctl_bits >> 2) & 0x00ff;//取开关控制位的3-10位
43         send_to_tri_74595(first_byte,second_byte,third_byte);//从模拟SPI口送出三个准备好的字节
44         if(dis_index < 3)//更新下一个须要刷新的位置
45             dis_index++; 46         else
47             dis_index = 0; 48         OSTimeDly(5);//刷新速度是5个时钟周期也就是200Hz 
49  } 50 }
刷新显示和输出任务

七、验证电路

常见的万用表、示波器等仪表一般能够直接测量阻值和电压值,不能直接测量导纳和电流。所以须要一个电路来验证导纳信号发生器产生的信号是否符合设计要求。采用下列由运算放大器为主构成的电路来将导纳值变换成电压值,来验证上述设计的正确性。

图6 将导纳转换为方便测量和观察的电压信号的电路

其中,RL是导纳信号发生器。VREF_-0.1V是由电压基准芯片分压后,再由跟随器产生的标准-0.1V电压。使用时尤为要注意,导纳信号发生器的GND和这里的GND不是同一GND,导纳信号发生器和上图必定要分开供电(好比,上图电路采用电池供电)不然必定会形成短路和工做不正常。

根据虚短原理,运算放大器OPAB的反向输入端被钳置在0电平。则由左至右流过RL的电流等于0.1/RL,改写为导纳YL后有:

又由虚短原理,流过电阻R1后,运算放大器OPAB的输出为:

也就是使运算放大器OPAB的输出电压正比于导纳信号发生器输出的阻抗。将(3)、(2)和(4)式代入上式获得下式。

上式中K是打开的固态继电器的数量,ω是正弦变化导纳的角速度,单位为V(伏特)。第一项表明静态导纳,第二项表明动态导纳。与人体导纳类似,动态导纳约为静态导纳的百分之一。此时经过万用表测量OPAB的输出,能够发现每当导纳信号发生器在键盘控制下多打开一个固态继电器开关,输出的直流电压就增长0.5V。

为了进一步验证动态导纳的正确性,须要将上式信号中的第二项(交流部分)放大100倍左右。运算放大器OPAA就是这个交流放大器,Cac是隔直电容,二极管D一、D2起到尽快稳定交流放大器工做点的做用,OPAA接成同相放大形式,以提升输入阻抗。下图是从示波器上观测到的OPAA的输出。 

图7 用示波器观测动态导纳变化

 

与预想的相同,OPAA的输出是一个稍有噪声的正弦信号,该正弦信号频率随导纳信号发生器的设置的改变而改变。图6中的OPAB被链接成了反相放大形式,图7中的负半周对应图5中理想正弦波形的正半周。能够看到图7的实测波形的负半周确实分辨率较正半周低。另外,因为Matlab生成阻值代码表格中的点数只有64点,实测波形的连续性也不十分理想。但对于人体导纳测试应用这已经足够了,如有更高需求,能够适当增长阻值表格的长度。

相关文章
相关标签/搜索