Before you get started here, make sure you are familiar with the usage of the
IOxxx
functions.
This tutorial requires the use of the Application Manager modules (either Asynchronous or Synchronous, the I/O Event Manager module, and the RS-232 Utility module.
You'll need to create a sizeable buffer - whatever your biggest 'chunk' of
comms is going to be. Use a GLOBAL
variable to keep the memory
handle. NOTE: In the examples below there is a global variable called
commlen%
- it is used by the I/O routines to tell how much serial
data has been received. It is set to the size of the buffer just before each I/O
request and checked when each request is handled. More information is given
under Implement an I/O handler.
... GLOBAL commbuf%, commlen% ... commbuf% = ALLOC( 1024 ) ...
I would suggest using the IOxxx
functions for this. I like to
keep the LOPEN
and related functions for emergencies like debugging
logs, etc.
The $100
options flags work for me. I can send and receive
binary data just fine.
... IOOPEN( shand%, "TTY:A", $100 ) ...
After the serial port is open, use my rsset:
procedure. See the
source code for a full explanation of the parameters. Use the
term&
bitmap carefully. You can improve responsiveness by
asking for a large chunk of data (say 1024 bytes), but setting the terminator so
that as soon as a control-character is received (such as a carriage-return),
your comms request will complete and you can process the received data.
Character-by-character reception will be very slow.
An alternative would be to use the IOCANCEL
command. If you ask
for a large chunk of data, it could be a long time before that request
completes. The solution? Setup a timeout (say, one second) using the
"TIM:"
device and call IOCANCEL
when it
expires. The comms request will complete immediately and you will be able to
read whatever has been received so far. The IOCANCEL
command
does not discard any received data!
As an example, the
NMEA protocol
used by many GPS receivers
always terminates each message with a carriage return. The terminating bitmap
for just a carriage return is &00002000
. Using that terminator
means that your I/O handler will be called only when a complete message has been
received (assuming the comms buffer is big enough). Infrequent calls to your
I/O handler are more efficient and
OPL-friendly.
... rsset:( shand%, rsbaud%:( "19200" ), 0, 8, 1, 4, &00002000 ) ...
Now that the serial port is configured, a procedure to handle the I/O needs to be registered.
... sslot% = ioAdd%:( "hdlcomm", 127 ) :REM create a new I/O handler ioShand:( sslot%, shand% ) :REM let the I/O manager know about the I/O handle ...
sslot%
identifies a unique number for this I/O handler.
In this case, the
OPL procedure called
hdlcomm
will be called whenever each I/O request you make
completes. See Implement an I/O handler below.
Also, we only need to keep the I/O slot number as a global variable. It is
not necessary to keep the I/O device handle as a global as we can get to it via
the slot number: ioGhand%:( sslot% )
.
Your newly registered handler won't be doing anything unless you make a request!
... REM set the maximum amount of data that can be received at once commlen% = LENALLOC( commbuf% ) REM make the request IOC( ioGhand%:( sslot% ), 1, #ioGstat%:( sslot% ), #commbuf%, commlen% ) ...
When the IOC
completes, commlen%
will be set to the
amount of data actually received.
In summary, the initialisation procedure may look like:
PROC main: GLOBAL commbuf%, commlen%, sslot% ... REM rest of application setup, etc here ... ENDP REM Hey!!! There's error checking here!!! :-) PROC init%: LOCAL shand% REM allocate a buffer commbuf% = ALLOC( 1024 ) IF commbuf% = 0 RETURN -1 ENDIF REM open the serial port IF IOOPEN( shand%, "TTY:A", $100 ) < 0 RETURN -2 ENDIF REM configure the serial port rsset:( shand%, rsbaud%:( "19200" ), 0, 8, 1, 4, &00002000 ) REM register an I/O handler sslot% = ioAdd%:( "hdlcomm", 127 ) :REM create a new I/O handler IF sslot% = 0 IOCLOSE( shand% ) FREEALLOC commbuf% RETURN -3 ENDIF ioShand:( sslot%, shand% ) :REM let the I/O manager know about the I/O handle REM make a comms request commlen% = LENALLOC( commbuf% ) IOC( ioGhand%:( sslot% ), 1, #ioGstat%:( sslot% ), #commbuf%, commlen% ) REM done RETURN 0 ENDP
This is the procedure that will be called every time your I/O request completes. It returns an integer value (which happens to be ignored!) and accepts a single integer parameter - the I/O slot value you created before. The slot value is useful if you decide to have your procedure handle two different I/O requests - say serial comms and a timeout. The slot value can be used to distinguish the two.
IMPORTANT NOTE: It is vital that when you've handled the I/O, you must make a new I/O request, otherwise your handler will never be called again!
PROC hdlcomm%:( slot% ) LOCAL i% REM The comms data is now in the buffer (commbuf%). Do something with it... REM This will be slow, but it is *something*... i% = 0 WHILE i% < commlen% PRINT CHR$( PEEKB( UADD( commbuf%, i% ) ) ); i% = i% + 1 ENDWH REM make a new request - VERY IMPORTANT commlen% = LENALLOC( commbuf% ) IOC( ioGhand%:( slot% ), 1, #ioGstat%:( slot% ), #commbuf%, commlen% ) ENDP
When you're finished with the I/O stuff, be a good citizen and clean up after yourself...
... REM cancel the outstanding request IOCANCEL( ioGhand%:( sslot% ) ) IOWAITSTAT #ioGstat%:( sslot% ) REM close the serial port IOCLOSE( ioGhand%:( sslot% ) ) REM un-register the handler ioRem%:( sslot% ) REM free up the comms buffer FREEALLOC commbuf% ...