摘要:通過互聯(lián)網可以使陳舊的計算機設備重煥生機。本篇應用筆記闡述了如何采用TINI?,并通過網絡使只有一個并行端口的舊式點陣打印機重新發(fā)揮作用。采用相同的協(xié)議轉換技術,可將許多設備連接至互聯(lián)網。
TINI平臺支持TCP/IP網絡棧(IPv4和IPv6)、存儲器管理、進程調度以及諸如I2C、SPI和CAN等通信協(xié)議。通過常見的編程接口,可用8051匯編語言、C或Java對TINI進行編程。C runtime可提供一個Berkeley套接字(socket interface),Java運行環(huán)境支持Java 1.1.8 API內核。
TINI具有豐富的IO、簡單的網絡接口以及多種編程方法,是一套功能強大的協(xié)議轉換器。這正是挽救那臺舊式打印機所需要的:TINI提供網絡接口,我來決定如何讓TINI與打印機通信。
所討論的打印機型號為Epson LX-800。從碳定年檢測和厚厚的灰塵來看,它是鄰近真空管和呼拉圈年代的產物。LX-800是9針打印機,這里的9針是指打印頭的針數,而不是驅動打印機的并行信號個數。實際上驅動打印機需要17個信號(不包括地)。PC打印機并行接口的常用信號及其在25芯打印機連接器中的排列如圖1所示。

