ARM firmware

From ExtraTerrestrik

Contents

[hide]

LPC2148

The LPC2148 User Manual describes the periphery of the chip, and how to program for it. We use the following chip resources:

  • 512kByte ROM for the code
  • 64k Byte SRAM for data
  • 8kByte USB DMA RAM for most USB I/O
  • PLL0 to get from the external 12MHz clock to 60MHz CPU/PCLK clock
  • PLL1 to provide 48MHz to the USB interface
  • UART0 as a flashing and debugging port
  • JTAG for flashing and debugging, with open boards only
  • USB to talk to a PC
  • SPI0 to talk to
    • 1MByte external flash
    • µSD card
    • a plugin on the power board, e.g.,
      • an enc28j60 ethernet chip
      • an LCD display driver
      • a stepper motor driver
  • SSP/SPI1 to talk to the Altera
  • Timer1 to supply an auxillary clock to the Altera (unused)
  • Timer0 to capture a S/E attn signal from the Altera (unused)
  • GPIO to select SPI0 targets and to boot the Altera
  • DAC uses include
    • biasing a temperature sensor
    • controlling the power load of the sirena
  • ADCs are used for housekeeping.
    • Frontend power voltages and currents
    • Detector bias voltages and currents
    • Input power voltage and current.
    • Temperatures.
  • RTC to provide wall clock time and 1 second cron/housekeeping
  • VIC for USB, UART0, and SSP interrupts.

Toolchain

We use a typical gnuarm toolchain, arm-none-eabi, with newlib.

binutils-2.21/configure --prefix=/usr/local/gnuarm --target=arm-none-eabi --disable-interwork --disable-multilib --disable-nls --disable-werror
gcc-4.3.5/configure --prefix=/usr/local/gnuarm --target=arm-none-eabi --enable-interwork --disable-multilib --disable-nls --enable-languages=c --without-headers --disable-shared --disable-libssp --disable-hardfloat
newlib-1.19.0/configure --prefix=/usr/local/gnuarm --target=arm-none-eabi --enable-interwork --disable-multilib --disable-nls --disable-newlib-io-float
gcc-4.3.5/configure --prefix=/usr/local/gnuarm --target=arm-none-eabi --enable-interwork --disable-multilib --disable-nls --enable-languages=c --disable-shared --disable-libssp --disable-hardfloat --with-newlib --with-headers=/usr/local/gnuarm/arm-none-eabi/include
gdb/configure --target=arm-none-eabi --prefix=/usr/local/gnuarm --disable-interwork --disable-multilib

Flashing the arm rom vi RS232 requires the program lpc21isp.

Runtime

A small assembly stub called crt.s is linked at the beginning of the code image, to initialize the stacks and the RAM. It then calls the function main() without arguments.

The linker script is modified from something found on the internet, a rather simple affair. It links teh section startup first, which is the section where crt.s puts the startup code.

main()

The file irena.c provides a function irena_init() which initializes most hardware according to the board configuration. The function main() in the same file first calls this initialization, and then the main_loop().

main_loop()

The file mainloop.c is the main user code. The entry is the function main_loop(). This function initializes some periphery according to the typical use, and then enters an inifinte loop for the foreground execution thread.

Execution threads

The firmware executes one foreground thread and several interrupt threads.

Foreground

The function main_loop() runs an infinite loop, to

  • look for, and execute commands received via UART0 and USB,
  • stream memory blocks to USB DMA engine,
  • stream data blocks from the DMA engine to SPI target(s),
  • test for a tick from the RTC and do, once a second
    • execute the cron script
    • feed housekeeping data into the Altera data stream

Each executed command returns one response message (max 64 bytes), which is sent back to the source of the command. A command can execute a script.

The command

  • sleep EXPRESSION

executes part of the main loop, basically omiting the first item: it does not look for commands, until the specified number of RTC ticks happened.

SPI

