源代碼包中:
parallelportcontrol.rar為示例工程下載
WinIoLib.rar為WinIo庫下載
目前,在實驗室和工業應用的各種控制系統中,串口是常用的計算機與外部控制系統之間的數據傳輸通道。由於串行通信方便易行,所以應用廣泛。但是使用串行通信,在實時性、速度、數據量等方面受到限制。而計算機的並行端口傳輸數據時是一次性傳送8個位(一個字節)或更多,由於傳輸量較大,因此數據的傳輸速度要比串口快,在許多必須講究傳輸速度的控制系統裡,用PC並行端口與之連接就是一個很好的解決方案。
本文介紹PC並行端口在單片機等控制系統中作為數字I/O口的應用。在控制系統中,有許多的數字開關量、數字控制信號、數字信號等,都可以通過計算機並行端口進行采集;並將采集的數字信號經計算機處理後形成數字控制信號,再從計算機的並行端口輸出進行各種自動控制。
一、PC並行端口介紹
目前,計算機中的並行接口主要作為打印機端口,接口使用的不再是36針接頭而是25針D形接頭。所謂“並行”,是指8位數據同時通過並行線進行傳送,這樣數據傳送速度大大提高。
現在常見的並口有五種:SPP型、PS/2型、EPP型、ECP型和多模式接口,大多數PC機配有SPP並口:
SPP標准並行口有4位、8位、半8位:4位口一次只能輸入4位數據,但可以輸出8位數據;8位口可以一次輸入和輸出8位數據;半8位也可以。
PS/2簡單雙向並行口:它引入雙向數據端口,這種雙向數據端口容許外設每次向PC機發送8位信息。PS/2型並口是指所有具有雙向數據端口,但不支持後面介紹的EPP或ECP模式的並行接口。
EPP增強並行口:允許8位雙向數據傳送,它可以在大約1ms的時間內完成包括握手聯絡在內的一個字節的數據傳送;而SPP或SP/2接口則需要大約4ms才能完成同樣的工作。因此可以連接各種非打印機設備,如掃描儀、LAN適配器、磁盤驅動器和CDROM 驅動器等。
ECP口擴展並行口:是雙向數據端口,並能以ISA總線速度傳送數據。ECP有緩沖區,支持命令周期、數據周期和多個邏輯設備尋址,在多任務環境下可以使用DMA(直接存儲器訪問)。
多模式接口:許多新型接口支持多種模式,可以工作在以上提到的部分或全部模式下,用戶可以使用配置選擇,使用上述各種接口形式,或只使用其中一些而禁止其它。
二、PC標准配備並行口介紹
本文主要介紹計算機的標准配備並行端口即25針的母接頭端口的應用,在此基礎上可以運用相同的原理使用其它模式的並行端口。並行端口共有25支腳,但不是每支腳均被使用到。這些腳被區分為3種主要的功能,分別是用於數據的傳送、檢查打印機的狀態及控制打印機,其接口如下所示:
在PC機中,標准並行口使用3個8位的端口寄存器,PC就是通過對這些寄存器,也就是所說的數據、狀態、控制寄存器的讀寫訪問並口的信號的。本文中使用一些通用的叫法,8個數據位分別為D0~D7,5個狀態位為S3~S7,4個控制為C0~C3。其中字母表示了端口寄存器,數字則表示該信號在寄存器中的位。
數據寄存器
數據端口或稱數據寄存器(D0~D7)保存了寫入數據輸出端口的一字節信息。數據端口可以寫入數據,也可以讀出數據(即可擦寫);寫進去的當然是我們希望從數據端口引腳輸出的數據,不過讀進來的也只是我們上次寫進去的數據,或是原來保留在裡面的數據,並不是從端口引腳輸入PC的數據。數據端口引腳是PIN2~PIN9,其定義如下:
數據寄存器(即數據輸出端口) 可擦寫、基地址 bit 引腳:D-sub 信號名 信號源 是否在連接器處倒相 0 Pin2 D0 PC 否 1 Pin3 D1 PC 否 2 Pin4 D2 PC 否 3 Pin5 D3 PC 否 4 Pin6 D4 PC 否 5 Pin7 D5 PC 否 6 Pin8 D6 PC 否 7 Pin9 D7 PC 否
如果我們把這8支腳當成一般的數字輸出的腳位看待,上述8支腳就相當於是8個數字輸出的位置一般,我們就可以把它們當成是8個可以自由控制的輸出點。當我們通過數據端口傳送數據時,就是改變這8支腳的電平狀態;而接受方也按照相同的編碼原則解釋,就可以獲得傳送的數據。
狀態寄存器
狀態端口或稱狀態寄存器保存的是5個輸入(S3~S7)的邏輯狀態。S0~S2位不出現在並口連接器中。除了S0以外,狀態寄存器是只讀的,讀出數據信息是狀態端口引腳上的邏輯狀態。S0是支持EPP傳輸並口的超時標志信息,可以用軟件方法清零。在許多並口中,狀態輸入接有上拉電阻。狀態端口引腳是Pin10~Pin13、Pin15,其定義如下:
狀態寄存器(即狀態輸入端口) 基地址+1 bit 引腳:D-sub 信號名 信號源 是否在連接器處倒相 0 Time-Out 1 未使用 2 未使用 3 Pin15 nError(nFault) 外設 否 4 Pin13 Select 外設 否 5 Pin12 PaperEnd 外設 否 6 Pin10 nAck 外設 否 7 Pin11 Busy 外設 是
上表中所謂的(基地址+1)指的是:如果我們的LPT地址是378H,在加上1就是379H;這個地址是專門用來傳遞打印機的狀態的。和數據地址比較起來不一樣的是,這裡地址並非在連接器的腳位上均有對應點。在這個狀態的顯示上只有5個腳位有對應,位S0~S2是沒有的--最起碼是無法讓計算機有對應的值可讀取。
如果打印機接到並口上,那麼打印機的狀態將會通過這幾支腳傳送到PC,程序只要去基地址+1的位置讀取數值即可知道現在打印機所處的狀態。由於這幾支腳可以讓打印機傳送狀態給PC,那麼我們可以把這幾支腳位拿來當作數字輸入的通道;我們可以讓這幾支腳位的狀態發生電位的改變,而利用程序去讀取這些腳位的數值,即可實現數據的輸入。
控制寄存器
控制端口或稱控制寄存器保存了C0~C3的4位的控制信息。C4~C7不出現在並口連接器中。一般來說,這些位被用來輸出,然而大多數SPP中,控制位為集電極開路/漏極開路模式,也就是說,它們同樣可以用作輸入。要從控制位上讀取外部邏輯信號,首先將向相應的輸出寫入“1”,然後讀取控制寄存器的值即可。但是,為了提高交換速度,大多數支持EPP和ECP接口中,控制位工作在不能用作輸入的推拉模式下。在一些多模式接口中,控制位采用的是改進型的推拉模式,可以用作輸入。控制端口引腳是Pin1、Pin14、Pin16和Pin17,其定義如下:
控制寄存器(即控制輸出端口) 基地址+2 bit 引腳:D-sub 信號名 信號源 是否在連接器處倒相 0 Pin1 nStrobe PC 是 1 Pin14 nAutoLF PC 是 2 Pin16 nInit PC 否 3 Pin17 nSelectIn PC 是 4 IRQ 5 未使用 6 未使用 7 未使用
上表中所謂的(基地址+2)指的是:如果我們的LPT地址是378H,在加上1就是37AH;這個地址是專門用來控制打印機動作的。
如同數據的送出,我們的程序只要將我們的信息送往(基地址+2)的地址去,就可以實現數據輸出,接受端在相應引腳就可以接受到相應的邏輯電位狀態。當控制端口的信號源為高電平時,這些引腳可以作為輸入引腳,如同狀態端口引腳一樣。
在上述定義表格中,所謂“是否在連接器處倒相”是指並口硬件將連接器與相應寄存器位之間的4個信號進行了倒相處理。具體說來,S7、C0、C1、C3信號的邏輯狀態在連接器處是與相應寄存器位反相的。當你對這些位進行寫操作時,必須牢記寫入的值應該與你想在連接器處設置的值相反;當要對這些位進行讀操作時,也必須記住所讀取的值與連接器處的值相反。
計算機的標准配備並行端口除以上介紹的數據端口引腳Pin2~Pin9、狀態端口引腳Pin15、Pin10~Pin13、控制端口引腳Pin1、Pin14、pin16、Pin17外,連接器上的 其它引腳Pin18~Pin25是歸地引腳GND。
三、PC並行口數字輸入/輸出
所謂的數字輸出就是在程序要求某一個設備的某一開關點開或關,產生高電位或低電位。從計算機的觀點來說,低電位就是0.7V以下(邏輯0),而高電位是2.1V以上(邏輯1),若電位處在0.7~2.1V時,電位的邏輯狀態是不確定的。想要通過計算機去控制外部設備,最簡單的方法就是控制數字輸出。
所謂的數字輸入,也就是外界的狀況被計算機用0或1的數值予以記錄下來而儲存,此0與1就代表了外界某一個設備的某一開關點開或關的兩種情形。
PC並行口即可以作數字輸出口,也可以作數字輸入口。其中的數據端口、控制端口都可以作為數字輸出端口,數據端口共8位,控制端口共4位,兩個端口可以組成1~12位的任意數字輸出端口;其中的狀態端口、控制端口都可以作為數字輸入端口,狀態端口共5位,控制端口共4位,兩個端口可以組成1~9位的任意數字輸入端口。本文給出了並行端口3種寄存器的讀寫方法,如下圖所示:
四、PC並行口數字輸入/輸出的VC實現
由於Windows對系統底層操作采取了屏蔽的策略,因而對用戶而言,系統變得更為安全,但這卻給眾多的硬件或者系統軟件開發人員帶來了不小的困難,因為只要應用中涉及到底層的操作,開發人員就不得不深入到Windows的內核去編寫屬於系統級的設備驅動程序。對並行口的讀寫操作就是如此,由於Windows對系統的保護,絕對不允許任何的直接I/O動作發生,所以必須帶上*.dll、*.sys或*.vxd文件,這些文件用來讓操作系統知道有一個特定的I/O可能會被調用。系統開機後,這些文件中的內容就會加載到內存中,一旦有對應的動作發生,就會引發I/O的實際動作。
本文只是介紹並行口作為數字I/O口的使用,不在於介紹並行I/O口驅動的編寫。故本文中直接使用由 Yariv Kaplan 編寫的 WinIo 庫,它有如下特點:WinIo 庫通過使用內核模式下設備驅動程序和 其它一些底層編程技巧繞過 Windows 安全保護機制,允許32位 Windows 程序直接對 I/O 口進行操作;
支持Windows 9x、Windows NT、Windows2000、WindowsXP環境;在Windows NT/2000/XP下,允許非 Administrator 用戶應用 WinIo 應用程序;不支持中斷。
注意事項:使用這個類代碼時請確保不要與其它使用常規 Win32 調用操作並行端口的程序發生沖突。
WinIo庫在VC應用程序中的使用(WinIo庫下載)
為了在VC中能正常使用WinIo庫,必須按以下步驟進行配置:
(1):將WinIo.dll、WinIo.sys、WINIO.VXD三個文件放在程序可執行文件所在目錄下;
(2):將WinIo.lib添加到工程中,WinIo.lib及winio.h文件必須放在工程目錄下;
(3):在StdAfx.h頭文件中加入#include "winio.h"語句;
(4):調用InitializeWinIo函數初始化WinIo驅動庫;
(5):調用讀寫IO口的GetPortVal或SetPortVal函數;
(6):調用ShutdownWinIo函數;
在非管理員權限下運行,必須首先完成以下步驟:
(1):將WinIo.dll、WinIo.sys、WINIO.VXD三個文件放在任一WinIo應用程序可執行文件所在目錄下;
(2):以管理員或其它具有管理員權限的用戶身份登陸;
(3):調用InstallWinIoDriver函數,第一個參數設置為WinIo.sys文件所在目錄路徑,第二個參數設置為false;
(4):重新啟動系統;
(5):以普通用戶身份登錄,現在可以調用WinIo庫函數;
(6):當不再需要WinIo庫時,可以再次以管理員身份或其它具有管理員權限的用戶身份登陸系統,調用RemoveWinIoDriver卸載該庫;
WinIo庫中幾個函數說明:
(1):初始化與終止
bool _stdcall InitializeWinIo();
void _stdcall ShutdownWinIo();
(2):安裝與卸載
bool _stdcall InstallWinIoDriver(PSTR pszWinIoDriverPath, bool IsDemandLoaded = false);
bool _stdcall RemoveWinIoDriver();
(3):讀寫I/O口
bool _stdcall GetPortVal(WORD wPortAddr, PDWORD pdwPortVal, BYTE bSize);
bool _stdcall SetPortVal(WORD wPortAddr, DWORD dwPortVal, BYTE bSize);
GetPortVal函數從指定端口讀取一個BYTE/WORD/DWORD類型的值;
wPortAddr是指定一個端口地址值;
pdwPortVal為指向一雙字節型變量的指針,該變量存儲從wPortAddr端口讀取的值;
bSize指定讀取字節數,值可以為1,2或4。
SetPortVal函數向指定端口寫入一個BYTE/WORD/DWORD類型的值;
除dwPortVal為輸入參數,表示待寫入外,其余個變量含義與GetPortVal相似。
PC並行口數字輸出的VC實現(示例工程下載)
為了測試並行口的數字輸出,可以准備12支LED發光二極管,將LED的陽極分別與數據端口引腳Pin2~Pin9和控制端口引腳Pin1、Pin14、Pin16、Pin17相連接;將LED的陰極連接在一起與並行口的歸地引腳GND相連即可。在實際控制應用中不能這樣連接,因為數據端口引腳、控制端口引腳輸出的電流非常小,只有10mA左右,必須添加 其它硬件電路。
(1):數據端口數字輸出的VC實現
//獲得數據端口地址
WORD m_nport=(WORD)0x378;
//獲得要寫入數據端口的值WriteValue(數據范圍為0~255)
DWORD m_nValue=(DWORD)WriteValue;
//調用WinIo庫函數SetPortVal寫端口值
SetPortVal(m_nport, m_nValue, 1);//write a BYTE value to an I/O port
(2):控制端口數字輸出的VC實現
//獲得控制端口地址
WORD m_nport=(WORD)0x37A;
//獲得控制端口的值,保持高位值不變,將要輸出的值從低4位輸出,且使連接器上的電位狀態與想輸出的值一致
DWORD temp_dwPortVal;
unsigned int temp_aa;
GetPortVal(m_nport, &temp_dwPortVal, 1); //reads a BYTE value from an I/O port
temp_aa=(unsigned int)temp_dwPortVal;
temp_aa=temp_aa&0x0F0; //取低8位值,將低4位置為0;高4位不變;
temp_aa=temp_aa^0x0B; //將低4位中C0、C1、C3置為1,C2置為0;高4位不變;
//獲得要寫入控制端口的值WriteValue(數據范圍為0~15)
unsigned int WriValue;
WriValue=WriteValue&0x0F; //取低4位;
temp_aa=temp_aa^WriValue; //將寫入值的低4位中的C0、C1、C3取反,C2位不變,高4位保持端口值不變
SetPortVal(m_nport, (DWORD)temp_aa, 1); //寫出的值中,高4位保持端口原來的值不變,
//低4位是寫入什麼電平,連接器上既是什麼電平
(3):數據端口及控制端口組合成12位數字輸出的VC實現
//獲得端口地址
WORD m_nportData=(WORD)0x378;
WORD m_nportControl=(WORD)0x37A;
//獲得要寫入端口的值WriteValue(數據范圍為0~4095)
DWORD m_nValue=(DWORD)(WriteValue&0x0FF);//取低8位值
SetPortVal(m_nportData, m_nValue, 1);//write a BYTE value to Data port
DWORD temp_dwPortVal;
unsigned int temp_aa;
GetPortVal(m_nportControl, &temp_dwPortVal, 1); //reads a BYTE value from an I/O port
temp_aa=(unsigned int)temp_dwPortVal;
temp_aa=temp_aa&0x0F0; //取低8位值,將低4位置為0;高4位不變;
temp_aa=temp_aa^0x0B; //將低4位中C0、C1、C3置為1,C2置為0;高4位不變;
unsigned int WriValue;
WriValue=WriValue>>8;//取高4位值
temp_aa=temp_aa^WriValue; //將寫入值的低4位中的C0、C1、C3取反,C2位不變,高4位保持端口值不變
SetPortVal(m_nportControl, (DWORD)temp_aa, 1); //寫出的值中,高4位保持端口原來的值不變,
//低4位是寫入什麼電平,連接器上既是什麼電平
PC並行口數字輸入的VC實現
(1):狀態端口數字輸入的VC實現
為了測試並行口狀態端口的數字輸入,可以將數據端口引腳Pin2~Pin6連接到狀態端口引腳Pin15、Pin13、Pin12、Pin10、Pin11上。引腳接好後,先從數據端口輸出數據,在從狀態端口和控制端口讀出數據,讀出的數據應與寫入的數據一致,數據范圍為0~31。
DWORD dwPortVal;
unsigned int ValueGet=0;
//獲得端口地址
WORD m_nport=(WORD)0x379;
//獲得端口數據
GetPortVal(m_nport, &dwPortVal, 1);
ValueGet=(unsigned int)dwPortVal;
ValueGet=ValueGet^0x80; //保持得到的State值與連接器處的值一直;
ValueGet=ValueGet&0xF8; //去掉S0 ~S2位;
ValueGet=ValueGet>>3; //右移3位,將S7~S3變為低5位
(2):控制端口數字輸入的VC實現
為了測試並行口控制端口的數字輸入,可以將數據端口引腳Pin2~Pin5連接到控制端口引腳Pin1、Pin14、Pin16、Pin17上 。引腳接好後,先從數據端口輸出數據,在從狀態端口和控制端口讀出數據,讀出的數據應與寫入的數據一致,數據范圍為0~15。
//獲得端口地址
WORD m_nport=(WORD)0x37A;
//===== 將C0~C3位置1,即使連接器上為高電平 ,使控制端口為輸入端口=====
DWORD temp_dwPortVal;
unsigned int temp_aa;
GetPortVal(m_nport, &temp_dwPortVal, 1); //獲取端口的當前值
temp_aa=(unsigned int)temp_dwPortVal;
temp_aa=temp_aa&0x0F0; //取低8位值,將低4位置為0;高4位不變;
temp_aa=temp_aa^0x4; //將低4位中C0、C1、C3置為0,C2置為1;高4位不變;
SetPortVal(m_nport, (DWORD)temp_aa, 1); //寫出的值中,高4位保持端口原來的值不變,
//低4位是寫高電平,即使連接器上是高電平
//=============================================================
unsigned int ValueGet=0;
DWORD dwPortVal;
//獲得端口數據
GetPortVal(m_nport, &dwPortVal, 1);
ValueGet=(unsigned int)dwPortVal;
ValueGet=ValueGet^0x0B; //保持C0,C1,C3位的值與連接器處的值一至;
ValueGet=ValueGet&0x0F; //去掉高4位值
(3):控制端口及狀態端口組合成9位數字輸入的VC實現
為了測試並行口的數字輸入,可以將數據端口引腳Pin2~Pin9連接到控制端口引腳Pin1、Pin14、Pin16、Pin17和狀態端口引腳Pin15、Pin13、Pin12、Pin10上 ,Pin11引腳連接到歸地引腳GND或懸空。引腳接好後,先從數據端口輸出數據,在從狀態端口和控制端口讀出數據,讀出的數據應與寫入的數據一致,當Pin11引腳連接到歸地引腳GND時,數據范圍為0~255;當Pin11引腳懸空時,數據范圍為256~511。
unsigned int ValueGet=0;
//獲得端口地址
WORD m_nportState=(WORD)0x379;
WORD m_nportControl=(WORD)0x37A;
//Read State Port
DWORD dwPortVal;
unsigned int ValueState=0;
GetPortVal(m_nportState, &dwPortVal, 1);
ValueState=dwPortVal;
ValueState=ValueState^0x80; //保持得到的State值與連接器處的值一直;
ValueState=ValueState&0xF8; //去掉S0 ~S2位;
ValueState=ValueState<<1; //左移1位,將S7~S3變為高5位
//Read control Port
//========== 將C0~C3位置1,即使連接器上是高電平 ,使控制端口為輸入端口=====
GetPortVal(m_nportControl, &dwPortVal, 1); //獲取端口的當前值
ValueGet=(unsigned int)dwPortVal;
ValueGet=ValueGet&0x0F0; //取低8位值,將低4位置為0;高4位不變;
ValueGet=ValueGet^0x4; //將低4位中C0、C1、C3置為0,C2置為1;高4位不變;
SetPortVal(m_nportControl, (DWORD)ValueGet, 1); //寫出的值中,高4位保持端口原來的值不變,
//低4位是寫高電平,即使連接器上是高電平
//=============================================================
unsigned int ValueControl=0;
GetPortVal(m_nportControl, &dwPortVal, 1);
ValueControl=(unsigned int)dwPortVal;
ValueControl=ValueControl^0x0B; //保持C0,C1,C3位的值與連接器處的值一至;
ValueControl=ValueControl&0x0F; //去掉高4位值
//get 9bit value
ValueGet=ValueState^ValueControl;
五、結束
本文只是介紹PC並行端口作為數字I/O口的應用方法,在實際運用到控制系統中進行數字信號通信時,必須注意對並行端口信號進行其它的處理,以提高端口信號的抗干擾能力、穩定性及可靠性等。
本文配套源碼