使用 RTLinux 实现磁力轴承的实时控制

作者:Harland Alpaugh

实时控制最苛刻的应用之一是主动磁力轴承 (AMB)。以高于 10,000 RPM 的速度在磁场中悬浮轴,间隙为 0.015 英寸(大约是食盐粒的大小)需要精确、可靠地控制磁场。磁悬浮本质上是不稳定的。想象一下走钢丝的演员用下巴平衡一根长杆,您就能体会到控制系统在稳定磁力轴承时遇到的困难。然而,磁力轴承具有一些优点,证明了它们的使用是合理的。主要优点是磁力轴承消除了轴和支撑之间的物理接触,最大限度地减少了摩擦,并消除了传统滚柱轴承固有的磨损。

最近在日本开发的一个应用是植入式心脏泵。由于磁力轴承无需维护,因此这是使用它们的理想情况。出于同样的原因,卫星轮也是一个合乎逻辑的应用。

测试设置

实验测试装置如图 1 所示,由两个八极叠片定子组件组成,每个磁极上都有单独的绕组。轴承组件包括在每对直径相对的磁极中心线处的感应式间隙传感器。这些轴承支撑着一个由无刷直流电机驱动的两英尺长的轴。图 2 显示了单轴的示意图。控制器使用来自间隙传感器的信号来调整来自功率放大器的电流,从而驱动磁线圈,以保持旋转轴在间隙中居中。原始控制器是一个模拟电路,它被数字控制器取代。保留了运行模拟或数字控制器的能力。数字控制器在配备多通道数据采集板和多通道模拟输出板的 Intel Pentium III PC 上实现。PC 配置为双启动系统,用户可以在启动时选择普通 Linux 或 RTLinux。

Real-Time Control of Magnetic Bearings Using RTLinux

图 1. 实验磁力轴承测试装置

Real-Time Control of Magnetic Bearings Using RTLinux

图 2. 磁力轴承单轴示意图

实时操作系统必须确保特定任务以固定速率执行,而不管操作系统承受的许多系统级需求。为了满足这一要求,FSMLabs 和 RTAI 两个组织开发了专用内核,这些内核将 Linux 作为实时操作系统内的低优先级任务运行。这大大缩短了从桌面系统上的数百毫秒到微秒范围的时间。它还允许用户精确控制关键控制过程的定时。

对于这个磁力轴承项目,我选择了 FSMLabs 提供的免费 RTLinux 实现。RTLinux 由 Michael Barabanov 和 Victor Yodaiken 于 1996 年开发,目前由位于新墨西哥州的私营公司 FSMLabs 销售。FSMLabs 提供两个版本的 RTLinux,包括我用于此项目的 RTLinux/Free。FSMLabs 拥有 RTLinux 的软件专利,但该专利的许可允许将其用于在 GNU GPL 下许可的项目。

从概念上讲,RTLinux 将操作系统分为用户空间和实时内核。您可以将它们想象成两个独立的城市,彼此隔离,只能通过受保护的通道进行通信,例如实时先进先出设备 (RT-FIFO)。用户空间是熟悉的 Linux 系统,具有所有友好的实用程序,例如 vi 编辑器、GCC 编译器和 shutdown 命令。实时内核是 Spartan 环境,它无情地执行实时任务,而不管用户空间中的活动如何。

实时程序被编码为内核模块,并且不使用用户空间 C 程序的 main{} 程序结构。该模块需要两个函数:init_module(启动时调用)和 cleanup_module(关闭实时模块时调用)。init_module 创建实时模块的入口点,并分配用于与用户空间通信的 RT-FIFO。要启动实时模块,请使用 insmod 命令。一旦实时模块启动,只能通过发出 rmmod 命令或拔掉处理器插头来停止它。作为 RTLinux 的新用户,我非常不安地发现,尽管发出了 shutdown 命令,控制器仍在继续运行。

控制理论的重叠

控制理论是现代技术(从汽车到喷气式客机)的核心,是一个广泛的领域,研究生们为此辛勤工作了几十年。我无法在这里涵盖这个广泛的理论体系,但我可以解释磁力轴承数字控制的要点。首先,对要控制的量进行仪器化和测量。在本例中,该量是旋转轴承和轴承磁极之间的间隙。该间隙通过信号调理器转换为电压,并输入到模/数输入 (AI) 板。在我的设置中,四个独立的间隙传感器信号控制旋转轴。所有四个间隙信号电压都被同时采样。

