Chapter 13

Common System Devices

Abstract

This chapter briefly describes some of the devices which are present in most modern computer systems. It then describes in detail the clock management devices on the Raspberry Pi and the pcDuino. Next, it gives an explanation of asynchronous serial communications, and explains how there is some tolerance for mismatch between the clock rate of the transmitter and receiver. It then explains the Universal Asynchronous Receiver/Transmitter (UART) device. Next it covers in detail the UART devices present on the Raspberry Pi and the PcDuino. Once again, the reader is given the opportunity to do a comparison between two different devices which perform almost precisely the same functions.

Keywords

Universal asynchronous receiver/transmitter (UART); Clock manager; Serial communications; RS232

There are some classes of devices that are found in almost every system, including the smallest embedded systems. Such common devices include hardware for managing the clock signals sent to other devices, and serial communications (typically RS232). Most mid-sized or large systems also include devices for managing virtual memory, managing the cache, driving a display, interfacing with keyboard and mouse, accessing disk and other storage devices, and networking. Small embedded systems may have devices for converting analog signals to digital and vice versa, pulse width modulation, and other purposes. Some systems, such as the Raspberry Pi and pcDuino, have all or most of the devices of large systems, as well as most of the devices found on embedded systems. In this chapter, we look at two devices found on almost every system.

13.1 Clock Management Device

Very simple computer systems can be driven by a single clock. Most devices, including the CPU, are designed as state machines. The clock device sends a square-wave signal at a fixed frequency to all devices that need it. The clock signal tells the devices when to transition to the next state. Without the clock signal, none of the devices would do anything.

More complex computers may contain devices which need to run at different rates. This requires the system to have separate clock signals for each device (or group of devices). System designers often solve this problem by adding a clock manager device to the system. This device allows the programmer to configure the clock signals that are sent to the other devices in the system. Fig. 13.1 shows a typical system. The clock manager, just like any other device, is configured by the CPU writing data to its registers using the system bus.

f13-01-9780128036983
Figure 13.1 Typical system with a clock management device.

13.1.1 Raspberry Pi Clock Manager

The BCM2835 system-on-chip contains an ARM CPU and several devices. Some of the devices need their own clock to drive their operation at the correct frequency. Some devices, such as serial communications receivers and transmitters, need configurable clocks so that the programmer has control over the speed of the device. To provide this flexibility and allow the programmer to have control over the clocks for each device, the BCM2835 includes a clock manager device, which can be used to configure the clock signals driving the other devices in the system.

The Raspberry Pi has a 19.2 MHz oscillator which can be used as a base frequency for any of the clocks. The BCM2835 also has three phase-locked-loop circuits that boost the oscillator to higher frequencies. Table 13.1 shows the frequencies that are available from various sources. Each device clock can be driven by one of the PLLs, the external 19.2 MHz oscillator, a signal from the HDMI port, or either of two test/debug inputs.

Table 13.1

Clock sources available for the clocks provided by the clock manager

NumberNameFrequencyNote
0GND0 HzClock is stopped
1oscillator19.2 MHz
2testdebug0UnknownUsed for system testing
3testdebug1UnknownUsed for system testing
4PLLA650 MHzMay not be available
5PLLC200 MHzMay not be available
6PLLD500 MHz
7HDMI auxiliaryUnknown
8–15GND0 HzClock is stopped

t0010

Among the clocks controlled by the clock manager device are the core clock (CM_VPU), the system timer clock (PM_TIME) which controls the speed of the system timer, the GPIO clocks which are documented in the Raspberry Pi peripheral documentation, the pulse modulator device clocks, and the serial communications clocks. It is generally not a good idea to modify the settings of any of the clocks without good reason.

The base address of the clock manager device is 2010100016. Some of the clock manager registers are shown in Table 13.2. Each clock is managed by two registers: a control register and a divisor. The control register is used to enable or disable a clock, to select which source oscillator drives the clock, and to select an optional multistage noise shaping (MASH) filter level. MASH filtering is useful for reducing the perceived noise when a clock is being used to generate an audio signal. In most cases, MASH filtering should not be used.

Table 13.2

Some registers in the clock manager device

OffsetNameDescription
07016CM_GP0_CTLGPIO Clock 0 (GPCLK0) Control
07416CM_GP0_DIVGPIO Clock 0 (GPCLK0) Divisor
07816CM_GP1_CTLGPIO Clock 1 (GPCLK1) Control
07c16CM_GP1_DIVGPIO Clock 1 (GPCLK1) Divisor
08016CM_GP2_CTLGPIO Clock 2 (GPCLK2) Control
08416CM_GP2_DIVGPIO Clock 2 (GPCLK2) Divisor
09816CM_PCM_CTLPulse Code Modulator Clock (PCM_CLK) Control
09c16CM_PCM_DIVPulse Code Modulator Clock (PCM_CLK) Divisor
0a016CM_PWM_CTLPulse Modulator Device Clock (PWM_CLK) Control
0a416CM_PWM_DIVPulse Modulator Device Clock (PWM_CLK) Divisor
0f016CM_UART_CTLSerial Communications Clock (UART_CLK) Control
0f416CM_UART_DIVSerial Communications Clock (UART_CLK) Divisor

