Do cơ chế bảo vệ hệ thống của Windows, các ứng dụng người dùng không được phép thực hiện thao tác I/O trực tiếp với phần cứng. Để truy cập các cổng như cổng song song (parallel port), cần sử dụng thư viện hỗ trợ hoạt động ở chế độ kernel. Bài viết này trình bày cách sử dụng thư viện WinIo để đọc/ghi dữ liệu số qua cổng song song trên PC bằng Visual C++.
Giới thiệu WinIo
WinIo là thư viện mã nguồn mở do Yariv Kaplan phát triển, cho phép ứng dụng 32-bit trên Windows (9x/NT/2000/XP) truy cập trực tiếp vào các cổng I/O vật lý bằng cách nạp driver ở chế độ kernel. Thư viện này không hỗ trợ ngắt và yêu cầu một số bước cấu hình đặc biệt, đặc biệt khi chạy dưới quyền người dùng thường.
Cấu hình WinIo trong dự án VC++
- Đặt các tệp
WinIo.dll,WinIo.sys,WINIO.VXDcùng thư mục với file thực thi. - Thêm
WinIo.libvào project và đảm bảowinio.hcó sẵn trong thư mục dự án. - Bao gồm header:
#include "winio.h"trongStdAfx.h. - Gọi
InitializeWinIo()để khởi tạo. - Sử dụng
GetPortVal()hoặcSetPortVal()để đọc/ghi cổng. - Gọi
ShutdownWinIo()khi kết thúc.
Đối với môi trường không có quyền Administrator (trên NT/2000/XP):
- Đăng nhập với tài khoản Administrator.
- Gọi
InstallWinIoDriver("đường_dẫn_WinIo.sys", false). - Khởi động lại hệ thống.
- Đăng nhập lại bằng tài khoản thường và sử dụng WinIo bình thường.
- Khi không cần nữa, gọi
RemoveWinIoDriver()để gỡ bỏ driver.
Hàm chính trong WinIo
bool _stdcall InitializeWinIo();
void _stdcall ShutdownWinIo();
bool _stdcall InstallWinIoDriver(PSTR pszWinIoDriverPath, bool IsDemandLoaded = false);
bool _stdcall RemoveWinIoDriver();
bool _stdcall GetPortVal(WORD wPortAddr, PDWORD pdwPortVal, BYTE bSize);
bool _stdcall SetPortVal(WORD wPortAddr, DWORD dwPortVal, BYTE bSize);
Xuất tín hiệu số qua cổng song song
Cổng song song tiêu chuẩn (LPT1) có địa chỉ cơ sở 0x378. Các thanh ghi liên quan:
- Data port:
0x378– 8 bit đầu ra (Pin 2–9) - Status port:
0x379– 5 bit đầu vào (Pin 10–13, 15) - Control port:
0x37A– 4 bit (2 đầu ra, 2 đầu vào đảo – Pin 1, 14, 16, 17)
1. Xuất qua Data Port
WORD dataPort = 0x378;
DWORD outputValue = value & 0xFF; // 0–255
SetPortVal(dataPort, outputValue, 1); // ghi 1 byte
2. Xuất qua Control Port
Lưu ý: Bit C2 (Pin 17) bị đảo logic. Giá trị ghi xuống cần xử lý để đảm bảo mức điện áp đúng trên connector.
WORD ctrlPort = 0x37A;
DWORD currentVal;
GetPortVal(ctrlPort, ¤tVal, 1);
// Giữ nguyên 4 bit cao, đặt 4 bit thấp theo giá trị mong muốn
unsigned int regVal = (unsigned int)currentVal;
regVal &= 0xF0; // xóa 4 bit thấp
regVal |= (outputValue & 0x0F) ^ 0x0B; // áp dụng giá trị và đảo bit cần thiết
SetPortVal(ctrlPort, regVal, 1);
3. Xuất 12-bit kết hợp Data + Control
WORD dataPort = 0x378;
WORD ctrlPort = 0x37A;
// Ghi 8 bit thấp vào Data Port
SetPortVal(dataPort, (value & 0xFF), 1);
// Ghi 4 bit cao vào Control Port
DWORD ctrlVal;
GetPortVal(ctrlPort, &ctrlVal, 1);
unsigned int reg = (unsigned int)ctrlVal;
reg &= 0xF0;
reg |= ((value >> 8) & 0x0F) ^ 0x0B;
SetPortVal(ctrlPort, reg, 1);
Nhập tín hiệu số từ cổng song song
1. Đọc từ Status Port
Chỉ 5 bit (S3–S7, tương ứng Pin 11,10,12,13,15) có thể dùng làm đầu vào. Bit S7 (Pin 11) không bị đảo, nhưng các bit còn lại có thể bị ảnh hưởng bởi pull-up.
WORD statusPort = 0x379;
DWORD rawVal;
GetPortVal(statusPort, &rawVal, 1);
unsigned int input = (unsigned int)rawVal;
input ^= 0x80; // đảo S7 để khớp mức thực tế
input &= 0xF8; // giữ S3–S7
input >>= 3; // căn phải thành 5 bit
2. Đọc từ Control Port
Để dùng Control Port làm đầu vào, cần đặt các chân ở mức cao (kích hoạt pull-up bên trong). Bit C2 (Pin 17) bị đảo.
WORD ctrlPort = 0x37A;
// Cấu hình Control Port làm đầu vào
DWORD config;
GetPortVal(ctrlPort, &config, 1);
unsigned int setup = (unsigned int)config;
setup &= 0xF0;
setup |= 0x04; // C2 = 1 → Pin 17 = LOW (do đảo), nhưng kích hoạt pull-up
SetPortVal(ctrlPort, setup, 1);
// Đọc giá trị
DWORD readVal;
GetPortVal(ctrlPort, &readVal, 1);
unsigned int input = (unsigned int)readVal;
input ^= 0x0B; // đảo C0, C1, C3 để khớp mức thực tế
input &= 0x0F; // giữ 4 bit thấp
3. Đọc 9-bit kết hợp Status + Control
Kết hợp 5 bit từ Status và 4 bit từ Control để tạo 9-bit đầu vào.
WORD statPort = 0x379;
WORD ctrlPort = 0x37A;
// Đọc Status Port
DWORD sVal;
GetPortVal(statPort, &sVal, 1);
unsigned int statusBits = (unsigned int)sVal;
statusBits ^= 0x80;
statusBits &= 0xF8;
statusBits <<= 1; // chuyển thành bit 8–4
// Cấu hình và đọc Control Port
DWORD cVal;
GetPortVal(ctrlPort, &cVal, 1);
unsigned int ctrlSetup = (unsigned int)cVal;
ctrlSetup &= 0xF0;
ctrlSetup |= 0x04;
SetPortVal(ctrlPort, ctrlSetup, 1);
GetPortVal(ctrlPort, &cVal, 1);
unsigned int controlBits = (unsigned int)cVal;
controlBits ^= 0x0B;
controlBits &= 0x0F; // bit 3–0
unsigned int combinedInput = statusBits | controlBits;
Lưu ý: Trong ứng dụng thực tế, dòng ra từ cổng song song rất yếu (~10mA), không đủ để điều khiển tải trực tiếp. Cần thêm mạch đệm (buffer/transistor) để tăng khả năng chịu tải và chống nhiễu.