Common Programming Guide#
The following pages discuss the use of every sub-module in Common. This includes information about memory allocation, logging, error management, use of the shell or authentication sub-modules, etc.
Initialization of the Common Module#
Recommended: Read these additional pages to understand the details of how Common can be configured and initialized:
The Common module uses the initialization and configuration method described in Stacks Initialization Methods .
The configuration of the Common module and of its sub-module is explained in Common Run-Time Configuration .
The Common module is composed of several sub-modules. The initialization of the Common core is always required, but the other initializations (Shell and Authentication described below) are optional, and need to be performed only if your application requires the use of these sub-modules.
Common#
The Common module is initialized by calling Common_Init(). This function initializes common internal sub-modules, such as LIB and logging (if present).
Shell#
The Shell sub-module is initialized by calling Shell_Init(). If the Shell sub-module is present, Shell_Init() must be called before Auth_Init().
Authentication#
The Authentication sub-module is initialized by calling Auth_Init().
Initialization Example#
The example below shows how to initialize Common and all of its sub-modules.
Listing - Initialization of Common and its sub-modules#
void Ex_CommonInit (void)
{
RTOS_ERR err;
Common_Init(&err);
APP_RTOS_ASSERT_CRITICAL(err.Code == RTOS_ERR_NONE, ;);
#ifdef RTOS_MODULE_COMMON_SHELL_AVAIL
Shell_Init(&err);
APP_RTOS_ASSERT_CRITICAL(err.Code == RTOS_ERR_NONE, ;);
#endif
Auth_Init(&err);
APP_RTOS_ASSERT_CRITICAL(err.Code == RTOS_ERR_NONE, ;);
}
Authentication Module Programming Guide#
Create a User Profile#
To create a user profile, call the Auth_CreateUser() function with a user name and password combination. The new user profile has no rights by default, but you can add them later using the Auth_GrantRight()function. The following example shows how to create a user profile.
Listing - Ex_AuthCreate#
void Ex_AuthCreate (void)
{
AUTH_USER_HANDLE new_user_handle;
RTOS_ERR err;
new_user_handle = Auth_CreateUser("UserA", "PwdA", &err);
APP_RTOS_ASSERT_CRITICAL(err.Code == RTOS_ERR_NONE, ;);
PP_UNUSED_PARAM(new_user_handle);
}
Validate a User Name/Password Combination#
To obtain a user's handle and confirm that the credentials are valid, use the Auth_ValidateCredentials() function. This function compares the submitted credentials against the list of existing user profiles to find a match. Once the match is found, the function returns the associated user handle.
If the user handle has the rights, use this user handle to grant or revoke a right for another user. You can also use it to proceed with the next action the user was trying to take.
Listing - Ex_AuthValidate#
void Ex_AuthValidate (void)
{
AUTH_USER_HANDLE user_handle;
RTOS_ERR err;
(void)Auth_CreateUser("UserB", "PwdB", &err);
APP_RTOS_ASSERT_CRITICAL(err.Code == RTOS_ERR_NONE, ;);
user_handle = Auth_ValidateCredentials("UserB", "Pwd0", &err);
if (err.Code == RTOS_ERR_INVALID_CREDENTIALS) {
/* Invalid user name/password combination. */
/* At this point, we would normally return or indicate an error. */
} else if (err.Code != RTOS_ERR_NONE) {
/* Handle error. */
}
user_handle = Auth_ValidateCredentials("UserB", "PwdB", &err);
if (err.Code == RTOS_ERR_INVALID_CREDENTIALS) {
/* Invalid user name/password combination. */
} else if (err.Code != RTOS_ERR_NONE) {
/* Handle error. */
}
/* If credentials were good, we may continue and use the 'user_handle' obtained. */
PP_UNUSED_PARAM(user_handle);
}
Get a User Without Validating Its Credentials#
You can obtain a user's handle without validating the credentials by using the Auth_GetUser() function instead of Auth_ValidateCredentials(). Once you recover the user handle, it only indicates that the user exists, but does not display any associated rights.
Listing - Ex_AuthGet#
void Ex_AuthGet (void)
{
AUTH_USER_HANDLE user_a_unvalidated_handle;
RTOS_ERR err;
/* The user must have been created before, */
user_a_unvalidated_handle = Auth_GetUser("UserA", &err); /* for this call to be successful. */
APP_RTOS_ASSERT_CRITICAL(err.Code == RTOS_ERR_NONE, ;);
/* If obtained, this merely means */
PP_UNUSED_PARAM(user_a_unvalidated_handle); /* that the user exists. */
}
Rights#
To grant or revoke rights to a user handle, use the Auth_GrantRight() and Auth_RevokeRight() functions respectively. Before you can call these two functions, you need to retrieve two user handles.
The first user handle is for the user to which you want to grant or revoke a right. In this case, you do not normally need to have that user's password.
The second user handle is for the user that wishes to grant or revoke the right. This user handle must either be a root user, have admin rights, or have granting/revoking rights. Verify this second user's credentials to prevent anyone from knowing only that user's name to grant rights to another user.
To check if a given user has a particular right, use the Auth_GetUserRight() function.
The following example shows how to grant rights to a user handle as an Admin. Typically, the user name and password come from an external source (such as typing it in or from a shell), but to simplify this example, the user name/password are directly included. The example also checks if the user has the right before the operation (no rights given) and after the operation (rights now given).
Listing - Ex_AuthRights#
void Ex_AuthRights (void)
{
AUTH_USER_HANDLE user_a_unvalidated_handle;
AUTH_USER_HANDLE admin_validated_handle;
AUTH_RIGHT right;
RTOS_ERR err;
/* The user must have been created before, */
user_a_unvalidated_handle = Auth_GetUser("UserA", &err); /* for this call to be successful. */
APP_RTOS_ASSERT_CRITICAL(err.Code == RTOS_ERR_NONE, ;);
/* If obtained, this only means that the user exists. */
admin_validated_handle = Auth_ValidateCredentials("admin", "admin", &err);
if (err.Code == RTOS_ERR_INVALID_CREDENTIALS) {
/* Invalid user name/password combination. */
/* At this point, we would normally return or indicate an error. */
} else if (err.Code != RTOS_ERR_NONE) {
/* Handle error. */
}
/* If credentials were good, we may continue and use the 'user_handle' obtained. */
right = Auth_GetUserRight(user_a_unvalidated_handle, &err);
APP_RTOS_ASSERT_CRITICAL(err.Code == RTOS_ERR_NONE, ;);
/* At this point, 'right' does not contain any bit set, since no right has been granted to that user. */
Auth_GrantRight((AUTH_RIGHT_6 | AUTH_RIGHT_8), user_a_unvalidated_handle, admin_validated_handle, &err);
if (err.Code == RTOS_ERR_PERMISSION) {
/* This would mean that the 'as_user_handle' does not have the right to add. */
/* At this point, we would normally return or indicate an error. */
} else if (err.Code != RTOS_ERR_NONE) {
/* Handle error. */
}
/* If no error, rights 6 and 8 were added, let's confirm. */
right = Auth_GetUserRight(user_a_unvalidated_handle, &err);
APP_RTOS_ASSERT_CRITICAL(err.Code == RTOS_ERR_NONE, ;);
/* At this point, 'right' has the AUTH_RIGHT_6 and AUTH_RIGHT_8 set, */
/* since they have been granted to that user. */
Auth_RevokeRight(AUTH_RIGHT_6, user_a_unvalidated_handle, admin_validated_handle, &err);
if (err.Code == RTOS_ERR_PERMISSION) {
/* This would mean that the 'as_user_handle' does not have the right to revoke. */
/* At this point, we would normally return or indicate an error. */
} else if (err.Code != RTOS_ERR_NONE) {
/* Handle error. */
}
/* If no error, right 6 was revoked. */
right = Auth_GetUserRight(user_a_unvalidated_handle, &err);
APP_RTOS_ASSERT_CRITICAL(err.Code == RTOS_ERR_NONE, ;);
/* At this point, 'right' has ONLY the AUTH_RIGHT_8 set, since AUTH_RIGHT_6 has been revoked. */
PP_UNUSED_PARAM(right);
}
Rights Definition#
An application may define its own rights as follows:
Listing - Defining authentication right#
#include <rtos/include/common/auth.h> (1)
#define APP_FILE_READ_RIGHT AUTH_RIGHT_0 (2)
#define APP_FILE_WRITE_RIGHT (AUTH_RIGHT_1 | APP_FILE_READ_RIGHT) (3)
#define APP_FILE_DELETE_RIGHT (AUTH_RIGHT_2 | APP_FILE_WRITE_RIGHT) (4)
(1) Include the auth.h file.
(2) The READ right can only read and is defined as Right 0.
(3) The WRITE right can write and read, it is defined as Right 0 and Right 1.
(3) The DELETE right can delete, write and read, it is defined as Right 0, Right 1 and Right 2.
If two distinct modules use the same right numbers (AUTH_RIGHT_xx), this can cause a conflict. If a user does not have the right to execute an operation, but has an "equivalent" right from another module, the authentication module will not be able to distinguish between both rights. However, this may mean that the module may allow the user to have this right if they share the same right's value. To prevent this from happening, each module should have its own AUTH_RIGHT_xx fields, without sharing them with another module.
Shell Module Programming Guide#
Executing a Command#
Modules in need of a shell facility (such as TELNETs) interact with it using an application callback function. From the caller's point of view, once the commands have been developed and the initialization performed, all that is needed is to call the Shell execution function: Shell_Exec() .
This function parses the in parameter, which is a null-terminated string containing a complete command line (command name, followed by possible arguments separated by spaces). For example:
"App_Test –a –b –c readme.txt"
Once the command name and its arguments have been extracted, Shell searches in its command tables for a command match (in this case, App_Test is the name of the command), and invokes it. Note that the Shell_Exec() requires the in parameter to be a modifiable string, not a const one. The function also has an out_fnct argument, which is a pointer to a callback that handles the details of responding to the requester. In other words, if called by TELNETs, then TELNETs has to provide the details of the response; if called by a UART, the UART should handle the response. Finally, the p_cmd_param is a pointer to a structure containing additional parameters for the command to use. The example below shows how to call this function.
Listing - Executing a command via Shell_Exec()#
void Ex_CommonShellExec (void)
{
CPU_INT16S ret_val;
SHELL_CMD_PARAM cmd_param;
CPU_CHAR cmd_buf[25u];
RTOS_ERR err;
Str_Copy(cmd_buf, "help"); /* String passed to Shell_Exec must not be const. */
/* Call Shell_Exec() with: */
ret_val = Shell_Exec(cmd_buf, /* string containing the cmd to exec, */
Ex_CommonShellExecOutFnct, /* function to use to output, */
&cmd_param, /* a cmd param structure that could be used by cmd.*/
&err);
APP_RTOS_ASSERT_CRITICAL(err.Code == RTOS_ERR_NONE, ;);
APP_RTOS_ASSERT_CRITICAL(ret_val == SHELL_EXEC_ERR_NONE, ;);
}
Commands, Callbacks and Data Types#
Shell Commands Function#
Shell commands (that is, commands added to the shell module) are of the following prototype:
Listing - Shell Command Example#
CPU_INT16S MyShellCmd (CPU_INT16U argc,
CPU_CHAR *argv[],
SHELL_OUT_FNCT out_fnct,
SHELL_CMD_PARAM *p_cmd_param);
Based on the SHELL_CMD_FNCT type:
Listing - Shell Commands Type#
typedef CPU_INT16S (*SHELL_CMD_FNCT)(CPU_INT16U argc,
CPU_CHAR *argv[],
SHELL_OUT_FNCT out_fnct,
SHELL_CMD_PARAM *p_cmd_param);
Where:
argc is a count of the arguments supplied.
argv is an array of pointers to the strings which are those arguments.
out_fnct is a function pointer that outputs any information requested.
p_cmd_param passes additional information to the command.
The return value is command-specific and will be returned by the Shell_Exec() call for this command. However, in case of an error, SHELL_EXEC_ERR should be returned.
Output Function#
Each command is responsible for responding to its requester using the pointer to the output function parameter. This function uses the following prototype:
Listing - Shell Out Function Example#
CPU_INT16S MyShellOutFnct (CPU_CHAR *p_buf,
CPU_INT16U buf_len,
void *p_opt);
Based on the SHELL_OUT_FNCT type:
Listing - Shell Commands Type#
typedef CPU_INT16S (*SHELL_OUT_FNCT)(CPU_CHAR *p_buf,
CPU_INT16U buf_len,
void *p_opt);
Where:
p_buf
is a pointer to a response bufferbuf_len
is the length of the response bufferp_opt
is an optional argument used to provide implementation specific information (port number, UART identification, etc.)
The return value should be:
The number of data octets transmitted, if NO error occurred
SHELL_OUT_RTN_CODE_CONN_CLOSED
if the link has been closedSHELL_OUT_ERR
for any other error
The example below shows what could be a valid out_fnct:
Listing - Example of an out_fnct#
static CPU_INT16S Ex_CommonShellExecOutFnct (CPU_CHAR *p_buf,
CPU_INT16U buf_len,
void *p_opt)
{
CPU_INT16U tx_len = buf_len;
PP_UNUSED_PARAM(p_opt); /* Supplemental options not used in this command. */
while (tx_len != 0u) {
putchar(*p_buf); /* Any putchar-like function could be used, here. */
tx_len--;
p_buf++;
}
return (buf_len); /* Return nbr of tx'd characters. */
}
#endif /* RTOS_MODULE_COMMON_SHELL_AVAIL */
Adding a Command Table to Shell#
To add a new command to Shell, follow these steps:
Make sure an output function of the proper type (SHELL_OUT_FNCT) can be used by future shell commands. This output function can be as simple as the one provided in the example above, or more complex if outputting is done via UART or TELNETs.
Write functions with the proper signature. The correct prototype is defined by the SHELL_CMD_FNCT type, as described above. Any data that needs to be outputted should be outputted via the out_fnct. The samples below provide example functions that implement a wrapper for LIB's random number generator (RNG).
Listing - Shell commands functions#
static CPU_INT16S Ex_CommonShellRNG_Help (CPU_INT16U argc,
CPU_CHAR *p_argv[],
SHELL_OUT_FNCT out_fnct,
SHELL_CMD_PARAM *p_cmd_param)
{
SHELL_CMD *p_shell_cmd;
CPU_INT16S ret_val;
PP_UNUSED_PARAM(argc);
PP_UNUSED_PARAM(p_argv);
/* Iterate over all commands in cmd tbl. */
p_shell_cmd = Ex_CommonShellCmdAddCmdTbl;
while (p_shell_cmd->Fnct != 0) {
/* Output each cmd's name. */
ret_val = out_fnct((CPU_CHAR *)p_shell_cmd->Name, Str_Len(p_shell_cmd->Name), p_cmd_param->OutputOptPtr);
if ((ret_val == SHELL_OUT_RTN_CODE_CONN_CLOSED) ||
(ret_val == SHELL_OUT_ERR)) {
return (SHELL_EXEC_ERR);
}
/* Output new line. */
ret_val = out_fnct((CPU_CHAR *)STR_NEW_LINE, STR_NEW_LINE_LEN, p_cmd_param->OutputOptPtr);
if ((ret_val == SHELL_OUT_RTN_CODE_CONN_CLOSED) ||
(ret_val == SHELL_OUT_ERR)) {
return (SHELL_EXEC_ERR);
}
p_shell_cmd++;
}
return (SHELL_EXEC_ERR_NONE);
}
Listing - Shell commands functions#
static CPU_INT16S Ex_CommonShellRNG_Seed (CPU_INT16U argc,
CPU_CHAR *p_argv[],
SHELL_OUT_FNCT out_fnct,
SHELL_CMD_PARAM *p_cmd_param)
{
CPU_INT16S result_h;
CPU_INT16S result_help;
RAND_NBR seed;
if (argc != 2) { /* If not enough or too much args, display help. */
(void)out_fnct((CPU_CHAR *)EX_COMMON_SHELL_RNG_SEED_INVALID_ARG,
Str_Len(EX_COMMON_SHELL_RNG_SEED_INVALID_ARG),
p_cmd_param->OutputOptPtr);
(void)out_fnct((CPU_CHAR *)STR_NEW_LINE,
STR_NEW_LINE_LEN,
p_cmd_param->OutputOptPtr);
(void)out_fnct((CPU_CHAR *)EX_COMMON_SHELL_RNG_SEED_HELP,
Str_Len(EX_COMMON_SHELL_RNG_SEED_HELP),
p_cmd_param->OutputOptPtr);
(void)out_fnct((CPU_CHAR *)STR_NEW_LINE,
STR_NEW_LINE_LEN,
p_cmd_param->OutputOptPtr);
return (SHELL_EXEC_ERR_NONE);
}
result_h = Str_Cmp(p_argv[1u], "-h");
result_help = Str_Cmp(p_argv[1u], "--help");
if ((result_h == 0) ||
(result_help == 0)) { /* Display help. */
(void)out_fnct((CPU_CHAR *)EX_COMMON_SHELL_RNG_SEED_HELP,
Str_Len(EX_COMMON_SHELL_RNG_SEED_HELP),
p_cmd_param->OutputOptPtr);
(void)out_fnct((CPU_CHAR *)STR_NEW_LINE,
STR_NEW_LINE_LEN,
p_cmd_param->OutputOptPtr);
return (SHELL_EXEC_ERR_NONE);
}
/* Convert string number to int. */
seed = (RAND_NBR)Str_ParseNbr_Int32U((const CPU_CHAR *)p_argv[1u],
DEF_NULL,
10u);
Math_RandSetSeed(seed);
return (SHELL_EXEC_ERR_NONE);
}
Listing - Shell commands functions#
static CPU_INT16S Ex_CommonShellRNG_Get (CPU_INT16U argc,
CPU_CHAR *p_argv[],
SHELL_OUT_FNCT out_fnct,
SHELL_CMD_PARAM *p_cmd_param)
{
RAND_NBR rand;
CPU_INT16S ret_val;
CPU_CHAR rand_str_buf[DEF_INT_32U_NBR_DIG_MAX + 1u];
PP_UNUSED_PARAM(argc);
PP_UNUSED_PARAM(p_argv);
rand = Math_Rand(); /* Obtain random nbr from LIB Math module. */
/* Convert int decimal number to str. */
(void)Str_FmtNbr_Int32U(rand,
DEF_INT_32U_NBR_DIG_MAX,
DEF_NBR_BASE_DEC,
'\0',
DEF_NO,
DEF_YES,
&rand_str_buf[0u]);
/* Output random number obtained. */
ret_val = out_fnct(&rand_str_buf[0u], Str_Len(&rand_str_buf[0u]), p_cmd_param->OutputOptPtr);
if ((ret_val == SHELL_OUT_RTN_CODE_CONN_CLOSED) ||
(ret_val == SHELL_OUT_ERR)) {
return (SHELL_EXEC_ERR);
}
/* Output new line. */
ret_val = out_fnct((CPU_CHAR *)STR_NEW_LINE, STR_NEW_LINE_LEN, p_cmd_param->OutputOptPtr);
if ((ret_val == SHELL_OUT_RTN_CODE_CONN_CLOSED) ||
(ret_val == SHELL_OUT_ERR)) {
return (SHELL_EXEC_ERR);
}
return (SHELL_EXEC_ERR_NONE);
}
#endif /* RTOS_MODULE_COMMON_SHELL_AVAIL */
Once these functions have been correctly implemented and validated, build a Shell command table. This table associates a name to the functions created above. This table must be NULL-terminated by adding an empty entry at the end of the table. The example below shows how this is done for the three RNG functions.
Listing - Shell commands table#
static SHELL_CMD Ex_CommonShellCmdAddCmdTbl[] =
{
{EX_COMMON_SHELL_CMD_NAME_HELP, Ex_CommonShellRNG_Help}, /* Associate cmd str with cmd fnct. */
{EX_COMMON_SHELL_CMD_NAME_SET, Ex_CommonShellRNG_Seed},
{EX_COMMON_SHELL_CMD_NAME_GET, Ex_CommonShellRNG_Get},
{0, 0} /* Tbl is NULL-terminated to indicate end. */
};
Before these commands can be called via Shell_Exec(), call Shell_CmdTblAdd() to register them to Shell. The example below illustrates how this is done.
Listing - Shell commands table#
void Ex_CommonShellCmdAdd (void)
{
RTOS_ERR err;
/* Add cmds in Cmd Tbl to Shell's available cmds. */
Shell_CmdTblAdd((CPU_CHAR *)EX_COMMON_SHELL_CMD_TBL_NAME,
Ex_CommonShellCmdAddCmdTbl,
&err);
APP_RTOS_ASSERT_CRITICAL(err.Code == RTOS_ERR_NONE, ;);
}
The table name should be the same as the commands' prefix, which is rng, in our case.
At this point, the commands are ready to be used, just like any other Shell command.
Removing a Command Table From the Shell Module#
To remove a command table from the Shell module, call the Shell_CmdTblRem() function with the command table name that must be removed.
Toolchain Abstraction Utilities#
The Common module offers some degree of abstraction for toolchain-specific operations such as:
Detecting which version of the C standard is used during compilation
Flagging any unused parameters in a function to avoid compiler warnings
Specifying an explicit memory alignment for a given variable
To make use of the toolchain abstraction utilities, include the file rtos/common/include/toolchains.h.
C Standard Version Detection#
To detect which version of the C standard is used at compile time, you can do one of the following:
Check if specified #defines have been defined or not
Check the PP_C_STD_VERSION define value
The code snippets below show how to use either method.
Listing - C standard version detection#
void Ex_CommonToolchainC_Version (void)
{
#ifdef PP_C_STD_VERSION_C89_PRESENT
/* This part of the code will be executed only if the C standard version used to compile is at least C89. */
EX_TRACE("Compiling with at least C89 standard version.\r\n");
#endif
#ifdef PP_C_STD_VERSION_C90_PRESENT
/* This part of the code will be executed only if the C standard version used to compile is at least C90. */
EX_TRACE("Compiling with at least C90 standard version.\r\n");
#endif
#ifdef PP_C_STD_VERSION_C94_PRESENT
/* This part of the code will be executed only if the C standard version used to compile is at least C94. */
EX_TRACE("Compiling with at least C94 standard version.\r\n");
#endif
#ifdef PP_C_STD_VERSION_C99_PRESENT
/* This part of the code will be executed only if the C standard version used to compile is at least C99. */
EX_TRACE("Compiling with at least C99 standard version.\r\n");
#endif
#if (PP_C_STD_VERSION == PP_C_STD_VERSION_C89)
/* This part of the code will be executed only if the C standard version used to compile is C89. */
EX_TRACE("Compiling with the C89 standard version.\r\n");
#endif
#if (PP_C_STD_VERSION == PP_C_STD_VERSION_C90)
/* This part of the code will be executed only if the C standard version used to compile is C90. */
EX_TRACE("Compiling with the C90 standard version.\r\n");
#endif
#if (PP_C_STD_VERSION == PP_C_STD_VERSION_C94)
/* This part of the code will be executed only if the C standard version used to compile is C94. */
EX_TRACE("Compiling with the C94 standard version.\r\n");
#endif
#if (PP_C_STD_VERSION == PP_C_STD_VERSION_C99)
/* This part of the code will be executed only if the C standard version used to compile is C99. */
EX_TRACE("Compiling with the C99 standard version.\r\n");
#endif
}
Unused Parameter#
The PP_UNUSED_PARAM macro allows you to suppress any warnings associated with an unused function parameter. It is often used in template functions, or in functions that require a strict prototype but where some parameters are not useful.
The example below illustrates how to use the macro.
Listing - Removing warnings for unused function parameter#
void Ex_CommonToolchainUnusedParam (CPU_INT08U unused_param)
{
PP_UNUSED_PARAM(unused_param); /* Use macro to show unused param and remove warning. */
/* [...] */
return;
}
Logging Usage#
Understanding Asynchronous Logging and Ring Buffer Details#
When a logging channel has been set to output its entries asynchronously, none of these entries will be outputted automatically via the output function specified for that given channel.
Instead, the LOG_xxx call saves the logging entries and their information in an internal ring buffer (its size is specified during the initialization). The ring buffer mechanism allows the logging module to keep the recent log entries and discard the older ones. All the channels that are outputting asynchronously share the same ring buffer, which means that a certain channel entry may be overwritten by another channel's newer entry.
The advantage of using asynchronous mode is that logging calls have a smaller impact on the program flow than in synchronous mode. The ring buffer must either be large enough to hold several logging entries, or if every entry must be kept and outputted, it must output them periodically. Since the ring buffer always keeps the recent entries, it is also possible to not output any entry. The exception to this is when an unexpected event occurs, or if an error is detected, or if an operation did not execute as expected.
Outputting Asynchronous Logging Entries#
To output all the asynchronous entries that are currently in the internal ring buffer of the logging module, you must call the Log_Output() function. You can call this function in a very low-priority task (just higher than the Idle task, if present), which will execute only when no other tasks need to do anything. By calling this function in this way, there will be less of a delay for critical application tasks.
You can also call Log_Output() from the specific location where a known problem has occurred.
In the low-priority task, you can call Log_Output() continuously (see the example below). However, if this is done in the Idle task hook itself of the kernel, this method may prevent any kind of powered-down state to be reached if there is something to output.
You can also periodically call Log_Output() by adding a minimal delay after each call, to ensure that it does not run too often.
Listing - Call to Log_Output() in task#
void App_LoggingTaskFnct (void *p_arg)
{
PP_UNUSED_PARAM(p_arg);
while (DEF_TRUE) {
Log_Output();
}
}
Also, the Log_DataIsAvail() function can indicate if there is any data available to output. This function can be called in the Idle task hook, and if there is anything that needs to be outputted, a semaphore can be posted so that the logging output task can be readied and call Log_Output() once it is allowed to run. The following example illustrates how to do this, assuming the semaphore has already been created successfully.
First, ensure that the OS_CFG_APP_HOOKS_EN kernel configuration constant is set to DEF_ENABLED and to declare the hook needed:
Listing - Declare Idle task hook#
void App_IdleTaskHookFnct (void);
You must set App_IdleTaskHookFnct as the OS_AppIdleTaskHookPtr, after calling OSInit() and before calling OSStart(). For more information on this method, see the Kernel Documentation page .
Once done, the hook will be called each time the Idle task is entered, which results in being able to call Log_DataIsAvail().
Listing - Call to Log_DataIsAvail() in Idle task hook#
void App_IdleTaskHookFnct (void)
{
CPU_BOOLEAN data_is_avail;
RTOS_ERR err;
data_is_avail = Log_DataIsAvail();
if (data_is_avail) {
OSSemPost(&App_LoggingSem, OS_OPT_POST_1, &err);
if (RTOS_ERR_CODE_GET(err) != RTOS_ERR_NONE) {
/* Handle error. */
}
}
}
In the logging output task, pend on the semaphore and call Log_Output() whenever the semaphore is posted, before re-pending on it.
Listing - Call to LogOutput() in the logging output task#
void App_LoggingTaskFnct (void *p_arg)
{
RTOS_ERR err;
PP_UNUSED_PARAM(p_arg);
while (DEF_TRUE) {
OSSemPend(&App_LoggingSem, 0, OS_OPT_PEND_BLOCKING, DEF_NULL, &err);
if (RTOS_ERR_CODE_GET(err) != RTOS_ERR_NONE) {
/* Handle error. */
} else {
Log_Output();
}
}
}
RTOS_ERR Programming Guide#
Variations#
The content of the RTOS_ERR can vary based on on the configuration constants specified in rtos_err_cfg.h. This is described in RTOS_ERR Type Configuration .
Recommendation: You should use the abstraction macros to access any RTOS_ERR variable, assuming that these macros know which configuration is currently set and how to access the required information from the data structure. If you are not using the abstraction macros, and if the RTOS_ERR changes at any point, you may need to rewrite sections of your code. For example, you may need to prevent access to certain fields that no longer exist.
Non-Legacy Mode#
If legacy mode is not enabled (RTOS_ERR_CFG_LEGACY_EN is set to DEF_DISABLED), the RTOS_ERR will be a structure with various fields, with at least one error code, and be based on the configuration constants in the rtos_err_cfg.h file, as well as the C standard version that was used to compile the application.
The following table summarizes how each option affects the .
RTOS_ERR_CFG_LEGACY_EN is set to DEF_DISABLED;
Legacy Mode#
If you are using the legacy mode (by having set RTOS_ERR_CFG_LEGACY_EN to DEF_ENABLED), you can continue to use RTOS_ERR as a simple enum rather than a structure. You can still use the abstraction macros to obtain the error code and have the error code strings, although they will not be part of the RTOS_ERR . When legacy mode is enabled, you cannot use the extended error mode.
The following table summarizes how the configuration constants affect the errors.
RTOS_ERR_CFG_LEGACY_EN is set to DEF_ENABLED;
RTOS_ERR_CFG_EXT_EN is necessarily DEF_DISABLED.
Compiling with any C standard version will not affect the errors.
Abstraction Macros#
RTOS_ERR_CODE_GET(err_var)#
This macro obtains the error code contained in an error variable, no matter how the configuration constants are defined.
If the legacy mode is always used, you can access the RTOS_ERR variable as an enum to obtain the error code.
If the legacy mode is never used, the .Code field of the structure will always be present and can be accessed directly, without using the macro.
The RTOS_ERR_CODE_GET() macro allows you to ensure that the error code is always available, regardless of the configuration.
Listing - RTOS_ERR_CODE_GET() example#
void MyFnct(void)
{
RTOS_ERR err;
AnyCall(&err);
if (RTOS_ERR_CODE_GET(err) != RTOS_ERR_NONE) {
/* Handle error. */
}
}
This code snippet will always work, regardless of the configuration of the RTOS_ERR .
RTOS_ERR_STR_GET(err_code) & RTOS_ERR_DESC_STR_GET(err_code)#
These macros obtain the string associated with an error code (ex.: "RTOS_ERR_NONE" or "RTOS_ERR_INVALID_CFG") and the description string associated with an error code (ex.: "No error." or "Invalid configuration provided.").
These macros will only return valid values if RTOS_ERR_CFG_STR_EN is set to DEF_ENABLED. If not, they will return a "String not available." string.
If the RTOS_ERR_CFG_EXT_EN and the RTOS_ERR_CFG_STR_EN configuration constants always remain to DEF_ENABLED , you can access the fields directly. which are contained in the RTOS_ERR variable (see below):
Listing - Direct access to string fields of RTOS_ERR variable#
void MyFnct(void)
{
RTOS_ERR err;
AnyCall(&err);
if (RTOS_ERR_CODE_GET(err) != RTOS_ERR_NONE) {
printf("Error is: %s\r\n", err.CodeText);
printf("Description text is: %s\r\n", err.DescText);
}
}
The example above will no longer compile if RTOS_ERR_CFG_EXT_EN or RTOS_ERR_CFG_STR_EN is toggled to DEF_DISABLED. In this case, you should use the provided macros (see below):
Listing - Obtaining strings of RTOS_ERR variable with macros#
void MyFnct(void)
{
RTOS_ERR err;
AnyCall(&err);
if (RTOS_ERR_CODE_GET(err) != RTOS_ERR_NONE) {
printf("Error is: %s\r\n", RTOS_ERR_STR_GET(RTOS_ERR_CODE_GET(err)));
printf("Description text is: %s\r\n", RTOS_ERR_DESC_STR_GET(RTOS_ERR_CODE_GET(err)));
}
}
Note: To obtain the corresponding string, you must pass the error code (not the error variable) to both string macros.
RTOS_ERR_COPY(err_dst, err_src)#
This macro copies an error variable's content to another variable, keeping whatever values were present in the original variable. This can be useful to report errors asynchronously. The following code caption shows a simple use case:
Listing - RTOS_ERR_COPY() example#
void MyTwoOperationFnct (RTOS_ERR *p_err)
{
RTOS_ERR err_one;
RTOS_ERR err_two;
MyOperationOneFnct(&err_one);
MyOperationTwoFnct(&err_two);
if (RTOS_ERR_CODE_GET(err_one) != RTOS_ERR_NONE) {
RTOS_ERR_COPY(*p_err, err_one);
} else {
RTOS_ERR_COPY(*p_err, err_two);
}
}
RTOS_ERR_SET(err_var, err_code)#
This macro defines an error code in an error variable and defines any of the other available fields.
Note: You are not required to define the error variables to RTOS_ERR_NONE before passing them to any stack.
Listing - RTOS_ERR_SET() example#
void MyFnct (RTOS_ERR *p_err)
{
CPU_BOOLEAN success_flag;
success_flag = AnyOperation();
if (success_flag == DEF_OK) {
RTOS_ERR_SET(*p_err, RTOS_ERR_NONE);
} else {
RTOS_ERR_SET(*p_err, RTOS_ERR_FAIL);
}
}
Micrium OS Asserts Programming Guide#
The Common module offers assert capabilities that can be used in various situations in your application. For more information on asserts in general, please refer to Assertions .
Macro Calls#
The APP_RTOS_ASSERT_CRITICAL() and APP_RTOS_ASSERT_DBG() macros check if a given expression is evaluated with a positive result. If the result is not positive, the macro will do one of the following operations:
If RTOS_CFG_RTOS_ASSERT_CRITICAL_FAILED_END_CALL(ret_val) and/or RTOS_CFG_RTOS_ASSERT_DBG_FAILED_END_CALL(ret_val) are defined, the macro will call these.
If they are not defined, CPU_SW_EXCEPTION(ret_val) will be called.
The debug asserts typically check for conditions that are caused by invalid parameters or invalid configurations. They are used to notify the developer that something is not correct with the way the code is being used. Those can and should be disabled once development is completed.
The critical asserts typically check for conditions from which it is practically impossible to recover at run-time. Therefore, if such a condition is detected, the program's execution should be suspended before any more damage occurs.
An example of an assert using the macros is provided below.
Listing - Assert call example#
#include <rtos/common/include/rtos_utils.h>
/* These are the only valid values for this example. */
#define VALUE_FIRST 1u
#define VALUE_SECOND 2u
#define VALUE_THIRD 3u
void *ExAssert (CPU_INT08U value)
{
/* Make sure arg passed is one of the valid values. */
APP_RTOS_ASSERT_DBG(((value == VALUE_FIRST) ||
(value == VALUE_SECOND) ||
(value == VALUE_THIRD)), DEF_NULL);
/* Indicate a value to return in case of failure. */
/* ... */
return (DEF_NULL);
}
Macro Fail Calls#
These macro calls are intended to be used when an assert must be triggered without checking any additional expression results.
For example, ending in the default case of a switch statement when all known types have their specific case could be considered a critical failure. In these cases, you should use the APP_RTOS_ASSERT_CRITICAL_FAIL() and APP_RTOS_ASSERT_DBG_FAIL() macros. An example is provided below.
Listing - Assert fail call example#
#include <rtos/common/include/rtos_utils.h>
/* These are the only valid values for this example. */
#define VALUE_FIRST 1u
#define VALUE_SECOND 2u
#define VALUE_THIRD 3u
void ExAssertFail (CPU_INT08U value)
{
CPU_INT08U flag;
switch (value) {
case VALUE_FIRST:
case VALUE_SECOND:
flag = DEF_YES;
break;
case VALUE_THIRD:
flag = DEF_NO;
break;
default: /* Dflt case reached means an invalid arg was passed. */
/* Call APP_RTOS_ASSERT_DBG_FAIL() to indicate err. */
APP_RTOS_ASSERT_DBG_FAIL(;); /* Indicate ; as return value if function returns void. */
}
/* ... */
}
LIB Constants, Defines and Macros Guide#
The LIB sub-module of Common offers several #defines and constants that can be used in any type of application. These constants range from character constants to digits, time, bits, or alignment constants. The following sections details each of these categories and explain how they can be used.
General#
Constants#
The sections below contain a brief description of some of the defines in API LIB Def.
Boolean Constants#
LIB contains many Boolean constants to configure, assign, and test Boolean values or variables. These constants can include: DEF_TRUE/DEF_FALSE, DEF_YES/DEF_NO, DEF_ON/DEF_OFF, DEF_ENABLED/DEF_DISABLED, etc.
Bit Constants#
LIB contains bit constants to configure, assign, and test appropriately-sized bit-field or integer values or variables by defining values corresponding to specific bit positions. Currently, LIB supports bit constants up to 64-bits (DEF_BIT_63). These constants include: DEF_BIT_00, DEF_BIT_07, DEF_BIT_15, etc.
Octet Constants#
LIB contains octet constants to configure, assign, and test appropriately-sized, octet-related integer values or variables by defining octet or octet-related values. These constants include: DEF_OCTET_NBR_BITS and DEF_OCTET_MASK.
Integer Constants#
LIB contains integer constants to configure, assign, and test appropriately-sized, integer values or variables by defining integer-related values. These constants include: DEF_INT_08_MASK, DEF_INT_16U_MAX_VAL, and DEF_INT_32S_MIN_VAL.
Number Base Constants#
LIB contains number base constants to configure, assign, and test number base values or variables by defining number base values. These constants include: DEF_NBR_BASE_BIN and DEF_NBR_BASE_HEX.
Time Constants#
LIB contains time constants to configure, assign, and test time-related values or variables by defining time-related values. These constants include: DEF_TIME_NBR_HR_PER_DAY, DEF_TIME_NBR_SEC_PER_MIN, DEF_TIME_NBR_mS_PER_SEC, etc.
Macros#
Bit Macros#
LIB provides macros to perform various bit operations, such as setting or clearing a bit, checking if a bit or a group of bits is/are set or cleared, masking bits, etc. These macros are defined in API LIB Def and API LIB Utilities.
Integer Macros#
LIB offers macros to check if given values fit within a range or to obtain the maximum or minimum value between two variables. These macros are defined in API LIB Def.
Characters#
Character Values Constants#
LIB contains many character value constants such as:
ASCII_CHAR_LATIN_DIGIT_ZERO ... ASCII_CHAR_LATIN_DIGIT_NINE
ASCII_CHAR_LATIN_UPPER_A ... ASCII_CHAR_LATIN_UPPER_Z
ASCII_CHAR_LATIN_LOWER_A ... ASCII_CHAR_LATIN_LOWER_Z
One constant exists for each ASCII character, though additional aliases are provided for some characters. These constants should be used to configure, assign, and test appropriately-sized ASCII character values or variables.
Character Macros and Functions#
LIB provides macros and functions to act on characters. It is also possible to check if a character is a whitespace, a punctuation sign or an alphanumeric character. These functions and macros are defined in lib_ascii.c and lib_ascii.h .
For example, it is possible to know if a given character is upper-case or lower-case or convert it to upper- or lower-case.
Memory#
Memory Macros and Functions#
LIB contains functions and macros that replace standard library memory functions such as memclr(), memset(), memcpy(), memcmp(), etc; as well as generic versions of network functions, ntohl(), ntohs(), htonl(), htons(), to convert data with any endianness type to other types of endianness. These functions are defined in lib_mem.c and lib_mem.h .
Strings#
String Functions#
LIB contains library functions that replace standard library string functions such as strlen(), strcpy(), strcmp(), etc. These functions are defined in lib_str.c.
Math#
Math Functions#
LIB contains library functions that replace standard mathematics functions such as rand(), srand(), etc. These functions are defined in lib_math.c. As for the standard functions, the random number generator must first be seeded before it can provide good random numbers. This is done using Math_RandSeed() or Math_RandSetSeed() .
Memory Allocation Guide#
The LIB sub-module allows the user to create memory segments and use them for tasks such as allocating blocks or creating dynamic memory pools.
Memory Segments#
A memory segment is an object describing a memory region. It can describe virtually any kind of memory region, with the following parameters, when needed:
Base address of the memory region
Size of the memory region
Any padding required for data allocated in this memory region (to complete a cache line, for example)
For example, one memory segment can describe the following:
All of the external RAM on a board or only part of this RAM
Describe a region of dedicated memory for USB
Describe a region of cacheable memory
Describe a region that is accessible by DMA or not
And more
Once a memory segment is created using the Mem_SegCreate () function, you can use it in various ways. The table below summarizes each action that can be done using an existing memory segment, which function(s) can be used, and the parameters that the function(s) take to perform the operation.
Table - LIB Mem Operations#
Operation | Function |
---|---|
Obtain remaining data space in the memory segment. | Mem_SegRemSizeGet() |
Allocate a single block of a given size, from either the global heap or another segment. | Mem_SegAlloc() |
Allocate a single block of a given size, from either the global heap or another segment, with alignment | Mem_SegAllocExt() |
Allocate a single block of a given size, from either the global heap or another segment, with alignment, for memory that needs padding specified in memory segment (cacheable or DMA-accessed memory, typically). | Mem_SegAllocHW() |
Create a dynamic pool of fixed-size memory blocks. | Mem_DynPoolCreate() |
Create a dynamic pool of persistent fixed-size memory blocks, with an associated callback to initialize their content, if needed. | Mem_DynPoolCreatePersistent() |
Create dynamic pool of fixed-size memory blocks that needs padding specified in memory segment (cacheable or DMA-accessed memory, typically) | Mem_DynPoolCreateHW() |
Obtain block from the dynamic memory pool. | Mem_DynPoolBlkGet() |
Return block to the dynamic memory pool. | Mem_DynPoolBlkFree() |
Know how many blocks are left in a dynamic memory pool. | Mem_DynPoolBlkNbrAvailGet() |
Declaring Memory Segments at Compile-Time#
It is possible for memory segments to be created at compile-time to indicate to LIB Mem the existence of that particular segment by using the MEM_SEG_INIT() macro, combined with a call to Mem_SegReg() at run-time. If the call to Mem_SegReg() is omitted, the segment will still work correctly, but its information will not be included in any debug output. When another new segment is created, this segment will not be part of the ones checked for overlap.
Memory Segment Allocation#
LIB memory allocation functions provide for the allocation of memory from a general-purpose heap or particular memory segments. Single memory blocks may be allocated directly from the heap or from any segment. However, to prevent fragmentation, these memory blocks cannot be freed back.
Three different functions are available to allocate memory from a memory segment:
Table - Memory segment allocation operations#
The table below summarizes where every property used for an allocation comes from.
Table - Parameters used for allocating with Mem_SegAlloc() functions#
Function | Allocated data size | Allocated data alignment | Data padding alignment |
---|---|---|---|
Mem_SegAlloc() | Function parameter. | Defaults to sizeof(CPU_ALIGN). | Not used. |
Function parameter. | Function parameter. | Not used. | |
Mem_SegAllocHW() | Function parameter. | Function parameter. | Passed at segment creation. |
Example Usage of Memory Segments#
Listing - Memory Segment Allocation Usage Example in the Memory Allocation Guide page gives an example usage of memory segments. In this example, we attempt to create a memory segment and to allocate three buffers of 20 bytes, using the three different segment allocation functions available. Following the listing is an explanation of the differences between these three buffers.
Listing - Memory Segment Allocation Usage Example#
#define CACHE_LINE_LEN 32u
static CPU_INT08U MemSegData[4096u];
static MEM_SEG MemorySegment;
static void Mem_SegExample (void)
{
CPU_INT08U *p_general_purpose_buf;
CPU_INT08U *p_general_purpose_buf_with_align;
CPU_INT08U *p_hardware_buffer;
RTOS_ERR err_lib;
/* ------------ CREATION OF MEMORY SEGMENT ------------ */ (1)
Mem_SegCreate( "Name", /* Name of mem seg (for debugging purposes). */
&MemorySegment, /* Pointer to memory segment structure. */
(CPU_ADDR)MemSegData, /* Base address of memory segment data. */
4096u, /* Length, in byte, of the memory segment. */
CACHE_LINE_LEN, /* Padding alignment value. */
&err_lib);
if (err_lib != RTOS_ERR_NONE) { /* Validate memory segment creation is successful. */
/* Handle error case. */
return;
}
/* ------- ALLOCATION OF GENERAL PURPOSE BUFFER ------- */ (2)
p_general_purpose_buf = Mem_SegAlloc("General purpose buffer",
&MemorySegment,
20u, /* Requested buffer length. */
&err_lib);
if (err_lib != RTOS_ERR_NONE) { /* Validate memory segment allocation is successful. */
/* Handle error case. */
return;
}
/* --- ALLOCATION OF ALIGNED GENERAL PURPOSE BUFFER --- */ (3)
p_general_purpose_buf_with_align = Mem_SegAllocExt("General purpose buffer aligned",
&MemorySegment,
20u,
8u, /* Request 8 bytes boundary alignment. */
DEF_NULL,
&err_lib);
if (err_lib != RTOS_ERR_NONE) { /* Validate memory segment allocation is successful. */
/* Handle error case. */
return;
}
/* ---------- ALLOCATION OF HARDWARE BUFFER ----------- */ (4)
p_hardware_buffer = Mem_SegAllocHW("Buffer read/written via DMA engine",
&MemorySegment,
20u,
1024u, /* DMA engine required alignment. */
DEF_NULL,
&err_lib);
if (err_lib != RTOS_ERR_NONE) { /* Validate memory segment allocation is successful. */
/* Handle error case. */
return;
}
/* ... */
}
(1) Creation of the memory segment. The memory segment will be created on a statically allocated buffer of 4096 bytes. The base address of the memory segment data can also point to a controller dedicated memory.
(2) Allocation of a general-purpose buffer. p_general_purpose_buf is intended to be a control buffer and is only read/written by the CPU. This buffer will automatically be aligned on CPU word boundary.
(3) Allocation of a general-purpose buffer with specified alignment. p_general_purpose_buf_with_align will be allocated from the memory segment but will be aligned on an 8-byte boundary.
(4) Allocation of a hardware buffer (a buffer that can be read/written from a DMA engine). p_hardware_buffer will be allocated from the memory segment and will be aligned on a 1024 bytes boundary. The difference with p_general_purpose_buf_with_align is that this buffer will have a real length of 32 bytes as it will be padded using the padding alignment specified at the time of the memory segment creation at (1). The padding_align argument was set to a cache line length. This buffer is guaranteed to not share its memory cache line with other buffer hence preventing cache incoherence.
Dynamic Memory Pools#
Memory pool blocks can be allocated from either the general-purpose heap or from dedicated memory specified by the application. Memory pool blocks can be dynamically allocated and freed during application execution because memory pool blocks are fixed-size, which prevents possible fragmentation.
Dynamic memory pools are a pool of memory blocks that can be dynamically allocated from either the general-purpose heap or a specific memory segment. Since the blocks have a fixed size, it is possible to return (free) them to their pool at run-time, without any chance of fragmentation. They also have the particularity that if there are no blocks available when attempting to get one, it will be allocated from free space on the memory segment.
Each pool has an initial number of allocated blocks that will be allocated at pool creation from the memory segment passed. Each pool also has a maximum number of blocks that can be allocated. This maximum can either be a finite or infinite amount; every time there are no available blocks, pool blocks will be allocated from the memory segment until the memory segment is full.
The dynamic memory pools can allocate the following:
General-purpose memory blocks
Persistent blocks that keep the data stored in them even when freed
Hardware memory blocks
This means that there are three different functions available to create dynamic memory pools:
Table - Mem_DynPoolCreate() functions#
Function | Description | Use case |
---|---|---|
Creates a standard memory pool and allows to specify the memory alignment of each memory block. | General-purpose memory block. | |
Mem_DynPoolCreatePersistent() | Creates a standard memory pool that guarantees the data integrity of each block even when those blocks are freed and re-obtained. | Memory blocks that contain data that must never be lost; for example, containing other dynamically allocated data, or run-time created objects such as Kernel objects. |
Creates a hardware memory pool. The memory blocks will be aligned as specified and padded as per memory segment properties. | Memory blocks that can be read/written via a DMA engine. |
The table below summarizes where every property used for an allocation comes from.
Table - Parameters used for allocating with Mem_SegAlloc() functions#
Function | Allocated block size | Allocated block alignment | Block padding alignment |
---|---|---|---|
Mem_DynPoolCreate() | Function parameter. | Function parameter. | Not used. |
Mem_DynPoolCreatePersistent() | Function parameter. | Function parameter. | Not used. |
Mem_DynPoolCreateHW() | Function parameter. | Function parameter. | Passed at segment creation. |
Mem_DynPoolBlkGet() on a pool created with Mem_DynPoolCreate() | Passed at pool creation. | Passed at pool creation. | Not used. |
Mem_DynPoolBlkGet() on a pool created with Mem_DynPoolCreatePersistent() | Passed at pool creation. | Passed at pool creation. | Not used. |
Mem_DynPoolBlkGet() on a pool created with Mem_DynPoolCreateHW() | Passed at pool creation. | Passed at pool creation. | Passed at segment creation. |
Dynamic memory pool usage example#
Listing - Dynamic Memory Pool Usage Example in the Memory Allocation Guide page gives an usage example of dynamic memory pools.
Listing - Dynamic Memory Pool Usage Example#
#define CACHE_LINE_LEN 32u
static CPU_INT08U MemSegData[4096u];
static MEM_SEG MemorySegment;
static MEM_DYN_POOL DynamicMemPool;
static MEM_DYN_POOL DynamicMemPoolHW;
static void Mem_DynPoolExample (void)
{
CPU_INT08U *p_blk;
CPU_INT08U *p_blk_hw;
RTOS_ERR err_lib;
/* -------- CREATION OF MEMORY SEGMENT ------ */ (1)
Mem_SegCreate( "Segment name", /* Name of mem seg (for debugging purposes). */
&MemorySegment, /* Pointer to memory segment structure. */
(CPU_ADDR)MemSegData, /* Base address of memory segment data. */
4096u, /* Length, in byte, of the memory segment. */
CACHE_LINE_LEN, /* Padding alignment value. */
&err_lib);
if (err_lib != RTOS_ERR_NONE) { /* Check memory segment creation. */
/* Handle error case. */
return;
}
/* CREATE GENERAL-PURPOSE DYNAMIC MEMORY POOL */ (2)
Mem_DynPoolCreate("General-purpose dynamic memory pool",/* Name of dynamic pool (for debugging). */
&DynamicMemPool, /* Pointer to dynamic memory pool data. */
&MemorySegment, /* Pointer to memory segment to use. */
20u, /* Block size, in bytes. */
sizeof(CPU_ALIGN), /* Block alignment, in bytes. */
10u, /* Initial number of blocks. */ (3)
10u, /* Maximum number of blocks. */ (4)
&err_lib);
if (err_lib != RTOS_ERR_NONE) { /* Validate dynamic memory pools creation. */
/* Handle error case. */
return;
}
/* CREATION OF HARDWARE DYNAMIC MEMORY POOL */ (5)
Mem_DynPoolCreateHW("Hardware dynamic memory pool", /* Name of dynamic pool (for debugging). */
&DynamicMemPoolHW, /* Pointer to dynamic memory pool data. */
&MemorySegment, /* Pointer to memory segment to use. */
20u, /* Block size, in bytes. */
8u, /* Block alignment, in bytes. */
10u, /* Initial number of blocks. */
LIB_MEM_BLK_QTY_UNLIMITED, /* Maximum number of blocks. */ (6)
&err_lib);
if (err_lib != RTOS_ERR_NONE) { /* Check dynamic memory pools creation. */
/* Handle error case. */
return;
}
/* ---------- BLOCK GET OPERATION ----------- */ (7)
p_blk = (CPU_INT08U *)Mem_DynPoolBlkGet(&DynamicMemPool,
&err_lib);
if (err_lib != RTOS_ERR_NONE) { /* Validate block get operation. */
/* Handle error case. */
return;
}
/* ------ HARDWARE BLOCK GET OPERATION ------ */ (8)
p_blk_hw = (CPU_INT08U *)Mem_DynPoolBlkGet(&DynamicMemPoolHW,
&err_lib);
if (err_lib != RTOS_ERR_NONE) { /* Check block get operation is successful. */
/* Handle error case. */
return;
}
/* ... */
/* ---------- BLOCK FREE OPERATION ---------- */ (9)
Mem_DynPoolBlkFree( &DynamicMemPool,
(void *)p_blk,
&err_lib);
if (err_lib != RTOS_ERR_NONE) { /* Check block free operation is successful. */
/* Handle error case. */
return;
}
/* ----- HARDWARE BLOCK FREE OPERATION ------ */ (10)
Mem_DynPoolBlkFree( &DynamicMemPoolHW,
(void *)p_blk_hw,
&err_lib);
if (err_lib != RTOS_ERR_NONE) { /* Check block free operation is successful. */
/* Handle error case. */
return;
}
}
(1) Creation of the memory segment from which memory blocks will be allocated.
(2) Creation of the dynamic memory pool. This memory pool will allocate general-purpose memory blocks of 20 bytes and aligned on CPU word boundaries.
(3) At creation 10 blocks will be allocated and available.
(4) A maximum of 10 blocks can be allocated from this dynamic memory pools. Since the initial number of block equals the maximum number of blocks, this will create a static memory pool.
(5) Creation of a hardware dynamic memory pool. The requested alignment is 8 bytes, but since the padding alignment specified at the time of the memory segment creation is 32 bytes, the memory blocks will be aligned on a 32-byte boundary and will have a length of 32 bytes.
(6) No limit of memory block quantity is specified for this dynamic memory pool. When more than 10 blocks are taken from the pool, the dynamic memory pool will start allocating blocks from the free space of the memory segment. Once a memory block is freed, it will be available for the next allocation. It is possible to allocate memory blocks until the memory segment overflows.
(7) A general-purpose memory block is taken from the pool. The block is aligned on a CPU word boundary and is at least 20 bytes long.
(8) A hardware memory block is taken from the pool. The block is aligned on a 32-byte boundary and is padded to meet the padding alignment requirement of the memory segment. It is then safe to be read/written via a DMA engine.
(9) (10) Memory blocks are freed back to their respective dynamic memory pools. They are available for the next allocation. Note that the data that was present on these memory blocks WILL BE altered when freed.