Table 13.3 shows the meaning of the bits in the control registers for each of the clocks, and Table 13.4 shows the fields in the clock manager divisor registers. The procedure for configuring one of the clocks is:

Table 13.3

Bit fields in the clock manager control registers

BitNameDescription
3–0SRCClock source chosen from Table 13.1
4ENABWriting a 0 causes the clock to shut down. The clock will not stop immediately. The BUSY bit will be 1 while the clock is shutting down. When the BUSY bit becomes 0, the clock has stopped and it is safe to reconfigure it. Writing a 1 to this bit causes the clock to start
5KILLWriting a 1 to this bit will stop and reset the clock. This does not shut down the clock cleanly, and could cause a glitch in the clock output
6-Unused
7BUSYA 1 in this bit indicates that the clock is running
8FLIPWriting a 1 to this bit will invert the clock output. Do not change this bit while the clock is running
10–9MASHControls how the clock source is divided.

00: Integer division

01: 1-stage MASH division

10: 2-stage MASH division

11: 3-stage MASH division

Do not change this while the clock is running.
23–11Unused
31–24PASSWDThis field must be set to 5A16 every time the clock control register is written to

t0020

Table 13.4

Bit fields in the clock manager divisor registers

BitNameDescription
11–0DIVFFractional part of divisor. Do not change this while the clock is running
23–12DIVIInteger part of divisor. Do not change this while the clock is running
31–24PASSWDThis field must be set to 5A16 every time the clock divisor register is written to

1. Read the desired clock control register.

2. Clear bit 4 in the word that was read, then OR it with 5A00000016 and store the result back to the desired clock control register.

3. Repeatedly read the desired clock control register, until bit 7 becomes 0.

4. Calculate the divisor required and store it into the desired clock divisor register.

5. Create a word to configure and start the clock. Begin with 5A00000016, and set bits 3–0 to select the desired clock source. Set bits 10–9 to select the type of division, and set bit 4 to 1 to enable the clock.

6. Store the control word into the desired clock control register.

Selection of the divisor depends on which clock source is used, what type of division is selected, and the desired output of the clock being configured. For example, to set the PWM clock to 100 kHz, the 19.20 MHz clock can be used. Dividing that clock by 192 will provide a 100-KHz clock. To accomplish this, it is necessary to stop the PWM clock as described, store the value 5A0C000016 in the PWM clock divisor register, and then start the clock by writing 5A00001116 into the PWM clock control register.

13.1.2 pcDuino Clock Control Unit

The AllWinner A10/A20 SOCs have a relatively simple clock manager, which is referred to as the Clock Control Unit. All of the clock signals in the system are driven by two crystal oscillators: the main oscillator runs at 24 MHz, and the real-time-clock oscillator, which runs at 32768 Hz. The real-time-clock oscillator is used only to provide a signal to the real-time-clock device.

The main clock oscillator drives many of the devices in the system, but there are seven phase-locked-loop circuits in the CCU which provide signals for devices which need clocks that are faster or slower than 24 MHz. Table 13.5 shows which devices are driven by the nine clock signals.

Table 13.5

Clock signals in the AllWinner A10/A20 SOC

Clock DomainModulesFrequencyDescription
OSC24MMost modules24 MHzMain clock
CPU32_clkCPU2 kHz–1.2 GHzDrives CPU
AHB_clkAHB devices8 kHz–276 MHzDrives some devices
APB_clkPeripheral bus500 Hz–138 MHzDrives some devices
SDRAM_clkSDRAM0 Hz–400 MHzDrives SDRAM memory
Usb:clkUSB480 MHzDrives USB devices

t0030

13.2 Serial Communications

There are basically two methods for transferring data between two digital devices: parallel and serial. Parallel connections use multiple wires to carry several bits at one time, typically including extra wires to carry timing information. Parallel communications are used for transferring large amounts of data over very short distances. However, this approach becomes very expensive when data must be transferred more than a few meters. Serial, on the other hand, uses a single wire to transfer the data bits one at a time. When compared to parallel transfer, the speed of serial transfer typically suffers. However, because it uses significantly fewer wires, the distance may be greatly extended, reliability improved, and cost vastly reduced.

13.2.1 UART

One of the oldest and most common devices for communications between computers and peripheral devices is the Universal Asynchronous Receiver/Transmitter, or UART. The word “universal” indicates that the device is highly configurable and flexible. UARTs allow a receiver and transmitter to communicate without a synchronizing signal.

The logic signal produced by the digital UART typically oscillates between zero volts for a low level and five volts for a high level, and the amount of current that the UART can supply is limited. For transmitting the data over long distances, the signals may go through a level-shifting or amplification stage. The circuit used to accomplish this is typically called a line driver. This circuit boosts the signal provided by the UART and also protects the delicate digital outputs from short circuits and signal spikes. Various standards, such as RS-232, RS-422, and RS-485 define the voltages that the line driver uses. For example, the RS-232 standard specifies that valid signals are in the range of + 3 to + 15 V, or − 3 to − 15 V. The standards also specify the maximum time that is allowable when shifting from a high signal to a low signal and vice versa, the amount of current that the device must be capable of sourcing and sinking, and other relevant design criteria.

