Tip: Event driven RS232 class (windows)

For issues with communication ports, protocols, etc.
D.J.Peters
Posts: 8586
Joined: May 28, 2005 3:28
Contact:

Tip: Event driven RS232 class (windows)

Post by D.J.Peters »

I wrote an event driven RS232 class for my Parallax Propeller chip
but you can use it for any other serial com port app. COM1 to COM16.
(works with an USB to RS232 too)

Last version from: Wed Dec 12, 2018

Joshy

file: RS232.bi

Code: Select all

#ifndef __CLASS_RS232__
#define __CLASS_RS232__

#include "windows.bi"

#define RS232_OPEN    0
#define RS232_CLOSE   1
#define RS232_DATA_TX 2
#define RS232_DATA_RX 3

#define OBJECT_IN_DATA   0
#define OBJECT_OUT_DATA  1
#define OBJECT_IN_DONE   2
#define OBJECT_OUT_DONE  3
#define OBJECT_CLOSE     4

type RS232
  enum 
   _NO_
   _ODD_
   _EVEN_
   _MARK_
   _SPACE_
  end enum
  enum
   _STOP_1_
   _STOP_1_5_
   _STOP_2_
  end enum

  private:
  as HANDLE  hDevice
  as HANDLE  hEvents(4)
  as any ptr ThreadID

  ' TX
  as integer IsTXActive
  as OVERLAPPED OverlappedRX
  as any ptr pOutBuffer
  as integer OutBufferBytes
  as integer OutBufferSize
  
  ' RX
  as integer ISRXActive
  as OVERLAPPED OverlappedTX
  as any ptr pInBuffer
  as integer InBufferBytes
  as integer InBufferSize
  as integer UseInBufferSize

  public:
  declare constructor
  declare destructor
  declare sub      Run
  declare function Open(byval ParamPort     as integer, _
                        byval ParamBaud     as uinteger = CBR_9600, _
                        byval ParamParity   as integer =  _NO_, _
                        byval ParamBits     as integer =    8, _
                        byval ParamStopBits as integer = _STOP_1_) as boolean
  declare function Close() as boolean
  declare function SetUseInBufferSize(byval Size as integer) as boolean
  declare function Send(byval pBuffer as any ptr,byval Size as integer) as boolean
  declare function GetInBytes() as integer
  declare function GetInBuffer() as any ptr
  declare function SetInBufferEmpty() as boolean
  declare function EnableDTR(byval state as boolean) as boolean
  declare function EnableRTS(byval state as boolean) as boolean
  EventError   as sub (ErrMsg as string)
  EventOpen    as sub ()
  EventClose   as sub ()
  EventSend    as sub (byval size    as integer)
  EventReceive as sub (byval pBuffer as ubyte ptr,byval size as integer)
end type

#endif '  __CLASS_RS232__
file: RS232.bas

Code: Select all

#include "RS232.bi"

private sub EventStart(byval param as any ptr)
  if param=NULL then return
  cptr(RS232 ptr,param)->Run()
end sub

constructor RS232
  hDevice        = cptr(HANDLE,INVALID_HANDLE_VALUE)
  InBufferSize   = 256
  OutBufferSize  = 256
  UseInBufferSize= 1
  pInBuffer      = callocate(InBufferSize)
  pOutBuffer     = callocate(OutBufferSize)
  hDevice        = cptr(HANDLE,INVALID_HANDLE_VALUE)
  hEvents(OBJECT_IN_DATA ) = CreateEvent(NULL, TRUE , FALSE, NULL)
  hEvents(OBJECT_OUT_DATA) = CreateEvent(NULL, TRUE , FALSE, NULL)
  hEvents(OBJECT_IN_DONE ) = CreateEvent(NULL, FALSE, FALSE, NULL)
  hEvents(OBJECT_OUT_DONE) = CreateEvent(NULL, FALSE, FALSE, NULL)
  hEvents(OBJECT_CLOSE   ) = CreateEvent(NULL, FALSE, FALSE, NULL)
end constructor


destructor RS232
  for i as integer = 0 to 4
    if (hEvents(i)<>INVALID_HANDLE_VALUE) then
      CloseHandle(hEvents(i))
    end if
  next
  if (hDevice<>INVALID_HANDLE_VALUE) then
    CloseHandle(hDevice)
  end if
  if pInBuffer <>0 then deallocate(pInBuffer)
  if pOutBuffer<>0 then deallocate(pOutBuffer)
