Skip to content
This repository has been archived by the owner on May 20, 2024. It is now read-only.

Devices and Peripherals

rememberthe8bit edited this page Feb 17, 2013 · 5 revisions

Note: the code covered by this page is under development and may change unexpectedly

Introduction

Because the ARM CPU is rather minimalistic, it doesn't contain a large amount of functionality. Seperate peripherals must be added to do things like determining interrupt sources and timing. In the case of the Raspberry Pi, the BCM2835 ARM System on a Chip (SoC) contains the following peripherals:

  • Some General Purpose I/O (GPIO), seen on the Pi as the I/O header (or ribbon cable if you have the blue case from the Operating Systems II course)
  • Some Auxiliary serial interfaces (UART1 aka Mini-UART, SPI1, SPI2)
  • A PL011-like UART (UART0)
  • A SP804-like timer
  • A System Timer
  • A rudimentary interrupt controller
  • etc...

Each section below describes each peripheral lightly so that you can get a feeling for what they do and how they work.

GPIO

General Purpose I/O (GPIO henceforth) is a means for a computer to interface with the outside world. Usually this is via digitally controlled pins which can be set either high or low, or have a high or low state read from them. The Pi also has the optional capability to "pull" the I/O pins either high or low via internal resistors (this Wikipedia article may be helpful). Basically: you can do things like control motors, talk to LCD screens, and turn on LEDs with these pins (and appropriate interface components). GPIO pins are referred to by number and the Pi's GPIO port contains about 17 of them for us to use. Here is the an image that describes what each pin does (aka a "pinout") (from the ever helpful eLinux Wiki):

Raspberry Pi GPIO pinout image

By including the bcm2835.h header file, a number of macros are supplied:

GPIOMODE(pin, mode) set a GPIO pin to a mode. Modes are: FSEL_INPUT FSEL_OUTPUT FSEL_AF0 FSEL_AF1 FSEL_AF2 FSEL_AF3 FSEL_AF4 FSEL_AF5. ex: GPIOMODE(16, FSEL_OUTPUT); sets the GPIO pin that's hooked up to the OK (labeled ACT on the circuit board) LED to output mode.

GPIOSET(pin) sets a GPIO pin high. ex: GPIOSET(16); turns the OK/ACT LED off (it may seem odd, but 'set' does turn it off).

GPIOCLR(pin) sets a GPIO pin low. ex: GPIOCLR(16); turns the OK/ACT LED on.

GPIOREAD(pin) reads a bit from a GPIO pin. The least significant bit will be either 1 or 0.

I/O hardware initialization procedure

Because GPIO pins can perform so many different roles, they have alternate function settings (see GPIOMODE above). See page 102 of the BCM2835 datasheet for a table of these alternate functions and more.

The general procedure for initializing any hardware that needs to interact via GPIO pins is:

  1. Set the appropriate alternate function of the desired GPIO pin(s)
  2. Set the pull-up/pull-down resistor(s) for the desired GPIO pin(s)
  3. Set up and enable the hardware

For instance, a Mini-UART initialization routine would do the following:

  1. Set pin 14 to Alternate Function 5 to enable the TX pin
  2. Set pin 15 to Alternate Function 5 to enable the RX pin
  3. Turn off pull-up/pull-down resistors for the TX and RX pins
  4. Set up all of the Mini-UART's control registers and enable it

Mini-UART

A Universal Asynchronous Receiver and Transmitter (UART) is a device that takes input data and fires it out of a single I/O pin at high speed. Conversely, it also takes input from a different pin and stores it. Since it only uses a single pin/wire each for transmitting and receiving data, it makes it convenient to use to hook two devices together. Because it is asynchronous, we need to give it some configuration information before hand: How fast will the data be traveling?, What data format should we use?, etc. For Xinu, this is the interface we use to communicate with the Raspberry Pi hardware.

More detail: The Mini-UART is a very stripped down variant of the classic 16550 that is-- ironically-- harder to set up and use than the full UART. It has a very shallow FIFO (only 8 symbols), no DMA support, and apparently no way to acknowledge a transmitter FIFO empty interrupt. ACK methods that have been tried are: clearing the FIFO, reading all registers (in a real 16550, reading IIR should ACK an interrupt), and reading the IIR multiple times. Because of this, if we want to use this device, we must not use interrupts (any calls to printing routines would block until all characters were printed). The Broadcom datasheet also has documentation errors in more than a few areas about this device, making it not very useful. Additionally, not all registers adhere to the 16xx0 format, which prevents us from simply using a different datasheet. Fortunately, it seems that the PL011 is closer to the standard ARM implementation (lacking only some infra-red communication features and curiously: DMA, just like the Mini-UART). This way, we can rely on the ARM datasheet which has much more rigor. See the Datasheets page for a list of relevant documents.

PL011 UART

More detail: The full UART, a PL011 derivative, is actually much easier to work with and better documented than the Mini-UART. Because of this, it's a wonder why the Mini-UART was included in the design at all... Regardless, the PL011 is the way to go for serial I/O. The only peculiarity seen so far is that the transmitter FIFO only seems to hold 8 characters instead of the 16 mentioned in the Broadcom datasheet or the 32 claimed by the ARM datasheet (receiver FIFO untested). Having a shorter FIFO means that the FIFO will be empty more often and thus interrupt the CPU more often, reducing the number of operations the CPU can perform. Despite this hinderance, this UART is the only viable option we have at the moment so it's what we'll use.

SP804 Timer

While the SoC does provide a variant of the ARM SP804 timer, this unfortunately cannot be used for consistent timing. The clock input that timer gets is variable, so when the Pi goes into low power mode and clocks more slowly, the timer will also run more slowly. The datasheet's official reccomendation for timing-sensitive code, on page 196, is to use the system timer.

System Timer

More detail: The system timer is a very simple 64bit free-running timer that generates interrupts when the lower 32bits match one of its 4 comparison registers. While the datasheet doesn't mention it, David Welch discovered that the comparison registers 0 and 2 are in use by the GPU and are not free for ARM usage. Additionally, the IRQs for these comparison events were omitted from the IRQ table on page 113 but are actually IRQs 1 and 3 for compare match 1 and 3 respectively (the two comparison match registers we're free to use). It seems that we're stuck between a rock and a hard place, because the GPU could probably change its usage of the timer comparison registers after a revision to its firmware, but on the other hand the SP804 won't have consistent timing which makes it much less useful as the timer for Xinu. Despite the possible pitfalls, it seems that using the system timer is the better option since we can just use a known-good GPU binary.

The because the system timer is free running and only interrupts when a comparison matches, the IRQ handler must:

  1. Read the current value of the compare register
  2. Increment that by an interval value which determines how frequently we interrupt (like the LOAD register on a more conventional timer)
  3. Write that incremented value back to the compare register

This way, we can convert this timer setup to behave more like a conventional timer that goes off once every N timer clock cycles. It's also worthy of note that the input clock to this timer seems to be 1Mhz (as determined from experimentation and from a Baking Pi page).

Interrupt Controller

More detail: The SoC does not actually contain a vectored interrupt controller. Because of this, our IRQ routine must read both of the IRQ pending registers to determine which device triggered the IRQ. Page 113 of the SoC datasheet has a complete table which lists the bit numbers for each device that can cause an IRQ. Some devices like the Mini-UART will need extra checks since all AUX interrupts (which can be generated by the Mini-UART or either aux. SPI controller) trigger the AUX IRQ bit.

Examples

There is some rather messy example code that demonstrates the use of the System Timer, PL011, and the Interrupt Controller simultaneously here.

Clone this wiki locally