- Last edited one month ago ago
STM32WB Bluetooth® LE – Heart Rate Sensor project migration to Azure RTOS ThreadX OS
Contents
- 1 Overview
- 2 Azure RTOS ThreadX OS characteristics
- 3 Required adaptations to integrate ThreadX into BLE Heart Rate application
- 3.1 STM32WB platform specific integration
- 3.2 BLE Heart Rate application specific integration
- 4 References
1 Overview
This article presents the steps followed to port the BLE_HeartRate from ST Sequencer scheduler to ThreadX Real Time OS.
A simple but complete overview about ThreadX features is given, pointing to the official Microsoft documentation when a deeper knowledge is required.
A focus is made on how ThreadX was integrated inside STM32WB, and after and how the BLE Heart Rate application can be modified to replace Sequencer API with ThreadX API.
Note that ThreadX integration on all STM32 family follows a common framework. This approach has been properly highlighted pointing to general STM32 ThreadX internal documentation when required to clarify specific aspects.
2 Azure RTOS ThreadX OS characteristics
Below is a list of ThreadX key features:
- Picokernel, preemption-threshold, event-chaining unique features
- Execution profiling and performance metrics
- Totally available in source code (ANSI C and assembler)
- Safety certifications (TÜV, MISRA, UL)
- Integrated with other Azure RTOS components:
- USBX (USB stack)
- NETX (IPV4 stack + applications layers up to cloud connection) and NETXDUO (IPV6 stack + application layers up to cloud connection)
- FILEX (FAT compatible file-system)
- LEVELX (NAND and NOR flash wear leveling facilities)
- GUIX (embedded GUI)
- TRACEX (graphical host tool for real-time system events analysis)
Clearly one of the main advantages of ThreadX is the ability to integrate with other software components coming in the same Microsoft Azure RTOS ecosystem required to build a complete embedded application.
Fully detailed description of ThreadX OS features and benefits can be found at Microsoft AzureRTOS ThreadX documentation page [1].
Fully detailed description of all Azure RTOS components can be found at Microsoft Azure RTOS Ecosystem Overview [2].
2.1 ThreadX code organization
Main folders descriptions
- cmake: original build system (not mandatory)
- common: mcu architecture independent source code
- common_modules: module feature [3]
- common_smp: Symmetric Multi Processing feature [4]
- docs: Just a dependency tree of Azure RTOS components
- ports: mcu architecture dependent (M3, M4, M33,...)
- ports_module: architecture dependent module code
- ports_smp: architecture dependent smp code
- samples: Microsoft example code (demo_threadx.c)
- utility:
- benchmarks: Thread-Metric test suite
- execution_profile_kit: thread execution time tracker
- low_power: Low power management files
- rtos_compatibility_layers: adaptation layers (FreeRTOS™, Posix, OSEK)
- benchmarks: Thread-Metric test suite
Note that this layout represents the structure coming from the Microsoft ThreadX sources directory.
Some components not strictly required could be missing in this STM32WB example as features are simply not used.
For instance the "ports" folder contains only the code linked to Cortex®- M4 architecture and relevant to the selected IDE. And again "ports_module" is missing as ThreadX module feature is not used all.
2.2 ThreadX way of working
A typical ThreadX application could be similar to this one (courtesy of Microsoft):
1 #include "tx_api.h"
2 unsigned long my_thread_counter = 0;
3 TX_THREAD my_thread;
4 main( )
5 {
6 /* Enter the ThreadX kernel. */
7 tx_kernel_enter( );
8 }
9 void tx_application_define(void *first_unused_memory)
10 {
11 /* Create my_thread! */
12 tx_thread_create(&my_thread, "My Thread",
13 my_thread_entry, 0x1234, first_unused_memory, 1024,
14 3, 3, TX_NO_TIME_SLICE, TX_AUTO_START);
15 }
16 void my_thread_entry(ULONG thread_input)
17 {
18 /* Enter into a forever loop. */
19 while(1)
20 {
21 /* Increment thread counter. */
22 my_thread_counter++;
23 /* Sleep for 1 tick. */
24 tx_thread_sleep(1);
25 }
26 }
Consider that this is just the simplest skeleton to arrange a ThreadX based application. A more structured code organization could be present in this STM32WB example to cover specific needs.
tx_kernel_enter
This API is used to give the control to the OS, tx_application_define is called and, the OS scheduler is in charge to select the first thread (ready) to run.
tx_application_define
Here we have the creation of ThreadX resources (threads, semaphores, mutexes, events, queues, etc.). At least one thread must be created, other threads and resources can be created later.
Pay attention to how this function is called. Interrupts are disabled before the call to this function (inside _tx_initialize_low_level) and reenabled just after (inside _tx_thread_schedule). So, do not put any code relying on interrupt management inside tx_application_define.
2.3 ThreadX key files
tx_api.h: C header file containing all public definitions, data structures, and service prototypes usable in the application.
For a list of all available API see the ThreadX API [5]
tx_port.h: C header file containing all development-tool and target-specific data definitions and structures.
For more details see the target integration details [6]
tx_user.h: Several configuration options can be set when building the ThreadX library and the application. These configurations can be used to enable smallest code size, fastest execution, performance evaluation, error checking, quality coding rules and generally speaking extra features. These options can be defined in the application source, on the command line or inside tx_user.h (in this latter case TX_INCLUDE_USER_DEFINE_FILE has to be defined). A "tx_user_sample.h" template is given by Microsoft for reference.
In BLE_HeartRate_ThreadX example, this file is located inside Core/Inc folder.
In this file we have also all the mapping, between ThreadX low-power macros and STM32WB specific functions to manage low-power features. According to the different development stages, specific options can be activated to enable/disable error or performance checking in ThreadX API, for instance:
- TX_DISABLE_ERROR_CHECKING
- TX_ENABLE_MULTI_ERROR_CHECKING
- TX_ENABLE_STACK_CHECKING
- All ENABLE_PERFORMANCE_INFO options (available for almost all ThreadX object)
Or to enable a MISRA compliancy in ThreadX
- TX_ENABLE_MISRA
For more information see the ThreadX configuration options[7]
tx_initialize_low_level.S: Low-level processor initialization, including setting up interrupt-vectors, setting up a periodic timer interrupt source, saving the system stack pointer for use in ISR processing later and finding the first available RAM memory address for tx_application_define
Description of initialization process (between processor reset and the entry point of the thread scheduling loop) and the ThreadX way of working is fully detailed in the ThreadX way of working [8]. Refer to the following chapter before porting your application to ThreadX.
2.4 Idle Scheduler behavior
Unlike FreeRTOS ThreadX has no a default idle thread to run when no threads are ready to run. We have this simple loop:
__tx_ts_wait:
CPSID i // Disable interrupts
LDR r1, [r2] // Pickup the next thread to execute pointer
STR r1, [r0] // Store it in the current pointer
CBNZ r1, __tx_ts_ready // If non-NULL, a new thread is ready!
#ifdef TX_LOW_POWER
PUSH {r0-r3}
BL tx_low_power_enter // Possibly enter low power mode
POP {r0-r3}
#endif
#ifdef TX_ENABLE_WFI
DSB // Ensure no outstanding memory transactions
WFI // Wait for interrupt
ISB // Ensure pipeline is flushed
#endif
#ifdef TX_LOW_POWER
PUSH {r0-r3}
BL tx_low_power_exit // Exit low power mode
POP {r0-r3}
#endif
CPSIE i // Enable interrupts
B __tx_ts_wait // Loop to continue waiting
ThreadX runs always the PendSV_Handler, which is fixed at the lowest interrupt priority (15). The MCU does not service any other interrupts of the lowest priority while no threads are ready to run.
To solve this limitation, we must avoid assigning the same lowest priority (15) to an interrupt in charge of wake up for the system. Alternatively we can manually create an idle thread that is always ready to run. This idle thread is then in charge to enter a lower-power mode. Take care with this approach the SysTick cannot wake the MCU.
3 Required adaptations to integrate ThreadX into BLE Heart Rate application
We can identify two different types of modifications:
STM32WB platform specific (how Threadx OS uses the underlying hardware resources):
- microcontroller architecture
- Development environment (IDE)
- Memory allocation strategy used by the OS
- Low power
BLE Heart Rate Application specific (how application uses ThreadX OS API):
- Usage of ThreadX API according to the specific application logic
3.1 STM32WB platform specific integration
ThreadX is highly integrated to the hardware platform through a direct usage of assembler code so we should pay attention for the integration phase.
For a more generic description on ThreadX porting on STM32 family, see this page: ThreadX on STM32 family
3.1.1 Initialization code Integration
The most important role is played by tx_initialize_low_level.S placed in "Application/User/Core" of each ThreadX project.
In this file we can inform the OS at which clock frequency our microcontroller is running (through SYSTEM_CLOCK variable) and at which frequency a system tick interrupt is raised (through SYSTICK_CYCLES variables).
Note that "Systick timer" (Cortex core IP) is used exclusively by the OS and "HW Timer 17" peripheral is used by the ST HAL drivers.
The memory allocation strategy can be static or dynamic.
The dynamic approach can be selected using the USE_DYNAMIC_MEMORY_ALLOCATION macro and properly setting the heap memory.
By default a static approach is used through a static array declared at application level.
Inside this file we have also all the low-level initialization code as required by ThreadX.
This file is split into three sections to cover the three supported IDEs according to specific macro:
- __clang__ for Keil® (AC6)
- __IAR_SYSTEMS_ASM__ for IAR
- (__GNUC__) && !defined(__clang__) for CubeIDE
3.1.2 Scheduler integration
ThreadX scheduler is a pure assembler code, and so is strictly dependent on MCU architecture (M3/M4/M33/...) and compiler (IAR/Keil/GCC/...)
Therefore, scheduler integration refers to a correct selection of assembly files according to architecture/toolchain.
These files can be found in the "Middlewares/AzureRTOS/threadx/ports/<mcu_arc>/<compiler>" directory of each ThreadX project.
In the STM32WB specific case <mcu_arch> is cortex_m4 and <compiler> is "iar" for IAR, "gnu" for CubeIDE or "keil" for Keil.
3.1.3 Low power integration
The low-power mechanism is implemented through a mix of assembler code already available in the scheduler and C helper functions (tx_low_power.c and tx_low_power.h files). Theses are already defined by ThreadX in the utility sections and do not need to be modified.
In our example these files can be found in "Middlewares/AzureRTOS/threadx/utility/low_power".
In ThreadX we have no idle task but an assembler loop (__tx_ts_wait) defined in the scheduler that can invoke low power routines if no task is ready to run.
The low-power framework is fully customizable using specific macro and hook functions:
Threadx LP Macro | Usage | Implementation used in BLE_HeartRate_ThreadX |
---|---|---|
TX_LOW_POWER | To enable low power support | DEFINED |
TX_ENABLE_WFI | To enable WFI inside ThreadX scheduler | NOT DEFINED |
TX_LOW_POWER_TICKLESS | To enable tickless mode in LP | NOT DEFINED |
TX_LOW_POWER_USER_ENTER | Function to enter in LP | UTIL_LPM_EnterLowPower |
TX_LOW_POWER_USER_EXIT | Function to exit from LP | NOT DEFINED |
TX_LOW_POWER_TIMER_SETUP | Function to start a timer before entering in LP | APP_BLE_ThreadX_Low_Power_Setup |
TX_LOW_POWER_USER_TIMER_ADJUST | Function to calculate time spent in LP | APP_BLE_Threadx_Low_Power_Adjust_Ticks |
3.2 BLE Heart Rate application specific integration
Inside "Application/User/Core" of your project these files should be present
- stm32wbxx_hal_timebase_tim.c, as SysTick is to be used by the Threadx and not anymore by STM32 HAL. By design TIM17 was chosen for HAL timebase.
- tx_initialize_low_level.S, for low-level initialization
- tx_user.h, for ThreadX OS configuration
ThreadX component must be present in "Middlewares/AzureRTOS" section:
3.2.1 ST Sequencer VS ThreadX
The sequencer executes registered functions one by one. It is able to:
- Support up to 32 functions.
- Request a function to be executed.
- Pause/Resume the execution of a function.
- Wait for a specific event (might be not blocking).
- Priority on functions.
The sequencer is an optimized packaging of a while loop bare metal classic implementation. It does not intend to compete versus standard OSs but versus standard bare metal implementations. It allows avoiding race conditions, which are most of the time faced in bare metal implementation. This is especially when low-power mode is implemented.
For a detailed description of ST Sequencer, refer to:
Building wireless applications with STM32WB Series microcontrollers (AN5289)[9]
3.2.2 List of Sequencer API used in BLE Heart Rate application
void UTIL_SEQ_Run (UTIL_SEQ_bm_t mask_bm)
Requests the sequencer to execute functions that are pending and enabled in the mask mask_bm. Internal (not exposed API) function UTIL_SEQ_Idle is called when no activity is scheduled for execution (typically used to enter in low power)
void UTIL_SEQ_RegTask(UTIL_SEQ_bm_t task_id_bm, uint32_t flags, void (*task)( void ))
Registers a function (task) associated with a signal (task_id_bm) in the sequencer. The task_id_bm must have a single bit set.
void UTIL_SEQ_SetTask( UTIL_SEQ_bm_t task_id_bm,* UTIL_SEQ_PauseTask)
Requests the function associated with the task_id_bm to be executed. The task_prio is evaluated by the sequencer only when a function has finished. If several functions are pending at any one time, the one with the highest priority (0) is executed.
void UTIL_SEQ_PauseTask(UTIL_SEQ_bm_t task_id_bm )
Disables the sequencer to execute the function associated with task_id_bm.
void UTIL_SEQ_ResumeTask(UTIL_SEQ_bm_t task_id_bm )
Enables the sequencer to execute the function associated with task_id_bm.
void UTIL_SEQ_SetEvt(UTIL_SEQ_bm_t evt_id_bm )
Notifies the sequencer that the event evt_id_bm occurred (the event must have been first requested).
void UTIL_SEQ_WaitEvt(UTIL_SEQ_bm_t evt_id_bm )
Requests the sequencer to wait for a specific event evt_id_bm and does not return until the event is set with UTIL_SEQ_SetEvt().
3.2.3 ST Scheduler ThreadX API replacement strategy
In this application we have mapped the Sequencer API with ThreadX API according this table:
Sequencer | ThreadX | Description |
---|---|---|
UTIL_SEQ_RegTask | tx_thread_create | thread creation |
UTIL_SEQ_SetTask | tx_semaphore_put | unlocking a thread waiting for a semaphore |
UTIL_SEQ_SetEvt | tx_semaphore_put | unlocking a thread waiting for a semaphore |
UTIL_SEQ_WaitEvt | tx_semaphore_get | locking a thread waiting for a semaphore |
UTIL_SEQ_PauseTask | tx_mutex_get | locking a thread waiting for a mutex |
UTIL_SEQ_ResumeTask | tx_mutex_put | unlocking a thread waiting for a mutex |
UTIL_SEQ_Run | tx_kernel_enter | Launching the scheduler |
UTIL_SEQ_Idle (Internal) | __tx_ts_wait (Internal) | Internal function automatically called by the scheduler when no task ready to be scheduled |
3.2.4 ThreadX resources declaration
tx_application_define is the first function called by tx_kernel_enter.
ThreadX scheduler needs at least one thread that must be declared and ready to be executed.
In this application, tx_application_define (app_entry.c file) declares the thread for managing the system host communication interface.
three other threads are declared to manage standard hci commands, advertising policy update and the heart rate measurement.
Inside tx_application_define we find also the byte pool creation realized through a static allocation (a_memory_area array).
Note that MX_ThreadX_Init (main.c file) is just a wrapper for tx_kernel_enter.
3.2.5 Principal modifications description
The principal modifications and rationale are listed below.
3.2.5.1 Core\Src\main.c
A wrapper (MX_ThreadX_Init) to launch ThreadX scheduler has been used:
141 ...
142 /* Init code for STM32_WPAN */
143 /* Containing all application initialization could be run before kernel launching */
144 MX_APPE_Init();
145 /* Launching ThreadX kernel */
146 MX_ThreadX_Init();
147 /* Infinite loop */
148 while(1)
149 {
150 }
151 ...
Timer17 has to be used for HAL timebase:
492
493 /**
494 * @brief Period elapsed callback in non blocking mode
495 * @note This function is called when TIM17 interrupt took place, inside
496 * HAL_TIM_IRQHandler(). It makes a direct call to HAL_IncTick() to increment
497 * a global variable "uwTick" used as application time base.
498 * @param htim : TIM handle
499 * @retval None
500 */
501 void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
502 {
503 /* USER CODE BEGIN Callback 0 */
504
505 /* USER CODE END Callback 0 */
506 if (htim->Instance == TIM17) {
507 HAL_IncTick();
508 }
509 /* USER CODE BEGIN Callback 1 */
510
511 /* USER CODE END Callback 1 */
512 }
3.2.5.2 Core\Src\app_entry.c
ThreadX API should be used instead of Sequencer API:
27 ...
28 #include "tx_api.h"
29 ...
ThreadX resources, memory declaration and system hci process function prototype:
64 ...
65 /* USER CODE BEGIN PV */
66 CHAR* p_pointer = TX_NULL;
67 static TX_MUTEX mutex_shci;
68 static TX_SEMAPHORE semaphore_shci;
69 static TX_SEMAPHORE semaphore_shci_tl_notify_async_evt;
70 static TX_THREAD thread_ShciUserEvtProcess;
71 static TX_BYTE_POOL byte_pool_ble;
72 static UCHAR a_memory_area[DEMO_BYTE_POOL_SIZE];
73 /* USER CODE END PV */
74
75 /* Private functions prototypes-----------------------------------------------*/
76 static void thread_ShciUserEvtProcess_entry(ULONG thread_input);
77 ...
A simple wrapper to start the scheduler and tx_application_define declaring the thread in charge of system hci commands management:
154 ...
155 /**
156 * @brief MX_ThreadX_Init
157 * @param None
158 * @retval None
159 */
160 void MX_ThreadX_Init(void)
161 {
162 /* USER CODE BEGIN Before_Kernel_Start */
163
164 /* USER CODE END Before_Kernel_Start */
165
166 tx_kernel_enter();
167
168 /* USER CODE BEGIN Kernel_Start_Error */
169
170 /* USER CODE END Kernel_Start_Error */
171 }
172 void tx_application_define(void* first_unused_memory)
173 {
174 UNUSED(first_unused_memory);
175
176 /* Here we should declare all the initial ThreadX resources
177 * We should have at least one thread to be launched from the scheduler */
178
179 /* Create a byte memory pool from which to allocate the thread stacks,
180 * using static memory coming from a_memory_area array */
181 tx_byte_pool_create(&byte_pool_ble, "byte pool 0", a_memory_area, DEMO_BYTE_POOL_SIZE);
182
183 tx_mutex_create(&mutex_shci, "mutex_shci", TX_NO_INHERIT);
184 tx_semaphore_create(&semaphore_shci, "semaphore_shci", 0);
185 tx_semaphore_create(&semaphore_shci_tl_notify_async_evt,
186 "semaphore_shci_tl_notify_async_evt",
187 0);
188
189 tx_byte_allocate(&byte_pool_ble, (VOID**) &p_pointer, DEMO_STACK_SIZE_LARGE, TX_NO_WAIT);
190 tx_thread_create(&thread_ShciUserEvtProcess,
191 "thread_ShciUserEvtProcess",
192 thread_ShciUserEvtProcess_entry,
193 0,
194 p_pointer,
195 DEMO_STACK_SIZE_LARGE,
196 16,
197 16,
198 TX_NO_TIME_SLICE,
199 TX_AUTO_START);
200 }
201 ...
Sytem hci commands have to be processed one by one. A mutex is used to mage this pacing mechanism:
399 ...
400 static void APPE_SysStatusNot( SHCI_TL_CmdStatus_t status )
401 {
402 switch (status) {
403 case SHCI_TL_CmdBusy:
404 tx_mutex_get(&mutex_shci, TX_WAIT_FOREVER);
405 break;
406
407 case SHCI_TL_CmdAvailable:
408 tx_mutex_put(&mutex_shci);
409 break;
410
411 default:
412 break;
413 }
414 return;
415 }
416 ...
When a new shci event is notified we can call shci_user_evt_proc. Same semaphore sync mechanism is used for shci commands:
645
646 ...
647 static void thread_ShciUserEvtProcess_entry(ULONG thread_input)
648 {
649 UNUSED(thread_input);
650
651 while (1) {
652 tx_semaphore_get(&semaphore_shci_tl_notify_async_evt, TX_WAIT_FOREVER);
653 shci_user_evt_proc();
654 }
655 }
656
657 void shci_notify_asynch_evt(void* pdata)
658 {
659 UNUSED(pdata);
660 tx_semaphore_put(&semaphore_shci_tl_notify_async_evt);
661 return;
662 }
663
664 void shci_cmd_resp_release(uint32_t flag)
665 {
666 UNUSED(flag);
667 tx_semaphore_put(&semaphore_shci);
668 return;
669 }
670
671 void shci_cmd_resp_wait(uint32_t timeout)
672 {
673 UNUSED(timeout);
674 tx_semaphore_get(&semaphore_shci, TX_WAIT_FOREVER);
675 return;
676 }
677 ...
3.2.5.3 STM32_WPAN\App\app_ble.c
ThreadX API and limits.h (used for low power):
31 ...
32 #include "tx_api.h"
33 ...
34 #include "limits.h"
35 ...
Low power timer structure used by ThreadX:
171 ...
172 #if (CFG_LPM_SUPPORTED == 1)
173 typedef struct
174 {
175 uint32_t LpTXTimeLeftOnEntry;
176 uint8_t LpTXTimerThreadx_Id;
177 } LpTXTimerContext_t;
178 #endif
179 ...
Low-power timer definiton
250 ...
251 #if (CFG_LPM_SUPPORTED == 1)
252 static LpTXTimerContext_t LpTXTimerContext;
253 #endif
254 ...
ThreadX resources definition for hci management
Prototype of functions used in the threads in charge of advertisement process and hci process:
255 ...
256 static TX_MUTEX mtx_hci;
257 static TX_SEMAPHORE sem_hci;
258 static TX_THREAD thread_HciUserEvtProcess;
259 static TX_SEMAPHORE sem_HciUserEvtProcessSignal;
260 static TX_THREAD thread_AdvUpdateProcess;
261 static TX_SEMAPHORE sem_AdvUpdateProcessSignal;
262
263
264 /* Private function prototypes -----------------------------------------------*/
265 static void thread_AdvUpdateProcess_entry(ULONG thread_input);
266 static void thread_HciUserEvtProcess_entry(ULONG thread_input);
267 ...
Prototype of low-power timer callback:
277 /* USER CODE BEGIN PFP */
278 #if (CFG_LPM_SUPPORTED == 1)
279 static void APP_BLE_Threadx_LpTimerCb(void);
280 #endif
281 /* USER CODE END PFP */
282 ...
APP_BLE_Init requires the pointer to byte pool memory for threads creation:
289 ...
290 /* Functions Definition ------------------------------------------------------*/
291 void APP_BLE_Init(TX_BYTE_POOL* p_byte_pool)
292 {
293 SHCI_CmdStatus_t status;
294 /* USER CODE BEGIN APP_BLE_Init_1 */
295 ...
ThreadX resources definition and thread creation for hci event processing
341 ...
342 CHAR* p_pointer;
343 tx_mutex_create(&mtx_hci, "mtx_hci", TX_NO_INHERIT);
344 tx_semaphore_create(&sem_hci, "sem_hci", 0);
345 tx_semaphore_create(&sem_HciUserEvtProcessSignal, "sem_HciUserEvtProcessSignal", 0);
346 tx_byte_allocate(p_byte_pool, (VOID**) &p_pointer, DEMO_STACK_SIZE_LARGE, TX_NO_WAIT);
347 tx_thread_create(&thread_HciUserEvtProcess,
348 "thread_HciUserEvtProcess",
349 thread_HciUserEvtProcess_entry,
350 0,
351 p_pointer,
352 DEMO_STACK_SIZE_LARGE,
353 16,
354 16,
355 TX_NO_TIME_SLICE,
356 TX_AUTO_START);
357 ...
ThreadX resources definition and thread creation for advertisement management:
390 ...
391 tx_semaphore_create(&sem_AdvUpdateProcessSignal, "sem_AdvUpdateProcessSignal", 0);
392 tx_byte_allocate(p_byte_pool, (VOID**) &p_pointer, DEMO_STACK_SIZE_REDUCED, TX_NO_WAIT);
393 tx_thread_create(&thread_AdvUpdateProcess,
394 "thread_AdvUpdateProcess",
395 thread_AdvUpdateProcess_entry,
396 0,
397 p_pointer,
398 DEMO_STACK_SIZE_REDUCED,
399 16,
400 16,
401 TX_NO_TIME_SLICE,
402 TX_AUTO_START);
403 ...
HRSAPP_Init requires the pointer to byte pool memory for app specific thread creation.
A timer for ThreadX low power management is created.
415 ...
416 /**
417 * Initialize HRS Application
418 */
419 HRSAPP_Init(p_byte_pool);
420
421 /* USER CODE BEGIN APP_BLE_Init_3 */
422
423 /* USER CODE END APP_BLE_Init_3 */
424
425 /**
426 * Create timer to handle the connection state machine
427 */
428 HW_TS_Create(CFG_TIM_PROC_ID_ISR, &(BleApplicationContext.Advertising_mgr_timer_Id), hw_ts_SingleShot, Adv_Mgr);
429
430 #if (CFG_LPM_SUPPORTED == 1)
431 /**
432 * Create low power timer to handle the user ThreadX timers
433 */
434 HW_TS_Create(CFG_TIM_PROC_ID_ISR, &(LpTXTimerContext.LpTXTimerThreadx_Id), hw_ts_SingleShot, APP_BLE_Threadx_LpTimerCb);
435 #endif
436 ...
Functions to calculate how much time was spent in low power and to adjust the SysTick have been added:
824 ...
825 #if (CFG_LPM_SUPPORTED == 1)
826
827 static void APP_BLE_Threadx_LpTimerCb( void )
828 {
829 /**
830 * Nothing to be done
831 */
832 return;
833 }
834
835 /**
836 * @brief Request to start a low power timer
837 *
838 * @param tx_low_power_next_expiration: Number of ThreadX ticks
839 * @retval None
840 */
841 void APP_BLE_ThreadX_Low_Power_Setup(ULONG tx_low_power_next_expiration)
842 {
843 uint64_t time;
844 /* Timer was already created, here we need to start it */
845 /* By default, see tx_initialize_low_level.S, each tick is 10 ms */
846 /* This function should be very similar to LpTimerStart used in freertos_port.c */
847 /* Converts the number of FreeRTOS ticks into hw timer tick */
848 if (tx_low_power_next_expiration > (ULLONG_MAX / 1e12)) /* Prevent overflow in else statement */
849 {
850 time = 0xFFFF0000; /* Maximum value equal to 24 days */
851 }
852 else
853 {
854 /* The result always fits in uint32_t and is always less than 0xFFFF0000 */
855 time = tx_low_power_next_expiration * 1000000000000ULL;
856 time = (uint64_t)( time / ( CFG_TS_TICK_VAL_PS * TX_TIMER_TICK_PER_SECOND ));
857 }
858
859 HW_TS_Start(LpTXTimerContext.LpTXTimerThreadx_Id, (uint32_t)time);
860
861 /**
862 * There might be other timers already running in the timer server that may elapse
863 * before this one.
864 * Store how long before the next event so that on wakeup, it will be possible to calculate
865 * how long the tick has been suppressed
866 */
867 LpTXTimerContext.LpTXTimeLeftOnEntry = HW_TS_RTC_ReadLeftTicksToCount( );
868
869 return;
870 }
871 /**
872 * @brief Read how long the tick has been suppressed
873 *
874 * @param None
875 * @retval The number of tick rate (FreeRTOS tick)
876 */
877 unsigned long APP_BLE_Threadx_Low_Power_Adjust_Ticks(void)
878 {
879 uint64_t val_ticks, time_ps;
880 uint32_t LpTimeLeftOnExit;
881
882 LpTimeLeftOnExit = HW_TS_RTC_ReadLeftTicksToCount();
883 /* This cannot overflow. Max result is ~ 1.6e13 */
884 time_ps = (uint64_t)((CFG_TS_TICK_VAL_PS) * (uint64_t)(LpTXTimerContext.LpTXTimeLeftOnEntry - LpTimeLeftOnExit));
885
886 /* time_ps can be less than 1 RTOS tick in following situations
887 * a) MCU didn't go to STOP2 due to wake-up unrelated to Timer Server or woke up from STOP2 very shortly after.
888 * Advancing RTOS clock by 1 ThreadX tick doesn't hurt in this case.
889 * b) APP_BLE_ThreadX_Low_Power_Setup(tx_low_power_next_expiration) was called with xExpectedIdleTime = 2 which is minimum value defined by configEXPECTED_IDLE_TIME_BEFORE_SLEEP.
890 * The xExpectedIdleTime is decremented by one RTOS tick to wake-up in advance.
891 * Ex: RTOS tick is 1ms, the timer Server wakes the MCU in ~977 us. RTOS clock should be advanced by 1 ms.
892 * */
893 if(time_ps <= (1e12 / TX_TIMER_TICK_PER_SECOND)) /* time_ps < RTOS tick */
894 {
895 val_ticks = 1;
896 }
897 else
898 {
899 /* Convert pS time into OS ticks */
900 val_ticks = time_ps * TX_TIMER_TICK_PER_SECOND; /* This cannot overflow. Max result is ~ 1.6e16 */
901 val_ticks = (uint64_t)(val_ticks / (1e12)); /* The result always fits in uint32_t */
902 }
903
904 /**
905 * The system may have been out from another reason than the timer
906 * Stop the timer after the elapsed time is calculated other wise, HW_TS_RTC_ReadLeftTicksToCount()
907 * may return 0xFFFF ( TIMER LIST EMPTY )
908 * It does not hurt stopping a timer that exists but is not running.
909 */
910 HW_TS_Stop(LpTXTimerContext.LpTXTimerThreadx_Id);
911
912 return (unsigned long)val_ticks;
913 }
914 #endif
915 ...
Adv_Mgr unlock the semaphore in charge of launch the thread to modify the advertisement policy.
hci events/commands/responses management through semaphore/mutex synchronization:
1402 ...
1403 static void Adv_Mgr(void)
1404 {
1405 /**
1406 * The code shall be executed in the background as an aci command may be sent
1407 * The background is the only place where the application can make sure a new aci command
1408 * is not sent if there is a pending one
1409 */
1410 tx_semaphore_put(&sem_AdvUpdateProcessSignal);
1411
1412 return;
1413 }
1414
1415 static void thread_AdvUpdateProcess_entry(ULONG argument)
1416 {
1417 UNUSED(argument);
1418
1419 for (;;){
1420 tx_semaphore_get(&sem_AdvUpdateProcessSignal, TX_WAIT_FOREVER);
1421 Adv_Update();
1422 }
1423 }
1424
1425 static void Adv_Update(void)
1426 {
1427 Adv_Request(APP_BLE_LP_ADV);
1428
1429 return;
1430 }
1431
1432 static void thread_HciUserEvtProcess_entry(ULONG argument)
1433 {
1434 UNUSED(argument);
1435
1436 for (;;) {
1437 tx_semaphore_get(&sem_HciUserEvtProcessSignal, TX_WAIT_FOREVER);
1438 hci_user_evt_proc();
1439 }
1440 }
1441
1442 /* USER CODE BEGIN FD_SPECIFIC_FUNCTIONS */
1443
1444 /* USER CODE END FD_SPECIFIC_FUNCTIONS */
1445 /*************************************************************
1446 *
1447 * WRAP FUNCTIONS
1448 *
1449 *************************************************************/
1450 void hci_notify_asynch_evt(void* pdata)
1451 {
1452 UNUSED(pdata);
1453 tx_semaphore_put(&sem_HciUserEvtProcessSignal);
1454
1455 return;
1456 }
1457
1458 void hci_cmd_resp_release(uint32_t flag)
1459 {
1460 UNUSED(flag);
1461 tx_semaphore_put(&sem_hci);
1462
1463 return;
1464 }
1465
1466 void hci_cmd_resp_wait(uint32_t timeout)
1467 {
1468 UNUSED(timeout);
1469 tx_semaphore_get(&sem_hci, TX_WAIT_FOREVER);
1470
1471 return;
1472 }
1473
1474 static void BLE_UserEvtRx(void *p_Payload)
1475 {
1476 SVCCTL_UserEvtFlowStatus_t svctl_return_status;
1477 tHCI_UserEvtRxParam *p_param;
1478
1479 p_param = (tHCI_UserEvtRxParam *)p_Payload;
1480
1481 svctl_return_status = SVCCTL_UserEvtRx((void *)&(p_param->pckt->evtserial));
1482 if (svctl_return_status != SVCCTL_UserEvtFlowDisable)
1483 {
1484 p_param->status = HCI_TL_UserEventFlow_Enable;
1485 }
1486 else
1487 {
1488 p_param->status = HCI_TL_UserEventFlow_Disable;
1489 }
1490
1491 return;
1492 }
1493
1494 static void BLE_StatusNot(HCI_TL_CmdStatus_t Status)
1495 {
1496 switch (Status)
1497 {
1498 case HCI_TL_CmdBusy:
1499 /**
1500 * All tasks that may send an aci/hci commands shall be listed here
1501 * This is to prevent a new command is sent while one is already pending
1502 */
1503 tx_mutex_get(&mtx_hci, TX_WAIT_FOREVER);
1504 /* USER CODE BEGIN HCI_TL_CmdBusy */
1505
1506 /* USER CODE END HCI_TL_CmdBusy */
1507 break;
1508
1509 case HCI_TL_CmdAvailable:
1510 /**
1511 * All tasks that may send an aci/hci commands shall be listed here
1512 * This is to prevent a new command is sent while one is already pending
1513 */
1514 tx_mutex_put(&mtx_hci);
1515 /* USER CODE BEGIN HCI_TL_CmdAvailable */
1516
1517 /* USER CODE END HCI_TL_CmdAvailable */
1518 break;
1519
1520 default:
1521 /* USER CODE BEGIN default */
1522
1523 /* USER CODE END default */
1524 break;
1525 }
1526
1527 return;
1528 }
1529 ...
3.2.5.4 STM32_WPAN\App\hrs_app.c
28 ...
29 #include "tx_api.h"
30 ...
69 ...
70 static TX_THREAD thread_HrsProcess;
71 static TX_SEMAPHORE sem_HrsProcessSignal;
72
73 /* USER CODE BEGIN PV */
74
75 /* USER CODE END PV */
76
77 /* Private functions prototypes-----------------------------------------------*/
78 static void HrMeas(void);
79 static void thread_HrsProcess_entry(ULONG argument);
80 static void HRSAPP_Measurement(void);
81 ...
143 ...
144 void HRSAPP_Init(TX_BYTE_POOL* p_byte_pool)
145 {
146 CHAR* p_pointer;
147 tx_semaphore_create(&sem_HrsProcessSignal, "sem_HrsProcessSignal", 0);
148 tx_byte_allocate(p_byte_pool, (VOID**) &p_pointer, DEMO_STACK_SIZE_REDUCED, TX_NO_WAIT);
149 tx_thread_create(&thread_HrsProcess,
150 "thread_HrsProcess",
151 thread_HrsProcess_entry,
152 0,
153 p_pointer,
154 DEMO_STACK_SIZE_REDUCED,
155 16,
156 16,
157 TX_NO_TIME_SLICE,
158 TX_AUTO_START);
159 /* USER CODE BEGIN HRSAPP_Init */
160 ...
200 ...
201 static void thread_HrsProcess_entry(ULONG argument)
202 {
203 UNUSED(argument);
204
205 for(;;)
206 {
207 tx_semaphore_get(&sem_HrsProcessSignal, TX_WAIT_FOREVER);
208 HRSAPP_Measurement( );
209 }
210 }
211 ...
233 ...
234 static void HrMeas( void )
235 {
236 /**
237 * Notifying a new measure is available
238 */
239 tx_semaphore_put(&sem_HrsProcessSignal);
240 /* USER CODE BEGIN HrMeas */
241
242 /* USER CODE END HrMeas */
243
244 return;
245 }
246 ...
4 References