All IO to the SPI is done in the foreground. Interrupts do not help much, since the hardware has no FIFO, and at full speed the CPU is kept pretty busy feeding bytes to the interface. There are hooks in the code to implement asynchronous IO to the SPI, but that has never been used. SPI IO include

  • IO to the flash chip, during command execution,
  • IO to the uSD card, for streaming in and out, and by commands,
  • IO to a plugin, i.e. streaming to the ethernet chip.

Commands

Commands are ASCII text messages, formated as command name and parameters. Command names (and other keywords) can be abreviated, the first match hits, even if it is not unique.

Command names are hierachical, separated with slash and/or spaces. Subsystems define command menus, which are linked into the toplevel command menu.

Command resonses are ASCII text messages, up to 64 bytes. They should start with a tree-digit resonse code. Codes greater or equal 500 are considered error codes. A single char will be prepended to each message before it is returned to the host. Currently only two cases are implemented: An m is prepended when unread messages are pending, or a dot when not.

Messages

For asynchronous messages the firmware provides a message ring that can store up to 32 messages of up to 64 byte length. Messages are submitted to the message ring with a priority. Two global verbosity levels decide on the fate of submitted messages. Priorities above the value of the verbosity variable are discarded. Messages with priority up to the value of the variable uart_verbosity are send straight to the RS232, blocking the called, with interrupts disabled. This is normally reserved for priority zero: fatal. Other priorities are error, warning, info, debug, debug1, ...

The message command returns the next unread message from the ring. The return code indicated the number of remaining unread messages.

Variables

The firmware exports a set of unsigned int variables to be used by the expression parser for command parameters. Some of these are direct hardware registers, some provide status of subsustems, some control functions in subsystems. Mots numerical parameters accept expressions of constants and variables. Variable names can be abbreviated just as command names. The list of variables is defined in mainloop.c.

Single uppercase letters are names 26 general variables. Digits and uppercase letters preceded by a $ character are script parameters and local variables in scripts.

Scripts

A command may execute scripts read from the FAT filesystem on the flash chip. The command

  • script/execute "FILENAME"

is provided to open a file and execute the script inside.

A command must fit on a line of at most 63 characters. Multiple commands may be separated by ;. A command may be preceded by an @ character to suppress its response message.

Limited support is provided for conditionals, one-line loops, backward goto.

  • script/if EXPRESSION: command(s)
  • script/while EXPRESSION: command(s)
  • script/until EXPRESSION: command(s)
  • script/for EXPRESSION: command(s)
  • script/label EXPRESSION
  • script/goto EXPRESSION
  • script/exit

Scripts deliver command responses to the message ring. Four variables define the priorities of these messages:

  • script_error_prio: error responses,
  • script_prio: normal command response,
  • script_at_prio: response from commands preceded by an @ character,
  • script_cond_prio: response from commands in loop or conditionals.
  • script_cron_prio: response from cron script commands.

The firmware can open eight files simultaneously. This limits the nesting of scripts. This includes files opened for other purposes. The filesystem is read-only.

Cron scripts

The command

  • script/cron "FILENAME"

registers a cron script, or unregisters a script when the FILENAME is omitted. The file is looked up and opened, and the open file descriptor is registered. At each RTC tick, the mainloop seeks to be beginning of the file and executes the script.

Interrupts

UART0

The RS232 interface interrupts collect command lines from the UART, and submit complete lines to the mainloop. The response will be sent back through the RS232 to the host.

SSP

The SSP interface to the Altera is driven by a complex state machine run in interrupt context, which feed 16-bit words to the SSP fifo, and collects the resonse frames, which are optionally collected in 512 Byte pages of the USB DMA state machine.

The SSP interrupt disables itself when no work is pending, but the main_loop is kicking it back on in every loop. Any submission of work also kicks it on.

USB

The USB has two interrupt driven engines, the DMA and the protocol.

Control and enumaration