end destructor

function RS232.Close() as boolean
  if (hDevice=INVALID_HANDLE_VALUE) then 
    if (EventError<>0) then EventError("Close() failed (hDevice=INVALID_HANDLE_VALUE) !")
    return false
  end if
  if hEvents(OBJECT_CLOSE)=INVALID_HANDLE_VALUE then 
    if (EventError<>0) then EventError("Close() failed hEvents(OBJECT_CLOSE)=INVALID_HANDLE_VALUE !")
    return false
  end if
  SetEvent(hEvents(OBJECT_CLOSE))
  return true
end function

function RS232.Open(byval ParamPort     as integer, _
                    byval ParamBaud     as uinteger, _
                    byval ParamParity   as integer, _
                    byval ParamBits     as integer, _
                    byval ParamStopBits as integer) as boolean

  if (hDevice<>INVALID_HANDLE_VALUE) then 
    CloseHandle(hDevice)
    hDevice=cptr(HANDLE,INVALID_HANDLE_VALUE)
  end if
  
  hDevice=CreateFile(!"\\\\.\\COM" & str(ParamPort), _
                     GENERIC_READ or GENERIC_WRITE, _
                     0, _
                     NULL, _
                     OPEN_EXISTING, _
                     FILE_FLAG_OVERLAPPED, _
                     NULL)
  if hDevice=INVALID_HANDLE_VALUE then
    if (EventError<>0) then EventError("CreateFile() failed !")
    return false
  end if

  ' configure the device
  dim as COMMPROP     Properties
  dim as COMMTIMEOUTS TimeOuts
  dim as DCB          DeviceConfig

  if     ParamBaud <    CBR_300 then
    ParamBaud=CBR_110
  elseif ParamBaud <    CBR_600 then
    ParamBaud=CBR_300
  elseif ParamBaud <   CBR_1200 then
    ParamBaud=CBR_600
  elseif ParamBaud <   CBR_2400 then
    ParamBaud=CBR_1200
  elseif ParamBaud <   CBR_4800 then
    ParamBaud=CBR_2400
  elseif ParamBaud <   CBR_9600 then
    ParamBaud=CBR_4800
  elseif ParamBaud <  CBR_14400 then
    ParamBaud=CBR_9600
  elseif ParamBaud <  CBR_19200 then
    ParamBaud=CBR_14400
  elseif ParamBaud <  CBR_38400 then
    ParamBaud=CBR_19200
  elseif ParamBaud <  CBR_57600 then
    ParamBaud=CBR_38400
  elseif ParamBaud < CBR_115200 then
    ParamBaud=CBR_57600
  elseif ParamBaud < CBR_128000 then
    ParamBaud=CBR_115200
  elseif ParamBaud < CBR_256000 then
    ParamBaud=CBR_128000
  elseif ParamBaud < 512000 then
    ParamBaud=CBR_256000
  else
    ' on real COM ports CBR_256000 is the max
    ' but on USB<->serial devices higher baudrates are possible
  end if

  ' get the default config from device manager at first
  DeviceConfig.DCBlength = SizeOf(DCB)
  if GetCommState(hDevice,@DeviceConfig)=0 then
    CloseHandle(hDevice)
    hDevice = cptr(HANDLE,INVALID_HANDLE_VALUE)
    if (EventError<>0) then EventError("GetCommState() failed !")
    return false
  end if

  ' setup in/out buffer size
  if SetupComm(hDevice,InBufferSize,OutBufferSize)=0 then 
    CloseHandle(hDevice)
    hDevice = cptr(HANDLE,INVALID_HANDLE_VALUE)
    if (EventError<>0) then EventError("SetupComm() failed !")
    return false
  end if

  ' first disable all HW events
  if SetCommMask(hDevice,0) =0 then
    CloseHandle(hDevice)
    hDevice = cptr(HANDLE,INVALID_HANDLE_VALUE)
    if (EventError<>0) then EventError("SetCommMask() failed !")
    return false
  end if

  ' setup time outs
  if SetCommTimeouts(hDevice,@TimeOuts)=0 then
    CloseHandle(hDevice)
    hDevice = cptr(HANDLE,INVALID_HANDLE_VALUE)
    if (EventError<>0) then EventError("SetCommTimeouts() failed !")
    return false
  end if

  ' setup our parameters
  with DeviceConfig
    .fBinary     = 1                 ' on Windows it must be binary
    .BaudRate    = ParamBaud         ' CBR_XXXX
    .Parity      = ParamParity       ' _NO_ , _ODD_ , _EVEN__
    .StopBits    = ParamStopBits     ' _STOP_1_, _STOP_1_5, _STOP_2_
    .ByteSize    = ParamBits         ' 5,6,7 and 8
    .fDtrControl = DTR_CONTROL_DISABLE
    .fRtsControl = RTS_CONTROL_DISABLE
  end with

  if SetCommState(hDevice,@DeviceConfig)=0 then
    CloseHandle(hDevice)
    hDevice = cptr(HANDLE,INVALID_HANDLE_VALUE)
    if (EventError<>0) then EventError("SetCommState() failed !")
    return false
  end if

  OverLappedRX.hEvent = hEvents(OBJECT_IN_DATA)
  OverLappedTX.hEvent = hEvents(OBJECT_OUT_DATA)
  ThreadID            = ThreadCreate(@EventStart,@this)
  return true
