Please note as of Wednesday, August 15th, 2018 this wiki has been set to read only. If you are a TI Employee and require Edit ability please contact x0211426 from the company directory.

SYS/BIOS for the MSP430

From Texas Instruments Wiki
Jump to: navigation, search


Sysbios image.png
This page summarizes some MSP430-specific features, and implementation details of SYS/BIOS for MSP430 devices.

On this page, SYS/BIOS configuration settings are described with configuration script snippets. These configuration steps can also be accomplished via the XGCONF Graphical Configuration Tool, but that is not described here.

Contents

Overview of the SYS/BIOS boot sequence

This section summarizes the bootstrap sequence for SYS/BIOS. It begins with a review of the boot sequence without SYS/BIOS. This description includes some MSP430-specific items, but the general flow of the boot sequence applies to all the device families supported by SYS/BIOS.

Normal boot sequence, without SYS/BIOS

The picture below summarizes the typical device boot sequence to main(), without SYS/BIOS. The next section will describe SYS/BIOS additions to this sequence. The source files mentioned in this section can be found in the rtssrc.zip file that typically resides in this directory: {CCS Install Directory}\tools\compiler\msp430\lib

Normal boot.jpg

When the device is released from reset, the reset vector will initiate C runtime initialization, at the _c_int00() entry point (in the file boot.c). The first step to initialize the device is to initialize the system stack pointer (to the value defined by the symbol __STACK_END). This same stack is used during initial booting, as well as in and after main(). The size of the system stack defaults to 128 bytes, but can be overridden via the "-stack_size" linker option (defined via CCS project Build Properties->Tool Settings->MSP430 Linker->Basic Options).

Once the stack pointer is initialized, the _system_pre_init() hook function (in file pre_init.c) is called. By default, this function simply returns a value of 1. The user can override this behavior by linking in their own _system_pre_init() function (before the rts430 library), to do their own application-specific initialization at this early stage of boot. Because this function runs so early, it must not access any C global variables, because these have not been initialized yet. If this function returns a non-zero value, the _auto_init() function (in the file autoinit.c) will be called.

The auto_init() function will process the global variable initialization records (the “cinit” records) to initialize the global variables in the .bss section. After processing these records, auto_init() will traverse the table of .pinit functions (if any are defined), and call each function. These .pinit functions are typically C++ constructor functions, but can be used for other purposes as well. Once the table of .pinit functions has been processed, auto_init() returns back to _c_int00(), which will then call main(). If there are arguments to pass to main() (depending upon the application build options), the args_main() function (in the file SHARED\args_main.c) is used to call main(). At this point, the C runtime environment has been fully initialized, and the application can begin its intended purpose.

Boot sequence with SYS/BIOS

The picture below shows additions to the boot sequence when using SYS/BIOS. The dark blue boxes indicate hook mechanisms into the normal boot flow. The green boxes indicate the additional control points that result, and allow calling of SYS/BIOS-defined, as well as user-defined functions, at progressive stages of the boot flow. The red numeric bubbles are used to reference the description below.

Note that for this section there are mentions to source files that are modified versions of those provided with TI's MSP430 compiler (which were mentioned in the previous section). The modified files referenced here are delivered with XDCtools, and can be found at this location: {XDCtools Install Dir}\packages\ti\targets\msp430\rts430

Boot sequence updated.jpg


1. Startup_reset() refers to a hook mechanism added to boot.c to allow a table of functions (see '1a' below) to be called immediately after the stack pointer has been initialized. Since only the stack pointer is initialized at this point, these functions must not reference any global variables, because global variables have not been initialized yet, and their locations will be overwritten later in the boot sequence.

1a. For the MSP430 there are two functions for SYS/BIOS that are typically called at this stage: one for disabling the watchdog timer, and another for boosting up the CPU frequency. (The default boost frequency for booting is currently 8.192MHz; a user can change frequency later after booting, or disable the default boost, and add their own boost as an application-provided reset function.) By default, both these reset functions will be called; you can disable these functions by adding the following configuration script statements to your application configuration file:

To disable the disabling of the watchdog:

var Boot = xdc.useModule('ti.catalog.msp430.init.Boot');
Boot.disableWatchdog = false;

To disable the bootstap frequency boost:

var Boot = xdc.useModule('ti.catalog.msp430.init.Boot');
Boot.configureDCO = false;

If you want to add your own functions to this table of early reset functions, you can do so by adding statements like the following to your application configuration file:

Reset = xdc.useModule('xdc.runtime.Reset');
Reset.fxns[Reset.fxns.length++] = '&myResetFxn';

