SILABS AN672

AN672
P RECISION 3 2 ™ SI 32L IBRARY O VERVIEW
1. Introduction
A 32-bit platform with large memory enables a big and complex firmware system on the device. This complexity
can slow development, as firmware consists of more layers with interweaving tasks and threads that are more
difficult to create and debug.
The si32Library is a set of flexible, reusable, and portable source modules enabling core application level
functionality for Silicon Laboratories 32-bit Precision32™ MCUs. It includes facilities for debug logging, memory
allocation, data collections, data transfers, and cooperative multitasking. The si32Library package provides
working abstractions of the hardware layer, reduces coding effort, and provides structure to aid and speed up toplayer application development.
The si32Library is written in C99 and leverages the Silicon Laboratories si32Hal hardware access layer. This
document describes the components of the si32Library. A detailed and complete description of the si32Library
component functions can be found in the corresponding header files. The library is installed as part of the
Precision32 Software Development Kit package available at www.silabs.com/32bit-software.
The primary architectural elements of an application built with the si32Library are:
Application:
the application-specific code
reusable object based software modules
Real Time Operating System: 3rd party multitasking library
Hardware Access: CMSIS-inspired HAL encapsulating hardware peripherals
Hardware: the MCU itself, support chips, and boards
Figure 1 depicts this si32Library system.
si32Library:
CODE EXAMPLES
APPLICATION
si32Library
Callback
RTOS
CMSIS CoreSupport
(from ARM)
CMSIS
CMSIS DeviceSupport
(from Silicon Labs)
HARDWARE
Figure 1. si32Library System
Rev. 0.1 2/12
Copyright © 2012 by Silicon Laboratories
AN672
AN672
1.1. Diagram Conventions
Portions of the si32Library are diagrammed using a UML-like convention, as depicted by the types of graphical
elements shown in Figure 2.
si32BufferObject
si32RootObject
si32BufferObject
buffer
sometype
elements
| | | | | |
A double line box is used to indicate the subject of the
diagram.
A single line box is used to indicate a base type,
from which the thing being described is derived.
A named box is used to indicate an object
aggregated by or composited within the subject.
The name used by the subject to reference it is indicated.
An "array" box is used to indicate a cArray
of some integral or structural type; i.e., a non-object.
The name that it is given by the object that owns it
is indicated beneath the element type.
A chip box indicates a hardware peripheral.
SI32_XXX_X_Type
This type of arrow means that the thing pointed to
is the base type, then the thing pointing to it is the
derived type.
This type of arrow means that the thing on the diamond
side has a pointer, or an index, to the thing pointed to.
It indicates "aggregation".
This type of arrow means that the thing on the diamond
side contains the thing pointed to, typically as a struct
field. It indicates "composition".
This type of arrow indicates a relationship. It is used to
indicate things like "these are indices into those", or "these
are pointers to those", or "this talks to that".
Figure 2. Graphical Elements
2
Rev. 0.1
AN672
2. Relevant Documentation
Precision32 Application Notes are listed on the following website: www.silabs.com/32bit-mcu.
AN664:
AN673:
Precision32™ CMSIS and HAL User’s Guide
Precision32™ Software Development Kit (SDK) Overview
3. Examples
The examples for the si32Library install with the Precision32 package to the si32-x.y\Examples\si32Library
directory, where x is the major SDK version number and y is the minor SDK version directory. Some of these
examples are simple demonstrations of how to use the components, and some are full application examples like a
HID USB-to-UART bridge interface.
4. Component Architecture
The si32Library is structured as a collection of independent and cooperating components. A component is a
collection of related objects with a header file that includes any other components it may require, as well as any
internal objects or sub-components that it provides. Therefore, to use a component in an application or another
component, it is sufficient to include that component's header file and add the component's sources and its
dependencies to the project.
All components are named according to the form somethingComponent, where something is a meaningful
name like USB. The only exception is si32Library, which is the top level and does not have the Component suffix.
Every component is represented in the source tree as a directory. That directory contains the following types of
files:
*
.h: The component and object header files collectively comprise the interface. All of the documentation
about the interface is here and not in the .c files.
* .c: The individual source files collectively comprise the implementation for the component. There may be
more than one implementation of any given interface. Documentation relevant to a specific implementation
is found here.
Some component directories contain subdirectories whose names also end in Component. These are subcomponents and follow the same structure recursively. Sub-components are used to provide logical groupings of
components.
There are some specific files that each application must possess. These files contain various elements that are
necessary for a build, but that are specific to each individual application.
myBuildOptions.h
contains all of the build options. It allows the application developer to enable or disable
various compile time options, including assertions and debug logging.
myLinkerOptions_<chain>.<ext> contains the definitions for application specific configuration options
such as the desired stack and heap sizes. This file is included by the tool chain during compilation and is
used to configure the stack and heap. The supported <chain>.<ext> pairs are _p32.ld, _arm.sct, _iar.icf.
Applications do not include the myBuildOptions.h file directly because the si32BaseComponent of the
si32Library includes it. It is the very first file included because it specifies the build options for the si32Library.
Changes to this file to specify different build options require recompliation of the library. This allows each individual
application to configure the si32Library as desired without needing to modify any of the library code.
Figure 3 shows the dependencies of an si32Library-based application.
Rev. 0.1
3
AN672
Application
myLinkerOptions_<chain>.<ext>
*.c
myBuildOptions.h
si32Library
...
si32ContainerComponent.h
si32ObjectComponent.h
si32BaseComponent.h
si32Library.h
si32Hal
...
...
...
SI32_CMP_A_Type.c
SI32_CMP_A_Type.h
SI32_CMP_A_Registers.h
SI32_SARADC_A_Type.c
SI32_SARADC_A_Type.h
SI32_SARADC_A_Registers.h
(includes all of these)
si32_device.h
system_sim3u1xx.c
linker_sim3u1xx_<chain>.<ext>
sim3u1xx.h
system_sim3u1xx.h
CPU
core_cmInst.h
retarget_arm.c
retarget_iar.c
core_cm3.h
core_cmFunc.h
retarget_p32.c
Figure 3. si32Library Application Dependencies
4
Rev. 0.1
AN672
5. Base Component
COMPONENT: si32BaseComponent
REQUIRES: none
PROVIDES: none
This component provides facilities for logging, error handling, memory allocation, and control flow.
5.1. Build Options
si32Base.h provides benign defaults for all available build options. To change a build option for an application,
enable or disable that option as appropriate in the application's myBuildOptions.h.
si32BuildOption_enable_assertions:
si32BuildOption_enable_logging:
Enables si32Assert(). Default is off.
Enables logging. Logging must be enabled to use any si32LogXXX()
routine. Default is off.
si32BuildOption_log_flow: Enables si32LogPrologue(), si32LogSelf(), si32LogArg(), si32LogAttr(),
si32LogVar, si32LogEpilogue(). Default is off.
si32BuildOption_log_ref_counts: Enables logging of reference counts if _log_flow is also enabled.
Default is off.
si32BuildOption_tally_allocations: Dynamic allocations encode size information for leak checking.
Default is off.
si32BuildOption_retention_zone_size: Retention allocation zone reserve (for MCUs with retention
RAM). Default is 1.
si32BuildOption_incremental_zone_size: Incremental allocation zone reserve. Default is 1.
si32BuildOption_addressable_labels: Enable use of computed labels instead of switch abuse for local
continuations. Default is off; switch/case is used.
si32BuildOption_logPath: Workstation platforms can log to a file. Default is "/dev/stdout".
5.2. Logging
si32BaseLogger.h defines a number of macros for logging waypoints during program execution. These depend
on the si32HAL and require use of si32HAL/CPU/retarget_<chain>.c to redirect printf/scanf primitives to ITM, a
UART, or some other device.
void
si32StartLogging(void): Configures the logging subsystem and initiates logging. For simulation
builds on workstations this opens the log file.
void si32StopLogging(void): Terminates logging. For simulation builds on workstations this closes the log
file.
void si32LogBeSilent(bool yn): Used to temporarily change the state of the logger without disabling it, or
recompiling to turn it off. When silent the log does not send data to ITM etc.
bool si32LogIsSilent(void): Queries whether the log is silent, or verbose.
void si32LogBeIndented(bool yn): Controls whether the log is indented. When YES, si32LogPrologue()
indents the log, and si32LogEpilogue() outdents the log, in addition to logging the entry/exit of a function,
respectively. The default is YES, indent the log.
bool si32LogIsIndented(void): Queries whether the log is indented, or flat.
int si32LogPrint(char* fmt, ...): Prints to the log, with parameters similar to printf. Does not indent, even
when preceded by si32LogBeIndented(YES). Does not append a newline. For io retargeting, it essentially
just wraps printf.
int si32LogTrace(char* fmt, ...): Prints to the log, with parameters similar to printf. It indents per
si32LogBeIndented(yn) and appends a newline.
int si32LogWarning(char* fmt, ...): Prints to the log, with parameters similar to printf. It indents per
si32LogBeIndented(yn). Prepends "WARNING: " and appends a newline.
int si32LogError(char* fmt, ...): Prints to the log, with parameters similar to printf. It indents per
Rev. 0.1
5
AN672
si32LogBeIndented(yn), prepends "ERROR: " and appends a newline.
void si32LogPrologue(void): Logs the name of the surrounding function preceded by ">" and indented
per si32LogBeIndented(yn). Useful for marking function entry. Intended to be paired with
si32LogEpilogue() particularly when preceded by si32LogBeIndented(YES).
void si32LogEpilogue(void): Logs the name of the surrounding function preceded by "<" and indented
per si32LogBeIndented(yn). Useful for marking function exit. Intended to be paired with si32LogPrologue()
particularly when preceded by si32LogBeIndented(YES).
5.3. Error Handling
Error handling in si32Library adopts the strategy of storing an error code, with a get/set error methodology. This
style is chosen over the "error out parameter" style in order to reduce the number of parameters to functions in the
general case, improving the compiler's opportunity to pass parameters via registers. The error code is a char*, not
an integer code, and points to a non-localized string. Error strings are intended specifically to aid the developer in
debugging the application. These entry points are all macros.
si32Assert(expression):
Evaluates an expression and "halts" if that expression evaluates to false.
si32If (expression) { handler statements }: If the expression is true, sets an error with the text of the
expression, and executes the handler statements. It is 'if', but with internal error check, set, and log
functionality. Use it just like 'if'.
IF (expression) { handler statements }: Identical to si32If, but intended to be less disruptive when
reading code.
si32BreakIf(expression): Conditional break statement.
si32ContinueIf(expression): Conditional continue statement.
si32Catch(exception): Control flow escape for local exception handing. Catches a local throw from within
the same function.
si32Throw(exception): Control flow escape for local exception handing. Throws to the corresponding
catch within the same function.
si32ThrowExitIf(expression): Conditional throw to 'exit'.
si32ThrowIf(expression, exception): Similar to si32ThrowTrapIf except instead of throwing 'trap' it throws
the specified exception, and does not specify an error string.
si32ThrowTrapIf(expression): Conditional throw to 'trap'. Evaluates an expression and throws a trap
exception if that expression evaluates to true.
si32TrapIf(expression): Synonymous with si32ThrowTrapIf. si32TrapIf is deprecated but still exists for
compatibility with vintage code.
si32Halt(): Halts, triggering a debugger breakpoint if possible, or simulates one via while(1).
bool si32SetError(fmt, ...): Logs and pushes an error. Error stack depth is implementation dependent, and
varies across embedded and workstation targets. StdC implementations are 1 deep. ObjC implementations
are deeper. Parameters work like printf. Current implementations always return YES.
char* si32GetError(): Peeks the current error. I.e, returns the error string but does not pop the error stack.
si32ClearError: For embedded StdC implementation, clears the current error. For ObjC implementations,
pops the error stack.
6
Rev. 0.1
AN672
5.4. Memory Management
The si32Library supports static/compile time memory allocation, dynamic one-time allocation, and dynamic heapbased allocation.
Static
allocation relies on declaring all memory usage as data structures to be reserved and initialized by
the compiler when the system boots.
Dynamic one-time allocation relies on a statically allocated block of memory that is used to incrementally
satisfy allocation requests at runtime. Memory allocated in this manner is not recoverable via deallocation
or reallocation. It is only recoverable by resetting the entire memory block.
Dynamic heap-based allocation is essentially the malloc/free model supporting allocation, reallocation,
and deallocation on a heap.
Static allocation is performed by writing C code that declares and initializes data structures, and is outside the
scope of this discussion.
Dynamic
one-time
allocation
is
performed
by
overriding
the
default
value
of
si32BuildOption_incremental_zone_size, thus creating a block of memory for servicing allocations. Calls to
si32Base_allocate(SI32_INCREMENTAL_ZONE, size, alignment) claim memory from this block, called the
incremental zone. Attempts to free this memory via si32Base_deallocate(pointer) will succeed, but the memory
will not be reclaimed. Similarly, calls to si32Base_reallocate(pointer) will reallocate the pointer by claiming a new
block from the incremental zone, effectively leaking the previous allocation.
Dynamic heap allocation allows allocation, reallocation, and deallocation. si32Base_allocate(SI32_HEAP_ZONE,
size, alignment) behaves similarly to calloc(). si32Base_reallocate(pointer) behaves similarly to realloc() when
called on a heap pointer. si32Base_deallocate(pointer) behaves similarly to free() when called on a heap pointer.
Note: The performance of heap-based dynamic memory depends on the heap implementation provided by the tool chain.
Each supported compiler provides a unique implementation.
Rev. 0.1
7
AN672
6. Object Component
COMPONENT: si32ObjectComponent
REQUIRES: si32BaseComponent
PROVIDES: si32RootObject
Although it is sometimes practical to implement applications in C++ by using only a subset of the language’s
features, use of C++ may be precluded by the target application's requirements. The si32Library object system
provides a C99 based subset of the capabilities of C++ for use in applications where C++ is inappropriate or
unavailable. The si32Library is object based, providing encapsulation, inheritance, and polymorphism. The object
system provides a simple class hierarchy, but is neither truly class or prototype based. It is simply a design pattern
that uses structure composition and aggregation to implement a type system wherein new types can be derived
from base types, inheriting their behavior and properties. Therefore, the term class is generally avoided.
In many cases the precise type of an object is known a-priori, at compile time. In such cases, early binding can be
employed effectively to eliminate indirect function calls. For example:
_SomeType_SomeOperation(SomeObject, SomeArguments);
In other cases, however, late binding is necessary because the type of an object is not known until run-time. This is
particularly true for objects representing optional hardware that is discovered at run-time. For example:
SomeObject->Operations->SomeOperation(SomeObject, SomeArguments);
The si32ObjectComponent provides both early and late binding capabilities.
Objects are implemented as C structures. These structures contain specific fields and are constructed in a manner
that facilitates the implementation of inheritance by means of structure composition. Figure 4 shows the essential
structure of a base object.
obj* my_ object
functions
variable
...
variable
(*fn)()
...
(*fn)()
Figure 4. Object Structure
An object's variables can be accessed in this manner:
my_object->context.variable
The object’s functions can be called as follows:
my_object->context.functions->fn(my_object, ...)
An object reference (or a pointer to an object) is represented by the type obj*. This is an anonymous (void) pointer
that can reference anything. Therefore, it must be assigned to a variable of the appropriate type and checked at
runtime.
8
Rev. 0.1
AN672
Object functions typically follow this pattern:
uint32 result = 0;
// meta:{{si32SomeObject}do_something}
si32Assert(is_si32SomeObject(self));
si32SomeObject *my = self;
result = my->someObjectContext.variable;
// meta:{{si32SomeObject}do_something}
return result;
The first line declares a variable for the function result and initializes it. The comment marks the end of the function
entry and the start of the body. si32Assert is an assertion that halts in the debugger if the expression is false.
is_si32SomeObject performs the runtime type check. Next, a variable of the appropriate type is declared and
bound to self. Note that this is 'safe' because the runtime type check succeeded. The symbol my is just chosen to
reinforce that referenced attributes belong to the object bound to self. Next, the contents of the object's variables
(i.e., its attributes) are accessed through the typed pointer. The second comment marks the end of the body and
starts the function exit.
Logging and assertions only have a code footprint in debug builds. They are intended for use during development
and debugging, not for final production code. Therefore, build options exist to cause these constructs to compile
away to nothing for release builds.
Calling the functions of an object (i.e., invoking its operations) is accomplished in a similar fashion. However, it is
not necessary to employ a type* my = self statement in order to invoke an operation on an object. The following is
sufficient:
uint32 result = si32SomeObject_do_something(&my_object, arg);
Macros are employed to access the object's function tables in order to determine the correct implementation of the
operation to invoke. This is how runtime function overloading is implemented by the si32ObjectComponent and,
therefore, all of the si32Library. For example:
uint32 _si32ListObject_get_capacity(obj* /*self*/);
#define si32ListObject_get_capacity(self)
as_si32ListObject(get_capacity, (self))
The first line provides the function prototype for the early binding. Calling this directly invokes that specific function,
bypassing the function table and thus preventing polymorphism. Early bindings are preceded by an underscore.
The second line provides the late binding, which calls through the function table, enabling polymorphism.
Rev. 0.1
9
AN672
6.1. Derived Types
Each object contains a context, which contains a pointer to a function table, and zero or more attributes. Derived
objects contain multiple contexts, each with their own function pointer and attributes. This allows derived types to
have attributes with the same names as those of their base types that are independent of the same named
attributes of the base type. Figure 5 shows the derived object structure.
obj* my_ object
Base
functions
variable
...
variable
(*fn)()
...
(*fn)()
Base
Derived
functions
variable
...
variable
(*fn)()
...
(*fn)()
Derived
Figure 5. Derived Object Structure
Objects also have a type signature that is not represented in the diagram. This type signature allows operations to
assert that the self parameter is an object of a specific type. Those assertions can be compiled away in release
builds. The current implementation uses cstrings and strcmp to perform runtime type checking. This allows
checking whether an object is derived from some other object and is legible in memory. Occasionally attempting to
call strcmp on a non-object can result in a fault. This is arguably less friendly than an assertion, but the error is
caught in the debugger.
6.2. Object Life Cycles
All objects in the library derive from the si32RootObject. This provisions all of the library objects with a small set of
fundamental capabilities. All objects in the library, including those that are not dynamically allocated on the heap,
employ reference counting to govern their life cycles. The operations for accomplishing this are:
allocate:
An object can be allocated on the stack, or it can be incrementally, or dynamically, allocated from
one of several memory zones. The act of allocating an object reserves memory for that object and
configures it for use as an object by preparing it's reference count, function tables, etc.
initialize: All objects must be initialized prior to being used.
retain: Increments the object's reference count.
release: Decrements the object’s reference count. When a dynamic object's reference count decrements
from 1 to 0 it is deallocated.
deallocate: Called when the reference count reaches 0. Only objects dynamically allocated from the heap
zone are actually deallocated.
This reference counting mechanism is employed throughout the si32Library. The policy is that newly created
objects have a reference count of 1, and it is the responsibility of the creator of that object to release it once it is no
longer needed. When a reference to an object is provided to some other object, the receiving object must retain it
in order use it beyond the current function call. Otherwise it may be released, potentially resulting in deallocation;
thus, subsequent use may result in a pointer error. The policy is to retain it if it will continue to be used.
Consequently, every call to retain must be paired with a corresponding call to release. This allows one object to
allocate a second object, hand it over to a third object, and then release it, thus relieving itself of ownership. The
object that performed the allocation is not required to be responsible for its eventual deallocation.
In general, memory is allocated at compile time or automatically on the stack at runtime. Heap-based dynamic
memory allocation functions are provided to allocate, reallocate, and deallocate RAM in a controlled, monitored
10
Rev. 0.1
AN672
manner. These functions are provided by the si32BaseComponent and should be used for all dynamic memory
allocation because they provided tracked, aligned, memory management transparently to the library and its
applications.
6.2.1. Allocation of Objects
Objects must be allocated and initialized before they can be used. They are typically allocated on the stack as
automatic variables. For example:
si32UsartAPortalObject my_usart = si32UsartAPortalObject();
Objects whose lifetime must exceed that of the scope in which they are defined can be declared static, just like any
other variable in C.
static si32UsartAPortalObject my_usart = si32UsartAPortalObject();
Objects can also be dynamically allocated.
si32UsartAPortalObject* my_usart = si32UsartAPortalObject_allocate();
Objects are allocated from the default allocation zone, one of the si32AllocationZoneEnumType values:
SI32_RETENTION_ZONE, SI32_INCREMENTAL_ZONE, or SI32_HEAP_ZONE. The default zone is
SI32_HEAP_ZONE. Applications are free to change the default allocation zone at runtime.
Both the retention zone and the incremental zone are incremental. They are simply blocks of bytes, and each
allocation request just advances a pointer within the zone by the amount requested. Allocating from these zones
does not incur additional RAM overhead. However, it is not possible to reclaim memory allocated from these zones
except by resetting the entire zone. They are intended to support a programming model wherein all memory is
preallocated at startup time and persists throughout the duration of the program's execution.
The heap zone is implemented using the allocator provided by the tool chain. It allows dynamic deallocation and
reallocation, at the cost of potential memory fragmentation and subsequent compaction.
To determine the default allocation zone use:
si32AllocationZoneEnumType si32Base_get_default_allocation_zone()
To change the default allocation use:
si32Base_set_default_allocation_zone(si32AllocationZoneEnumType zone)
6.2.2. Rules for Initializing Objects
Regardless of how an object is allocated, it should not be used until it has been initialized. Initialization sets up the
object's attributes and configures it for operation.
si32UsartAPortalObject_initialize(&my_usart,
SI32_PORTAL_OBJECT_CAPABILITY_FULL_DUPLEX
|SI32_PORTAL_OBJECT_CAPABILITY_TRANSMIT
|SI32_PORTAL_OBJECT_CAPABILITY_RECEIVE,
SI32_USART_0);
Attempting to invoke an object function on an uninitialized object results in an assertion (for debug builds) or
(occasionally) a fault.
Rev. 0.1
11
AN672
6.2.3. Usage
Once an object has been allocated and initialized, it can be used.
si32UsartAPortalObject_set_configuration(&my_usart,
SI32_PORTAL_OBJECT_CONFIGURATION_FULL_DUPLEX
|SI32_PORTAL_OBJECT_CONFIGURATION_TRANSMIT
|SI32_PORTAL_OBJECT_CONFIGURATION_RECEIVE);
actual_rd = si32UsartAPortalObject_read(&my_usart, buffer, N);
si32UsartAPortalObject_release(&my_usart);
Another example:
// allocate an object on the stack.
si32PseudoThreadObject my_thread = si32PseudoThreadObject();
// initialize it.
si32PseudoThreadObject_initialize(&my_thread, &my_run_loop);
// use it.
si32PseudoThreadObject_run(&thread, my_thread_fn);
// eventually release it, once it is no longer needed.
si32PseudoThreadObject_release(&thread);
6.2.4. Rules for Retaining and Releasing Objects
Regardless of how an object is allocated, it should be retained and released in order to correctly manage its
reference count. In some cases, a reference to an object may be passed to a function, and that object may or may
not be dynamically allocated. If the reference to the object must remain valid even after the function returns (i.e., it
was stored somewhere for later use), then it must be retained. All calls to retain must be matched with a call to
release, once the retainer of the object no longer needs a reference to it. Reference counting allows dynamic
objects that are no longer needed to become deallocated, and allows static objects that are no longer needed to
release any aggregated objects whose references they hold.
For example:
// retain it.
si32BufferObject_retain(&my_tx_buffer);
// use it...
// release it.
si32BufferObject_release(&my_tx_buffer);
All objects are initialized with a reference count of one. This means that statically allocated objects created on the
stack (but not those defined at file scope) must be released prior to return from the function wherein they are
allocated. The reason is that those objects may contain references to other objects. These other objects must be
released when the statically allocated stack object goes out of scope; otherwise, their reference counts can never
decrement to zero.
For example:
12
Rev. 0.1
AN672
void f()
{
// allocate an object on the stack and zero it.
si32BufferObject my_buffer = si32BufferObject();
// initialize it.
si32BufferObject_initialize(&my_buffer, ...);
// use it...
// release it.
si32BufferObject_release(&my_tx_buffer);
}
6.3. si32RootObject
si32RootObject is the root of the object hierarchy.
Rev. 0.1
13
AN672
7. Container Component
COMPONENT: si32ContainerComponent
REQUIRES: si32BaseComponent, si32ObjectComponent
PROVIDES: si32BufferObject, si32BufferObjectV2, si32PoolObject, si32QueueObject, si32QueueObjectV2,
si32ValueObject
si32ContainerComponent provides a variety of objects that can hold and manage blocks of memory, and other
objects.
7.1. si32BufferObject
An si32BufferObject is a one dimensional collection of homogenous elements whose type is indeterminate. Its
buffer associates a block of memory with its capacity and provides simple accessors that allow other objects to
access its contents in a uniform way. Elements of a buffer are accessible by index and can be addressed in any
order. Operations on buffers read and write entire elements, not pointers to them. Buffers are mutable and
inelastic; the elements are modifiable but the capacity is constant. Once initialized, the buffer size cannot be
changed.
Figure 6 shows the si32BufferObject diagram.
si32RootObject
bytes
elements
| | | | | |
si32BufferObject
Figure 6. si32BufferObject Diagram
7.2. si32BufferObjectV2
An si32BufferObjectV2 is a one-dimensional collection of homogenous elements whose type is indeterminate. Its
buffer associates a block of memory with its capacity and provides simple accessors that allow other objects to
access its contents in a uniform way. Elements of a buffer are accessible by index and can be addressed in any
order. Operations on buffers read and write entire elements, not pointers to them. BufferV2 is mutable and elastic;
memory for the elements of the buffer is allocated from the default allocation zone whenever the capacity is
changed using _set_Capacity().
Figure 7 depicts the si32BufferObjectV2 diagram.
si32RootObject
si32BufferObjectV2
bytes
elements
| | | | | |
Figure 7. si32BufferObjectV2 Diagram
14
Rev. 0.1
AN672
7.3. si32PoolObject
An si32PoolObject is a collection of homogenous elements whose type is indeterminate. Pools are mutable and
inelastic; the elements are modifiable but the capacity is constant. A pool employs a queue to manage a buffer of
elements, and the elements can only be accessed in first-in-first-out order. Pools read and write only pointers to
elements, not the elements themselves.
Figure 8 depicts the si32PoolObject diagram.
si32RootObject
si32BufferObject
buffer
si32QueueObject
queue
si32PoolObject
Figure 8. si32PoolObject Diagram
7.4. si32QueueObject
An si32QueueObject is a collection of homogenous elements whose type is indeterminate. Queues are mutable
and inelastic; the elements are modifiable but the capacity is constant. Queues are circular, and elements can only
be accessed in first-in-first-out order. Queues read and write entire elements, not pointers to them. When
initialized, a queue can use memory provided by the client or it can allocate memory from the default allocation
zone.
Figure 9 shows the si32QueueObject diagram.
si32RootObject
void*
elements
| | | | | |
si32QueueObject
Figure 9. si32QueueObject Diagram
Rev. 0.1
15
AN672
7.5. si32QueueObjectV2
An si32QueueObjectV2 is a collection of objects. Elements are retained when written and released when read. The
memory to store the object pointers is allocated from the platform.
Figure 10 depicts the si32QueueObjectV2 diagram.
si32RootObject
si32QueueObjectV2
obj*
elements
| | | | | |
Figure 10. si32QueueObjectV2 Diagram
7.6. si32ValueObject
An si32ValueObject is a box intended to allow non-object entities, such as integers, pointers, and structs, to be
managed by containers that operate on objects. This implementation limits the size of wrapped structures to 255
bytes. Value objects can wrap integers, unsigned integers, floats, pointers, and structs. Additional memory is
allocated from the platform to wrap structures, but not for integral types. That memory is deallocated when the
value object is deallocated. In this implementation a value object configured to wrap a structure cannot be reused
to wrap some other type or the structure memory will leak.
Figure 11 depicts the si32ValueObject diagram.
si32RootObject
si32ValueType
element
| | | | | |
si32ValueObject
Figure 11. si32ValueObject Diagram
16
Rev. 0.1
AN672
8. I/O Component
COMPONENT: si32IoComponent
REQUIRES: si32BaseComponent, si32ObjectComponent
PROVIDES: si32IoObject, si32PortalObject, si32TimerObject
The si32IoComponent contains objects that abstract the behavior of hardware. Derived objects in
si32McuComponent implement hardware specific concrete instances of these objects.
8.1. si32PortalObject
In general, there are several types of streaming I/O operations possible:
Non-blocking
Synchronous: Reads return immediately with an actual count of 0 or more, up to the
desired count, depending on how much data is available (i.e., hardware receive FIFO). Writes return
immediately with an actual count of 0 or more, up to the desired count, depending on how much space is
available (i.e., hardware transmit FIFO).
Non-blocking Asynchronous: Reads/writes take a callback and return immediately. The callback may be
invoked before the read/write call returns. The callback always indicates completion, and the actual count
is always the desired count unless there was an error or the request was aborted.
Blocking Synchronous: Reads/writes do not return until the full desired count has been moved or an
error occurs.
Blocking Asynchronous: Essentially the same as non-blocking asynchronous, except that it is possible
for reads/writes to block before returning, even after the callback has been invoked.
The si32Library portal objects implement un-buffered, non-blocking, synchronous and asynchronous operations.
They are designed to be as simple as possible in that they do not require other library objects or components. In
other words, si32UsartAPortalObject is derived from si32PortalObject but does not depend on other objects such
as timers, queues, buffers, pseudo-threads, or run-loops. This means that portals do not implement timeouts, they
do not implement transmit/receive queues, they do not support multiple sessions, and they do not offer blocking
operations. Additional entities are required in order to support those functionalities and are outside of the scope of
portals. Note that portals do implement operations to abort reads/writes in progress, enabling timeout/error
handlers to regain control of the underlying hardware.
Asynchronous operations on portal objects typically invoke the callback from interrupt dispatch level. The callback
function must restrict its behavior to only perform interrupt- (and possibly thread-) safe operations. Ensuring correct
behavior of callbacks is outside of the scope of the portal objects.
Figure 12 shows the si32PortalObject diagram.
si32RootObject
si32PortalObject
SI32_XXX_X_Type
Figure 12. si32PortalObject Diagram
Rev. 0.1
17
AN672
8.2. si32IoObject
Low level streaming I/O is handled by hardware specific objects directly wrapping the HAL. These objects are
called portals and implement the simple API for moving data. The si32Library provides portals for USART, I2C,
SPI, I2S, and USB. However, portals do not implement timeouts, and they do not implement transmit/receive
queues; the si32IoObject adds those capabilities. Io objects may invoke their callbacks from the interrupt or
foreground levels. Callback functions must restrict their behavior to only perform interrupt- (and possibly thread-)
safe operations. Ensuring correct behavior of callbacks is outside of the scope of the Io objects.
Figure 13 depicts the si32IoObject diagram.
si32RootObject
si32TimerObject
receive_timer
si32PoolObject
receive_pool
si32TimerObject
transmit_timer
si32IoObject
si32QueueObject
receive_queue
si32QueueObject
transmit_queue
SI32_XXX_X_Type
Figure 13. si32IoObject Diagram
18
si32PoolObject
transmit_pool
Rev. 0.1
AN672
8.3. si32TimerObject
si32TimerObject represents an abstract timer that is independent of specific timer hardware. Timer objects manage
groups of si32TimerType structures, one per interval. In other words, a timer object can time multiple time intervals
concurrently, each with its own duration and callback. Hardware specific subtypes of si32TimerObject access the
MCU's hardware timers within si32McuComponent. For example, si32SystickTimerObject operates off of the CMx
SysTick timer hardware.
Timeouts are handled by timer objects. Timer objects are hardware specific objects directly wrapping the HAL. A
timer can be started by calling _start() with a time interval and a callback. A time interval can be stopped
prematurely by calling _stop(). Timers monitor the time remaining on each of their active intervals and invoke the
callback when the interval expires. Timers are designed to be as simple as possible in that they do not require
other si32Library objects or components. For example, si32SystickTimerObject is derived from si32TimerObject
and does not depend on other objects such as queues, buffers, pseudo-threads, or run-loops.
Depending on the circumstance leading to the rescheduling operation that terminates a timer interval, timer objects
can invoke their callbacks at either foreground or interrupt levels. Therefore timer callback functions must restrict
their behavior to only perform interrupt- (and possibly thread-) safe operations. Ensuring correct behavior of
callbacks is outside of the scope of the timer objects.
Figure 14 illustrates the si32TimerObject diagram.
si32RootObject
si32TimerType
timers
| | | | | |
si32TimerObject
Figure 14. si32TimerObject Diagram
Rev. 0.1
19
AN672
9. PseudOS Component
COMPONENT: si32PseudOsComponent
REQUIRES: si32BaseComponent, si32ObjectComponent, si32ContainerComponent
PROVIDES: si32PseudoThreadObject, si32RunLoopObject
The si32PseudOsComponent provides simplistic cooperative multitasking capabilities. The si32RunLoopObject
is a cyclic executive that dispatches event handlers called work requests. Work requests are submitted to a run
loop for processing via _post_work_request() and can be marked as high or low priority. Applications built on
si32Library typically have only one run loop object that dispatches events throughout the system.
In addition to the run loop object, this component also provides si32PseudoThreadObject, which represents a
simplistic cooperative thread or task. Each pseudo-thread maintains a pointer to a block of application defined
thread local storage. Pseudo-threads have an application defined entry point established via _run(). To cause a
pseudo-thread to execute it is necessary to call _resume() on that thread. This posts a work request on the current
run loop, resulting in subsequent invocation of the thread's entry point. The thread then executes until it yields.
9.1. si32PseudoThreadObject
The si32PseudoThreadObject is not intended as an RTOS replacement. It is intended for use in very simple
applications where multitasking is convenient, but the added complexity of an RTOS is not warranted.
The si32PseudoThreadObject represents a green thread form of light-weight thread similar to a protothread. This
implementation is inspired by the work of:
Tom
Duff: http://en.wikipedia.org/wiki/Duff's_device
Simon Tatham: http://www.chiark.greenend.org.uk/~sgtatham/coroutines.html
Adam Dunkels: http://www.sics.se/~adam/dunkels06protothreads.pdf
Larry Ruane: http://code.google.com/p/protothread/
green threads: http://en.wikipedia.org/wiki/green_threads
A pseudo-thread is an object that maintains an entry point, a pointer to private storage, and a push-down stack for
spawning sub-threads. A thread's entry point is established when the thread is activated via _run(), which installs
the entry point and resumes the thread by posting a work request on the current run-loop. Each time the thread
resumes, its entry point is called.
The "pseudo-thread" concept is inspired and heavily influenced by protothreads. Pseudo-threads fundamentally
behave like protothreads, even to the point of offering build options to choose whether the implementation employs
switch abuse or computed labels. Pseudo-threads differ, however, in that they are si32 objects requiring a run-loop
to drive them, memory management, and a push-down stack of execution contexts allowing thread entry points to
be invoked in a manner similar to subroutines.
Figure 15 shows the si32PseudoThreadObject diagram.
si32RootObject
void*
thread_local_storage
| | | | | |
si32PseudoThreadObject
si32PseudoThreadStackElementType
invocation_stack
| | | | | |
Figure 15. si32PseudoThreadObject Diagram
20
Rev. 0.1
AN672
9.2. si32RunLoopObject
The si32RunLoopObject is a cyclic executive that watches two queues for events and dispatches them in priority
order. The current implementation provides a fixed priority bias wherein the high priority work request queue is
processed three times more often than the low priority work request queue. Runtime adjustable biasing is planned.
The capacities of the work request queues must be configured when the run loop is created.
Figure 16 depicts the si32RunLoopObject diagram.
si32RootObject
si32QueueObjectV2
spare_queue
si32RunLoopObject
si32QueueObjectV2
work_queue[2]
si32WorkRequestType
work_requests
| | | | | |
Figure 16. si32RunLoopObject Diagram
Rev. 0.1
21
AN672
10. USB Component
COMPONENT: si32UsbComponent
REQUIRES: none
PROVIDES: si32UsbAudioComponent, si32UsbCdcComponent, si32UsbCoreComponent,
si32UsbDfuComponent, si32UsbHidComponent, si32UsbMscComponent
The USB component is a collection of sub-components that add specific USB functionality:
si32UsbAudioComponent
provides the USB Audio Device Class functionality and definitions.
si32UsbMscComponent adds USB Mass Storage Class functionality and definitions.
si32UsbHidComponent adds USB HID Class functionality.
si32UsbDfuComponent adds USB DFU Device Class functionality.
All class components depend on the si32USBCoreComponent to provide the basic USB definitions required by
the core USB Specification and are optional; they only need to be compiled if the functionality is needed.
The si32UsbCoreComponent provides the basic USB Device model and device controller functions.
10.1. si32UsbCoreComponent
COMPONENT: si32UsbCoreComponent
REQUIRES: none
PROVIDES: si32UsbConfigurationObject, si32UsbDefaultEndpointObject, si32UsbDeviceObject,
si32UsbEndpointObject, si32UsbInterfaceObject
The si32UsbCoreComponent provides support for the standard USB Device Model and the standard USB
Requests required for USB Enumeration.
This component includes objects that provide the basic functionality that collectively can be used to act as a standalone USB device. The objects, however, are intended to serve as the base objects that are extended by the USB
Device Class components or custom applications.
10.1.1. si32UsbDeviceObject
This is the root object of any USB Device. An application can define many si32UsbDeviceObjects, but only one can
be associated with the USB device controller at any given time. An si32UsbDeviceObject also has a reference to
an si32UsbStringTable, which is used to look up strings by index and language ID, and a collection of one or more
si32UsbConfigurationObjects.
All si32UsbDeviceObjects contain a default endpoint object that can be shared among all interfaces contained in
the device's configurations. Control requests for standard Chapter 9 requests for enumeration, as well as any
device class or vendor specific requests, will all be received by the default endpoint and dispatched by the
si32UsbDeviceObject.
An si32UsbDeviceObject receives notification of a new control request and is a control request recipient, so it may
dispatch control requests that specify a Device recipient to itself.
10.1.2. si32UsbConfigurationObject
This object contains the specific power and function configuration provided by the USB device. An
si32UsbDeviceObject contains a collection of one or more configuration objects. When a configuration object has
been set through a USB SET_CONFIG standard request, the device is considered to be functional and
enumerated.
An si32UsbConfigurationObject contains a collection of si32UsbInterfaceObjects that provide the functionality
supported by the configuration.
An si32UsbConfigurationObject is not a control request recipient; therefore, it will not receive notification of control
requests that may be targeted at an interface or endpoint contained in its configuration. However, the collection of
available interfaces and endpoints is dependent on the active configuration, so a device object queries a
configuration for its interfaces or endpoints when attempting to deliver a control request.
22
Rev. 0.1
AN672
10.1.3. si32UsbEndpointObject
This object represents the device-side of a USB endpoint. An endpoint is bound to a specific interface object and
can only be used when that interface has been set. All USB I/O occurs using an si32UsbEndpointObject or an
object derived from an si32UsbEndpointObject.
An si32UsbEndpointObject is a control request recipient, so it will receive control requests that specify an endpoint
recipient through its RequestHandler operation.
10.1.4. si32UsbDefaultEndpointObject
This object derives from the si32EndpointObject and provides the support expected from a USB default control
endpoint.
10.1.5. si32UsbInterfaceObject
This object represents a single USB Interface that can be enabled within a configuration. The interface may contain
or refer to 0 or more endpoint objects through which the firmware may communicate with the host when the
interface is enabled.
The si32UsbInterfaceObject is a control request recipient, so it will receive control requests that specify an
interface recipient through its RequestHandler operation.
An si32UsbInterfaceObject contains a collection of si32UsbEndpointObjects that it uses to communicate with the
host.
10.2. si32UsbAudioComponent
COMPONENT: si32UsbAudioComponent
REQUIRES: none
PROVIDES: si32UsbAudioControlInterfaceObject, si32UsbAudioEndpointObject, si32UsbAudioInterfaceObject,
si32UsbAudioObject, si32UsbAudioStreamingEndpointObject,
si32UsbAudioStreamingInterfaceObject
The si32UsbAudioComponent encapsulates the set of objects that add support for the USB Audio Device Class.
The objects in this component extend the standard USB device objects provided by the si32UsbCoreComponent.
10.3. si32UsbDfuComponent
COMPONENT: si32UsbDfuComponent
REQUIRES: none
PROVIDES: si32UsbDfuDeviceObject, si32UsbDfuInterfaceObject, si32UsbDfuObject
The si32UsbDfuComponent contains a set of objects that adds support for the USB Firmware Download
specification, allowing applications to easily add DFU capability to their embedded applications.
10.4. si32UsbHidComponent
COMPONENT: si32UsbHidComponent
REQUIRES: none
PROVIDES: si32UsbHidInterfaceObject
The si32UsbHidComponent contains objects that add support for the USB Human Interface Device (HID) class
specification to the si32UsbComponent.
The component currently consists of the minimum objects necessary to allow an application to intercept USB HID
class specific messages for the application-specific HID interface and provide the application with endpoints that
can be used to send or receive hid reports.
Rev. 0.1
23
AN672
10.5. si32UsbMscComponent
COMPONENT: si32UsbMscComponent
REQUIRES: none
PROVIDES: si32ScsiDeviceObject, si32ScsiMediaObject, si32StorageObject, si32UsbMscObject
The si32UsbMscComponent contains the objects necessary to add support for the USB Mass Storage Class
specification.
The component defines and implements Bulk-only transport and provides default implementations for the common
SCSI Block Commands and an abstract SCSI Media Interface. An application can quickly add a new media type
using this interface by providing the media capacity, block size, a read function, and a write function.
24
Rev. 0.1
AN672
11. MCU Component
COMPONENT: si32McuComponent
REQUIRES: none
PROVIDES: si32IicAPortalObject, si32IisAPortalObject, si32SpiAPortalObject, si32SystickTimerObject,
si32UsartAPortalObject, si32UsartIoObject, si32UsbEndpointIoObject, si32UsbIoObject,
si32UsbPortalObject
The si32McuComponent provides a variety of objects that interact directly with hardware, via the si32 HAL.
11.1. si32IicAPortalObject
si32IicAPortalObject implements the si32PortalObject interface on top of SI32_I2C_A_Type modules. It does not
support non-blocking synchronous operation, only asynchronous, due to the inherently state-machine nature of
I2C. This implementation is half duplex single master only.
Figure 17 shows the si32IicAPortalObject diagram.
si32PortalObject
si32IicAPortalObject
SI32_I2C_A_Type
Figure 17. si32IicAPortalObject Diagram
Rev. 0.1
25
AN672
11.2. si32IisAPortalObject
si32IisAPortalObject implements the si32PortalObject interface on top of SI32_I2S_A_Type modules. This
implementation is full duplex master mode.
Figure 18 shows the si32IisAPortalObject diagram.
si32PortalObject
si32IisAPortalObject
SI32_I2S_A_Type
Figure 18. si32IisAPortalObject Diagram
11.3. si32SpiAPortalObject
si32SpiAPortalObject implements the si32PortalObject interface on top of SI32_SPI_A_Type modules. This
implementation is full duplex master mode.
Figure 19 illustrates the si32SpiAPortalObject diagram.
si32PortalObject
si32SpiAPortalObject
SI32_SPI_A_Type
Figure 19. si32SpiAPortalObject Diagram
26
Rev. 0.1
AN672
11.4. si32SystickTimerObject
si32SystickTimerObject is a special purpose si32TimerObject that uses the CMx's SysTick timer hardware to
schedule callbacks. It is intended as a singleton; there can be only one per CPU.
Figure 20 shows the si32SystickTimerObject diagram.
si32TimerObject
si32SystickTimerObject
SysTick
Figure 20. si32SystickTimerObject Diagram
11.5. si32UsartAPortalObject
si32UsartAPortalObject implements the si32PortalObject interface on top of SI32_USART_A_Type modules.
Figure 21 shows the si32UsartAPortalObject diagram.
si32PortalObject
si32UsartAPortalObject
SI32_USART_A_Type
SI32_UART_A_Type
Figure 21. si32UsartAPortalObject Diagram
Rev. 0.1
27
AN672
11.6. si32UsartIoObject
This io object implements full duplex transfers on SI32_USART_A_Type, or an SI32_UART_A_Type masked as an
SI32_USART_A_Type.
Figure 22 shows the si32UsartIoObject diagram.
si32IoObject
si32UsartIoObject
SI32_USART_A_Type
SI32_UART_A_Type
Figure 22. si32UsartIoObject Diagram
28
Rev. 0.1
AN672
11.7. si32UsbIoObject, si32UsbEndpointIoObject, and si32UsbDefaultEndpointIoObject
The si32UsbIoObject extends the si32IoObject by adding definitions and initialization of resources required
specifically to support USB I/O requests, including independent IoRequestPools, IoBufferPools, and
IoRequestQueues.
The si32UsbDefaultEndpointIoObject extends the si32UsbIoObject by implementing I/O operations specifically
using the USB control pipe protocol over the default endpoint (EP0). The si32UsbDefaultEndpointIoObject treats
transmit and receive operations as interdependent operations and enables support for SETUP packet detection
and notification. The si32UsbDefaultEndpointIoObject does not have any dependencies on the
si32UsbEndpointIoObject so that USB applications that require only the default endpoint can exclude the code and
memory overhead required to support unused endpoints.
si32UsbEndpointIoObject extends the si32UsbIoObject by adding definitions and the initialization of resources
required to support non-EP0 USB I/O Requests, including Bulk, Interrupt and Isochronous transfers. The
si32UsbEndpointIoObject requires use of both the SI32_USB_A_Type and SI32_USBEP_A_Type hardware
instances. Unlike the si32UsbDefaultEndpointIoObject, each direction of an si32UsbIoObject is designed to
operate completely independent of the other direction and for configuring the SI32_USBEP_A hardware on
demand, dynamically reconfiguring the endpoint control to reflect the run-time configuration.
Figure 23 shows the USB objects diagram.
si32IoObject
si32UsbIoObject
si32UsbEndpointIoObject
si32UsbDefaultEndpointIoObject
SI32_USBEP_A_Type
SI32_USB_A_Type
Figure 23. si32UsbIoObject, si32UsbEndpointIoOject, and si32UsbDefaultEndpointIoObject
Diagram
Rev. 0.1
29
AN672
CONTACT INFORMATION
Silicon Laboratories Inc.
400 West Cesar Chavez
Austin, TX 78701
Tel: 1+(512) 416-8500
Fax: 1+(512) 416-9669
Toll Free: 1+(877) 444-3032
Please visit the Silicon Labs Technical Support web page:
https://www.silabs.com/support/pages/contacttechnicalsupport.aspx
and register to submit a technical support request.
The information in this document is believed to be accurate in all respects at the time of publication but is subject to change without notice.
Silicon Laboratories assumes no responsibility for errors and omissions, and disclaims responsibility for any consequences resulting from
the use of information included herein. Additionally, Silicon Laboratories assumes no responsibility for the functioning of undescribed features
or parameters. Silicon Laboratories reserves the right to make changes without further notice. Silicon Laboratories makes no warranty, representation or guarantee regarding the suitability of its products for any particular purpose, nor does Silicon Laboratories assume any liability
arising out of the application or use of any product or circuit, and specifically disclaims any and all liability, including without limitation consequential or incidental damages. Silicon Laboratories products are not designed, intended, or authorized for use in applications intended to
support or sustain life, or for any other application in which the failure of the Silicon Laboratories product could create a situation where personal injury or death may occur. Should Buyer purchase or use Silicon Laboratories products for any such unintended or unauthorized application, Buyer shall indemnify and hold Silicon Laboratories harmless against all claims and damages.
Silicon Laboratories and Silicon Labs are trademarks of Silicon Laboratories Inc.
Other products or brandnames mentioned herein are trademarks or registered trademarks of their respective holders.
30
Rev. 0.1