a small operating system for microcontrollers
Introduction
Concept and Goals
mµOS is supposed to be a general purpose Operating-System for small Microcontrollers. With proper configuration it scales down to be useable on AVR ATtiny MPUs. The main focus is on getting Projects efficently and robustly done in short time.
The core of mµOS is a Scheduler which serves function calls from a few queues (Timer and 2 Priorities by default). This approach was choosen to minimize resource demands. Everything is served only by one Stack. There are no threads or context switches, but there are some facilities to call the Scheduler recursively.
Users code always runs synchronously from these queues which are scheduled by the mainloop provided by mµOS. Interrupts are handled by drivers and do the bare minimum necessary to serve the request, postpone any further processing themself onto the work queues.
This means that there are less possiblities of race conditions and in many cases a user doesn’t need to care for synchronization (unless writing drivers).
Still any resource which is not managed by mµOS is available to the user and can be freely accessed and configured. This is a deliberate design decision to put mµOS between bare-metal programming and some more complete OS.
Whenever reasonable mµOS reuses existing Platform and C libraries, but also adds its own concepts and API’s breaking history with POSIX and other common API’s whenever there are some reasons to do so.
Features
-
Most things in mµOS are configurable in very small detail. The build system only links parts which are referenced into the resulting firmware, thus it hard to tell what size mµOS has, because it depends on the application build on top. When slimmed down then sizes smaller than 1kByte and RAM usage of only few bytes are possible.
-
The mainloop pops functions from work queues, executing them and puts the µC to sleep when nothing needs to be done. Any Interrupt wakes the mainloop, the Interupt handler may put work onto the queues which in turn get called as soon the interrupt handler finishes. Scheduling latency is typically 20µs on a 8MHz AVR with the code yet unoptimized.
-
muos_yield() and muos_wait() calls can be used to suspend the current job and call the mainloop recursively.
-
mµOS uses one Hardware counter as main-timer, any timer, 8 or 16 bit can be used for this. The prescaler of this timer is configureable to give a lot freedom on the desired clock granularity and interrupt load. The overflow interrupt is used to increment a global counter and one of the output compare units is used to schedule wakeups of the mainloop.
-
There are (configureable) queues:
-
clpq the timer queue, using a sliding window priority-queue implementation to schedule work within near future, attainable timespans depend on the timer prescaler and shortclock type. clpq jobs are executed with the highest priority.
-
hpq and bgq are simple queues for high and low priority jobs. The scheduler executes from the front of this queues, first all jobs in the hpq and then all jobs in the bgq. One can push new jobs to then end or to the front of these queues, giving roughly 4 different priority levels. One should split work to be done into reasonable small parts and queue them to either work queue. Drivers are also supposed to do most of the work outside of Interrupts by pushing them to the queues.
-
-
Errors can happen asynchronously in interrupt handlers. Any error has a correspondending bit in a bit-array which flags its occurence. When any errors happened in interrupt handlers are not handled there, then the mainloop first calls a user-defined error handling function.
Supported Hardware
Hardware support is added on demand. The following Platforms are tested and known to work. But not all features are supported on all microcontrollers. Other ones may work and can be added with little effort.
Future plans include to port mµOS to STM32 and other platforms.
AVR
- attiny84
-
Bare chip, programmed with usbasp/avrdude.
- attiny85
-
Digispark (http://digistump.com/products/1) with micronucleus bootloader.
- atmega328p
-
Arduino Nano. This is the reference Platform. Most things are tested and developed here.
Infrastructure
Requirements
mµOS is developed on Linux with GNU-Tools. As far these are available it should be compilable on other Operating Systems as well.
When versions are noted, this are the versions which are proven to work, if for some reason only older versions are available and work for you, please report this. The exact package names may differ depending on the Distribution.
For building Projects
- make (>= 4.0)
-
GNU make
- gcc-avr (>= 4.7)
-
GNU C compiler, version 4.7 is needed at least for __flash memory support. MµOS relies on -flto which will improve with newer Compiler versions.
- avr-libc (>= 1.8)
-
The GNU libc for AVR processors
- binutils-avr
-
Tools for building
For flashing
- avrdude (>= 6.1)
-
Needed for flashing via USBasp or Arduino bootloaders (and possibly some more in future)
- micronucleus (>= 2.0)
-
Needed for flashing micronucleus based bootloaders (DigiSpark and other attiny based products)
Revision control
- git
-
For cloning and maintaining the mµOS source tree
Building the Documentation
- lua
-
used by the pipadoc documentation extractor (included in mµOS)
- asciidoc
-
for generating HTML
- a2x, dblatex, …
-
For generating PDF documentation
Development
MµOS are developed for the avr-gcc/avr-libc toolchain, on 8-bit AVR’s (ATTiny, ATMega) with some portability in mind. It is later planned to extend this to other platforms like 32bit ARM (STM32). Direction is still to stay as simple as the avr toolchain is to make mirgration for any programmer coming from Atmel programming to STM32 (and perhaps others) as easy as possible. Still we will make no compromises in functionality where required.
As Free Software project, mµOS is open to any contributor. Development will progress on demand. Whatever is needed will be integrated.
mµOS comes comes with a Makefile managing all project tasks. For the example Project bundled with the mµOS source, everything is configured inside this Makefile (this may be refined in future).
Build System
The build-system tries to do exact dependency tracking and building things in parallel. The Makefile in the users src/ directory includes the main muos/muos.mk Makefile, which in turn includes sub Makefiles for to support different flashing tools and different platforms.
By default the make output is silent. If one adds the "MAKE_DEBUG=1" flag to the make invocation then parallel builds are disabled and the it produces verbose output.
The most importnat build targets are:
- all
-
Builds everything
- upload
-
Flashes the firmware to the target
- doc
-
Build the documentation
- issues
-
Extracts
muos_issues.txt
and buildsmuos_issues.html
- clean
-
Deletes anything that got build
Documenation
The Documentation is writiten in asciidoc and distributed in serveral .txt
files and added as special comments to the sources. The pipadoc tool
shipped with mµOS is used to extract this Documentation into single text
files. For building the Documentation one needs a lua interpreter, the
asciidoc toolchain and perhaps dblatex for pdf generation.
Issues
Issues are tagged inside source and documentation files. pipadoc takes care
of generating a muos_issues.txt
and muos_issues.html
. There are
deliberately only 3 tags for issues:
-
FIXME
-
Known bug/problem which leads to failures and must be fixed.
-
TODO
-
Intended functionality which is not yet implemented/finished. If not used this should not run into errorneous behaviour.
-
PLANNED
-
Notes about future features and ideas, may be subject to refinement and should not affect the existing functionality at all.
The build system also prints these issues lists on stderr, so that the relevant source positions are navigateable from an IDE. Which issues are presented there is decided on the currently checked out git branch.
In master only FIXME
are shown. In devel FIXME
and TODO
are shown.
for any other branch FIXME
, TODO
are shown and PLANNED
are filtered by
that only issues where the pathname matches the current branchname are shown.
MµOS
The Mainloop
In default configuration mµOS provides a mainloop which schedules queues holding user-defined functions to be called. main() is part of mµOS and not defined in user programs.
At start up mµOS first schedules a (configurable) init() function which does the initialization defined by the user. This init is called with the timer and drivers still uninitialized/stopped, second after the init() function muos_start() is scheduled which then sets up all drivers and starts the timer. Upon its completion everything is initialized and the system runs.
Some drivers provide configurable hooks which are scheduled when some event
happened (examples are like lineedit completed with the user pressing
[RETURN]
)
For every iteration the mainloop sets a global variable to the current time which then can be queried with muos_now(). This time guaranteed to stay stable for the whole mainloop iteration with only the exceptions that nested mainloop invocation (muos_wait() and muos_yield() will update it). Using this should be the preferred way to get the current time over the direct clock api’s because it is faster to access and provides the necessary stability as time tag for this iteration.
If there are any pending errors left from asynchronous calls (Interrupts and Drivers) it calls a user defined error handler. This is done at highest priority with interrupts still disabled. The error-handler is called only once per mainloop iteration, even if it leaves errors pending.
Then the mainloop checks if there is any work on the queues (see below) and executes these functions appropriately. When there is nothing left to do it either puts the µC into sleep or, for very short times, does a busy loop to ensure exact timing.
Scheduler Queues
By default there are 3 queues served by the mainloop. Each of them can be configured in some details and disabled if not required.
clock priority queue
Uses the clock to wake the mainloop for scheduling events in near future. near future is defined by the clock configuration, times longer than the muos_shortclock type can not be handled. This times are also affected by the timers prescaler, the faster the clock runs, the shorter are the time spans it can handle. Common ranges are from few milliseconds to many hours.
Events scheduled by the clpq have the highest priority, but are still called synchronously within the mainloop and interrupts enabled.
clpq API
The priority queue uses a sliding-window implementation, this allows to add times covering the while range of the muos_shortclock datatype.
The clpq uses the priority-queue implementation (and thus the types) from
void muos_clpq_at (
muos_spriq_priority base,
muos_spriq_priority when,
muos_spriq_function what
)
-
base
-
time used as relative base for the timing calculation
-
when
-
offset to base for the destination time
-
what
-
function to be scheduled
For scheduling on time base
has some more contraints to handle
overflows correctly. The clpq handles that, refer to the source
for details.
void muos_repeat (
const struct muos_spriq_entry* event,
muos_spriq_priority when
)
-
event
-
the event pointer passed which get passed into queued functions
-
when
-
time when to repeat this event
clpq configuration
Only the length is configured for the clpq, most of its configurations come from the clock and spriq configuration see below.
- MUOS_CLPQ_LENGTH
-
Number of entries the scheduling Queue can hold, set to 0 to disable the clpq
Work Queues
The mainloop will schedule work on the Workqueues until nothing left to be done. Work is always popped from the front of these queues. There are API’s to push work on the back (preferred case) and on the front of this queues. Pushing something on the front of a queue means that it will be scheduled immediately before anything else pending. Thus one get 4 priority levels for work from this queues.
One can push an optional intptr_t argument together with each scheduled function to give a limited ability to pass data around. If this argument is used it will use additional memory in the queue.
The underlying queue implementation in [lib_queue] can be configured in several ways.
high priority queue
Scheduled with a priority below the clpq and above bgq.
hpq API
muos_error muos_hpq_pushback (muos_queue_function fn)
muos_error muos_hpq_pushback_arg (muos_queue_function_arg fn, intptr_t arg)
muos_error muos_hpq_pushfront (muos_queue_function fn)
muos_error muos_hpq_pushfront_arg (muos_queue_function_arg fn, intptr_t arg)
muos_error muos_hpq_pushback_isr (muos_queue_function fn, bool schedule)
muos_error muos_hpq_pushback_arg_isr (muos_queue_function_arg fn, intptr_t arg, bool schedule)
muos_error muos_hpq_pushfront_isr (muos_queue_function fn, bool schedule)
muos_error muos_hpq_pushfront_arg_isr (muos_queue_function_arg fn, intptr_t arg, bool schedule)
-
fn
-
function to schedule
-
arg
-
argument to pass to the function
-
schedule
-
when false the scheduler will not check for functions scheduled after a wake up from sleep.
The *_isr variants of these functions are intended to be called from Interrupt handlers or contexts where interrupts are already disabled.
These functions return muos_success on success and muos_error_hpq_overflow on error.
intptr_t muos_hpq_pop_isr (void)
removes and returns the first element (argument) from the hpq. Must be called while interrupts are still disabled at the start of scheduled functions. (Note: no safety net when the caller didn’t push an argument)
hpq configuration
- MUOS_HPQ_LENGTH
-
How many entries the high priority queue can hold, set to 0 to disable the hpq
background queue
Scheduled with the lowest priority.
bgq API
muos_error muos_bgq_pushback (muos_queue_function fn)
muos_error muos_bgq_pushback_arg (muos_queue_function_arg fn, intptr_t arg)
muos_error muos_bgq_pushfront (muos_queue_function fn)
muos_error muos_bgq_pushfront_arg (muos_queue_function_arg fn, intptr_t arg)
muos_error muos_bgq_pushback_isr (muos_queue_function fn, bool schedule)
muos_error muos_bgq_pushback_arg_isr (muos_queue_function_arg fn, intptr_t arg, bool schedule)
muos_error muos_bgq_pushfront_isr (muos_queue_function fn, bool schedule)
muos_error muos_bgq_pushfront_arg_isr (muos_queue_function_arg fn, intptr_t arg, bool schedule)
-
fn
-
function to schedule
-
arg
-
argument to pass to the function
-
schedule
-
when false the scheduler will not check for functions scheduled after a wake up from sleep.
The *_isr variants of these functions are intended to be called from Interrupt handlers or contexts where interrupts are already disabled.
These functions return muos_success on success and muos_error_bgq_overflow on error.
intptr_t muos_bgq_pop_isr (void)
removes and returns the first element (argument) from the bgq. Must be called while interrupts are still disabled at the start of scheduled functions. (Note: no safety net when the caller didn’t push an argument)
bgq configuration
- MUOS_BGQ_LENGTH
-
How many entries the background queue can hold, set to 0 to disable the bgq
Mainloop Control
API
typedef bool (*muos_wait_fn)(intptr_t)
muos_error muos_wait (muos_wait_fn fn, intptr_t param, muos_shortclock timeout)
-
fn
-
function checking for some condition, must return false while the condition is not met and finally true on success. Can be NULL, then muos_wait() uses only the timeout for the wait.
-
param
-
an optinal intptr_t argument passed to the test function
-
timeout
-
time limit for the wait
Calls a recursive mainloop and with testing for a given condition for some time.
Care must be taken that I/O (and other things) are not anymore in order when the mainloop is called recursively. Often it is better to avoid waiting and divide the work into smaller tasks which are put in order on the work queues.
Because of stack limits entering the mainloop recursively is limited. One should always expect that a wait can return instantly with muos_warn_sched_depth.
muos_wait() is only available when MUOS_SCHED_DEPTH is defined.
- muos_success
-
the wait condition got met
- muos_warn_sched_depth
-
depth limit for recursive mainloops hit
- muos_warn_wait_timeout
-
timed out
muos_error muos_yield (uint8_t count)
-
count
-
number of jobs to schedule, must be less than 254
Calls a recursive mainloop executing at most count jobs. A job here is any one thing queued on any of the works queues. Returns if nothing left to do the count limit got reached or the scheduler depth limit got reached. Same precautions as on muos_wait() apply.
Yielding is applicable when one has code which loops for some extended time but shall not stall the work to be done and this code will never be called recursively.
muos_yield() is only available when MUOS_SCHED_DEPTH is defined.
- muos_warn_sched_depth
-
depth limit for recursive mainloops hit
- muos_success
-
yielded at most count times
configuration
- MUOS_INITFN
-
Name of the user-defined initialization function. This function is pushed on the hpq (or, if not available the bgq) when starting up. It is responsible for initializing the system. Interrupts are still disabled and the clock is stopped and will be started after this init function returns.
- MUOS_SCHED_DEPTH
-
Set the maximum depth for nested scheduler calls (muos_wait() muos_yield()). When this depth is exceeded, these calls return immediately, flagging muos_warn_sched_depth. When not defined then the wait and yield functions become unavailable
- MUOS_SCHED_SLEEP
-
Set sleep mode for the main loop if supported
- MUOS_YIELD_DEPTH
-
Set the maximum depth for scheduler muos_yield() calls. When this depth is exceeded, the calls return immediately with muos_warn_sched_depth. When not defined then the yield function becomes unavailable.
The Clock
The clock is one of the most important parts of the OS. Many core parts and other drivers rely on it.
Only one hardware timer is used mµOS to implement the global clock. This can be any timer (8bit, 16bit). MµOS uses its overflow interrupt and needs one compare-match unit timer for scheduling wake ups. The configuration of this timer (prescaler, which hardware timer to be used) is fully customize-able to the user.
The clock is started at the begin of the operations (after the users init job got called) and then left freely running.
The clock API also holds a function to calibrate the µC main frequency with some external signal, as far this is supported by the hardware.
The full time is defined by the global overflow counter and the timers internal hardware register. This overflow counter can be selected from various unsigned integer types (16, 32, 64 bit wide), the internal timer counter register extends this value by its size, enabling rather high resolution clocks from 24 up to 80 bits precision. Depending on the circumstances one should select a proper size so that overflows don’t happen or do not matter.
There are following types used:
-
muos_clock
-
Generic type used for moderate long time spans. Depending on configuration it overflows rarely (or preferably never). This is the primary datatype for the clock and the global overflow counter
-
muos_shortclock
-
Type for short time intervals. Used in the clock priority for events which are scheduled within shorter times.
-
muos_hwclock
-
The type of the hardware timer. Usually 8 or 16 bits wide
-
muos_fullclock
-
a structure containing the whole clock state with the high bits stored as muos_clock and the low bits stored as muos_hwclock. When properly configured this state should never overflow for the application run time.
-
The default for the overflow counter is uint32_t, calculations show that 16bit makes hardly any sense, because it would overflow quite often even in slow increment configurations.
-
More than 32 bit is only needed for fast running clocks or when very long run times are intended.
-
Choosing 16 bit hwclock when it is available will have less interrupt load but needs more memory. When in doubt, it is not mandatory when the timer prescaler is not on the fastest setting. This is especially useful if only one 16 bit timer is available and is needed for something else. However some mµOS drivers may require a 16 bit timer.
-
muos_clock alone overflows quite often but using a 64 bit datatype for it would need a lot memory in the queues (and 64 bit math libs).
API
MUOS_CLOCK_SECONDS(t)
MUOS_CLOCK_MILLISECONDS(t)
MUOS_CLOCK_MICROSECONDS(t)
-
t
-
time to be converted
The clock runs on timer ticks. These macros convert time from second, ms, µs to ticks. Because this needs to result in a integer number of ticks, the result might be inexact and add a slight drift.
The correct configuration of F_CPU is mandatory for these macros to work correctly.
typedef struct {
muos_clock coarse;
muos_hwclock fine;
} muos_fullclock;
-
coarse
-
the global counter for hardware overflows
-
fine
-
the hardware part of the clock
This is the type with the widest range. With sane configurations it can be ensured that muos_fullclock never overflows. The drawback is that it needs the most memory for its representaton which becomes a problem when storing multiple timestamps in queues. Most often the smaller datatypes muos_clock and muos_shortclock are more approbiate when one handles overflows correctly.
muos_clock muos_now (void)
This function is very cheap and gives a consistent time throughout the whole mainloop iteration.
Returns time if each mainloop (also recursive from muos_wait() and muos_yield()) iteration start.
muos_clock muos_clock_now (void)
muos_clock muos_clock_now_isr (void)
Returns the current time as queried from the hardware.
muos_shortclock muos_clock_shortnow (void)
muos_shortclock muos_clock_shortnow_isr (void)
Returns the current time as queried from the hardware, using the muos_shortclock datatype.
muos_fullclock muos_clock_fullnow (void)
muos_fullclock muos_clock_fullnow_isr (void)
Returns the current time as queried from the hardware, using the muos_fullclock datatype.
muos_clock muos_clock_elapsed (muos_clock now, muos_clock start)
-
now
-
End of the timespan to be calculated
-
start
-
Begin of the timespan to to be calculated
returns the time difference between now and start. The result is always positive or zero. Simple overflows on the arguments are respected. Thus the full range of muos_clock is available.
void muos_clock_calibrate (const muos_clock now, const muos_clock sync)
-
now
-
Time at which the external sync signal happened
-
sync
-
Timespan which should be elapsed since the last call of this function
For µC’s which support it, the main frequency can be calibrated by some external signal.
For example with some 1 second pulse from a RTC one could call
muos_clock_calibrate (muos_clock_now(), MUOS_CLOCK_SECONDS(1))
upon receiving this signal.
There is a special case that if sync is 0 then only the internal state is recorded but no frequency calibration is executed. This is to be used for initialization or when the time elapsed since the last call can’t be determined.
Note that frequency calibration is often too coarse to archive perfect synchronization. You should expect some drift remaining. The configuration specially includes a deadband for this. Otherwise when calibration tries to constantly change the frequency there would be very much jitter on the timing.
configuration
- MUOS_CLOCK_CALIBRATE
-
When defined the frequency calibration API for syncing the µC frequency clock with some external signal is enabled.
- MUOS_CLOCK_CALIBRATE_DEADBAND
-
Adds a deadband/range around the target clock. On µC where the clock calibration is rather coarse an exact match can not be reached and constant readjustments will result in high jitter.
- MUOS_CLOCK_CALIBRATE_MAX_DEVIATION
-
When the time elapsed since the last sync differs more than +/- this value, the calibration measurement is reset and starts over. This filters out glitches from API calls.
- MUOS_CLOCK_HW
-
The Hardware timer to use. This is a hardware dependent config. Timer hardware setup is tightly bound to the hardware capabilities check for the respective hardware documentation about possible settings.
- MUOS_CLOCK_HW_PRESCALER
-
Prescaler from some hardware defined master clock. Possible Values depend on the hardware.
- MUOS_CLOCK_SHORT_TYPE
-
The type used for short time spans. Should be some unsigned integer. What suits best depends on the application, often 16 bit is enough with higher Prescaler.
- MUOS_CLOCK_TYPE
-
The type used for the primary clock functions. Should be some unsigned integer, 32 bit width is recommended.
Error handling
Errors can happen synchronously by returning a error code directly from the called function or asynchronously by setting a flag from interrupts and drivers. This two approaches are necessary because errors can not always be handled where they happen.
The synchronous way is quite straightforward. If a function does return anything but muos_success some error happened and can be handled right away.
Asynchronous errors are different as they can be flagged at any time (from interrupts) which can not handle them appropriately. There is a flag (bitfield) for any possible errorcode, but these don’t queue up. This means asynchronous errors need to be handled as soon as possible.
For to do this mµOS supports a global error handling function which is called in the main-loop when any error is pending. This error handling function has the highest priority and upon enter, interrupts are still disabled. It is advised to enable interrupts there as soon as possible (there is no need to disabled them again, the main-loop takes care of that). The global error handling function only needs to handle most important failures. Any errors not handled there can be handled elsewhere from normal queued functions as well.
Errors can be flagged and checked, checking for an error automatically clears the flag. There is also a function to check if an error flag is set without clearing it.
Error codes
The following error codes are used nby mµOS, depending on configuration, some might be disabled when the respective subsystem/driver is not enabled.
-
muos_error_error
-
unspecified error
-
muos_warn_sched_depth
-
recursive scheduler calls exceeded
-
muos_warn_wait_timeout
-
muos_wait() timed out
-
muos_error_clpq_overflow
-
clock priority queue full
-
muos_error_hpq_overflow
-
high priority queue full
-
muos_error_bgq_overflow
-
background priority queue full
-
muos_error_nodev
-
device index out of range
-
muos_error_sm_state
-
state transition not possible
-
muos_error_tx_blocked
-
tx is blocked by another job
-
muos_error_tx_buffer_overflow
-
To much data to send
-
muos_error_rx_blocked
-
rx is blocked by another job
-
muos_error_rx_buffer_overflow
-
dropped received data (user code)
-
muos_error_rx_buffer_underflow
-
read while no data available
-
muos_error_rx_frame
-
wrong stop bit timing
-
muos_error_rx_overrun
-
dropped received data (uart driver)
-
muos_error_rx_parity
-
parity error detected
-
muos_error_txqueue_overflow
-
To much data to send (TXQUEUE)
-
muos_error_eeprom_busy
-
eeprom busy
-
muos_error_eeprom_verify
-
eeprom verification failed
-
muos_error_configstore_locked
-
configstore operation in process
-
muos_error_configstore_invalid
-
configstore has a problem (check status)
-
muos_error_stepper_noinit
-
not initialized
-
muos_error_stepper_state
-
action not possible from current state
-
muos_error_stepper_range
-
some parameter out of range
-
muos_error_stepper_noslot
-
no position match slot
-
muos_error_stepper_slope
-
no position match slot
-
muos_error_cppm_frame
-
received broken cppm frame
-
muos_error_cppm_hpq_callback
-
hpq overflow when pushing cppm handler
API
MUOS_OK(fn)
Wraps fn which must be a function call returning a muos_error an a check for success or returning the error.
typedef enum {...} muos_error
uint8_t muos_error_pending (void)
Returns the number of errors which are flagged.
void muos_error_set (muos_error err)
void muos_error_set_isr (muos_error err)
-
err
-
Errorcode to flag
When err is muos_success this function just returns. Thus idioms like
muos_error_set (function_which_may_return_an_error ());
are possible.
When err is already flagged, nothing happens.
The *_isr function is for contexts where interrupts are disabled.
Returns err.
bool muos_error_peek (muos_error err)
-
err
-
error code to query
Returns true when the error is flagged, false otherwise.
bool muos_error_check (muos_error err)
bool muos_error_check_isr (muos_error err)
-
err
-
error code to query
When err was flagged then return true and clear that flag, otherwise return false.
The *_isr function is for contexts where interrupts are disabled.
configuration
- MUOS_ERRORFN
-
Name of the user-defined function which is called when am error is pending.
Drivers
State Machines
MµOS provides a infrastructure for implementing generic state machines. One can For this the user has to define a list of all possible states and then define functions for the transistions between these states.
While the list of all possible states is global, one can define multiple independent state machines. The transition implementation will ensure that only valid states appear in each machine.
Each state machine stores a few parameters to queue continutation states. This allows the implementation of nested states which return to a calling state or sequencing states in some predefined order.
When more than one state machine is defined an user defined extra data member is passed around for identifying the actual state machine.
When multiple state machines are defined they are identified by index.
Example
Prepare the file states.def, configure the Makefile with -DMUOS_SM_DEF=states.def and -DMUOS_SM_NUM=1.
#define MUOS_SM_STATES \ STATE(INIT) \ STATE(ONE) \ STATE(TWO)
provide enter/leave functions (empty bodies for example):
muos_error state_INIT_leave (enum muos_sm_state trans[4]) { return muos_success; } void state_INIT_enter (void) { } muos_error state_ONE_leave (enum muos_sm_state trans[4]) { return muos_success; } void state_ONE_enter (void) { } muos_error state_TWO_leave (enum muos_sm_state trans[4]) { return muos_success; } void state_TWO_enter (void) { }
Then initialize the state machine within the MUOS_INIT function or somewhere else within the application and eventually call state transitions. Error returns left out in this example:
void init (void) { muos_interrupt_enable (); MUOS_SM_INIT (INIT); ... MUOS_SM_CHANGE(ONE); }
API
enum muos_sm_state
STATE_NONE,
MUOS_SM_STATES...
}
A global list of all defined states. The user has to supply a file which defines MUOS_SM_STATES. This gets x-macro expanded for various things. Note that all states get prefixed with STATE_ not the usual MUOS_* prefix (these are user application defined states anyway).
STATE_NONE gets always defined as the first member implicitly. This is used to flag the state machine when it is not yet initialized or a state transition is in progress.
typedef muos_error (*const state_leave_fn)(enum muos_sm_state params[4]);
typedef void (*state_enter_fn)(void);
typedef muos_error (*const state_leave_fn)(enum muos_sm_state params[4], intptr_t extra);
typedef void (*state_enter_fn)(intptr_t extra);
For each state the user has to define an enter and a leave function. The MµOS state machine driver calls those for changing states.
When MUOS_SM_NUM is greater than one, then state transition functions take an extra parameter which passes user defined data and wont be changed by the state machine driver.
This functions need to be named state_STATE_enter and state_STATE_leave, where STATE is the state as defined above (without the STATE_ prefix).
The leave function will be called first with the intended state transition in the params field. params[0] is the state to be changed to, params[1..3] are user defined subsequent state parameters. The leave function shall validate the possible state transition and may return an error when the transition is denied. Otherwise it should clean the current state up and return muos_successs. Note that the current state might not be fully initialized when the enter function did not complete and changes into a new state immediately. When the leave function returns any error the state stays unchanged.
The enter function is called to finish the state transition and should set up the new state. Errors on enter shall be handled by changing into another error-handling state.
#define MUOS_SM_INIT(sm, extra, initialstate, ...)
muos_error
muos_sm_init (uint8_t sm, enum muos_sm_state params[4], intptr_t extra);
#define MUOS_SM_INIT(initialstate, ...)
muos_error
muos_sm_init (uint8_t sm, enum muos_sm_state params[4]);
An uninitialized state machine is initially at STATE_NONE and must be initialized to a first state. Again here the API is different depending on the number of state machines to be defined with MUOS_SM_NUM.
When there is more than one state machine one must identify the state machine with the sm index and an initial extra member gets passed and initialized. Later this extra member gets passed unaltered to to the state transition functions.
MUOS_SM_INIT() shall be used to initialize a state machine. Adds some convenience over calling the muos_sm_init() function directly. MUOS_SM_INIT() takes a number of optional arguments for the parameter array which will be initialized to STATE_NONE if not given. The STATE_ prefix must be omitted here as it gets automatically applied.
The initialization calls the initial states enter function synchronously, on successful return the state machine is fully initialized and running.
Initialization returns an error when sm or initialstate is out of range. The state stays at STATE_NONE then.
#define MUOS_SM_CHANGE(sm, newstate, ...)
#define MUOS_SM_CHANGE(newstate, ...)
muos_error
muos_sm_change (uint8_t sm, enum muos_sm_state params[4]);
A state transition on a initialized state machine will be initiated by calling the MUOS_SM_CHANGE() macro. Again depending on the MUOS_SM_NUM the API may require the index of the state machine to change. The first newstate parameter is the state one would like to change to. Any optional subsequent parameters will passed in the params array or initialized to STATE_NONE if not given.
State changing will validate it’s arguments then call the leave function of the current state. When this succeeds the state is temporally set to STATE_NONE to flag that a state transition is in progress and the state enter function for the gets appended to the hpq.
On scheduling before the enter function for the new state gets called the current state and params will be set to the new state.
enum muos_sm_state*
muos_sm_params_get (uint8_t sm);
enum muos_sm_state
muos_sm_get (uint8_t sm);
#ifdef MUOS_SM_NAMES
const char __flash *
muos_sm_name_get (uint8_t sm);
#endif
bool
muos_sm_ready (intptr_t sm);
muos_sm_params_get() queries the params array for the state machine.
muos_sm_get() returns the current state.
muos_sm_name_get() returns a textual representation of the current state.
muos_sm_ready() returns true when the state is not STATE_NONE. For use as predicate to muos_wait()
configuration
- MUOS_SM
-
If defined, the state machine driver gets compiled in
- MUOS_SM_DEF
-
The file which defines the states.
- MUOS_SM_NAMES
-
Enable storing the textual representation of state names within the program.
- MUOS_SM_NUM
-
Number of state machines. :
Serial
Communication over serial interfaces is split into serveral parts. On the lowest level is a UART driver which operates directly on the hardware.
Above that is a library which provides output routines for many types. MµOS does not implement the typical stdio like facilities or printf like format strings. This ensures the required flexibility and extensibility which wouldn’t be possible in a standard approach.
One of the most distinct features is the TXQUEUE. This is an additional queue which uses a tagged binary representation for the data to be transmitted over serial.
Hardware UART
The UART runs asynchronously driven by interrupts. Data is transferred byte by byte with immediate cyclic buffers. Where supported the transmitter and receiver can be disabled independently.
Access to the UART is always non-blocking, requests which can’t be served returning or flagging an error.
At startup the receiver is in desynched state and waits for \r to appear in the stream where it changes into the synchronized state and data can be read. This ensures that only complete lines are returned to the program. Details can be be configured. In future it will also synchronize when the line is idle.
I/O comes in blocking and non-blocking variants each can be called independently for TX and RX. Blocking I/O means that the job making the I/O is put on hold and the scheduler is called recursively with muos_wait(). There can be only one non-blocking (per direction) I/O pending at any time. Further attempts to do I/O will result in a muos_tx_blocked or muos_rx_blocked error.
Blocking I/O is in some cases more convenient but needs more space on the stack and few more errors can happen. It still works very well with small buffers which may outweigh the stack costs. Over/under-runs of the buffers can never happen.
Non-Blocking I/O is easier to use when it is used exclusively, then only errors for buffer over/under-runs need to be handled, but the program logic can become more complicated when a lot data needs to be transferred as some requests might be larger than the buffers.
There is also support for registering a function which gets pushed onto the hpq when characters ready for reading. MµOS comes with a line editor which can be used in this place.
API
muos_error muos_serial_tx_nonblocking_byte (uint8_t b);
muos_error muos_serial_tx_blocking_byte (uint8_t b);
muos_error muos_serial_tx_byte (uint8_t data)
-
data
-
The byte to send
Pushes a single byte on the TX buffer.
- muos_serial_tx_nonblocking_byte
-
Will not block. In case of error one of the following errors gets returned:
- muos_error_tx_buffer_overflow
-
Transmission buffer is full
- muos_error_tx_blocked
-
There is already a blocking write pending
- muos_serial_tx_blocking_byte
-
Waits until data can be send, entering the scheduler recursively. May return one of the following errors:
- muos_warn_sched_depth
-
Scheduler depth exceeded.
- muos_error_tx_blocked
-
There is already a blocking write pending
- muos_warn_wait_timeout
-
TX got stuck. The timeout is calculated internally depending on baudrate so that some chars should been send. When this error happens something got seriously wrong and the TX doesn’t send any data.
- muos_serial_tx_byte
-
Picks one of the functions above, depending on configuration.
All calls return muos_success on success or errors as noted above.
uint16_t muos_serial_rx_nonblocking_byte (void)
uint16_t muos_serial_rx_blocking_byte (muos_shortclock timeout)
uint16_t muos_serial_rx_byte (void)
-
timeout
-
Time to wait for blocking reads
Pops and a byte from the receive buffer. Zero or positive return value is a successful read from the buffer. Negative return indicates an error by the negated error number. Note that the UART driver may flag asynchronous errors too.
- muos_serial_rx_nonblocking_byte
-
Will not block. Following errors can happen:
- muos_error_rx_buffer_underflow
-
No data available for reading
- muos_error_rx_blocked
-
There is already a blocking read pending
- muos_serial_rx_blocking_byte
-
Waits until data becomes available, entering the scheduler recursively. A timeout of 0 means infinite waits.
Following errors can happen:
- muos_warn_sched_depth
-
Scheduler depth exceeded.
- muos_error_rx_blocked
-
There is already a blocking read pending
- muos_warn_wait_timeout
-
No data received within timeout.
- muos_serial_rx_byte
-
Picks one of the functions above, depending on configuration. The timeout for the blocking case defaults to infinite waits.
Return a character value or a negated error as noted above.
typedef bool (*muos_serial_rxcallback)(void)
The type for the user-defined MUOS_SERIAL_RXCALLBACK function. This function gets called from the hpq when there is data data on the RX buffer. When it does not consume all data will called again when it returns true. When it returns false it will only be called again when new data is available on the buffer.
configuration
- MUOS_SERIAL
-
Enable Serial
- MUOS_SERIAL_BAUD
-
Baudrate for the serial interface
- MUOS_SERIAL_HW
-
Serial hardware to use
- MUOS_SERIAL_RXBUFFER
-
length of the receive buffer
-
0 disables receiving data
-
>1 creates a buffer of that size
-
- MUOS_SERIAL_RXCALLBACK
-
Function to be called from hpq to handle the RX buffer data
- MUOS_SERIAL_RXSYNC
-
The receiver stays in desynced state unti the given character got received
- MUOS_SERIAL_RX_BLOCKING
-
Make the RX blocking by default.
- MUOS_SERIAL_TXBUFFER
-
length of the transmission buffer
-
0 disables sending data
-
>1 creates a buffer of that size
-
- MUOS_SERIAL_TX_BLOCKING
-
Make the TX blocking by default.
I/O Library
MµOS comes with a library to do formatted text output over serial. This library does not use printf alike format strings but provides functions for printing each data type. Actually there are 2 implementations with the same API but different functionality. The first one pushes data directly to the transmission buffer while the second one has its own data queue.
Note
|
While the basic variant and the TXQUEUE have the same API, not all functions are implemented for both variants. This will be fixed in future as far it makes sense. |
Pushing data directly out makes the code pretty small and the linker will only link functions which are actually used. The downside is that the TX buffer needs to hold every byte to be printed. When it overruns an error is reported, but the output might be truncated. This is suitable for small targets with little I/O demands, as long one can provide a big enough TX buffer.
Uses a tagged queue as front-end for the TX buffer. Tagged means that the data can be stored in binary form with some tag about the type in front. The I/O library then also manages a job on the hpq which does the transfer from the txqueue to he txbuffer. When the TXQUEUE is in use the TX buffer can be configured to hold only few bytes.
The implementation ensures that the most compact form will be stored. This can dramatically reduce the memory footprint for the queue. For example numbers are represented in the smallest binary form possible, constant strings can be stored in Flash ROM and are only referenced from the TXQUEUE.
This advantage comes with some weight on the flash side, the TXQUEUE implementation currently weight over 2k of code, still whenever enough flash space is available it should be the considered.
Another advantage is that operations on the TXQUEUE are atomic. Data gets never truncated when the queue is full and an error is returned, either the whole thing gets pushed or nothing in case of an error. Future plans include to add some begin/commit transactions support too.
API
-
muos_output_base (uint8_t)
-
set numeric base for the next integer conversion (2-36)
-
muos_output_char (char)
-
a single character
-
muos_output_csi_char (const char)
-
CSI followed by a single character
-
muos_output_csi_cstr (const char*)
-
CSI followed by a c-string
-
muos_output_csi_cstr_P ("literal")
-
puts "literal" into flash section and uses
muos_output_csi_fstr
for printing it -
muos_output_csi_fstr (muos_flash_cstr)
-
CSI followed by a c-string stored in flash ROM
-
muos_output_cstr (const char*)
-
a c-string
-
muos_output_cstr_P ("literal")
-
puts "literal" into flash section and uses
muos_output_fstr
for printing it -
muos_output_cstr_R (const char*)
-
c-string by reference
-
muos_output_cstrn (const char*, uint8_t)
-
c-string with given maximal length
-
muos_output_cstrn_R (const char*, uint8_t)
-
c-string by reference with given maximal length
-
muos_output_fstr (muos_flash_cstr)
-
c-string stored in flash ROM
-
muos_output_int16 (int16_t)
-
signed numeric value
-
muos_output_int32 (int32_t)
-
signed numeric value
-
muos_output_int64 (int64_t)
-
signed numeric value
-
muos_output_int8 (int8_t)
-
signed numeric value
-
muos_output_intptr (intptr_t)
-
signed numeric value of a pointer
-
muos_output_mem (const uint8_t*, uint8_t)
-
raw memory with given address and length
-
muos_output_nl (void)
-
system dependent newline sequence
-
muos_output_pbase (uint8_t)
-
set default numeric base (2-36)
-
muos_output_pupcase (bool)
-
set default digit representation
-
muos_output_uint16 (uint16_t)
-
unsigned numeric value
-
muos_output_uint32 (uint32_t)
-
unsigned numeric value
-
muos_output_uint64 (uint64_t)
-
unsigned numeric value
-
muos_output_uint8 (uint8_t)
-
unsigned numeric value
-
muos_output_uintptr (uintptr_t)
-
unsigned numeric value of a pointer
-
muos_output_upcase (bool)
-
set digit representation for the next integer conversion
configuration
- MUOS_SERIAL_TXQUEUE
-
length of the txqueue
-
0 disables the txqueue
-
>1 creates a txqueue of the given size
-
Lineedit
For building command line interfaces, mµOS comes with a highly configurable line editor. Over time this is planned to become a complete framework for controlling the program.
One can enable it by choosing it as callback function of the UART receiver
MUOS_SERIAL_RXCALLBACK=muos_lineedit
. Then the line editor presents a user
configurable callback which gets called when a line got terminated.
The Line editor can be configure to handle UTF8 encoded text. This only adds moderate size to the code but causes more serial traffic since editing often forces a complete line redraw. When UTF8 support is disabled and only 7-bit ASCII encoding is supported.
Most common line commands are supported. The is cursor moved with cursor keys or vi keys, other editing keys (Home, End, Backspace, Delete, Overwrite) are supported.
There is a very simple line recall handler which lets one restore the previous line when no edits happened yet (Cursor up) or clear the current line (cursor down). This recall feature is very lightweight and needs only one additional byte of RAM and little over 100 bytes program space.
configuration
- MUOS_LINEEDIT_BUFFER
-
capacity of the linedit buffer
- MUOS_LINEEDIT_CALLBACK
-
User defined function to call when a line from lineedit is done (return pressed)
- MUOS_LINEEDIT_RECALL
-
Enable lineedit recall
- MUOS_LINEEDIT_UTF8
-
Enable UTF8 support for lineedit
EEPROM
There are two drivers available for storing data within the microcontrollers EEPROM. The lowlevel EEPROM driver allows asynchronous basic access to the eeprom data. Including reading, writing, verifying and erasing data.
The higher level configstore driver implements a more robust, fault tolerant and wear leveling algorithm on top of the EEPROM facilities.
Low level EEPROM access
Implements basic EEPROM operations with reasonable fine grained control of actual semantics. Writes are asynchronous, reading can be configured to do batched access. Upon completion of a request a user supplied callback can be called. This callback should also check if any error happend.
- error_hpq_overflow, error_bgq_overflow
-
Could not schedule the completion callback.
- error_eeprom_verify
-
A verification failed (write, writeverify, is_erased).
The standard write method does a smart write, as it only erases and writes when necessary. There is also a configureable option for retrying a few times writes when write verification failed before returning an error.
Since EEPROM wears out over time overly frequent writing should be avoided. One safety measure against programming bugs writing data in a tight loop is to add a hard delay before the write. This can be configured but should only be enabled in debug builds.
By its nature EEPROM is considered slow and less timing critical, thus it prefers to schedule callbacks on the BGQ when available.
API
The parameters used for accessing the eeprom are orthogonal though all access functions
-
address
-
start address in ram
-
eeprom
-
start address in EEPROM
-
size
-
size in bytes of for the operation
-
complete
-
callback function called upon completion (also on failure). May be NULL.
The access function returns error_eeprom_busy when they can not start an asynchronous job. All other errors are set asynchronously and should be handled in the complete callback.
muos_error muos_eeprom_read (void* address,
uintptr_t eeprom,
size_t size,
muos_eeprom_callback complete)
Reads the EEPROM.
muos_error muos_eeprom_write (void* address,
uintptr_t eeprom,
size_t size,
muos_eeprom_callback complete)
muos_error muos_eeprom_writeerase (void* address,
uintptr_t eeprom,
size_t size,
muos_eeprom_callback complete)
muos_error muos_eeprom_writeverify (void* address,
uintptr_t eeprom,
size_t size,
muos_eeprom_callback complete)
muos_error muos_eeprom_writeonly (void* address,
uintptr_t eeprom,
size_t size,
muos_eeprom_callback complete)
Transfers data from memory to eeprom.
- muos_eeprom_write
-
Smart write, erase/write only when necessary. Fastest when existing content with only few changes is overwritten. Reduces EEPROM wear.
- muos_eeprom_writeerase
-
Erases data before writing. Faster than erasing the block first.
- muos_eeprom_writeverify
-
Erases data before writing. Verifies after write.
- muos_eeprom_writeonly
-
Only writes (clears bits) without erasing.
muos_error muos_eeprom_erase (uintptr_t eeprom,
size_t size,
muos_eeprom_callback complete)
Erases the given range.
muos_error muos_eeprom_verify (void* address,
uintptr_t eeprom,
size_t size,
muos_eeprom_callback complete)
Compares a block of memory with eeprom contents. Aborts on first error, calling complete.
muos_error muos_eeprom_xor (uint8_t* address,
uintptr_t eeprom,
size_t size,
muos_eeprom_callback complete)
XOR’es the given range with address pointing to a single uint8_t.
muos_error muos_eeprom_crc16 (uint16_t* address,
uintptr_t eeprom,
size_t size,
muos_eeprom_callback complete)
Caclculates the CRC16 (configured by MUOS_EEPROM_CRC16_FN) of the given range and stores it at address. NOTE: the initial value of *address must be set by the user.
Only available when MUOS_EEPROM_CRC16_FN is configured.
muos_error muos_eeprom_is_erased (uintptr_t eeprom,
size_t size,
muos_eeprom_callback complete)
Checks if the given range is erased.
enum muos_eeprom_mode muos_eeprom_state (void)
Polls the state of the driver.
Returns 0 (MUOS_EEPROM_IDLE) when no operation is in progress.
configuration
- MUOS_EEPROM
-
Enable the low level asynchronous EEPROM driver
- MUOS_EEPROM_CRC16_FN
-
Enables the crc16 api, must name a function to call with
uint16_t crc16_update (uint16_t crc, uint8_t data)
as prototype the avr-libc <util/crc16.h> can be used - MUOS_EEPROM_CRC16_INCLUDE
-
Define an extra include for the CRC16 function definition.
- MUOS_EEPROM_CRC16_INIT
-
the initial value used for the crc16 caclulation.
- MUOS_EEPROM_DEBUG_WDELAY
-
Delay program execution by roughly this much milliseconds before each write. This ensures programming errors won’t wear out the eeprom in no time.
Warning
|
Development option only, uses busy loop delay and will halt processing. |
Configstore
The configstore comes with different facilities.
-
A flat representation of configuration values which can be queried from the application.
-
A mapping from strings to the value to be used for CLI and other protocol implementations which are text based.
-
A backend for loading and saving this configuration in a very (configureable) reliable/atomic way. By using some journaled write strategy the wear of the EEPROM will be leveled/mitigated.
With all bells and whistles enabled it is failure tolerant to power glitches and damaged eeprom cells.
API
#define CONFIGSTORE_DATA \
CONFIGSTORE_ENTRY(type, ary, name) \
...
-
type
-
C type of the data.
-
ary
-
Size for arrays. 0 for single variables. Otherwise one of 2, 3, 4, 5, 6, 7,8, 16, 32, 64, 128, or ARRAY(n) to define the size of an array.
-
name
-
C identifier for the variable.
Configuration variables are defined by a C-Preprocessor defined DSL in a single included file. The MUOS_CONFIGSTORE_INCLUDE configuration from the Makefile must point to this file.
The user defines CONFIGSTORE_DATA to a sequence of CONFIGSTORE_ENTRY(type, ary, name) definitions. MµOS uses this to expand the provided data into various datastructures.
muos_error
muos_configstore_load (muos_configstore_callback callback)
muos_error
muos_configstore_save (muos_configstore_callback callback)
-
callback
-
function called on completion.
Both functions return muos_error_configstore_locked in case of an error. Other errors should be handled in callback.
const struct muos_configstore_data*
muos_configstore_lock (void)
struct muos_configstore_data*
muos_configstore_wlock (void)
void
muos_configstore_unlock (const struct muos_configstore_data** lock)
void
muos_configstore_unwlock (struct muos_configstore_data** lock)
struct muos_configstore_data*
muos_configstore_initial (void)
The configuration data implements a simple locking scheme with multiple readers or a single writer. Each successful lock must be paired with a unlock, no further consistency checks are made!
Locking works only on valid data.
There are approx. 250 read locks available. Exceeding this number makes the lock fail.
- muos_configstore_lock ()
-
Checks for availability of the configstore. On success it places a read lock on the configstore and returns a pointer to a const muos_configstore_data data structure which holds all the defined elements. On failure
NULL
is returned and one may inspect the configstore status. No mutations must be made to the data. - muos_configstore_wlock ()
-
Checks for availability of the configstore. On success it places a write lock on the configstore and returns a pointer to a muos_configstore_data data structure which holds all the defined elements. On failure
NULL
is returned and one may inspect the configstore status. The write lock blocks all other access to the configstore and may modify its contents. - muos_configstore_initial ()
-
Works only when the configstore is invalid. Places a write locks on the data which must be unlocked afterwards. This is used when the configstore is uninitialized/prime or damaged to populate it with defaults.
- muos_configstore_unlock () muos_configstore_unwlock ()
-
Frees the lock obtained by muos_configstore_lock(), muos_configstore_wlock() or muos_configstore_initial (). Care must be taken that lock is matched by a unlock.
configuration
- MUOS_CONFIGSTORE
-
Enable the configuration store (EEPROM) driver
- MUOS_CONFIGSTORE_INCLUDE
-
The file which defines the configuration variables
- MUOS_CONFIGSTORE_OFFSET
-
Use EEPROM addresses from OFFSET upward, user is responsible that this is page aligned
- MUOS_CONFIGSTORE_SIZE
-
Use this much bytes at most
Stepper motors
Stepper driver for motor controllers taking pulse/direction signals. Allows for continious movements of stepper motors with speed profiles.
Speed Marks
- min_speed
-
implicit, the slowest speed the hardware can generate.
- cal_speed
-
speed used for calibration/zeroing.
- slow_speed
-
motors must not loose steps when started/stopped to this speed. minimum speed for absolute positioning with acceleration/deceleration. maximum speed for relative positioning which does not accelerate/decelerate.
- max_speed
-
absolute maximum speed which will not exceeded. motors must run stable up to this speed.
Speed Profiles
The Driver generates a slope which accelerates towards max_speed with decreasing acceleration (steppers have less torque at higher speeds), then running a stretch on constant (max.) speed, then going into deceleration until the target speed is reached. Finally some steps are done at the target speed. As soon a slope is loaded and executed the next slope can be generated in background to allow fluent movement.
General Operation Overview
Before one can start moving a steppers it needs some preparations. If configured then one needs to enable (energize) the steppers first. This may already be blocked by some external input (Emergency Switch).
An energized stepper still does not know it’s position, further some configuration for the machine parameters (speed limits, prescaler, acceleration/deceleration etc.) is required for all but the simplest raw movements.
With steppers enabled and configuration given the stepper can do slow relative movements. But the origin is not known yet. For this the stepper needs to be zeroed (by probing limit switches or similar). When zeroing is done the steppers becomme fully armed and all movement types are possible.
When a stepper completes one or multiple movements it will usually stay in the highest possible state, staying enabled and keep its position until they become explicitly disabled.
Raw movemnt are provided for low level calibration taskes. Care must been taken when using raw movements because these are not constrained by machine parameters and may cause overload/damages.
document that steppers *must* move before zeroing to lock in phase
Slope Preparation
Since the generation of slope parameters is relatively expensive it can not be done at the instant the stepper shall start moving but need to be prepared beforehand.
For this the set of slope parameters is doublebuffered and a callback function can be invoked to generate the next set of parameters as soon the current ones get activated.
It is also possible to pregenerate and cache slope parameters for common, esp. short movements.
Moving multiple Axis in sync
CPPM
Parses sum-signals from a RC Receiver. The results are stored in global array and a function can be pushed onto the hpq whenever a frame got received. The CPPM driver uses the input capture feature from the main clock. Thus one has to select a timer which supports this feature when using CPPM. Using input capture ensures the most precise timing.
Note
|
There is a plan to implement an alternative CPPM driver using pin-change interrupts. This will allow some flexibiltiy in choosing the timer for the clock and free the input capture for other tasks. |
There are 2 configurable ways how the driver can store the results for the user, raw timer ticks or cooked values in the range from -125 to 125.
Details
One should configure the minimum and maximum signal lengths. If a signal is to
short then muos_error_cppm_frame
will be flagged, while longer signals mark
the start of a new frame.
When MUOS_CPPM_FRAME_CLOCKSYNC is enabled, mµOS will adjust the µC speed to the cppm frames. This required MUOS_CPPM_FRAME to be configured correctly. This is a way to get even better timing from µC which uses a poor internal oscillator.
In case the hpq is full when the driver wants to push the
MUOS_CPPM_CALLBACK function, the error muos_error_cppm_hpq_callback
gets
flagged.
The callback function will not be called when erroneous frames are received. The user is advised to implement some watchdog to handle this case.
API
uint16_t muos_cppm_channel_raw[MUOS_CPPM_CHANNELS]
Stores the time in timer ticks as measuered for each channel directly, after applying some configureable filter. This gives the most precision but also needs more memory. Must be enabled with MUOS_CPPM_RAW.
int8_t muos_cppm_channel_cooked[MUOS_CPPM_CHANNELS]
Stores the channel data as values from -125 to 125 mapping to the range from MUOS_CPPM_COOKED_MIN to MUOS_CPPM_COOKED_MAX. Little overflows from -128 to 127 are tolerated. Cooked values need less memory and are more stable, but have lower precision. Must be enabled with MUOS_CPPM_COOKED.
configuration
- MUOS_CPPM
-
Enable the CPPM Parser for Radio Control Signals
- MUOS_CPPM_CALLBACK
-
function to be pushed to the hpq when a frame got received
- MUOS_CPPM_CAPTURE
-
Select Input-Capture driver
- MUOS_CPPM_CHANNELS
-
Number of CPPM channels
- MUOS_CPPM_COOKED
-
Store processed cppm data.
- MUOS_CPPM_COOKED_MAX
-
Defines the maximum signal length for cooked values
- MUOS_CPPM_COOKED_MIN
-
Defines the minimum signal length for cooked values.
- MUOS_CPPM_FRAME
-
Length of a full CPPM frame, required for clocksync
- MUOS_CPPM_FRAME_CLOCKSYNC
-
Use the MUOS_CLOCK_CALIBRATE to synchronize the µC clock with the CPPM frames
- MUOS_CPPM_MAX
-
Maximum CPPM Pulse length, any longer pulse defines the frame start
- MUOS_CPPM_MIN
-
Minimum CPPM Pulse length
- MUOS_CPPM_RAW
-
Store the raw cppm timing data.
- MUOS_CPPM_RAW_FILTER
-
Filter expression for raw channel data
Debug
Subsystem for using few GPIOs as debug outputs for mµOS internals and user defined channels.
API
MUOS_DEBUG_Cx_ON
MUOS_DEBUG_Cx_OFF
MUOS_DEBUG_Cx_TOGGLE
-
x
-
Debug channel (1-4)
Sets the debug channel to the given state.
MUOS_DEBUG_INTR_ON
MUOS_DEBUG_INTR_OFF
MµOS Interrupt debug channel. User defined interrupts should turn the GPIO on right at the start and off when done.
configuration
- MUOS_DEBUG
-
When defined, the debug driver is enabled
- MUOS_DEBUG_BUSY
-
Uses a GPIO to indicate when the CPU is busy. While sleeping the GPIO is turned off.
- MUOS_DEBUG_Cx
-
MUOS_DEBUG_C1, MUOS_DEBUG_C2, MUOS_DEBUG_C3, MUOS_DEBUG_C4. User defined Debugging channels.
- MUOS_DEBUG_ERROR
-
Uses a GPIO to indicate when an error got set, Turned off when no errors are pending.
- MUOS_DEBUG_INTR
-
Uses a GPIO to indicate when the CPU is handling interrupts.
- MUOS_DEBUG_SWITCH
-
Uses a GPIO to indicate when the scheduler switches to another function, Pin gets toggled!
Library
Some common data structures and algorithms are implemented in a small run time library which ships with mµOS. This library functions are meant as simple building blocks for other facilities in mµOS. Some things are deliberately left out or simplified, if one wants to use parts of the library he has to be careful about this.
Notably most library calls usually don’t disable/enable interrupts and don’t check for errors. The user of the library has to implement this.
This is intentionally left out because often one wants more than one single operation atomic. Like pushing two elements on a queue, which should either fail and leave the queue in the old state or succeed with both elements on the queue. This can only be implemented from the callers scope.
queue
The Queue used for the work queues, storing functions and arguments. The size of the queue must be given at compile time. The implementation works with any size, there is no requirement for the size to be a power of two.
Functions pushed on the queue can take an optional argument along. The current implementation flagging this in MSB of the function pointer. This restricts the current implementation to 64k address space (AVR, word addressed). Future implementation will lift this restriction.
The type used for indexing can be configured to 4, 8 or 16 bits. For extremely small microprocessors using 4 bits only makes the queue management data fit into a single byte. Queue size is constrained to 16 elements then. Passing arguments to functions require one additional element.
The API is made from a small layer of macros above the underlying functions which take care of passing the correct type and size along.
API
MUOS_QUEUEDEF(size)
-
size
-
number of elements
Macro defining the type of a queue for the given size. To instantiate a queue
use MUOS_QUEUEDEF(16) myqueue;
for example.
void muos_queue_init (struct muos_queue* queue)
-
queue
-
pointer to the queue
Initialization is not necessary at startup, it is only required for to reinitialize and delete an existing queue.
void muos_queue_pushback (struct muos_queue* queue,
const muos_queue_size size,
muos_queue_function func)
void muos_queue_pushback_arg (struct muos_queue* queue,
const muos_queue_size size,
muos_queue_function func,
intptr_t arg)
void muos_queue_pushfront (struct muos_queue* queue,
const muos_queue_size size,
muos_queue_function func)
void muos_queue_pushfront_arg (struct muos_queue* queue,
const muos_queue_size size,
muos_queue_function func,
intptr_t arg)
intptr_t muos_queue_pop (struct muos_queue* queue,
const muos_queue_size size)
-
queue
-
the queue
-
size
-
size of the queue
-
func
-
function pointer
-
arg
-
inptr_t argument
-
muos_queue_pushback ()
pushes func onto the back of the queue -
muos_queue_pushback_arg ()
pushes func with arg onto the back of the queue -
muos_queue_pushfront ()
pushes func onto front of the queue -
muos_queue_pushfront_arg ()
pushes func with arg onto the front of the queue -
muos_queue_pop ()
removes the first argument from the queue
muos_queue_size muos_queue_free (struct muos_queue* queue, const muos_queue_size size)
-
queue
-
the queue
-
size
-
size of the queue
Returns the number of free elements in the queue.
configuration
- MUOS_QUEUE_INDEX
-
the bits used for indexing queues comes in 3 variants
-
4 bits allow only small queues for up to 16 elements, use only for really small targets
-
8 bits allow queues for up to 256 elements, default
-
16 bits allow huge queues, use only when really required
-
spriq
Priority queue for storing functions scheduled by time (clpq). This implements a sliding window, times (priorities) are stored in future to a given base. Times smaller (due integer overflows) than the base will compare as greater than the base in the queue, appending them at the end and thus define the sliding window.
This sliding window implementation was chosen because in most cases one only wants to schedule jobs within relative short times into the future. Storing a full time-stamp (which can be up to 80bit) within the priority queue would be a huge waste of memory.
API
struct muos_spriq_entry
muos_spriq_priority when;
muos_spriq_function fn;
}
typedef void (*muos_spriq_function)(const struct muos_spriq_entry* event)
The type for functions stored in a spriq. This function is called with the time it was to be scheduled. The scheduler passes the spriq entry to the function, this makes it simple to schedule repeating jobs, where this event→when just becomes the new base for next push.
void muos_spriq_init (struct muos_spriq* spriq)
-
spriq
-
pointer to the spriq
Initialization is not necessary at startup, it is only required for to reinitialize and delete an existing queue.
void muos_spriq_push (
struct muos_spriq* spriq,
muos_spriq_priority base,
muos_spriq_priority when,
muos_spriq_function fn)
-
spriq
-
Pointer to the spriq
-
base
-
Base priority
-
when
-
Offset to base for the priority
-
fn
-
Function to push
base must be smaller or equal to the smallest (first) element in the queue. For times this is usually muos_now() or the time the event was scheduled. when can cover the full range of the priority data type.
void muos_spriq_pop (struct muos_spriq* spriq)
-
spriq
-
Spriq where to pop from
No return, no error checking!
configuration
- MUOS_SPRIQ_INDEX
-
Type to keep track of the size of the spriq.
uint8_t
for up to 255 entries,uint16_t
for up to 65k entries. - MUOS_SPRIQ_TYPE
-
Type used for the priorities in spriq.
cbuffer
Cyclic byte buffer used for I/O queues. Normally used as queue, but has functions to pop functions from the end and peek and poke at arbitrary positions.
API
MUOS_CBUFFERDEF(size)
-
size
-
number of elements
Macro defining the type of a cbuffer for the given size.
void muos_cbuffer_init (struct muos_cbuffer* cbuffer)
-
cbuffer
-
pointer to the cbuffer
Initialization is not necessary at startup, it is only required for to reinitialize and delete an existing queue.
MUOS_CBUFFER_SIZE(cbuffer)
MUOS_CBUFFER_FREE(cbuffer)
MUOS_CBUFFER_USED(cbuffer)
MUOS_CBUFFER_PUSH(cbuffer, value)
MUOS_CBUFFER_POP(cbuffer)
MUOS_CBUFFER_RPOP(cbuffer)
MUOS_CBUFFER_POPN(cbuffer, n)
MUOS_CBUFFER_PEEK(cbuffer, n)
MUOS_CBUFFER_POKE(cbuffer, n, value)
-
cbuffer
-
the cbuffer as defined with
MUOS_CBUFFERDEF()
-
value
-
byte (
uint8_t
) value -
n
-
number or position of elements
-
MUOS_CBUFFER_SIZE(cbuffer)
returns the size -
MUOS_CBUFFER_FREE(cbuffer)
returns how many bytes are free -
MUOS_CBUFFER_USED(cbuffer)
returns how many bytes are used -
MUOS_CBUFFER_PUSH(cbuffer, value)
pushes a byte to the end -
MUOS_CBUFFER_POP(cbuffer)
pops and returns the first byte -
MUOS_CBUFFER_RPOP(cbuffer)
pops the last byte (no return) -
MUOS_CBUFFER_POPN(cbuffer, n)
pops n bytes frome the begin (no return) -
MUOS_CBUFFER_PEEK(cbuffer, n)
returns the byte at position n -
MUOS_CBUFFER_POKE(cbuffer, n, value)
changes the byte at position n to value
configuration
- MUOS_CBUFFER_INDEX
-
type used to the cyclic buffer length and indexing
utf8
Few routines for handling utf8 encoded strings.
API
bool muos_utf8ascii (const char c)
bool muos_utf8start (const char c)
bool muos_utf8cont (const char c)
-
c
-
character to check
-
muos_utf8ascii (c)
when c is a latin1 character -
muos_utf8start (c)
when c is the begin of a multibyte sequence -
muos_utf8cont (c)
when c is the a continuation of a multibyte sequence
size_t muos_utf8len (const char* str)
-
str
-
zero terminated c-string
str must not be a continuation byte
Returns the length of a utf-8 encoded string in characters.
uint8_t muos_utf8size (const char* char)
-
char
-
character to analyze
char can point into the middle or to the end of a multibyte sequence, but the sequence must have a proper start byte.
Returns the size in bytes of the given multibyte character sequence.
Appendix A: LICENSE
MµOS is licensed under GPLv3. For questions about licensing details and
possible relicensing conditions contact the author Christian Thäter
<ct@pipapo.org>.