Serial Port Communication on PC and Microcontroller


screenshot of SerialPorts.exe

Applications involving microcontrollers, actuators or sensors often require serial communication (RS232) with a PC. I have experimented with the Java, C/C++, and C# code below. The executable is a versatile terminal program for Windows ideal for testing.

Serial communication in Java (until Windows XP) * javax.comm.zip 35 kB
Serial communication in C/C++ (Windows) ** serial.zip 13 kB
Terminal for Windows (author unknown) SerialPorts.exe 180 kB
* not included in the standard Java SDK. Tested on Windows 2k. To write PC programs that implement serial port communication extract the archive, and ** library by Thierry Schneider, see also the documentation by Microsoft.
Erfahrung heißt gar nichts.
Man kann seine Sache auch 35 Jahre schlecht machen.
Kurt Tucholsky

Independent of the operating system, I require the following intermediate serial port handling strategy:

Table of contents:

Serial Port implementation in Visual C++ for Windows

Code serial.hpp adapted from Thierry Schneider. The following C++ code was compiled with Microsoft 32-bit C/C++ Compiler 15. The command line compiler instruction is

  cl /EHsc /nologo /I "D:\work\cpp\_utils" javacom.cpp /link ws2_32.lib

All source code and binary included in javacom.zip.

#define NDEBUG

#include <WinSock2.h>
#include <process.h>
#include <iostream>
#include "serial.hpp"

using namespace std;

int running=1;
DCB dcb;
HANDLE hnd;

int hexchar(char chr) {
  if ('0'<=chr && chr<='9')
    return chr-'0';
  if ('A'<=chr && chr<='F')
    return chr+10-'A';
  if ('a'<=chr && chr<='f')
    return chr+10-'a';
  running=0;
  return -1;
}

unsigned __stdcall serial_tx_thread(void* arg) {
  int count=0;
  char chr;
  while (running) {
    int msg=cin.get(); // converts 0x1a into EOF
    running=msg!=EOF;
    if (running)
      if (count) {
        chr+=hexchar(msg);
        int cnt=serial_send(hnd,&chr,1);
        running=cnt==1;
        count=0;
      } else {
        chr=hexchar(msg)<<4;
        ++count;
      }
  }
  return 0;
}

#define RX_BUFFER_SIZE 32

int main(int argc, char* argv[]) {
  if (2<argc) {
    serial_init(dcb,atoi(argv[2]),NOPARITY);
    hnd=serial_open(dcb,atoi(argv[1]));
    if (0<=(int)hnd) {
      void* arg=0;
      unsigned ret;
      _beginthreadex(0,0,serial_tx_thread,(void*) arg,0,&ret);      
      char msg[RX_BUFFER_SIZE];
      while (running) {
        int poll=serial_poll(hnd);
        if (0<poll) {
          poll=min(poll,RX_BUFFER_SIZE);
          int res=serial_read(hnd,msg,poll);          
          for (int c0=0;c0<res;++c0)
            printf("%02x",(unsigned char)msg[c0]);
          cout.flush();
        } else 
          Sleep(1L); // this is necessary otherwise cpu goes up        
      }
      serial_close(hnd);
    }
  }
  return 0;
}

In case you wish to control the serial port via TCP/IP, I recommend the C++ Socket Class for Windows by René Nyffenegger.

Serial Port implementation in C# for Windows

Ian Kamajaya ported the above C++ code to C#.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO.Ports;
using System.Threading;

namespace Javacom {
  class Program {
    static bool running = true;
    private static Thread serial_tx_thread;
    static System.IO.Ports.SerialPort UARTport = new System.IO.Ports.SerialPort();

    private static int hexnum(int num) {
      if ('0' <= num && num <= '9')
        return num - '0';
      if ('A' <= num && num <= 'F')
        return num + 10 - 'A';
      if ('a' <= num && num <= 'f')
        return num + 10 - 'a';
      running = false;
      return -1;
    }

    private static void SerialTxThread() {
      bool odd = false;
      byte[] by_tx = new byte[1];
      while (running) {
        int msg = System.Console.Read();
        if (msg == 0x1A)
          running = false;
        if (running) {
          if (odd) {
            if (msg > 32) {
              by_tx[0] += (byte)hexnum(msg);              
              UARTport.Write(by_tx, 0, 1);
              odd = false;
            }
          } else {
            if (msg > 32) {
              by_tx[0] = (byte)(hexnum(msg) << 4);
              odd = true;
            }
          }
        }
      }      
    }

