Serial Communication with Liberty Basic

Abstract

Attempting to communicate with a serial device, other than a modem,(such as an I/O board) using the standard LB2 commands gives at best poor or unreliable results. 
Far better results are obtained using LB3!
Under LB2, the most common problem being a delay between sending a command to the device and having the action performed ( such as toggling an output). This delay ranges from several hundred milliseconds to several seconds. An other and more equally serious problem is that reading the port smoetimes freezes the system.
It appears that these problems are caused by the interaction of LB2 and Windows. 
The solution to the problems is to call the communication functions in the USER DLL.


Communicating using the DLL with LB2
(after going through all of this I can garantee you will want to do it with LB3 )

To communicate using the available functions requires several steps

1- create an instance of the needed DCB structure
2- open the User.DLL
3- build the DCB
4- open the comm port
5- read or write to the port
6- close the port when done
7- close the DLL

For those of you still using LB2, it looks like this:
NB You must do things in this order!
   
*1- create an instance of the DCB structure  ( very long structure )

    struct myDCB,_
            ID as char[1],_
            BaudRate as ushort,_
            ByteSize as char[1],_
            Parity as   char[1],_
            StopBits as char[1],_
            RlsTimeOut as ushort,_
            CtsTimeOut as ushort,_
            DsrTimeOut as ushort,_
                    fBinary as      ushort,_
                    fRtsDisable as  ushort,_
                    fParity as      ushort,_
                    fOutxCtsFlow as ushort,_
                    fOutxDsrFlow as ushort,_
                    fDummy       as ushort,_
                    fDtrDisable  as ushort,_
            fOutX           as ushort,_
            fInX            as ushort,_
            fPeChar         as ushort,_
            fNull           as ushort,_
            fChEvt          as ushort,_
            fDtrflow        as ushort,_
            fRtsflow        as ushort,_
            fDummy2         as ushort,_
                XonChar  as char[1],_
                XoffChar as char[1],_
                XonLim   as char[1],_
                XoffLim  as char[1],_
                PeChar   as char[1],_
                EofChar  as char[1],_
                EvtChar  as char[1],_
                TxDelay  as ushort
          ' end of structure

   

'*2- open dll
    open "user" for dll as #user
    
'*3- call OpenComm                      'opens port
        com$ = "COM1"+chr$(0)

        CallDll #user,"OpenComm",_
                    com$ as ptr,_
                    1024 as ushort,_     'output buffer size
                    128 as ushort,_      'input buffersize
                    idComDev as ushort   'this id is used everytime the port is accessed
    
'*4- build DCB 
        buildCom$ = "COM1:9600,n,8,1"+chr$(0) 'required port settings

        CallDll #user,"BUILDCOMMDCB",_
                        buildCom$ as ptr,_
                        myDCB as struct,_    ' defined above
                        Bresult as ushort    ' if negative = error

    if Bresult < 0 then
        
        initport$="error" ' variable used to check for error
    else
        initport$ = "OK"
    end if
    myDCB.fNull.struct = 1                ' specifies that null char are to be discarded


'*5- set com state  must do this before using the port...
        CallDll #user,"SETCOMMSTATE",_
                        myDCB as struct,_
                        setResult as ushort  ' if negative = error
     if setResult < 0 then
       
        initport$ = "error"
     else
       initport$="OK"
    end if


'* >> serial port COM1 is now ready to communicate <<

 
After doing all of this, you still haven't really done anything with the port

here are the subroutines to write, read and flush the port

'*6a- Writting to the port is done this way :
 

'************************************
'* [write]                          *
'* sub #1 write to port             *
'* uses User.dll function WriteComm *
'* var                              *
'*    write.Length, write.String$   *
'*    returns write.result          *
'************************************
[write]
        write.Length = 0 ' reset this to 0

        write.String$ = "anything you want to send to the port"

        write.Length = len(write.String$) ' calc length

        CallDll #user,"WriteComm",_
            idComDev as ushort,_          ' obtained fromn opencomm
            write.String$ as ptr,_        ' string sent to port
            write.Length as ushort,_      ' number of bytes to send
            write.Result as ushort        ' number of bytes sent


        if write.Result <> write.Length then
           
            Notice "Error"+chr$(13)+"problems writting to port"
        end if

        write.String$ = ""                ' reset
    return


'6b -And Reading the port is done as follows:
'************************************
'* [read]                           *
'* sub #2 read port                 *
'* uses User.dll function readComm  *
'* var                              *
'* 	read.Buffer$		    *
'*	read.Bytes		    *
'*	read.Nb			    *
'*	read.Txt$		    *		     
'************************************
[read]

        read.Buffer$= space$(128) + chr$(0)  ' buffer for reading port
      	read.Bytes = 2 ' number of bytes to read 
      	

        read.Nb = 0                          ' number of bytes actually read
        read.Txt$ = ""                       ' result of reading port

    ' loop until there is something in the buffer i.e read.Nb > 0
        while read.Nb = 0
             CallDll #user,"readComm",_
                    idComDev as ushort,_    'obtained from opencomm
                    read.Buffer$ as ptr,_   ' read buffer
                    read.Bytes as ushort,_  ' number of bytes to read
                    read.Nb as ushort       ' number of bytes read

               ' for d = 1 to 1000
               ' next d
               ' small delay; may not be necessary
        wend
        read.Txt$ = Trim$(read.Buffer$)      ' remove blanks,Chr$(13)& Chr$(0)

   return

6c) flushing the port buffers
    Often used before reading the port 