The the USB enumaration protocol is not using DMA. The interrupts respond to control requests. The box offers a single configuration wioth a single interface. This interface provides two pairs of endpoints.

One pair accepts up to 64-byte commands and returns 64-byte responses. The other pair is used to send 512-byte data blocks streamed from the Altera or some memory (flash, uSD). The OUT endpoint can receive 512-byte data blocks as parameters for user commands. All four endpoints are driven by the DMA controller.

The vendor:product code is ffff:ee...

DMA

The DMA memory is devided into a set of descriptors and buffers

  • 0x0000: 128 byte, 32 dma descriptor pointers
  • 0x0080: 64 byte, command buffer
  • 0x00c0: 64 byte, command response buffer
  • 0x0100: 256 bytes, 16 device descriptors
  • 0x0200: 512 bytes, parameter block buffer
  • 0x0400: 7168 bytes, 14 stream block buffers

Three endpoints each have a single buffer assigned, and are serviced by relatively simple interrupt service routines.

The block stream endpoint is driven by a more complex engine. This machine hands out blocks on request by a data source, and queues them up for transmission when the data source returns them filled with data. The transmission can happen though the USB DMA, or via the main_loop to some SPI target, or both.

The data source can be the SSP interrupt or the stream engine of the main_loop.

Mass Storage

The plan in the back of my head, deep down in the stack of other things to do, is to implement a second interface in the USB configuration, that provides a mass-storage interface. This would execute without DMA and provide access to the 1Mbyte flash chip. The actual access to the flash would need to be done in the foreground thread. The USB IO in the interrupt context. The host could then read and write files to the flash filesytem.

Altera

There are two interfaces between the ARM and the Altera. The FPGA is configured by the ARM in PS (passive serial) mode via ARM GPIO single ended CMOS signaling. The runtime interface is based on LVDS signaling via SPI1/SSP. The SSP operate on three wires, clock/in/out. A fourth signal is not used in current versions.

The protocol employs the ARM SSP hardware to send 16-bit frames at 30 Mbits per second. The ARM sends 16- or 32-bit messages to the FPGA. The FPGA returns any responses whenever they become available in any 16-bit frame that follows a recongnized message frame. When no data is available it sends zeros, or some other configurable value.

Message format

A message starts with a 1 bit, followed by a parameter flag, and 14 address bits, MSB first. If the parameter flag is 1, the next frame is considered a 16-bit message parameter to complete a 32-bit message. Else, the message goes without paramater. 32 consecutive zeros are guaranteed to resynchronize the frames.

Synchronous response

Most messages are expected to return a single 16-bit response. The nominal sequence of frames to synchronously read such a responce is

ADDR [PARA] NOOP ZERO
The bits returned during the ZERO

frame are the response. NOOP is command code 0x8001. ZERO is 0x0000, i.e., no message.

A frame following a ZERO frame will not return data, but zeros, because response data is loaded into the shift register at the end of an ADDR od PARA frame only.

Telemetry streaming

A second mode of communication is meant for telemetry streaming. The ARM sends a stream of RFIFO commands, which pops one or more words from telemetry FIFOs into the FPGA resonse FIFO. The ARM shall filter out long streams of zeros from the resonse frames and forward the rest to the USB DMA machine. The RFIFO message is 0x8000. This message is guaranteed to maintain frame synchronisaztion forever.

SSP engine

The ARM firmware includes an interrupt driven engine to feed the SSP. This engine can be in sevaral modes or states. The engine can be fed a message (string of frames) to be sent. When the message is exausted, a finite or infinite stream of idle-frames will be sent, if the idle-frame is set, or nothing further is sent. When the SSP-fifo runs empty, interrupts will be disbaled, until more work is submittet.

The responses are stored in a buffer which needs to be registered with the engine. If the buffer runs out, the engine tries to get a new one. In DMA streaming mode, a new block buffer is requested from the USB DMA machine. If none is available, the engine blocks until a new buffer becomes available. If not in streaming mode, an internal scratch buffer is used when the user-supplied buffer runs out.

