Keywords: Serial Port Communication | MinGW | Windows API | C++ Programming | RS-232
Abstract: This article provides a comprehensive guide for migrating serial port communication implementations from legacy 16-bit Turbo C++ to modern 32-bit MinGW compilers in C++. It addresses the absence of bios.h header in MinGW and introduces Windows API as the core alternative solution. The content covers complete initialization workflows including port opening, parameter configuration, timeout settings, and data read/write operations, with detailed code examples. Cross-platform permission management differences are also analyzed, offering practical insights for developers transitioning between development environments.
Technical Evolution of Serial Port Communication
In traditional 16-bit C++ development environments, Turbo C++ IDE provided comprehensive serial port communication support through the bios.h header file. This header encapsulated BIOS interrupt calls, enabling developers to easily access RS-232 serial ports. However, with the evolution to 32-bit and 64-bit architectures, modern compilers like MinGW no longer include bios.h, primarily because direct BIOS interrupt calls are no longer applicable in protected mode modern operating systems.
Windows API as Core Solution
In 32-bit MinGW environments, implementing serial port communication requires reliance on the API functions provided by the Windows operating system. These APIs operate serial port devices through file I/O abstraction, treating serial ports as special files.
Complete Serial Port Initialization Process
The initialization process for serial port communication involves multiple critical steps, each requiring precise configuration to ensure communication reliability.
Port Opening and Handle Acquisition
Use the CreateFile function to open the serial port device, which returns a device handle. All subsequent operations are based on this handle. The device name format is \\.\COMx, where x represents the port number.
#include <windows.h>
HANDLE serialHandle = CreateFile("\\\\.\\COM1",
GENERIC_READ | GENERIC_WRITE,
0, NULL, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL, NULL);
if (serialHandle == INVALID_HANDLE_VALUE) {
// Error handling code
}
Communication Parameter Configuration
Configure basic communication parameters through the DCB (Device Control Block) structure. These parameters include baud rate, data bits, stop bits, and parity, which must exactly match the settings of the connected device.
DCB serialParams = {0};
serialParams.DCBlength = sizeof(serialParams);
if (!GetCommState(serialHandle, &serialParams)) {
// Error handling
}
serialParams.BaudRate = CBR_9600; // 9600 baud rate
serialParams.ByteSize = 8; // 8 data bits
serialParams.StopBits = ONESTOPBIT; // 1 stop bit
serialParams.Parity = NOPARITY; // No parity
if (!SetCommState(serialHandle, &serialParams)) {
// Error handling
}
Timeout Setting Strategy
Reasonable timeout settings are crucial to prevent I/O operations from blocking indefinitely. The COMMTIMEOUTS structure allows developers to finely control the timeout behavior of read and write operations.
COMMTIMEOUTS timeouts = {0};
timeouts.ReadIntervalTimeout = 50; // Inter-character timeout
timeouts.ReadTotalTimeoutConstant = 50; // Fixed timeout
timeouts.ReadTotalTimeoutMultiplier = 10; // Timeout multiplier per byte
timeouts.WriteTotalTimeoutConstant = 50;
timeouts.WriteTotalTimeoutMultiplier = 10;
SetCommTimeouts(serialHandle, &timeouts);
Data Read/Write Operation Implementation
After configuration, standard file I/O functions can be used for data exchange. The ReadFile and WriteFile functions provide both synchronous and asynchronous operation modes.
// Data writing example
char writeBuffer[] = "Hello Serial Port";
DWORD bytesWritten;
if (!WriteFile(serialHandle, writeBuffer, strlen(writeBuffer),
&bytesWritten, NULL)) {
// Error handling
}
// Data reading example
char readBuffer[256];
DWORD bytesRead;
if (ReadFile(serialHandle, readBuffer, sizeof(readBuffer)-1,
&bytesRead, NULL)) {
readBuffer[bytesRead] = '\0'; // Add string terminator
// Process received data
}
Cross-Platform Permission Management Comparison
In Linux environments, access permission management for serial port device files is an important consideration. Experience from reference articles indicates that proper user group configuration is crucial for serial port access. Users need to be added to groups such as dialout, tty, uucp, and plugdev to gain serial port access permissions. This permission management model contrasts sharply with Windows system's handle-based access control.
Resource Release and Error Handling
After serial port communication is completed, system resources must be properly released. Use the CloseHandle function to close the serial port handle and prevent resource leaks.
if (serialHandle != INVALID_HANDLE_VALUE) {
CloseHandle(serialHandle);
serialHandle = INVALID_HANDLE_VALUE;
}
Modern Development Best Practices
In modern C++ development, it's recommended to adopt object-oriented design patterns to encapsulate serial port operations. Create a SerialPort class that completes initialization in the constructor and automatically releases resources in the destructor, utilizing the RAII (Resource Acquisition Is Initialization) principle to ensure proper resource management. Simultaneously, implement comprehensive exception handling mechanisms, fully leveraging the good support for C++ exceptions in 32-bit compilers.
Through the methods introduced in this article, developers can smoothly migrate from traditional 16-bit serial port programming to modern 32-bit environments, while gaining better code maintainability and cross-platform compatibility. The serial port communication interface provided by Windows API, although different from the traditional bios.h approach, is more powerful and flexible, capable of meeting the needs of modern applications.