end function

function RS232.SetUseInBufferSize(byval Size as integer) as boolean
  if Size>0 then
    if Size>InBufferSize then
      UseInBufferSize=InBufferSize
    else
      UseInBufferSize=Size
    end if
    return true
  end if
  if (EventError<>0) then EventError("SetUseInBufferSize() size<0 !")
  return false
end function

function RS232.GetInBuffer() as any ptr
  return pInBuffer
end function

function RS232.GetInBytes() as integer
  return InBufferBytes
end function

function RS232.SetInBufferEmpty() as boolean
  if hDevice=INVALID_HANDLE_VALUE then 
    if (EventError<>0) then EventError("SetInBufferEmpty() failed hDevice=INVALID_HANDLE_VALUE !")
    return false
  end if
  InBufferBytes=0
  if hEvents(OBJECT_IN_DONE)=INVALID_HANDLE_VALUE then
    if (EventError<>0) then EventError("SetInBufferEmpty() failed hEvents(OBJECT_IN_DONE)=INVALID_HANDLE_VALUE !")
    return false
  end if
  SetEvent(hEvents(OBJECT_IN_DONE))
  return true
end function

function RS232.Send(byval pBuffer as any ptr,byval BufferSize as integer) as boolean
  if hDevice = INVALID_HANDLE_VALUE then
    if (EventError<>0) then EventError("Send() failed hDevice = INVALID_HANDLE_VALUE !") 
    return false
  end if
  if hEvents(OBJECT_OUT_DONE)=INVALID_HANDLE_VALUE then
    if (EventError<>0) then EventError("Send() failed hEvents(OBJECT_OUT_DONE)=INVALID_HANDLE_VALUE !") 
    return false
  end if
  if BufferSize<1 then
    if (EventError<>0) then EventError("Send() failed BufferSize<1 !") 
    return false
  end if
  if pBuffer =0 then
    if (EventError<>0) then EventError("Send() failed Buffer=NULL !") 
    return false
  end if

  if (IsTXActive = 0) then
    IsTXActive = 1
    dim as ubyte ptr pSrc=pBuffer
    dim as ubyte ptr pDes=pOutBuffer
    OutBufferBytes=BufferSize
    for i as integer=0 to OutBufferBytes-1
      pDes[i] = pSrc[i]
    next
    SetEvent(hEvents(OBJECT_OUT_DONE))
    return true
  end if
  return false
end function


function RS232.EnableDTR(byval state as boolean) as boolean
  if hDevice=INVALID_HANDLE_VALUE then 
    if (EventError<>0) then EventError("EnableDTR() failed hDevice=INVALID_HANDLE_VALUE !") 
    return false
  end if
  EscapeCommFunction(hDevice,iif(state,SETDTR,CLRDTR))
  return true
end function

function RS232.EnableRTS(byval state as boolean) as boolean
  if hDevice=INVALID_HANDLE_VALUE then
    if (EventError<>0) then EventError("EnableRTS() failed hDevice=INVALID_HANDLE_VALUE !") 
    return false
  end if
  EscapeCommFunction(hDevice,iif(state,SETRTS,CLRRTS))
  return true
