-
Notifications
You must be signed in to change notification settings - Fork 32
Devices and Peripherals
Note: the code covered by this page is under development and may change unexpectedly
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.
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):
By including the system/platforms/raspberry-pi/gpio.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.
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:
- Set the appropriate alternate function of the desired GPIO pin(s)
- Set the pull-up/pull-down resistor(s) for the desired GPIO pin(s)
- Set up and enable the hardware
For instance, a Mini-UART initialization routine would do the following:
- Set pin 14 to Alternate Function 5 to enable the TX pin
- Set pin 15 to Alternate Function 5 to enable the RX pin
- Turn off pull-up/pull-down resistors for the TX and RX pins
- Set up all of the Mini-UART's control registers and enable it
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.
More detail: The full UART, a PL011 derivative, is actually much easier to work with and better documented than the Mini-UART. 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. Ultimately, it's still the best UART available on this system so we'll use it as our serial device.
One interesting property of this UART is that if you disable the device while it is busy, the busy flag will stay set. Xinu can actually transmit with kprintf()
before the serial port is properly initialized (the GPU initializes the PL011 for us as we specify in config.txt
), which means that the UART can be in the middle of transmitting when we enter the initialization code. Thus, one should be careful to wait for transmission to stop before disabling the UART. Otherwise, we'll get a deadlock as the CPU waits for the UART to become un-busy and the UART waits for the CPU to enable it so it can finish transmitting.
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.
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:
- Read the current value of the compare register
- Increment that by an interval value which determines how frequently we interrupt (this value is like the LOAD register on a more conventional timer)
- 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).
One important consequence of this kind of operation is that if the timer ISR takes too long (or the interval value is too small), the compare value will "fall behind" the current timer value and the timer won't go off anymore. (it may go off again when the timer value rolls over, but that takes a while)
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.
The IRQ routine snippet on page 111 of the datasheet mentions a possibility of race conditions when determining interrupt sources. Additionally, there is mention of these quirks in the Linux kernel's source code. On the surface, this appears to only effect devices that use the basic IRQ pending register, not the UARTs or timers.
There is some rather messy example code that demonstrates the use of the System Timer, PL011, and the Interrupt Controller simultaneously here.