使用嵌入式 Linux 将污水泵连接到网络

作者:Tad Truex

我和妻子在 1996 年夏天买了这栋房子。1997 年早春,地下室灌满了水。1998 年早春,地下室又灌满了水。2001 年早春,地下室再次灌满了水。受够了地下室总是进水,我们决定是时候安装一个污水泵了。

2003 年末,望着山上超过四英尺厚的积雪,预示着我们的地下室又要被淹,我们意识到我们从未着手安装 2001 年就决定要装的污水泵。这次我们发誓真的要做点什么,而且我们确实做了。经过一周对地下室的凿岩,我们拥有了一个闪亮的新污水泵,配有周边排水系统、电池备用电源和时髦的盖子。由于不信任这个小泵排出所有融雪的能力,我开始对污水泵感到痴迷。我每十分钟检查一次水位。晚上醒来也要检查水位。我从公司打电话回家询问情况——这简直太荒谬了。本来应该让我睡得更安稳的东西突然让我心神不宁。然后我突然想到:为什么不安装一个装置,让我可以远程监控水泵呢?我为这个项目设定了一些目标:1) 绝不能烧毁房子,2) 绝不能以任何方式导致水泵停止工作,以及 3) 学到一些东西。

根据我的主要和次要目标,我决定不会在水泵的电源线路上串联任何新的电路。除了让 10 安培电流通过我搭建的电路的明显危险之外,还需要相当数量的隔离电路来确保我不会烧毁我要连接的处理器。一种选择是将一圈导线缠绕在水泵电源线中的载流导体上。据推测,经过适当的信号调理后,感应电压将在处理器上可检测到。不幸的是,考虑到我的家庭电子实验室非常简陋,这种方法可能需要太长时间才能开发出来。

在拒绝了其他几个想法后,我转向了 Google。最终,我想起了一种称为霍尔效应的现象。霍尔效应是洛伦兹力在磁场中流动的电子上表现出来的一种现象。洛伦兹力的作用方向垂直于电场和磁场,导致电子在洛伦兹力方向上呈不均匀分布。导体表面在该方向上感应的电压与磁场强度成正比,因此可以用来检测其强度。市面上销售各种各样的霍尔效应传感器,它们在内部提供的信号调理量以及它们敏感的磁场强度方面有所不同。对于此应用,我选择了 Allegro Microsystems A3240LUA,这是一种相当灵敏的单极传感器;数据表可在 www.allegromicro.com/sf/3240 获取。单极传感器基本上充当 NPN 晶体管,当器件处于南磁极存在时,其基极电流导通。

我购买了一些传感器进行实验。我希望远程传感器仅由连接到处理器上通用输入/输出 (GPIO) 引脚的霍尔效应器件组成。为了简化软件调试,我决定构建一个独立的电路,通过 LED 指示水泵是否开启。这将增强我对传感器能够检测电流的信心。我构建了图 1 所示的电路。

Put a Sump Pump on the Web with Embedded Linux

图 1. 测试电路原理图

我插入电池,在传感器前挥舞一块磁铁,正如预期的那样,LED 亮了。下一步是等待水泵启动,并在电源线附近挥动传感器,看看它是否可以检测到近场。等等,挥动,什么也没有;等等,挥动,什么也没有。似乎近场/远场边界比我最初想象的更靠近导体。电源线中火线和零线的磁场足够接近以相互抵消,所以我无法检测到它们。无论我将传感器放在哪里,我都无法检测到电流。我没有气馁,而是对计划进行了一些修改。我用一根旧的 15 安培延长线和一块软铁芯,通过将大约十圈零线缠绕在铁芯上,开始加强磁场。几根扎带和一点环氧树脂完成了这项工作。有了我修改后的电线,我下到污水坑。将水泵插入延长线后,我等待了一些动静。当水泵这次启动时,我能够将测试传感器足够靠近铁芯以检测到磁场。用一块万用板、一些焊锡和更多环氧树脂,最终传感器完成了。图 2 显示了完成的传感器。

Put a Sump Pump on the Web with Embedded Linux

图 2. 完成的传感器

接下来要做的决定是使用哪种处理器。主要考虑因素是价格、板载以太网、可用的 GPIO 以及运行 Linux 的能力。搜索了一段时间后,我得出的结论是,许多嵌入式微控制器都配备了以太网,并且具有足够的马力来完成这项任务,但大多数都没有明确提及 Linux。另一方面是 PC/104 级嵌入式 PC,它们提供的性能明显超出本项目所需,成本也更高。最终我选择了 Soekris Engineering 的 Net4501,这是一款单板计算机,带有 CompactFlash 插槽、64MB 内存、AMD Elan 处理器和三个板载以太网控制器。额外的好处是,该板能够通过预启动执行环境 (PXE) 通过网络启动。

