This tutorial teaches you to program your Windows PC to communicate with an external peripheral like data acquisition boards, USB GPIO Boards,Relayboards etc through its serial port (Virtual or Real).
Here i am assuming that you are using a Virtual COM port based any of the numerous IC's available on the market.
The tutorial is aimed at Software/Embedded developers who may want to interface real world sensors like DAQ's,USB Relays,USB GPIO boards ,Temperature sensors,Humidity sensors etc to Windows PC .
The software is written using C language and communicates with the Serial Port using Win32 API.
All codes have been tested on a 64bit Windows 10 System.
All the codes inthis tutorial are licensed under MIT License making it eligible to be used for developing proprietary software applications.
Isolated USB to Serial Converters
One major issue with interfacing real word devices with your PC's is that High voltage spurious transients can couple on to your USB or RS232 or RS485 lines.These high voltage transients can cause severe damage to your computer or its ports.
It is highly recommeded to use an
to protect your PC .
Sourcecodes
- All the C sourefiles used in this tutorial can be downloaded from our GitHub Page.
- Browse Win32 Serial Programming Repo on Github
- Download the Entire Repository as Zip Archive.
- Please note that the sourcecodes on the website show only the relevant sections to highlight the process of programming the serial port.
- Please use the complete sourcecodes from our github repo when building your own program.
Compilers and IDE's used
Since we are developing on Windows platform,
- Recommended IDE is Visual Studio (Professional or Community Edition).
You can also compile the code using GCC or MinGW after a few tweaks.
The Code uses some Microsoft specific safe C functions like
- strcat_s() and
- scanf_s()
which are used to get the COM number from the user and create the COM port address string (Eg "\\.\COM9" ).The code will compile on GCC after removing those functions.
Make sure that variables referenced by those functions are also changed.
Finding out your COM port Number
A little bit of History,
Serial ports are simple 9 pin legacy ports used by the PC to communicate with external peripherals like Printers,Data Acquisition Sytems,Printers etc.
The simple protocol and the 3 pin hardware physical lines (RX,TX,GND) made it quite popular among manufacturers who wanted an easy way to connect their devices with a computer.
In the 2020's, the old DB9 Serialport is found only on industrial motherboards and old PC's.The once ubiquitous serial port have been completely replaced by the familiar USB standard.
The arrival of cheap USB to UART chips like FTD FT232RL,TUSB3410,CP2102 have made it quite easy to upgrade the existing software that used to talk over serial to USB protocol.These chips provide a virtual serial port that behaves like a real one but transmits the data through USB.
If your PC does not have any hardware serial ports (RS232 DB9 ports), you can use USB to Serial Converter's like USB2SERIAL.(non Isolated).
In Windows ,
Serial ports are named as COM1,COM2 ,COM3.. etc .COM1 and COM2 usually refer to the hardware serial ports present in the PC (quite rare now)
while COM numbers in double digits like COM32,COM54,COM24.. etc are given to USB to Serial Converters or PCI serial port extenders.
To find out the COM number corresponding to your serial port,
- Open Device Manager by right clicking on My Computer icon and selecting Manage→ Device Manager.or
- by typing "Device Manager" on the taskbar searchbox in Windows 10
Under Ports(COM & LPT) you can see the parallel and serial ports (COM) detected by your system.
If your PC has any hardware ports, it will be shown either as COM1 or COM2 under the Ports Section.
I am using a FTDI based USB to Serial Converter (USB2SERIAL)which is recognized as COM24 (this may be different under your system).
If you double click on COM24,you can see the details of the corresponding port.
Windows10 Accounts
The codes/executables are able to open the connection to the serialport under both Administrator Account and Standard User Account in Windows 10.
Opening and Closing a Serial Port
Previous versions of this tutorial have used CreateFile() function to open the serial port (Note the missing A at the end). In Visual studio ver 19 , CreateFile() function is not working and will not be able to open the connection. It has to be replaced with CreateFileA() function which is used in this tutorial. |
In Windows
we use the CreateFileA() function to open a serial port.
CreateFileA() is a Win 32 function which is used to create or open a file, stream or an IO device like serial port.
On success CreateFileA() will return a handle which is then used to refer the connection in all subsequent operations.
After opening a serial port using the CreateFileA() function you should close it with CloseHandle() function, otherwise port will become unavailable to other programs.
Now let's write a small program to open and close a serial port on Windows.
Open a text editor like notepad and type the below code and save it as “serial.c”.If you are using IDE like VS Express, use the one integrated with it.
#include<windows.h> hComm = CreateFileA(“\\\\.\\COM24”, //port name if (hComm == INVALID_HANDLE_VALUE) CloseHandle(hComm);//Closing the Serial Port return 0; |
Find out the COM port corresponding to your system and substitute in CreateFileA() instead of COM24.
Now compile and run the program by pressing F5 in Visual Studio
or
by running the following command for gcc (MingW).Please make sure that gcc is added to you system path.
D:\> gcc -o serial serial.c
Now let me explain the code ,
windows.h header file contain all the definitions, function prototypes and constants required by the program.
In Windows everything is controlled by using handles.In the first line
HANDLEhComm;
we declare a handle hcommto access and control the serial port.
Next we open a connection to serial port using CreateFile() function. The CreateFile() function on success, returns a valid handle to the hComm variable.
CreateFileA() function takes 7 arguments,
- Name of the serial port to be opened here\\\\.\\COM24.
- Mode of access, here Read and Write
- Sharing options, Serial ports can't be shared so 0
- NULL for Serial ports, used for File operations
- Open the existing port, OPEN_EXISTING
- Overlapped IO or Non overlapped IO, here 0 means we are using NonOverlapped IO. Overlapped IO is used for multithreaded programs where several threads can interact with the port simultaneously.
- NULL for Serial port, used for file operations
If the function succeeds in opening the serial port, it returns a valid handle to hcomm which is then used for error checking.
After that the connection to the port is closed using
CloseHandle(hComm);
Please note that in Windows, COM port numbers from COM1 to COM9 are reserved by the system.
If you are using a serial port whose COM port number falls in that range, you don’t need the back slashes (\\\\.\\)shown in the above code.
You can access the serial port like this,
hComm = CreateFileA(“COM1”, // for COM1—COM9 only
GENERIC_READ | GENERIC_WRITE, //Read/Write
0, // No Sharing
NULL, // No Security
OPEN_EXISTING, // Open existing port only
0, // Non Overlapped I/O
NULL);
Configuring the DCB Structure
In Windows ,settings like Baud rate ,Number of start/Stop bits,data formats etc for the serial port are controlled by the DCB structure.
To Configure the DCB structure we use two functions,
GetCommState() function which retrieves the current control settings of the serial port and
SetCommState() function which configures the serial port with the new values in DCB structure provided by us.
First you declare a new structure of type DCB and initializes it.
DCB dcbSerialParams = { 0 }; // Initializing DCB structure
dcbSerialParams.DCBlength = sizeof(dcbSerialParams);
After that retrieve the current settings of the serial port using the GetCommState() function.
Status = GetCommState(hComm, &dcbSerialParams);
and set the values for Baud rate, Byte size, Number of start/Stop bits etc.
dcbSerialParams.BaudRate = CBR_9600; // Setting BaudRate = 9600
dcbSerialParams.ByteSize = 8; // Setting ByteSize = 8
dcbSerialParams.StopBits = ONESTOPBIT;// Setting StopBits = 1
dcbSerialParams.Parity = NOPARITY; // Setting Parity = None
if you want to change the Baud rate values prefix standard values with CBR like this CBR_4800,CBR_9600,CBR_192600etc.
Number of Stop bits can be changed to ONESTOPBIT or TWOSTOPBITS.
Parity can also be changed to EVENPARITY,ODDPARITY,NOPARITY etc.
Now its time to configure the serial port according to the DCB structure using SetCommState() function.
SetCommState(hComm, &dcbSerialParams);
Setting Timeouts
Timeouts helps to prevent your program from waiting endlessly till data arrives. It helps the read or write calls to return after a set time period has elapsed.
COMMTIMEOUTS timeouts = { 0 };
timeouts.ReadIntervalTimeout = 50; // in milliseconds
timeouts.ReadTotalTimeoutConstant = 50; // in milliseconds
timeouts.ReadTotalTimeoutMultiplier = 10; // in milliseconds
timeouts.WriteTotalTimeoutConstant = 50; // in milliseconds
timeouts.WriteTotalTimeoutMultiplier = 10; // in milliseconds
All values are in milliseconds.
ReadIntervalTimeout Specifies the maximum time interval between arrival of two bytes. If the arrival time exceeds these limits the ReadFile() function returns.
ReadTotalTimeoutConstant is used to calculate the total time-out period for read operations. For each read operation, this value is added to the product of the ReadTotalTimeoutMultiplier member and the requested number of bytes.
ReadTotalTimeoutMultiplier is used to calculate the total time-out period for read operations. For each read operation, this value is multiplied by the requested number of bytes to be read.
WriteTotalTimeoutConstantsimilar to ReadTotalTimeoutConstant but for write operation.
WriteTotalTimeoutMultiplier similar to ReadTotalTimeoutMultiplier but for write operation.
After this you have to set the values using SetCommTimeouts() function.
Writing Data to Serial Port
Writing data to the opened serial port is accomplished by the WriteFile() function. WriteFile() function can be used to write both into the files and I/O ports.
char lpBuffer[] = "A"; Status = WriteFile(hComm, // Handle to the Serial port |
hComm Handle of the serial port to write.
lpBuffer[] character array containing the data to write into the serial port.
dNoOFBytestoWrite is the total number of bytes to be written into the serial port.Here we are using the sizeof() operator to find out that .
dNoOFBytestoWrite = sizeof(lpBuffer);
dNoOfBytesWritten is the total number of bytes written successfully to the port by the WriteFile() operation.
If your PC does not have any hardware serial ports you can use any USB to Serial Converters(I am using USB2SERIAL).
Microcontroller Interface
I have interfaced a microcontroller MSP430G2553 to the serial port like this
You can use any microcontroller of your choice like 8051,AVR or ARM(LPC2148).
The Controller waits for a character to be received and lights up the corresponding LED.
The code for interfacing MSP430 is available in the Github repo.
.If you want to know how to configure the MSP430 controller UART you can check this tutorial.
We have also included code for
in the Repo too. The connection setup is similar to the MSP430.
Please note that if you are using a DB9 RS232 Serial Port of your PC, you will have to build a RS232 signal level converter at the microcontroller side to decode the RS232 signal.
Directly connecting the PC's RS232 Serial port to MSP430 's pins will damage the chip.
Here is the screen shot of the Program writing into serial port of Windows 7 Computer
Please note that i have changed the names of the C files in the latest release.
Use the files in the "PC_Transmits" folder.
Here is the screen shot of the Program writing into serial port of Windows 10Computer
Reading from the Serial Port
Reading from the serial port is accomplished by the ReadFile() function.
One way to do that is to use polling where the ReadFile() continuously reads from the serial port and checks for any received characters.
Other way is to setup an event and let windows notify us when a character is received.
We are going to use the second method here, following are the steps.
1. Create an Event for a particular action like character reception, change in modem lines etc using SetCommMask() function .
2. Ask windows to wait for the event set by SetCommMask() function using WaitCommEvent() and notify us when the condition happens.
3. Call ReadFile () to read the received data from the Serial port.
Functions used are
SetCommMask() is used to set the events to be monitored for a communication device. Here we are going to set the event as character received (EV_RXCHAR).The function takes two arguments, Handle of the serial port (hComm) and the code for the event (EV_RXCHAR) to be monitored.
Status = SetCommMask(hComm, EV_RXCHAR);
WaitCommEvent() is used to wait for the events set by SetCommMask() to happen, in this case reception of a character. The flow of execution of the program stops and the program waits until a character is received.
DWORD dwEventMask;
Status = WaitCommEvent(hComm, &dwEventMask, NULL);
dwEventMask contains a hex value indicating the event, which has caused WaitCommEvent() to return.
After WaitCommEvent() has returned, call ReadFile() function to read the received characters from the Serial Port Buffer.
char TempChar; //Temporary character used for reading do SerialBuffer[i] = TempChar;// Store Tempchar into buffer while (NoBytesRead > 0); |
ReadFile() function is similar to the WriteFile() function we had seen earlier,instead of writing we are reading from the serial port.
&TempChar Temporary variable used to store the byte read from serial port buffer.
sizeof(TempChar) used to calculate the number of bytes to read.
&NoBytesRead Bytes successfully read by the ReadFile().
Codes running on Windows 7 PC.
Please note that i have changed the names of the C files in the latest release.
Use the files in the "PC_Receives" folder.
Reset the Micro controller to transmit the string “Hello from MSP430”.
Codes running on Windows 10 PC.
Check out our next section,
If you want to know how to control the RTS and DTR pins of the serial port .