圖1. 并行打印機接口的25芯信號定義
采用傳統(tǒng)的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系統(tǒng)。微控制器僅需讀/寫特定地址就能訪問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會讀取錯誤的指令或數據,并產生無法預料的系統(tǒng)操作(即“產生錯誤”)。同樣,并非所有寫操作都是針對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?開發(fā),用Verilog?硬件描述語言編寫,模塊定義如圖2所示,包括獨立的8位寄存器模塊、多路復用器以及譯碼器。在頂層模塊中實現(xiàn)模塊和地址譯碼。
CPLD安裝并編程完畢后,在與打印機連接之前,我還想檢查確認具體用到了哪些信號。于是搭建了一個小型電路板,將4組LED與4個邏輯寄存器(IO7:0、IO15:8、IO23:16和IO31:24)相連,如圖3所示。用C編寫了一個小程序,使4組LED的數值增加,中間插入暫停,以便通過視覺驗證程序的操作過程。確信CPLD編程及連接正確后,可接入打印機。圖3所示為TINI板與25芯打印機連接器之間的飛線連接。
圖3. TINIs400插座板與Epson打印機連接
第一步,先嘗試簡單的應用,在不聯(lián)網的情況下向打印機發(fā)送一些ASCII字符。這種方法只能起到調試作用。從理論上講,只要發(fā)送的不是指令編碼和其它非ASCII字符,打印機會將每個字符直接打印出來。希望這個項目能像所有好的工程項目一樣,能夠流暢快速地打印,從而能盡快確定網絡接口。實際上我完全錯了。
利用Keil Software? μVision? 2工具套件,并采用C語言編寫了第一個打印機應用程序。根據Indispensable PC Hardware Book的說明,似乎需要對打印機進行初始化,并開始寫字符1。寫字符的算法比較簡單:
012345678901234567890123456789...
將程序下載至TINI上并運行。我立刻知道打印機初始化程序已正常工作了,因為在關機并再次開機時,能聽到打印頭移動的聲音。然而,幾秒鐘后沒打印出任何東西。將程序復位,仔細檢查連接是否正確。遺憾的是,沒有留出時間監(jiān)視打印機的狀態(tài)信號。一切都很正常,于是重新到計算機硬件指南中尋找答案。經過一番研讀,辨別出某些信號的極性出現(xiàn)了混淆。例如,就傳統(tǒng)的PC接口而言,當BSY為0時該信號有效,然而從硬件方面來說它實際上為高電平有效。反復修改C程序,并改變不同信號的極性,但打印機初始化后仍不能工作。又檢查了一遍程序,使邏輯電平與我想象的打印機信號電平相匹配。修改程序并運行后,打印機依然沒有反應。
離開片刻,聽到點陣打印機打印完一行文本時發(fā)出的刺耳聲音。近前一看,已打印出一行漂亮的字符,但隨后打印機又不動了。一個原理突然浮現(xiàn)在腦海中:無論是否達到80個字符的極限或是否將行結束序列插入數據流,打印機都是直到行末才進行緩沖。幾次打印操作的工作過程證明了這一原理。現(xiàn)在可以打印幾行文本了。
理想設計應該是一個虛擬并行端口。也就是說,虛擬并行端口提供與標準并行端口一樣的接口,但就驅動而言,實際上是通過網絡向TINI發(fā)送和接收數據。類似于這樣的虛擬端口和協(xié)議轉換器是TINI的一般應用,按照這一設計思想,我曾有過一些實現(xiàn)虛擬串行端口的經驗。似乎很多應用都是直接寫PC的并行寄存器,因此很難確定如何實現(xiàn)這種設計思想。下一步就是查看現(xiàn)有協(xié)議。根據同事建議,查閱了RFC 1179,行式打印機監(jiān)控程序LPD)協(xié)議2。
LPD正好可以滿足需要。UNIX通常都支持這種網絡協(xié)議,但經過簡單的配置后也可在Windows中使用該協(xié)議。安裝驅動時,通知Windows安裝與計算機相連的新本地打印機,而不是通知它使用哪一個現(xiàn)有端口,這樣就創(chuàng)建了一個新的LPR端口。這里可以定義運行LPD服務器的機器名稱(本例中為TINI的IP地址),并提供打印隊列的名稱。(這里只有一個名為‘crusty’的打印隊列,我想這個名字非常適合我的打印機。) 最后,告訴Windows打印機型號(筆者的打印機為Epson LX-800)。
這一設置具有一個好處,Windows打印機驅動能自動處理所有數據的格式,以采用打印機的原始語句(Epson ESC/P)進行打印。這樣就不必了解ESC/P語言,也不必解析PC傳送給TINI的任何數據。此外,非常容易實現(xiàn)基本的LPD服務器;調用少量Java本地方法實現(xiàn)對存儲器映射CPLD的訪問。最后的實際代碼約有400行,準備用于TINI的Java類文件僅有4kB。需要注意的是,我并沒有實現(xiàn)整個LPD協(xié)議,只是實現(xiàn)了每次打印一個文件所需的部分協(xié)議。
打印MS Word文檔時出現(xiàn)了一點小問題。為使測試文件變得更有趣,每隔兩、三個字就變換不同的字體,這樣便有機會看到一些以前從未見過的字體。打印時,出現(xiàn)了許多無用的字符圖形:打印機會跳過幾行,隨即打印出一些奇怪的ASCII圖形字符(0x80至0xFF范圍內的一些字符),并不斷發(fā)出嘟嘟聲。最后,終于打印出一些正確的文字,但隨后又是一堆亂碼。經過多輪調試,并跟蹤發(fā)送至TINI的ESC/P指令,決定試著為轉義字符增加延遲。這樣就能正確地運行打印程序,但是非常慢。經過多次試驗后,發(fā)現(xiàn)只需在打印時碰到行結束字符時增加延遲即可。這種情況很少出現(xiàn),因此實際打印時感覺不到延遲。
由于打印MS Word文檔時碰到了一定的困難,因此一度斷定打印GIF文件幾乎是不可能的,但事實并非如此。打印時僅碰到一個問題:GIF文件轉換為ESC/P指令后尺寸大于64k,而64k正是所用TINI版本支持的最大動態(tài)緩沖區(qū)大小。然而,TINI支持無此限制的文件系統(tǒng),因此可以將接收的數據存儲在文件中,而不是存儲在動態(tài)存儲器緩沖區(qū)中。一旦改用上述方法,就可以順利地打印圖形文件了,并且僅受TINI中RAM大小的限制。
引言
妻子將只有兩個破洞的襪子以及略沾草跡的襯衫都扔掉了,這沒什么。但當她將目標轉向那臺舊式點陣打印機時,我表示了抗議。她不屑地說:“這東西很久沒用了,而且也不能連接到任何電腦上。”就像科學怪人一樣,我不能容忍任何丟棄舊計算機設備的念頭。目的非常明確:使打印機變廢為寶,或者干脆將其丟棄。我決定通過網絡使其重新煥發(fā)生機,幸運地是用一個TINI (微型網絡接口)可完成這項工作。連接網絡
TINI是Maxim提供的嵌入式網絡平臺,它是基于該公司的DS80C390、DS80C400、DS80C410和DS80C411微控制器而構建的。這些器件都是增強型8051控制器,具有24位地址、硬件網絡控制器、多個數據指針、專用硬件堆棧和高速工作模式等特性。TINI平臺支持TCP/IP網絡棧(IPv4和IPv6)、存儲器管理、進程調度以及諸如I2C、SPI和CAN等通信協(xié)議。通過常見的編程接口,可用8051匯編語言、C或Java對TINI進行編程。C runtime可提供一個Berkeley套接字(socket interface),Java運行環(huán)境支持Java 1.1.8 API內核。
TINI具有豐富的IO、簡單的網絡接口以及多種編程方法,是一套功能強大的協(xié)議轉換器。這正是挽救那臺舊式打印機所需要的:TINI提供網絡接口,我來決定如何讓TINI與打印機通信。
硬件配置
系統(tǒng)的核心部分采用TINI評估(EV)板。該評估板基于DS80C400微控制器,包括1MB閃存、1MB RAM,以及用于RS-232和以太網通信的連接器。雖然其存儲器配置與TINI Java運行環(huán)境兼容,但是仍可采用C和匯編語言進行編程。這給打印機接口原型設計和應用實現(xiàn)提供了多種選擇。所討論的打印機型號為Epson LX-800。從碳定年檢測和厚厚的灰塵來看,它是鄰近真空管和呼拉圈年代的產物。LX-800是9針打印機,這里的9針是指打印頭的針數,而不是驅動打印機的并行信號個數。實際上驅動打印機需要17個信號(不包括地)。PC打印機并行接口的常用信號及其在25芯打印機連接器中的排列如圖1所示。