2. After the table of reset functions has been processed, booting continues in _c_int00(). The next step is to call _system_pre_init() (in the file pre_init.c). In this (XDCtools) implementation of the function, the .bss section will be initialized to zero. After this, the auto_init() routine will be called to process .cinit records to initialize global variables.

3. After .cinit records have been processed, additional SYS/BIOS and user-defined initialization functions can be called via addition of the Startup_exec() hook mechanism added to auto_init() (in file autoinit.c). Since global variables have been initialized, these functions have much more freedom in what they can do, compared to the early reset functions described in 1 and 1a above. One thing that they must not do is to enable global interrupts, because that would allow interrupts to fire before the bootstrap sequence is completed. The startup functions are partitioned into three categories: first functions, module initialization functions, and last functions.

3a. First functions execute before module initialization or last functions. First functions are referenced in a table, and each function in the table is called before proceeding to the module initialization functions. SYS/BIOS typically provides one first function: a function that initializes the system stack with a "watermark" value; this allows checking of the stack later, for depth of use, and overflow.

If you want to add your own first function to be called at this stage, you can do so by adding statements like the following to your application configuration file (*.cfg):

var Startup = xdc.useModule('xdc.runtime.Startup');
Startup.firstFxns[Startup.firstFxns.length++] = '&myFirstFxn';

3b. Module initialization functions are used to initialize the SYS/BIOS or other modules that have been configured into the application. A typical use for these functions is to initialize statically-configured instances of module objects. For example, if the application configuration file statically created two semaphores, the Semaphore module will initialize the corresponding data structures at this stage of boot. Note that this stage only applies to *module* initialization; it cannot be hooked into for application-level initialization purposes.

3c. The last functions will be invoked after all regular SYS/BIOS module initialization functions have executed. At this stage, SYS/BIOS will typically insert a function to start the Timestamp counter (if enabled in the application configuration).

If you want to add your own last functions to be called at this state, you can add statements like the following to the application configuration file:

var Startup = xdc.useModule('xdc.runtime.Startup');
Startup.lastFxns[Startup.lastFxns.length++] = '&myLastFxn';

4. After all last functions have been executed, control returns back to auto_init(), which will proceed to invoke any .pinit functions that have been defined. After this, control returns back to boot.c, which then calls to main().

Once in main() the C runtime has been fully initialized, and SYS/BIOS modules have been initialized. But SYS/BIOS has not "started up" yet; this will happen at the end of main(), when BIOS_start() is called. Before that point, the application can do many things, like creating new threads, doing application-specific initializations, etc. Two things that the application must not do at this point are: do a global interrupt enable (i.e., set GIE=1), or to call a SYS/BIOS API that blocks execution waiting for some condition. These types of calls must wait until after BIOS_start() is invoked. See each SYS/BIOS module's API descriptions to see the valid calling contexts.

5. As the last step of bootstrap, BIOS_start() must be called at the end of main(). Note that once SYS/BIOS is "started up", control will never return back to main().

The final steps for SYS/BIOS startup are:

a. Execute any user-defined startup functions. If you want to define a function that is called at this stage, you can add code like the following to your application configuration script:

var BIOS = xdc.useModule('ti.sysbios.BIOS');
BIOS.addUserStartupFunction('&myBiosStartFxn');

b. Enable servicing of hardware interrupts (i.e., set GIE=1). Any interrupts that are fully enabled can now be triggered.

c. Initialize any Timers that have been configured for the application. If individual timers are configured to "auto start", start them now.

d. If enabled within the application configuration, enable the Software Interrupt (Swi) scheduler. If any Swis are ready to run (i.e., they have been "posted"), they will preempt the startup sequence at this point, and control returns here when no Swis are ready to run.

e. If enabled within the application configuration, enable the Task scheduler. If any user-defined Tasks are ready to run they will run at this point. When none are ready, the Idle Task runs.

f. If execution reaches this point (because Tasks are not enabled in the application configuration), any configured Idle functions will be run in a continuous loop.

Interrupt Handling

This section summarizes SYS/BIOS interrupt handling on the MSP430. The design leverages MSP430-specific features, and differs from the traditional dispatched-interrupt model normally provided by SYS/BIOS.

The description begins with a quick overview of traditional interrupt handling for straight-C programs. Next is a list of some required as well as beneficial enhancements to the straight-C interrupt model for an RTOS environment. Following this is the description of the interrupt model used by SYS/BIOS on MSP430, along with some examples.

Traditional MSP430 interrupt handling in C

Typically an interrupt handler is a C function that is tagged with the "interrupt" keyword. The function takes no arguments, and has a void return type. A #pragma is used to associate the function to the corresponding interrupt vector. For example:

#pragma vector = 54;
interrupt void timerISR(void)
{
    /* service the timer interrupt */
}

When the interrupt source has been enabled, and then the interrupt is asserted by the peripheral, the CPU is vectored to the interrupt service routine. Because of the interrupt keyword, the return instruction at the end of the function is actually a "return from interrupt" instruction (RETI), which will additionally restore the Status Register (SR) that was pushed onto the stack at the start of servicing the interrupt.

On the MSP430 there are bits in this Status Register that control transitions to low power modes (see the description of the SR in the corresponding MSP430 Family User's Guide, as well as the Idling with Low Power Modes description below). Because these bits are restored as part of the return from interrupt, by default, the CPU will automatically return to the power mode in effect at the time the interrupt occurred. The C compiler provides an intrinsic function (_bic_SR_register_on_exit()) that can be called from within the ISR function to modify the SR value that was pushed onto the stack, so that the CPU can instead be "kept awake" at the end of the ISR when this SR value is restored from the stack. In this case, the CPU will resume execution following the statement that activated the low power mode from which the CPU was interrupted.

Enhancements for an RTOS environment

An enhancement to the standard "C" interrupt model that is typically required for an RTOS is:

Scheduler hold off - For example, if in an ISR an OS thread is posted or "readied" to run, it is important that the OS scheduler be "held off" from immediately switching execution to the new thread, until it is appropriate and safe to do so. An RTOS typically provides an API to be called at the start of an ISR to temporarily disable the scheduler, and another API to be called at the end of the ISR to re-enable the scheduler.

In addition to this required enhancement, there are some additional enhancements that can provide significant benefit:

Interrupt Stack - If the OS provides a system "interrupt stack", and switches to this stack immediately upon servicing a first interrupt, and then switches back after the interrupt (and any nested-on-top interrupts) completes, it can significantly reduce the amount of memory required for individual task stacks - because they all do not need to be sized large enough to accommodate the maximum possible interrupt stack depth.

Interrupt entry/exit tracking - There may be some calling constraints on certain user or OS APIs, restricting them to not be called from a hardware interrupt context, for example. So an OS typically has a mechanism to track interrupt entry/exit, so that it "knows" when execution is in the context of an interrupt, and calling constraints can be enforced.

Instrumentation - The OS can generate ISR entry/exit instrumentation to data logs, to allow full profiling of interrupt execution.

Power management - The OS can handle "keep awake" or transition back to a low power mode following completion of an interrupt, so that this does not need to be explicitly handled within each ISR function.

The SYS/BIOS interrupt model on MSP430

The approach used by SYS/BIOS is to closely mimic the traditional C approach described first, but to conditionally allow add-on of all of the enhancements just described.

To do this, scripting support from XDCtools is used to automatically generate SYS/BIOS "interrupt stub" functions, which will call user-defined ISR functions at the appropriate points of execution. The auto-generated stub functions use the "interrupt" keyword, but the user-defined functions are standard C callable functions.

By default, all the enhancements described in the previous section will be automatically added to each SYS/BIOS interrupt stub. However, the user can override these defaults via configuration changes (i.e., to reduce what is done in each interrupt stub), on an interrupt-by-interrupt basis, to fine-tune the processing for each interrupt. For example, if no SYS/BIOS scheduler-related APIs (e.g., Semaphore_post()) are called for the particular interrupt, there is no need to hold off the SYS/BIOS schedulers, so the scheduler disable/re-enable calls can be eliminated from the generated stub function.

Example Interrupt Stubs

Full Stub with all features

An example of a default stub for an ISR that schedules a Task to run from the ISR function is shown below. The ISR function (hwiFxn53()) gets called after: the Task scheduler is disabled, the stack is switched from the interrupted Task's stack to the interrupt stack, the Swi scheduler is disabled, and the execution context is updated. After the function returns, the interrupt unwinds by restoring the Swi scheduler, restoring the execution context, switching back to the interrupted Task's stack, and restoring the Task scheduler and returning from the interrupt.

#pragma vector = 53;
interrupt Void ti_sysbios_family_msp430_Hwi53(Void)
{
    ti_sysbios_BIOS_ThreadType prevThreadType;
    UInt taskKey;
    UInt swiKey;
    Char* stackKey;

    /* disable Task scheduler */
    taskKey = ti_sysbios_knl_Task_disable();

    /* switch to ISR stack */
    stackKey = ti_sysbios_family_xxx_Hwi_switchToIsrStack();

    /* disable Swi scheduler */
    swiKey = ti_sysbios_knl_Swi_disable();

    /* set thread type to Hwi */
    prevThreadType = ti_sysbios_BIOS_setThreadType(ti_sysbios_BIOS_ThreadType_Hwi);

    /* run ISR function */
    hwiFxn53(0);

    /* run any posted Swis */
    ti_sysbios_knl_Swi_restoreHwi(swiKey);

    /* restore thread type */
    ti_sysbios_BIOS_setThreadType(prevThreadType);

    /* switch back to Task stack */
    ti_sysbios_family_xxx_Hwi_switchToTaskStack(stackKey);

    /* handle any Task re-scheduling as required */
    ti_sysbios_knl_Task_restoreHwi(taskKey);
}

Note that this stub looks like it carries "a lot" of overhead. But keep in mind that this is the generated C code for the stub, and the compiler and linker will optimize this.

The script for configuring this interrupt, with all default features:

var Hwi = xdc.useModule('ti.sysbios.family.msp430.Hwi');
Hwi.create(53, '&hwiFxn53');

Reduced stub for a Swi-only application

An example of a stub for an ISR that posts a software interrupt (Swi) from the ISR function (e.g., via a call to Swi_post()) is shown below:

#pragma vector = 54;
interrupt Void ti_sysbios_family_msp430_Hwi54(Void)
{
    UInt swiKey;

    /* disable Swi scheduler */
    swiKey = ti_sysbios_knl_Swi_disable();
    
    /* run ISR function */
    hwiFxn54(0);
    
    /* run any posted Swis */
    ti_sysbios_knl_Swi_restoreHwi(swiKey);
}


This stub temporarily disables the Swi scheduler before calling the user's ISR function (hwiFxn54()). After the function returns, the Swi scheduler state is restored and any Swis posted by the ISR run at this time, before the return from interrupt (RETI).

The script for configuring this interrupt:

var Hwi = xdc.useModule('ti.sysbios.family.msp430.Hwi');
hwiParams.swiEnabled = true;
hwiParams.taskEnabled = false;
hwiParams.threadTypeEnabled = false;
Hwi.create(54, '&hwiFxn54', hwiParams);

Minimal Stub

An example of a minimal stub, which simply calls a user configured function (e.g., "hwiFxn55()") is :

#pragma vector = 55;
interrupt Void ti_sysbios_family_msp430_Hwi55(Void)
{
    /* run ISR function */
    hwiFxn55(0);
}

The script for configuring this interrupt:

var Hwi = xdc.useModule('ti.sysbios.family.msp430.Hwi');
hwiParams.swiEnabled = false;
hwiParams.taskEnabled = false;
hwiParams.threadTypeEnabled = false;
Hwi.create(55, '&hwiFxn55', hwiParams);

MSP430-unique Hwi configuration parameters

The MSP430 family Hwi Module-level configuration parameters are:

Parameter Default Usage
fillVectors true Boolean: If "true", SYS/BIOS should trap unused interrupts. Else, no traps.
resetFunc _c_int00 The program entry point to execute after the CPU is released from reset.
alwaysWake false Boolean: If "true", the stubs for all Hwi instances will modify the Status Register (SR) pushed on the stack, so that the CPU always stays awake upon return from interrupt (RETI). Else, whether the stub for a Hwi instance keeps the CPU awake or not is defined by that Hwi's individual keepAwakeEnabled configuration parameter.


The Hwi instance configuration parameters are:

Parameter Default Usage
swiEnabled true Boolean: If "true", this stub should disable the Swi scheduler upon stub entry, and restore it upon interrupt completion. If no Swi-scheduling APIs (e.g., Swi_post()) are called from within this interrupt's ISR function, then this can be set to "false".
taskEnabled true Boolean: If "true", this stub should disable the Task scheduler upon stub entry, and restore it upon interrupt completion. If no Task-scheduling APIs (e.g., Semaphore_post()) are called from within this interrupt's ISR function, then this can be set to "false".
isrStackEnabled true Boolean: If "true", the SYS/BIOS interrupt/system stack should be used when running this interrupt's ISR function (instead of the interrupted Task's dedicated stack). Else, no switching to the interrupt/system stack will occur for this interrupt. Note that "isrStackEnabled" is only relevant for the Task module; if the Task module is not enabled, only a single stack is used (i.e., the "system/interrupt stack").
nestingEnabled false Boolean: Set to "true" if the configured interrupt function will enable global interrupts temporarily within the function, to allow other interrupts to nest on top of this interrupt.
loggingEnabled false Boolean: If "true", this stub will emit interrupt begin (LM_begin) and interrupt end (LM_end) Log messages.
threadTypeEnabled true Boolean: If "true", this stub will fully track the execution context. For example, if this ISR's function (or one of the functions it calls) were to call BIOS_getThreadType(), the returned value would be BIOS_ThreadType_Hwi.
keepAwakeEnabled false Boolean: If "true", this interrupt stub should modify the SR pushed onto the stack so that when it is restored by the RETI instruction the CPU will stay in the Active mode. Else, the unmodified SR will be restored upon RETI, and the CPU may return to a previous low power mode (LPM).