The UART transmits data by sending each bit sequentially. The receiving UART re-assembles the bits into the original data. Fig. 13.2 shows how the transmitting UART converts a byte of data into a serial signal, and how the receiving UART samples the signal to recover the original data. Serializing the transmission and reassembly of the data are accomplished using shift registers. The receiver and transmitter each have their own clocks, and are configured so that the clocks run at the same speed (or close to the same speed). In this case, the receiver’s clock is running slightly slower than the transmitter’s clock, but the data are still received correctly.

f13-02-9780128036983
Figure 13.2 Transmitter and receiver timings for two UARTS. (A) Waveform of a UART transmitting a byte. (B) Timing of UART receiving a byte.

To transfer a group of bits, called a data frame, the transmitter typically first sends a start bit. Most UARTs can be configured to transfer between four and eight data bits in each group. The transmitting and receiving UARTS must be configured to use the same number of data bits. After each group of data bits, the transmitter will return the signal to the low state and keep it there for some minimum period. This period is usually the time that it would take to send two bits of data, and is referred to as the two stop bits. The stop bits allow the receiver to have some time to process the received byte and prepare for the next start bit. Fig. 13.2A shows what a typical RS-232 signal would look like when transferring the value 5616 (the ASCII “V” character). The UART enters the idle state only if there is not another byte immediately ready to send. If the transmitter has another byte to send, then the start bit can begin at the end of the second stop bit.

Note that it is impossible to ensure that the receiver and transmitter have clocks which are running at exactly the same speed, unless they use the same clock signal. Fig. 13.2B shows how the receiver can reassemble the original data, even with a slightly different clock rate. When the start bit is detected by the receiver, it prepares to receive the data bits, which will be sent by the transmitter at an expected rate (within some tolerance). The receive circuitry of most UARTS is driven by a clock that runs 16 times as fast as the baud rate. The receive circuitry uses its faster clock to latch each bit in the middle of its expected time period. In Fig. 13.2B, the receiver clock is running slower than the transmitter clock. By the end of the data frame, the sample time is very far from the center of the bit, but the correct value is received. If the clocks differed by much more, or if more than eight data bits were sent, then it is very likely that incorrect data would be received. Thus, as long as their clocks are synchronized within some tolerance (which is dependent on the number of data bits and the baud rate), the data will be received correctly.

The RS-232 standard allows point-to-point communication between two devices for limited distances. With the RS-232 standard, simple one-way communications can be accomplished using only two wires: One to carry the serial bits, and another to provide a common ground. For bi-directional communication, three wires are required. In addition, the RS-232 standard specifies optional hand-shaking signals, which the UARTs can use to signal their readiness to transmit or receive data. The RS-422 and RS-485 standards allow multiple devices to be connected using only two wires.

The first UART device to enjoy widespread use was the 8250. The original version had 12 registers for configuration, sending, and receiving data. The most important registers are the ones that allow the programmer to set the transmit and receive bit rates, or baud. One baud is one bit per second. The baud is set by storing a 16 bit divisor in two of the registers in the UART. The chip is driven by an external clock, and the divisor is used to reduce the frequency of the external clock to a frequency that is appropriate for serial communication. For example, if the external clock runs at 1 MHz, and the required baud is 1200, then the divisor must be 833.3¯833si1_e. Note that the divisor can only be an integer, so the device cannot achieve exactly 1200 baud. However, as explained previously, the sending and receiving devices do not have to agree precisely on the baud. During the transmission and reception of a byte, 1200.48 baud is close enough that the bits will be received correctly even if the other end is running slightly below 1200 baud. In the 8250, there was only one 8-bit register for sending data and only one 8-bit register for receiving data. The UART could send an interrupt to the CPU after each byte was transmitted or received. When receiving, the CPU had to respond to the interrupt very quickly. If the current byte was not read quickly enough by the CPU, it would be overwritten by the subsequent incoming byte. When transmitting, the CPU needed to respond quickly to interrupts to provide the next byte to be sent, or the transmission rate would suffer.

The next generation of UART device was the 16550A. This device is the model for most UART devices today. It features 16-byte input and output buffers and the ability to trigger interrupts when a buffer is partially full or partially empty. This allows the CPU to move several bytes of data at a time and results in much lower CPU overhead and much higher data transmission and reception rates. The 16550A also supports much higher baud rates than the 8250.

13.2.2 Raspberry Pi UART0

The BCM2835 system-on-chip provides two UART devices: UART0 and UART1. UART 1 is part of the I2C device, and is not recommended for use as a UART. UART0 is a PL011 UART, which is based on the industry standard 16550A UART. The major differences are that the PL011 allows greater flexibility in configuring the interrupt trigger levels, the registers appear in different locations, and the locations of bits in some of the registers is different. So, although it operates very much like a 16550A, things have been moved to different locations. The transmit and receive lines can be routed through GPIO pin 14 and GPIO pin 15, respectively. UART0 has 18 registers, starting at its base address of 2E2010016. Table 13.6 shows the name, location, and a brief description for each of the registers.

Table 13.6

Raspberry Pi UART0 register map

