doc: Add internal documentation for AltOS
authorKeith Packard <keithp@keithp.com>
Tue, 23 Nov 2010 06:26:19 +0000 (22:26 -0800)
committerKeith Packard <keithp@keithp.com>
Tue, 23 Nov 2010 06:28:00 +0000 (22:28 -0800)
Signed-off-by: Keith Packard <keithp@keithp.com>
doc/Makefile
doc/altos.xsl [new file with mode: 0644]

index 57300c10f8a9bf931b57ee9ce3c0f3e7d140a180..52934290777b14d59a070decf9d367398caf9eaa 100644 (file)
@@ -2,8 +2,8 @@
 #      http://docbook.sourceforge.net/release/xsl/current/README
 #
 
-HTML=telemetrum-doc.html altosui-doc.html
-PDF=telemetrum-doc.pdf altosui-doc.pdf
+HTML=telemetrum-doc.html altosui-doc.html altos.html
+PDF=telemetrum-doc.pdf altosui-doc.pdf altos.pdf
 DOC=$(HTML) $(PDF)
 HTMLSTYLE=/usr/share/xml/docbook/stylesheet/docbook-xsl/html/docbook.xsl
 FOSTYLE=/usr/share/xml/docbook/stylesheet/docbook-xsl/fo/docbook.xsl
