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 ZEROThe 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>