OffsetNameDescription
0016UART_DRData Register
0416UART_RSRECRReceive Status Register/Error Clear Register
1816UART_ FRFlag register
2016UART_ILPRnot in use
2416UART_IBRDInteger Baud rate divisor
2816UART_FBRDFractional Baud rate divisor
2c16UART_LCRHLine Control register
3016UART_CRControl register
3416UART_IFLSInterrupt FIFO Level Select Register
3816UART_IMSCInterrupt Mask Set Clear Register
3c16UART_RISRaw Interrupt Status Register
4016UART_MISMasked Interrupt Status Register
4416UART_ICRInterrupt Clear Register
4816UART_DMACRDMA Control Register
8016UART_ITCRTest Control register
8416UART_ITIPIntegration test input reg
8816UART_ITOPIntegration test output reg
8c16UART_TDRTest Data reg

UART_DR: The UART Data Register is used to send and receive data. Data are sent or received one byte at a time. Writing to this register will add a byte to the transmit FIFO. Although the register is 32 bits, only the 8 least significant bits are used in transmission, and 12 least significant bits are used for reception. If the FIFO is empty, then the UART will begin transmitting the byte immediately. If the FIFO is full, then the last byte in the FIFO will be overwritten with the new byte that is written to the Data Register. When this register is read, it returns the byte at the top of the receive FIFO, along with four additional status bits to indicate if any errors were encountered. Table 13.7 specifies the names and use of the bits in the UART Data Register.

Table 13.7

Raspberry Pi UART data register

BitNameDescriptionValues
7–0DATAData

Read: Last data received

Write: Data byte to transmit

8FEFraming error

0: No error

1: The received character did not have a valid stop bit

9PEParity error

0: No error

1: The received character did not have the correct parity, as set in theEPS and SPS bits of the Line Control Register (UART_LCRH)

10BEBreak error

0: No error

1: A break condition was detected. The data input line was held low forlonger than the time it would take to receive a complete byte,including the start and stop bits.

11OEOverrun error

0: No error

1: Data was not read quickly enough, and one or more bytes wereoverwritten in the input buffer

31–12-Not usedWrite as zero, read as don’t care

t0040

UART_RSRECR: The UART Receive Status Register/Error Clear Register is used to check the status of the byte most recently read from the UART Data Register, and to check for overrun conditions at any time. The status information for overrun is set immediately when an overrun condition occurs. The Receive Status Register/Error Clear Register provides the same four status bits as the Data Register (but in bits 3–0 rather than bits 11–8). The received data character must be read first from the Data Register, before reading the error status associated with that data character from the RSRECR register. Since the Data Register also contains these 4 bits, this register may not be required, depending on how the software is written. Table 13.8 describes the bits in this register.

Table 13.8

Raspberry Pi UART receive status register/error clear register

BitNameDescriptionValues
0FEFraming error

0: No error

1: The received character did not have a valid stop bit

1PEParity error

0: No error

1: The received character did not have the correct parity, as set in theEPS and SPS bits of the Line Control Register (UART_LCRH)

2BEBreak error

0: No error

1: A break condition was detected. The data input line was held low for longer than the time it would take to receive a complete byte,including the start and stop bits.

3OEOverrun error

0: No error

1: Data was not read quickly enough, and one or more bytes wereoverwritten in the input buffer

31–4Not usedWrite as zero, read as don’t care

t0045

UART_FR: The UART Flag Register can be read to determine the status of the UART. The bits in this register are used mainly when sending and receiving data using the FIFOs. When several bytes need to be sent, the TXFF flag should be checked to ensure that the transmit FIFO is not full before each byte is written to the data register. When receiving data, the RXFE bit can be used to determine whether or not there is more data to be read from the FIFO. Table 13.9 describes the flags in this register.

Table 13.9

Raspberry Pi UART flags register bits

BitNameDescriptionValues
0CTSClear To Send

0: Sender indicates they are ready to receive

1: Sender is NOT ready to receive

1DSRData Set ReadyNot implemented: Write as zero, read as don’t care
2DCDData Carrier DetectNot implemented: Write as zero, read as don’t care
3BUSYUART is busy

0: UART is not transmitting data

1: UART is transmitting a byte

4RXFEReceive FIFO Empty

0: Receive FIFO contains bytes that have been received

1: Receive FIFO is empty

5TXFFTransmit FIFO is Full

0: There is room for at least one more byte in the transmit FIFO

1: Transmit FIFO is full – do not write to the data register at this time

6RXFFReceive FIFO is Full

0: There is no more room in the receive FIFO

1: There is still some space in the receive FIFO

7TXFETransmit FIFO is Empty

0: There are no bytes waiting to be transmitted

1: There is at least one byte waiting to be transmitted

8RIRing IndicatorNot implemented: Write as zero, read as don’t care
31–9Not usedWrite as zero, read as don’t care

t0050

UART_ILPR: This is the IrDA register, which is supported by some PL011 UARTs. IrDA stands for the Infrared Data Association, which is a group of companies that cooperate to provide specifications for a complete set of protocols for wireless infrared communications. The name “IrDA” also refers to that set of protocols. IrDA is not implemented on the Raspberry Pi UART. Writing to this register has no effect and reading returns 0.

UART_IBRD and UART_FBRD: UART_FBRD is the fractional part of the baud rate divisor value, and UART_IBRD is the integer part. The baud rate divisor is calculated as follows:

BAUDDIV=UARTCLK16×Baudrate