Important note regarding the function called by a SYS/BIOS interrupt stub

For interrupt functions called from a SYS/BIOS interrupt stub it is critical that the function does not use the "interrupt" keyword. Doing so will typically cause an application crash upon the first attempt to return from the interrupt.

Configuring a user interrupt with no SYS/BIOS interrupt stub

Finally, it is important to note that one does not need to use SYS/BIOS interrupt stubs for all interrupts. For example, if the interrupt is simply reading a port and setting a flag that is sampled elsewhere in the program, and it makes no SYS/BIOS API calls from within the ISR, then the user can simply plug this ISR directly, on their own, and not involve SYS/BIOS at all. They can do this by providing an interrupt-keyworded ISR function, and then placing it with a "#pragma vector =" directive, as shown in the traditional C description above. Note that to do this successfully, the user must set "Hwi.fillVectors=false;" (see below) to avoid a link error because both this #pragma and SYS/BIOS will be attempting to place a value at the vector location.


Power Management

As mentioned earlier, the MSP430 supports several low power modes (LPM) that can be activated via bits in the Status Register (SR). Once activated, the device will "power down" to a reduced power consumption state, until the next interrupt wakes the CPU to its Active state.

The SR is pushed onto the stack as part of initially servicing an interrupt, and restored from the stack upon the return from interrupt (RETI). This means that if an LPM is activated prior to the interrupt, by default, that same LPM mode will be resumed when the RETI instruction is invoked. To instead keep the CPU awake after the RETI instruction, one must modify the saved SR value on the stack, so that when it is restored during RETI the appropriate bits in the SR are cleared, and the CPU is kept in its Active state. The C compiler provides an intrinsic (_bic_SR_register_on_exit()) that can be called in the interrupt routine, that takes care of the details of finding the SR value on the stack, and then clearing the specified bits.