A command message to the FPGA can be submitted to the SSP engine while in streaming mode. The message will be injected between the streaming idle words. The FPGA is better configured to not respond to these commands, else those responses will get mixed into the telemetry.

In telemetry streaming mode, idle compression can be turned on. The compression replaces all sequences of idle response frames into two word, the idle response followed by a repeat count. When the telemetry contains single idle response wird, they will actually expand to two words.

Commands

Whatever is not obvious, nor documented here, is better looked up in the firmware source code. The numbers in front of the command names are their nominal return code.

  • 199 echo <arbitrary text>
  • 100 message
  • 197 sleep [<seconds>=1]
  • 198 clock [ YYYY-MM-DD [ HH:MM[:SS] ]]

variable

  • 308 var/print <expression>
  • 301 var/set <variable> [ = <expression> ]
  • 300 var/shift

script

  • 220 script/list [ "<dirpath>" ]
  • 240 script/execute "<filepath>"
  • 243 script/exit
  • 241 script/label <expression>
  • 242 script/goto <expression>
  • 246 script/while <expression>: <command> [; <commands>]
  • 247 script/until <expression>: <command> [; <commands>]
  • 244 script/if <expression>: <command> [; <commands>]
  • 245 script/for <expression>: <command> [; <commands>]
  • 230 script/mount

mount reads the superblock from the filesystem. list return the first filename from the directory, or the next one if no directory is given.

flash

  • 231 flash/id
  • 232 flash/status
  • 233 flash/read <block>
  • 234 flash/write <block> [ erase(default) | noerase ]
  • 238 flash/dump [<offset>]
  • 235 flash/erase <sector>
  • 236 flash/message <block>

The flash subsystem provides a 512 byte flash_buffer, which is (ab)used by a lot of other subsystems for all kinds of purposes. E.g., the flash/message command copies parts of the message ring to the flashbuffer, which can be used by a script to write the message ring to the uSD card. flash/dump return 16 hex bytes of the flash_buffer. flash/read and flash/write transfer the flash_buffer to or from a pair of sectors on the flash chip.

memory

  • 200 memory/peek <address>
  • 201 memory/poke <address> <value>

Very dangerous! Can access hardware registers as well.

altera

  • 255 altera/send <command> [<nread>]
  • 255 altera/register <address> [<value>]
  • 251 altera/reset
  • 252 altera/file "<filename>"
  • 253 altera/flashbuf
  • 254 altera/usbblock
  • 250 altera/status
  • 256 altera/sync "<word>"
  • 259 altera/inject <command>
  • 258 altera/idle [ <word> [<mic>] ]

reset, status, file, usbblock, flashbuf drive the configuration interface.

sync, idle, inject, send, register send messages to the FPGA in increasing fashion of sophistication. sync syncronously sends a single frame, idle send a stream of idle-frames, inject sends a one or two frame message, also while streaming, send send a message syncronously including <nread> responce frames, and register does a three/four frame FPGA register read/write.

altera/stream

  • 257 altera/stream <flags> [<syncword>]
    • /off 0
    • /usb 1
    • /spi 2
    • /sdcard 0x10002
    • /ethernet 0x20002
    • /hk 0x0010
    • /fifos 0x00e0
    • /f1 0x0020
    • /f2 0x0040
    • /f3 0x0080
    • /events 0x0020
    • /samples 0x0040
    • /direna 0x0060

altera/stream turns on/off telemetry streaming. The hex number in the list are the numerical values of the flags. The flags, in any combination and sequence, mean:

    • off: turn streaming of
    • usb: send buffer to usb host
    • spi: send buffers to the main_loop for processing
    • sdcard: (implies spi) stream to uSD card
    • ethernet: (implies spi) stream to ethernet
    • hk: enable the FPGA housekeeping fifo
    • fifos: /f1/f2/f3
    • f1: enable FPGA fifo 1 (2, 3)
    • events: /f1 (direna event fifo)
    • samples: /f2 (direna samples fifo)
    • direna: /f1/f2
  • 258 altera/dmastatus

