- 1 Introduction
- 2 Hardware view
- 3 eLua view
- 4 Further reading
The Universal Asynchronous Receiver Transmitter circuit or simple UART (pronounced "you art") is one of the more common interfaces used to communicate serially. Some computers, such as the IBM PC, used an integrated circuit called a UART to convert characters to and from asynchronous serial form. By ‘serial’, we mean that data is transmitted one bit at a time.
This circuit was at the base of Recommended Standard - 232 (RS-232 in short) that also defined the physical external COM port in IBM PC. The latest revision to RS-232 is the EIA RS-232 that was done in 1997.
In recent years, the PC has lost the RS-232 port (maybe only externally), replacing it in favour of the USB, but RS-232 is still in wide use including many variants, from RS-485 used in the industry to SpaceWire used in the Space Station. In its simpler forms, it is currently widely adopted in the embedded industry.
To understand a bit more the UART circuit and the RS-232 let’s take a look at the past. Serial transmission is very old, born with the first Teleprinters (TTYs). The teleprinters were in use from 1914 to recent times, with the last company manufacturing TTY closing in 1990, while their last use is reported for air lines bulletin in recent years and most of the terms used come from the TTY world. Mark and Space for example are terms describing logic levels in teleprinter circuits. The baud rate, or Symbol rate, is the speed of a serial connection and is based on multiples of the rates for electromechanical teleprinters.
Today, although it is becoming less common in personal computers, it remains one of the most common peripherals found on micro-controllers (MCU) and is used for communication with external devices and systems, to talk with on-board serial devices or to make links between boards, between boxes or between embedded boards and PCs with an RS-232 port.
RS-232 globally specifies:
- signal voltages
- signal functions
- signal timing
- protocol for information exchange
- UART configuration
With a micro-controller oriented view we will go now to examine all the above points.
The UART is a full duplex communication channel, in asynchronous mode each line is indipendent of the others in terms of its main function. The RX pin can receive data regardless of the TX pin’s activity and vice versa.
Usually the UART wires from the micro-controller are labelled TX, RX, GND (for transmit, receive and ground) in 3 wire configuration (without flow control implemented) and 5 wires TX, RX, GND, RTS, CTS, with hardware flow control, where RTS stay for Request To Send and CTS for Clear To Send.
The signal is described as a positive voltage to communicate the logic value 0 called a “Mark”, and a negative voltage to communicate the logic value 1, called a “Space”.
This signals are usually too weak in current and voltage as get out from the micro-controller and the standard specify that should be used voltages from ±5V to ±15V with length of wire of 10 meters, so how to interface to the UART lines, that output ±3V with very low current?
The MAX232 chip on the Mizar32's Serial UART add-on board is a TTL to RS-232 level converter which pumps the voltage from ±3V to ±15V.
The signal timing is measured in Baud, where one baud in binary communication corresponds to one bit per second, so at 9600 baud rate, we have 9600 bits per second with a time frame to describe each bit of 104 μs 1/9600. Often the clock will run at 16 times the baud rate to allow the receiver to do centre sampling.
The data exchange protocol is very simple.
The packet begins with start bit, which is a logic 0, being transmitted/received first. In the software side, this bit is important as we can poll the RX pin for this bit to signal that a packet of data is coming. The data bits or the payload may or may not have a parity bit, then the packets end with a logic 1, one or two stop bit.
|XXXXXXX||(7 or 8 bit of data)|
|X||1 optional bit of parity|
|1||one or two stop bits|
Note that the least significant bit of the byte is sent first, whereas we normally write numbers with the LSB on the right, so we should read it from right to left.
Regarding configuration parameters, a common configuration (usually stored in a register) is 9600/8n1 that means for the serial port: 9600 baud, 8 bits data, no parity bit, 1 stop bit. Obviously both UART should be configured in the same way in order to communicate.
The Mizar32 has two serial ports available on the bus connectors, UART0 on the right bus and UART1 on the left bus. UART0 has just data (TXD, RXD) and hardware flow control (CTS, RTS) signals, while UART1 also has modem control signals (DSR, DTR, DCD, RI).
In the Atmel documents, these are called "USART"s because they can also be programmed into synchronous mode to work as additional SPI ports.
|Signal||GPIO||Bus pin||eLua name|
|UART0_RX||PA0||BUS4 pin 3||
|UART0_TX||PA1||BUS4 pin 4||
|UART0_RTS||PA3||BUS4 pin 5||
|UART0_CTS||PA4||BUS4 pin 6||
|UART1_RX||PA5||BUS3 pin 3||
|UART1_TX||PA6||BUS3 pin 4||
|UART1_DCD||PB23||BUS3 pin 5||
|UART1_DSR||PB24||BUS3 pin 6||
|UART1_DTR||PB25||BUS3 pin 7||
|UART1_RI||PB26||BUS3 pin 8||
|UART1_CTS||PA9||BUS3 pin 9||
|UART1_RTS||PA8||BUS3 pin 10||
RS232/RS485 serial add-on board
The add-on serial board has two banks of switches, DIP1 and DIP2 to select between RS232 and RS485 modes.
If all switches of DIP1 are up and all of DIP2 down, it converts the bus signals to RS232 levels on its female DB9 connector J7. This connector is configured as DCE equipment, which is the opposite of a PC serial port, so a cable to communicate with a PC should connect the same pins at each end; a null modem cable is not required. Connecting other DCE equipment to it, like a modem or GPS receiver, requires interchanging of TX and RX, for example with a null modem cable.
Note that in the 1.1.1 version of the serial port the CTS and RTS pins were swapped over by mistake, so to get the right connections here you need to modify either the board or the cable. However, hardware flow control is not yet working in eLua so it makes no difference; see issue #29.
|Signal||Bus pin||UART module
|UART0_RX||P5 pin 3||Pin 3 (input)||Pin 3 (input)|
|UART0_TX||P5 pin 4||Pin 2 (output)||Pin 2 (output)|
|UART0_RTS||P5 pin 5||Pin 8 (output)||Pin 7 (output)|
|UART0_CTS||P5 pin 6||Pin 7 (input)||Pin 8 (input)|
|GND||Various||Pin 5||Pin 5|
If all switches of DIP1 are down and all of DIP2 up, the board's DB9 connector is disabled and RS485 signals appear on the four screw terminals.
This interface allows up to 32 RS485 devices to be connected to the same wires with a cable length of up to 1200m at 100 kbit/sec.
Currently, RS485 mode is not supported in eLua; see issue #77.
Depending on which firmware you have, UART0 may be used for the Lua console (configured at 115200 baud, 8 data bits, 1 stop bit, no parity) and Lua's default input and output files are the console, so functions like "
print()" and "
io.write()" can be used to output characters on the serial port (with, and without, a trailing CR-LF newline sequence, respectively)
-- Greet the user io.write( "What's your name? " ) -- Issue a prompt (with no trailing newline) name = io.read() -- Read a line of input and store it in "name" print( "Hello, " .. name .. "!" ) -- Salute them
UART0 can also be accessed (and UART1 must be accessed) using the lower-level
uart eLua module, which gives a higher degree of control over the UART's behaviour.
The following example sets a different baud rate on UART0 and spits out a prompt character twice a second until a character is received in reply. To do this, it uses the
uart.setup() function and the optional timeout parameter of the
-- Prompt a 9600 baud serial device until we receive a character in reply uartid = 0 -- Which UART should we be talking on? timeout = 500000 -- Prompt once every half second timerid = 0 -- Use timer 0 to measure the timeout prompt = "U" -- The prompt character (0x55 : binary 01010101) uart.setup( uartid, 9600, 8, 0, 1 ) -- Configure the UART repeat uart.write( uartid, prompt ) reply = uart.getchar( uartid, timeout, timerid ) until reply ~= ""
Hardware flow control
Note that enabling hardware flow control with
uart.set_flow_control( uartid, uart.FLOW_RTS + uart.FLOW_CTS )
does not work yet. See issue #29.
When a UART receives a character it will remember it until you ask for its value using
uart.getchar(). However, if a second character arrives before you have read the first one, the first one will be forgotten.
You can get round this by enabling a UART buffer, for example:
uart.setup( 1, 115200, 8, 0, 1 ) -- Configure UART 1 uart.set_buffer( 1, 1024 ) -- and give it an input buffer
and this will allow the UART to receive up to 1024 characters and remember them all even if you haven't read the first one yet (the 1025th character will provoke an error message and will be forgotten.
UART buffer sizes must be a power of two, i.e. 1, 2, 4, 8, 16 and so on up to a maximum of 32768 characters.
Some firmware uses UART0 as the eLua console. When this is the case, a buffer is always enabled on this UART.
USB CDC serial port
More recent firmware, from the 2013 release, includes software that emulates another serial port on the USB interface. You connect a USB cable from the Mizar32 to your PC and a new serial port appears on the PC. Under Linux it is called
/dev/ttyACM0 and on Windows it appears as a new "USB serial port".
Usually, this virtual serial port is used as the eLua console, sending eLua output and error messages and receiving keyboard input from the user. However, you can send the console output elsewhere by compiling your own firmware at http://builder.simplemachines.it and you can talk to it by specifying serial port number 176 to eLua's low-level
It is slightly different from physical serial ports in that:
- It is more than ten times faster than the fastest RS232 serial port;
- It always implements flow control, which ensures you never lose any output or input due to overruns, but if your program produces output and there is no PC attached to the USB port, your program will freeze after a kilobyte or two of output;
- Some settings, like baud rate and stop bits, make no difference because the signals do not travel over RS232 wires;
- I don't know whether Lua interrupts work on the USB serial port or not.
When input buffering is enabled on a UART, an interrupt is generated every time a character is received. This interrupt saves the character in the buffer you asked for, until your program is ready to read it.
If you use firmware with Lua interrupts (included in the Mizar A and B firmware from the 20120123 elua 0.8 release) you can also arrange for your own piece of code to be called every time a character is received.
The following example code quickly flashes the on-board LED every time a character is received:
-- Test UART interrupts handled in Lua. -- Should flash the onboard LED each time a character is received. led = pio.PB_29 -- Which PIO pin is the LED connected to? function uart_handler( resnum ) -- flash the onboard LED pio.pin.setlow( led ) for i=1,10000 do end -- for about 1/100th of a second pio.pin.sethigh( led ) end pio.pin.sethigh( led ) -- off pio.pin.setdir( pio.OUTPUT, led ) uart.setup( 0, 115200, 8, uart.PAR_NONE, 1 ) uart.set_buffer( 0, 1024 ) -- buffer must be enabled for UART IRQs to happen -- tell eLua which function it should call every time the UART receives cpu.set_int_handler( cpu.INT_UART_RX, uart_handler ) -- and enable that Lua interrupt cpu.sei( cpu.INT_UART_RX, 0 ) -- Wait for about ten seconds while the test runs for i=1,10000000 do end -- disable the Lua interrupt cpu.cli( cpu.INT_UART_RX, 0 ) -- and remove our handler function cpu.set_int_handler( cpu.INT_UART_RX, nil )
The character is received from the UART and placed in the buffer before the Lua interrupt routine is called, so you can read it in your Lua interrupt routine with
uart.getchar( 0, 0 ) and act on it immediately.
Baud rate accuracy
The following table gives the actual baud rates set by eLua for the most commonly used ones:
Hijacking the serial board's TX LED
If UART0 is not used, the LED on the serial board can toggled by using
pio.PA_1 as a generic Mizar32/PIO output: a low output value turns this LED off and a high value turns it on.
-- Turn the serial board's TX LED on (a low output lights the LED) txled = pio.PA_1 pio.pin.setlow( txled ) -- Prepare "off" as the output value pio.pin.setdir( pio.OUTPUT, txled ) -- Make the pin a GPIO output, disabling serial port 0