si2_e  (13.1)

where UARTCLK is the frequency of the UART_CLK that is configured in the Clock Manager device. The default value is 3 MHz. BAUDDIV is stored in two registers. UART_IBRD holds the integer part and UART_FBRD holds the fractional part. Thus BAUDDIV should be calculated as a U(16,6) fixed point number. The contents of the UART_IBRD and UART_FBRD registers may be written at any time, but the change will not have any effect until transmission or reception of the current character is complete. Table 13.10 shows the arrangement of the integer baud rate divisor register, and Table 13.11 shows the arrangement of the fractional baud rate divisor register.

Table 13.10

Raspberry Pi UART integer baud rate divisor

BitNameDescriptionValues
15–0IBRDInteger Baud Rate DivisorSee Eq. (13.1)
31–16Not usedWrite as zero, read as don’t care

t0055

Table 13.11

Raspberry Pi UART fractional baud rate divisor

BitNameDescriptionValues
5-0FBRDFractional Baud Rate DivisorSee Eq. (13.1)
31-6Not usedWrite as zero, read as don’t care

t0060

UART_LCRH: UART_LCRH is the line control register. It is used to configure the communication parameters. This register must not be changed until the UART is disabled by writing zero to bit 0 of UART_CR, and the BUSY flag in UART_FR is clear. Table 13.12 shows the layout of the line control register.

Table 13.12

Raspberry Pi UART line control register bits

BitNameDescriptionValues
0BRKSend Break

0: Normal operation

1: After the current character is sent, take the TXD output to a lowlevel and keep it there

1PENParity Enable

0: Parity checking and generation is disabled

1: Generate and send parity bit and check parity on received data

2EPSEven Parity Select

0: Odd parity

1: Even parity

3STP2Two Stop Bits

0: Send one stop bit for each data word

1: Send two stop bits for each data word

4FENFIFO Enable

0: Transmit and Receive FIFOs are disabled

1: Transmit and Receive FIFOs are enabled

6–5WLENWord Length

00: 5 bits per data word

01: 6 bits per data word

10: 7 bits per data word

11: 8 bits per data word

31–7Not usedWrite as zero, read as don’t care

t0065

UART_CR: The UART Control Register is used for configuring, enabling, and disabling the UART. Table 13.13 shows the layout of the control register. To enable transmission, the TXE bit and UARTEN bit must be set to 1. To enable reception, the RXE bit and UARTEN bit must be set to 1. In general, the following steps should be used to configure or re-configure the UART:

Table 13.13

Raspberry Pi UART control register bits

BitNameDescriptionValues
0UARTENUART Enable

0: UART disabled

1: UART enabled.

1SIRENNot usedWrite as zero, read as don’t care
2SIRLPNot usedWrite as zero, read as don’t care
3–6Not usedWrite as zero, read as don’t care
7LBELoopback Enable

0: Loopback disabled

1: Loopback enabled. Transmitted data is also fed back to the receiver.

8TXETransmit enable

0: Transmitter is disabled

1: Transmitter is enabled

9RXEReceive enable

0: Receiver is disabled

1: Receiver is enabled

10DTRNot usedWrite as zero, read as don’t care
11RTSComplement of nUARTRTS
12OUT1Not usedWrite as zero, read as don’t care
13OUT2Not usedWrite as zero, read as don’t care
14RTSENRTS Enable

0: Hardware RTS disabled.

1: Hardware RTS Enabled

15CTSENCTS Enable

0: Hardware CTS disabled.

1: Hardware CTS Enabled

16–31Not usedWrite as zero, read as don’t care

t0070

(a) Disable the UART.

(b) Wait for the end of transmission or reception of the current character.

(c) Flush the transmit FIFO by setting the FEN bit to 0 in the Line Control Register.

(d) Reprogram the Control Register.

(e) Enable the UART.

Interrupt Control: The UART can signal the CPU by asserting an interrupt when certain conditions occur. This will be covered in more detail in Chapter 14. For now, it is enough to know that there are five additional registers which are used to configure and use the interrupt mechanism.
UART_IFLS defines the FIFO level that triggers the assertion of the interrupt signal. One interrupt is generated when the FIFO reaches the specified level. The CPU must clear the interrupt before another can be generated.
UART_IMSC is the interrupt mask set/clear register. It is used to enable or disable specific interrupts. This register determines which of the possible interrupt conditions are allowed to generate an interrupt to the CPU.
UART_RIS is the raw interrupt status register. It can be read to raw status of interrupts conditions before any masking is performed.
UART_MIS is the masked interrupt status register. It contains the masked status of the interrupts. This is the register that the operating system should use to determine the cause of a UART interrupt.
UART_ICR is the interrupt clear register. writing to it clears the interrupt conditions. The operating system should use this register to clear interrupts before returning from the interrupt service routine.

UART_DMACR: The DMA control register is used to configure the UART to access memory directly, so that the CPU does not have to move each byte of data to or from the UART. DMA will be explained in more detail in Chapter 14.

Additional Registers: The remaining registers, UART_ITCR, UART_ITIP, and UART_ITOP, are either unimplemented or are used for testing the UART. These registers should not be used.

13.2.3 Basic Programming for the Raspberry Pi UART

