2015年5月8日 星期五

Simple Windows Serial Port Programming in C


    Similar with the linux version , this article would demonstrate how to write a simple windows .

    I divide the 2 operations : write and read into 2 threads. Of course, one could call ReadFile and WriteFile (those are mapping to read/wrrite function in linux) at the same thread.
    Be prudent, if you would like to create more than 2 threads, do not be annoyed with locking/unlocking the threads.


#include <windows.h>
#include <stdio.h>

#define MAX_STR_LEN      (512)
#define UART_TRANS_BUFFER    (128)

#define MILLI_SEC      (1)

BOOL isLeaveThread;

CRITICAL_SECTION UARTThreadCriticalSection;


TCHAR *GetLastErrorMessage(DWORD lastError)
{
 static TCHAR errmsg[MAX_STR_LEN];

 if (0 == FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 0, lastError,
    MAKELANGID(LANG_ENGLISH, SUBLANG_DEFAULT) /*american empire language*/,
    errmsg,  MAX_STR_LEN - 1, NULL))
 {
  /* if we fail, call ourself to find out why and return that error */
  return (GetLastErrorMessage(GetLastError()));  
 }

 return errmsg;
}/*GetLastErrorMessage*/


DWORD WINAPI ReadThread(LPVOID  lpParameter)
{
 HANDLE hSerial; 
 char readBuffer[UART_TRANS_BUFFER];


 hSerial = *((HANDLE*)lpParameter);

 do
 { 
  BOOL isSuc;
  DWORD bytesRead;
  unsigned int i;

  ZeroMemory(&readBuffer[0], UART_TRANS_BUFFER);

  EnterCriticalSection(&UARTThreadCriticalSection);
  isSuc = ReadFile(hSerial, &readBuffer[0], UART_TRANS_BUFFER, &bytesRead, NULL);

  if(FALSE == isSuc)
  {
   printf("ERROR : %s : %s\n", __FUNCTION__, 
   GetLastErrorMessage( GetLastError()) );
  }/*if*/


  Sleep(5);

  if(0 == bytesRead)
  {
   printf("  No Ack!\n");
   continue;
  }

  for(i = 0; i< bytesRead; i++)
   printf("%c(%d %#x)\t", readBuffer[i], readBuffer[i], readBuffer[i]);

  LeaveCriticalSection(&UARTThreadCriticalSection);
 }while(FALSE == isLeaveThread);

 printf("leaving %s\n", __FUNCTION__);

 ExitThread((DWORD)0);
}/*ConfirmThread*/


DWORD WINAPI WriteThread(LPVOID  lpParameter)
{
 HANDLE hSerial;
 char sendBuffer[UART_TRANS_BUFFER];

 DWORD bytesWritten; 
 BOOL isSuc;


 hSerial = *((HANDLE*)lpParameter);

 sprintf_s(&sendBuffer[0], UART_TRANS_BUFFER, "hello!");

 do
 {

  EnterCriticalSection(&UARTThreadCriticalSection);

  isSuc = WriteFile(hSerial, &sendBuffer[0], strlen(&sendBuffer[0]), &bytesWritten, NULL);  

  if(FALSE == isSuc)
  {
   printf("ERROR : %s : %s\n", __FUNCTION__, 
   GetLastErrorMessage( GetLastError()) );
  }/*if*/

  Sleep(5); 
  LeaveCriticalSection(&UARTThreadCriticalSection);
 }while(FALSE == isLeaveThread);

 printf("leaving %s\n", __FUNCTION__);

 ExitThread((DWORD)0);
}/*CommandThread*/


/*
  for timeoutInterval:
    0 : return immediately 
 a value in range of (0 25.5] : timeout mode, the value be in unit of milli-sec
 otherwise : blockmode
*/

BOOL SetSerialProperties( HANDLE hSerial, DWORD baudRate, BYTE stopBits, 
 BYTE parity, int timeoutInterval)
{
 DCB dcbSerialParams;
 COMMTIMEOUTS timeouts;

 ZeroMemory(&dcbSerialParams, sizeof(DCB));
 ZeroMemory(&timeouts, sizeof(COMMTIMEOUTS));


 if(FALSE == GetCommState(hSerial, &dcbSerialParams))
 {
  fprintf(stderr, "ERROR : GetCommState , ");
  return FALSE;
 }/*if FALSE == isSuc*/



 dcbSerialParams.BaudRate = baudRate;
 dcbSerialParams.ByteSize = 8;
 dcbSerialParams.StopBits = stopBits;
 dcbSerialParams.Parity = parity;

 if(FALSE == SetCommState(hSerial, &dcbSerialParams))
 {
  fprintf(stderr, "Error : setting device parameters , ");
  return FALSE;
 }/*if */


 if(0 == timeoutInterval)
 {
  //return immediately 
  timeouts.ReadIntervalTimeout = MAXDWORD;
  timeouts.ReadTotalTimeoutMultiplier = 0; 
  timeouts.ReadTotalTimeoutConstant = 0; 
 }
 else if( 0 > timeoutInterval || timeoutInterval > MAXBYTE*100*MILLI_SEC ) /*compatible with linux*/
 {
  //block mode 
  timeouts.ReadIntervalTimeout = 0;
  timeouts.ReadTotalTimeoutMultiplier = 0 ; /*timeout for per read*/
  timeouts.ReadTotalTimeoutConstant = 0 ; 
 }
 else
 {
  // timeout mode
  timeouts.ReadIntervalTimeout = 1; /* time per byte,  milli-sec*/
  timeouts.ReadTotalTimeoutMultiplier = 1; /*timeout for per read*/
  timeouts.ReadTotalTimeoutConstant = (DWORD)timeoutInterval; /*ertra time for read timeout*/
 }/*if timeoutInterval*/

 /*WriteTotalTimeoutConstant = WriteTotalTimeoutMultiplier = 0 : write operation is without timeout*/

 timeouts.WriteTotalTimeoutConstant = 0; /*timeout for per write*/
 timeouts.WriteTotalTimeoutMultiplier = 0; /*extra time for write timeout*/

 if(FALSE == SetCommTimeouts(hSerial, &timeouts)) 
 {
  fprintf(stderr, "error : setting port state , ");
  return FALSE;
 }/*if */

 return TRUE;
}/*SetSerialProperties*/