间隙由流经磁体的电流控制,磁体由八个功率放大器驱动。功率放大器由来自单独的数/模输出 (AO) 板的电压控制。AO 板接收数字输入并将其转换为电压,该电压保持恒定直到下一个信号。这种采样保持操作是所有数字控制系统的基础。在控制回路中,AO 板在数值处理后接收来自 AI 板的处理信号。在理想的数字控制器中,AI 和 AO 操作都以精确的恒定间隔同时发生。尽管无法实现这种理想情况,但您必须确保控制算法内的代码高效运行。在我的控制程序中,这种情况发生在 10kHz。

控制程序中的数值运算包括控制器在先前几个步骤的输入 x 和输出 y 的历史记录。这些存储在内存中,并在每次执行控制循环时递增一个增量。历史记录包含在差分方程中

y(n)=A*y(n-1)+B*y(n-2)+...+C*x(n) + D*x(n-1) +...

其中 y(n) 是当前时间步的控制器输出,y(n-1) 是前一个时间步的控制器输出,y(n-2) 是过去两个步骤的输出,y(n-3) 是过去三个步骤的输出,依此类推,直到控制算法的复杂性所要求的深度。类似地,x(n) 是当前时间步的输入电压,x(n-1) 是前一个步骤的输入电压。A、B、C、D 和其余部分是由特定控制律实现确定的常数系数。控制器可以是单输入单输出 (SISO) 或多输入多输出 (MIMO)。在我的磁力轴承测试装置中,y 是驱动功率放大器的电压,x 是来自间隙传感器的信号。我在我的磁力轴承差分方程中使用前三个值。

数字控制实现

数字控制器在配备六槽 PCI 总线的 1GHz Intel Pentium III PC 上实现。该系统作为定制桌面个人计算机采购,预装了 Red Hat Linux 7.2 版。在实验室中,PC 未联网。我从 FSMLabs 下载的 tar 存档中安装了 3.1 版 RTLinux。

选择数字采集和控制 (DAC) 板的三种可能方法是:编写所需的板驱动程序软件、从 Comedi 等开源项目获取驱动程序以及使用供应商提供的驱动程序软件。第一种和第二种选择都需要在 Linux 和数据采集编程方面具有较高的复杂性和专业知识。第二种选择反映了 Linux 系统的开源性质,但供应商的选择有限,并且通常无法获得最新产品。第三种选择虽然需要最少的专业知识,但用户却受制于板供应商。供应和支持必要驱动程序的供应商数量有限,并且经常使用与第二种选择相同的来源。最后,我选择了第三种选择,并从 United Electronics, Inc. 购买了两块 PCI 总线多通道 DAC 板。这些板卡附带了所需的 RTLinux 驱动程序。

在实现数字控制律之前,我进行了测试以表征数字系统行为。这些测试是评估数字板卡的转换和定时交互的各种程序代码。对于我的主要功能测试,我设计并编写了一个 C 语言模块,该模块读取模拟输入板上的模拟数据,将其转换为浮点变量,将其转换回数字变量,然后通过模拟输出板输出信号。

清单 1 显示了主要功能测试的 C 程序框架。实时控制程序的核心是 RTLinux 函数 pthread_wait_np,它挂起当前正在运行的实时线程的执行,直到下一个周期的开始。此线程使用 pthread_make_periodic_np 标记为执行。线程放弃控制,直到下一个时间周期。RTLinux 中的默认算术是整数。我的控制应用程序需要浮点数,这通过 pthread_setfp_np 启用。输入和输出的比较记录在 Tektronix 双通道数字存储示波器上。图 3 显示了此测试中系统性能的典型记录。软件中的主循环在此图中设置为 10kHz;模拟输入是 1,000Hz 锯齿波。输出显示了采样保持操作的阶梯波形特性。

清单 1. 用于测试 A/D 和 D/A 转换的实时代码框架

#include <stdlib.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <rtl.h>
#include <time.h>
#include <rtl_fifo.h>

// #includes for DAQ cards here

#define PERIODIC_FREQ_HZ        10000.0
#define FRAME_PERIOD_NS
  ((hrtime_t)((1.0/PERIODIC_FREQ_HZ)*1000000000.0))

pthread_t periodic_thread;
int AI_board_handle = 0;
int AO_board_handle = 1;

