From 14ad137fd14707bc7b45a3512a4a6f81915ca1c1 Mon Sep 17 00:00:00 2001 From: Keith Packard Date: Sat, 31 Oct 2015 22:40:13 -0700 Subject: [PATCH] doc: Convert AltOS doc to asciidoc It's still pretty stale, but at least it isn't in docbook? Signed-off-by: Keith Packard --- doc/Makefile | 12 +- doc/altos-docinfo.xml | 32 + doc/altos.txt | 1400 +++++++++++++++++++++++++++++++++++ doc/altos.xsl | 1612 ----------------------------------------- 4 files changed, 1441 insertions(+), 1615 deletions(-) create mode 100644 doc/altos-docinfo.xml create mode 100644 doc/altos.txt delete mode 100644 doc/altos.xsl diff --git a/doc/Makefile b/doc/Makefile index df1a884c..04402c88 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -134,12 +134,18 @@ SVG=\ RELNOTES_PDF=$(RELNOTES_INC:.inc=.pdf) RELNOTES_HTML=$(RELNOTES_INC:.inc=.html) +ALTOS_TXT_FILES=\ + altos.txt + +ALTOS_RAW_FILES=$(ALTOS_TXT_FILES:.txt=.raw) +ALTOS_PDF_FILES=$(ALTOS_TXT_FILES:.txt=.pdf) + HTML=altusmetrum.html altos.html telemetry.html companion.html micropeak.html telegps.html $(RELNOTES_HTML) -PDF=altusmetrum.pdf $(RELNOTES_PDF) altos.pdf telemetry.pdf companion.pdf micropeak.pdf telegps.pdf \ +PDF=altusmetrum.pdf $(RELNOTES_PDF) $(ALTOS_PDF_FILES) telemetry.pdf companion.pdf micropeak.pdf telegps.pdf \ $(OUTLINE_PDF_FILES) -FOSTYLE=xorg-fo.xsl +FOSTYLE=am-fo.xsl TEMPLATES_TMPL=titlepage.templates.tmpl @@ -189,7 +195,7 @@ publish: $(DOC) git push) clean: - rm -f $(HTML) $(PDF) $(TEMPLATES_XSL) + rm -f $(HTML) $(PDF) $(TEMPLATES_XSL) $(RAW_FILES) $(TELEGPS_RAW_FILES) $(MICROPEAK_RAW_FILES) distclean: clean rm -f $(HTML) $(PDF) diff --git a/doc/altos-docinfo.xml b/doc/altos-docinfo.xml new file mode 100644 index 00000000..1b6ad648 --- /dev/null +++ b/doc/altos-docinfo.xml @@ -0,0 +1,32 @@ +Altos Metrum Operating System + + Keith + Packard + keithp@keithp.com + +05 November 2012 + + 2012 + Keith Packard + + + + This document is released under the terms of the + + Creative Commons ShareAlike 3.0 + + license. + + + + + 1.1 + 05 November 2012 + Portable version + + + 0.1 + 22 November 2010 + Initial content + + diff --git a/doc/altos.txt b/doc/altos.txt new file mode 100644 index 00000000..f2b1ee59 --- /dev/null +++ b/doc/altos.txt @@ -0,0 +1,1400 @@ += AltOS +:doctype: book +:toc: +:numbered: + +== Overview + + AltOS is a operating system built for a variety of + microcontrollers used in Altus Metrum devices. It has a simple + porting layer for each CPU while providing a convenient + operating enviroment for the developer. AltOS currently + supports three different CPUs: + + * STM32L series from ST Microelectronics. This ARM Cortex-M3 + based microcontroller offers low power consumption and a + wide variety of built-in peripherals. Altus Metrum uses this + in the TeleMega, MegaDongle and TeleLCO projects. + + * CC1111 from Texas Instruments. This device includes a + fabulous 10mW digital RF transceiver along with an + 8051-compatible processor core and a range of + peripherals. This is used in the TeleMetrum, TeleMini, + TeleDongle and TeleFire projects which share the need for a + small microcontroller and an RF interface. + + * ATmega32U4 from Atmel. This 8-bit AVR microcontroller is one + of the many used to create Arduino boards. The 32U4 includes + a USB interface, making it easy to connect to other + computers. Altus Metrum used this in prototypes of the + TeleScience and TelePyro boards; those have been switched to + the STM32L which is more capable and cheaper. + + Among the features of AltOS are: + + * Multi-tasking. While microcontrollers often don'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. + + * 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. + + * Sleep/wakeup scheduling. Taken directly from ancient + Unix designs, these two provide the fundemental scheduling + primitive within AltOS. + + * Mutexes. As a locking primitive, mutexes are easier to + use than semaphores, at least in my experience. + + * Timers. Tasks can set an alarm which will abort any + pending sleep, allowing operations to time-out instead of + blocking forever. + + 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: + + .... + \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(); + \} + .... + + As you can see, a long sequence of subsystems are initialized + and then the scheduler is started. + +== AltOS Porting Layer + + AltOS provides a CPU-independent interface to various common + microcontroller subsystems, including GPIO pins, interrupts, + SPI, I2C, USB and asynchronous serial interfaces. By making + these CPU-independent, device drivers, generic OS and + application code can all be written that work on any supported + CPU. Many of the architecture abstraction interfaces are + prefixed with ao_arch. + + === Low-level CPU operations + + These primitive operations provide the abstraction needed to + run the multi-tasking framework while providing reliable + interrupt delivery. + + ==== ao_arch_block_interrupts/ao_arch_release_interrupts + + .... + static inline void + ao_arch_block_interrupts(void); + + static inline void + ao_arch_release_interrupts(void); + .... + + These disable/enable interrupt delivery, they may not + discard any interrupts. Use these for sections of code that + must be atomic with respect to any code run from an + interrupt handler. + + ==== ao_arch_save_regs, ao_arch_save_stack, ao_arch_restore_stack + + .... + static inline void + ao_arch_save_regs(void); + + static inline void + ao_arch_save_stack(void); + + static inline void + ao_arch_restore_stack(void); + .... + + These provide all of the support needed to switch + between tasks.. ao_arch_save_regs must save all CPU + registers to the current stack, including the + interrupt enable state. ao_arch_save_stack records the + current stack location in the current ao_task + structure. ao_arch_restore_stack switches back to the + saved stack, restores all registers and branches to + the saved return address. + + ==== ao_arch_wait_interupt + + .... + #define ao_arch_wait_interrupt() + .... + + This stops the CPU, leaving clocks and interrupts + enabled. When an interrupt is received, this must wake up + and handle the interrupt. ao_arch_wait_interrupt is entered + with interrupts disabled to ensure that there is no gap + between determining that no task wants to run and idling the + CPU. It must sleep the CPU, process interrupts and then + disable interrupts again. If the CPU doesn't have any + reduced power mode, this must at the least allow pending + interrupts to be processed. + + === GPIO operations + + These functions provide an abstract interface to configure and + manipulate GPIO pins. + + ==== GPIO setup + + These macros may be invoked at system + initialization time to configure pins as + needed for system operation. One tricky aspect + is that some chips provide direct access to + specific GPIO pins while others only provide + access to a whole register full of pins. To + support this, the GPIO macros provide both + port+bit and pin arguments. Simply define the + arguments needed for the target platform and + leave the others undefined. + + ===== ao_enable_output + + .... + #define ao_enable_output(port, bit, pin, value) + .... + + Set the specified port+bit (also called 'pin') + for output, initializing to the specified + value. The macro must avoid driving the pin + with the opposite value if at all possible. + + ===== ao_enable_input + + .... + #define ao_enable_input(port, bit, mode) + .... + + Sets the specified port/bit to be an input + pin. 'mode' is a combination of one or more of + the following. Note that some platforms may + not support the desired mode. In that case, + the value will not be defined so that the + program will fail to compile. + + * AO_EXTI_MODE_PULL_UP. Apply a pull-up to the + pin; a disconnected pin will read as 1. + + * AO_EXTI_MODE_PULL_DOWN. Apply a pull-down to + the pin; a disconnected pin will read as 0. + + * 0. Don't apply either a pull-up or + pull-down. A disconnected pin will read an + undetermined value. + + ==== Reading and writing GPIO pins + + These macros read and write individual GPIO pins. + + ===== ao_gpio_set + + .... + #define ao_gpio_set(port, bit, pin, value) + .... + + Sets the specified port/bit or pin to + the indicated value + + ===== ao_gpio_get + + .... + #define ao_gpio_get(port, bit, pin) + .... + + Returns either 1 or 0 depending on + whether the input to the pin is high + or low. +== Programming the 8051 with SDCC + + 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. + + When built on other architectures, the various SDCC-specific + symbols are #defined as empty strings so they don't affect the + compiler. + + === 8051 memory spaces + + 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. + + ==== __data + + 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. + + ==== __idata + + 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. + + ==== __xdata + + 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. + + ==== __pdata + + 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. + + ==== __code + + 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. + + ==== __bit + + 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. + + ==== __sfr, __sfr16, __sfr32, __sbit + + Access to physical registers in the device use this mode + which declares the variable name, its type and the + address it lives at. No memory is allocated for these + variables. + + === Function calls on the 8051 + + 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. + + ==== __reentrant functions + + 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. + + 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. + + ==== Non __reentrant functions + + 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. + + 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. + + ==== __interrupt functions + + 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. + + ==== __critical functions and statements + + 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. AltOS doesn't use this form in shared + code as other compilers wouldn't know what to do. Use + ao_arch_block_interrupts and ao_arch_release_interrupts instead. + +== Task functions + + This chapter documents how to create, destroy and schedule + AltOS tasks. + + === ao_add_task + + .... + \void + \ao_add_task(__xdata struct ao_task * task, + \ void (*start)(void), + \ __code char *name); + .... + + 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. + + === ao_exit + + .... + void + ao_exit(void) + .... + + This terminates the current task. + + === ao_sleep + + .... + void + ao_sleep(__xdata void *wchan) + .... + + 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. + + Because ao_wakeup wakes every task waiting on a particular + location, ao_sleep should be used in a loop that first checks + the desired condition, blocks in ao_sleep and then rechecks + until the condition is satisfied. If the location may be + signaled from an interrupt handler, the code will need to + block interrupts around the block of code. Here's a complete + example: + + .... + \ao_arch_block_interrupts(); + \while (!ao_radio_done) + \ ao_sleep(&ao_radio_done); + \ao_arch_release_interrupts(); + .... + + === ao_wakeup + + .... + void + ao_wakeup(__xdata void *wchan) + .... + + Wake all tasks blocked on 'wchan'. This makes them + available to be run again, but does not actually switch + to another task. Here's an example of using this: + + .... + \if (RFIF & RFIF_IM_DONE) { + \ ao_radio_done = 1; + \ ao_wakeup(&ao_radio_done); + \ RFIF &= ~RFIF_IM_DONE; + \} + .... + + Note that this need not block interrupts as the + ao_sleep block can only be run from normal mode, and + so this sequence can never be interrupted with + execution of the other sequence. + + === ao_alarm + + .... + void + ao_alarm(uint16_t delay); + + void + ao_clear_alarm(void); + .... + + 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. ao_clear_alarm + resets any pending alarm so that it doesn't fire at + some arbitrary point in the future. + + .... + ao_alarm(ao_packet_master_delay); + ao_arch_block_interrupts(); + while (!ao_radio_dma_done) + if (ao_sleep(&ao_radio_dma_done) != 0) + ao_radio_abort(); + ao_arch_release_interrupts(); + ao_clear_alarm(); + .... + + In this example, a timeout is set before waiting for + incoming radio data. If no data is received before the + timeout fires, ao_sleep will return 1 and then this + code will abort the radio receive operation. + + === ao_start_scheduler + + .... + void + ao_start_scheduler(void); + .... + + This is called from 'main' when the system is all + initialized and ready to run. It will not return. + + === ao_clock_init + + .... + void + ao_clock_init(void); + .... + + This initializes the main CPU clock and switches to it. + +== Timer Functions + + AltOS sets up one of the CPU 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 sensors to + collect current data readings. Doing this from the ISR ensures + that the values are sampled at a regular rate, independent + of any scheduling jitter. + + === ao_time + + .... + uint16_t + ao_time(void) + .... + + Returns the current system tick count. Note that this is + only a 16 bit value, and so it wraps every 655.36 seconds. + + === ao_delay + + .... + void + ao_delay(uint16_t ticks); + .... + + Suspend the current task for at least 'ticks' clock units. + + === ao_timer_set_adc_interval + + .... + void + ao_timer_set_adc_interval(uint8_t interval); + .... + + 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. + + === ao_timer_init + + .... + void + ao_timer_init(void) + .... + + This turns on the 100Hz tick. It is required for any of the + time-based functions to work. It should be called by 'main' + before ao_start_scheduler. + +== AltOS Mutexes + + 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. + + === ao_mutex_get + + .... + void + ao_mutex_get(__xdata uint8_t *mutex); + .... + + Acquires the specified mutex, blocking if the mutex is + owned by another task. + + === ao_mutex_put + + .... + void + ao_mutex_put(__xdata uint8_t *mutex); + .... + + Releases the specified mutex, waking up all tasks waiting + for it. + +== DMA engine + + The CC1111 and STM32L both contain a useful bit of extra + hardware in the form of a number of 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 both internal and external devices. + + Because the CC1111 and STM32L DMA engines are different, the + interface to them is also different. As the DMA engines are + currently used to implement platform-specific drivers, this + isn't yet a problem. + + 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. + + 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. + + === CC1111 DMA Engine + + ==== ao_dma_alloc + + .... + uint8_t + ao_dma_alloc(__xdata uint8_t *done) + .... + + Allocate a DMA engine, returning the + identifier. '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. + + ==== ao_dma_set_transfer + + .... + void + ao_dma_set_transfer(uint8_t id, + void __xdata *srcaddr, + void __xdata *dstaddr, + uint16_t count, + uint8_t cfg0, + uint8_t cfg1) + .... + + 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. + + ==== ao_dma_start + + .... + void + ao_dma_start(uint8_t id); + .... + + Arm the specified DMA engine and await a + signal from either hardware or software to + start transferring data. + + ==== ao_dma_trigger + + .... + void + ao_dma_trigger(uint8_t id) + .... + + Trigger the specified DMA engine to start + copying data. + + ==== ao_dma_abort + + .... + void + ao_dma_abort(uint8_t id) + .... + + Terminate any in-progress DMA transaction, + marking its 'done' variable with the + AO_DMA_ABORTED bit. + + === STM32L DMA Engine + + ==== ao_dma_alloc + + .... + uint8_t ao_dma_done[]; + + void + ao_dma_alloc(uint8_t index); + .... + + Reserve a DMA engine for exclusive use by one + driver. + + ==== ao_dma_set_transfer + + .... + void + ao_dma_set_transfer(uint8_t id, + void *peripheral, + void *memory, + uint16_t count, + uint32_t ccr); + .... + + Initializes the specified dma engine to copy + data between 'peripheral' and 'memory' for + 'count' units. 'ccr' is a value directly out + of the STM32L documentation and tells the DMA + engine what the transfer unit size, direction + and step are. + + ==== ao_dma_set_isr + + .... + void + ao_dma_set_isr(uint8_t index, void (*isr)(int)) + .... + + This sets a function to be called when the DMA + transfer completes in lieu of setting the + ao_dma_done bits. Use this when some work + needs to be done when the DMA finishes that + cannot wait until user space resumes. + + ==== ao_dma_start + + .... + void + ao_dma_start(uint8_t id); + .... + + Arm the specified DMA engine and await a + signal from either hardware or software to + start transferring data. 'ao_dma_done[index]' + 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. + + ==== ao_dma_done_transfer + + .... + void + ao_dma_done_transfer(uint8_t id); + .... + + Signals that a specific DMA engine is done + being used. This allows multiple drivers to + use the same DMA engine safely. + + ==== ao_dma_abort + + .... + void + ao_dma_abort(uint8_t id) + .... + + Terminate any in-progress DMA transaction, + marking its 'done' variable with the + AO_DMA_ABORTED bit. + +== Stdio interface + + AltOS offers a stdio interface over USB, serial and the RF + packet link. This provides for control of the device locally or + remotely. This is hooked up to the stdio functions by providing + the standard putchar/getchar/flush functions. These + automatically multiplex the available communication channels; + output is always delivered to the channel which provided the + most recent input. + + === putchar + + .... + void + putchar(char c) + .... + + Delivers a single character to the current console + device. + + === getchar + + .... + char + getchar(void) + .... + + 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. + + === flush + + .... + void + flush(void) + .... + + Flushes the current console device output buffer. Any + pending characters will be delivered to the target device. + + === ao_add_stdio + + .... + void + ao_add_stdio(char (*pollchar)(void), + void (*putchar)(char), + void (*flush)(void)) + .... + + This adds another console device to the available + list. + + '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(&ao_stdin_ready) when it receives + input to tell getchar that more data is available, at + which point 'pollchar' will be called again. + + 'putchar' queues a character for output, flushing if the output buffer is + full. It may block in this case. + + 'flush' forces the output buffer to be flushed. It may + block until the buffer is delivered, but it is not + required to do so. + +== Command line interface + + AltOS includes a simple command line parser which is hooked up + to the stdio interfaces permitting remote control of the + device over USB, serial 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. + + === ao_cmd_register + + .... + void + ao_cmd_register(__code struct ao_cmds *cmds) + .... + + 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: + + .... + \struct ao_cmds { + \ char cmd; + \ void (*func)(void); + \ const char *help; + \}; + .... + '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: + + ao_cmd_success:: + + The command was parsed successfully. There is no need + to assign this value, it is the default. + + ao_cmd_lex_error:: + + 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. + + ao_syntax_error:: + + The command line is invalid for some reason other than + invalid tokens. + + === ao_cmd_lex + + .... + void + ao_cmd_lex(void); + .... + + 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. + + === ao_cmd_put16 + + .... + void + ao_cmd_put16(uint16_t v); + .... + + Writes 'v' as four hexadecimal characters. + + === ao_cmd_put8 + + .... + void + ao_cmd_put8(uint8_t v); + .... + + Writes 'v' as two hexadecimal characters. + + === ao_cmd_white + + .... + void + ao_cmd_white(void) + .... + + 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. + + === ao_cmd_hex + + .... + void + ao_cmd_hex(void) + .... + + 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; + + === ao_cmd_decimal + + .... + void + ao_cmd_decimal(void) + .... + + 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; + + === ao_match_word + + .... + uint8_t + ao_match_word(__code char *word) + .... + + 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. + + === ao_cmd_init + + .... + void + ao_cmd_init(void + .... + + 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. + +== USB target device + + AltOS contains a full-speed USB target device driver. 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. + + 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. + + === ao_usb_flush + + .... + void + ao_usb_flush(void); + .... + + 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. + + === ao_usb_putchar + + .... + void + ao_usb_putchar(char c); + .... + + 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. + + === ao_usb_pollchar + + .... + char + ao_usb_pollchar(void); + .... + + 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. + + === ao_usb_getchar + + .... + char + ao_usb_getchar(void); + .... + + This uses ao_pollchar to receive the next character, + blocking while ao_pollchar returns AO_READ_AGAIN. + + === ao_usb_disable + + .... + void + ao_usb_disable(void); + .... + + 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. + + Note that neither TeleDongle v0.2 nor TeleMetrum v1 + 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. + + === ao_usb_enable + + .... + void + ao_usb_enable(void); + .... + + 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. + + === ao_usb_init + + .... + void + ao_usb_init(void); + .... + + 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. + +== Serial peripherals + + 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. + + To prevent loss of data, AltOS provides receive and transmit + fifos of 32 characters each. + + === ao_serial_getchar + + .... + char + ao_serial_getchar(void); + .... + + Returns the next character from the receive fifo, blocking + until a character is received if the fifo is empty. + + === ao_serial_putchar + + .... + void + ao_serial_putchar(char c); + .... + + Adds a character to the transmit fifo, blocking if the + fifo is full. Starts transmitting characters. + + === ao_serial_drain + + .... + void + ao_serial_drain(void); + .... + + Blocks until the transmit fifo is empty. Used internally + when changing serial speeds. + + === ao_serial_set_speed + + .... + void + ao_serial_set_speed(uint8_t speed); + .... + + 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. + + === ao_serial_init + + .... + void + ao_serial_init(void) + .... + + Initializes the serial peripheral. Call this from 'main' + before jumping to ao_start_scheduler. The default speed + setting is AO_SERIAL_SPEED_4800. + +== CC1111/CC1120/CC1200 Radio peripheral + + === Radio Introduction + + The CC1111, CC1120 and CC1200 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: + + . 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. + + . 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. + + 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. + + 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. + + . 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. + + === ao_radio_set_telemetry + + .... + void + ao_radio_set_telemetry(void); + .... + + 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. + + === ao_radio_set_packet + + .... + void + ao_radio_set_packet(void); + .... + + 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. + + === ao_radio_set_rdf + + .... + void + ao_radio_set_rdf(void); + .... + + 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. + + === ao_radio_idle + + .... + void + ao_radio_idle(void); + .... + + Sets the radio device to idle mode, waiting until it reaches + that state. This will terminate any in-progress transmit or + receive operation. + + === ao_radio_get + + .... + void + ao_radio_get(void); + .... + + Acquires the radio mutex and then configures the radio + frequency using the global radio calibration and channel + values. + + === ao_radio_put + + .... + void + ao_radio_put(void); + .... + + Releases the radio mutex. + + === ao_radio_abort + + .... + void + ao_radio_abort(void); + .... + + Aborts any transmission or reception process by aborting the + associated DMA object and calling ao_radio_idle to terminate + the radio operation. + + === Radio Telemetry + + 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. + + ==== ao_radio_send + + .... + void + ao_radio_send(__xdata struct ao_telemetry *telemetry); + .... + + 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. + + ==== ao_radio_recv + + .... + void + ao_radio_recv(__xdata struct ao_radio_recv *radio); + .... + + 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()). + + === Radio Direction Finding + + In radio direction finding mode, there's just one function to + use + + ==== ao_radio_rdf + + .... + void + ao_radio_rdf(int ms); + .... + + This sends an RDF packet lasting for the specified amount + of time. The maximum length is 1020 ms. + + === Radio Packet Mode + + 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. + + ==== ao_packet_putchar + + .... + void + ao_packet_putchar(char c); + .... + + 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. + + ==== ao_packet_pollchar + + .... + char + ao_packet_pollchar(void); + .... + + 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. + + ==== ao_packet_slave_start + + .... + void + ao_packet_slave_start(void); + .... + + This is available only on the slave side and starts a task + to listen for packet data. + + ==== ao_packet_slave_stop + + .... + void + ao_packet_slave_stop(void); + .... + + Disables the packet slave task, stopping the radio receiver. + + ==== ao_packet_slave_init + + .... + void + ao_packet_slave_init(void); + .... + + 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. + + ==== ao_packet_master_init + + .... + void + ao_packet_master_init(void); + .... + + Adds the 'p' packet forward command to start packet mode. diff --git a/doc/altos.xsl b/doc/altos.xsl deleted file mode 100644 index 6092dfcb..00000000 --- a/doc/altos.xsl +++ /dev/null @@ -1,1612 +0,0 @@ - - - - - AltOS - Altos Metrum Operating System - - - Keith - Packard - - - 2010 - Keith Packard - - - - This document is released under the terms of the - - Creative Commons ShareAlike 3.0 - - license. - - - - - 1.1 - 05 November 2012 - Portable version - - - 0.1 - 22 November 2010 - Initial content - - - - - Overview - - AltOS is a operating system built for a variety of - microcontrollers used in Altus Metrum devices. It has a simple - porting layer for each CPU while providing a convenient - operating enviroment for the developer. AltOS currently - supports three different CPUs: - - - - STM32L series from ST Microelectronics. This ARM Cortex-M3 - based microcontroller offers low power consumption and a - wide variety of built-in peripherals. Altus Metrum uses - this in the TeleMega, MegaDongle and TeleLCO projects. - - - - - CC1111 from Texas Instruments. This device includes a - fabulous 10mW digital RF transceiver along with an - 8051-compatible processor core and a range of - peripherals. This is used in the TeleMetrum, TeleMini, - TeleDongle and TeleFire projects which share the need for - a small microcontroller and an RF interface. - - - - - ATmega32U4 from Atmel. This 8-bit AVR microcontroller is - one of the many used to create Arduino boards. The 32U4 - includes a USB interface, making it easy to connect to - other computers. Altus Metrum used this in prototypes of - the TeleScience and TelePyro boards; those have been - switched to the STM32L which is more capable and cheaper. - - - - Among the features of AltOS are: - - - Multi-tasking. While microcontrollers often don'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. - - - - 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. - - - - Sleep/wakeup scheduling. Taken directly from ancient - Unix designs, these two provide the fundemental scheduling - primitive within AltOS. - - - - Mutexes. As a locking primitive, mutexes are easier to - use than semaphores, at least in my experience. - - - - Timers. Tasks can set an alarm which will abort any - pending sleep, allowing operations to time-out instead of - blocking forever. - - - - - - 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: - - 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(); - } - - As you can see, a long sequence of subsystems are initialized - and then the scheduler is started. - - - - AltOS Porting Layer - - AltOS provides a CPU-independent interface to various common - microcontroller subsystems, including GPIO pins, interrupts, - SPI, I2C, USB and asynchronous serial interfaces. By making - these CPU-independent, device drivers, generic OS and - application code can all be written that work on any supported - CPU. Many of the architecture abstraction interfaces are - prefixed with ao_arch. - -
- Low-level CPU operations - - These primitive operations provide the abstraction needed to - run the multi-tasking framework while providing reliable - interrupt delivery. - -
- ao_arch_block_interrupts/ao_arch_release_interrupts - - static inline void - ao_arch_block_interrupts(void); - - static inline void - ao_arch_release_interrupts(void); - - - These disable/enable interrupt delivery, they may not - discard any interrupts. Use these for sections of code that - must be atomic with respect to any code run from an - interrupt handler. - -
-
- ao_arch_save_regs, ao_arch_save_stack, - ao_arch_restore_stack - - static inline void - ao_arch_save_regs(void); - - static inline void - ao_arch_save_stack(void); - - static inline void - ao_arch_restore_stack(void); - - - These provide all of the support needed to switch between - tasks.. ao_arch_save_regs must save all CPU registers to the - current stack, including the interrupt enable - state. ao_arch_save_stack records the current stack location - in the current ao_task structure. ao_arch_restore_stack - switches back to the saved stack, restores all registers and - branches to the saved return address. - -
-
- ao_arch_wait_interupt - - #define ao_arch_wait_interrupt() - - - This stops the CPU, leaving clocks and interrupts - enabled. When an interrupt is received, this must wake up - and handle the interrupt. ao_arch_wait_interrupt is entered - with interrupts disabled to ensure that there is no gap - between determining that no task wants to run and idling the - CPU. It must sleep the CPU, process interrupts and then - disable interrupts again. If the CPU doesn't have any - reduced power mode, this must at the least allow pending - interrupts to be processed. - -
-
-
- GPIO operations - - These functions provide an abstract interface to configure and - manipulate GPIO pins. - -
- GPIO setup - - These macros may be invoked at system initialization time to - configure pins as needed for system operation. One tricky - aspect is that some chips provide direct access to specific - GPIO pins while others only provide access to a whole - register full of pins. To support this, the GPIO macros - provide both port+bit and pin arguments. Simply define the - arguments needed for the target platform and leave the - others undefined. - -
- ao_enable_output - - #define ao_enable_output(port, bit, pin, value) - - - Set the specified port+bit (also called 'pin') for output, - initializing to the specified value. The macro must avoid - driving the pin with the opposite value if at all - possible. - -
-
- ao_enable_input - - #define ao_enable_input(port, bit, mode) - - - Sets the specified port/bit to be an input pin. 'mode' is - a combination of one or more of the following. Note that - some platforms may not support the desired mode. In that - case, the value will not be defined so that the program - will fail to compile. - - - - AO_EXTI_MODE_PULL_UP. Apply a pull-up to the pin; a - disconnected pin will read as 1. - - - - - AO_EXTI_MODE_PULL_DOWN. Apply a pull-down to the pin; - a disconnected pin will read as 0. - - - - - 0. Don't apply either a pull-up or pull-down. A - disconnected pin will read an undetermined value. - - - - -
-
-
- Reading and writing GPIO pins - - These macros read and write individual GPIO pins. - -
- ao_gpio_set - - #define ao_gpio_set(port, bit, pin, value) - - - Sets the specified port/bit or pin to the indicated value - -
-
- ao_gpio_get - - #define ao_gpio_get(port, bit, pin) - - - Returns either 1 or 0 depending on whether the input to - the pin is high or low. - -
-
-
-
- - Programming the 8051 with SDCC - - 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. - - - When built on other architectures, the various SDCC-specific - symbols are #defined as empty strings so they don't affect the compiler. - -
- 8051 memory spaces - - 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. - -
- __data - - 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. - -
-
- __idata - - 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. - -
-
- __xdata - - 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. - -
-
- __pdata - - 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. - -
-
- __code - - 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. - -
-
- __bit - - 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. - -
-
- __sfr, __sfr16, __sfr32, __sbit - - Access to physical registers in the device use this mode - which declares the variable name, its type and the - address it lives at. No memory is allocated for these - variables. - -
-
-
- Function calls on the 8051 - - 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. - -
- __reentrant functions - - 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. - - - 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. - -
-
- Non __reentrant functions - - 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. - - - 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. - -
-
- __interrupt functions - - 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. - -
-
- __critical functions and statements - - 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. AltOS doesn't use this form in shared - code as other compilers wouldn't know what to do. Use - ao_arch_block_interrupts and ao_arch_release_interrupts instead. - -
-
-
- - Task functions - - This chapter documents how to create, destroy and schedule AltOS tasks. - -
- ao_add_task - - void - ao_add_task(__xdata struct ao_task * task, - void (*start)(void), - __code char *name); - - - 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. - -
-
- ao_exit - - void - ao_exit(void) - - - This terminates the current task. - -
-
- ao_sleep - - void - ao_sleep(__xdata void *wchan) - - - 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. - - - Because ao_wakeup wakes every task waiting on a particular - location, ao_sleep should be used in a loop that first checks - the desired condition, blocks in ao_sleep and then rechecks - until the condition is satisfied. If the location may be - signaled from an interrupt handler, the code will need to - block interrupts around the block of code. Here's a complete - example: - - ao_arch_block_interrupts(); - while (!ao_radio_done) - ao_sleep(&ao_radio_done); - ao_arch_release_interrupts(); - - -
-
- ao_wakeup - - void - ao_wakeup(__xdata void *wchan) - - - Wake all tasks blocked on 'wchan'. This makes them - available to be run again, but does not actually switch - to another task. Here's an example of using this: - - if (RFIF & RFIF_IM_DONE) { - ao_radio_done = 1; - ao_wakeup(&ao_radio_done); - RFIF &= ~RFIF_IM_DONE; - } - - Note that this need not block interrupts as the ao_sleep block - can only be run from normal mode, and so this sequence can - never be interrupted with execution of the other sequence. - -
-
- ao_alarm - - void - ao_alarm(uint16_t delay); - - void - ao_clear_alarm(void); - - - 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. ao_clear_alarm resets any pending - alarm so that it doesn't fire at some arbitrary point in the - future. - - ao_alarm(ao_packet_master_delay); - ao_arch_block_interrupts(); - while (!ao_radio_dma_done) - if (ao_sleep(&ao_radio_dma_done) != 0) - ao_radio_abort(); - ao_arch_release_interrupts(); - ao_clear_alarm(); - - In this example, a timeout is set before waiting for - incoming radio data. If no data is received before the - timeout fires, ao_sleep will return 1 and then this code - will abort the radio receive operation. - -
-
- ao_start_scheduler - - void - ao_start_scheduler(void); - - - This is called from 'main' when the system is all - initialized and ready to run. It will not return. - -
-
- ao_clock_init - - void - ao_clock_init(void); - - - This initializes the main CPU clock and switches to it. - -
-
- - Timer Functions - - AltOS sets up one of the CPU 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 sensors to - collect current data readings. Doing this from the ISR ensures - that the values are sampled at a regular rate, independent - of any scheduling jitter. - -
- ao_time - - uint16_t - ao_time(void) - - - Returns the current system tick count. Note that this is - only a 16 bit value, and so it wraps every 655.36 seconds. - -
-
- ao_delay - - void - ao_delay(uint16_t ticks); - - - Suspend the current task for at least 'ticks' clock units. - -
-
- ao_timer_set_adc_interval - - void - ao_timer_set_adc_interval(uint8_t interval); - - - 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. - -
-
- ao_timer_init - - void - ao_timer_init(void) - - - This turns on the 100Hz tick. It is required for any of the - time-based functions to work. It should be called by 'main' - before ao_start_scheduler. - -
-
- - AltOS Mutexes - - 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. - -
- ao_mutex_get - - void - ao_mutex_get(__xdata uint8_t *mutex); - - - Acquires the specified mutex, blocking if the mutex is - owned by another task. - -
-
- ao_mutex_put - - void - ao_mutex_put(__xdata uint8_t *mutex); - - - Releases the specified mutex, waking up all tasks waiting - for it. - -
-
- - DMA engine - - The CC1111 and STM32L both contain a useful bit of extra - hardware in the form of a number of 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 both internal and external devices. - - - Because the CC1111 and STM32L DMA engines are different, the - interface to them is also different. As the DMA engines are - currently used to implement platform-specific drivers, this - isn't yet a problem. - - - 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. - - - 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. - -
- CC1111 DMA Engine -
- ao_dma_alloc - - uint8_t - ao_dma_alloc(__xdata uint8_t *done) - - - Allocate a DMA engine, returning the identifier. '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. - -
-
- ao_dma_set_transfer - - void - ao_dma_set_transfer(uint8_t id, - void __xdata *srcaddr, - void __xdata *dstaddr, - uint16_t count, - uint8_t cfg0, - uint8_t cfg1) - - - 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. - -
-
- ao_dma_start - - void - ao_dma_start(uint8_t id); - - - Arm the specified DMA engine and await a signal from - either hardware or software to start transferring data. - -
-
- ao_dma_trigger - - void - ao_dma_trigger(uint8_t id) - - - Trigger the specified DMA engine to start copying data. - -
-
- ao_dma_abort - - void - ao_dma_abort(uint8_t id) - - - Terminate any in-progress DMA transaction, marking its - 'done' variable with the AO_DMA_ABORTED bit. - -
-
-
- STM32L DMA Engine -
- ao_dma_alloc - - uint8_t ao_dma_done[]; - - void - ao_dma_alloc(uint8_t index); - - - Reserve a DMA engine for exclusive use by one - driver. - -
-
- ao_dma_set_transfer - - void - ao_dma_set_transfer(uint8_t id, - void *peripheral, - void *memory, - uint16_t count, - uint32_t ccr); - - - Initializes the specified dma engine to copy data between - 'peripheral' and 'memory' for 'count' units. 'ccr' is a - value directly out of the STM32L documentation and tells the - DMA engine what the transfer unit size, direction and step - are. - -
-
- ao_dma_set_isr - - void - ao_dma_set_isr(uint8_t index, void (*isr)(int)) - - - This sets a function to be called when the DMA transfer - completes in lieu of setting the ao_dma_done bits. Use this - when some work needs to be done when the DMA finishes that - cannot wait until user space resumes. - -
-
- ao_dma_start - - void - ao_dma_start(uint8_t id); - - - Arm the specified DMA engine and await a signal from either - hardware or software to start transferring data. - 'ao_dma_done[index]' 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. - -
-
- ao_dma_done_transfer - - void - ao_dma_done_transfer(uint8_t id); - - - Signals that a specific DMA engine is done being used. This - allows multiple drivers to use the same DMA engine safely. - -
-
- ao_dma_abort - - void - ao_dma_abort(uint8_t id) - - - Terminate any in-progress DMA transaction, marking its - 'done' variable with the AO_DMA_ABORTED bit. - -
-
-
- - Stdio interface - - AltOS offers a stdio interface over USB, serial and the RF - packet link. This provides for control of the device locally or - remotely. This is hooked up to the stdio functions by providing - the standard putchar/getchar/flush functions. These - automatically multiplex the available communication channels; - output is always delivered to the channel which provided the - most recent input. - -
- putchar - - void - putchar(char c) - - - Delivers a single character to the current console - device. - -
-
- getchar - - char - getchar(void) - - - 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. - -
-
- flush - - void - flush(void) - - - Flushes the current console device output buffer. Any - pending characters will be delivered to the target device. - -
-
- ao_add_stdio - - void - ao_add_stdio(char (*pollchar)(void), - void (*putchar)(char), - void (*flush)(void)) - - - This adds another console device to the available - list. - - - '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(&ao_stdin_ready) when it receives - input to tell getchar that more data is available, at - which point 'pollchar' will be called again. - - - 'putchar' queues a character for output, flushing if the output buffer is - full. It may block in this case. - - - 'flush' forces the output buffer to be flushed. It may - block until the buffer is delivered, but it is not - required to do so. - -
-
- - Command line interface - - AltOS includes a simple command line parser which is hooked up - to the stdio interfaces permitting remote control of the device - over USB, serial 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. - -
- ao_cmd_register - - void - ao_cmd_register(__code struct ao_cmds *cmds) - - - 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: - - struct ao_cmds { - char cmd; - void (*func)(void); - const char *help; - }; - - '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: - - - ao_cmd_success - - - The command was parsed successfully. There is no - need to assign this value, it is the default. - - - - - ao_cmd_lex_error - - - 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. - - - - - ao_syntax_error - - - The command line is invalid for some reason other - than invalid tokens. - - - - - -
-
- ao_cmd_lex - - void - ao_cmd_lex(void); - - - 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. - -
-
- ao_cmd_put16 - - void - ao_cmd_put16(uint16_t v); - - - Writes 'v' as four hexadecimal characters. - -
-
- ao_cmd_put8 - - void - ao_cmd_put8(uint8_t v); - - - Writes 'v' as two hexadecimal characters. - -
-
- ao_cmd_white - - void - ao_cmd_white(void) - - - 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. - -
-
- ao_cmd_hex - - void - ao_cmd_hex(void) - - - 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; - -
-
- ao_cmd_decimal - - void - ao_cmd_decimal(void) - - - 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; - -
-
- ao_match_word - - uint8_t - ao_match_word(__code char *word) - - - 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. - -
-
- ao_cmd_init - - void - ao_cmd_init(void - - - 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. - -
-
- - USB target device - - AltOS contains a full-speed USB target device driver. 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. - - - 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. - -
- ao_usb_flush - - void - ao_usb_flush(void); - - - 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. - -
-
- ao_usb_putchar - - void - ao_usb_putchar(char c); - - - 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. - -
-
- ao_usb_pollchar - - char - ao_usb_pollchar(void); - - - 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. - -
-
- ao_usb_getchar - - char - ao_usb_getchar(void); - - - This uses ao_pollchar to receive the next character, - blocking while ao_pollchar returns AO_READ_AGAIN. - -
-
- ao_usb_disable - - void - ao_usb_disable(void); - - - 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. - - - 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. - -
-
- ao_usb_enable - - void - ao_usb_enable(void); - - - 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. - -
-
- ao_usb_init - - void - ao_usb_init(void); - - - 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. - -
-
- - Serial peripherals - - 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. - - - To prevent loss of data, AltOS provides receive and transmit - fifos of 32 characters each. - -
- ao_serial_getchar - - char - ao_serial_getchar(void); - - - Returns the next character from the receive fifo, blocking - until a character is received if the fifo is empty. - -
-
- ao_serial_putchar - - void - ao_serial_putchar(char c); - - - Adds a character to the transmit fifo, blocking if the - fifo is full. Starts transmitting characters. - -
-
- ao_serial_drain - - void - ao_serial_drain(void); - - - Blocks until the transmit fifo is empty. Used internally - when changing serial speeds. - -
-
- ao_serial_set_speed - - void - ao_serial_set_speed(uint8_t speed); - - - 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. - -
-
- ao_serial_init - - void - ao_serial_init(void) - - - Initializes the serial peripheral. Call this from 'main' - before jumping to ao_start_scheduler. The default speed - setting is AO_SERIAL_SPEED_4800. - -
-
- - CC1111 Radio peripheral -
- Radio Introduction - - 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: - - - - 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. - - - - - 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. - - - 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. - - - 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. - - - - - 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. - - - - -
-
- ao_radio_set_telemetry - - void - ao_radio_set_telemetry(void); - - - 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. - -
-
- ao_radio_set_packet - - void - ao_radio_set_packet(void); - - - 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. - -
-
- ao_radio_set_rdf - - void - ao_radio_set_rdf(void); - - - 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. - -
-
- ao_radio_idle - - void - ao_radio_idle(void); - - - Sets the radio device to idle mode, waiting until it reaches - that state. This will terminate any in-progress transmit or - receive operation. - -
-
- ao_radio_get - - void - ao_radio_get(void); - - - Acquires the radio mutex and then configures the radio - frequency using the global radio calibration and channel - values. - -
-
- ao_radio_put - - void - ao_radio_put(void); - - - Releases the radio mutex. - -
-
- ao_radio_abort - - void - ao_radio_abort(void); - - - Aborts any transmission or reception process by aborting the - associated DMA object and calling ao_radio_idle to terminate - the radio operation. - -
-
- Radio Telemetry - - 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. - -
- ao_radio_send - - void - ao_radio_send(__xdata struct ao_telemetry *telemetry); - - - 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. - -
-
- ao_radio_recv - - void - ao_radio_recv(__xdata struct ao_radio_recv *radio); - - - 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()). - -
-
-
- Radio Direction Finding - - In radio direction finding mode, there's just one function to - use - -
- ao_radio_rdf - - void - ao_radio_rdf(int ms); - - - This sends an RDF packet lasting for the specified amount - of time. The maximum length is 1020 ms. - -
-
-
- Radio Packet Mode - - 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. - -
- ao_packet_putchar - - void - ao_packet_putchar(char c); - - - 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. - -
-
- ao_packet_pollchar - - char - ao_packet_pollchar(void); - - - 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. - -
-
- ao_packet_slave_start - - void - ao_packet_slave_start(void); - - - This is available only on the slave side and starts a task - to listen for packet data. - -
-
- ao_packet_slave_stop - - void - ao_packet_slave_stop(void); - - - Disables the packet slave task, stopping the radio receiver. - -
-
- ao_packet_slave_init - - void - ao_packet_slave_init(void); - - - 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. - -
-
- ao_packet_master_init - - void - ao_packet_master_init(void); - - - Adds the 'p' packet forward command to start packet mode. - -
-
-
-
-- 2.30.2