Soekris 网站 (www.soekris.com) 上的文档清楚地表明,Elan 的几个 GPIO 引脚可以通过易于访问的接头获得,以及 +5V 电源。价格非常合理,包括电源和一个用于容纳电路板的漂亮金属外壳。Elan 的每个 GPIO 引脚都有一个内部上拉或下拉电阻。选择一个带有内部上拉电阻的引脚让我可以将裸传感器连接到 CPU,而无需任何其他组件。

接下来,我构建了一个最新的 (2.4.19) 内核,该内核能够通过网络启动并禁用了几乎所有内容。我构建的内核没有模块,并启用了 Natsemi 以太网驱动程序、root NFS、串行控制台和 SC520 看门狗定时器。除了正常的配置过程外,本项目还需要对内核进行额外的更改。在 2.4 系列内核中,x86 的默认定时器中断设置为 100Hz。因为我知道我要采样一个频率几乎相同 (60Hz) 的信号,所以我决定提高定时器频率。中断定时器频率由 asm/param.h 中的 Hz 定义控制。此值的上限为 2K,所以我将其设置为 1,500,以便每秒提供 1,500 次定时器中断。由于机器上几乎没有其他程序运行,因此由于频率增加,基于定时器的例程之间不太可能相互干扰。

我配置了我的 DHCP 服务器和 PXELINUX 以提供先前构建的内核。剩下的就是为 TFTP 服务器填充根文件系统。为了创建初始运行时环境,我构建了最新的 uClibc、BusyBox、TinyLogin 和 utelnetd 软件包。我将所有三个可执行文件静态链接到 uClibc。默认情况下,BusyBox 版本的 init 在控制台端口上启动一个 shell。为了添加其他功能,我添加了自己的 /etc/inittab。它启用控制台 shell 并调用一个简单的 init 脚本,该脚本最初(重新)挂载根文件系统,启用看门狗并启动 telnetd,以便我可以远程连接。将终端连接到串行控制台端口后,我重新启动了设备。通过控制台端口,我能够看到系统加载并最终进入 BusyBox 版本的 shell。

系统启动并运行后,就该专注于新的驱动程序了。本项目所需的唯一内核空间设备驱动程序是监视传感器连接到的 GPIO 引脚的驱动程序。因为我是内核编程的新手,所以我决定通过将此驱动程序限制为尽可能少的代码来最大限度地降低内核中出现任何问题的可能性。为此,我决定编写一个 /proc 文件系统驱动程序,该驱动程序只需将水泵的状态报告为开启或关闭。任何其他需要使用用户空间程序的程序都会轮询低级驱动程序。

驱动程序的 init 函数执行三个主要任务。首先,它使用 create_proc_entry 调用将其自身注册为 /proc 文件系统模块。proc_dir_entry 返回的两个重要结构是文件和 inode 操作表,它们被设置为两个静态结构,其中条目已适当填充。因为这是一个如此简单的模块,所以两个结构中的绝大多数条目都为 NULL。创建 proc 条目后,init 例程会探测一些特定于 Elan 的寄存器,以确保将所需的 GPIO 设置为输入。

初始化例程做的最后一件事是启动一个定时器,以定期检查引脚。定时器函数需要进行更多探索

static void sample( unsigned long data ){

  static int remaining_samples = 0;
  __u16 dat;
  int ntimeout = 1;
  int mtimeout = Hz*mper - nsamp*ntimeout;

  if ( remaining_samples ){
    /*
     * Take another microsample
     */
    timer.expires = jiffies + ntimeout;
    remaining_samples--;
    dat = readw( pio_dat ) & DMASK;
    if ( dat == 0 ){
      /* low true logic at the pin */
      sample_buf[remaining_samples] = 1;
    }
    add_timer( &timer );
  }
  else {
    /*
     * We have accumulated a full buffer worth of
     * samples.  Decide if the pump is on.
     */
    int i;
    char buf[MAXSAMPLES];

    pump_state = 0;

    for( i = 0; i < nsamp; i++ ){
      /* itegrate the pin signal */
      pump_state += sample_buf[i];

      /* convert to a printable buffer for
         osciloscope mode */
      buf[i] = sample_buf[i] + '0';

      /* clear the buffer for next time */
      sample_buf[i] = 0;
    }
    buf[i] = '\0';

    if((verbose==1 && pump_state) || (verbose>1) ){
      /* print the signal trace */
      printk( KERN_INFO
              "Sample buffer at tick (%ld) %s\n",
              jiffies, buf );
    }

    /* long tick between samples */
    remaining_samples = nsamp;
    timer.expires = jiffies + mtimeout;
    add_timer(&timer);
  }

}

因为磁场以及传感器的输出会振荡,所以我不能简单地采样引脚的状态并将其报告为水泵的状态。为了避免由于不合时宜的采样而在统计数据中引入任何噪声,我实现了一个粗略的积分器。当定时器最初触发时,剩余的采样计数将重置为所需的每个周期采样数。在默认情况下,我们将此值设置为 Hz/60 或每个周期 25 个样本。请记住,Hz 是内核定时器中断的频率,当构建内核时,我将其增加到 1,500。

