The Phōs project

From Spivey's Corner
Jump to: navigation, search

Phōs is a very simple embedded OS that is used for teaching the first year course on Digital Systems. Unlike most other embedded operating systems, it is based on the single synchronisation primitive of synchronous message passing, with all interrupts converted to messages.

The project repository can be found at https://bitbucket.org/Spivey/phos.

At present, Phōs is specific to the BBC micro:bit, and has drivers for some of the peripheral devices on that board, including the timers and serial port. Drivers for other simple devices, such as the die thermometer and the random number generator, make good exercises for the labs associated with the course. Potential projects include:

  • Write device drivers for other devices on the micro:bit.
    • The I2C interface [Done], and the accelerometer [Done, both kinds] and magnetometer attached to it.
    • An attached display device, driven over I2C or SPI. Perhaps a 16x2 lcd with attached microchip port expander (really needs I2C level shifter), a 128x64 lcd with AVR driver, or a fancy SPI-driven TFT.
    • The on-board packet radio interface in Nordic proprietary mode. (Bluetooth Low Energy mode requires a closed-source driver called a 'SoftDevice' that we probably don't want to tangle with. But there may be open-source alternatives?)
      • The micro:bit samples contain simple programs for transmitting and receiving packets, a good place to start for incrementally replicating the functions.
    • A bit-banging implementation of the protocol for WS2812 'neopixels' [Done, using assembly language with interrupts disabled]. An interrupt-driven version using the SPI hardware would be a demanding test for lightweight high-priority interrupts, but is probably beyond what can be done.
    • We could see whether there are components of the MyNewt project that could be re-purposed.
  • Improve the API for Phōs. Desirable changes include:
    • Delegating all management of the NVIC to the operating system and OS-provided subroutines. Note that we don't really need priorities, so can simplify at the same time. [Done]
    • Providing for timeouts on hardware operations [Done, via general version of receive with timeout]. For example, we might provide a special form of send to be used by a clock process that allows TIMEOUT messages to be sent as if by hardware, so that receive(HARWARE, &m) calls would be completed by a timeout. This is an important pre-requisite for reliable implementation of bus protocols like I2C that may suffer from unpredictable lock-ups.
    • Implementing checks for stack overflow on each context switch.
    • If the number of processes is limited, using a bitmap to represent the set of acceptable processes in a call to receive.
    • Making as much as possible of the kernel interruptible, and allow special-purpose interrupt handlers with high priority.
    • Making interrupt handlers perform only a partial context save, and provide a PendSV handler so that they can request a context switch [Done]. A handler can send a message by modifying the ready queue (with interrupts disabled), then requesting a PendSV interrupt to activate the scheduler.
  • Port Phōs to another board. Top options are boards that are supported by the MBED ecosystem, which provides working code samples (Blinky and Echo) for all supported boards.
    • The Freescale FRDM-KL25Z and FRDM-K64F boards.
    • ST Microelectronics Nucleo boards, like the Nucleo-F091RC (Cortex-M0, 32kB RAM) and a selection of M4-based boards.
    • ARM-based Arduino boards.
    • Adafruit's Cortex-M0 boards, including the M0 Trinket and the Metro M0 Express.
    • Paul Stoffregen's Teensy boards: a non-MBED platform that also supports drag-and-drop programming over USB.
    • It might be best to begin with an MBED board based on a Cortex-M0 processor, and if that goes well, move to an -M3 or -M4 board in the same family. A processor with floating point will require us to save the floating point registers on context switch, so brings with it unattractive complications.
  • Provide debugging tools.
    • Add whatever is needed to make gdb aware of the existence of multiple threads.
    • Enhance panic a bit so it prints the name of the current thread [Done].
  • Vanity projects
    • There's a version of the ppc compiler from the second-year Compilers course that can generate Thumb code. Integrate it with Phōs so client programs can be (partially) written in a Pascal dialect. Provide suitable language support for varying message formats.

(In the list above, [Done] means that Mike has worked on the problem, but usually the result still needs packaging and documenting.)

For development, it will be easiest to take the Phōs source and edit it as necessary. In a later stage of the project, we will want to merge the sources for different processors and boards so that the common parts are shared, isolating the parts that are board-specific, and providing multiple implementations of device driver processes so that Phōs applications are portable between boards.

Other boards don't have the switches and lights of the micro:bit but some, including the FRDM boards, support Arduino-compatible add-on 'shield' boards, and we could easily make a shield that replicates the features of the micro:bit.

A dire warning[edit]

The micro:bit bootloader expects files in Intel hex format (extension .hex). The bootloader on the FRDM-KL25Z expects binary images (extension .bin). If you upload an Intel hex file to the board, it is likely to be interpreted as a binary file and written to the flash directly. Your program will not work! But worse than that, some bits of the image are interpreted as protection bits for the flash memory, and if you are unlucky, you will have bricked the board.

I have succeeded in unbricking at least one board, but after multiple inconclusive experiments, I don't know which method actually did the job. It's worth trying the PyOCD tool with the command pyocd-flashtool -ce to erase the chip. Another apparently-bricked board was resurrected just by uploading a valid binary image.

The latest (May 2014) firmware for the KL25Z apparently tries to avoid locking the board, so it's worth making sure it is installed. You can do so by opening the file mbed.html on the board using a text editor. The file starts with a comment that contains the firmware version.

Project sub-directories[edit]

phos-ubit The initial micro:bit implementation of Phōs.
mini-kl25z A minimal program for the KL25Z that echoes characters on the UART over USB. Ripped from MBED code, then iteratively simplified.
neo-ubit A bit-banging driver for neopixels on the micro:bit.

API for timeouts[edit]

There is a new version of receive that takes a timeout value in milliseconds:

void receive_t(int src, message *msg, int timeout)

If no other message is received before the timeout happens, then a message is delivered with source HARDWARE and type TIMEOUT. The timeout is automatically cancelled on delivery of a genuine message, whether from HARDWARE (i.e., an interrupt) or from a normal process. The source filter src may have any value, not just HARDWARE or ANY; any timeout message will come from HARDWARE, but is delivered even if the receive call specified a different process – this is more useful than the alternative, because it allows us to wait for a limited time for a message from a specific process, ignoring others.

A timer process can periodically invoke a new system call,

void tick(int ms)

where ms is the number of milliseconds since the last invocation of tick. This increments all active timeout clocks by the specified amount, and delivers TIMEOUT messages to any that have gone off, making the corresponding processes ready to run. There is no need for an immediate context switch, since responding to a timeout presumably has a low priority.

Any Phōs program that uses timeouts will need a timer process to trigger them, but programs that do not use timeouts can still be written with no need for a timer process. The guarantees are weak: a receive_t will time out eventually (providing there is a timer process continually calling tick), and it will do so in a time that is at least the one specified. In order to prevent premature timeouts, it will be best to deliver the message on the tick following the one where the timeout value is reached: that way, the message cannot be delivered early because of an initial short tick. We might allow timeout=0 as a special case, meaning to deliver a message if one it waiting, otherwise to deliver an immediate timeout.

Ticks can be relatively infrequent, so it is OK to implement tick() by looping over all processes checking for timeouts. Note that (depending on the device) an interrupt can arrive after the timeout message has been delivered, either before the process has been scheduled, or afterwards while it is dealing with the timeout; in either case, the interrupt will be queued and delivered on the next receive. So it might be necessary to write the device driver carefully, so that unexpected interrupts are ignored.

Handy links[edit]

The simulation of a complex protocol by setting the values on GPIO pins under direct program control, with delay loops for timing. For example, serial communications require timings that are more precise that can be achieved by bit-banging, unless the processor is doing nothing else.

A symbolic representation of the machine code for a program.

(Nested Vector Interrupt Controller). An ARM processor component that is able to assign priorities to external interrupts (as opposed to those generated by internally by the processor) and control the servicing of interrupts. As the name indicates, it is able to cope with nested interrupts, where servicing of one interrupt is itself interrupted by another with higher priority. Note that, according to ARM conventions, higher priorities are indicated by lower numbers.

An alternative instruction encoding for the ARM in which each instruction is encoded in 16 rather than 32 bits. The advantage is compact code, the disadvantage that only a selection of instructions can be encoded, and only the first 8 registers are easily accessible. In Cortex-M microcontrollers, the Thumb encoding is the only one provided.

see Read-only memory.

(Universal Asynchronous Receiver/Transmitter). A peripheral interface that is able to send and receive characters serially, commonly used in the past for communication between a computer and an attached terminal. It is commonly used in duplex mode, with the transmitter of one device connected to the receiver of the other with one wire, and the receiver of the one connected to transmitter of the other with a different wire. The asynchronous part of the name refers to the fact that the transmitter and receiver on each wire do not share a common clock, but rely instead on the signalling protocol and precise timing to achieve synchronisation.