Special note on nesting and keep awake: When interrupts are allowed to nest upon one another, it is the saved SR value on the stack from the first occurring interrupt that will ultimately determine if the CPU stays awake. Because, any interrupt that nests upon this first interrupt will interrupt the CPU while it is in its Active state, so the RETI for that interrupt will always restore the SR such that the CPU remains Active. This means that an application that is structured to rely upon the CPU ‘keep awake’ mechanism must be very careful when using interrupt nesting, to avoid deadlock situations. For example, a control "super loop" is looping checking for flags set in an ISR, and using an LPM while waiting. For the control loop to run, the ISR must use the "keep awake" feature. But if this ISR nests on top of another interrupt that is not doing "keep awake", the set-the-flag ISR function attempts "keep awake" (by modifying the SR value on the stack), but this is ultimately ignored, because the interrupt at the bottom of the nest doesn't modify the SR in effect when *it* interrupted. So, the CPU returns to the LPM, and the control loop does not run.

The LPM modes available on a particular MSP430 device are described in the corresponding device family user's guide. For example, for the 5xx/6xx devices, the following operating modes are defined (see Table 1-2 in SLAU208):

Lp modes.png

The SCG1, SCG0, OSC OFF and CPU OFF control bits are located at the following positions in the Status Register:

Sr register.png

Idling with SYS/BIOS

SYS/BIOS for the MSP430 provides a Power module, which can be used to automatically idle the CPU when no threads (i.e., no Hwi, Swi, or Tasks) are ready to run. When enabled to do so, the Power module will automatically insert a function into the SYS/BIOS Idle loop that activates the specified LPM mode. The CPU will stay in that LPM mode until an enabled hardware interrupt is triggered, to transition the CPU to its Active state.

To use the power module your application configuration needs to 'use' it:

var Power = xdc.useModule('ti.sysbios.family.msp430.Power');

Once the module is 'used', by default, SYS/BIOS will be enabled to idle the CPU in the Idle loop when no threads are ready to run, i.e.:

Power.idle = true;

The default LPM mode for idling is LPM0, but a deeper level can be configured, e.g.:

Power.idleMode = Power.LPM2;


Timers