end function

sub RS232.Run()
  if hDevice=INVALID_HANDLE_VALUE then return
  IsTXActive = 0
  IsRXActive = 0
  GetLastError()
  if (EventOpen<>0) then EventOpen()
  SetEvent(hEvents(OBJECT_IN_DONE))
  dim as integer nBytes
  dim as boolean blnRun=true
  while blnRun=true
    dim as integer result = WaitForMultipleObjects(5,@hEvents(0),FALSE,INFINITE) - WAIT_OBJECT_0
    if result<0 or result>4 then
      if (EventError<>0) then EventError("WaitForMultipleObjects() failed wrong result !") 
      blnRun=false
    else
      select case as const result
      case OBJECT_CLOSE
        blnRun=false
      case OBJECT_IN_DONE
        if (IsRXActive=0) then
          IsRXActive = 1
          if 0=ReadFile(hDevice,pInBuffer,UseInBufferSize,@nBytes,@OverlappedRX) then
            if (GetLastError() <> ERROR_IO_PENDING ) then
              if (EventError<>0) then EventError(" ReadFile() failed !") 
              blnRun=false
            end if 
          end if
        end if

      case OBJECT_IN_DATA
        if GetOverlappedResult(hDevice,@OverlappedRX,@nBytes, FALSE) then
          ResetEvent(hEvents(OBJECT_IN_DATA))
          IsRXActive = 0
          if (nBytes>0) andalso (EventReceive<>0) then
            InBufferBytes=nBytes
            EventReceive(pInBuffer,nBytes)
            nBytes=0
          end if
          SetEvent(hEvents(OBJECT_IN_DONE))
        elseif (GetLastError()<>ERROR_IO_PENDING) then
          if (EventError<>0) then EventError("GetOverlappedResult(RX) failed ! " & GetLastError()) 
          blnRun=false
        end if

      case OBJECT_OUT_DONE
        if WriteFile(hDevice,pOutBuffer,OutBufferBytes,@nBytes,@OverlappedTX) then
          OutBufferBytes-=nBytes
          nBytes=0
        elseif (GetLastError() <> ERROR_IO_PENDING ) then
          if (EventError<>0) then EventError("WriteFile() failed ! ") 
          blnRun=false
        end if

      case OBJECT_OUT_DATA
        if (GetOverlappedResult(hDevice,@OverlappedTX,@nBytes,FALSE)) then
          if (EventSend<>0) then EventSend(nBytes)
          nBytes=0
          ResetEvent(hEvents(OBJECT_OUT_DATA))
          IsTXActive = 0
        elseif (GetLastError() <> ERROR_IO_PENDING) then
          if (EventError<>0) then EventError("GetOverlappedResult(TX) failed ! ") 
          blnRun=false
        end if
      end select
    end if
  wend

  if (hDevice<>INVALID_HANDLE_VALUE) then
    CloseHandle(hDevice)
    hDevice = cptr(HANDLE,INVALID_HANDLE_VALUE)
    if (EventClose<>0) then Eventclose()
  end if

end sub
test01.bas

Code: Select all

' test01.bas
#include "RS232.bas"

sub OpenCB()
  print
  print "device open event"
  print
end sub

sub CloseCB()
  print
  print "device close event"
  print
end sub

sub ErrorCB(msg as string)
  print
  print "ups: an error " & msg
  print
end sub

sub SendCB(byval Size as integer)
  print
  print "device send event " & size & " bytes."
  print
end sub

sub ReceiveCB(byval pBuffer as ubyte ptr, byval size as integer)
  print "device receive event"
  print "rx: ";
  for i as integer=0 to size-1
    print chr(pBuffer[i]);
  next
  print
end sub



dim as RS232 Com

' optional set your callback's
Com.EventOpen    = @OpenCB
Com.EventClose   = @CloseCB
Com.EventError   = @ErrorCB
Com.EventSend    = @SendCB
Com.EventReceive = @ReceiveCB

' open COM1 with 2400 bps
if Com.Open(1,2400)=false then
  print "error: can't open RS232 !"
  beep : sleep : end 
end if

dim as string txt="hello RS232"
Com.Send(strptr(txt),len(txt))