    static void Main(string[] args) {
      if (1 < args.Length) {
        byte[] uart_received_buffer = new byte[2048];
        byte[] uart_send_buffer = new byte[2048];        
        int length;
        UARTport.PortName = "COM" + args[0];
        UARTport.BaudRate = Convert.ToInt32(args[1]);
        UARTport.Open();

        serial_tx_thread = new Thread(new ThreadStart(SerialTxThread));
        serial_tx_thread.IsBackground = true;
        serial_tx_thread.Start();

        while (running) {
          length = UARTport.BytesToRead;
          if (0 < length) {
            UARTport.Read(uart_received_buffer, 0, length);
            for (int i = 0; i < length; ++i)
              System.Console.Write(uart_received_buffer[i].ToString("x2"));            
          } else
            System.Threading.Thread.Sleep(1);
        }

        serial_tx_thread.Abort();
        UARTport.Close();
      }
    }
  }
}

Serial Port implementation in C for Linux

Under Linux serial communications works as a root used

sudo stty raw crtscts -echo ispeed 9600 ospeed 9600 -F /dev/ttyUSB0

compile and run for instance with

g++ javacom.cpp -o javacom -lpthread
./javacom /dev/ttyUSB0 9600

provides output stream listens to input stream minimal cpu thanks to threading resonsiveness can be adjusted by changing the sleep duration cpu

#include <iostream>
#include <stdlib.h>
#include <termios.h>
#include <fcntl.h>
#include <pthread.h>
#include <stdio.h>
#include <sys/io.h>
#include <unistd.h>

using namespace std;

int running = 1;
int tty_fid;

void* serial_tx_thread(void *ptr) {
  while (running) {
    int msg = cin.get();
    running = msg != -1; // assumption that EOF == -1 (see libio.h)
    if (running) {
      char chr = msg;
      ssize_t cnt = write(tty_fid, &chr, 1);
      running = cnt == 1;
    }
  }
  return 0;
}

#define RX_BUFFER_SIZE 32

// compile with
// g++ javacom.cpp -o javacom -lpthread
// example how to run
// ./javacom /dev/ttyUSB0 115200
int main(int argc, char** argv) {
  if (2 < argc) {
    tty_fid = open(argv[1], O_RDWR | O_NOCTTY | O_NDELAY);
    if (tty_fid == -1)
      return 1;
    char command[100];
    sprintf(command,"stty raw -crtscts -echo ispeed %s ospeed %s -F %s",argv[2],argv[2],argv[1]);
    system(command);
    fcntl(tty_fid, F_SETFL, O_NONBLOCK);
    pthread_t thread;
    void* arg = 0;
    int ret = pthread_create(&thread, NULL, serial_tx_thread, arg);
    // ---
    char msg[RX_BUFFER_SIZE];
    while (running) {
      ssize_t tty_read = read(tty_fid, msg, RX_BUFFER_SIZE);
      if (0 < tty_read) {
        for (int c0 = 0; c0 < tty_read; ++c0)
          cout << msg[c0];
        cout.flush();
      }
      usleep(2000); // in virtual machine this results in 2-3% cpu 
    }
    // pthread_join(thread, NULL);
    close(tty_fid);
  }
  return 0;
}

Serial Port implementation in C for C8051 microcontroller

In order to handle serial port communication in a non-blocking fashion on the c8051 microcontroller, we use the interrupt callback to send and receive messages. The source code below was tested on the chip model C8051F120.

// code adapted from CodeVision AVR by HP

#include <c8051F120.h>
#include "C8051_UART0.h"

// the buffer and buffer size are set in the open function and shall not
// be changed until closing the uart
char* UART0_rx;
short UART0_rx_size;
char* UART0_tx;
short UART0_tx_size;

// these are private variables managed by the uart
short UART0_rx_uart;
short UART0_rx_main;
short UART0_rx_available;

short UART0_tx_uart = 0;
short UART0_tx_main = 0;
short UART0_tx_available = 0;

char UART0_rx_errorCount = 0;
char UART0_tx_errorCount = 0;

// private function
void UART0_resetRx() {
  bit ES0_SAVE = ES0;
  ES0 = 0;
  UART0_rx_uart = 0;
  UART0_rx_main = 0;
  UART0_rx_available = 0;
  ES0 = ES0_SAVE;
}