On the MSP430 SYS/BIOS can use any Timer_A or Timer_B type timer for implementing timing services. The timers are configured to run in "compare" mode, and count upwards continuously from 0 to maximum count (0xFFFF) before rolling over. The timer's channel 0 compare threshold is used to trigger interrupts to the CPU at the appropriate intervals. For a one-shot Timer the timer peripheral is stopped after this first interrupt. For periodic timers, the compare threshold is advanced for the next interrupt, while the timer is allowed to continue to count upwards (so there is no skew introduced to reprogram the timer).

The convention used to assign logical timer IDs to the physical timers on a particular MSP430 device type is: ID "0" corresponds to the first Timer_A peripheral (TA0) available (if any) on the device. ID "1" is assigned to the next available Timer_A (TA1), and so on, until there are no more Timer_A peripherals. Then the next ID is assigned to the first Timer_B peripheral (if any), and so on. A summary page that describes the logical IDs and the corresponding physical mappings (timers, base addresses, interrupt numbers) for all the supported MSP430 device types is provided as the link "Timer Mapping Tables" in the documentation for the ti.sysbios.family.msp430.Timer module.

Clock Tick Suppression

Clock tick suppression is a feature where the timer peripheral providing the tick interrupts for the Clock module is dynamically reprogrammed to interrupt not on the next periodic tick, but on the next *needed* tick.

For example, in normal “TickMode_PERIODIC” mode, the timer peripheral will “tick” and cause a CPU interrupt on each period interval. If the application does not have any work to do, say for the next 10 ticks, the CPU will still need to wake on each tick and then resume what it was doing. If the CPU was waiting in a low power state, this is particularly wasteful, because the CPU wakes only for the purpose of counting ticks.

For MSP430, SYS/BIOS provides a “TickMode_DYNAMIC” tick mode, where the Clock module’s timer peripheral is dynamically reprogrammed to interrupt on the next *needed* tick interval. For example, if there are no timeouts registered within Clock until 10 ticks into the future, the timer peripheral is reprogrammed to interrupt the CPU after the longer, 10-tick interval. The next needed tick is determined by dynamically tracking the active timeouts currently registered within the Clock module. Depending upon the application, this can provide significant power savings, because the CPU can stay in a low power mode until it has work to do, rather than waking periodically to count ticks, and then going right back to the low power mode.

The following oscilloscope trace shows Clock tick suppression in action.

TickSupress.JPG

The top trace (in yellow) is generated by a GPIO from a dedicated (separate) timer peripheral that interrupts every 1msec. Here it is used as a reference that the Clock module’s activity can be compared to, and represents CPU wake activity when TickMode_PERIODIC is used. The bottom trace (in blue) is generated by a GPIO toggled by the Clock module when it activates in TickMode_DYNAMIC, as needed, to service a timeout. In this case, there are two periodic Clock functions, each running once every 10 ticks, but out of phase by 2 ticks. With TickMode_DYNAMIC, the Clock module wakes the CPU only when there is work to be done, and the CPU can otherwise stay in a low power mode while waiting for the next needed tick.

The TickMode_DYNAMIC implementation uses the compare feature of the MSP430 timer peripherals to set an appropriate next interrupt “threshold”. This avoids any timing skew that would be introduced if the timer was actually stopped, reprogrammed, and restarted.

Up until the SYS/BIOS 6.34 product release, TickMode_DYNAMIC was the default mode for the MSP430 Clock module. With 6.34 and later, TickMode_PERIODIC is the default mode for Clock. The Clock tick mode can optionally be changed to either TickMode_DYNAMIC or TickMode_PERIODIC, as described later in the HOWTO section.

Important Notes Regarding Clock Tick Suppression

1. The implementation of Clock tick suppression requires timely servicing of the timer peripheral interrupt by the CPU. If the CPU cannot service the timer tick interrupt quickly enough, then it is possible to get into a situation where the timer interrupt threshold is programmed too late, and won’t interrupt until the timer count “wraps around” again, typically about 2 seconds later. There is logic in the implementation to reduce this likelihood, but there is a limitation to how long the servicing can be delayed. Currently “quickly enough” is one Clock tick period. For example, if the Clock tick period is 10msec, then the Clock module must not be delayed more than 10msec from servicing the timer peripheral once the interrupt is raised, otherwise the wraparound situation can occur.

To avoid this situation, the following are required:

a. Hardware interrupts must not be disabled too long, to exceed a Clock tick period interval.

b. Non-nestable hardware ISRs must not run longer than the Clock tick period interval.

c. No application Swi should be allowed to run at the same or higher priority as the Clock Swi, long enough to exceed the Clock tick period interval.

d. The total execution time of Clock functions that execute on a given tick interrupt must not exceed the Clock tick period interval.

If these situations cannot be avoided, the Clock module should be configured to use TickMode_PERIODIC.