Listing 13.1 shows four basic functions for initializing the UART, changing the baud rate, sending a character, and receiving a character using UART0 on the Raspberry Pi. Note that a large part of the code simply defines the location and offset for all of the registers (and bits) that can be used to control the UART.

f13-03a-9780128036983f13-03b-9780128036983f13-03c-9780128036983f13-03d-9780128036983f13-03e-9780128036983
Listing 13.1 Assembly functions for using the Raspberry Pi UART.

13.2.4 pcDuino UART

The AllWinner A10/A20 SOC includes eight UART devices. They are all fully compatible with the 16550A UART, and also provide some enhancements. All of them provide transmit (TX) and receive (RX) signals. UART0 has the full set of RS232 signals, including RTS, CTS, DTR, DSR, DCD, and RING. UART1 has the RTS and CTS signals. The remaining six UARTs only provide the TX and RX signals. They can all be configured for serial IrDA. Table 13.14 shows the base address for each of the eight UART devices.

Table 13.14

pcDuino UART addresses

NameAddress
UART00x01C28000
UART10x01C28400
UART20x01C28800
UART30x01C28C00
UART40x01C29000
UART50x01C29400
UART60x01C29800
UART70x01C29C00

When the 16550 UART was designed, 8-bit processors were common, and most of them provided only 16 address bits. Memory was typically limited to 64 kB, and every byte of address space was important. Because of these considerations, the designers of the 16550 decided to limit the number of addresses used to 8, and to only use eight bits of data per address. There are 10 registers in the 16550 UART, but some of them share the same address. For example, there are three registers mapped to an offset address of zero, two registers mapped at offset four, and two registers mapped at offset eight. Bit seven in the Line Control Register is used to determine which of the registers is active for a given address.

Because they are meant to be fully backwards-compatible with the 16550, the AllWinner A10/A20 SOC UART devices also use only 8 bits for each register, and the first 12 registers correspond exactly with the 16550 UART. The only differences are that the pcDuino uses word addresses rather than byte addresses, and they provide four additional registers that are used for IrDA mode. Table 13.15 shows the arrangement of the registers in each of the 8 UARTs on the pcDuino. The following sections will explain the registers.

Table 13.15

pcDuino UART register offsets

Register NameOffsetDescription
UART_RBR0x00UART Receive Buffer Register
UART_THR0x00UART Transmit Holding Register
UART_DLL0x00UART Divisor Latch Low Register
UART_DLH0x04UART Divisor Latch High Register
UART_IER0x04UART Interrupt Enable Register
UART_IIR0x08UART Interrupt Identity Register
UART_FCR0x08UART FIFO Control Register
UART_LCR0x0CUART Line Control Register
UART_MCR0x10UART Modem Control Register
UART_LSR0x14UART Line Status Register
UART_MSR0x18UART Modem Status Register
UART_SCH0x1CUART Scratch Register
UART_USR0x7CUART Status Register
UART_TFL0x80UART Transmit FIFO Level
UART_RFL0x84UART_RFL
UART_HALT0xA4UART Halt TX Register

The baud rate is set using a 16-bit Baud Rate Divisor, according to the following equation:

BAUDDIV=sclk16×Baudrate

si3_e  (13.2)

where sclk is the frequency of the UART serial clock, which is configured by the Clock Manager device. The default frequency of the clock is 24 MHz. BAUDDIV is stored in two registers. UART_DLL holds the least significant 8 bits, and UART_DLH holds the most significant 8 bits. Thus BAUDDIV should be calculated as a 16-bit unsigned integer. Note that for high baud rates, it may not be possible to get exactly the rate desired. For example, a baud rate of 115200 would require a divisor of 13.02083¯si4_e. Since the baud rate divisor can only be given as an integer, the desired rate must be based on a divisor of 13, so the true baud rate will be 2400000016×13=115384.615385si5_e, or about 0.16% faster than desired. Although slightly fast, it is well within the tolerance for RS232 communication.

UART_RBR: The UART Receive Buffer Register is used to receive data, 1 byte at a time. If the receive FIFO is enabled, then as the UART receives data, it places the data into a receive FIFO. Reading from this address removes 1 byte from the receive FIFO. If the FIFO becomes full and another data byte arrives, then the new data are lost and an overrun error occurs. Table 13.16 shows the layout of the receive buffer register.

Table 13.16

pcDuno UART receive buffer register

BitNameDescriptionValues
7–0RBRDataRead only: One byte of received data. Bit 7 of LCR must bezero.
31–8Unused

t0085

UART_THR: Writing to the Transmit Holding Register will cause that byte to be transmitted by the UART. If the transmit FIFO is enabled, then the byte will be added to the end of the transmit FIFO. If the FIFO is empty, then the UART will begin transmitting the byte immediately. If the FIFO is full, then the new data byte will be lost. Table 13.17 shows the layout of the transmit holding register.

Table 13.17

pcDuno UART transmit holding register

BitNameDescriptionValues
7–0THRDataWrite only: One byte of data to transmit. Bit 7 of LCR must bezero.
31–8Unused

t0090

UART_DLL: The UART Divisor Latch Low register is used to set the least significant byte of the baud rate divisor. When bit 7 of the Line Control Register is set to one, writing to this address will access the DLL register. If bit 7 of the Line Control Register is set to zero, then writing to this address will access the transmit holding register. Table 13.18 shows the layout of the UART_DLL register.