void UART0_open(char* rx_buffer, short rx_size, char* tx_buffer, short tx_size, char p_uc_SSTA0) {
  char SFRPAGE_SAVE = SFRPAGE;
  char dummy;
  UART0_rx = rx_buffer;
  UART0_rx_size = rx_size;
  UART0_tx = tx_buffer;
  UART0_tx_size = tx_size;
  UART0_close();
  UART0_resetRx();
  
  SFRPAGE = UART0_PAGE;
  SCON0 = 0x50; // 0101 0000
  SSTA0 = p_uc_SSTA0;
  // Indicate TX0 ready
  TI0 = 0;
  RI0 = 0;
  ES0 = 1; // enable UART0 interrupt
  SFRPAGE = SFRPAGE_SAVE;
}

void UART0_close() {
  char SFRPAGE_SAVE = SFRPAGE;
  SFRPAGE = UART0_PAGE;
  ES0 = 0;
  TI0 = 0;
  RI0 = 0;
  UART0_rx_available = 0;
  UART0_tx_uart = 0;
  SFRPAGE = SFRPAGE_SAVE;
}

short UART0_getRxAvailable() {
  short value;
  bit ES0_SAVE = ES0;
  ES0 = 0;
  value = UART0_rx_available;
  ES0 = ES0_SAVE;
  return value;
}

// returns false if not sufficient data in buffer
char UART0_pollChars(void* ptr, short length) {
  char* message = ptr;
  short count;
  if (length <= UART0_getRxAvailable()) {
    for (count = 0; count < length; ++count)
      message[count] = UART0_rx[(UART0_rx_main + count) % UART0_rx_size];
    return 1;
  }
  return 0;
}

void UART0_advance(short length) {
  bit ES0_SAVE = ES0;
  ES0 = 0;
  UART0_rx_available -= length;
  UART0_rx_main += length;
  UART0_rx_main %= UART0_rx_size;
  ES0 = ES0_SAVE;
}

void UART0_putChars(void* ptr, short length) {
  short count;
  char* message = (char*) ptr;
  for (count = 0; count < length; ++count)
    UART0_putChar(message[count]);
}

void UART0_putChar(char value) {
  char SAVE_SFRPAGE;
  bit ES0_SAVE;
  while (UART0_tx_available == UART0_tx_size)
    ;
  ES0_SAVE = ES0;
  ES0 = 0;
  if (UART0_tx_available) {
    UART0_tx[UART0_tx_main] = value;
    ++UART0_tx_main;
    if (UART0_tx_main == UART0_tx_size)
      UART0_tx_main = 0;
  } else {
    SAVE_SFRPAGE = SFRPAGE;
    SFRPAGE = UART0_PAGE;
    SBUF0 = value;
    SFRPAGE = SAVE_SFRPAGE;
  }
  ++UART0_tx_available;
  ES0 = ES0_SAVE;
}

UART0_ISR(void) interrupt (4) {
  if (RI0 == 1) {
    RI0 = 0;
    // data will be loaded in SBUF0 if
    // RI0 == 0 && (SM20 == 1 => StopBit == 1)
    if (SSTA0 & 0xC0) { // 0xC0 = 1100 0000 frame error and receiver overflow
      ++UART0_rx_errorCount;
      SSTA0 &= 0x3F; // Clear FE0 & RXOV0 flags
    } else {
      UART0_rx[UART0_rx_uart] = SBUF0; // Read a character from UART0 Data Buffer
      ++UART0_rx_uart;
      if (UART0_rx_uart == UART0_rx_size)
        UART0_rx_uart = 0;
      ++UART0_rx_available;
      // we do not check if buffer overflow
    }
  }
  if (TI0 == 1) { // cause of interrupt: previous tx is finished
    TI0 = 0;
    if (SSTA0 & 0x20) { // tx collision error
      ++UART0_tx_errorCount;
      SSTA0 &= 0xDF;
    }
    --UART0_tx_available; // Decrease array size
    if (UART0_tx_available) { // If buffer not empty
      SBUF0 = UART0_tx[UART0_tx_uart]; // Transmit
      ++UART0_tx_uart; // Update counter
      if (UART0_tx_uart == UART0_tx_size)
        UART0_tx_uart = 0;
    }
  }
}

char UART0_getRxErrorCount() {
  char count;
  bit ES0_SAVE;
  ES0_SAVE = ES0;
  ES0 = 0;
  count = UART0_rx_errorCount;
  ES0 = ES0_SAVE;
  return count;
}

char UART0_getTxErrorCount() {
  char count;
  bit ES0_SAVE;
  ES0_SAVE = ES0;
  ES0 = 0;
  count = UART0_tx_errorCount;
  ES0 = ES0_SAVE;
  return count;
}
Engineers cheat in order to get results.
Mathematicians work on toy problems in order to get results.
Program verifiers cheat on toy problems in order to get results.
Anonymous