圖1. 并行打印機接口的25芯信號定義
采用傳統(tǒng)的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具有非常靈活的內部架構,能實現(xiàn)許多邏輯功能和狀態(tài)機。TINI插座板為100引腳VQFP封裝的XILINX? CoolRunner?-II XC2C64 CPLD預留了空間;也支持封裝和引腳配置相同的其它CoolRunner-II。本項目采用具有相同引腳配置的大容量器件XC2C128。CPLD通過DS80C400微控制器的外部存儲器總線連接至TINI系統(tǒng)。微控制器僅需讀/寫特定地址就能訪問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需要讀取指示打印機工作情況的狀態(tài)信號(如BSY),并提供輸出信號(如8位數據總線和寫選通STR)以控制打印機。圖2所示為CPLD需要實現(xiàn)的功能框圖。

圖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會讀取錯誤的指令或數據,并產生無法預料的系統(tǒng)操作(即“產生錯誤”)。同樣,并非所有寫操作都是針對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?開發(fā),用Verilog?硬件描述語言編寫,模塊定義如圖2所示,包括獨立的8位寄存器模塊、多路復用器以及譯碼器。在頂層模塊中實現(xiàn)模塊和地址譯碼。
CPLD安裝并編程完畢后,在與打印機連接之前,我還想檢查確認具體用到了哪些信號。于是搭建了一個小型電路板,將4組LED與4個邏輯寄存器(IO7:0、IO15:8、IO23:16和IO31:24)相連,如圖3所示。用C編寫了一個小程序,使4組LED的數值增加,中間插入暫停,以便通過視覺驗證程序的操作過程。確信CPLD編程及連接正確后,可接入打印機。圖3所示為TINI板與25芯打印機連接器之間的飛線連接。
圖3. TINIs400插座板與Epson打印機連接
與打印機通信
最終目標是:無需任何專門驅動或客戶應用程序,就能使打印機正常工作。簡單地說,就是嘗試用內置打印驅動并通過Windows?打印對話框,使舊式打印機發(fā)揮作用。本應用的最后測試步驟是打印MS Word文檔;如果出現(xiàn)問題,Word會透露一些信息。盡管打印出MS Word文檔是最終目標,仍希望用更簡潔的方案來實現(xiàn)這一目標。第一步,先嘗試簡單的應用,在不聯(lián)網的情況下向打印機發(fā)送一些ASCII字符。這種方法只能起到調試作用。從理論上講,只要發(fā)送的不是指令編碼和其它非ASCII字符,打印機會將每個字符直接打印出來。希望這個項目能像所有好的工程項目一樣,能夠流暢快速地打印,從而能盡快確定網絡接口。實際上我完全錯了。
利用Keil Software? μVision? 2工具套件,并采用C語言編寫了第一個打印機應用程序。根據Indispensable PC Hardware Book的說明,似乎需要對打印機進行初始化,并開始寫字符1。寫字符的算法比較簡單:
- 等待BSY變?yōu)闊o效。
- 設置數據總線上的輸出值。
- 設置寫選通STR為有效。
- 等待ACK信號,確認已收到數據。
- 設置寫選通STR為無效。
- 可根據需要進行清除或重復操作。
012345678901234567890123456789...
將程序下載至TINI上并運行。我立刻知道打印機初始化程序已正常工作了,因為在關機并再次開機時,能聽到打印頭移動的聲音。然而,幾秒鐘后沒打印出任何東西。將程序復位,仔細檢查連接是否正確。遺憾的是,沒有留出時間監(jiān)視打印機的狀態(tài)信號。一切都很正常,于是重新到計算機硬件指南中尋找答案。經過一番研讀,辨別出某些信號的極性出現(xiàn)了混淆。例如,就傳統(tǒng)的PC接口而言,當BSY為0時該信號有效,然而從硬件方面來說它實際上為高電平有效。反復修改C程序,并改變不同信號的極性,但打印機初始化后仍不能工作。又檢查了一遍程序,使邏輯電平與我想象的打印機信號電平相匹配。修改程序并運行后,打印機依然沒有反應。
離開片刻,聽到點陣打印機打印完一行文本時發(fā)出的刺耳聲音。近前一看,已打印出一行漂亮的字符,但隨后打印機又不動了。一個原理突然浮現(xiàn)在腦海中:無論是否達到80個字符的極限或是否將行結束序列插入數據流,打印機都是直到行末才進行緩沖。幾次打印操作的工作過程證明了這一原理。現(xiàn)在可以打印幾行文本了。
實現(xiàn)LPD監(jiān)控程序
第二天,決定將網絡與打印機接口連接。簡單說,就是創(chuàng)建一個用戶應用程序,直接從網絡上接收數據,并輸出給打印機。這種方法存在的問題是:若要打印機與MS Word一起工作,還需要編寫一個Windows打印機驅動程序,這一步工作可以實現(xiàn),但這并不是我的樂趣所在。編寫驅動程序這一做法老早就有,用不著再多此一舉,我開始考慮其它選擇,希望不用了解打印機驅動的全部細節(jié)就可實現(xiàn)目標。理想設計應該是一個虛擬并行端口。也就是說,虛擬并行端口提供與標準并行端口一樣的接口,但就驅動而言,實際上是通過網絡向TINI發(fā)送和接收數據。類似于這樣的虛擬端口和協(xié)議轉換器是TINI的一般應用,按照這一設計思想,我曾有過一些實現(xiàn)虛擬串行端口的經驗。似乎很多應用都是直接寫PC的并行寄存器,因此很難確定如何實現(xiàn)這種設計思想。下一步就是查看現(xiàn)有協(xié)議。根據同事建議,查閱了RFC 1179,行式打印機監(jiān)控程序LPD)協(xié)議2。
LPD正好可以滿足需要。UNIX通常都支持這種網絡協(xié)議,但經過簡單的配置后也可在Windows中使用該協(xié)議。安裝驅動時,通知Windows安裝與計算機相連的新本地打印機,而不是通知它使用哪一個現(xiàn)有端口,這樣就創(chuàng)建了一個新的LPR端口。這里可以定義運行LPD服務器的機器名稱(本例中為TINI的IP地址),并提供打印隊列的名稱。(這里只有一個名為‘crusty’的打印隊列,我想這個名字非常適合我的打印機。) 最后,告訴Windows打印機型號(筆者的打印機為Epson LX-800)。
這一設置具有一個好處,Windows打印機驅動能自動處理所有數據的格式,以采用打印機的原始語句(Epson ESC/P)進行打印。這樣就不必了解ESC/P語言,也不必解析PC傳送給TINI的任何數據。此外,非常容易實現(xiàn)基本的LPD服務器;調用少量Java本地方法實現(xiàn)對存儲器映射CPLD的訪問。最后的實際代碼約有400行,準備用于TINI的Java類文件僅有4kB。需要注意的是,我并沒有實現(xiàn)整個LPD協(xié)議,只是實現(xiàn)了每次打印一個文件所需的部分協(xié)議。
實施打印
最后再做3件事即可宣告設計圓滿完成:打印Notepad文件,打印MS Word文件,如果意猶未盡,還可以打印小的GIF或JPG文件。在對LPD服務器進行一些調試之后,順利地打印出了Notepad文件。比較奇怪的是,打印2行文本約向TINI發(fā)送了5,000個字節(jié)。其實這是因為Windows驅動將整個文件轉換為ESC/P指令,從而發(fā)送一個位圖圖像,而不是簡單地發(fā)送ASCII字符。打印完Notepad文檔后,轉而打印MS Word文檔。打印MS Word文檔時出現(xiàn)了一點小問題。為使測試文件變得更有趣,每隔兩、三個字就變換不同的字體,這樣便有機會看到一些以前從未見過的字體。打印時,出現(xiàn)了許多無用的字符圖形:打印機會跳過幾行,隨即打印出一些奇怪的ASCII圖形字符(0x80至0xFF范圍內的一些字符),并不斷發(fā)出嘟嘟聲。最后,終于打印出一些正確的文字,但隨后又是一堆亂碼。經過多輪調試,并跟蹤發(fā)送至TINI的ESC/P指令,決定試著為轉義字符增加延遲。這樣就能正確地運行打印程序,但是非常慢。經過多次試驗后,發(fā)現(xiàn)只需在打印時碰到行結束字符時增加延遲即可。這種情況很少出現(xiàn),因此實際打印時感覺不到延遲。
由于打印MS Word文檔時碰到了一定的困難,因此一度斷定打印GIF文件幾乎是不可能的,但事實并非如此。打印時僅碰到一個問題:GIF文件轉換為ESC/P指令后尺寸大于64k,而64k正是所用TINI版本支持的最大動態(tài)緩沖區(qū)大小。然而,TINI支持無此限制的文件系統(tǒng),因此可以將接收的數據存儲在文件中,而不是存儲在動態(tài)存儲器緩沖區(qū)中。一旦改用上述方法,就可以順利地打印圖形文件了,并且僅受TINI中RAM大小的限制。
評論