altera/fifos

  • 255 altera/fifos/reset [<value>] 0x8007
  • 255 altera/fifos/status [<value>] 0x8005
  • 255 altera/fifos/errors [<value>] 0x8006
  • 255 altera/fifos/control [<value>] 0x800b
  • 255 altera/fifos/set [<value>] 0x800a
  • 255 altera/fifos/clear [<value>] 0x8009
  • 255 altera/fifos/idleword [<value>] 0x8002
  • 255 altera/fifos/clockl [<value>] 0x800c
  • 255 altera/fifos/clockh [<value>] 0x800f
  • 255 altera/fifos/hksize [<value>] 0x8018
  • 255 altera/fifos/hkmask [<value>] 0x8019
  • 255 altera/fifos/hkhval [<value>] 0x801a
  • 255 altera/fifos/hkstat [<value>] 0x8010
  • 255 altera/fifos/f1stat [<value>] 0x8011
  • 255 altera/fifos/f2sta [<value>]t 0x8012
  • 255 altera/fifos/f3stat [<value>] 0x8013

These are convenient synonyms for altera/register REG, for access to the registers in the standard FPGA interface code. REG is the register address given in the hex values in the list above.

reset

The reset register is write only, sending 16 reset signals into the FPGA core.

  • 0x0008: clock reset
  • 0x00f0: fifo resets f3/f2/f1/hk
  • 0x0100: spi reset
  • 0x0200: mconf reset
status

Read only status bits.

  • 0x0001: error summary
  • 0x00f0: fifos full, f3/f2/f1/hk
  • 0x0f00: fifos empty, f3/f2/f1/hk
  • 0xf000: fifos have a packet, f3/f2/f1/hk
errors

Latching status/error bits. Writing 1s clears bits.

  • 0x0001: spi_overflow
  • 0x00f0: fifos were full, f3/f2/f1/hk
  • 0x0f00: fifos were empty, f3/f2/f1/hk
  • 0xf000: fifos had a packet, f3/f2/f1/hk

Some status and/or error bits may be reassigned to application specific purposes in later versions. Please check the verilog code.

config

control, set, clear write all/ones/zeros to the mconf control bits.

  • 0x0008: clock enable
  • 0x00f0: fifos enable, f3/f2/f1/hk
  • 0x0100: fifos to ssp disable (clear for streaming)
  • 0x0200: frontend to ssp disable (set for streaming)
  • 0x0400: core to ssp disable (set for streaming)
clock

A 32-bit counter counts ticks (defined by the core). The clock occupies four register addresses. One address bits selects the lower/upper part of the counter, and one bit controls latching. The aliases are defined so that when the lower part is read/written first, the counter is affected atomically.

idleword

This register defines the frame that the SSP return when there is no data available. Normally zero.

fifos

The FPGA frontend provides four fifos for telemetry packets. The fifo each have an associated packet size, header mask, and header value. For telemetry streaming, Each of these fifos delivers data in packets to the SSP FIFO. The highest priority fifo that has a complete packet, i.e., at least as many words as its defined packet size, will transfer as many words to the SSP FIFO. When the header mask is not zero, any words that do not match the masked value will be sent, but do not count towards the packet size. This may help to resynchronize to packet boundaries.

During telemetry streaming, the ARM may want to inject packets of its own into the packet stream. But it does not know about packet boundaries. That's what the houskeeping fifo in the FPGA is used for. The ARM can write a packet into that fifo, which will be injected at highest priority into the stream when it is complete.

The hksize register defines the size of a packet in the housekeeping fifo. The provided number is one less than the number or words. hkmask defines which bits in the first word of a packet are to be compared with the hkhval, to check for synchronisation.