'************************************
'* [flush]                          *
'* sub #3 flush port buffer         *
'* uses User.dll function readComm  *
'* var                              *
'*	flush.Q		     	    *	
'************************************

' flushes queues i.e serial port buffers
[flush]
            fnQueue = flush.Q                '0 = transmission
                                             '1 = receiving queue

            CallDll #user,"FlushComm",_
                    idComDev as ushort,_
                    fnQueue as ushort,_
                    flushResult as ushort   ' 0 if ok
             

        if flushResult <> 0 then
            Notice " Error" + chr$(13) + "problem flushing queue"+str$(flushQ)
         end if


Finally the port must be closed before exitibg the program

'7-Finally closing the port 

   CallDll #user,"CloseComm",_
            idComDev as ushort,_
            result as void
  
   close #user	

	

Now lets look at things under LB3!

Carl has done us a favor, by building into LB3 most of the work for the serial port.
As described in the new documentation, LB3 uses Windows communication API.

With LB3 there are only 4 steps needed to use the serial port!

Before using the port you may want to change the size of the buffers.
This must be done before opening the port.
Just set Com variable to the size you want i.e Com = 8096 ( 8k)

1- Open port

	Open "COMn:baud,parity,data,stop,CS,Ds,PE,RS" for random as #com

	For I/O boards I have found that the following usually works:

	Open "COM1:9600,N,8,1,CS0,DS0,RS"

	i.e baud rate 9600, No parity, data length 8 bits, 1 stop bit
	    both the CTS and DSR should be set to zero 
	      	the default is 1000 millisecond time out, this often causes the
	 	program to hang. With the io boards that I use, I have found 
		that only CS0 and DS0 work. 
	    and finally the request to send (RS) is disabled


2- Write to the port		
	
	nothing could simpler, just print to it !

	print #com,message$

3- Reading the port
	
	Only a few more lines of code are needed

	
    	while lof(#com) < NbofBytesToRead
		'do nothing
        wend
 	readport$ = input$(#com,lof(#com))	

4- Finally closing the port

	Close #com



In the lab that I work, I use IO boards from ONTRAK control systems
The boards are called ADRs. They have 8 relay outputs, 4 contact or TTL inputs
and an input that can count events.
The following is the bare bones program ( no gui )that I use to test the boards

To help me track whats going on with the boards and/or com errors, 
every step concastenates the LOG$.

'Progarm ADR

Log$ = ""

open "com1:9600,n,8,1,cs0,ds0,rs" for random as #com
	Log$ = "Opening com1"+chr$(13)
oncomerror [error]

ADR$="2" 'board address from 0 to 9 change as needed

print " all relays being set"
for R = 0 to 7
   f$= setRelay$(ADR$,R) : print R;f$
    for t = 1 to 2000: next t
next R


'test read
print " reading port with interrupt request"
 s= setIE(ADR$)
 result$=readADR$(ADR$)

    print "board : ";left$(result$,1)
    print "input : ";mid$(result$,2)


print " resetting all relays"
 for R = 0 to 7
    r$ = resetRelay$(ADR$,R)
    print R;r$
    for t = 1 to 2000 : next t
  next R




print " event counter test "
print " a) clearing counter"
    c = clearCounter(ADR$)

print" b) setting counter to 5"
    sc = setCounter(ADR$,5)
print" c) please trigger 5 times"
print "     waiting for counter interrupt... "
     dat$ = readCounter$(ADR$)
    print" >>   counter toggled   <<"
    print"      board nb "; left$(dat$,1)
    print"      input nb "; mid$(dat$,2,1)

goto [quit]

[error]

    print "comm problem";ComError$	
	Log$ = Log$+ComError$+chr$(13)
	
    goto [quit]



[quit]

' save Log$ incase of bugs or problems

	Open "LogBook" for output as #lg
		print #lg,Log$
	close #lg

print " all done "
close #com
End

' sets i.e closes relay
function setRelay$(board$,relay)

    message$=board$+"SK"+str$(relay)+chr$(13)
    print #com,message$
    
    setRelay$ = "true" 
	Log$ = Log$+message$+chr$(13)	
    
 
end function

'resets opens relay
function resetRelay$(board$,relay)
    message$=board$+"RK"+str$(relay)+chr$(13)
    print #com,message$
    
    resetRelay$ = "true"	
    Log$ = Log$+message$+chr$(13)
end function

'sets interrupt
function setIE(board$)
 it$ = board$+"IE"+chr$(13)
 print #com,it$
    setIE=1
	Log$ = Log$+"interrupt enabled"+chr$(13)
end function

'reads io board

function readADR$(board$)

    while lof(#com) < 3 ' the ADR always sends back 3 digits
        wend
 readADR$ = input$(#com,lof(#com))
 Log$ = Log$+"read board"+chr$(13)	
end function

'clears 16bit counter

function clearCounter(board$)

    ct$ = board$+"CE"+chr$(13)
    print #com,ct$
 clearCounter = 1
 Log$=Log$+"counter cleared"+chr$(13)
end function

'sets counter to specified value

function setCounter(board$,count)
    ct$ = board$+"TL"+str$(count)+chr$(13)
    print #com,ct$
  setCounter = count
  Log$ = Log$ +"Counter set to : "+str$(count)+chr$(13)
end function

'reads counter
function readCounter$(board$)

    while lof(#com) < 3
        wend
 readCounter$ = input$(#com,lof(#com))
 Log$ =Log$+"read counter"

end function

