Orchard Firmware Developer's Guide

From Studio Kousagi Wiki
Revision as of 08:56, 6 June 2015 by Bunnie (talk | contribs) (Serial Console)
Jump to: navigation, search

Setting up your Environment

For the sake of convenience, Orchard firmware development, including compilation, flashing, and manufacturing provisioning is all done with a single tool: A Raspberry Pi.

Recommended tools:

  • Raspberry Pi 2 Model B with 8 GB microSD card
  • Firmware image: Raspberry pi Orchard development SD disk image
  • A Linux machine to write the initial firmware image out
  • Jumper cables, 5-wire (for SWD) and 2-wire (for UART), going between male 0.1" headers.

Hardware Setup

1. Download the RPi firmware image (~2GiB).

2. Image the downloaded firmware onto an SD card using the following Linux command: (This may take about 10 minutes)

zcat orchard-pi-dev.img.gz | sudo dd of=/dev/sdX bs=1M   # for SD cards mounted via USB adapters

or

zcat orchard-pi-dev.img.gz | sudo dd of=/dev/mmcblkX bs=1M  # for machines with directly accessible SD cards

Note that /dev/sdX is the device node of the SD card as mounted on your Linux system. If you don't know what we're talking about, be careful, because if you pick the wrong /dev/sdX node you'll end up destroying your local system boot disk.

3. Insert card into RPi and boot.

4. Figure out the IP address of the RPi and ssh into it. This is probably the hardest step.

  • Here's a guide to figuring out your IP address
  • One method is to access the local router configuration page and query the DHCP client list and look for the rasperrypi client. This works if you're in a home environment where you have access to the local DHCP server
  • If you don't have access to that, you can plug in an HDMI monitor and keyboard and find the IP address using ifconfig
  • A final method is to use nmap to scan for the IP address
  • Another solution is to use zeroconf. The image provided runs zeroconf and if your local system supports that. Linux users should install avahi-dnsconfd. Windows users will need to install bonjour printing services. If you have zeroconf, you should be able to run
ssh pi@raspberrypi.local 

And the system will automatically find it (assuming it's the only Pi on the network, additional Pi's get a hyphenated numeral after the raspberrypi name)

Either way, the credentials are "pi" and "raspberry" for the username and password.

5. Wire up your raspberry pi to the orchard dev board.

Be careful to not use the two pins closest to the outside corner of the board. They have 5V on them and will damage orchard if you wire it up to its signal pins.

rpi-orchard-connection.gif

If you're unsure, here's some photos of the orchard-raspberry pi connection.

6. Plug power into the orchard dev board via the microUSB cable.

Firmware Development

Build your firmware image

1. Update your source code

cd orchard-src
git pull

2. Build an image

cd ~/orchard-src/orchard
make -j4

Note: If you're using an alternate keyboard or board version from the default Makefile (currently BC1/EVT1B), specify the version as follows in the environment variable:

make -j4 KEY_LAYOUT=BM BOARD_REV=EVT1

Connect OpenOCD

Start openocd, the connection between the Rpi host and the Orchard target CPU:

sudo openocd -f sysfsgpio-rpi.cfg &

If you've wired up the boards correctly, you should see this somewhere in the log:

Info : SWD IDCODE 0x0bc11477

The correct IDCODE means we can see the CPU over the SWD debug port.

First-time Provisioning

On a blank device (factory new or mass-erased), you will need to provision an initial firmware image.

The Kinetis W repeatedly stabs itself in the eye when the Flash is empty, and will not work with GDB until it has something valid to run. The current procedure involves using a telnet protocol to run openOCD commands directly to provision an initial image:

telnet localhost 4444

Once in the telnet session, use these commands:

reset halt
program build/orchard.elf
reset

GDB Debugging

Once the initial image is provisioned, you can connect via gdb.

gdb build/orchard.elf
target remote localhost:3333

From this point, you can use all your usual gdb commands: breakpoints, running, source code listing, disassembly, variable inspection, single-stepping, etc. etc. For a list of common gdb commands, see Orchard gdb cheatsheet

If when you try to connect via GDB, you see this message:

(gdb) target remote localhost:3333
Remote debugging using localhost:3333
Info : accepting 'gdb' connection on tcp/3333
Warn : Cannot communicate... target not halted.
Error: auto_probe failed

You need to halt the CPU manually. You can do this by doing

telnet localhost 4444
reset halt
exit

And then running the gdb command again.

Serial Console

To talk to the serial console, open another window (perhaps via a second ssh session) and type this command:

screen /dev/ttyAMA0 115200

You should see a "ch>" prompt if you hit enter.

To exit the screen session, type cntrl-A \ y

Code Documentation

Overview

Orchard runs ChibiOS 3, a lightweight multi-threaded RTOS.