2. With TickMode_DYNAMIC, the function Clock_getTicks() will require more cycles to execute because it needs to read the timer peripheral and calculate the number of corresponding Clock ticks, as if the timer had actually been interrupting the CPU. For TickMode_PERIODIC, no calculation is needed, and the tick interrupt count can be returned quickly.

3. If CCS is halted while servicing a Clock tick, the “tRemaining” fields in the ROV displays can be inaccurate, because the Clock module has not finished updating internal state. Similarly, the Clock tick counts will often be tagged with “(stale data)”, to indicate that the Clock module is in the midst of a tick suppression interval, and ROV cannot accurately compute and present the tick count, as it could do with TickMode_PERIODIC.

HOWTOs

Turn off SYS/BIOS’s disabling of the watchdog timer

By default, SYS/BIOS will immediately disable the watchdog timer (WDT) after release from reset. To disable this, and let the WDT reset the MSP430 if it is not serviced in a timely fashion by the application, add the following to the application configuration script:

var Boot = xdc.useModule('ti.catalog.msp430.init.Boot');
Boot.disableWatchdog = false;

Turn off SYS/BIOS’s frequency boost at boot

By default, after release from reset, SYS/BIOS will boost MCLK and SMCLK frequencies to 8.192MHz. To disable this, and have the MSP430 boot with the device defaults, add this to the application configuration script:

var Boot = xdc.useModule('ti.catalog.msp430.init.Boot');
Boot.configureDCO = false;

Have all interrupt stubs keep the CPU awake upon return from interrupt

There is a Hwi module configuration "switch" that will direct all the SYS/BIOS-generated interrupt stubs to emit __bic_SR_register_on_exit(0xF0) to keep the CPU awake upon return from interrupt. This is provided as a debug aid, as well as a mechanism to ease porting of legacy MSP430 application code to run with SYS/BIOS. Specifying this module-level "alwaysWake" Boolean will override all of the Hwi-instance-specific "keepAwakeEnabled" Booleans. An example configuration script snippet:

var Hwi = xdc.useModule('ti.sysbios.family.msp430.Hwi');
Hwi.alwaysWake = true;

Set a breakpoint at the entry to a SYS/BIOS interrupt stub

Each generated SYS/BIOS interrupt stub will have a unique label corresponding to the interrupt priority number. This number corresponds to the "PRIORITY" number listed for the interrupt in the "Interrupt Vector Addresses" table in the device data sheet. For example, for an MSP430F5438A, the ADC12_A has a vector location corresponding to interrupt priority 54. A breakpoint can be set at the symbol "ti_sysbios_family_msp430_Hwi54" to halt execution upon activation of an ADC12_A interrupt.

Disable auto-fill of unused interrupt vectors with traps

By default, SYS/BIOS provides a mechanism to trap unintended activation of unused device interrupts (which at times may cause an immediate application crash, but at others may cause odd application failures). This mechanism can be *deactivated* via a single configuration switch, e.g., with the following script:

var Hwi = xdc.useModule('ti.sysbios.family.msp430.Hwi');
Hwi.fillVectors = false;

When "fillVectors" defaults to true, SYS/BIOS configures a trap function for each interrupt that the user has not explicitly configured, nor has been implicitly configured (e.g., by use of the Timer module). The default trap is a simple spin loop.

Note that if there are any ISRs created explicitly to *not* use SYS/BIOS interrupt stubs (see earlier description of using #pragma directly), then specifying "fillVectors=true;" will cause linker errors, because there will be collisions for placement of those ISR vectors, and the traps from SYS/BIOS for what it considers to be unused interrupts.

Configure SYS/BIOS to invoke an LPM mode when idle, with no threads ready to run

To enable idling by SYS/BIOS when no threads are in the "ready" to run state, add the following to your application configuration script:

var Power = xdc.useModule('ti.sysbios.family.msp430.Power');
Power.idle = true;

Specify the LPM mode to be use by SYS/BIOS for idling

The default depth for idling by SYS/BIOS is LPM0. To configure a different LPM mode, you can add something like the following to your application configuration script:

var Power = xdc.useModule('ti.sysbios.family.msp430.Power');
Power.idle = true;
Power.idleMode = Power.LPM3;

Note that SYS/BIOS will activate the corresponding LPM mode by setting the appropriate bits in the status register (SR). SYS/BIOS will not make any other changes to the device (e.g., configuring pins so there are no floating I/Os), which may be necessary to achieve lowest power consumption. The application is responsible for doing any necessary device configuration, and SYS/BIOS handles the SR modifications when appropriate to activate the low power mode.

Dynamically change the LPM mode used by SYS/BIOS for idling

By default, when enabled to do idling SYS/BIOS will invoke a statically-defined LPM mode, specified in the application configuration (see previous HOWTOs). If the application needs to dynamically change this LPM mode at runtime, it needs to: 1) enable dynamic changes via the application configuration (i.e., set “Power.allowDynamicMode = true;”), and then at runtime, 2) call a Power module API to change the LPM mode. For example, the following configuration script will enable idling, and dynamic changes to the level, with a initial level of LPM2 :