The other three fifos are controlled by the core, which may define constants for size, mask, and/or value, or may provide further registers.

hkstat return the housekeeping fifo status. A Write to that address goes into the housekeeping fifo. f1stat returns the status of fifo 1 (2, 3).

  • 0x0800: fifo full
  • 0x0fff: number of words in the fifo
  • 0x1000: a packet is available
  • 0x2000: fifo is busy sending a packet
  • 0x4000: fifo is empty
  • 0x8000: fifo is too full to accept another packet.

direna

  • 301 direna/reset <flags> [<expression>]
    • /fifos 0x00f00000
    • /clock 0x80080000
    • /run 0xff
    • /spi 0x01000000
    • /all 0xffffffff
    • /frontend 0x11110000
  • 302 direna/initialize
  • 303 direna/enable <flags>
    • /off 0
    • /usb 1
    • /spi 2
    • /sdcard 0x10002
    • /ethernet 0x20002
    • /hk 0x0010
    • /fifos 0x00e0
    • /f1 0x0020
    • /f2 0x0040
    • /f3 0x0080
    • /events 0x0020
    • /samples 0x0040
    • /direna 0x0060
  • 304 direna/register <address> <value>
  • 309 direna/nsamples <samples>
  • 305 direna/fifos <flags>
    • /disable 0x00004001
    • /enable 0x00004002
    • /clock 0x00080000
    • /housekeeping 0x00100000
    • /events 0x00200000
    • /samples 0x00400000
    • /readback 0x06000000
    • /off 0x00084003
    • /on 0x06784003
  • 300 direna/status
  • 310 direna/pulse/send <channel>
  • 311 direna/pulse/set <index> <timeindex> <acoeff> <bcoeff>
  • 306 direna/adcmask <mask>
  • 308 direna/threshold <channel> <threshold>
  • 307 direna/triggermask <mask>

usb

  • 261 usb/discard
  • 262 usb/bcopy

discard and bcopy drop a data block that came in from the USB, bcopy copies it to the flash_buffer first.

usb/stream

  • 263 usb/stream/status
  • 264 usb/stream/sdcard <sector> <count>
  • 265 usb/stream/flash <sector> <count>
  • 266 usb/stream/memory <address> <bytes>
  • 269 usb/stream/stop

These commands feed data into the USB DMA engine.

sdcard

  • 260 sdcard/status
  • 261 sdcard/init
  • 263 sdcard/read <sector>
  • 264 sdcard/write <sector>
  • 266 sdcard/cid
  • 265 sdcard/csd
  • 267 sdcard/copy_from_flash <sector> [<flashsector>=0 [<count>=0x800]]
  • 237 sdcard/copy_to_flash <sector> [<flashsector>=0 [<count>=0x800]]
  • 262 sdcard/erase <first> <last>
  • 268 <code>sdcard/findfree [ <first>=0 [ <last>=<size> [ <test>=0xffff0000 ]]]

Most of these use the flash_buffer to move around blocks of data.

After sdcard/init, which is called by the firmware automatically at boot, a call to sdcard/csd reads the size of the SD card to the variable sd_size in units of blocks. The command sdcard/findfree performs a binary search for the first free block on the card, the number of which is saved in the variable sd_free. Those variables can now be used to setup data streaming, appending into the free space on the card.

ethernet

  • 270 ethernet/initialize [ [full_]duplex | half_duplex | LED ]
  • 271 ethernet/send
  • 272 ethernet/rxen [ enable(default) | disable ]
  • 273 ethernet/source/mac <hh:hh:hh:hh:hh:hh>
  • 273 ethernet/source/ip <iii.iii.iii.iii>
  • 273 ethernet/source/port <port>
  • 274 ethernet/destination/mac <hh:hh:hh:hh:hh:hh>
  • 274 ethernet/destination/ip <iii.iii.iii.iii>
  • 274 ethernet/destination/port <port>