diff --git a/doc/altos.xsl b/doc/altos.xsl
new file mode 100644 (file)
index 0000000..9a88a5b
--- /dev/null
@@ -0,0 +1,1441 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
+  "/usr/share/xml/docbook/schema/dtd/4.5/docbookx.dtd">
+
+<book>
+  <title>AltOS</title>
+  <subtitle>Altos Metrum Operating System</subtitle>
+  <bookinfo>
+    <author>
+      <firstname>Keith</firstname>
+      <surname>Packard</surname>
+    </author>
+    <copyright>
+      <year>2010</year>
+      <holder>Keith Packard</holder>
+    </copyright>
+    <legalnotice>
+      <para>
+        This document is released under the terms of the
+        <ulink url="http://creativecommons.org/licenses/by-sa/3.0/">
+          Creative Commons ShareAlike 3.0
+        </ulink>
+        license.
+      </para>
+    </legalnotice>
+    <revhistory>
+      <revision>
+        <revnumber>0.1</revnumber>
+        <date>22 November 2010</date>
+        <revremark>Initial content</revremark>
+      </revision>
+    </revhistory>
+  </bookinfo>
+  <chapter>
+    <title>Overview</title>
+    <para>
+      AltOS is a operating system built for the 8051-compatible
+      processor found in the TI cc1111 microcontroller. It's designed
+      to be small and easy to program with. The main features are:
+    <itemizedlist>
+      <listitem>
+       <para>Multi-tasking. While the 8051 doesn't provide separate
+       address spaces, it's often easier to write code that operates
+       in separate threads instead of tying everything into one giant
+       event loop.
+       </para>
+      </listitem>
+      <listitem>
+       <para>Non-preemptive. This increases latency for thread
+       switching but reduces the number of places where context
+       switching can occur. It also simplifies the operating system
+       design somewhat. Nothing in the target system (rocket flight
+       control) has tight timing requirements, and so this seems like
+       a reasonable compromise.
+       </para>
+      </listitem>
+      <listitem>
+       <para>Sleep/wakeup scheduling. Taken directly from ancient
+       Unix designs, these two provide the fundemental scheduling
+       primitive within AltOS.
+       </para>
+      </listitem>
+      <listitem>
+       <para>Mutexes. As a locking primitive, mutexes are easier to
+       use than semaphores, at least in my experience.
+       </para>
+      </listitem>
+      <listitem>
+       <para>Timers. Tasks can set an alarm which will abort any
+       pending sleep, allowing operations to time-out instead of
+       blocking forever.
+       </para>
+      </listitem>
+    </itemizedlist>
+    </para>
+    <para>
+      The device drivers and other subsystems in AltOS are
+      conventionally enabled by invoking their _init() function from
+      the 'main' function before that calls
+      ao_start_scheduler(). These functions initialize the pin
+      assignments, add various commands to the command processor and
+      may add tasks to the scheduler to handle the device. A typical
+      main program, thus, looks like:
+      <programlisting>
+void
+main(void)
+{
+       ao_clock_init();
+
+       /* Turn on the LED until the system is stable */
+       ao_led_init(LEDS_AVAILABLE);
+       ao_led_on(AO_LED_RED);
+       ao_timer_init();
+       ao_cmd_init();
+       ao_usb_init();
+       ao_monitor_init(AO_LED_GREEN, TRUE);
+       ao_rssi_init(AO_LED_RED);
+       ao_radio_init();
+       ao_packet_slave_init();
+       ao_packet_master_init();
+#if HAS_DBG
+       ao_dbg_init();
+#endif
+       ao_config_init();
+       ao_start_scheduler();
+}
+      </programlisting>
+      As you can see, a long sequence of subsystems are initialized
+      and then the scheduler is started.
+    </para>
+  </chapter>
+  <chapter>
+    <title>Programming the 8051 with SDCC</title>
+    <para>
+      The 8051 is a primitive 8-bit processor, designed in the mists
+      of time in as few transistors as possible. The architecture is
+      highly irregular and includes several separate memory
+      spaces. Furthermore, accessing stack variables is slow, and the
+      stack itself is of limited size. While SDCC papers over the
+      instruction set, it is not completely able to hide the memory
+      architecture from the application designer.
+    </para>
+    <section>
+      <title>8051 memory spaces</title>
+      <para>
+       The __data/__xdata/__code memory spaces below were completely
+       separate in the original 8051 design. In the cc1111, this
+       isn't true—they all live in a single unified 64kB address
+       space, and so it's possible to convert any address into a
+       unique 16-bit address. SDCC doesn't know this, and so a
+       'global' address to SDCC consumes 3 bytes of memory, 1 byte as
+       a tag indicating the memory space and 2 bytes of offset within
+       that space. AltOS avoids these 3-byte addresses as much as
+       possible; using them involves a function call per byte
+       access. The result is that nearly every variable declaration
+       is decorated with a memory space identifier which clutters the
+       code but makes the resulting code far smaller and more
+       efficient.
+      </para>
+      <variablelist>
+       <title>SDCC 8051 memory spaces</title>
+       <varlistentry>
+         <term>__data</term>
+         <listitem>
+           <para>
+             The 8051 can directly address these 128 bytes of
+             memory. This makes them precious so they should be
+             reserved for frequently addressed values. Oh, just to
+             confuse things further, the 8 general registers in the
+             CPU are actually stored in this memory space. There are
+             magic instructions to 'bank switch' among 4 banks of
+             these registers located at 0x00 - 0x1F. AltOS uses only
+             the first bank at 0x00 - 0x07, leaving the other 24
+             bytes available for other data.
+           </para>
+         </listitem>
+       </varlistentry>
+       <varlistentry>
+         <term>__idata</term>
+         <listitem>
+           <para>
+             There are an additional 128 bytes of internal memory
+             that share the same address space as __data but which
+             cannot be directly addressed. The stack normally
+             occupies this space and so AltOS doesn't place any
+             static storage here.
+           </para>
+         </listitem>
+       </varlistentry>
+       <varlistentry>
+         <term>__xdata</term>
+         <listitem>
+           <para>
+             This is additional general memory accessed through a
+             single 16-bit address register. The CC1111F32 has 32kB
+             of memory available here. Most program data should live
+             in this memory space.
+           </para>
+         </listitem>
+       </varlistentry>
+       <varlistentry>
+         <term>__pdata</term>
+         <listitem>
+           <para>
+             This is an alias for the first 256 bytes of __xdata
+             memory, but uses a shorter addressing mode with
+             single global 8-bit value for the high 8 bits of the
+             address and any of several 8-bit registers for the low 8
+             bits. AltOS uses a few bits of this memory, it should
+             probably use more.
+           </para>
+         </listitem>
+       </varlistentry>
+       <varlistentry>
+         <term>__code</term>
+         <listitem>
+           <para>
+             All executable code must live in this address space, but
+             you can stick read-only data here too. It is addressed
+             using the 16-bit address register and special 'code'
+             access opcodes. Anything read-only should live in this space.
+           </para>
+         </listitem>
+       </varlistentry>
+       <varlistentry>
+         <term>__bit</term>
+         <listitem>
+           <para>
+             The 8051 has 128 bits of bit-addressible memory that
+             lives in the __data segment from 0x20 through
+             0x2f. Special instructions access these bits
+             in a single atomic operation. This isn't so much a
+             separate address space as a special addressing mode for
+             a few bytes in the __data segment.
+           </para>
+         </listitem>
+       </varlistentry>
+       <varlistentry>
+         <term>__sfr, __sfr16, __sfr32, __sbit</term>
+         <listitem>
+           <para>
+             Access to physical registers in the device use this mode
+             which declares the variable name, it's type and the
+             address it lives at. No memory is allocated for these
+             variables.
+           </para>
+         </listitem>
+       </varlistentry>
+      </variablelist>
+    </section>
+    <section>
+      <title>Function calls on the 8051</title>
+      <para>
+       Because stack addressing is expensive, and stack space
+       limited, the default function call declaration in SDCC
+       allocates all parameters and local variables in static global
+       memory. Just like fortran. This makes these functions
+       non-reentrant, and also consume space for parameters and
+       locals even when they are not running. The benefit is smaller
+       code and faster execution.
+      </para>
+      <section>
+       <title>__reentrant functions</title>
+       <para>
+         All functions which are re-entrant, either due to recursion
+         or due to a potential context switch while executing, should
+         be marked as __reentrant so that their parameters and local
+         variables get allocated on the stack. This ensures that
+         these values are not overwritten by another invocation of
+         the function.
+       </para>
+       <para>
+         Functions which use significant amounts of space for
+         arguments and/or local variables and which are not often
+         invoked can also be marked as __reentrant. The resulting
+         code will be larger, but the savings in memory are
+         frequently worthwhile.
+       </para>
+      </section>
+      <section>
+       <title>Non __reentrant functions</title>
+       <para>
+         All parameters and locals in non-reentrant functions can
+         have data space decoration so that they are allocated in
+         __xdata, __pdata or __data space as desired. This can avoid
+         consuming __data space for infrequently used variables in
+         frequently used functions.
+       </para>
+       <para>
+         All library functions called by SDCC, including functions
+         for multiplying and dividing large data types, are
+         non-reentrant. Because of this, interrupt handlers must not
+         invoke any library functions, including the multiply and
+         divide code.
+       </para>
+      </section>
+      <section>
+       <title>__interrupt functions</title>
+       <para>
+         Interrupt functions are declared with with an __interrupt
+         decoration that includes the interrupt number. SDCC saves
+         and restores all of the registers in these functions and
+         uses the 'reti' instruction at the end so that they operate
+         as stand-alone interrupt handlers. Interrupt functions may
+         call the ao_wakeup function to wake AltOS tasks.
+       </para>
+      </section>
+      <section>
+       <title>__critical functions and statements</title>
+       <para>
+         SDCC has built-in support for suspending interrupts during
+         critical code. Functions marked as __critical will have
+         interrupts suspended for the whole period of
+         execution. Individual statements may also be marked as
+         __critical which blocks interrupts during the execution of
+         that statement. Keeping critical sections as short as
+         possible is key to ensuring that interrupts are handled as
+         quickly as possible.
+       </para>
+      </section>
+    </section>
+  </chapter>
+  <chapter>
+    <title>Task functions</title>
+    <para>
+      This chapter documents how to create, destroy and schedule AltOS tasks.
+    </para>
+    <variablelist>
+      <title>AltOS Task Functions</title>
+      <varlistentry>
+       <term>ao_add_task</term>
+       <listitem>
+         <programlisting>
+void
+ao_add_task(__xdata struct ao_task * task,
+            void (*start)(void),
+            __code char *name);
+         </programlisting>
+         <para>
+           This initializes the statically allocated task structure,
+           assigns a name to it (not used for anything but the task
+           display), and the start address. It does not switch to the
+           new task. 'start' must not ever return; there is no place
+           to return to.
+         </para>
+       </listitem>
+      </varlistentry>
+      <varlistentry>
+       <term>ao_exit</term>
+       <listitem>
+         <programlisting>
+void
+ao_exit(void)
+         </programlisting>
+         <para>
+           This terminates the current task.
+         </para>
+       </listitem>
+      </varlistentry>
+      <varlistentry>
+       <term>ao_sleep</term>
+       <listitem>
+         <programlisting>
+void
+ao_sleep(__xdata void *wchan)
+         </programlisting>
+         <para>
+           This suspends the current task until 'wchan' is signaled
+           by ao_wakeup, or until the timeout, set by ao_alarm,
+           fires. If 'wchan' is signaled, ao_sleep returns 0, otherwise
+           it returns 1. This is the only way to switch to another task.
+         </para>
+       </listitem>
+      </varlistentry>
+      <varlistentry>
+       <term>ao_wakeup</term>
+       <listitem>
+         <programlisting>
+void
+ao_wakeup(__xdata void *wchan)
+         </programlisting>
+         <para>
+           Wake all tasks blocked on 'wchan'. This makes them
+           available to be run again, but does not actually switch
+           to another task.
+         </para>
+       </listitem>
+      </varlistentry>
+      <varlistentry>
+       <term>ao_alarm</term>
+       <listitem>
+         <programlisting>
+void
+ao_alarm(uint16_t delay)
+         </programlisting>
+         <para>
+           Schedules an alarm to fire in at least 'delay' ticks. If
+           the task is asleep when the alarm fires, it will wakeup
+           and ao_sleep will return 1.
+         </para>
+       </listitem>
+      </varlistentry>
+      <varlistentry>
+       <term>ao_wake_task</term>
+       <listitem>
+         <programlisting>
+void
+ao_wake_task(__xdata struct ao_task *task)
+         </programlisting>
+         <para>
+           Force a specific task to wake up, independent of which
+           'wchan' it is waiting for.
+         </para>
+       </listitem>
+      </varlistentry>
+      <varlistentry>
+       <term>ao_start_scheduler</term>
+       <listitem>
+         <programlisting>
+void
+ao_start_scheduler(void)
+         </programlisting>
+         <para>
+           This is called from 'main' when the system is all
+           initialized and ready to run. It will not return.
+         </para>
+       </listitem>
+      </varlistentry>
+      <varlistentry>
+       <term>ao_clock_init</term>
+       <listitem>
+         <programlisting>
+void
+ao_clock_init(void)
+         </programlisting>
+         <para>
+           This turns on the external 48MHz clock then switches the
+           hardware to using it. This is required by many of the
+           internal devices like USB. It should be called by the
+           'main' function first, before initializing any of the
+           other devices in the system.
+         </para>
+       </listitem>
+      </varlistentry>
+    </variablelist>
+  </chapter>
+  <chapter>
+    <title>Timer Functions</title>
+    <para>
+      AltOS sets up one of the cc1111 timers to run at 100Hz and
+      exposes this tick as the fundemental unit of time. At each
+      interrupt, AltOS increments the counter, and schedules any tasks
+      waiting for that time to pass, then fires off the ADC system to
+      collect current data readings. Doing this from the ISR ensures
+      that the ADC values are sampled at a regular rate, independent
+      of any scheduling jitter.
+    </para>
+    <variablelist>
+      <title>AltOS Timer Functions</title>
+      <varlistentry>
+       <term>ao_time</term>
+       <listitem>
+         <programlisting>
+uint16_t
+ao_time(void)
+         </programlisting>
+         <para>
+           Returns the current system tick count. Note that this is
+           only a 16 bit value, and so it wraps every 655.36 seconds.
+         </para>
+       </listitem>
+      </varlistentry>
+      <varlistentry>
+       <term>ao_delay</term>
+       <listitem>
+         <programlisting>
+void
+ao_delay(uint16_t ticks);
+         </programlisting>
+         <para>
+           Suspend the current task for at least 'ticks' clock units.
+         </para>
+       </listitem>
+      </varlistentry>
+      <varlistentry>
+       <term>ao_timer_set_adc_interval</term>
+       <listitem>
+         <programlisting>
+void
+ao_timer_set_adc_interval(uint8_t interval);
+         </programlisting>
+         <para>
+           This sets the number of ticks between ADC samples. If set
+           to 0, no ADC samples are generated. AltOS uses this to
+           slow down the ADC sampling rate to save power.
+         </para>
+       </listitem>
+      </varlistentry>
+      <varlistentry>
+       <term>ao_timer_init</term>
+       <listitem>
+         <programlisting>
+void
+ao_timer_init(void)
+         </programlisting>
+         <para>
+           This turns on the 100Hz tick using the CC1111 timer 1. It
+           is required for any of the time-based functions to
+           work. It should be called by 'main' before ao_start_scheduler.
+         </para>
+       </listitem>
+      </varlistentry>
+    </variablelist>
+  </chapter>
+  <chapter>
+    <title>AltOS Mutexes</title>
+    <para>
+      AltOS provides mutexes as a basic synchronization primitive. Each
+      mutexes is simply a byte of memory which holds 0 when the mutex
+      is free or the task id of the owning task when the mutex is
+      owned. Mutex calls are checked—attempting to acquire a mutex
+      already held by the current task or releasing a mutex not held
+      by the current task will both cause a panic.
+    </para>
+    <variablelist>
+      <title>Mutex Functions</title>
+      <varlistentry>
+       <term>ao_mutex_get</term>
+       <listitem>
+         <programlisting>
+void
+ao_mutex_get(__xdata uint8_t *mutex);
+         </programlisting>
+         <para>
+           Acquires the specified mutex, blocking if the mutex is
+           owned by another task.
+         </para>
+       </listitem>
+      </varlistentry>
+      <varlistentry>
+       <term>ao_mutex_put</term>
+       <listitem>
+         <programlisting>
+void
+ao_mutex_put(__xdata uint8_t *mutex);
+         </programlisting>
+         <para>
+           Releases the specified mutex, waking up all tasks waiting
+           for it.
+         </para>
+       </listitem>
+      </varlistentry>
+    </variablelist>
+  </chapter>
+  <chapter>
+    <title>CC1111 DMA engine</title>
+    <para>
+      The CC1111 contains a useful bit of extra hardware in the form
+      of five programmable DMA engines. They can be configured to copy
+      data in memory, or between memory and devices (or even between
+      two devices). AltOS exposes a general interface to this hardware
+      and uses it to handle radio and SPI data.
+    </para>
+    <para>
+      Code using a DMA engine should allocate one at startup
+      time. There is no provision to free them, and if you run out,
+      AltOS will simply panic.
+    </para>
+    <para>
+      During operation, the DMA engine is initialized with the
+      transfer parameters. Then it is started, at which point it
+      awaits a suitable event to start copying data. When copying data
+      from hardware to memory, that trigger event is supplied by the
+      hardware device. When copying data from memory to hardware, the
+      transfer is usually initiated by software.
+    </para>
+    <variablelist>
+      <title>AltOS DMA functions</title>
+      <varlistentry>
+       <term>ao_dma_alloc</term>
+       <listitem>
+         <programlisting>
+uint8_t
+ao_dma_alloc(__xdata uint8_t *done)
+         </programlisting>
+         <para>
+           Allocates a DMA engine, returning the identifier. Whenever
+           this DMA engine completes a transfer. 'done' is cleared
+           when the DMA is started, and then receives the
+           AO_DMA_DONE bit on a successful transfer or the
+           AO_DMA_ABORTED bit if ao_dma_abort was called. Note that
+           it is possible to get both bits if the transfer was
+           aborted after it had finished.
+         </para>
+       </listitem>
+      </varlistentry>
+      <varlistentry>
+       <term>ao_dma_set_transfer</term>
+       <listitem>
+         <programlisting>
+void
+ao_dma_set_transfer(uint8_t id,
+                   void __xdata *srcaddr,
+                   void __xdata *dstaddr,
+                   uint16_t count,
+                   uint8_t cfg0,
+                   uint8_t cfg1)
+         </programlisting>
+         <para>
+           Initializes the specified dma engine to copy data
+           from 'srcaddr' to 'dstaddr' for 'count' units. cfg0 and
+           cfg1 are values directly out of the CC1111 documentation
+           and tell the DMA engine what the transfer unit size,
+           direction and step are.
+         </para>
+       </listitem>
+      </varlistentry>
+      <varlistentry>
+       <term>ao_dma_start</term>
+       <listitem>
+         <programlisting>
+void
+ao_dma_start(uint8_t id);
+         </programlisting>
+         <para>
+           Arm the specified DMA engine and await a signal from
+           either hardware or software to start transferring data.
+         </para>
+       </listitem>
+      </varlistentry>
+      <varlistentry>
+       <term>ao_dma_trigger</term>
+       <listitem>
+         <programlisting>
+void
+ao_dma_trigger(uint8_t id)
+         </programlisting>
+         <para>
+           Trigger the specified DMA engine to start copying data.
+         </para>
+       </listitem>
+      </varlistentry>
+      <varlistentry>
+       <term>ao_dma_abort</term>
+       <listitem>
+         <programlisting>
+void
+ao_dma_abort(uint8_t id)
+         </programlisting>
+         <para>
+           Terminate any in-progress DMA transation, marking its
+           'done' variable with the AO_DMA_ABORTED bit.
+         </para>
+       </listitem>
+      </varlistentry>
+    </variablelist>
+  </chapter>
+  <chapter>
+    <title>SDCC Stdio interface</title>
+    <para>
+      AltOS offers a stdio interface over both USB and the RF packet
+      link. This provides for control of the device localy or
+      remotely. This is hooked up to the stdio functions in SDCC by
+      providing the standard putchar/getchar/flush functions. These
+      automatically multiplex the two available communication
+      channels; output is always delivered to the channel which
+      provided the most recent input.
+    </para>
+    <variablelist>
+      <title>SDCC stdio functions</title>
+      <varlistentry>
+       <term>putchar</term>
+       <listitem>
+         <programlisting>
+void
+putchar(char c)
+         </programlisting>
+         <para>
+           Delivers a single character to the current console
+           device.
+         </para>
+       </listitem>
+      </varlistentry>
+      <varlistentry>
+       <term>getchar</term>
+       <listitem>
+         <programlisting>
+char
+getchar(void)
+         </programlisting>
+         <para>
+           Reads a single character from any of the available
+           console devices. The current console device is set to
+           that which delivered this character. This blocks until
+           a character is available.
+         </para>
+       </listitem>
+      </varlistentry>
+      <varlistentry>
+       <term>flush</term>
+       <listitem>
+         <programlisting>
+void
+flush(void)
+         </programlisting>
+         <para>
+           Flushes the current console device output buffer. Any
+           pending characters will be delivered to the target device.
+xo       </para>
+       </listitem>
+      </varlistentry>
+      <varlistentry>
+       <term>ao_add_stdio</term>
+       <listitem>
+         <programlisting>
+void
+ao_add_stdio(char (*pollchar)(void),
+            void (*putchar)(char),
+            void (*flush)(void))
+         </programlisting>
+         <para>
+           This adds another console device to the available
+           list.
+         </para>
+         <para>
+           'pollchar' returns either an available character or
+           AO_READ_AGAIN if none is available. Significantly, it does
+           not block. The device driver must set 'ao_stdin_ready' to
+           1 and call ao_wakeup(&amp;ao_stdin_ready) when it receives
+           input to tell getchar that more data is available, at
+           which point 'pollchar' will be called again.
+         </para>
+         <para>
+           'putchar' queues a character for output, flushing if the output buffer is
+           full. It may block in this case.
+         </para>
+         <para>
+           'flush' forces the output buffer to be flushed. It may
+           block until the buffer is delivered, but it is not
+           required to do so.
+         </para>
+       </listitem>
+      </varlistentry>
+    </variablelist>
+  </chapter>
+  <chapter>
+    <title>Command line interface</title>
+    <para>
+      AltOS includes a simple command line parser which is hooked up
+      to the stdio interfaces permitting remote control of the device
+      over USB or the RF link as desired. Each command uses a single
+      character to invoke it, the remaining characters on the line are
+      available as parameters to the command.
+    </para>
+    <variablelist>
+      <title>AltOS command line parsing functions</title>
+      <varlistentry>
+       <term>ao_cmd_register</term>
+       <listitem>
+         <programlisting>
+void
+ao_cmd_register(__code struct ao_cmds *cmds)
+         </programlisting>
+         <para>
+           This registers a set of commands with the command
+           parser. There is a fixed limit on the number of command
+           sets, the system will panic if too many are registered.
+           Each command is defined by a struct ao_cmds entry:
+           <programlisting>
+struct ao_cmds {
+       char            cmd;
+       void            (*func)(void);
+       const char      *help;
+};
+           </programlisting>
+           'cmd' is the character naming the command. 'func' is the
+           function to invoke and 'help' is a string displayed by the
+           '?' command. Syntax errors found while executing 'func'
+           should be indicated by modifying the global ao_cmd_status
+           variable with one of the following values:
+           <variablelist>
+             <varlistentry>
+               <term>ao_cmd_success</term>
+               <listitem>
+                 <para>
+                   The command was parsed successfully. There is no
+                   need to assign this value, it is the default.
+                 </para>
+               </listitem>
+             </varlistentry>
+             <varlistentry>
+               <term>ao_cmd_lex_error</term>
+               <listitem>
+                 <para>
+                   A token in the line was invalid, such as a number
+                   containing invalid characters. The low-level
+                   lexing functions already assign this value as needed.
+                 </para>
+               </listitem>
+             </varlistentry>
+             <varlistentry>
+               <term>ao_syntax_error</term>
+               <listitem>
+                 <para>
+                   The command line is invalid for some reason other
+                   than invalid tokens.
+                 </para>
+               </listitem>
+             </varlistentry>
+           </variablelist>
+         </para>
+       </listitem>
+      </varlistentry>
+      <varlistentry>
+       <term>ao_cmd_lex</term>
+       <listitem>
+         <programlisting>
+void
+ao_cmd_lex(void);
+         </programlisting>
+         <para>
+           This gets the next character out of the command line
+           buffer and sticks it into ao_cmd_lex_c. At the end of the
+           line, ao_cmd_lex_c will get a newline ('\n') character.
+         </para>
+       </listitem>
+      </varlistentry>
+      <varlistentry>
+       <term>ao_cmd_put16</term>
+       <listitem>
+         <programlisting>
+void
+ao_cmd_put16(uint16_t v);
+         </programlisting>
+         <para>
+           Writes 'v' as four hexadecimal characters.
+         </para>
+       </listitem>
+      </varlistentry>
+      <varlistentry>
+       <term>ao_cmd_put8</term>
+       <listitem>
+         <programlisting>
+void
+ao_cmd_put8(uint8_t v);
+         </programlisting>
+         <para>
+           Writes 'v' as two hexadecimal characters.
+         </para>
+       </listitem>
+      </varlistentry>
+      <varlistentry>
+       <term>ao_cmd_white</term>
+       <listitem>
+         <programlisting>
+void
+ao_cmd_white(void)
+         </programlisting>
+         <para>
+           This skips whitespace by calling ao_cmd_lex while
+           ao_cmd_lex_c is either a space or tab. It does not skip
+           any characters if ao_cmd_lex_c already non-white.
+         </para>
+       </listitem>
+      </varlistentry>
+      <varlistentry>
+       <term>ao_cmd_hex</term>
+       <listitem>
+         <programlisting>
+void
+ao_cmd_hex(void)
+         </programlisting>
+         <para>
+           This reads a 16-bit hexadecimal value from the command
+           line with optional leading whitespace. The resulting value
+           is stored in ao_cmd_lex_i;
+         </para>
+       </listitem>
+      </varlistentry>
+      <varlistentry>
+       <term>ao_cmd_decimal</term>
+       <listitem>
+         <programlisting>
+void
+ao_cmd_decimal(void)
+         </programlisting>
+         <para>
+           This reads a 32-bit decimal value from the command
+           line with optional leading whitespace. The resulting value
+           is stored in ao_cmd_lex_u32 and the low 16 bits are stored
+           in ao_cmd_lex_i;
+         </para>
+       </listitem>
+      </varlistentry>
+      <varlistentry>
+       <term>ao_match_word</term>
+       <listitem>
+         <programlisting>
+uint8_t
+ao_match_word(__code char *word)
+         </programlisting>
+         <para>
+           This checks to make sure that 'word' occurs on the command
+           line. It does not skip leading white space. If 'word' is
+           found, then 1 is returned. Otherwise, ao_cmd_status is set to
+           ao_cmd_syntax_error and 0 is returned.
+         </para>
+       </listitem>
+      </varlistentry>
+      <varlistentry>
+       <term>ao_cmd_init</term>
+       <listitem>
+         <programlisting>
+void
+ao_cmd_init(void
+         </programlisting>
+         <para>
+           Initializes the command system, setting up the built-in
+           commands and adding a task to run the command processing
+           loop. It should be called by 'main' before ao_start_scheduler.
+         </para>
+       </listitem>
+      </varlistentry>
+    </variablelist>
+  </chapter>
+  <chapter>
+    <title>CC1111 USB target device</title>
+    <para>
+      The CC1111 contains a full-speed USB target device. It can be
+      programmed to offer any kind of USB target, but to simplify
+      interactions with a variety of operating systems, AltOS provides
+      only a single target device profile, that of a USB modem which
+      has native drivers for Linux, Windows and Mac OS X. It would be
+      easy to change the code to provide an alternate target device if
+      necessary.
+    </para>
+    <para>
+      To the rest of the system, the USB device looks like a simple
+      two-way byte stream. It can be hooked into the command line
+      interface if desired, offering control of the device over the
+      USB link. Alternatively, the functions can be accessed directly
+      to provide for USB-specific I/O.
+    </para>
+    <variablelist>
+      <title>AltOS USB functions</title>
+      <varlistentry>
+       <term>ao_usb_flush</term>
+       <listitem>
+         <programlisting>
+void
+ao_usb_flush(void);
+         </programlisting>
+         <para>
+           Flushes any pending USB output. This queues an 'IN' packet
+           to be delivered to the USB host if there is pending data,
+           or if the last IN packet was full to indicate to the host
+           that there isn't any more pending data available.
+         </para>
+       </listitem>
+      </varlistentry>
+      <varlistentry>
+       <term>ao_usb_putchar</term>
+       <listitem>
+         <programlisting>
+void
+ao_usb_putchar(char c);
+         </programlisting>
+         <para>
+           If there is a pending 'IN' packet awaiting delivery to the
+           host, this blocks until that has been fetched. Then, this
+           adds a byte to the pending IN packet for delivery to the
+           USB host. If the USB packet is full, this queues the 'IN'
+           packet for delivery.
+         </para>
+       </listitem>
+      </varlistentry>
+      <varlistentry>
+       <term>ao_usb_pollchar</term>
+       <listitem>
+         <programlisting>
+char
+ao_usb_pollchar(void);
+         </programlisting>
+         <para>
+           If there are no characters remaining in the last 'OUT'
+           packet received, this returns AO_READ_AGAIN. Otherwise, it
+           returns the next character, reporting to the host that it
+           is ready for more data when the last character is gone.
+         </para>
+       </listitem>
+      </varlistentry>
+      <varlistentry>
+       <term>ao_usb_getchar</term>
+       <listitem>
+         <programlisting>
+char
+ao_usb_getchar(void);
+         </programlisting>
+         <para>
+           This uses ao_pollchar to receive the next character,
+           blocking while ao_pollchar returns AO_READ_AGAIN.
+         </para>
+       </listitem>
+      </varlistentry>
+      <varlistentry>
+       <term>ao_usb_disable</term>
+       <listitem>
+         <programlisting>
+void
+ao_usb_disable(void);
+         </programlisting>
+         <para>
+           This turns off the USB controller. It will no longer
+           respond to host requests, nor return characters. Calling
+           any of the i/o routines while the USB device is disabled
+           is undefined, and likely to break things. Disabling the
+           USB device when not needed saves power.
+         </para>
+         <para>
+           Note that neither TeleDongle nor TeleMetrum are able to
+           signal to the USB host that they have disconnected, so
+           after disabling the USB device, it's likely that the cable
+           will need to be disconnected and reconnected before it
+           will work again.
+         </para>
+       </listitem>
+      </varlistentry>
+      <varlistentry>
+       <term>ao_usb_enable</term>
+       <listitem>
+         <programlisting>
+void
+ao_usb_enable(void);
+         </programlisting>
+         <para>
+           This turns the USB controller on again after it has been
+           disabled. See the note above about needing to physically
+           remove and re-insert the cable to get the host to
+           re-initialize the USB link.
+         </para>
+       </listitem>
+      </varlistentry>
+      <varlistentry>
+       <term>ao_usb_init</term>
+       <listitem>
+         <programlisting>
+void
+ao_usb_init(void);
+         </programlisting>
+         <para>
+           This turns the USB controller on, adds a task to handle
+           the control end point and adds the usb I/O functions to
+           the stdio system. Call this from main before
+           ao_start_scheduler.
+         </para>
+       </listitem>
+      </varlistentry>
+    </variablelist>
+  </chapter>
+  <chapter>
+    <title>CC1111 Serial peripheral</title>
+    <para>
+      The CC1111 provides two USART peripherals. AltOS uses one for
+      asynch serial data, generally to communicate with a GPS device,
+      and the other for a SPI bus. The UART is configured to operate
+      in 8-bits, no parity, 1 stop bit framing. The default
+      configuration has clock settings for 4800, 9600 and 57600 baud
+      operation. Additional speeds can be added by computing
+      appropriate clock values.
+    </para>
+    <para>
+      To prevent loss of data, AltOS provides receive and transmit
+      fifos of 32 characters each.
+    </para>
+    <variablelist>
+      <title>AltOS serial functions</title>
+      <varlistentry>
+       <term>ao_serial_getchar</term>
+       <listitem>
+         <programlisting>
+char
+ao_serial_getchar(void);
+         </programlisting>
+         <para>
+           Returns the next character from the receive fifo, blocking
+           until a character is received if the fifo is empty.
+         </para>
+       </listitem>
+      </varlistentry>
+      <varlistentry>
+       <term>ao_serial_putchar</term>
+       <listitem>
+         <programlisting>
+void
+ao_serial_putchar(char c);
+         </programlisting>
+         <para>
+           Adds a character to the transmit fifo, blocking if the
+           fifo is full. Starts transmitting characters.
+         </para>
+       </listitem>
+      </varlistentry>
+      <varlistentry>
+       <term>ao_serial_drain</term>
+       <listitem>
+         <programlisting>
+void
+ao_serial_drain(void);
+         </programlisting>
+         <para>
+           Blocks until the transmit fifo is empty. Used internally
+           when changing serial speeds.
+         </para>
+       </listitem>
+      </varlistentry>
+      <varlistentry>
+       <term>ao_serial_set_speed</term>
+       <listitem>
+         <programlisting>
+void
+ao_serial_set_speed(uint8_t speed);
+         </programlisting>
+         <para>
+           Changes the serial baud rate to one of
+           AO_SERIAL_SPEED_4800, AO_SERIAL_SPEED_9600 or
+           AO_SERIAL_SPEED_57600. This first flushes the transmit
+           fifo using ao_serial_drain.
+         </para>
+       </listitem>
+      </varlistentry>
+      <varlistentry>
+       <term>ao_serial_init</term>
+       <listitem>
+         <programlisting>
+void
+ao_serial_init(void)
+         </programlisting>
+         <para>
+           Initializes the serial peripheral. Call this from 'main'
+           before jumping to ao_start_scheduler. The default speed
+           setting is AO_SERIAL_SPEED_4800.
+         </para>
+       </listitem>
+      </varlistentry>
+    </variablelist>
+  </chapter>
+  <chapter>
+    <title>CC1111 Radio peripheral</title>
+    <para>
+      The CC1111 radio transceiver sends and receives digital packets
+      with forward error correction and detection. The AltOS driver is
+      fairly specific to the needs of the TeleMetrum and TeleDongle
+      devices, using it for other tasks may require customization of
+      the driver itself. There are three basic modes of operation:
+      <orderedlist>
+       <listitem>
+         <para>
+           Telemetry mode. In this mode, TeleMetrum transmits telemetry
+           frames at a fixed rate. The frames are of fixed size. This
+           is strictly a one-way communication from TeleMetrum to
+           TeleDongle.
+         </para>
+       </listitem>
+       <listitem>
+         <para>
+           Packet mode. In this mode, the radio is used to create a
+           reliable duplex byte stream between TeleDongle and
+           TeleMetrum. This is an asymmetrical protocol with
+           TeleMetrum only transmitting in response to a packet sent
+           from TeleDongle. Thus getting data from TeleMetrum to
+           TeleDongle requires polling. The polling rate is adaptive,
+           when no data has been received for a while, the rate slows
+           down. The packets are checked at both ends and invalid
+           data are ignored.
+         </para>
+         <para>
+           On the TeleMetrum side, the packet link is hooked into the
+           stdio mechanism, providing an alternate data path for the
+           command processor. It is enabled when the unit boots up in
+           'idle' mode.
+         </para>
+         <para>
+           On the TeleDongle side, the packet link is enabled with a
+           command; data from the stdio package is forwarded over the
+           packet link providing a connection from the USB command
+           stream to the remote TeleMetrum device.
+         </para>
+       </listitem>
+       <listitem>
+         <para>
+           Radio Direction Finding mode. In this mode, TeleMetrum
+           constructs a special packet that sounds like an audio tone
+           when received by a conventional narrow-band FM
+           receiver. This is designed to provide a beacon to track
+           the device when other location mechanisms fail.
+         </para>
+       </listitem>
+      </orderedlist>
+    </para>
+    <variablelist>
+      <title>AltOS radio functions</title>
+      <varlistentry>
+       <term>ao_radio_set_telemetry</term>
+       <listitem>
+         <programlisting>
+void
+ao_radio_set_telemetry(void);
+         </programlisting>
+         <para>
+           Configures the radio to send or receive telemetry
+           packets. This includes packet length, modulation scheme and
+           other RF parameters. It does not include the base frequency
+           or channel though. Those are set at the time of transmission
+           or reception, in case the values are changed by the user.
+         </para>
+       </listitem>
+      </varlistentry>
+      <varlistentry>
+       <term>ao_radio_set_packet</term>
+       <listitem>
+         <programlisting>
+void
+ao_radio_set_packet(void);
+         </programlisting>
+         <para>
+           Configures the radio to send or receive packet data.  This
+           includes packet length, modulation scheme and other RF
+           parameters. It does not include the base frequency or
+           channel though. Those are set at the time of transmission or
+           reception, in case the values are changed by the user.
+         </para>
+       </listitem>
+      </varlistentry>
+      <varlistentry>
+       <term>ao_radio_set_rdf</term>
+       <listitem>
+         <programlisting>
+void
+ao_radio_set_rdf(void);
+         </programlisting>
+         <para>
+           Configures the radio to send RDF 'packets'. An RDF 'packet'
+           is a sequence of hex 0x55 bytes sent at a base bit rate of
+           2kbps using a 5kHz deviation. All of the error correction
+           and data whitening logic is turned off so that the resulting
+           modulation is received as a 1kHz tone by a conventional 70cm
+           FM audio receiver.
+         </para>
+       </listitem>
+      </varlistentry>
+      <varlistentry>
+       <term>ao_radio_idle</term>
+       <listitem>
+         <programlisting>
+void
+ao_radio_idle(void);
+         </programlisting>
+         <para>
+           Sets the radio device to idle mode, waiting until it reaches
+           that state. This will terminate any in-progress transmit or
+           receive operation.
+         </para>
+       </listitem>
+      </varlistentry>
+      <varlistentry>
+       <term>ao_radio_get</term>
+       <listitem>
+         <programlisting>
+void
+ao_radio_get(void);
+         </programlisting>
+         <para>
+           Acquires the radio mutex and then configures the radio
+           frequency using the global radio calibration and channel
+           values.
+         </para>
+       </listitem>
+      </varlistentry>
+      <varlistentry>
+       <term>ao_radio_put</term>
+       <listitem>
+         <programlisting>
+void
+ao_radio_put(void);
+         </programlisting>
+         <para>
+           Releases the radio mutex.
+         </para>
+       </listitem>
+      </varlistentry>
+      <varlistentry>
+       <term>ao_radio_abort</term>
+       <listitem>
+         <programlisting>
+void
+ao_radio_abort(void);
+         </programlisting>
+         <para>
+           Aborts any transmission or reception process by aborting the
+           associated DMA object and calling ao_radio_idle to terminate
+           the radio operation.
+         </para>
+       </listitem>
+      </varlistentry>
+    </variablelist>
+    <variablelist>
+      <title>AltOS radio telemetry functions</title>
+      <para>
+       In telemetry mode, you can send or receive a telemetry
+       packet. The data from receiving a packet also includes the RSSI
+       and status values supplied by the receiver. These are added
+       after the telemetry data.
+      </para>
+      <varlistentry>
+       <term>ao_radio_send</term>
+       <listitem>
+         <programlisting>
+void
+ao_radio_send(__xdata struct ao_telemetry *telemetry);
+         </programlisting>
+         <para>
+           This sends the specific telemetry packet, waiting for the
+           transmission to complete. The radio must have been set to
+           telemetry mode. This function calls ao_radio_get() before
+           sending, and ao_radio_put() afterwards, to correctly
+           serialize access to the radio device.
+         </para>
+       </listitem>
+      </varlistentry>
+      <varlistentry>
+       <term>ao_radio_recv</term>
+       <listitem>
+         <programlisting>
+void
+ao_radio_recv(__xdata struct ao_radio_recv *radio);
+         </programlisting>
+         <para>
+           This blocks waiting for a telemetry packet to be received.
+           The radio must have been set to telemetry mode. This
+           function calls ao_radio_get() before receiving, and
+           ao_radio_put() afterwards, to correctly serialize access
+           to the radio device. This returns non-zero if a packet was
+           received, or zero if the operation was aborted (from some
+           other task calling ao_radio_abort()).
+         </para>
+       </listitem>
+      </varlistentry>
+    </variablelist>
+    <variablelist>
+      <title>AltOS radio direction finding function</title>
+      <para>
+       In radio direction finding mode, there's just one function to
+       use
+      </para>
+      <varlistentry>
+       <term>ao_radio_rdf</term>
+       <listitem>
+         <programlisting>
+void
+ao_radio_rdf(int ms);
+         </programlisting>
+         <para>
+           This sends an RDF packet lasting for the specified amount
+           of time. The maximum length is 1020 ms.
+         </para>
+       </listitem>
+      </varlistentry>
+    </variablelist>
+    <variablelist>
+      <title>Packet mode functions</title>
+      <para>
+       Packet mode is asymmetrical and is configured at compile time
+       for either master or slave mode (but not both). The basic I/O
+       functions look the same at both ends, but the internals are
+       different, along with the initialization steps.
+      </para>
+      <varlistentry>
+       <term>ao_packet_putchar</term>
+       <listitem>
+         <programlisting>
+void
+ao_packet_putchar(char c);
+         </programlisting>
+         <para>
+           If the output queue is full, this first blocks waiting for
+           that data to be delivered. Then, queues a character for
+           packet transmission. On the master side, this will
+           transmit a packet if the output buffer is full. On the
+           slave side, any pending data will be sent the next time
+           the master polls for data.
+         </para>
+       </listitem>
+      </varlistentry>
+      <varlistentry>
+       <term>ao_packet_pollchar</term>
+       <listitem>
+         <programlisting>
+char
+ao_packet_pollchar(void);
+         </programlisting>
+         <para>
+           This returns a pending input character if available,
+           otherwise returns AO_READ_AGAIN. On the master side, if
+           this empties the buffer, it triggers a poll for more data.
+         </para>
+       </listitem>
+      </varlistentry>
+      <varlistentry>
+       <term>ao_packet_slave_start</term>
+       <listitem>
+         <programlisting>
+void
+ao_packet_slave_start(void);
+         </programlisting>
+         <para>
+           This is available only on the slave side and starts a task
+           to listen for packet data.
+         </para>
+       </listitem>
+      </varlistentry>
+      <varlistentry>
+       <term>ao_packet_slave_stop</term>
+       <listitem>
+         <programlisting>
+void
+ao_packet_slave_stop(void);
+         </programlisting>
+         <para>
+           Disables the packet slave task, stopping the radio receiver.
+         </para>
+       </listitem>
+      </varlistentry>
+      <varlistentry>
+       <term>ao_packet_slave_init</term>
+       <listitem>
+         <programlisting>
+void
+ao_packet_slave_init(void);
+         </programlisting>
+         <para>
+           Adds the packet stdio functions to the stdio package so
+           that when packet slave mode is enabled, characters will
+           get send and received through the stdio functions.
+         </para>
+       </listitem>
+      </varlistentry>
+      <varlistentry>
+       <term>ao_packet_master_init</term>
+       <listitem>
+         <programlisting>
+void
+ao_packet_master_init(void);
+         </programlisting>
+         <para>
+           Adds the 'p' packet forward command to start packet mode.
+         </para>
+       </listitem>
+      </varlistentry>
+    </variablelist>
+  </chapter>
+</book>