void *Periodic_entry_point(void)
{
     static double volts[MAX_RESULT_QTY+1];
     static u16 adc_data[MAX_RESULT_QTY];
     static u16 dac_data0;
pthread_make_periodic_np(pthread_self(),
                      gethrtime(),FRAME_PERIOD_NS);
pthread_setfp_np(pthread_self(),1);

// Initialize DAQ boards here

while (1)
{
    pd_ain_get_samples(AI_board_handle,
                        MAX_RESULT_QTY,
                        adc_data, &samples);

  for (i = 0; i < (CL_SIZE+1); i++)
   {volts[i+1]=
     ((float)(adc_data[i]^0x8000)*.00030518)-10.0;}
    //  output to AO board
        dac_data0 = (volts[1]+10.0)*3276.6;
        ret = pd_ao32_writex(AO_board_handle, 0,
                               dac_data0,0,0);
       pd_ain_sw_cl_start(AI_board_handle);
   // multiplies for timing test
         for (i = 0; i < 5000; i++) z=x*y;
         pthread_wait_np();
     }
}

int init_module(void)
{
   pthread_attr_t attrib;
   sched_param.sched_priority
     sched_get_priority_max(SCHED_FIFO);
   pthread_attr_setschedparam(&attrib,&sched_param);
// create the thread
   pthread_create(&periodic_thread, &attrib,
                    Periodic_entry_point,NULL);
   pthread_wakeup_np(periodic_thread);
}

void cleanup_module(void)
{
     pthread_delete_np(periodic_thread);
}

清单 2. 控制模块核心的代码片段

while (1)
{
// Read in new coefficients on control FIFO
  rtf_get(CONTROL_FIFO_ID,&coeffs,sizeof(coeffs));
  rtf_flush(CONTROL_FIFO_ID);

// This code places coeffs[] in A, B, C, D, E ...

   pd_ain_get_samples(AI_board_handle,NO_CHANNELS,
                            adc_data,  &samples);
  for (i = 0;i < (NO_CHANNELS);i++)
   volts[i+1]=
   ((float)(adc_data[i]^0x8000)*.00030518)-10.000;

// Difference equation
       x0[0]=volts[1];
       x1[0]=volts[2];
       x2[0]=volts[3];
       x3[0]=volts[4];
       y0[0] =d1*x0[0]+stuff0;
       y1[0] =d2*x1[0]+stuff1;
       y2[0] =d1*x2[0]+stuff2;
       y3[0] =d2*x3[0]+stuff3;
       y4[0] = -y0[0];
       y5[0] = -y1[0];
       y6[0] = -y2[0];
       y7[0] = -y3[0];

//  output to AO board
       dac_data[0] = (y0[0]+10.0)*3276.6;
       dac_data[1] = (y1[0]+10.0)*3276.6;
       dac_data[2] = (y2[0]+10.0)*3276.6;
       dac_data[3] = (y3[0]+10.0)*3276.6;
       dac_data[4] = (y4[0]+10.0)*3276.6;
       dac_data[5] = (y5[0]+10.0)*3276.6;
       dac_data[6] = (y6[0]+10.0)*3276.6;
       dac_data[7] = (y7[0]+10.0)*3276.6;
  for (j = 0;j < 8 ;j++)
    pd_ao32_write(AO_board_handle,j,dac_data[j]);

// Perform shift operations
    for (i = 3; i > 0; i--)
        {
         x0[i]=x0[i-1];
        x1[i]=x1[i-1];
        x2[i]=x2[i-1];
        x3[i]=x3[i-1];
         y0[i]=y0[i-1];
         y1[i]=y1[i-1];
         y2[i]=y2[i-1];
         y3[i]=y3[i-1];
         }

// Setup difference equations
         stuff0 = A*y0[1]+B*y0[2]+C*y0[3]
                   +E*x0[1]+F*x0[2]+G*x0[3];
         stuff1 = A*y1[1]+B*y1[2]+C*y0[3]
                   +E*x1[1]+F*x1[2]+G*x1[3];
         stuff2 = A*y2[1]+B*y2[2]+C*y0[3]
                   +E*x2[1]+F*x2[2]+G*x2[3];
         stuff3 = A*y3[1]+B*y3[2]+C*y0[3]
                   +E*x3[1]+F*x3[2]+G*x3[3];

// end this thread until next periodic call
         pthread_wait_np();
         pd_ain_sw_cl_start(AI_board_handle);
// multiplies for time test
          for (i = 0; i < 5000; i++) qq=a*b;
  }

Real-Time Control of Magnetic Bearings Using RTLinux