while inkey=""
  
  sleep 1000
  
wend
Com.Close
Last edited by D.J.Peters on Dec 12, 2018 3:37, edited 11 times in total.
Sisophon2001
Posts: 1706
Joined: May 27, 2005 6:34
Location: Cambodia, Thailand, Lao, Ireland etc.
Contact:

Post by Sisophon2001 »

This I will keep.

Thanks

Garvan
vdecampo
Posts: 2992
Joined: Aug 07, 2007 23:20
Location: Maryland, USA
Contact:

Post by vdecampo »

Most Awesome!

Thanks DJ!

-Vince
D.J.Peters
Posts: 8586
Joined: May 28, 2005 3:28
Contact:

Post by D.J.Peters »

Looks like for Linux i must use Select() as replacement for the Win32 function WaitForMultipleObjects()

i'm right?

Joshy
technoweasel
Posts: 70
Joined: Aug 06, 2008 22:47

Post by technoweasel »

What are you using the prop for? I have a small robot from Parallax that I added all kinds of stuff to: basic stamp 2, propeller proto board, serial lcd, (broken) sonar, mouse encoder for tracking motion, extra battery, etc. I don't really do much with it, though. Anyway, do you have a robot, or are you doing something else?
D.J.Peters
Posts: 8586
Joined: May 28, 2005 3:28
Contact:

Post by D.J.Peters »

technoweasel wrote:What are you using the prop for?
Nothing special at the moment.
I like the prop assembler language.
Curently i read a book about the whole USB specs.
Maybe i try to build an USB controller with the chip.
Or the USB SIE part in software (serial interface engine)

Joshy
D.J.Peters
Posts: 8586
Joined: May 28, 2005 3:28
Contact:

Post by D.J.Peters »

hello Garvan and Vince
i changed a litle bit (see first post)
now you can define (optional) for any event it's own event proc.

can you try to build a clean libRS232.a ?

if i do it i run in to problems the class method RUN() must be started as a thread
but the EvenStart() sub outside the class will produce an double defined Link error.

Joshy
technoweasel
Posts: 70
Joined: Aug 06, 2008 22:47

Post by technoweasel »

I wish someone would make a Basic compiler for the Propeller. There's a guy writing a free C compiler, but I don't think he is getting very far. I might try it myself, someday. I really don't like the Spin language. It's like the mutant child of Pascal and Python.
D.J.Peters
Posts: 8586
Joined: May 28, 2005 3:28
Contact:

Post by D.J.Peters »

From my point of view the interpreted SPIN language is modern and cool for the prop with 8 cores
every core can akt as an SPIN object
(PS2, SERIAL, VIDEO, SOUND, USB or what ever)
and there is an free SPIN Compiler out

But i don't need SPIN, i learn the prop assembler language

Joshy

(learn it too and mabe you or we together can build an fast optimized Prop. BASIC Compiler in future)
midnightrambler905
Posts: 9
Joined: Aug 07, 2009 20:57
Location: Toronto GTA, ON, Canada
Contact:

your event driven RS232 program

Post by midnightrambler905 »

Hello D.J.

I have looked at your program and was wondering how I would/could modify it to pick up data froma barcode scanner. I am new to FB, so your probably thinking it is self explanatory, but I just cant seem to get the hang of your Com.Receive.

Specifically I would like to be able to scan 1 barcode (lengths vary - one barcode type is 6 characters, another is 18 or that might be 8 & 20 im still working on this too LOL), and save it to a file (i would be content with making it display on the screen first which is what i think your receive does at the moment).

If ya got the time I would appreciate the help.
dragonalong
Posts: 23
Joined: May 14, 2009 12:06
Location: San Antonio, Texas

your event driven RS232 program

Post by dragonalong »

midnightrambler905:

I suspect those folks are going to be more help than I will be on this. I don't have any serial hardware around to try it out on, but...

If it helps any, I'll try to offer my understanding of the basic process. The people who're up on this will correct me where I'm wrong. Once, in the deep mists of time, I was trying to capture data from a bar code scanner using VB (before they ruined it) and I had a devil of a time. Sometimes having an overview of how things work is useful.

Unless things have changed...

