Accessing the Serial Port from C

by Richard Lancaster

I get the feeling that people hate the serial port and don't want to touch it with a barge pole. Acorn didn't make it any more friendly by spreading the relevant information over three chapters of the PRM, nor did we get a C library to ease the pain. What follows is my own concise guide to accessing the serial port via kernel.h.

Introduction

There are several things that must be done before you can actually use the serial port:

You can then either read or write to the serial input and output buffers (the serial port is buffered). Finally you can flush the buffers if necessary.

Enabling the serial input buffer

The serial input buffer is not by default turned on. It therefore will not accept data from the outside world, and so you will not be able to read in serial data. The buffer can be set to one of three states :

Serial input buffer states
0 Off (No data ever read from the serial cable into the buffer; computer functions as normal) [This is the default].
1 Keyboard input from serial port (The keyboard is disabled and keyboard input is taken from the serial port. Warning, if you don't have a serial keyboard or another computer connected then this will effectively lock you out of your computer until you perform a hard reset!).
2 Background (Data read into the serial input buffer and can be accessed by a program when necessary, computer functions as normal).

The state can be set using the following piece of code (where r is of type _kernel_swi_regs):

  r.r[0] = 2;
  r.r[1] = level;
  _kernel_swi(OS_Byte, &r, &r);

Where level is 0, 1 or 2 as above. If you want normal serial port operation then set it to 2. I have left out any error checking etc. for reasons of space.

Setting the baud rates

The transmission baud rate can be set with the following piece of code :

  r.r[0] = 6;
  r.r[1] = rate;
  _kernel_swi(OS_SerialOp, &r, &r);

Where rate is the baud rate code, which can be found from this table:

Unknown - FixMe
Code Baud Code Baud Code Baud Code Baud
0 9600 4 1200 8 19200 12 600
1 75 5 2400 9 50 13 1800
2 150 6 4800 10 110 14 3600
3 300 7 9600 11 134.5 15 7200

The reception rate can be set in the same way but with R0 set to 5 instead of 6.

I haven't got access to the rate codes for the faster baud rates on the RISC PC so perhaps someone would like to fill me in.

Setting the data format

The following piece of code allows you to set how many data bits you want, how many stop bits you want and whether you want parity etc. :

  r.r[0] = 1;
  r.r[1] = format;
  _kernel_swi(OS_SerialOp, &r, &r);

Where format is a 32 bit word that describes the data format you want to use. Bits 0 and 1 describe the number of data bits, bit 2 specifies the number of stop bits, bit 3 specifies whether you want parity bits added, bits 4 and 5 set the type of parity and, finally bits 6 to 31 must be set to 0, presumably to allow for future expansion. This table sets out the meaning of different bit values:

Unknown - FixMe
5 4 Parity type 3 Parity bits 2 Stop bits 1 0 Data bits
0 0 Odd parity 0 No 0 1 0 0 8
0 1 Even parity 1 Yes 1 21 0 1 7
1 0 Transmit 1; ignore received 1 0 6
1 1 Transmit 0; ignore received 1 1 5

Some example settings would be : format = 0x0; would give 8 data bits, 1 stop bit and no parity, format = 0xb; would give 5 data bits, 1 stop bit and odd parity.

Setting the handshaking type

The handshaking type can be set with the following code :

  r.r[0] = 0;
  r.r[1] = XORMask;
  r.r[2] = ANDMask;
  _kernel_swi(OS_SerialOp, &r, &r);

This call sets a register that specifies the handshaking type. The call allows you to alter only the bits of the register you want to while leaving the others intact. You should therefore ensure that you don't alter any of the bits that you don't explicitly intend to. The SWI decides whether you want a bit set, unset or left alone by looking as the corresponding bits in the AND and XOR masks. The following table tells you what will happen to a bit depending on the state of that bit in the masks:

Unknown - FixMe
XORMask bit ANDmask bit Effect on register bit
0 0 Bit becomes 0
0 1 Bit is unchanged
1 0 Bit becomes 1
1 1 Bit is toggled

The three register bits that concern us are bits 0, 1 and 2 the rest can be left alone. The purpose of each bit is detailed below :

0 If set then XON/XOFF handshaking is employed.

1 If bit is unset then use DCD handshaking.

2 If bit is unset then use DSR handshaking.

Therefore if we wish to turn XON/XOFF handshaking on then we use: ANDMask = 0xfffffffe; XORMask = 0x00000001;

This is the most confusing of the serial port calls and you might like to read the description of OS_SerialOp 0 in the PRMs for more details.

Reading bytes from the input buffer

Bytes can be read from the input buffer using the following code :

  r.r[0] = 4;
  r.r[1] = 300;
  _kernel_swi(OS_SerialOp, &r, &r);

If there is a byte in the input buffer then it will be placed in R1. If there isn't any data in the buffer then R1 will be preserved. You can therefore test whether you have actually read any data out of the buffer by placing a number greater then 255 (i.e. larger than can be stored in a byte) into R1 before you call the SWI (in the example I have used 300). If after the SWI R1 is still the number you put in then the buffer was empty, otherwise R1 will hold the byte read from the buffer.

Writing bytes to the output buffer

This stumped me for a very long time. The reason is that the OS_SerialOp 3 command that sends bytes to the buffer sets the processor carry flag if the buffer was full and the byte wasn't sent. You might wonder what the problem is. Well, you obviously have to detect that the byte was not sent otherwise you will lose data. The problem is how do you read the carry flag from C? I almost resorted to writing an assembler routine but I then found the following function :

  _kernel_oserror *_kernel_swi_c(int no, _kernel_swi_regs *in,
                                 _kernel_swi_regs *out, int *carry);

As well as calling the SWI as normal this sets the integer carry to the status of the carry flag on exit. The following piece of code will therefore transmit bytes :

  int notsent; /* read from carry flag */
  r.r[0] = 3;
  r.r[1] = (int) byte;
  _kernel_swi_c(OS_SerialOp, &r, &r, &notsent);

Where on exit you can test notsent to see whether the byte was transmitted or not.

Detecting data in the input buffer

If you simply wish to see whether there is any data in the input buffer without extracting a byte then you can use the following piece of code :

  int bufferempty;
  r.r[0] = 152;
  r.r[1] = 1;
  _kernel_swi_c(OS_Byte, &r, &r, &bufferempty);

Here bufferempty is set to 1 if the buffer is empty and 0 if it has data in.

Flushing the buffers

Finally you can flush the buffers using :

  r.r[0] = 21;
  r.r[1] = buffer;
  _kernel_swi(OS_Byte, &r, &r);

Where buffer is set to 1 for the input buffer and to 2 for the output buffer.

1  but 1 stop bit if you use an 8 bit word with parity and 1.5 stop bits if you use a 5 bit word without parity.

From CAUGers volume 2 issue 2       Comments to caug@accu.org


Mirrored from http://www.accu.org/