From 4eeafe33572672cde723069af78afb25db39587b Mon Sep 17 00:00:00 2001 From: Derek Jamison Date: Fri, 6 Oct 2023 13:46:11 -0700 Subject: [PATCH] Thread demo (PWM example) --- misc/WIP_thread_demo/README.md | 13 ++++ misc/WIP_thread_demo/application.fam | 10 +++ misc/WIP_thread_demo/demo.png | Bin 0 -> 1895 bytes misc/WIP_thread_demo/wip_thread_demo.c | 101 +++++++++++++++++++++++++ 4 files changed, 124 insertions(+) create mode 100644 misc/WIP_thread_demo/README.md create mode 100644 misc/WIP_thread_demo/application.fam create mode 100644 misc/WIP_thread_demo/demo.png create mode 100644 misc/WIP_thread_demo/wip_thread_demo.c diff --git a/misc/WIP_thread_demo/README.md b/misc/WIP_thread_demo/README.md new file mode 100644 index 0000000..ecf7938 --- /dev/null +++ b/misc/WIP_thread_demo/README.md @@ -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`. diff --git a/misc/WIP_thread_demo/application.fam b/misc/WIP_thread_demo/application.fam new file mode 100644 index 0000000..865fda6 --- /dev/null +++ b/misc/WIP_thread_demo/application.fam @@ -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", +) diff --git a/misc/WIP_thread_demo/demo.png b/misc/WIP_thread_demo/demo.png new file mode 100644 index 0000000000000000000000000000000000000000..75f2c99fcf380b8b405319ee0ae95981200375c3 GIT binary patch literal 1895 zcma)64Qvx-7`~2evUQUv2uR4d9RH5?uD5k#=Sm%I*{Zuy_Q&SW*sb?7$iBqIotYArAq!++&EdF&YY`^9bJMg&o<k7s|&?se^+^U2-1 zQ_D`4SEgG&VCUA=G{4(-?4#C$0)M}==g+Gvi|%)-%Ubt!Wb8S<>HMVrJD)FWU*A1< zfBuh|KkeAIf9rB*aEEP^@$3(&Po7`)+sj9OzxLtc8L7`qJ^bbLJ-a3aW(S(n6E5DM znT;LmW(GFCxl*p*e&_Jcv+h*~=ezq_&NR%Jy6H+sU~~Vt4FlEs;LX5J@rraA9DbO# z_h}z#%+Pev<>`sc*LQ=&xyh=={)~Cw=i_7g&uuW?IDYMJ$vo@6=gNa?yD^TOaO){= z<-yr!cAFQ6E)*a7W8mn=i?{ zC}}7vwKvq+b6Cu{AT>GHPeBDYkQvnPc6lVqZ@|LB6ueeo97Dqtxz2!9&^FY>ivZPY z^;!ah=_r~jvJR@)yeOIu?+lnzmIVsOeLkPom!ajwT3nZtlYyT}PL$9qskmZ{;rvH^n*!q$MF8y5FS(FVX);7SD7X$i$_m?}7VnU|cr zFk~i%KNOBhk~zme=qr&JD(u+8oJiCEPIbG7Y)i6b1uQt)zZlDd!cu9y0PtcU z@m|pmEGvLVo{phP11J;k;>8j;1;BvmqL_{I16PHaa|4eA+X@53$Vla5k)g&!#Au#{ zS{WERjKkd$T`dMoD$ZYsS}mq5T~1b(hSb7`DxIRNZl)F#0#B{%j684{UCOaBpi+p- ztDJb5hl8b)QHhG-fC0-U@j>azzvJ*MC>l;G%gBtGk)dIdfODoJH6&3==&4MS(&^KQ z9EyOYhsiw4IqLt58lG+xesW +#include + +#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; +} \ No newline at end of file