Thread demo (PWM example)

This commit is contained in:
Derek Jamison 2023-10-06 13:46:11 -07:00
parent 7595c42fdc
commit 4eeafe3357
4 changed files with 124 additions and 0 deletions

View File

@ -0,0 +1,13 @@
# WIP thread demo
The Flipper Zero supports the concepts of threads. The idea is that while your main application is running some code, another thread can be running different code in the same application process, at the same time. The reality is likely that the processor will end up switching back and forth between the various threads. Threads can get paused momentarily, while processing instructions from another thread. A thread can set its priority by calling `furi_thread_set_current_priority(FuriThreadPriorityHigh)`, passing the new priority of the thread. Warning -- shanging the priority could cause other threads to not get time to execute.
In this demonstration, we use a thread to blink pin A7 on/off quickly, effectively creating a PWM signal and varying the brightness of an LED connected to pin A7. Our main thread sets information, such as the frequency and duty cycle (percentage of time to have the pin on.)
A thread is allocated using a command like `FuriThread* thread = furi_thread_alloc_ex("MyThread", 2048, my_thread_callback, context);`. The first parameter is the friendly name for the thread. The second parameter is the stack size. The third parameter is the method that the thread should run, with a signature like `int32_t my_thread_callback(void* context)`. The fourth parameter is the context to pass to the method.
After you have allocated the thread, you can call `furi_thread_flags_set` passing the `furi_thread_get_id` of the Thread and the bits you want to set. When the main application is done, it should signal to the thread that it should terminate, and then the main application should do a `furi_thread_join` to wait for the thread to complete.
In the thread callback, it can get the flags, clear the flags, and wait on flags. Typically you want your thread to check periodically to see if it should be exiting (for example, because the main application is exiting.)
NOTE: This current demo has a flicker when the PWM is a low percentage, because while the code in the thread checking to see if it should exit is running, the LED is not blinking. This is just a demo app of how to use a Thread. If you really want to do PWM, you should look into the `furi_hal_pwm_start` API, or the underlying LL*TIM* functions in `lib\stm32wb_hal\Inc\stm32wbxx_ll_tim.h`.

View File

@ -0,0 +1,10 @@
App(
appid="wip_thread_demo",
name="WIP Thread Demo",
apptype=FlipperAppType.EXTERNAL,
entry_point="thread_demo_app",
requires=["gui", "subghz"],
stack_size=2 * 1024,
fap_icon="demo.png",
fap_category="Misc",
)

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -0,0 +1,101 @@
#include <furi.h>
#include <furi_hal.h>
#define TAG "WIP_Thread_Demo"
const GpioPin* pin = &gpio_ext_pa7;
typedef struct {
uint8_t duty_cycle;
uint32_t freq;
} MyThreadContext;
typedef enum {
MyThreadFlagUpdated = 1 << 0,
MyThreadFlagFoo = 1 << 1,
MyThreadFlagExiting = 1 << 2,
MyThreadFlagAll = (1 << 3) - 1,
} MyThreadFlags;
int32_t my_thread_callback(void* context) {
MyThreadContext* mtc = (MyThreadContext*)context;
furi_thread_flags_clear(MyThreadFlagAll);
uint32_t cycle = 1000000 / mtc->freq;
uint32_t cycle_on = cycle * mtc->duty_cycle / 100;
uint32_t cycle_off = cycle - cycle_on;
uint32_t num_cycles = mtc->freq;
MyThreadFlags flags;
while(((flags = furi_thread_flags_get()) & MyThreadFlagExiting) != MyThreadFlagExiting) {
// Do some work (for ~1 second) then check to see if thead should exit...
// NOTE: This causes some "flicker" because the LED is not changing states
// while we do the above and below checks.
if(flags & MyThreadFlagUpdated) {
cycle = 1000000 / mtc->freq;
cycle_on = cycle * mtc->duty_cycle / 100;
cycle_off = cycle - cycle_on;
num_cycles = mtc->freq;
furi_thread_flags_clear(MyThreadFlagUpdated);
}
for(uint32_t i = 0; i < num_cycles * 3; i++) {
// Turn on
furi_hal_gpio_write(pin, true);
furi_delay_us(cycle_on);
// Turn off
furi_hal_gpio_write(pin, false);
furi_delay_us(cycle_off);
}
};
FURI_LOG_D(TAG, "Exiting the thread");
return 0;
}
int thread_demo_app(char* p) {
UNUSED(p);
furi_hal_gpio_init_simple(pin, GpioModeOutputPushPull);
MyThreadContext* context = (MyThreadContext*)malloc(sizeof(MyThreadContext));
context->duty_cycle = 100;
context->freq = 30000;
FuriThread* thread = furi_thread_alloc_ex("MyThread", 2048, my_thread_callback, context);
furi_thread_start(thread);
// This would normally do some work. We sleep here just as a demo..
FURI_LOG_D(TAG, "Running the main program");
furi_delay_ms(3000);
context->duty_cycle = 50;
furi_thread_flags_set(furi_thread_get_id(thread), MyThreadFlagUpdated);
FURI_LOG_D(TAG, "Set duty cycle to %d.", context->duty_cycle);
furi_delay_ms(3000);
context->duty_cycle = 10;
furi_thread_flags_set(furi_thread_get_id(thread), MyThreadFlagUpdated);
FURI_LOG_D(TAG, "Set duty cycle to %d.", context->duty_cycle);
furi_delay_ms(3000);
context->duty_cycle = 2;
furi_thread_flags_set(furi_thread_get_id(thread), MyThreadFlagUpdated);
FURI_LOG_D(TAG, "Set duty cycle to %d.", context->duty_cycle);
furi_delay_ms(3000);
FURI_LOG_D(TAG, "Exiting the main program");
// Our app is done. Release any resources...
// Tell the thread we are exiting.
furi_thread_flags_set(furi_thread_get_id(thread), MyThreadFlagExiting);
// Wait for thread to exit.
furi_thread_join(thread);
furi_hal_gpio_init_simple(pin, GpioModeAnalog);
return 0;
}