For those who have not done multi-threaded programming, the key thing to pay attention to are the locks on resources, to prevent contention that can lead to hardware lock-ups and hanging. As you write code, think to yourself, "if someone else interrupted me in the middle of this, would that be bad?" If the answer is yes, you should consider using a lock on a resource.

The base firmware as of June 2015 runs the following threads:

  • main
  • idle
  • charger watchdog
  • LED effects
  • shell
  • orchard app

3792 bytes of heap are free, out of 16k total RAM, and 80k of ROM is used (this includes -f debug-all-the-things).

Threads

The main thread is the first thread run by chibiOS. Eventually, this thread is responsible for managing and launching the apps. It has a 768 byte stack area.

The charger watchdog is responsible for pinging the BQ24260 charger when in boost or charger mode. The charger will turn off charging or boosting if it thinks the MCU has crashed, as a matter of safety. The thread is always running, and the behavior to ping the the charger is configured via a static variable called the "chargerIntent". The idea being that another thread will communicate the intent of the system to the thread, and the next time it's scheduled to run the charger thread will actually implement that intent. This thread has a 192 byte stack area.

The LED effects thread is responsible for rendering the WS2812B frame buffer (16 pixels total on the burning man edition), compositing with the UI cues, and running the bit-bang protocol to send data to the LEDs. The bit-bang protocol is a hand-coded assembly routine (see ws281b_ll.s), tuned via clock counting to create the correct waveforms. Since it's timing sensitive, the actual LED update locks the entire system for the duration of the update. The LED thread also has a one-way mode to turn off the LEDs for system shutdown (if you don't render all off-state to the LEDs, they'll stay on). This thread has a 256 byte stack area.

The shell thread launches shell commands. These are commands entered via the serial debug port. Shell commands are typically used for debugging and configuration. More on this later. The shell thread has a 768 byte stack.

The orchard app thread launches UI apps. These are applets that use the OLED and typically respond to touch surface events for interaction. Apps are the actual user-visible front ends to orchard functionality. The app thread has a 2048 byte stack.

The idle thread runs when nothing else is scheduled to be run. See chibiOS thread documentation.

Conventions

  • Functions that are globally visible have names in camelCase.
  • Functions that are static to a file use_underscores_between_words.
  • All low level drivers have a Start and Stop function (e.g., accelStart(), accelStop())
  • Some drivers also have an "init" call. The function of this is inconsistent.

Gotchas

Be aware when you're executing in an interrupt context. Typically callbacks and handlers run in an interrupt context. This includes:

  • Timer callbacks
  • interrupt handlers
  • ADC callbacks

When you're in an interrupt context, you can't run operations that can potentially cause another interrupt to happen, which turns out to be most interesting things.

Typically to get around this, handlers issue events which are queued into a thread, and handled in a normal, interruptable context.

Eventing system

One fun feature of ChibiOS is a many-to-many event system. Events are represented by an object of type event_source_t. Objects must be initialized once using chEvtObjectInit().

Events can then be hooked and unhooked using the call evtTableHook() and evtTableUnhook(). You can assign multiple hooks to a single event, thus allowing one event to drive behaviors in multiple threads.

An event is broadcast to the system using chEvtBroadCast(), or alternatively, chEvtBroadCastI() when you're running in an interrupt context. chEvtBroadCastI must be flanked by chSysLockFromISR() and chSysUnlockFromISR() on either side.

An example of a fairly complex event system is the USB charge detection loop. The purpose of this loop is to periodically measure the D+/D- pins on the USB interface to see if we've been plugged into a charger.

Most of the code is in orchard-app.c. It starts with a timer, chargecheck_timer, that runs a callback run_chargecheck:

chVTReset(&chargecheck_timer);
chVTSet(&chargecheck_timer, MS2ST(CHARGECHECK_INTERVAL), run_chargecheck, NULL); 

run_chargecheck itself is in an interrupt context, so it can't directly query the ADC -- the ADC is interrupt-driven. So all it can do is issue an event, and then reschedule itself to run again:

static void run_chargecheck(void *arg) {
  (void)arg;
  chSysLockFromISR();
  chEvtBroadcastI(&chargecheck_timeout);
  chVTSetI(&chargecheck_timer, MS2ST(CHARGECHECK_INTERVAL), run_chargecheck, NULL);
  chSysUnlockFromISR();
}

chargechcek_timeout is an event that's setup as follows:

 chEvtObjectInit(&chargecheck_timeout);
 evtTableHook(orchard_events, chargecheck_timeout, handle_chargecheck_timeout);

The code for the event handler is simple, it just kicks off a call to update the USB status:

static void handle_chargecheck_timeout(eventid_t id) {
  (void)id;
  // this kicks off an asynchronous ADC request that results in a usbdet_rdy event 
  analogUpdateUsbStatus();
}