var Power = xdc.useModule('ti.sysbios.family.msp430.Power');
Power.idle = true;
Power.allowDynamicMode = true;
Power.idleMode = Power.LPM2;

And then in the application source, calls are added to change the LPM mode when appropriate, for example:

Power_changeIdleMode(Power_LPM3);

Know the mapping between logical Timer IDs and physical timers

The convention used to assign logical timer IDs to the physical timers on a particular MSP430 device type is: ID "0" corresponds to the first Timer_A peripheral (TA0) available (if any) on the device. ID "1" is assigned to the next available Timer_A (TA1), and so on, until there are no more Timer_A peripherals. Then the next ID is assigned to the first Timer_B peripheral (if any), and so on. A summary page that describes the logical IDs and the corresponding physical mappings (timers, base addresses, interrupt numbers) for all the supported MSP430 device types is provided as the link "Timer Mapping Tables" in the documentation for the ti.sysbios.family.msp430.Timer module.

Specify the input clock source for a Timer

SYS/BIOS supports two input clock source selections for Timers: the auxiliary clock (ACLK) and the subsystem master clock (SMCLK). (For a description of these clocks, see the corresponding MSP430 Family User’s Guide.) The default selection is ACLK, but this can be changed via a configuration parameter specified during Timer creation, for example:

var Timer = xdc.useModule('ti.sysbios.family.msp430.Timer');
var timerParams = new Timer.Params();
...
timerParams.clockSource = Timer.Source_SMCLK;
...
Timer.create(3, '&myTickFxn', timerParams);

Switch Clock module from using TickMode_PERIODIC to TickMode_DYNAMIC

To enable the SYS/BIOS Clock tick suppression feature add the following to your application configuration script:

var Clock = xdc.useModule('ti.sysbios.knl.Clock');
Clock.tickMode = Clock.TickMode_DYNAMIC;

Switch Clock module from using TickMode_DYNAMIC to TickMode_PERIODIC

To disable the SYS/BIOS Clock tick suppression feature, and have the timer peripheral used underneath Clock interrupt the CPU on every tick interval (i.e., every Clock.tickPeriod), add the following to your application configuration script:

var Clock = xdc.useModule('ti.sysbios.knl.Clock');
Clock.tickMode = Clock.TickMode_PERIODIC;

Have Clock and Timestamp use separate timer peripherals

By default on MSP430, if both the SYS/BIOS Clock and Timestamp modules are used in an application they will share a single timer peripheral, to implement both types of timing services. The timer will use its ACLK input clock, and will generate interrupts at the appropriate intervals to implement Clock ticks and timeouts. If there is an extra timer peripheral available, Timestamp can be configured to use a separate (dedicated) timer peripheral, which defaults to the same ACLK rate:

var Timestamp = xdc.useModule('ti.sysbios.family.msp430.TimestampProvider');
Timestamp.useClockTimer = false;

Change the rate of Timestamps (for the dedicated timer case)

For the case where a dedicated timer has been select for Timestamping (see above), the default is to have this timer run at the ACLK rate. To change the timer to use its SMCLK clock source (which is typically faster, giving better timestamp resolution), the following configuration script can be used:

var Timestamp = xdc.useModule('ti.sysbios.family.msp430.TimestampProvider');
Timestamp.useClockTimer = false;
Timestamp.clockSource = Timestamp.Source_SMCLK;

Training

The "Intro to TI-RTOS Kernel Workshop" is now available. Follow the link below to find out more. The TI-RTOS Kernel Workshop covers the SYS/BIOS operating system available for all TI embedded processors - C28x, MSP430, Tiva-C, C6000 and AM335x (Cortex A-8). You can take a LIVE workshop (scheduled at various sites around the U.S.) or download/stream the videos of each chapter online and watch at your own pace. All of the labs, solutions, powerpoint slides, student guides, installation instructions, lab procedures, etc., are all available to you. The workshop labs run on all MCU platforms and the C6000. Check it out...

Intro to TI-RTOS Kernel Workshop

Additionally, you can refer to the "MSP430 Design Workshop" for more information about the MSP430:
MSP430 Design Workshop