BOOL CtrlHandler( DWORD fdwCtrlType ) 
{
 switch( fdwCtrlType ) 
 { 
  // Handle the CTRL-C signal. 
 case CTRL_C_EVENT: 
  printf( "Ctrl-C event\n\n" );
  Beep( 750, 300);
  isLeaveThread = TRUE;
  return( TRUE );
#if(0)
  // CTRL-CLOSE: confirm that the user wants to exit. 
 case CTRL_CLOSE_EVENT: 
  Beep( 600, 200); 
  printf( "Ctrl-Close event\n\n" );
  return( TRUE ); 

  // Pass other signals to the next handler. 
 case CTRL_BREAK_EVENT: 
  Beep( 900, 200); 
  printf( "Ctrl-Break event\n\n" );
  return FALSE; 

 case CTRL_LOGOFF_EVENT: 
  Beep( 1000, 200); 
  printf( "Ctrl-Logoff event\n\n" );
  return FALSE; 

 case CTRL_SHUTDOWN_EVENT: 
  Beep( 750, 500); 
  printf( "Ctrl-Shutdown event\n\n" );
  return FALSE; 
#endif
 default: 
  break;
 }/*switch*/ 

 return FALSE; 
}/*CtrlHandler*/


int main(int argc, char *argv[])
{

 HANDLE hSerial; 
 BOOL  isSuc;

 char comPortName[MAX_STR_LEN];

 HANDLE hWriteThread, hReadThread;

 isSuc = FALSE;

 sprintf_s(&comPortName[0], MAX_STR_LEN, "\\\\.\\COM14");


 isLeaveThread = FALSE;

 if( FALSE == SetConsoleCtrlHandler( (PHANDLER_ROUTINE) CtrlHandler, TRUE ) ) 
 { 
  printf("ERROR : SetConsoleCtrlHandler , ");
  goto Flag_CloseSerial;
 }/*if */



 hSerial = CreateFile(&comPortName[0], GENERIC_READ|GENERIC_WRITE, 0, NULL,
  OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL );

 if (hSerial == INVALID_HANDLE_VALUE) 
 {
  printf("ERROR : open %s , ", &comPortName[0]);
  goto Flag_CloseSerial;
 }/*if */

 /*set timeout mode, the time limits of 2000 milli-sec*/
 if(FALSE == SetSerialProperties(hSerial, CBR_57600, ONESTOPBIT, NOPARITY, 2000*MILLI_SEC))
  goto Flag_CloseSerial;


 InitializeCriticalSection(&UARTThreadCriticalSection);

 hWriteThread = (HANDLE)_beginthreadex(NULL, 0, WriteThread, 
  &hSerial,  0/*or CREATE_SUSPENDED*/, NULL);

 if (NULL == hWriteThread) 
 {
  fprintf(stderr, "error : create confirming thread : ");
  goto Flag_CloseSerial;
 }/*if NULL == hConfirmThread*/

 hReadThread = (HANDLE)_beginthreadex(NULL, 0, ReadThread, 
  &hSerial,  0/*or CREATE_SUSPENDED*/, NULL);

 if (NULL == hReadThread) 
 {
  fprintf(stderr, "error : create command thread : ");
  goto Flag_CloseSerial;
 }/*if NULL == hConfirmThread*/


 if(FALSE == SetThreadPriority(hWriteThread, THREAD_PRIORITY_LOWEST))
 {
  fprintf(stderr, "error :SetThreadPriority : ");
  goto Flag_CloseSerial;
 }/*if*/

 WaitForSingleObject(hWriteThread, INFINITE); 
 WaitForSingleObject(hReadThread, INFINITE); 


 DeleteCriticalSection(&UARTThreadCriticalSection); 
 isSuc = TRUE;

 Flag_CloseSerial:
 if(FALSE == isSuc) 
 printf("%s\n", GetLastErrorMessage( GetLastError() ));


 isSuc = CloseHandle(hSerial);

 if(FALSE == isSuc) 
 {
  printf("ERROR : close %s , %s\n", &comPortName[0], 
  GetLastErrorMessage( GetLastError()) );
 }/*if FALSE == isSuc*/

 return 0;
}/*main*/


    In this example, I assume target com-port  be COM14. You should modify it as your actual com-port number.

    Note that the com-port string for CreateFile argument should be form of "\\.\COMX" (where the X is a natural number). If the one does the  use simple formal "COMX" and X > 9 for CreateFile , the function would return NULL handle(INVALID_HANDLE_VALUE). More detail discussion could be refer to the thread on stackoverflow.


    Most functions are intuitive ( if the person familiar with linux serial port programming ). The confusing functions is only one : SetCommTimeouts with structure COMMTIMEOUTS,  for setting blocking or timeout mode. I have organize those in function SetSerialProperties,


沒有留言:

張貼留言