图 3. 示波器轨迹,显示测试信号(黄色)和模拟输出(蓝色)

测试系统运行的更直观方法是将模拟输出连接到音频放大器和扬声器,并从外部信号发生器输入正弦波。我这样做了,声音清晰稳定。原始模拟控制器是每个四个轴承轴上的简单超前滞后滤波器,它使用差分方程复制

[ y(n) = 0.7467*y(n-1 )+ 4.6380*x(n) - 4.5189*x(n-1)]

图 4 显示了数字和模拟控制器对轴上机械脉冲的响应。响应几乎相同。在此图中,数字控制器响应显示在顶部,模拟控制器响应显示在底部。图中显示的时间为 100 毫秒。数字环路在 MIMO 配置中以 10kHz 运行,该配置具有五个输入通道和八个输出通道。

Real-Time Control of Magnetic Bearings Using RTLinux

图 4. 示波器轨迹,比较数字(上)和模拟(下)控制器对机械脉冲的响应

高级实验

替代控制律很容易在 C 语言中实现并通过实验验证。其中更稳健的一种在差分方程中显示

y(n) = 1.4934*y(n-1) - 0.5576*y(n-2) + 0.5795*x(n) + .01487*x(n-1) - 0.5646* x(n-2)

转子已成功加速到 11,000 RPM,AMB 完全处于数字控制之下,并以 2,700 RPM 的速度通过了临界转速。在几乎所有旋转机械中,从最简陋的吹风机到现代客机发动机,临界转速都发生在不同的 RPM 下。在这些临界转速下,旋转轴的振动会变得很大,并对轴承和其他部件施加高负载。这些对轴承提出了极端的考验。

为了在设备运行时更改控制律的系数,我使用了 RT-FIFO。这些是用于在 Linux 用户空间和 RTLinux 线程之间进行通信的先进先出文件。由于 RT-FIFO 是单向的,因此我创建了两个单独的文件,用于与控制模块进行双向通信。函数 rtf_create(fifo_id_no, fifo_length) 为指定的 FIFO ( /dev/rtf0, /dev/rtf1,..../dev/rtf64 ) 分配指定大小的缓冲区。它必须从 init_module() 中调用。函数 rtf_destroy 在执行完成时释放 FIFO。它可以从 init_module( ) 或 clean-up_module() 中调用。这允许我通过在实时模块运行时更改差分方程系数来动态更改控制律。在实时线程中使用函数 rtf_get(fifo_id,&variables,sizeof(variables)) 以非阻塞模式读取系数。用于将系数发送到实时模块的用户空间代码是

ctl = open("/dev/rtf1",O_WRONLY);
write(ctl,&coeffs,sizeof(coeffs));
ctl = close(ctl);

此代码嵌入在 NCURSES 界面中,这允许我在设备旋转时通过手动输入来更改系数。Pradeep Padala 编写的 NCURSES Programming How-To(请参阅在线资源)是这项工作的优秀资源。

以类似的方式,我可以访问控制程序中的数据流并将其发送到用户空间。实时模块中的相应函数是 rtf_put(framerate_rtfifo_id, volts, offset)。在用户空间中,使用以下命令将输出发送到文件cat /dev/rtf0 > file。必须编写一个简单的 C 程序来将文件转换为可读形式。

结论

RTLinux 用于控制塔夫茨大学的工作转子测试装置。控制器在传统的 Pentium III 个人计算机上使用 Linux 操作系统的 RTLinux 扩展实现。控制算法在 C 语言中实现。各种控制律都可以在实际实验中实现和尝试。

另一个优点是消除了目标计算机,因为实时操作系统与主机计算机在同一处理器上运行。大多数作为数字控制系统开发的应用都作为专有实时目标计算机上的启动可执行文件启动。本文介绍的方法有所不同;它不针对基于专有开发系统的 RT 控制器。它使用为需要硬实时(确定性)执行的控制和数据采集应用开发的 Linux 软件环境。

致谢

我要感谢塔夫茨大学的 Fred Nelson 教授和 Denis Fermental 教授对这项工作的支持。这项工作部分由马萨诸塞州剑桥市的 C. S. Draper Lab 资助。

本文的资源: /article/8260

Harland Alpaugh (justanoldpaddler@verizon.net) 是塔夫茨大学机械工程专业的博士生。他喜欢激流皮划艇,并且经常可以在新英格兰的溪流中找到他,当冰雪融化时。

加载 Disqus 评论