在 25 个快速采样结束时,我将定时器重置为在宏采样间隔结束时再次到期。在默认情况下,我每五秒钟进行一次宏采样。在重置定时器的同时,我还积分(添加)活动快速采样的数量。因为我采样速度相当快,所以我可以确定检测到水泵已开启。当我检测到水泵已开启时,我设置 pump_state 变量。当读取模块时(在 pump_output 函数中),我只需检查此变量的状态并报告它。这种机制允许我调整采样时序,而不会影响驱动程序的响应时间。

在调试驱动程序时,我添加了一个 verbose 参数,用于将各种信息打印到日志文件中。使用 verbose=1 作为参数运行模块会导致它在认为水泵开启时转储采样缓冲区。这提供了一个简单的示波器功能,让我可以定期检查日志文件以确认我没有收到任何虚假结果。它还提供了第二个有点有趣的能力。了解传感器的跳变点以及两点之间的相位角(时间),我可以计算传感器处的峰值磁场强度。在 1,500Hz 时,传感器开启 5 个样本。考虑到磁场以 60Hz 的频率振荡,这为我提供了样本之间 0.4PI 弧度的相位角。下面的方程式可以针对我的传感器的最坏情况跳变点(50 高斯时开启,5 高斯时关闭)求解,峰值幅度为 51.4 高斯。使用指示字段可能约为 38G 峰值的典型值(35 和 25)。

A * sin( theta ) = 35

A * sin( theta + 0.4PI ) = 25

我编写的另一个调试辅助工具是一个类似的驱动程序,当指示这样做时,它会激活板载 LED,该 LED 也连接到 GPIO。此驱动程序类似于水泵驱动程序,只是输出函数(从模块读取 from)不需要任何复杂的采样,并且驱动程序现在有一个输入(写入模块 to),允许用户设置 GPIO 引脚的状态。新函数 (led_input) 从用户空间读取缓冲区,并决定是否需要设置、清除或切换引脚的当前状态。此新函数使用 file_operations 结构注册。此驱动程序和水泵驱动程序之间唯一的其他结构差异是文件权限(在 create_proc_entry 调用中指定)必须允许写入访问。此驱动程序与一个简单的 shell 脚本相结合,可在水泵处提供软件正在工作的反馈——如果 LED 状态跟踪水泵状态,则一切正常运行。

在基本驱动程序到位后,就该从所有部件构建一个有用的系统了。需要的第一个用户空间代码是一个守护程序,以启用远程查询来确定水泵状态。由于我的根文件系统是通过 NFS 从我的主服务器挂载的,因此我可以运行一个简单的 shell 脚本,该脚本会休眠一段时间,检查 /proc/pump 并使用结果更新一个真实文件。但是,我没有选择简单的方法,而是编写了 pumpserv。

pumpserv 是一个简单的守护程序,它接受端口 5678 上的连接,并将 /proc/pump 的全部内容复制到调用者。管道的另一端是 pumpwatch。pumpwatch 是另一个在主机上运行的守护程序,它定期检查水泵以记录每次状态更改的时间。转换会添加时间戳并转储到日志文件中。然后可以使用任何所需的统计方法对日志文件进行后处理,或者可以将其上传到精美的网站以供全世界查看。

该系统自 2003 年 4 月以来一直运行。鉴于它似乎运行良好,我可能应该称之为胜利并继续前进,但我忍不住思考 pumpserv2。如果我最终着手开发,我会做一些不同的事情。当前系统的一个基本缺陷是它需要 NFS 服务器来提供根文件系统,更重要的是,它需要在服务器上运行守护程序来捕获数据。一个更加健壮的系统会将根文件系统本地化到水泵监视器;Soekris Net4501 有一个 CF 插槽,因此这应该很容易实现。也希望在水泵监视器上的守护程序本地记录数据,并在请求时提供此数据。这样,如果主服务器发生故障,也不会丢失任何数据。

这种基本技术可以很容易地修改为监视其他消耗足够电流以触发传感器的设备。一些可能性包括空调、冰箱、热水浴缸加热器、水井泵、啤酒冷却器——可能性是无穷无尽的。此技术或许最有用的应用是跟踪办公室咖啡壶中最重要的咖啡液位,因为加热器循环的频率与剩余咖啡的体积成反比。

对于任何有兴趣实施类似方案的人,init 脚本以及两个驱动程序、pumpwatch 和 pumpserv 的所有源代码都可在 pumps.oldtools.org/src 获取。水泵监视器的文件系统的压缩 tarball 也可用。任何有兴趣了解我的地下室是否正在发生洪水的人都可以查看 pumps.oldtools.org

源代码也可以从 Linux Journal FTP 站点 ftp://ftp.linuxjournal.com/pub/lj/listings/issue113/6827.tgz 获取。

Tad Truex 白天在 HP 设计 Alpha 微处理器。晚上,他努力兼顾两个孩子和众多爱好。

加载 Disqus 评论