Serial devices are all treated as if they were the same kind of device, for compatibility. Modem, scanner, printer, whatever, they all work the same. The hardware handles movement of data from computer to peripheral and back. It used to be that the buffer was memory mapped. I'm guessing, from looking at that code, that is no longer true. But it still looks to me like it is up to the programmer to service that buffer. There is no explicit SEND/RECEIVE command. What you put in the output buffer goes out. You are expected to pick up whatever's in the input buffer when you are told it is there. You don't get there in time...oh well. Hence the need for BIG buffers and fast machines.

Like I said, I haven't done this in over a decade, but this lack of firmware handshaking is why data was organized into packets with discrete, known sizes, so we could do parity checking and the like. You need to know a little about how the input data is organized. How is the end-of-message marked? and so on.

Then, set up a loop to suck data out of that buffer as fast as you can, copy it somewhere else until the machine has time to look it over more carefully. (Over a decade, remember? :-) ) If memory serves, I had to put an "OK, ready to receive" message in the output buffer, then go watch the input buffer. Part of the difficulty is that you (meaning the computer) can't anticipate when that data will appear, since it's a human-interface device. I was having trouble with the device timing out, as I recall.

I'm sorry I'm not more help with this. I know D.J. and Vince can get you rolling with the code, and fix up my errors.

Take care, Dave
phishguy
Posts: 1201
Joined: May 05, 2006 16:12
Location: West Richland, Wa

Post by phishguy »

@midnightrambler905

Although D.J.'s code looks cool, I don't see any advantage to using it for the purpose that you intend. You should just be able to use "Open Com" and Input# or input$ commands to get your data. Check out some of the data logging routines by BasicScience or EZterm wiritten by me. They should give you a good idea of how to receive data. If you let me know the data format of the barcode data, I could probably whip up an example for you.

http://www.freebasic.net/forum/viewtopi ... 16&start=0

http://www.freebasic.net/forum/viewtopi ... ght=ezterm
midnightrambler905
Posts: 9
Joined: Aug 07, 2009 20:57
Location: Toronto GTA, ON, Canada
Contact:

thanks phishguy... here is my need in a nutshell

Post by midnightrambler905 »

While I fully intend to go over your examples in detail (ezterm looks like it might work for being able to test my barcode scanner) I will give you the gist of what I am specifically looking to build.

I have to add barcode scanning into a system written in Clipper - which does not support serial input. I do not have the kind of time required to re-write the entire system in freeBasic, but it is being added to my list of things to be done and looked into (but my skills are woefully lacking since i only found freeBasic this week LOL). So what i was thinking was a simple little module which can read barcodes and write them out to a flat ASCII file which Clipper can work with. Originally i was thinking
1) open com port & flat file
2) accept scanned code
3) write out scanned code to flat file
4) close everything and exit
Then I could let the clipper application take care of the rest.

However, I may want to make it scan all codes into a single file, but that should be a VERY minor change to the freebasic program. Was thinking the KISS method to start with, but will all be sunject to execution of database side of application.
phishguy
Posts: 1201
Joined: May 05, 2006 16:12
Location: West Richland, Wa

Post by phishguy »

My EZTerm program also has the ability to capture text to a file. It should be fairly simple to modify for your use. A lot of the functionality of the program that you don't need could be stripped out. What is the format of the serial data from the barcode reader? Is it just simple text followed by a carriage return? From reading your posts, it appears that your application is written for DOS. That is somewhat unfortunate. If it was a Windows application, I have written programs that work like a wedge. The serial data is converted into keystrokes and sent to the current active window.
midnightrambler905
Posts: 9
Joined: Aug 07, 2009 20:57
Location: Toronto GTA, ON, Canada
Contact:

ezterm

Post by midnightrambler905 »

I have been looking at the EZTERM program was thinking I could modify it.
Yes, the data is simple ASCII text using a CR (I beleive, but testing will bear that out shortly), and yes this old app is DOS based but still works like a charm on an XP platform. Like I said earlier, I will be attempting to rebuild this app in freeBasic. There are a number of functions that I would like to add to it, and if understand the basics of the API, I should be able to take most advanced functions and incorporate them (ie. automating an email response, print previews, etc.) but it will be a slow arduous task since my skills are so far behind LOL.

I am going to try modifying the EZTERM program today or tomorrow, will keep you advised.
Post Reply