Table 13.18

pcDuno UART divisor latch low register

BitNameDescriptionValues
7–0DLLDataWrite only: Least significant eight bits of the Baud Rate Divisor. Bit 7 of LCR must be one.
31–8Unused

t0095

UART_DLH: The UART Divisor Latch High register is used to set the most significant byte of the baud rate divisor. When bit 7 of the Line Control Register is set to one, writing to this address will access the DLH register. If bit 7 of the Line Control Register is set to zero, then writing to this address will access the Interrupt Enable Register rather than the Divisor Latch High register. Table 13.19 shows the layout of the UART_DLL register.
If the two Divisor Latch Registers (DLL and DLH) are set to zero, the baud clock is disabled and no serial communications occur. DLH should be set before DLL, and at least eight clock cycles of the UART clock should be allowed to pass before data are transmitted or received.

Table 13.19

pcDuno UART divisor latch high register

BitNameDescriptionValues
7–0DLHDataWrite only: Most significant eight bits of the Baud Rate Divisor. Bit 7 of LCR must be one.
31–8Unused

t0100

UART_FCR: is the UART FIFO control register. It is used to enable or disable the receive and transmit FIFOs (buffers), flush their contents, set the level at which the transmit and receive FIFOs trigger an interrupt, and to control Direct Memory Access (DMA) Table 13.20 shows the layout of the UART_FCR register.

Table 13.20

pcDuno UART FIFO control register

BitNameDescription
0FIFOEFIFO Enable

0: transmit and receive FIFOs disabled

1: transmit and receive FIFOs enabled

1RFIFORReceive FIFO Reset: writing a 1 to this bit causes the receive FIFO to be reset, and then continue normal operation
2XFIFORTransmit FIFO Reset: writing a 1 to this bit causes the transmit FIFO to be reset, and then continue normal operation
3DMAMDMA Mode:

0: Mode 0

1: Mode 1

5–4TETTransmit Empty Trigger: These bits control the level at which the Transmit Holding Register Empty interrupt is triggered

00: FIFO is completely empty

01: There are two characters in the FIFO

10: The FIFO is 25% full

11: The FIFO is 50% full

This setting has no effect if THRE_MODE_USER is disabled
7–6RTReceive Trigger: These bits control the level at which the Received Data Available interrupt is triggered.

00: There is one character in the FIFO

01: The FIFO is 25% full

10: The FIFO is 50% full

11: There is room for two more characters in the FIFO

This setting has no effect if THRE_MODE_USER is disabled.
31–8Unused

t0105

UART_LCR: The Line Control Register is used to control the parity, number of data bits, and number of stop bits for the serial port. Bit 7 also controls which registers are mapped at offsets 0, 4, and 8 from the device base address. Table 13.21 shows the layout of the UART_LCR register.

Table 13.21

pcDuno UART line control register

BitNameDescription
1–0DLSThis field controls the number of data bits:

00: 5 data bits

01: 6 data bits

10: 7 data bits

11: 8 data bits

2STOPThis bit controls the number of stop bits used for transmitting and receiving data.

0: 1 stop bit

1: If DLS is set to 00, then 1.5 stop bits, otherwise 2 stop bits

3PENParity Enable:

0: Parity disabled

1: Parity enabled

4EPSEven Parity Select:

0: Odd Parity

1: Even Parity

5Unused
6BCBWriting a one to this bit causes a break to be sent. This bit must be set to zero for normal operation.
7DLABThe Divisor Latch Access Bit controls the behavior of other registers:

0: The RBR, THR, and IER registers are accessible (RBR is used for read at offset 0, and THR for write at offset 0).

1: The DLL and DLM registers are accessible

31–8Unused

t0110

UART_LSR: The Line Status Register is used to read status information from the UART. Table 13.22 shows the layout of the UART_LSR register.

Table 13.22

pcDuno UART line status register

BitNameDescription
0DRWhen the Data Ready bit is set to 1, it indicates that at least one byte is ready to be read from the receive FIFO or RBR.
1OEWhen the Overrun Error bit is set to 1, it indicates that an overrun error occurred for the byte at the top of the receive FIFO.
2PEWhen the Parity Error bit is set to 1, it indicates that a parity error occurred for the byte at the top of the receive FIFO.
3FEWhen the Framing Error bit is set to 1, it indicates that a framing error occurred for the byte at the top of the receive FIFO.
4BIWhen the Break Interrupt bit is set to 1, it indicates thata break has been received.
5THREWhen the Transmit Holding Register Empty bit is 1, it indicates that there are there are no bytes waiting to be transmitted, but there may be a byte currently being transmitted.
6TEMTWhen the Transmitter Empty bit is 1, it indicates that there are no bytes waiting to be transmitted and no byte currently being transmitted.
7FIFOERRWhen this bit is 1, an error has occurred (PE, BE, or BI) in the receive FIFO. This bit is cleared when the Line Status Register is read.
31–8Unused

UART_USR: The UART Status Register is used to read information about the status of the transmit and receive FIFOs, and the current state of the receiver and transmitter. Table 13.23 shows the layout of the UART_USR register. This register contains essentially the same information as the status register in the Raspberry Pi UART.

