摘要:通过互联网可以使陈旧的计算机设备重焕生机。本篇应用笔记阐述了如何采用TINI®,并通过网络使只有一个并行端口的旧式点阵打印机重新发挥作用。采用相同的协议转换技术,可将许多设备连接至互联网。
TINI平台支持TCP/IP网络栈(IPv4和IPv6)、存储器管理、进程调度以及诸如I²C、SPI和CAN等通信协议。通过常见的编程接口,可用8051汇编语言、C或Java对TINI进行编程。C runtime可提供一个Berkeley套接字(socket interface),Java运行环境支持Java 1.1.8 API内核。
TINI具有丰富的IO、简单的网络接口以及多种编程方法,是一套功能强大的协议转换器。这正是挽救那台旧式打印机所需要的:TINI提供网络接口,我来决定如何让TINI与打印机通信。
所讨论的打印机型号为Epson LX-800。从碳定年检测和厚厚的灰尘来看,它是邻近真空管和呼拉圈年代的产物。LX-800是9针打印机,这里的9针是指打印头的针数,而不是驱动打印机的并行信号个数。实际上驱动打印机需要17个信号(不包括地)。PC打印机并行接口的常用信号及其在25芯打印机连接器中的排列如图1所示。
图1. 并行打印机接口的25芯信号定义
采用传统的25芯Centronics打印机电缆连接TINI与打印机,首先需要与图1所示的连接方式相匹配。然而,还存在一个问题:TINI板的IO不适合用作较宽的并行总线。仅有8个或9个通用IO (GPIO)信号,约为所需信号数的一半。这些GPIO信号分布于多个不同的寄存器中。由于需要对多个寄存器进行写操作来设置8个输出位,这种GPIO信号的限制使得对打印机的8位数据总线进行简单的写操作变得不太方便。幸好,TINI插座板为添加CPLD (复杂可编程逻辑器件)预留了焊盘和走线,可大大增强TINI的IO性能。一旦安装了CPLD后,只需要一个用于编程CPLD的插头(通过JTAG接口)和一个能访问某些TINI引脚的插头。
CPLD通过DS80C400微控制器的外部存储器总线连接至TINI系统。微控制器仅需读/写特定地址就能访问CPLD:
图2. CPLD自身IO引脚和微控制器地址总线之间必须具有一个接口。
CPLD内有四个8位寄存器,对应CPLD的32个I/O引脚。寄存器位设为0时,对应输出引脚被驱动为低电平;寄存器位设为1时,输出引脚悬空。因此,CPLD通过I/O引脚读数据时,应将对应寄存器位设为1,这样就不会将该引脚驱动至任何数值。
图2中的所有输入信号都来自于TINI的外部存储器总线。当TINI写CPLD时,数据总线上是TINI将要写入的8位数值。当TINI进行读操作时,CPLD根据检测到的输入值来驱动数据总线。然而,由于其它器件可能共享总线,因此,CPLD不能在每次TINI请求读操作(即每次nPSEN信号有效)时,都去驱动数据总线。如果CPLD总是响应读操作,则TINI会读取错误的指令或数据,并产生无法预料的系统操作(即“产生错误”)。同样,并非所有写操作都是针对CPLD的,因此,CPLD也不能总是响应TINI的每一次写操作(即每次nWr信号有效)。因此,图2中的读、写信号是否有效都是由地址线决定的。
只要地址线AH0和AH1 (TINI的第16、17条地址线)为0,并且所选的芯片使能有效(本例中使用芯片使能6),则CPLD被激活。由于TINI的每个芯片使能对应2MB空间,因此访问CPLD的基址为0xC00000 (2,097,152 × 6 = 12,582,912 = 0xC00000)。需要注意,本设计中TINI和CPLD接口时,TINI的大多数地址线并未指定。例如,TINI的地址线A2至A15为任意值时,仍可激活CPLD。然而为了方便起见,源代码始终采用地址0xC00000至0xC00003访问四个8位寄存器。
用HDL (硬件描述语言)编写CPLD程序。这里采用Xilinx WebPACK™开发,用Verilog™硬件描述语言编写,模块定义如图2所示,包括独立的8位寄存器模块、多路复用器以及译码器。在顶层模块中实现模块和地址译码。
CPLD安装并编程完毕后,在与打印机连接之前,我还想检查确认具体用到了哪些信号。于是搭建了一个小型电路板,将4组LED与4个逻辑寄存器(IO7:0、IO15:8、IO23:16和IO31:24)相连,如图3所示。用C编写了一个小程序,使4组LED的数值增加,中间插入暂停,以便通过视觉验证程序的操作过程。确信CPLD编程及连接正确后,可接入打印机。图3所示为TINI板与25芯打印机连接器之间的飞线连接。
图3. TINIs400插座板与Epson打印机连接
第一步,先尝试简单的应用,在不联网的情况下向打印机发送一些ASCII字符。这种方法只能起到调试作用。从理论上讲,只要发送的不是指令编码和其它非ASCII字符,打印机会将每个字符直接打印出来。希望这个项目能像所有好的工程项目一样,能够流畅快速地打印,从而能尽快确定网络接口。实际上我完全错了。
利用Keil Software® µVision® 2工具套件,并采用C语言编写了第一个打印机应用程序。根据Indispensable PC Hardware Book的说明,似乎需要对打印机进行初始化,并开始写字符¹。写字符的算法比较简单:
012345678901234567890123456789...
将程序下载至TINI上并运行。我立刻知道打印机初始化程序已正常工作了,因为在关机并再次开机时,能听到打印头移动的声音。然而,几秒钟后没打印出任何东西。将程序复位,仔细检查连接是否正确。遗憾的是,没有留出时间监视打印机的状态信号。一切都很正常,于是重新到计算机硬件指南中寻找答案。经过一番研读,辨别出某些信号的极性出现了混淆。例如,就传统的PC接口而言,当BSY为0时该信号有效,然而从硬件方面来说它实际上为高电平有效。反复修改C程序,并改变不同信号的极性,但打印机初始化后仍不能工作。又检查了一遍程序,使逻辑电平与我想象的打印机信号电平相匹配。修改程序并运行后,打印机依然没有反应。
离开片刻,听到点阵打印机打印完一行文本时发出的刺耳声音。近前一看,已打印出一行漂亮的字符,但随后打印机又不动了。一个原理突然浮现在脑海中:无论是否达到80个字符的极限或是否将行结束序列插入数据流,打印机都是直到行末才进行缓冲。几次打印操作的工作过程证明了这一原理。现在可以打印几行文本了。
理想设计应该是一个虚拟并行端口。也就是说,虚拟并行端口提供与标准并行端口一样的接口,但就驱动而言,实际上是通过网络向TINI发送和接收数据。类似于这样的虚拟端口和协议转换器是TINI的一般应用,按照这一设计思想,我曾有过一些实现虚拟串行端口的经验。似乎很多应用都是直接写PC的并行寄存器,因此很难确定如何实现这种设计思想。下一步就是查看现有协议。根据同事建议,查阅了RFC 1179,行式打印机监控程序LPD)协议²。
LPD正好可以满足需要。UNIX通常都支持这种网络协议,但经过简单的配置后也可在Windows中使用该协议。安装驱动时,通知Windows安装与计算机相连的新本地打印机,而不是通知它使用哪一个现有端口,这样就创建了一个新的LPR端口。这里可以定义运行LPD服务器的机器名称(本例中为TINI的IP地址),并提供打印队列的名称。(这里只有一个名为‘crusty’的打印队列,我想这个名字非常适合我的打印机。) 最后,告诉Windows打印机型号(笔者的打印机为Epson LX-800)。
这一设置具有一个好处,Windows打印机驱动能自动处理所有数据的格式,以采用打印机的原始语句(Epson ESC/P)进行打印。这样就不必了解ESC/P语言,也不必解析PC传送给TINI的任何数据。此外,非常容易实现基本的LPD服务器;调用少量Java本地方法实现对存储器映射CPLD的访问。最后的实际代码约有400行,准备用于TINI的Java类文件仅有4kB。需要注意的是,我并没有实现整个LPD协议,只是实现了每次打印一个文件所需的部分协议。
打印MS Word文档时出现了一点小问题。为使测试文件变得更有趣,每隔两、三个字就变换不同的字体,这样便有机会看到一些以前从未见过的字体。打印时,出现了许多无用的字符图形:打印机会跳过几行,随即打印出一些奇怪的ASCII图形字符(0x80至0xFF范围内的一些字符),并不断发出嘟嘟声。最后,终于打印出一些正确的文字,但随后又是一堆乱码。经过多轮调试,并跟踪发送至TINI的ESC/P指令,决定试着为转义字符增加延迟。这样就能正确地运行打印程序,但是非常慢。经过多次试验后,发现只需在打印时碰到行结束字符时增加延迟即可。这种情况很少出现,因此实际打印时感觉不到延迟。
由于打印MS Word文档时碰到了一定的困难,因此一度断定打印GIF文件几乎是不可能的,但事实并非如此。打印时仅碰到一个问题:GIF文件转换为ESC/P指令后尺寸大于64k,而64k正是所用TINI版本支持的最大动态缓冲区大小。然而,TINI支持无此限制的文件系统,因此可以将接收的数据存储在文件中,而不是存储在动态存储器缓冲区中。一旦改用上述方法,就可以顺利地打印图形文件了,并且仅受TINI中RAM大小的限制。
引言
妻子将只有两个破洞的袜子以及略沾草迹的衬衫都扔掉了,这没什么。但当她将目标转向那台旧式点阵打印机时,我表示了抗议。她不屑地说:“这东西很久没用了,而且也不能连接到任何电脑上。”就像科学怪人一样,我不能容忍任何丢弃旧计算机设备的念头。目的非常明确:使打印机变废为宝,或者干脆将其丢弃。我决定通过网络使其重新焕发生机,幸运地是用一个TINI (微型网络接口)可完成这项工作。连接网络
TINI是Maxim提供的嵌入式网络平台,它是基于该公司的DS80C390、DS80C400、DS80C410和DS80C411微控制器而构建的。这些器件都是增强型8051控制器,具有24位地址、硬件网络控制器、多个数据指针、专用硬件堆栈和高速工作模式等特性。TINI平台支持TCP/IP网络栈(IPv4和IPv6)、存储器管理、进程调度以及诸如I²C、SPI和CAN等通信协议。通过常见的编程接口,可用8051汇编语言、C或Java对TINI进行编程。C runtime可提供一个Berkeley套接字(socket interface),Java运行环境支持Java 1.1.8 API内核。
TINI具有丰富的IO、简单的网络接口以及多种编程方法,是一套功能强大的协议转换器。这正是挽救那台旧式打印机所需要的:TINI提供网络接口,我来决定如何让TINI与打印机通信。
硬件配置
系统的核心部分采用TINI评估(EV)板。该评估板基于DS80C400微控制器,包括1MB闪存、1MB RAM,以及用于RS-232和以太网通信的连接器。虽然其存储器配置与TINI Java运行环境兼容,但是仍可采用C和汇编语言进行编程。这给打印机接口原型设计和应用实现提供了多种选择。所讨论的打印机型号为Epson LX-800。从碳定年检测和厚厚的灰尘来看,它是邻近真空管和呼拉圈年代的产物。LX-800是9针打印机,这里的9针是指打印头的针数,而不是驱动打印机的并行信号个数。实际上驱动打印机需要17个信号(不包括地)。PC打印机并行接口的常用信号及其在25芯打印机连接器中的排列如图1所示。
图1. 并行打印机接口的25芯信号定义
采用传统的25芯Centronics打印机电缆连接TINI与打印机,首先需要与图1所示的连接方式相匹配。然而,还存在一个问题:TINI板的IO不适合用作较宽的并行总线。仅有8个或9个通用IO (GPIO)信号,约为所需信号数的一半。这些GPIO信号分布于多个不同的寄存器中。由于需要对多个寄存器进行写操作来设置8个输出位,这种GPIO信号的限制使得对打印机的8位数据总线进行简单的写操作变得不太方便。幸好,TINI插座板为添加CPLD (复杂可编程逻辑器件)预留了焊盘和走线,可大大增强TINI的IO性能。一旦安装了CPLD后,只需要一个用于编程CPLD的插头(通过JTAG接口)和一个能访问某些TINI引脚的插头。
CPLD配置
CPLD可被看作是可编程硬件。CPLD具有非常灵活的内部架构,能实现许多逻辑功能和状态机。TINI插座板为100引脚VQFP封装的XILINX® CoolRunner®-II XC2C64 CPLD预留了空间;也支持封装和引脚配置相同的其它CoolRunner-II。本项目采用具有相同引脚配置的大容量器件XC2C128。CPLD通过DS80C400微控制器的外部存储器总线连接至TINI系统。微控制器仅需读/写特定地址就能访问CPLD:
read_from_cpld: mov dptr, #0C00000h ; CPLD address movx a, @dptr ; read from the deviceCPLD的任务较复杂。一方面,CPLD需要正确响应TINI的外部存储器总线信号:当TINI写CPLD时,CPLD必须读取总线数值;当TINI读CPLD时,CPLD必须提供总线数值。另一方面,对于打印机,CPLD需要读取指示打印机工作情况的状态信号(如BSY),并提供输出信号(如8位数据总线和写选通STR)以控制打印机。图2所示为CPLD需要实现的功能框图。
图2. CPLD自身IO引脚和微控制器地址总线之间必须具有一个接口。
CPLD内有四个8位寄存器,对应CPLD的32个I/O引脚。寄存器位设为0时,对应输出引脚被驱动为低电平;寄存器位设为1时,输出引脚悬空。因此,CPLD通过I/O引脚读数据时,应将对应寄存器位设为1,这样就不会将该引脚驱动至任何数值。
图2中的所有输入信号都来自于TINI的外部存储器总线。当TINI写CPLD时,数据总线上是TINI将要写入的8位数值。当TINI进行读操作时,CPLD根据检测到的输入值来驱动数据总线。然而,由于其它器件可能共享总线,因此,CPLD不能在每次TINI请求读操作(即每次nPSEN信号有效)时,都去驱动数据总线。如果CPLD总是响应读操作,则TINI会读取错误的指令或数据,并产生无法预料的系统操作(即“产生错误”)。同样,并非所有写操作都是针对CPLD的,因此,CPLD也不能总是响应TINI的每一次写操作(即每次nWr信号有效)。因此,图2中的读、写信号是否有效都是由地址线决定的。
只要地址线AH0和AH1 (TINI的第16、17条地址线)为0,并且所选的芯片使能有效(本例中使用芯片使能6),则CPLD被激活。由于TINI的每个芯片使能对应2MB空间,因此访问CPLD的基址为0xC00000 (2,097,152 × 6 = 12,582,912 = 0xC00000)。需要注意,本设计中TINI和CPLD接口时,TINI的大多数地址线并未指定。例如,TINI的地址线A2至A15为任意值时,仍可激活CPLD。然而为了方便起见,源代码始终采用地址0xC00000至0xC00003访问四个8位寄存器。
用HDL (硬件描述语言)编写CPLD程序。这里采用Xilinx WebPACK™开发,用Verilog™硬件描述语言编写,模块定义如图2所示,包括独立的8位寄存器模块、多路复用器以及译码器。在顶层模块中实现模块和地址译码。
CPLD安装并编程完毕后,在与打印机连接之前,我还想检查确认具体用到了哪些信号。于是搭建了一个小型电路板,将4组LED与4个逻辑寄存器(IO7:0、IO15:8、IO23:16和IO31:24)相连,如图3所示。用C编写了一个小程序,使4组LED的数值增加,中间插入暂停,以便通过视觉验证程序的操作过程。确信CPLD编程及连接正确后,可接入打印机。图3所示为TINI板与25芯打印机连接器之间的飞线连接。
图3. TINIs400插座板与Epson打印机连接
与打印机通信
最终目标是:无需任何专门驱动或客户应用程序,就能使打印机正常工作。简单地说,就是尝试用内置打印驱动并通过Windows®打印对话框,使旧式打印机发挥作用。本应用的最后测试步骤是打印MS Word文档;如果出现问题,Word会透露一些信息。尽管打印出MS Word文档是最终目标,仍希望用更简洁的方案来实现这一目标。第一步,先尝试简单的应用,在不联网的情况下向打印机发送一些ASCII字符。这种方法只能起到调试作用。从理论上讲,只要发送的不是指令编码和其它非ASCII字符,打印机会将每个字符直接打印出来。希望这个项目能像所有好的工程项目一样,能够流畅快速地打印,从而能尽快确定网络接口。实际上我完全错了。
利用Keil Software® µVision® 2工具套件,并采用C语言编写了第一个打印机应用程序。根据Indispensable PC Hardware Book的说明,似乎需要对打印机进行初始化,并开始写字符¹。写字符的算法比较简单:
- 等待BSY变为无效。
- 设置数据总线上的输出值。
- 设置写选通STR为有效。
- 等待ACK信号,确认已收到数据。
- 设置写选通STR为无效。
- 可根据需要进行清除或重复操作。
012345678901234567890123456789...
将程序下载至TINI上并运行。我立刻知道打印机初始化程序已正常工作了,因为在关机并再次开机时,能听到打印头移动的声音。然而,几秒钟后没打印出任何东西。将程序复位,仔细检查连接是否正确。遗憾的是,没有留出时间监视打印机的状态信号。一切都很正常,于是重新到计算机硬件指南中寻找答案。经过一番研读,辨别出某些信号的极性出现了混淆。例如,就传统的PC接口而言,当BSY为0时该信号有效,然而从硬件方面来说它实际上为高电平有效。反复修改C程序,并改变不同信号的极性,但打印机初始化后仍不能工作。又检查了一遍程序,使逻辑电平与我想象的打印机信号电平相匹配。修改程序并运行后,打印机依然没有反应。
离开片刻,听到点阵打印机打印完一行文本时发出的刺耳声音。近前一看,已打印出一行漂亮的字符,但随后打印机又不动了。一个原理突然浮现在脑海中:无论是否达到80个字符的极限或是否将行结束序列插入数据流,打印机都是直到行末才进行缓冲。几次打印操作的工作过程证明了这一原理。现在可以打印几行文本了。
实现LPD监控程序
第二天,决定将网络与打印机接口连接。简单说,就是创建一个用户应用程序,直接从网络上接收数据,并输出给打印机。这种方法存在的问题是:若要打印机与MS Word一起工作,还需要编写一个Windows打印机驱动程序,这一步工作可以实现,但这并不是我的乐趣所在。编写驱动程序这一做法老早就有,用不着再多此一举,我开始考虑其它选择,希望不用了解打印机驱动的全部细节就可实现目标。理想设计应该是一个虚拟并行端口。也就是说,虚拟并行端口提供与标准并行端口一样的接口,但就驱动而言,实际上是通过网络向TINI发送和接收数据。类似于这样的虚拟端口和协议转换器是TINI的一般应用,按照这一设计思想,我曾有过一些实现虚拟串行端口的经验。似乎很多应用都是直接写PC的并行寄存器,因此很难确定如何实现这种设计思想。下一步就是查看现有协议。根据同事建议,查阅了RFC 1179,行式打印机监控程序LPD)协议²。
LPD正好可以满足需要。UNIX通常都支持这种网络协议,但经过简单的配置后也可在Windows中使用该协议。安装驱动时,通知Windows安装与计算机相连的新本地打印机,而不是通知它使用哪一个现有端口,这样就创建了一个新的LPR端口。这里可以定义运行LPD服务器的机器名称(本例中为TINI的IP地址),并提供打印队列的名称。(这里只有一个名为‘crusty’的打印队列,我想这个名字非常适合我的打印机。) 最后,告诉Windows打印机型号(笔者的打印机为Epson LX-800)。
这一设置具有一个好处,Windows打印机驱动能自动处理所有数据的格式,以采用打印机的原始语句(Epson ESC/P)进行打印。这样就不必了解ESC/P语言,也不必解析PC传送给TINI的任何数据。此外,非常容易实现基本的LPD服务器;调用少量Java本地方法实现对存储器映射CPLD的访问。最后的实际代码约有400行,准备用于TINI的Java类文件仅有4kB。需要注意的是,我并没有实现整个LPD协议,只是实现了每次打印一个文件所需的部分协议。
实施打印
最后再做3件事即可宣告设计圆满完成:打印Notepad文件,打印MS Word文件,如果意犹未尽,还可以打印小的GIF或JPG文件。在对LPD服务器进行一些调试之后,顺利地打印出了Notepad文件。比较奇怪的是,打印2行文本约向TINI发送了5,000个字节。其实这是因为Windows驱动将整个文件转换为ESC/P指令,从而发送一个位图图像,而不是简单地发送ASCII字符。打印完Notepad文档后,转而打印MS Word文档。打印MS Word文档时出现了一点小问题。为使测试文件变得更有趣,每隔两、三个字就变换不同的字体,这样便有机会看到一些以前从未见过的字体。打印时,出现了许多无用的字符图形:打印机会跳过几行,随即打印出一些奇怪的ASCII图形字符(0x80至0xFF范围内的一些字符),并不断发出嘟嘟声。最后,终于打印出一些正确的文字,但随后又是一堆乱码。经过多轮调试,并跟踪发送至TINI的ESC/P指令,决定试着为转义字符增加延迟。这样就能正确地运行打印程序,但是非常慢。经过多次试验后,发现只需在打印时碰到行结束字符时增加延迟即可。这种情况很少出现,因此实际打印时感觉不到延迟。
由于打印MS Word文档时碰到了一定的困难,因此一度断定打印GIF文件几乎是不可能的,但事实并非如此。打印时仅碰到一个问题:GIF文件转换为ESC/P指令后尺寸大于64k,而64k正是所用TINI版本支持的最大动态缓冲区大小。然而,TINI支持无此限制的文件系统,因此可以将接收的数据存储在文件中,而不是存储在动态存储器缓冲区中。一旦改用上述方法,就可以顺利地打印图形文件了,并且仅受TINI中RAM大小的限制。
评论
查看更多