Secondary Micro User Guide
Secondary Micro User Guide
Confidential Page 1 of 52
Revision History
Change SVN
Change Author Date
Version
First draft checked into version control chibner 192 23Aug2016
Added CAN, UDS reprogramming, and UART
chibner 945 01Dec2017
updates
Added CCP and UDS reprogramming inhibit
Chibner 1469 17Apr2018
APIs
Added UDS Diagnostic API MDuBois 1552 05May2018
Added SWT APIs JStepchuk 1558 08May2018
Added loop period guidance. Move
software watchdog timer to its own section chibner 1788 09Jul2018
under peripheral APIs
Updated loop_usage_pct APIs JStepchuk 2063 30Oct2018
Updating Pi Innovo logo in document
Updates to section 5 to remove the obsolete
SOS_RTNTBL_TimerRtnTable[] references JStepchuk 75723 02Apr2020
Updating return values for suart_transmit
and suart_receive
Update docs for setting loop rates and new
Cbalcom 78505 10/20/2020
Init/loop function standards
Refinements to API docs and added
CBalcom 79254 1/12/2020
Migration Guide for 3.1
OS Operating System
1.2 References
[Ref 1] M560 Technical Specification
2. Secondary Microcontroller Software Overview
The secondary micro software package is comprised of a bootloader, the application platform software,
and the header (configuration).
All secondary microcontroller source files created by Pi Innovo will start with the letter ‘s’ to denote that
they belong to the secondary microcontroller, followed by a prefix that is an abbreviation of the software
module. For instance, “sos.h” is the secondary microcontroller operating system header file, and “sdio.h”
is the header file for secondary microcontroller digital IO. All other secondary micro source files that do
not begin with the letter ‘s’, as well as some that do, were supplied by the microcontroller manufacturer.
Note: Floats, doubles, and floating-point math should not be used in secondary applications. The
CodeWarrior compiler has been known to occasionally cause issues that give incorrect data with floating-
point variables and arithmetic.
3. Operating System
The Operating System (OS) is responsible for initialization after reset, scheduling of tasks (routines), and
providing timer services. An overview of the operating system is shown in the following block diagram:
Init
Clear RAM
Config Peripherals
Init IO
Execution
Note: Background Loop, Slow Loop, and Timer Loop run in parallel
Set IO
Background Loop
Routines
Each bubble in Figure 1 refers to a task table that is executed by the operating system. Details for each
task table are given in the following sections.
3.1 Initialization
initialized variable declarations may be overwritten to 0 by the platform. All application variables should
The RAM init function must take no arguments and have no returns. The following function prototype
must be adhered to for the RAM initialization routine:
3.1.4 IO Initialization
All IO initialization will be handled by the platform.
3.2 Execution
app_timer_loop_period must be greater than or equal to 500 microseconds. If UDS communications are
used, app_timer_loop_period must be less than or equal to 15000 microseconds (15ms).
CAN and UART messages are serviced by the low-level drivers periodically by the platform in the timer
loop. Therefore, increasing app_timer_loop_period will result in slower UDS and CCP communication.
To ensure no missed application defined CAN or UART messages, app_timer_loop_period should be set
to less than or equal to half of the period of the most frequent periodic CAN or UART messages that are
received by the application code running in the timer loop.
Application routines can be added to the timer loop by adding functionality or a call to a function in the
application timer loop routine. The application timer loop routine must take no arguments and have no
returns. The following function prototype must be adhered to for the application timer loop routine:
The following APIs are provided to determine how long the timer loop is taking to execute:
Syntax: U8 sos_timer_loop_usage_pct(void)
Parameters(in): none
Parameter(out): none
Return value: U8 Percentage(actually fraction) of timer loop used, scaled by 256
Returns the fraction of the total loop time that was used by code executing in the timer
Description:
loop, for the most recent timer loop.
Caveats: Called from timer, background, or slow loop
Calls: None
Called By: Application
Syntax: U8 sos_max_timer_loop_usage_pct(void)
Parameters(in): none
Parameter(out): none
Maximum percentage (actually fraction) of timer loop used, scaled by
Return value: U8
256
Returns the maximum fraction of the total loop time that was used by code executing in
Description:
the timer loop since the last microcontroller reset.
Caveats: Called from timer, background, or slow loop
Calls: None
Called By: Application
Note 1: The returned loop usage is an 8-bit fixed-point variable with LSB of 2^-8. This means that a return
of 255 is 0.99609 (99.609% loop usage), 128 is 0.5 (50% loop usage), etc.
Note 2: since the code that calculates the timer loop usage percentage is executed at the end of the timer
loop, the loop time usage percentage may not be calculated if the code execution time exceeds the loop
time. This is because the periodic timer interrupt will occur before the usage calculation occurs, and the
timer loop will start over again prior to executing code that calculates the processor usage.
The background loop begins by running the RAM integrity routine. The RAM integrity routine can be used
to test for RAM corruption of the variables used by the software module.
If the application RAM integrity routine returns FALSE, the platform will force the ECU to reset.. The
following function prototype must be adhered to for the application RAM integrity routine:
Syntax: U8 app_ram_integrity (void)
Parameters(in): none
Parameter(out): none
Return value: U8 1 (TRUE) if RAM integrity test passes, 0 (FALSE) otherwise
Description: Function to run application-defined RAM integrity checks.
Caveats: Must be present in the application. If the return is FALSE, the ECU will reset.
Calls: User defined
Called By: Called by the platform.
Application routines can be added to the background loop by adding functionality or a call to a function
in the application background loop routine. The application background loop routine must take no
arguments and have no returns. The following function prototype must be adhered to for the application
background loop routine:
Once all routines are executed in the background loop, one slow loop pass (as described in 3.2.3) is
executed and then the loop waits until a delay timer expires. Note that the background loop can be pre-
empted at any time by the timer loop or external interrupts.
The background loop time is build-time configurable by the application software by setting the
app_background_loop_period constant defined in app_info.c.
It is important to note that the background loop is timed by the expiration of a timer, rather than being
triggered by a timer interrupt. This means that if the code executing in the background loop does not
complete within the loop period, the background loop will continue to execute until completion, thus
overrunning its allotted time. The subsequent background loop will NOT be shortened by the time
overrun.
app_background_loop_period should be less than 250ms. Any tasks that need to run at a period greater
than 250ms should go into the slow loop.
The following APIs are provided to determine how long the code in the background loop is taking to
execute:
Syntax: U8 sos_bkgnd_loop_usage_pct(void)
Parameters(in): none
Parameter(out): none
Return value: U8 Percentage (actually fraction) of background loop used, scaled by 256
Returns the fraction of the total loop time that was used by code executing in the
Description:
background loop, for the most recent background loop.
Caveats: Called from timer, background, or slow loop
Calls: None
Called By: application
Syntax: U8 sos_max_bkgnd_loop_usage_pct(void)
Parameters(in): none
Parameter(out): none
Maximum percentage (actually fraction) of background loop used,
Return value: U8
scaled by 256
Returns the maximum fraction of the total loop time that was used by code executing in
Description:
the background loop since the last microcontroller reset.
Caveats: Called from timer, background, or slow loop
Calls: None
Called By: application
Note: The returned loop usage is an 8-bit fixed-point variable with LSB of 2^-8. This means that a return
of 255 is 0.99609 (99.609% loop usage), 128 is 0.5 (50% loop usage), etc.
As mention in section 3.2.2 above, one slow loop routine pass is executed at the end of the background
loop. Because of this, the slow loop period is defined as a multiple of background loops rather than as a
time period. The slow loop period can be set by using the app_background_loops_per_slow_loop
constant defined in app_info.c. For example, if you set app_background_loop_period to 2000
microseconds, and app_background_loops_per_slow_loop to 125, then the slow loop period will be
0.250 seconds.
The OS has a built-in mechanism to allow each slow loop routine to be executed over multiple passes.
This allows a long background routine to be broken into several passes that each take a shorter period of
time to execute, which helps the background loop finish on time.
A summary of the slow loop execution is shown in the following diagram in which the first slow loop task
is broken into 3 passes, the second task has only one pass, and the third task has two passes.
Slow Loop (X*N ms)
Task 1 Pass 1
NotCompete
LoopCnt++;
Wait for next X ms background loop
Task 1 Pass 2
NotCompete
LoopCnt++;
Wait for next X ms background loop
Task 1 Pass 3
Task 2 Pass 1
Compete
LoopCnt++;
Wait for next X ms background loop
Task 3 Pass 1
NotCompete
LoopCnt++;
Wait for next X ms background loop
Task 3 Pass 2
Compete
LoopCnt++;
Wait for nextX ms background loop
LoopCnt < N
Note that it is up to the application software developer to keep the total number of passes of all slow
loop routines to be less than app_background_loops_per_slow_loop.
The slow loop routine function must have one argument of type PASS_COUNTER_E which allows the
function to know which pass number it is currently executing. The slow loop routine must return an
unsigned 8-bit integer which must be one of two macros, DONE or NOT_DONE, which signifies to the
operating system if the slow routine has finished executing all of its passes. This will be further
explained in the example in section 5.3.9. The slow loop function must adhere to the following function
description:
Syntax: SSC_RC_T ssc_get_platform_build_date (U16 *sscf_year, U16 *sscf month, U16 *sscf_day)
Parameters(in): none
U16* Pointer to store the build year
3.4 OS Timer
The platform provides a 32-bit free-running timer that increments once at the beginning of every Timer
Loop. The API provides functions to facilitate simple timeout behavior.
▪ Analog Inputs
▪ Digital I/O
▪ UART
▪ CAN
Note that when setting a digital output, state 1 is logic high, while state 0 is logic low. Two macros are
provided to help reduce confusion: DIO_HIGH and DIO_LOW.
4.4 UART
The main microcontroller and secondary microcontroller communicate via a UART serial port. The UART
API provides the ability to transmit and receive arbitrary UART messages, as well as receive a heartbeat
message from the primary microcontroller for the purpose of implementing a watchdog feature.
4.4.1 UART Transmit
The platform provides the following API for transmitting UART messages to the primary micro:
The return code of this function will be one of the following, which are defined in suart.h:
Syntax: U8 suart_comm_established(void)
Parameters(in): None
Parameter(out): None
Boolean representing UART status.
4.5 CAN
The secondary microcontroller communicates via CAN on CAN bus A (pins YE4+YF4). The CAN API
provides the ability to transmit and receive arbitrary CAN messages, as well as set up CCP and UDS
communication channels.
SCAN_ADDR_STANDARD – 11-bit ID
SCAN_ADDR_EXTENDED – 29-bit ID
The return code is currently unused and should be cast away to void during the function call.
The baud enumeration can be set to any valid enumerated value defined in scan_api.h:
The return code is currently unused and should be cast away to void during the function call.
The return value will be TRUE if the passed-in IDs were valid, FALSE otherwise.
The platform provides the following API for inhibiting reprogramming over UDS:
A single call to this API with a TRUE or 1 input argument will inhibit reprogramming over UDS until this
API is called with and input argument of FALSE or 0.
All CAN messages use one hardware message buffer per message ID. This design means that hardware
ID filtering is used for all received messages, which reduces CPU overhead. This design also means that
all CAN messages must be declared during application initialization.
The secondary microcontroller has 32 message buffers built in to the CAN peripheral. Five of these
message buffers are taken by CCP and UDS messages. This leaves 27 buffers available for user defined
CAN messages.
The platform provides the following API for assigning a CAN message to a buffer:
U32 Message ID
Parameter(out): none
message handle used in subsequent communication
Return value: SCAN_MSG_HANDLE_T
APIs
Description: Declares CAN message and associates it with a message buffer
Caveats: Must be called before CAN communication begins
Calls: None
Called By: application
If all message buffers have already been assigned, this function will return -1 as the message handle.
The platform provides the following API for transmitting a CAN message:
The platform provides the following API for receiving a CAN message:
Data is copied to the passed in array ONLY if the message handle was valid AND at least one CAN message
was received for this handle, otherwise the passed in array remains unchanged.
The application should use the timestamp to determine if a new message has been received since the last
call to scan_receive().
The platform provides the following API for checking the transmission status of a CAN message buffer:
U16*
Number of transmission acknowledgements for this message
SCAN_RC_BAD_ARGS if an invalid message buffer was
Return value: BOOL
specified, SCAN_RC_OK otherwise
Description: Returns the transmission status for a given message buffer
Caveats:
Calls: None
Called By: application
• tx_requests for the given message buffer will be incremented every time scan_transmit() is
called for that message buffer.
• tx_acks will only increment once the CAN peripheral confirms that a message was transmitted on
the bus for the given message buffer.
The platform provides the following API for retrieving the status of the CAN bus:
SCAN_BUS_INVALID – The CAN bus was not configured for any messages
The following UDS services are supported for reprogramming the secondary microcontroller. For UDS
programming guidance, please contact OpenECU support:
The API also provides a function to cause an immediate reset by enabling the watchdog timer with a
timeout of 16 microseconds.The following APIs are provided for the secondary software watchdog timer:
The following APIs are provided for the secondary software stack usage:
1. Read an analog and digital input, and then set a digital output pin high if the analog input and
digital input disagree. This is time critical and must execute every 500 microseconds.
2. A watchdog feature must reset the primary microcontroller if a UART message is not received
from the primary microcontroller within a certain time interval.
3. Read an analog input, and set a digital output pin high if the analog input crosses a threshold.
This is not time critical, but maximum allowable latency is 3 ms.
4. Send a UART message to the main micro and check that it responds with a known repeatable
response. This shall be used to periodically check the integrity of the UART communication four
times per second. Note: this requires that the primary microcontroller application includes the
code to expect this message from the secondary micro and respond appropriately.
5. No inputs shall be read and no outputs shall be set until UART communication has been
established between the two microcontrollers. Note: this requires that the primary
microcontroller application includes the code to expect this message from the secondary micro
and respond appropriately.
Requirement 3 is not time critical, and the maximum latency is 3 ms. This code can go in the background
loop, and we decided that it should be executed every 2 ms to satisfy the latency requirement.
The UART integrity check must happen only four times per second, so we will put this code in the slow
loop and we will set the slow loop to execute every 250 ms. Since the slow loop is defined by how many
background loops are executed per slow loop, we need 125 background loops at 2ms each to set the
slow loop to 250 ms.
/*****************************************************************************
*
* Define slow loop period here
* SOS_BKGND_LOOPS_PER_SLOW_LOOP is a multipe of background loop time. For
* example, if the background loop is 2 ms, then 125 background loops = 250 ms
*
*****************************************************************************/
const U32 app_background_loops_per_slow_loop = 150;
In addition to the parts of the code mentioned in the previous paragraph, there is one requirement that
is a little tricky: requirement 5: the inputs and outputs will only be enabled once UART communication is
established between the two micros. This will require an initialization sequence to be added to the
application to establish communication. The example application uses the following state machine to
execute this initialization sequence in the background loop:
Start
Response Incorrect
Check Comm Response c
// DEBUG:
#define static
static U8 app_comm_check_msg[APP_COMM_CHECK_LENGTH] = {0xDE,0xAD,3,4,5};
static U8 app_comm_check_response[APP_COMM_CHECK_LENGTH] = {0xBE, 0xEF, 3, 2, 1};
SCAN_MSG_HANDLE_T sio_tx_msg_handle[2];
SCAN_MSG_HANDLE_T sio_rx_msg_handle[2];
U8 sio_tx_msg0_data[8];
U8 sio_tx_msg1_data[8];
U8 sio_rx_msg0_data[8];
U8 sio_rx_msg1_data[8];
U16 sio_tx_msg0_cnt;
U16 sio_tx_msg1_cnt;
U16 sio_rx_msg0_cnt;
U16 sio_rx_msg1_cnt;
U32 sio_rx_msg0_timestamp;
U32 sio_rx_msg1_timestamp;
volatile static U16 sioc_can_msg_period_ms = 50;
U32 sio_can_timer_us;
/* inhibit reprogramming */
volatile static U8 app_inhibit_reprog_ccp;
volatile static U8 app_inhibit_reprog_uds;
static U8 app_inhibit_reprog_input;
static U8 app_enable_reprog_input;
/******************************************************************************
* app_ram_init:
*
* PARAMETERS: None.
*
* CONSTRAINTS: None
*
* DESCRIPTION: Initializes the simpleApp RAM.
*
* RETURNS: None
*
******************************************************************************
*/
void app_ram_init(void)
{
U8 i;
adc_pin_xe4 = 0;
adc_pin_za1 = 0;
dio_output_xa3 = DIO_LOW;
dio_output_reset_main = DIO_HIGH; /* reset line is active low */
dio_input_xa4 = 0;
adc_status_good = FALSE;
app_state = APP_STATE_DELAY;
startup_counter = 0;
comm_wait_counter = 0;
watchdog_counter = 0;
app_uart_fault = FALSE;
app_reset_main = FALSE;
app_rationality_good = FALSE;
timer_loop_cnt = 0;
app_run_time = 0;
app_inhibit_reprog_ccp = FALSE;
app_inhibit_reprog_uds = FALSE;
app_inhibit_reprog_input = FALSE;
app_enable_reprog_input = TRUE;
app_can_init();
}
void app_can_init(void)
{
scan_config(SCAN_BAUD_500_KBPS);
return (ret);
}
U16 j;
U16 x = 0;
/* Cause a delay */
for (j = 0; j < 2000; j++)
{
/* do nothing but wait */
x++;
}
app_enable_outputs_prev = appc_enable_outputs;
case APP_STATE_CHECK_COMM:
if (startup_counter >= APP_EST_COMM_TRIES)
{
app_state = APP_STATE_COMM_ERR;
app_uart_fault = TRUE;
startup_counter = 0;
}
else
{
suart_receive(&rx_length, rx_msg, &rx_trig);
if (TRUE == rx_trig)
{
startup_counter++;
for (i = 0; i < APP_COMM_CHECK_LENGTH; i++)
{
if (rx_msg[i] != app_comm_check_response[i])
{
comm_chk_good = FALSE;
}
}
if (FALSE == comm_chk_good)
{
app_state = APP_STATE_ESTABLISH_COMM;
}
else
{
/* requirement 5 - go to normal state since comm check is good */
app_state = APP_STATE_NORMAL;
}
}
}
break;
case APP_STATE_NORMAL:
case APP_STATE_COMM_ERR:
default:
{
/* Do nothing for state machine */
}
sio_can_timer_us += SOS_BACKGROUND_LOOP_PERIOD;
if ((sio_can_timer_us / 1000) >= sioc_can_msg_period_ms)
{
sio_can_timer_us = 0;
// simpleApp_UpdateCan();
}
} /* End simpleApp_Bkgnd */
/*****************************************************************************
* app_timer_routine:
*
* PARAMETERS: None
*
* CONSTRAINTS: None
*
* DESCRIPTION: This function runs every 500 us loop.
*
* RETURNS: None
*
*****************************************************************************
*/
void app_timer_routine(void)
{
/* Increment the timer loop count */
timer_loop_cnt++;
if (timer_loop_cnt >= (U16)((U32)1000000 / (U32)SOS_TIMER_LOOP_PERIOD))
{
timer_loop_cnt = 0;
app_run_time++;
}
The following code highlights how the slow loop can be broken into multiple passes, and how a slow loop
routine signals to the OS if it is finished or not. Also highlighted are the APIs for UART transmit and
receive.
/*****************************************************************************
* app_slow_routine:
*
* PARAMETERS: current pass to run
*
* CONSTRAINTS: None
*
* DESCRIPTION: This function runs once every background loop.
*
* RETURNS: None
*
*****************************************************************************
*/
U8 app_slow_routine(const PASS_COUNTER_E active_pass)
{
U8 rx_length;
U8 rx_msg[APP_COMM_CHECK_LENGTH];
U8 rx_trig;
U8 i;
U8 comm_chk_good = TRUE;
U8 ret = NOT_DONE;
/* requirement 5: only set/read IO if comms have been established */
if (APP_STATE_NORMAL == app_state)
{
/* Verfiy the active routine as a way to look for micro corruption */
if (TRUE == sos_verify_active_slow_routine(app_slow_routine))
{
switch (active_pass)
{
case PASS01:
/* send the request in pass 1 */
suart_transmit(APP_COMM_CHECK_LENGTH, app_comm_check_msg);
sos_increment_pass_counter();
break;
case PASS02:
/* wait for response and check respopnse in pass 2 */
suart_receive(&rx_length, rx_msg, &rx_trig);
if (TRUE == rx_trig)
{
/* Message has been received - check for correct response */
for (i = 0; i < APP_COMM_CHECK_LENGTH; i++)
{
if (rx_msg[i] != app_comm_check_response[i])
{
comm_chk_good = FALSE;
}
}
if (FALSE == comm_chk_good)
{
app_uart_fault = TRUE;
}
else
{
app_uart_fault = FALSE;
}
ret = DONE;
}
else
{
comm_wait_counter++;
if (comm_wait_counter > APP_COMM_WAIT_CNT)
{
/* message not received in time - declare UART fault */
app_uart_fault = TRUE;
ret = DONE;
}
else
{
/* still waiting for a response */
ret = NOT_DONE;
}
}
break;
}
}
else
{
/* this function was entered inadvertently - take remedial action */
sos_force_reset();
}
}
else
{
/* not yet in normal state - set return to DONE so we don't hold up
the rest of the software */
ret = DONE;
}
return(ret);
}
6. Building the Application
6.1 Requirements
To build an application for the M5xx secondary micro, you will need to install the following:
1. The OpenECU M5xx secondary platform software. This contains the platform library, an example
application, and several tools needed in the build process.
2. The CodeWarrior IDE. This contains the compiler for the secondary micro. A 30-day evaluation
version is available for free at
http://www.nxp.com/products/software-and-tools/software-development-tools/codewarrior-
development-tools/codewarrior-development-studios/codewarrior-for-microcontrollers/codewarrior-
development-studio-for-mpc55xx-mpc56xx-classic-ide-v2.10:CW-MPC55XX_56XX
NOTE: after the 30-day trial period, CodeWarrior will limit the code size to 128KB. A licensed version
will be required to build applications which exceed 128KB.
2. Edit the OE_Paths.bat file (in the OpenECU M560 install folder). Make sure the CodeWarrior
installation path is correct – it should look something like this: SET CODEWARRIOR_IDE="C:\Program Files
(x86)\Freescale\CW for MPC55xx and MPC56xx 2.7\bin\CmdIDE.exe"
3. Run the BuildM560ApplicationCLIStandalone.bat batch file. After a successful build, these files
should have been added to the
“(OpenECU M560 install folder)\targets\m560_secondary\build\M560_SecondaryUserApp” folder:
M560_SecondaryUserApp_tool_**.a2l – this is the ASAP2 database file that can be used with a
calibration tool to read and write memory addresses.
4. Flash the ECU using a CCP calibration tool connected to CAN bus A (pins YE4+YF4). Use the CCP IDs
from simpleApp.c. Defaults are:
a. CRO = 0x6FB
b. DTO = 0x6FA
c. station address = 1
6.3 Build Process
The M5xx secondary micro platform functions are provided as a library file
(M560_SecondaryPlatform.lib), that gets linked to the application at build time. The recommended
build procedure is to start with the example project and build scripts, and modify as necessary. The
most common modification is to add or rename files in the user application. To do this, open the
CodeWarrior project file in the CodeWarrior IDE. The Files tab of the project configuration lists all the
source files. Save any changes to the project file, close CodeWarrior, and then use the batch file to
build.
The file app_info.c contains several variables for tracking build time and version information. These
variables are automatically added to the ASAP2 databases so they can be retrieved with a calibration
tool.
Due to internal flash limits, the application size is limited to 122.88 kb. If this size is exceeded, the
linker will generate the warning “Specified length of memory directive 'internal_flash' was exceeded”.
The platform references several automatic variables that are generated at build time. The build
timestamp variables are updated from app_build_time.h, which has to be generated before the project
can be compiled.
After the application has been built, the s-record is modified by adding the memory image checksum
to the correct address. The output file from CodeWarrior is M560_SecondaryUserApp.mot. The
srecMod tool adds checksum, and writes the new file to M560_SecondaryUserApp_srecmod.s37.
The final step in the build process is to generate the A2L files (ASAP2 databases). These are used by
calibration tools to map internal variables and calibrations to memory addresses in the ECU. The symbols
and addresses are obtained from M560_SecondaryUserApp.elf, which is generated by CodeWarrior. This
file is translated into a text file M560_SecondaryUserApp.objdump by the GCC objdump tool. The capi tool
then parses the .objdump file for all the variables and calibrations in the data dictionary file
(M560_SecondaryUserApp.dde), and writes them to the ASAP2 database. All errors and warnings during
the build process are written to text files with a .err extension.
7. Migration Guide
7.1 Intro
This section outlines changes in application code that are necessary when upgrading to newer versions
of OpenECU.
• sos_timer_loop.c
o This previously could have been modified for further customization of the secondary’s
task scheduling behavior.
• sos_routine_tables.c
o This previously could have been modified to change the lists of routines to be called
upon certain platform events (timer loop, ram init, etc). These have been reworked to call
specifically-named application functions (app_ram_init, app_timer_routine, etc).
In addition, the controls for setting different loop periods have been migrated from sos.h to app_info.c.
• Review sos.h for previous timer period customizations and migrate them to the new definitions
in app_info.c
• Rename or migrate the following functions in your main app .c file (simpleApp.c in OpenECU
examples). Function prototypes can be found in sos_routine_tables.h
• Review sos_timer_loop.c and sos_routine_tables.c for previous customizations and re-apply them
elsewhere.