Table 13.23

pcDuno UART status register

BitNameDescription
0BUSYWhen the Busy bit is 1, it indicates that the UART is currently busy. When it is 0, the UART is idle or inactive.
1TFNFWhen the Transmit FIFO Not Full bit is 1, it indicates that at least one more byte can be safely written to the Transmit FIFO.
2TFEWhen the Transmit FIFO Empty bit is 1, it indicates that there are no bytes remaining in the transmit FIFO.
3RFNEWhen the Receive FIFO Not Empty bit is 1, it indicates that at least one more byte is waiting to be read from the receive FIFO.
4RFFWhen the Receive FIFO Full bit is 1, it indicates that there is no more room in the receive FIFO. If data is not read before the next character is received, an overrun error will occur.
31–5Unused

UART_TFL: The UART Transmit FIFO Level register allows the programmer to determine exactly how many bytes are currently in the transmit FIFO. Table 13.24 shows the layout of the UART_TFL register.

Table 13.24

pcDuno UART transmit FIFO level register

BitNameDescription
6–0TFLThe Transmit FIFO level field contains an integer which indicates the number of bytes currently in the transmit FIFO.
31–7Unused

UART_RFL: The UART Receive FIFO Level register allows the programmer to determine exactly how many bytes are currently in the receive FIFO. Table 13.25 shows the layout of the UART_RFL register.

Table 13.25

pcDuno UART receive FIFO level register

BitNameDescription
6–0RFLThe Receive FIFO level field contains an integer which indicates the number of bytes currently in the receive FIFO.
31–7Unused

UART_HALT: The UART transmit halt register is used to halt the UART so that it can be reconfigured. After the configuration is performed, it is then used to signal the UART to restart with the new settings. It can also be used to invert the receive and transmit polarity. Table 13.26 shows the layout of the UART_HALT register.

Table 13.26

pcDuno UART transmit halt register

BitNameDescription
0Unused
1CHCFG_AT_BUSYSetting this bit to 1 causes the UART to allow changing the Line Control Register (except the DLAB bit) and allows setting the baud rate even when the UART is busy. When this bit is set to 0, changes can only occur when the BUSY bit in the UART Status Register is 0.
2CHANGE_UPDATEAfter writing 1 to CHCFG_AT_BUSY and performing the configuration, 1 should be written to this bit to signal that the UART should re-start with the new configuration. This bit will stay at 1 while the new configuration is loaded, and go back to 0 when the re-start is complete.
3Unused
4SIR_TX_INVERTThis bit allows the polarity of the transmitter to be inverted.

0: Normal polarity

1: Polarity inverted

5SIR_RX_INVERTThis bit allows the polarity of the receiver to be inverted.

0: Normal polarity

1: Polarity inverted

31–5Unused

t0135

Interrupt Control: The UART can signal the CPU by asserting an interrupt when certain conditions occur. This will be covered in more detail in Chapter 14. For now, it is enough to know that there are five additional registers which are used to configure and use the interrupt mechanism.
UART_IFLS defines the FIFO level that triggers the assertion of the interrupt signal. One interrupt is generated when the FIFO reaches the specified level. The CPU must clear the interrupt before another can be generated.
UART_IER is the interrupt enable register. It is used to enable or disable the generation of interrupts for specific conditions.
UART_IIR is the Interrupt Identity Register. When an interrupt occurs, the CPU can read this register to determine what caused the interrupt.

Additional Registers There are several additional registers which are not needed for basic use of the UART.
UART_MCR is the Modem Control Register. It is used to configure the port for IrDA mode, enable Automatic Flow Control, and manage the RS-232 RTS and DTR hardware handshaking signals for the ports in which they are implemented. The default configuration disables these extra features.
UART_MSR is the Modem Status Register, which is used to read the state of the RS-232 modem control and status lines on ports that implement them. This register can be ignored unless a telephone modem is being used on the port.
UART_SCH is the Modem Scratch Register. It provides 8 bits of storage for temporary data values. In the days of 8 and 16-bit computers, when the 16550 UART was designed, this extra byte of storage was useful.

13.3 Chapter Summary

Most modern computer systems have some type of Universal Asynchronous Receiver/Transmitter. These are serial communications devices, and are meant to provide communications with other systems using RS-232 (most commonly) or some other standard serial protocol. Modern systems often have a large number of other devices as well. Each device may need its own clock source to drive it at the correct frequency for its operation. The clock sources for all of the devices are often controlled by yet another device: the clock manager.

Although two systems may have different UARTs, these devices perform the same basic functions. The specifics about how they are programmed will vary from one system to another. However, there is always enough similarity between devices of the same class that a programmer who is familiar with one specific device can easily learn to program another similar device. The more experience a programmer has, the less time it takes to learn how to control a new device.

Exercises

13.1 Write a function for setting the PWM clock on the Raspberry Pi to 2 MHz.

13.2 The UART_GET_BYTE function in Listing 13.1 contains skeleton code for handling errors, but does not actually do anything when errors occur. Describe at least two ways that the errors could be handled.

13.3 Listing 13.1 provides four functions for managing the UART on the Raspberry Pi. Write equivalent functions for the pcDuino UART.