From 942bbfaefe9ce638d8b09a5286645c1bdee722ee Mon Sep 17 00:00:00 2001 From: coreglitch Date: Tue, 13 Oct 2020 14:22:43 +0600 Subject: [PATCH] Core api concept (#144) * add input debounce code from old fw * exampl of input api * change input API to get/release * revert input API to read * pointer instead of instance * add input API description * add display API * rewrite display names * migrate to valuemanager * add LED API * add closing brakets * add sound api * fix led api * basic api * rename API pages * change pubsub implementation * move FURI AC -> flapp, add valuemutex example, add valuemanager implementation * pubsub usage example * user led example * update example * simplify input * add composed display * add SPI/GPIO and CC1101 bus * change cc1101 api * spi api and devices * spi api and devices * move SPI to page, add GPIO * not block pin open * backlight API and more * add minunit tests * fix logging * ignore unexisting time service on embedded targets * fix warning, issue with printf * Deprecate furi_open and furi_close (#167) Rename existing furi_open and furi_close to deprecated version * add exitcode * migrate to printf * indicate test by leds * add testing description * rename furi.h * wip basic api * add valuemutex, pubsub, split files * add value expanders * value mutex realization and tests * valuemutex test added to makefile * do not build unimplemented files * fix build furmware target f2 * redesigned minunit tests to allow testing in separate files * test file for valuemutex minunit testing * minunit partial test valuemutex * local cmsis_os2 mutex bindings * implement furi open/create, tests * migrate concurrent_access to ValueMutex * add spi header * Lib: add mlib submodule. Co-authored-by: rusdacent Co-authored-by: DrZlo13 --- .gitmodules | 3 + applications/applications.mk | 1 + .../coreglitch_demo_0/coreglitch_demo_0.c | 3 +- applications/display-u8g2/display-u8g2.c | 4 +- applications/examples/fatfs_list.c | 4 +- applications/examples/input_dump.c | 4 +- applications/examples/ipc.c | 7 +- applications/examples/u8g2_example.c | 2 +- applications/examples/u8g2_qrcode.c | 2 +- applications/input/input.c | 10 +- applications/startup.h | 2 +- applications/tests/furi_record_test.c | 258 +---------- applications/tests/furi_valuemutex_test.c | 123 ++++++ applications/tests/minunit_test.c | 47 +- core/api-basic/flapp.h | 47 ++ core/api-basic/furi.c | 14 + core/api-basic/furi.h | 26 ++ core/api-basic/pubsub.c.unimplemented | 48 +++ core/api-basic/pubsub.h | 83 ++++ .../api-basic/value-expanders.c.unimplemented | 24 ++ core/api-basic/value-expanders.h | 56 +++ core/api-basic/valuemutex.c | 52 +++ core/api-basic/valuemutex.h | 123 ++++++ core/api-hal/api-spi.h | 133 ++++++ core/app.cpp | 1 - core/core.mk | 1 + core/flipper.h | 3 +- core/flipper_v2.h | 7 + core/{furi.c => furi-deprecated.c} | 9 +- core/{furi.h => furi-deprecated.h} | 4 +- core/furi_ac.c | 3 +- core/log.c | 4 +- core/log.h | 2 +- core/tty_uart.c | 8 +- firmware/targets/local/Inc/cmsis_os.h | 28 ++ firmware/targets/local/Inc/cmsis_os2.h | 1 + firmware/targets/local/Src/lo_os.c | 23 + lib/lib.mk | 4 + lib/mlib | 1 + wiki/fw/Core-API.md | 88 ++-- wiki/fw/FURI.md | 20 - wiki/fw/api/API-Display.md | 61 --- wiki/fw/api/API-LED.md | 124 ------ wiki/fw/api/API-Sound.md | 122 ------ wiki/fw/api/Backlight-API.md | 100 +++++ wiki/fw/api/Basic-API.md | 402 ++++++++++++++++++ wiki/fw/api/Display-API.md | 68 +++ wiki/fw/api/HAL-API.md | 158 +++++++ wiki/fw/api/{API-Input.md => Input-API.md} | 8 +- wiki/fw/api/LED-API.md | 110 +++++ wiki/fw/api/SPI-Devices-API.md | 130 ++++++ 51 files changed, 1874 insertions(+), 692 deletions(-) create mode 100644 applications/tests/furi_valuemutex_test.c create mode 100644 core/api-basic/flapp.h create mode 100644 core/api-basic/furi.c create mode 100644 core/api-basic/furi.h create mode 100644 core/api-basic/pubsub.c.unimplemented create mode 100644 core/api-basic/pubsub.h create mode 100644 core/api-basic/value-expanders.c.unimplemented create mode 100644 core/api-basic/value-expanders.h create mode 100644 core/api-basic/valuemutex.c create mode 100644 core/api-basic/valuemutex.h create mode 100644 core/api-hal/api-spi.h create mode 100644 core/flipper_v2.h rename core/{furi.c => furi-deprecated.c} (97%) rename core/{furi.h => furi-deprecated.h} (97%) create mode 100644 firmware/targets/local/Inc/cmsis_os2.h create mode 160000 lib/mlib delete mode 100644 wiki/fw/api/API-Display.md delete mode 100644 wiki/fw/api/API-LED.md delete mode 100644 wiki/fw/api/API-Sound.md create mode 100644 wiki/fw/api/Backlight-API.md create mode 100644 wiki/fw/api/Basic-API.md create mode 100644 wiki/fw/api/Display-API.md create mode 100644 wiki/fw/api/HAL-API.md rename wiki/fw/api/{API-Input.md => Input-API.md} (90%) create mode 100644 wiki/fw/api/LED-API.md create mode 100644 wiki/fw/api/SPI-Devices-API.md diff --git a/.gitmodules b/.gitmodules index 26f0b707..38192f3d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "lib/STM32CubeL4"] path = lib/STM32CubeL4 url = https://github.com/STMicroelectronics/STM32CubeL4.git +[submodule "lib/mlib"] + path = lib/mlib + url = https://github.com/P-p-H-d/mlib.git diff --git a/applications/applications.mk b/applications/applications.mk index d199e638..71db1d39 100644 --- a/applications/applications.mk +++ b/applications/applications.mk @@ -16,6 +16,7 @@ C_SOURCES += $(APP_DIR)/tests/furiac_test.c C_SOURCES += $(APP_DIR)/tests/furi_record_test.c C_SOURCES += $(APP_DIR)/tests/test_index.c C_SOURCES += $(APP_DIR)/tests/minunit_test.c +C_SOURCES += $(APP_DIR)/tests/furi_valuemutex_test.c endif APP_EXAMPLE_BLINK ?= 0 diff --git a/applications/coreglitch_demo_0/coreglitch_demo_0.c b/applications/coreglitch_demo_0/coreglitch_demo_0.c index 2b939b08..bc4b67b7 100644 --- a/applications/coreglitch_demo_0/coreglitch_demo_0.c +++ b/applications/coreglitch_demo_0/coreglitch_demo_0.c @@ -9,7 +9,8 @@ void coreglitch_demo_0(void* p) { fuprintf(log, "coreglitch demo!\n"); // open record - FuriRecordSubscriber* fb_record = furi_open("u8g2_fb", false, false, NULL, NULL, NULL); + FuriRecordSubscriber* fb_record = + furi_open_deprecated("u8g2_fb", false, false, NULL, NULL, NULL); if(fb_record == NULL) { fuprintf(log, "[widget] cannot create fb record\n"); diff --git a/applications/display-u8g2/display-u8g2.c b/applications/display-u8g2/display-u8g2.c index 4f5d36b7..e2d0454c 100644 --- a/applications/display-u8g2/display-u8g2.c +++ b/applications/display-u8g2/display-u8g2.c @@ -143,7 +143,7 @@ void display_u8g2(void* p) { &_u8g2); // send init sequence to the display, display is in sleep mode after this u8g2_SetContrast(&_u8g2, 36); - if(!furi_create("u8g2_fb", (void*)&_u8g2, sizeof(_u8g2))) { + if(!furi_create_deprecated("u8g2_fb", (void*)&_u8g2, sizeof(_u8g2))) { fuprintf(log, "[display_u8g2] cannot create fb record\n"); furiac_exit(NULL); } @@ -162,7 +162,7 @@ void display_u8g2(void* p) { // subscribe to record. ctx will be passed to handle_fb_change FuriRecordSubscriber* fb_record = - furi_open("u8g2_fb", false, false, handle_fb_change, NULL, &ctx); + furi_open_deprecated("u8g2_fb", false, false, handle_fb_change, NULL, &ctx); if(fb_record == NULL) { fuprintf(log, "[display] cannot open fb record\n"); diff --git a/applications/examples/fatfs_list.c b/applications/examples/fatfs_list.c index ef8fc816..b15db514 100644 --- a/applications/examples/fatfs_list.c +++ b/applications/examples/fatfs_list.c @@ -49,14 +49,14 @@ void fatfs_list(void* p) { furi_log = get_default_log(); - FuriRecordSubscriber* fb_record = furi_open("u8g2_fb", false, false, NULL, NULL, NULL); + FuriRecordSubscriber* fb_record = furi_open_deprecated("u8g2_fb", false, false, NULL, NULL, NULL); if(fb_record == NULL) { fuprintf(furi_log, "[widget][fatfs_list] cannot create fb record\n"); furiac_exit(NULL); } FuriRecordSubscriber* event_record = - furi_open("input_events", false, false, event_cb, NULL, event_queue); + furi_open_deprecated("input_events", false, false, event_cb, NULL, event_queue); if(event_record == NULL) { fuprintf(furi_log, "[widget][fatfs_list] cannot register input_events callback\n"); furiac_exit(NULL); diff --git a/applications/examples/input_dump.c b/applications/examples/input_dump.c index 2aa50cdf..9f8d3f67 100644 --- a/applications/examples/input_dump.c +++ b/applications/examples/input_dump.c @@ -16,9 +16,9 @@ static void event_cb(const void* value, size_t size, void* ctx) { void application_input_dump(void* p) { // open record FuriRecordSubscriber* state_record = - furi_open("input_state", false, false, state_cb, NULL, NULL); + furi_open_deprecated("input_state", false, false, state_cb, NULL, NULL); FuriRecordSubscriber* event_record = - furi_open("input_events", false, false, event_cb, NULL, NULL); + furi_open_deprecated("input_events", false, false, event_cb, NULL, NULL); for(;;) { delay(100); diff --git a/applications/examples/ipc.c b/applications/examples/ipc.c index 7860ad9e..a965db98 100644 --- a/applications/examples/ipc.c +++ b/applications/examples/ipc.c @@ -60,7 +60,7 @@ void application_ipc_display(void* p) { } // create record - if(!furi_create("test_fb", (void*)_framebuffer, FB_SIZE)) { + if(!furi_create_deprecated("test_fb", (void*)_framebuffer, FB_SIZE)) { fuprintf(log, "[display] cannot create fb record\n"); furiac_exit(NULL); } @@ -79,7 +79,7 @@ void application_ipc_display(void* p) { // subscribe to record. ctx will be passed to handle_fb_change FuriRecordSubscriber* fb_record = - furi_open("test_fb", false, false, handle_fb_change, NULL, &ctx); + furi_open_deprecated("test_fb", false, false, handle_fb_change, NULL, &ctx); if(fb_record == NULL) { fuprintf(log, "[display] cannot open fb record\n"); @@ -124,7 +124,8 @@ void application_ipc_widget(void* p) { FuriRecordSubscriber* log = get_default_log(); // open record - FuriRecordSubscriber* fb_record = furi_open("test_fb", false, false, NULL, NULL, NULL); + FuriRecordSubscriber* fb_record = + furi_open_deprecated("test_fb", false, false, NULL, NULL, NULL); if(fb_record == NULL) { fuprintf(log, "[widget] cannot create fb record\n"); diff --git a/applications/examples/u8g2_example.c b/applications/examples/u8g2_example.c index 6c0aaf85..02f35ecb 100644 --- a/applications/examples/u8g2_example.c +++ b/applications/examples/u8g2_example.c @@ -5,7 +5,7 @@ void u8g2_example(void* p) { FuriRecordSubscriber* log = get_default_log(); // open record - FuriRecordSubscriber* fb_record = furi_open("u8g2_fb", false, false, NULL, NULL, NULL); + FuriRecordSubscriber* fb_record = furi_open_deprecated("u8g2_fb", false, false, NULL, NULL, NULL); if(fb_record == NULL) { fuprintf(log, "[widget] cannot create fb record\n"); diff --git a/applications/examples/u8g2_qrcode.c b/applications/examples/u8g2_qrcode.c index 1a067c43..630199c1 100644 --- a/applications/examples/u8g2_qrcode.c +++ b/applications/examples/u8g2_qrcode.c @@ -14,7 +14,7 @@ void u8g2_qrcode(void* p) { FuriRecordSubscriber* log = get_default_log(); // open record - FuriRecordSubscriber* fb_record = furi_open("u8g2_fb", false, false, NULL, NULL, NULL); + FuriRecordSubscriber* fb_record = furi_open_deprecated("u8g2_fb", false, false, NULL, NULL, NULL); // Allocate a chunk of memory to store the QR code // https://github.com/ricmoo/QRCode diff --git a/applications/input/input.c b/applications/input/input.c index 7858fd63..aa2e0346 100644 --- a/applications/input/input.c +++ b/applications/input/input.c @@ -1,7 +1,7 @@ #include #include #include -#include +#include static volatile bool initialized = false; static SemaphoreHandle_t event; @@ -16,25 +16,25 @@ void input_task(void* p) { event = xSemaphoreCreateCountingStatic(1, 0, &event_semaphore); - if(!furi_create("input_state", (void*)&input_state, sizeof(input_state))) { + if(!furi_create_deprecated("input_state", (void*)&input_state, sizeof(input_state))) { printf("[input_task] cannot create the input_state record\n"); furiac_exit(NULL); } FuriRecordSubscriber* input_state_record = - furi_open("input_state", false, false, NULL, NULL, NULL); + furi_open_deprecated("input_state", false, false, NULL, NULL, NULL); if(input_state_record == NULL) { printf("[input_task] cannot open the input_state record\n"); furiac_exit(NULL); } - if(!furi_create("input_events", NULL, 0)) { + if(!furi_create_deprecated("input_events", NULL, 0)) { printf("[input_task] cannot create the input_events record\n"); furiac_exit(NULL); } FuriRecordSubscriber* input_events_record = - furi_open("input_events", false, false, NULL, NULL, NULL); + furi_open_deprecated("input_events", false, false, NULL, NULL, NULL); if(input_events_record == NULL) { printf("[input_task] cannot open the input_events record\n"); furiac_exit(NULL); diff --git a/applications/startup.h b/applications/startup.h index acfb5448..d5c00d48 100644 --- a/applications/startup.h +++ b/applications/startup.h @@ -1,6 +1,6 @@ #pragma once -#include "furi.h" +#include "flipper.h" #define FURI_LIB (const char*[]) diff --git a/applications/tests/furi_record_test.c b/applications/tests/furi_record_test.c index b8d0b66f..f3fdcf1c 100644 --- a/applications/tests/furi_record_test.c +++ b/applications/tests/furi_record_test.c @@ -1,245 +1,18 @@ #include #include #include "flipper.h" +#include "flipper_v2.h" #include "log.h" +#include "minunit.h" -/* -TEST: pipe record - -1. create pipe record -2. Open/subscribe to it -3. write data -4. check that subscriber get data -5. try to read, get error -6. close record -7. try to write, get error -*/ - -static uint8_t pipe_record_value = 0; - -void pipe_record_cb(const void* value, size_t size, void* ctx) { - // hold value to static var - pipe_record_value = *((uint8_t*)value); -} - -bool test_furi_pipe_record() { - // 1. create pipe record - if(!furi_create("test/pipe", NULL, 0)) { - printf("cannot create record\n"); - return false; - } - - // 2. Open/subscribe to it - FuriRecordSubscriber* pipe_record = - furi_open("test/pipe", false, false, pipe_record_cb, NULL, NULL); - if(pipe_record == NULL) { - printf("cannot open record\n"); - return false; - } - - const uint8_t WRITE_VALUE = 1; - // 3. write data - if(!furi_write(pipe_record, &WRITE_VALUE, sizeof(uint8_t))) { - printf("cannot write to record\n"); - return false; - } - - // 4. check that subscriber get data - if(pipe_record_value != WRITE_VALUE) { - printf("wrong value (get %d, write %d)\n", pipe_record_value, WRITE_VALUE); - return false; - } - - // 5. try to read, get error - uint8_t read_value = 0; - if(furi_read(pipe_record, &read_value, sizeof(uint8_t))) { - printf("reading from pipe record not allowed\n"); - return false; - } - - // 6. close record - furi_close(pipe_record); - - // 7. try to write, get error - if(furi_write(pipe_record, &WRITE_VALUE, sizeof(uint8_t))) { - printf("writing to closed record not allowed\n"); - return false; - } - - return true; -} - -/* -TEST: holding data - -1. Create holding record -2. Open/Subscribe on it -3. Write data -4. Check that subscriber get data -5. Read and check data -6. Try to write/read wrong size of data -*/ - -static uint8_t holding_record_value = 0; - -void holding_record_cb(const void* value, size_t size, void* ctx) { - // hold value to static var - holding_record_value = *((uint8_t*)value); -} - -bool test_furi_holding_data() { - // 1. Create holding record - uint8_t holder = 0; - if(!furi_create("test/holding", (void*)&holder, sizeof(holder))) { - printf("cannot create record\n"); - return false; - } - - // 2. Open/Subscribe on it - FuriRecordSubscriber* holding_record = - furi_open("test/holding", false, false, holding_record_cb, NULL, NULL); - if(holding_record == NULL) { - printf("cannot open record\n"); - return false; - } - - const uint8_t WRITE_VALUE = 1; - // 3. write data - if(!furi_write(holding_record, &WRITE_VALUE, sizeof(uint8_t))) { - printf("cannot write to record\n"); - return false; - } - - // 4. check that subscriber get data - if(holding_record_value != WRITE_VALUE) { - printf("wrong sub value (get %d, write %d)\n", holding_record_value, WRITE_VALUE); - return false; - } - - // 5. Read and check data - uint8_t read_value = 0; - if(!furi_read(holding_record, &read_value, sizeof(uint8_t))) { - printf("cannot read from record\n"); - return false; - } - - if(read_value != WRITE_VALUE) { - printf("wrong read value (get %d, write %d)\n", read_value, WRITE_VALUE); - return false; - } - - // 6. Try to write/read wrong size of data - if(furi_write(holding_record, &WRITE_VALUE, 100)) { - printf("overflowed write not allowed\n"); - return false; - } - - if(furi_read(holding_record, &read_value, 100)) { - printf("overflowed read not allowed\n"); - return false; - } - - return true; -} - -/* -TEST: concurrent access - -1. Create holding record -2. Open it twice -3. Change value simultaneously in two app and check integrity -*/ - -// TODO this test broke because mutex in furi is not implemented - -typedef struct { - // a and b must be equal - uint8_t a; - uint8_t b; -} ConcurrentValue; - -void furi_concurent_app(void* p) { - FuriRecordSubscriber* holding_record = - furi_open("test/concurrent", false, false, NULL, NULL, NULL); - if(holding_record == NULL) { - printf("cannot open record\n"); - furiac_exit(NULL); - } - - for(size_t i = 0; i < 10; i++) { - ConcurrentValue* value = (ConcurrentValue*)furi_take(holding_record); - - if(value == NULL) { - printf("cannot take record\n"); - furi_give(holding_record); - furiac_exit(NULL); - } - // emulate read-modify-write broken by context switching - uint8_t a = value->a; - uint8_t b = value->b; - a++; - b++; - delay(2); // this is only for test, do not add delay between take/give in prod! - value->a = a; - value->b = b; - furi_give(holding_record); - } - - furiac_exit(NULL); -} - -bool test_furi_concurrent_access() { - // 1. Create holding record - ConcurrentValue holder = {.a = 0, .b = 0}; - if(!furi_create("test/concurrent", (void*)&holder, sizeof(ConcurrentValue))) { - printf("cannot create record\n"); - return false; - } +void test_furi_create_open() { + // 1. Create record + uint8_t test_data = 0; + mu_check(furi_create("test/holding", (void*)&test_data)); // 2. Open it - FuriRecordSubscriber* holding_record = - furi_open("test/concurrent", false, false, NULL, NULL, NULL); - if(holding_record == NULL) { - printf("cannot open record\n"); - return false; - } - - // 3. Create second app for interact with it - FuriApp* second_app = furiac_start(furi_concurent_app, "furi concurent app", NULL); - - // 4. multiply ConcurrentValue::a - for(size_t i = 0; i < 4; i++) { - ConcurrentValue* value = (ConcurrentValue*)furi_take(holding_record); - - if(value == NULL) { - printf("cannot take record\n"); - furi_give(holding_record); - return false; - } - // emulate read-modify-write broken by context switching - uint8_t a = value->a; - uint8_t b = value->b; - a++; - b++; - value->a = a; - delay(10); // this is only for test, do not add delay between take/give in prod! - value->b = b; - furi_give(holding_record); - } - - delay(50); - - if(second_app->handler != NULL) { - printf("second app still alive\n"); - return false; - } - - if(holder.a != holder.b) { - printf("broken integrity: a=%d, b=%d\n", holder.a, holder.b); - return false; - } - - return true; + void* record = furi_open("test/holding"); + mu_assert_pointers_eq(record, &test_data); } /* @@ -309,14 +82,14 @@ void mute_record_state_cb(FlipperRecordState state, void* ctx) { void furi_mute_parent_app(void* p) { // 1. Create pipe record - if(!furi_create("test/mute", NULL, 0)) { + if(!furi_create_deprecated("test/mute", NULL, 0)) { printf("cannot create record\n"); furiac_exit(NULL); } // 2. Open watch handler: solo=false, no_mute=false, subscribe to data FuriRecordSubscriber* watch_handler = - furi_open("test/mute", false, false, mute_record_cb, NULL, NULL); + furi_open_deprecated("test/mute", false, false, mute_record_cb, NULL, NULL); if(watch_handler == NULL) { printf("cannot open watch handler\n"); furiac_exit(NULL); @@ -336,7 +109,7 @@ bool test_furi_mute_algorithm() { // 2. Open handler A: solo=false, no_mute=false, NULL subscriber. Subscribe to state. FuriRecordSubscriber* handler_a = - furi_open("test/mute", false, false, NULL, mute_record_state_cb, NULL); + furi_open_deprecated("test/mute", false, false, NULL, mute_record_state_cb, NULL); if(handler_a == NULL) { printf("cannot open handler A\n"); return false; @@ -356,7 +129,8 @@ bool test_furi_mute_algorithm() { } // 3. Open handler B: solo=true, no_mute=true, NULL subscriber. - FuriRecordSubscriber* handler_b = furi_open("test/mute", true, true, NULL, NULL, NULL); + FuriRecordSubscriber* handler_b = + furi_open_deprecated("test/mute", true, true, NULL, NULL, NULL); if(handler_b == NULL) { printf("cannot open handler B\n"); return false; @@ -395,7 +169,8 @@ bool test_furi_mute_algorithm() { } // 4. Open hadler C: solo=true, no_mute=false, NULL subscriber. - FuriRecordSubscriber* handler_c = furi_open("test/mute", true, false, NULL, NULL, NULL); + FuriRecordSubscriber* handler_c = + furi_open_deprecated("test/mute", true, false, NULL, NULL, NULL); if(handler_c == NULL) { printf("cannot open handler C\n"); return false; @@ -406,7 +181,8 @@ bool test_furi_mute_algorithm() { // TODO: Try to write data to C and check that subscriber get data. // 5. Open handler D: solo=false, no_mute=false, NULL subscriber. - FuriRecordSubscriber* handler_d = furi_open("test/mute", false, false, NULL, NULL, NULL); + FuriRecordSubscriber* handler_d = + furi_open_deprecated("test/mute", false, false, NULL, NULL, NULL); if(handler_d == NULL) { printf("cannot open handler D\n"); return false; diff --git a/applications/tests/furi_valuemutex_test.c b/applications/tests/furi_valuemutex_test.c new file mode 100644 index 00000000..960d1543 --- /dev/null +++ b/applications/tests/furi_valuemutex_test.c @@ -0,0 +1,123 @@ +#include +#include +#include "flipper_v2.h" +#include "log.h" + +#include "minunit.h" + +void test_furi_valuemutex() { + const int init_value = 0xdeadbeef; + const int changed_value = 0x12345678; + + int value = init_value; + bool result; + ValueMutex valuemutex; + + // init mutex case + result = init_mutex(&valuemutex, &value, sizeof(value)); + mu_assert(result, "init mutex failed"); + + // acquire mutex case + int* value_pointer = acquire_mutex(&valuemutex, 100); + mu_assert_pointers_eq(value_pointer, &value); + + // second acquire mutex case + int* value_pointer_second = acquire_mutex(&valuemutex, 100); + mu_assert_pointers_eq(value_pointer_second, NULL); + + // change value case + *value_pointer = changed_value; + mu_assert_int_eq(value, changed_value); + + // release mutex case + result = release_mutex(&valuemutex, &value); + mu_assert(result, "release mutex failed"); + + // TODO + //acquire mutex blocking case + //write mutex blocking case + //read mutex blocking case +} + + +/* +TEST: concurrent access + +1. Create holding record +2. Open it twice +3. Change value simultaneously in two app and check integrity +*/ + +// TODO this test broke because mutex in furi is not implemented + +typedef struct { + // a and b must be equal + uint8_t a; + uint8_t b; +} ConcurrentValue; + +void furi_concurent_app(void* p) { + ValueMutex* mutex = (ValueMutex*)p; + if(mutex == NULL) { + printf("cannot open mutex\n"); + furiac_exit(NULL); + } + + for(size_t i = 0; i < 10; i++) { + ConcurrentValue* value = (ConcurrentValue*)acquire_mutex_block(mutex); + + if(value == NULL) { + printf("cannot take record\n"); + release_mutex(mutex, value); + furiac_exit(NULL); + } + + // emulate read-modify-write broken by context switching + uint8_t a = value->a; + uint8_t b = value->b; + a++; + b++; + delay(2); + value->a = a; + value->b = b; + release_mutex(mutex, value); + } + + furiac_exit(NULL); +} + +void test_furi_concurrent_access() { + // 1. Create holding record + ConcurrentValue value = {.a = 0, .b = 0}; + ValueMutex mutex; + mu_check(init_mutex(&mutex, &value, sizeof(value))); + + // 3. Create second app for interact with it + FuriApp* second_app = furiac_start(furi_concurent_app, "furi concurent app", (void*)&mutex); + + // 4. multiply ConcurrentValue::a + for(size_t i = 0; i < 4; i++) { + ConcurrentValue* value = (ConcurrentValue*)acquire_mutex_block(&mutex); + + if(value == NULL) { + release_mutex(&mutex, value); + mu_fail("cannot take record\n"); + } + + // emulate read-modify-write broken by context switching + uint8_t a = value->a; + uint8_t b = value->b; + a++; + b++; + value->a = a; + delay(10); // this is only for test, do not add delay between take/give in prod! + value->b = b; + release_mutex(&mutex, value); + } + + delay(50); + + mu_assert_pointers_eq(second_app->handler, NULL); + + mu_assert_int_eq(value.a, value.b); +} \ No newline at end of file diff --git a/applications/tests/minunit_test.c b/applications/tests/minunit_test.c index 62e0da37..f99b6204 100644 --- a/applications/tests/minunit_test.c +++ b/applications/tests/minunit_test.c @@ -6,12 +6,15 @@ bool test_furi_ac_create_kill(); bool test_furi_ac_switch_exit(); -bool test_furi_pipe_record(); -bool test_furi_holding_data(); -bool test_furi_concurrent_access(); + bool test_furi_nonexistent_data(); bool test_furi_mute_algorithm(); +// v2 tests +void test_furi_create_open(); +void test_furi_valuemutex(); +void test_furi_concurrent_access(); + static int foo = 0; void test_setup(void) { @@ -34,27 +37,22 @@ MU_TEST(mu_test_furi_ac_switch_exit) { mu_assert_int_eq(test_furi_ac_switch_exit(), true); } -MU_TEST(mu_test_furi_pipe_record) { - mu_assert_int_eq(test_furi_pipe_record(), true); -} - -MU_TEST(mu_test_furi_holding_data) { - mu_assert_int_eq(test_furi_holding_data(), true); -} - -MU_TEST(mu_test_furi_concurrent_access) { - mu_assert_int_eq(test_furi_concurrent_access(), true); -} - MU_TEST(mu_test_furi_nonexistent_data) { mu_assert_int_eq(test_furi_nonexistent_data(), true); } -/* -MU_TEST(mu_test_furi_mute_algorithm) { - mu_assert_int_eq(test_furi_mute_algorithm(test_log), true); +// v2 tests +MU_TEST(mu_test_furi_create_open) { + test_furi_create_open(); +} + +MU_TEST(mu_test_furi_valuemutex) { + test_furi_valuemutex(); +} + +MU_TEST(mu_test_furi_concurrent_access) { + test_furi_concurrent_access(); } -*/ MU_TEST_SUITE(test_suite) { MU_SUITE_CONFIGURE(&test_setup, &test_teardown); @@ -62,11 +60,14 @@ MU_TEST_SUITE(test_suite) { MU_RUN_TEST(test_check); MU_RUN_TEST(mu_test_furi_ac_create_kill); MU_RUN_TEST(mu_test_furi_ac_switch_exit); - MU_RUN_TEST(mu_test_furi_pipe_record); - MU_RUN_TEST(mu_test_furi_holding_data); - MU_RUN_TEST(mu_test_furi_concurrent_access); + MU_RUN_TEST(mu_test_furi_nonexistent_data); - // MU_RUN_TEST(mu_test_furi_mute_algorithm); + + // v2 tests + MU_RUN_TEST(mu_test_furi_create_open); + MU_RUN_TEST(mu_test_furi_valuemutex); + MU_RUN_TEST(mu_test_furi_concurrent_access); + } int run_minunit() { diff --git a/core/api-basic/flapp.h b/core/api-basic/flapp.h new file mode 100644 index 00000000..3ccaf7ee --- /dev/null +++ b/core/api-basic/flapp.h @@ -0,0 +1,47 @@ +#pragma once + +#include "flipper.h" + +// == Flipper Application control (flapp) == + +typedef FlappHandler uint32_t; // TODO + +/* +simply starts application. It call `app` entrypoint with `param` passed as argument +Useful for daemon applications and pop-up. +*/ +FlappHandler* flapp_start(void(app*)(void*), char* name, void* param); + +/* +swtich to other application. +System **stop current app**, call `app` entrypoint with `param` passed +as argument and save current application entrypoint to `prev` field in +current application registry. Useful for UI or "active" application. +*/ +FlappHandler* flapp_switch(void(app*)(void*), char* name, void* param); + +/* +Exit application +stop current application (stop thread and clear application's stack), +start application from `prev` entry in current application registry, +cleanup current application registry. +*/ +void flapp_exit(void* param); + +/* +stop specified `app` without returning to `prev` application. +*/ +bool flapp_kill(FlappHandler* app); + +/* +If case one app depend on other, notify that app is ready. +*/ +void flapp_ready(); + +/* +Register on-exit callback. +It called before app will be killed. +Not recommended to use in user scenario, only for system purpose +(unregister callbacks, release mutexes, etc.) +*/ +bool flapp_on_exit(void(cb*)(void*), void* ctx); diff --git a/core/api-basic/furi.c b/core/api-basic/furi.c new file mode 100644 index 00000000..62ca8ce2 --- /dev/null +++ b/core/api-basic/furi.c @@ -0,0 +1,14 @@ +#include "furi.h" +#include "furi-deprecated.h" + +bool furi_create(const char* name, void* ptr) { + return furi_create_deprecated(name, ptr, sizeof(size_t)); +} + +void* furi_open(const char* name) { + FuriRecordSubscriber* record = furi_open_deprecated(name, false, false, NULL, NULL, NULL); + void* res = furi_take(record); + furi_give(record); + + return res; +} \ No newline at end of file diff --git a/core/api-basic/furi.h b/core/api-basic/furi.h new file mode 100644 index 00000000..0729eca0 --- /dev/null +++ b/core/api-basic/furi.h @@ -0,0 +1,26 @@ +#pragma once + +#include "flipper.h" + +/* +== Flipper universal registry implementation (FURI) == + +## Requirements + +* start daemon app +* kill app +* start child thread (kill when parent app was killed) +* switch between UI apps +*/ + +/* +Create record. +creates new record in registry and store pointer into it +*/ +bool furi_create(const char* name, void* ptr); + +/* +Open record. +get stored pointer by its name +*/ +void* furi_open(const char* name); diff --git a/core/api-basic/pubsub.c.unimplemented b/core/api-basic/pubsub.c.unimplemented new file mode 100644 index 00000000..77132f75 --- /dev/null +++ b/core/api-basic/pubsub.c.unimplemented @@ -0,0 +1,48 @@ +#include "pubsub.h" + +void init_pubsub(PubSub* pubsub) { + pubsub->count = 0; + + for(size_t i = 0; i < NUM_OF_CALLBACKS; i++) { + pubsub->items[i]. + } +} + +// TODO add mutex to reconfigurate PubSub +PubSubId* subscribe_pubsub(PubSub* pubsub, PubSubCallback cb, void* ctx) { + if(pubsub->count >= NUM_OF_CALLBACKS) return NULL; + + pubsub->count++; + PubSubItem* current = pubsub->items[pubsub->count]; + + current->cb = cb; + currrnt->ctx = ctx; + + pubsub->ids[pubsub->count].self = pubsub; + pubsub->ids[pubsub->count].item = current; + + flapp_on_exit(unsubscribe_pubsub, &(pubsub->ids[pubsub->count])); + + return current; +} + +void unsubscribe_pubsub(PubSubId* pubsub_id) { + // TODO: add, and rearrange all items to keep subscribers item continuous + // TODO: keep ids link actual + // TODO: also add mutex on every pubsub changes + + // trivial implementation for NUM_OF_CALLBACKS = 1 + if(NUM_OF_CALLBACKS != 1) return; + + if(pubsub_id != NULL || pubsub_id->self != NULL || pubsub_id->item != NULL) return; + + pubsub_id->self->count = 0; + pubsub_id->item = NULL; +} + +void notify_pubsub(PubSub* pubsub, void* arg) { + // iterate over subscribers + for(size_t i = 0; i < pubsub->count; i++) { + pubsub->items[i]->cb(arg, pubsub->items[i]->ctx); + } +} diff --git a/core/api-basic/pubsub.h b/core/api-basic/pubsub.h new file mode 100644 index 00000000..bd38cc1f --- /dev/null +++ b/core/api-basic/pubsub.h @@ -0,0 +1,83 @@ +#pragma once + +#include "flipper.h" + +/* +== PubSub == + +PubSub allows users to subscribe on notifies and notify subscribers. +Notifier side can pass `void*` arg to subscriber callback, +and also subscriber can set `void*` context pointer that pass into +callback (you can see callback signature below). +*/ + +typedef void(PubSubCallback*)(void*, void*); + +typedef struct { + PubSubCallback cb; + void* ctx; +} PubSubItem; + +typedef struct { + PubSub* self; + PubSubItem* item; +} PubSubId; + +typedef struct { + PubSubItem items[NUM_OF_CALLBACKS]; + PubSubId ids[NUM_OF_CALLBACKS]; ///< permanent links to item + size_t count; ///< count of callbacks +} PubSub; + +/* +To create PubSub you should create PubSub instance and call `init_pubsub`. +*/ +void init_pubsub(PubSub* pubsub); + +/* +Use `subscribe_pubsub` to register your callback. +*/ +PubSubId* subscribe_pubsub(PubSub* pubsub, PubSubCallback cb, void* ctx); + +/* +Use `unsubscribe_pubsub` to unregister callback. +*/ +void unsubscribe_pubsub(PubSubId* pubsub_id); + +/* +Use `notify_pubsub` to notify subscribers. +*/ +void notify_pubsub(PubSub* pubsub, void* arg); + +/* + +```C +// MANIFEST +// name="test" +// stack=128 + +void example_pubsub_handler(void* arg, void* ctx) { + printf("get %d from %s\n", *(uint32_t*)arg, (const char*)ctx); +} + +void pubsub_test() { + const char* app_name = "test app"; + + PubSub example_pubsub; + init_pubsub(&example_pubsub); + + if(!subscribe_pubsub(&example_pubsub, example_pubsub_handler, (void*)app_name)) { + printf("critical error\n"); + flapp_exit(NULL); + } + + uint32_t counter = 0; + while(1) { + notify_pubsub(&example_pubsub, (void*)&counter); + counter++; + + osDelay(100); + } +} +``` +*/ \ No newline at end of file diff --git a/core/api-basic/value-expanders.c.unimplemented b/core/api-basic/value-expanders.c.unimplemented new file mode 100644 index 00000000..17e3a3e1 --- /dev/null +++ b/core/api-basic/value-expanders.c.unimplemented @@ -0,0 +1,24 @@ +#include "value-expanders.h" + +bool commit_managed(ValueManager* managed, void* value) { + if(value != managed->mutex->value) return false; + + notify_pubsub(&managed->pubsub, value); + + if(!osMutexGive(managed->mutex)) return false; + + return true; +} + +bool write_managed(ValueManager* managed, void* data, size_t len, uint32_t timeout) { + void* value = acquire_mutex(managed->mutex, timeout); + if(value == NULL) return false; + + memcpy(value, data, len): + + notify_pubsub(&managed->pubsub, value); + + if(!release_mutex(managed->mutex, value)) return false; + + return true; +} diff --git a/core/api-basic/value-expanders.h b/core/api-basic/value-expanders.h new file mode 100644 index 00000000..cec0e75c --- /dev/null +++ b/core/api-basic/value-expanders.h @@ -0,0 +1,56 @@ +#pragma once + +#include "flipper.h" + +/* +== Value composer == +*/ + +typedef void(ValueComposerCallback)(void* ctx, void* state); + +void COPY_COMPOSE(void* ctx, void* state) { + read_mutex((ValueMutex*)ctx, state, 0); +} + +typedef enum { + UiLayerBelowNotify + UiLayerNotify, + UiLayerAboveNotify +} UiLayer; + +ValueComposerHandle* add_compose_layer( + ValueComposer* composer, ValueComposerCallback cb, void* ctx, uint32_t layer +); + +bool remove_compose_layer(ValueComposerHandle* handle); + +void request_compose(ValueComposerHandle* handle); + +// See [LED](LED-API) or [Display](Display-API) API for examples. + +/* +== ValueManager == + +More complicated concept is ValueManager. +It is like ValueMutex, but user can subscribe to value updates. + +First of all you can use value and pubsub part as showing above: +aquire/release mutex, read value, subscribe/unsubscribe pubsub. +There are two specific methods for ValueManager: write_managed, commit_managed +*/ + +typedef struct { + ValueMutex value; + PubSub pubsub; +} ValueManager; + + +/* +acquire value, changes it and send notify with current value. +*/ +bool write_managed(ValueManager* managed, void* data, size_t len, uint32_t timeout); + +/* +commit_managed works as `release_mutex` but send notify with current value. +*/ +bool commit_managed(ValueManager* managed, void* value); diff --git a/core/api-basic/valuemutex.c b/core/api-basic/valuemutex.c new file mode 100644 index 00000000..564cb901 --- /dev/null +++ b/core/api-basic/valuemutex.c @@ -0,0 +1,52 @@ +#include "valuemutex.h" +#include + +bool init_mutex(ValueMutex* valuemutex, void* value, size_t size) { + // mutex without name, + // no attributes (unfortunatly robust mutex is not supported by FreeRTOS), + // with dynamic memory allocation + const osMutexAttr_t value_mutext_attr = { + .name = NULL, .attr_bits = 0, .cb_mem = NULL, .cb_size = 0U}; + + valuemutex->mutex = osMutexNew(&value_mutext_attr); + if(valuemutex->mutex == NULL) return false; + + valuemutex->value = value; + valuemutex->size = size; + + return true; +} + +void* acquire_mutex(ValueMutex* valuemutex, uint32_t timeout) { + if(osMutexAcquire(valuemutex->mutex, timeout) == osOK) { + return valuemutex->value; + } else { + return NULL; + } +} + +bool release_mutex(ValueMutex* valuemutex, void* value) { + if(value != valuemutex->value) return false; + + if(osMutexRelease(valuemutex->mutex) != osOK) return false; + + return true; +} + +bool read_mutex(ValueMutex* valuemutex, void* data, size_t len, uint32_t timeout) { + void* value = acquire_mutex(valuemutex, timeout); + if(value == NULL || len > valuemutex->size) return false; + memcpy(data, value, len > 0 ? len : valuemutex->size); + if(!release_mutex(valuemutex, value)) return false; + + return true; +} + +bool write_mutex(ValueMutex* valuemutex, void* data, size_t len, uint32_t timeout) { + void* value = acquire_mutex(valuemutex, timeout); + if(value == NULL || len > valuemutex->size) return false; + memcpy(value, data, len > 0 ? len : valuemutex->size); + if(!release_mutex(valuemutex, value)) return false; + + return true; +} \ No newline at end of file diff --git a/core/api-basic/valuemutex.h b/core/api-basic/valuemutex.h new file mode 100644 index 00000000..6ba7b919 --- /dev/null +++ b/core/api-basic/valuemutex.h @@ -0,0 +1,123 @@ +#pragma once + +#include "flipper.h" + +/* +== ValueMutex == + +The most simple concept is ValueMutex. +It is wrapper around mutex and value pointer. +You can take and give mutex to work with value and read and write value. +*/ + +typedef struct { + void* value; + size_t size; + osMutexId_t mutex; +} ValueMutex; + +/* +Creates ValueMutex. +*/ +bool init_mutex(ValueMutex* valuemutex, void* value, size_t size); + +/* +Call for work with data stored in mutex. +Returns pointer to data if success, NULL otherwise. +*/ +void* acquire_mutex(ValueMutex* valuemutex, uint32_t timeout); + +/* +Helper: infinitly wait for mutex +*/ +static inline void* acquire_mutex_block(ValueMutex* valuemutex) { + return acquire_mutex(valuemutex, osWaitForever); +} + +/* +Release mutex after end of work with data. +Call `release_mutex` and pass ValueData instance and pointer to data. +*/ +bool release_mutex(ValueMutex* valuemutex, void* value); + +/* +Instead of take-access-give sequence you can use `read_mutex` and `write_mutex` functions. +Both functions return true in case of success, false otherwise. +*/ +bool read_mutex(ValueMutex* valuemutex, void* data, size_t len, uint32_t timeout); + +bool write_mutex(ValueMutex* valuemutex, void* data, size_t len, uint32_t timeout); + +inline static bool write_mutex_block(ValueMutex* valuemutex, void* data, size_t len) { + return write_mutex(valuemutex, data, len, osWaitForever); +} + +inline static bool read_mutex_block(ValueMutex* valuemutex, void* data, size_t len) { + return read_mutex(valuemutex, data, len, osWaitForever); +} + +/* + +Usage example + +```C +// MANIFEST +// name="example-provider-app" +// stack=128 + +void provider_app(void* _p) { + // create record with mutex + uint32_t example_value = 0; + ValueMutex example_mutex; + // call `init_mutex`. + if(!init_mutex(&example_mutex, (void*)&example_value, sizeof(uint32_t))) { + printf("critical error\n"); + flapp_exit(NULL); + } + + if(furi_create("provider/example", (void*)&example_mutex)) { + printf("critical error\n"); + flapp_exit(NULL); + } + + // we are ready to provide record to other apps + flapp_ready(); + + // get value and increment it + while(1) { + uint32_t* value = acquire_mutex(&example_mutex, OsWaitForever); + if(value != NULL) { + value++; + } + release_mutex(&example_mutex, value); + + osDelay(100); + } +} + +// MANIFEST +// name="example-consumer-app" +// stack=128 +// require="example-provider-app" +void consumer_app(void* _p) { + // this app run after flapp_ready call in all requirements app + + // open mutex value + ValueMutex* counter_mutex = furi_open("provider/example"); + if(counter_mutex == NULL) { + printf("critical error\n"); + flapp_exit(NULL); + } + + // continously read value every 1s + uint32_t counter; + while(1) { + if(read_mutex(counter_mutex, &counter, sizeof(counter), OsWaitForever)) { + printf("counter value: %d\n", counter); + } + + osDelay(1000); + } +} +``` +*/ \ No newline at end of file diff --git a/core/api-hal/api-spi.h b/core/api-hal/api-spi.h new file mode 100644 index 00000000..b3bfe674 --- /dev/null +++ b/core/api-hal/api-spi.h @@ -0,0 +1,133 @@ +#include "flipper_v2.h" + +/* +struct used for handling SPI info. +*/ +typedef struct { + SPI_HandleTypeDef* spi; + PubSubCallback cb; + void* ctx; +} SpiHandle; + +/* +For transmit/receive data use `spi_xfer` function. + +* `tx_data` and `rx_data` size must be equal (and equal `len`) +* `cb` called after spi operation is completed, `(NULL, ctx)` passed to callback. +*/ +bool spi_xfer( + SPI_HandleTypeDef* spi, + uint8_t* tx_data, uint8_t* rx_data, size_t len, + PubSubCallback cb, void* ctx); + +/* +Blocking verison: +*/ +static inline bool spi_xfer_block(SPI_HandleTypeDef* spi, uint8_t* tx_data, uint8_t* rx_data, size_t len) { + semaphoreInfo s; + osSemaphore block = createSemaphoreStatic(s); + if(!spi_xfer(spi, tx_data, rx_data, len, RELEASE_SEMAPHORE, (void*)block)) { + osReleaseSemaphore(block); + return false; + } + osWaitSemaphore(block); + return false; +} + +/* +Common implementation of SPI bus: serial interface + CS pin +*/ +typedef struct { + GpioPin* cs; ///< CS pin + ValueMutex* spi; ///< +} SpiBus; + +/* +For dedicated work with one device there is `SpiDevice` entity. +It contains ValueMutex around SpiBus: after you acquire device +you can acquire spi to work with it (don't forget SPI bus is shared +around many device, release it after every transaction as quick as possible). +*/ +typedef struct { + ValueMutex* bus; ///< +} SpiDevice; + +## SPI IRQ device + +/* +Many devices (like CC1101 and NFC) present as SPI bus and IRQ line. +For work with it there is special entity `SpiIrqDevice`. +Use `subscribe_pubsub` for subscribinq to irq events. +*/ + +typedef struct { + ValueMutex* bus; ///< + PubSub* irq; +} SpiIrqDevice; + +/* +Special implementation of SPI bus: serial interface + CS, Res, D/I lines. +*/ +typedef struct { + GpioPin* cs; ///< CS pin + GpioPin* res; ///< reset pin + GpioPin* di; ///< D/I pin + ValueMutex* spi; ///< +} DisplayBus; + + +typedef struct { + ValueMutex* bus; ///< +} DisplayDevice; + +/* +# SPI devices (F2) + +* `/dev/sdcard` - SD card SPI, `SpiDevice` +* `/dev/cc1101_bus` - Sub-GHz radio (CC1101), `SpiIrqDevice` +* `/dev/nfc` - NFC (ST25R3916), `SpiIrqDevice` +* `/dev/display` - `DisplayDevice` +* `/dev/spiext` - External SPI (warning! Lock PA4, PA5, PA6, PA7) + +### Application example + +```C +// Be careful, this function called from IRQ context +void handle_irq(void* _arg, void* _ctx) { +} + +void cc1101_example() { + SpiIrqDevice* cc1101_device = open_input("/dev/cc1101_bus"); + if(cc1101_device == NULL) return; // bus not available, critical error + + subscribe_pubsub(cc1101_device->irq, handle_irq, NULL); + + { + // acquire device as device bus + SpiBus* spi_bus = acquire_mutex(cc1101_device->bus, 0); + if(spi_bus == NULL) { + printf("Device busy\n"); + // wait for device + spi_bus = acquire_mutex_block(cc1101_device->bus); + } + + // make transaction + uint8_t request[4] = {0xDE, 0xAD, 0xBE, 0xEF}; + uint8_t response[4]; + + { + SPI_HandleTypeDef* spi = acquire_mutex_block(spi_bus->spi); + + gpio_write(spi_bus->cs, false); + spi_xfer_block(spi, request, response, 4); + gpio_write(spi_bus->cs, true); + + release_mutex(cc1101_device->spi, spi); + } + + // release device (device bus) + release_mutex(cc1101_device->bus, spi_bus); + } +} +``` +*/ \ No newline at end of file diff --git a/core/app.cpp b/core/app.cpp index 24c19669..cda52a41 100644 --- a/core/app.cpp +++ b/core/app.cpp @@ -2,7 +2,6 @@ extern "C" { #include "flipper.h" -#include "furi.h" #include "log.h" #include "startup.h" #include "tty_uart.h" diff --git a/core/core.mk b/core/core.mk index ffc626c6..c9a00e80 100644 --- a/core/core.mk +++ b/core/core.mk @@ -3,4 +3,5 @@ CORE_DIR = $(PROJECT_ROOT)/core CFLAGS += -I$(CORE_DIR) ASM_SOURCES += $(wildcard $(CORE_DIR)/*.s) C_SOURCES += $(wildcard $(CORE_DIR)/*.c) +C_SOURCES += $(wildcard $(CORE_DIR)/api-basic/*.c) CPP_SOURCES += $(wildcard $(CORE_DIR)/*.cpp) diff --git a/core/flipper.h b/core/flipper.h index da78456b..8e4cd098 100644 --- a/core/flipper.h +++ b/core/flipper.h @@ -7,7 +7,8 @@ extern "C" { #include "main.h" #include "flipper_hal.h" #include "cmsis_os.h" -#include "furi.h" +#include "furi-deprecated.h" + #include "log.h" #include "input/input.h" diff --git a/core/flipper_v2.h b/core/flipper_v2.h new file mode 100644 index 00000000..f1079f71 --- /dev/null +++ b/core/flipper_v2.h @@ -0,0 +1,7 @@ +#pragma once + +#include "api-basic/furi.h" +//#include "api-basic/flapp.h" +#include "cmsis_os2.h" +#include "api-basic/valuemutex.h" +//#include "api-basic/pubsub.h" \ No newline at end of file diff --git a/core/furi.c b/core/furi-deprecated.c similarity index 97% rename from core/furi.c rename to core/furi-deprecated.c index d593314e..0df40efe 100644 --- a/core/furi.c +++ b/core/furi-deprecated.c @@ -1,5 +1,4 @@ -#include "furi.h" -#include "cmsis_os.h" +#include "furi-deprecated.h" #include // TODO: this file contains printf, that not implemented on uC target @@ -28,7 +27,7 @@ static FuriRecord* find_record(const char* name) { } // TODO: change open-create to only open -bool furi_create(const char* name, void* value, size_t size) { +bool furi_create_deprecated(const char* name, void* value, size_t size) { #ifdef FURI_DEBUG printf("[FURI] creating %s record\n", name); #endif @@ -73,7 +72,7 @@ bool furi_create(const char* name, void* value, size_t size) { return true; } -FuriRecordSubscriber* furi_open( +FuriRecordSubscriber* furi_open_deprecated( const char* name, bool solo, bool no_mute, @@ -94,7 +93,7 @@ FuriRecordSubscriber* furi_open( #endif // create record if not exist - if(!furi_create(name, NULL, 0)) { + if(!furi_create_deprecated(name, NULL, 0)) { return NULL; } diff --git a/core/furi.h b/core/furi-deprecated.h similarity index 97% rename from core/furi.h rename to core/furi-deprecated.h index eccfc68f..981146fa 100644 --- a/core/furi.h +++ b/core/furi-deprecated.h @@ -127,7 +127,7 @@ If NULL, create FURI Pipe (only callbacks management, no data/mutex) Returns false if registry have not enough memory for creating. */ -bool furi_create(const char* name, void* value, size_t size); +bool furi_create_deprecated(const char* name, void* value, size_t size); /*! Opens existing FURI record by name. @@ -137,7 +137,7 @@ When appication has exited or record has closed, all handlers is unmuted. It may be useful for concurrently acces to resources like framebuffer or beeper. \param[in] no_mute if true, another applications cannot mute this handler. */ -FuriRecordSubscriber* furi_open( +FuriRecordSubscriber* furi_open_deprecated( const char* name, bool solo, bool no_mute, diff --git a/core/furi_ac.c b/core/furi_ac.c index f75254f3..ea0b6728 100644 --- a/core/furi_ac.c +++ b/core/furi_ac.c @@ -1,5 +1,4 @@ -#include "furi.h" -#include "cmsis_os.h" +#include "flipper.h" // TODO: this file contains printf, that not implemented on uC target diff --git a/core/log.c b/core/log.c index 3023528f..3088f425 100644 --- a/core/log.c +++ b/core/log.c @@ -5,7 +5,7 @@ #include #include "log.h" -#include "furi.h" +#include "flipper.h" #define PRINT_STR_SIZE 64 @@ -21,5 +21,5 @@ void fuprintf(FuriRecordSubscriber* f, const char* format, ...) { } FuriRecordSubscriber* get_default_log() { - return furi_open("tty", false, false, NULL, NULL, NULL); + return furi_open_deprecated("tty", false, false, NULL, NULL, NULL); } \ No newline at end of file diff --git a/core/log.h b/core/log.h index b8b80b01..78c3a100 100644 --- a/core/log.h +++ b/core/log.h @@ -1,6 +1,6 @@ #pragma once -#include "furi.h" +#include "flipper.h" FuriRecordSubscriber* get_default_log(); void fuprintf(FuriRecordSubscriber* f, const char* format, ...); diff --git a/core/tty_uart.c b/core/tty_uart.c index 17d74422..b748d9c1 100644 --- a/core/tty_uart.c +++ b/core/tty_uart.c @@ -1,6 +1,6 @@ #define _GNU_SOURCE #include -#include "furi.h" +#include "flipper.h" #include "main.h" extern UART_HandleTypeDef DEBUG_UART; @@ -12,7 +12,7 @@ void handle_uart_write(const void* data, size_t size, void* ctx) { static ssize_t stdout_write(void* _cookie, const char* buf, size_t n) { FuriRecordSubscriber* log = pvTaskGetThreadLocalStoragePointer(NULL, 0); if(log == NULL) { - log = furi_open("tty", false, false, NULL, NULL, NULL); + log = furi_open_deprecated("tty", false, false, NULL, NULL, NULL); if(log == NULL) { return -1; } @@ -33,11 +33,11 @@ static ssize_t stdout_write(void* _cookie, const char* buf, size_t n) { } bool register_tty_uart() { - if(!furi_create("tty", NULL, 0)) { + if(!furi_create_deprecated("tty", NULL, 0)) { return false; } - if(furi_open("tty", false, false, handle_uart_write, NULL, NULL) == NULL) { + if(furi_open_deprecated("tty", false, false, handle_uart_write, NULL, NULL) == NULL) { return false; } diff --git a/firmware/targets/local/Inc/cmsis_os.h b/firmware/targets/local/Inc/cmsis_os.h index 652f35f9..ebe04567 100644 --- a/firmware/targets/local/Inc/cmsis_os.h +++ b/firmware/targets/local/Inc/cmsis_os.h @@ -68,3 +68,31 @@ void* pvTaskGetThreadLocalStoragePointer(TaskHandle_t xTaskToQuery, BaseType_t x void vTaskSetThreadLocalStoragePointer(TaskHandle_t xTaskToSet, BaseType_t xIndex, void *pvValue); QueueHandle_t xQueueCreate(UBaseType_t uxQueueLength, UBaseType_t uxItemSize); + +typedef struct { + const char *name; ///< name of the mutex + uint32_t attr_bits; ///< attribute bits + void *cb_mem; ///< memory for control block + uint32_t cb_size; ///< size of provided memory for control block +} osMutexAttr_t; + +typedef SemaphoreHandle_t osMutexId_t; + +osMutexId_t osMutexNew(const osMutexAttr_t *attr); + +/// Status code values returned by CMSIS-RTOS functions. +typedef enum { + osOK = 0, ///< Operation completed successfully. + osError = -1, ///< Unspecified RTOS error: run-time error but no other error message fits. + osErrorTimeout = -2, ///< Operation not completed within the timeout period. + osErrorResource = -3, ///< Resource not available. + osErrorParameter = -4, ///< Parameter error. + osErrorNoMemory = -5, ///< System is out of memory: it was impossible to allocate or reserve memory for the operation. + osErrorISR = -6, ///< Not allowed in ISR context: the function cannot be called from interrupt service routines. + osStatusReserved = 0x7FFFFFFF ///< Prevents enum down-size compiler optimization. +} osStatus_t; + +osStatus_t osMutexAcquire (osMutexId_t mutex_id, uint32_t timeout); +osStatus_t osMutexRelease (osMutexId_t mutex_id); + +#define osWaitForever portMAX_DELAY diff --git a/firmware/targets/local/Inc/cmsis_os2.h b/firmware/targets/local/Inc/cmsis_os2.h new file mode 100644 index 00000000..8633b6bc --- /dev/null +++ b/firmware/targets/local/Inc/cmsis_os2.h @@ -0,0 +1 @@ +#include "cmsis_os.h" \ No newline at end of file diff --git a/firmware/targets/local/Src/lo_os.c b/firmware/targets/local/Src/lo_os.c index f8bdb66b..e3603990 100644 --- a/firmware/targets/local/Src/lo_os.c +++ b/firmware/targets/local/Src/lo_os.c @@ -230,3 +230,26 @@ void vTaskSetThreadLocalStoragePointer(TaskHandle_t xTaskToSet, BaseType_t xInde pthread_setspecific(tls_keys[xIndex], pvValue); } + +osMutexId_t osMutexNew(const osMutexAttr_t *attr) { + StaticSemaphore_t* pxMutexBuffer = malloc(sizeof(StaticSemaphore_t)); + xSemaphoreCreateMutexStatic(pxMutexBuffer); + + return (osMutexId_t)pxMutexBuffer; +} + +osStatus_t osMutexAcquire(osMutexId_t mutex_id, uint32_t timeout) { + if(xSemaphoreTake((SemaphoreHandle_t)mutex_id, (TickType_t)timeout) == pdTRUE) { + return osOK; + } else { + return osErrorTimeout; + } +} + +osStatus_t osMutexRelease (osMutexId_t mutex_id) { + if(xSemaphoreGive((SemaphoreHandle_t)mutex_id) == pdTRUE) { + return osOK; + } else { + return osError; + } +} diff --git a/lib/lib.mk b/lib/lib.mk index 505b10d3..25a8ec02 100644 --- a/lib/lib.mk +++ b/lib/lib.mk @@ -2,6 +2,10 @@ LIB_DIR = $(PROJECT_ROOT)/lib CFLAGS += -I$(LIB_DIR) +# Mlib containers +CFLAGS += -I$(LIB_DIR)/mlib + +# U8G2 display library U8G2_DIR = $(LIB_DIR)/u8g2 CFLAGS += -I$(U8G2_DIR) C_SOURCES += $(U8G2_DIR)/u8x8_d_st7565.c diff --git a/lib/mlib b/lib/mlib new file mode 160000 index 00000000..eb7556f8 --- /dev/null +++ b/lib/mlib @@ -0,0 +1 @@ +Subproject commit eb7556f88faf0bbfd6a4ae99a3d53dcbe2064b88 diff --git a/wiki/fw/Core-API.md b/wiki/fw/Core-API.md index f9f97d34..77bc70b3 100644 --- a/wiki/fw/Core-API.md +++ b/wiki/fw/Core-API.md @@ -1,13 +1,27 @@ -# Basic concepts: +# [Basic concepts](Basic-API) * ValueMutex -* PubSub, Publisher, Subscriber +* PubSub * ValueManager -* LayeredReducer +* ValueComposer -# HAL +# [HAL and devices](HAL-API) -We use [Zephyr HAL](https://docs.zephyrproject.org/latest/reference/peripherals/index.html). +* GPIO +* PWM +* ADC +* I2C + +* IR RX (unimplemented) +* Comparator RX (touch key and RFID 125 kHz RX) (unimplemented) + +# [SPI Devices](SPI-Devices-API.md) + +* Sub-GHz chip +* NFC +* SD card +* display +* external SPI # OS @@ -15,68 +29,20 @@ We use [CMSIS OS v2](https://www.keil.com/pack/doc/CMSIS_Dev/RTOS2/html/group__C # UI +* **[Input](Input-API)** -* **[Input](https://github.com/Flipper-Zero/flipperzero-firmware-community/wiki/API:Input)** +* **[Display](Display-API)** -* **[Display](https://github.com/Flipper-Zero/flipperzero-firmware-community/wiki/API:Display)** +* **[LED](LED-API)** -* **[LED](https://github.com/Flipper-Zero/flipperzero-firmware-community/wiki/API:LED)** +* **[Backlight](Backlight-API)** (unimplemented) -* **vibro** +# [Power](Power-API) -* **[Sound](https://github.com/Flipper-Zero/flipperzero-firmware-community/wiki/API:Sound)** +* batt voltage +* batt charge -* **backlight** - -# System - -## batt voltage - -## batt charge - -# CC1101 - -## SPI - -## IRQ - -# SD Card - -## SPI - -# NFC - -## SPI - -## IRQ - -# IR - -## TX LED - -## RX ADC - -# RFID 125 kHz - -## Carrier - -## Pull - -## Comparator RX (shared with touch key) - -# Touch key - -## Pull - -## Comparator RX (shared with RFID 125 kHz) - -# External GPIO - -# External SPI - -# External I2C - -# UART +# [UART](Serial-API) # USB diff --git a/wiki/fw/FURI.md b/wiki/fw/FURI.md index 54defd3a..d534b4d3 100644 --- a/wiki/fw/FURI.md +++ b/wiki/fw/FURI.md @@ -6,27 +6,7 @@ Flipper Universal Registry Implementation or FURI is important part of Flipper f # Application registry and control (FURIAC) -### Start and change application wrokflow -**`FuriApp* furiac_start(void(app*)(void*), char* name, void* param)`** - -simply starts application. It call `app` entrypoint with `param` passed as argument. Useful for daemon applications and pop-up. - - -**`FuriApp furiac_switch(void(app*)(void*), char* name, void* param)`** - -swtich to other application. FURI **stop current app**, call `app` entrypoint with `param` passed as argument and save current application entrypoint to `prev` field in current application registry. Useful for UI or "active" application. - -### Exit application - -**`void furiac_exit(void* param)`** - -stop current application (stop thread and clear application's stack), start application from `prev` entry in current application registry, cleanup current application registry. - - -**`bool furiac_kill(FuriApp app)`** - -stop specified `app` without returning to `prev` application. # Data exchange diff --git a/wiki/fw/api/API-Display.md b/wiki/fw/api/API-Display.md deleted file mode 100644 index ab737162..00000000 --- a/wiki/fw/api/API-Display.md +++ /dev/null @@ -1,61 +0,0 @@ -All display operations based on [u8g2](https://github.com/olikraus/u8g2) library. - -API available as struct, contains u8g2 functions, instance and fonts: - -```C -typedef struct { - ValueManager* display; /// ValueManager - void (*u8g2_SetFont)(u8g2_t *u8g2, const uint8_t *font); - void (*u8g2_SetDrawColor)(u8g2_t *u8g2, uint8_t color); - void (*u8g2_SetFontMode)(u8g2_t *u8g2, uint8_t is_transparent); - u8g2_uint_t (*u8g2_DrawStr)(u8g2_t *u8g2, u8g2_uint_t x, u8g2_uint_t y, const char *str); - - Fonts fonts; -} Display; - -typedef struct { - const uint8_t* u8g2_font_6x10_mf; -} Fonts; -``` - -First of all you can open display API instance by calling `open_display` - -```C -/// Get display instance and API -inline Display* open_display(const char* name) { - return (Display*)furi_open(name); -} -``` - -Default display name is `/dev/display`. - -For draw something to display you can get display instance pointer by calling `take_display`, do something and commit your changes by calling `commit_display`: - -```C -/// return pointer in case off success, NULL otherwise -inline u8g2_t* take_display(Display* api, uint32_t timeout) { - return (u8g2_t*)take_mutex(api->display->value, timeout); -} - -inline void commit_display(Display* api, u8g2_t* display) { - commit_valuemanager(api->display, display); -} -``` - -## Usage example - -```C -void u8g2_example(void* p) { - Display* display_api = open_display("/dev/display"); - if(display_api == NULL) return; // display not available, critical error - - u8g2_t* display = take_display(display_api); - if(display != NULL) { - display_api->u8g2_SetFont(display, display_api->fonts.u8g2_font_6x10_mf); - display_api->u8g2_SetDrawColor(display, 1); - display_api->u8g2_SetFontMode(display, 1); - display_api->u8g2_DrawStr(display, 2, 12, "hello world!"); - } - commit_display(display_api, display); -} -``` \ No newline at end of file diff --git a/wiki/fw/api/API-LED.md b/wiki/fw/api/API-LED.md deleted file mode 100644 index e0ddac87..00000000 --- a/wiki/fw/api/API-LED.md +++ /dev/null @@ -1,124 +0,0 @@ -LED state describes by struct: - -```C -typedef struct { - uint8_t red; - uint8_t green; - uint8_t blue; -} Rgb; -``` - -LED API provided by struct: - -```C -typedef struct { - LayeredReducer* source; /// every app add its layer to set value, LayeredReducer - Subscriber* updates; /// LED value changes Supscriber - ValueMutex* state; /// LED state, ValueMutex -} LedApi; -``` - -You can get API instance by calling `open_led`: - -```C -/// Add new layer to LED: -inline LedApi* open_led(const char* name) { - return (LedApi*)furi_open(name); -} -``` - -Default system led is `/dev/led`. - -Then add new layer to control LED by calling `add_led_layer`: - -```C -inline ValueManager* add_led_layer(Rgb* layer, uint8_t priority) { - ValueManager* manager = register_valuemanager((void*)layer); - if(manager == NULL) return NULL; - - if(!add_layered_reducer(manager, priority, layer_compose_default)) { - unregister_valuemanager(manager); - return NULL; - } - - return manager; -} -``` - -For change led you can get display instance pointer by calling `take_led`, do something and commit your changes by calling `commit_led`. Or you can call `write_led`: - -```C -/// return pointer in case off success, NULL otherwise -inline Rgb* take_led(ValueManager* led, uint32_t timeout) { - return (Rgb*)take_mutex(led->value, timeout); -} - -inline void commit_led(ValueManager* led, Rgb* value) { - commit_valuemanager(led, value); -} - -/// return true if success, false otherwise -inline bool write_led(ValueManager* led, Rgb* value, uint32_t timeout) { - return write_valuemanager(state, (void*)value, sizeof(Rgb), timeout); -} -``` - -To read current led state you should use `read_led` function: - -```C -/// return true if success, false otherwise -inline bool read_led(ValueManager* led, Rgb* value, uint32_t timeout) { - return read_mutex(led->value, (void*)value, sizeof(Rgb), timeout); -} -``` - -Also you can subscribe to led state changes: - -Use `subscribe_led_changes` to register your callback: - -```C -/// return true if success, false otherwise -inline bool subscribe_led_changes(Subscriber* updates, void(*cb)(Rgb*, void*), void* ctx) { - return subscribe_pubsub(events, void(*)(void*, void*)(cb), ctx); -} -``` - -## Usage example - -```C - -void handle_led_state(Rgb* rgb, void* _ctx) { - printf("led: #%02X%02X%02X\n", rgb->red, rgb->green, rgb->blue); -} - -void led_example(void* p) { - LedApi* led_api = open_display("/dev/led"); - if(led_api == NULL) return; // led not available, critical error - - // subscribe to led state updates - subscribe_led_changes(led_api->updates, handle_led_state, NULL); - - Rgb current_state; - if(read_led(led_api->state, ¤t_state, OsWaitForever)) { - printf( - "initial led: #%02X%02X%02X\n", - current_state->red, - current_state->green, - current_state->blue - ); - } - - // add layer to control led - ValueManager* led_manager = add_led_layer(¤t_state, UI_LAYER_APP); - - // write only blue by getting pointer - Rgb* rgb = take_led(led_manager, OsWaitForever); - if(rgb != NULL) { - rgb->blue = 0; - } - commit_led(led_manager, rgb); - - // write RGB value - write_led(led_manager, &(Rgb{.red = 0xFA, green = 0xCE, .blue = 0x8D}), OsWaitForever); -} -``` diff --git a/wiki/fw/api/API-Sound.md b/wiki/fw/api/API-Sound.md deleted file mode 100644 index 4cd528bb..00000000 --- a/wiki/fw/api/API-Sound.md +++ /dev/null @@ -1,122 +0,0 @@ -sound state describes by struct: - -```C -typedef struct { - float freq; /// frequency in Hz - float width; /// pulse witdh 0...1 -} Tone; -``` - -sound API provided by struct: - -```C -typedef struct { - LayeredReducer* source; /// every app add its layer to set value, LayeredReducer - Subscriber* updates; /// sound value changes Supscriber - ValueMutex* state; /// sound state, ValueMutex -} SoundApi; -``` - -You can get API instance by calling `open_sound`: - -```C -/// Add new layer to sound: -inline SoundApi* open_sound(const char* name) { - return (SoundApi*)furi_open(name); -} -``` - -Default system sound is `/dev/sound`. - -Then add new layer to control sound by calling `add_sound_layer`: - -```C -inline ValueManager* add_sound_layer(Tone* layer, uint8_t priority) { - ValueManager* manager = register_valuemanager((void*)layer); - if(manager == NULL) return NULL; - - if(!add_layered_reducer(manager, priority, layer_compose_default)) { - unregister_valuemanager(manager); - return NULL; - } - - return manager; -} -``` - -For change sound you can get display instance pointer by calling `take_sound`, do something and commit your changes by calling `commit_sound`. Or you can call `write_sound`: - -```C -/// return pointer in case off success, NULL otherwise -inline Tone* take_sound(ValueManager* sound, uint32_t timeout) { - return (Tone*)take_mutex(sound->value, timeout); -} - -inline void commit_sound(ValueManager* sound, Tone* value) { - commit_valuemanager(sound, value); -} - -/// return true if success, false otherwise -inline bool write_sound(ValueManager* sound, Tone* value, uint32_t timeout) { - return write_valuemanager(state, (void*)value, sizeof(Tone), timeout); -} -``` - -To read current sound state you should use `read_sound` function: - -```C -/// return true if success, false otherwise -inline bool read_sound(ValueManager* sound, Tone* value, uint32_t timeout) { - return read_mutex(sound->value, (void*)value, sizeof(Tone), timeout); -} -``` - -Also you can subscribe to sound state changes: - -Use `subscribe_sound_changes` to register your callback: - -```C -/// return true if success, false otherwise -inline bool subscribe_sound_changes(Subscriber* updates, void(*cb)(Tone*, void*), void* ctx) { - return subscribe_pubsub(events, void(*)(void*, void*)(cb), ctx); -} -``` - -## Usage example - -```C - -void handle_sound_state(Tone* tone, void* _ctx) { - printf("sound: %d Hz, %d %%\n", (uint16_t)tone->freq, (uint8_t)(tone->witdh * 100)); -} - -void sound_example(void* p) { - soundApi* sound_api = open_display("/dev/sound"); - if(sound_api == NULL) return; // sound not available, critical error - - // subscribe to sound state updates - subscribe_sound_changes(sound_api->updates, handle_sound_state, NULL); - - Tone current_state; - if(read_sound(sound_api->state, ¤t_state, OsWaitForever)) { - printf( - "sound: %d Hz, %d %%\n", - (uint16_t)current_state->freq, - (uint8_t)(current_state->witdh * 100) - ); - } - - // add layer to control sound - ValueManager* sound_manager = add_sound_layer(¤t_state, UI_LAYER_APP); - - // write only freq by getting pointer - Tone* tone = take_sound(sound_manager, OsWaitForever); - if(tone != NULL) { - tone->freq = 440; - } - commit_sound(sound_manager, tone); - - // write tone value - write_sound(sound_manager, &(Tone{.freq = 110., witdh = 0.5}), OsWaitForever); -} -``` diff --git a/wiki/fw/api/Backlight-API.md b/wiki/fw/api/Backlight-API.md new file mode 100644 index 00000000..b055699a --- /dev/null +++ b/wiki/fw/api/Backlight-API.md @@ -0,0 +1,100 @@ +Backlight state describes by `uint8_t level;` brightness level. + +LED API provided by struct: + +```C +typedef struct { + ValueComposer* composer; /// every app add its value to compose, + ValueManager* state; /// value state and changes +} BacklightApi; +``` + +You can get API instance by calling `open_backlight`: + +```C +/// Add new layer to LED: +inline BacklightApi* open_backlight(const char* name) { + return (BacklightApi*)furi_open(name); +} +``` + +Default system led is `/dev/backlight`. + +To read current backlight state you should use `read_backlight` function: + +```C +/// return true if success, false otherwise +inline bool read_backlight(BacklightApi* api, uint8_t* value, uint32_t timeout) { + return read_mutex(api->state->value, (void*)value, sizeof(uint8_t), timeout); +} +``` + +Also you can subscribe to backlight state changes: + +Use `subscribe_backlight_changes` to register your callback: + +```C +/// return true if success, false otherwise +inline bool subscribe_backlight_changes(LedApi* led, void(*cb)(uint8_t*, void*), void* ctx) { + return subscribe_pubsub(led->state->pubsub, void(*)(void*, void*)(cb), ctx); +} +``` + +Userspace helpers + +```C +typedef struct { + uint8_t value; + ValueMutex value_mutex; + ValueComposerHandle* composer_handle; +} Backlight; + +inline bool init_backlight_composer(Backlight* backlight, BacklightApi* api, uint32_t layer) { + if(!init_mutex(&backlight->value_mutex, (void*)&backlight->value, sizeof(uint8_t))) { + return false; + } + backlight->composer_handle = add_compose_layer( + api->composer, COPY_COMPOSE, &backlight->value_mutex, layer + ); // just copy backlight state on update + + return backlight->composer_handle != NULL; +} + +inline void write_backlight(Backlight* backlight, uint8_t value) { + write_mutex(&backlight->value_mutex, (void*)&value, sizeof(uint8_t), OsWaitForever); + request_compose(backlight->composer_handle); +} +``` + + +## Usage example + +```C + +void handle_backlight_state(uint8_t* value, void* _ctx) { + printf("backlight: %d %%\n", (*value * 100) / 256); +} + +void backlight_example(void* p) { + BacklightApi* backlight_api = open_backlight("/dev/backlight"); + if(backlight_api == NULL) return; // backlight not available, critical error + + // subscribe to led state updates + subscribe_backlight_changes(backlight_api, handle_backlight_state, NULL); + // get current backlight value + uint8_t backlight_value; + if(read_backlight(backlight_api, &backlight_value, OsWaitForever)) { + printf( + "initial backlight: %d %%\n", + backlight_value * 100 / 256 + ); + } + + // create compose to control led + Backlight backlight; + if(!init_led_composer(&backlight, backlight_api, UiLayerBelowNotify)) return; + + // write RGB value + write_backlight(&backlight, 127); +} +``` diff --git a/wiki/fw/api/Basic-API.md b/wiki/fw/api/Basic-API.md new file mode 100644 index 00000000..588dcf56 --- /dev/null +++ b/wiki/fw/api/Basic-API.md @@ -0,0 +1,402 @@ +# Flipper universal registry implementation (FURI) + +Create record. + +```C +// creates new record in registry and store pointer into it +bool furi_create(const char* name, void* ptr); +``` + +Open record. + +```C +// get stored pointer by its name +void* furi_open(const char* name); +``` + +# Flipper Application control (flapp) + +## (in progress. Old verison) + +**`FlappHandler* flapp_start(void(app*)(void*), char* name, void* param)`** + +simply starts application. It call `app` entrypoint with `param` passed as argument. Useful for daemon applications and pop-up. + + +**`FlappHandler* flapp_switch(void(app*)(void*), char* name, void* param)`** + +swtich to other application. System **stop current app**, call `app` entrypoint with `param` passed as argument and save current application entrypoint to `prev` field in current application registry. Useful for UI or "active" application. + +### Exit application + +**`void flapp_exit(void* param)`** + +stop current application (stop thread and clear application's stack), start application from `prev` entry in current application registry, cleanup current application registry. + + +**`bool flapp_kill(FlappHandler* app)`** + +stop specified `app` without returning to `prev` application. + +**`void flapp_ready()`** + +If case one app depend on other, notify that app is ready. + +## Requirements + +* start daemon app +* kill app +* start child thread (kill when parent app was killed) +* switch between UI apps + +**`bool flapp_on_exit(void(cb*)(void*), void* ctx);`** + +Register on-exit callback. It called before app will be killed. Not recommended to use in user scenario, only for system purpose (unregister callbacks, release mutexes, etc.) + +# ValueMutex + +The most simple concept is ValueMutex. It is wrapper around mutex and value pointer. You can take and give mutex to work with value and read and write value. + +```C +typedef struct { + void* value; + size_t size; + osMutex mutex; + + osMutexDescriptor __static // some internals; +} ValueMutex; +``` + +Create ValueMutex. Create instance of ValueMutex and call `init_mutex`. + +```C +bool init_mutex(ValueMutex* valuemutex, void* value, size_t size) { + valuemutex->mutex = osMutexCreateStatic(valuemutex->__static); + if(valuemutex->mutex == NULL) return false; + + valuemutex->value = value; + valuemutex->size = size; + + return true; +} +``` + +For work with data stored in mutex you should call `acquire_mutex`. It return pointer to data if success, NULL otherwise. + +You must release mutex after end of work with data. Call `release_mutex` and pass ValueData instance and pointer to data. + +```C +void* acquire_mutex(ValueMutex* valuemutex, uint32_t timeout) { + if(osMutexTake(valuemutex->mutex, timeout) == osOk) { + return valuemutex->value; + } else { + return NULL; + } +} + +// infinitly wait for mutex +inline static void* acquire_mutex_block(ValueMutex* valuemutex) { + return acquire_mutex(valuemutex, OsWaitForever); +} + +bool release_mutex(ValueMutex* valuemutex, void* value) { + if(value != valuemutex->value) return false; + + if(!osMutexGive(valuemutex->mutex)) return false; + + return true; +} +``` +Instead of take-access-give sequence you can use `read_mutex` and `write_mutex` functions. Both functions return true in case of success, false otherwise. + +```C +bool read_mutex(ValueMutex* valuemutex, void* data, size_t len, uint32_t timeout) { + void* value = acquire_mutex(valuemutex, timeout); + if(value == NULL || len > valuemutex->size) return false; + memcpy(data, value, len > 0 ? len : valuemutex->size): + if(!release_mutex(valuemutex, value)) return false; + + return true; +} + +inline static bool read_mutex_block(ValueMutex* valuemutex, void* data, size_t len) { + return read_mutex(valuemutex, data, len, OsWaitForever); +} + +bool write_mutex(ValueMutex* valuemutex, void* data, size_t len, uint32_t timeout) { + void* value = acquire_mutex(valuemutex, timeout); + if(value == NULL || len > valuemutex->size) return false; + memcpy(value, data, len > 0 ? len : valuemutex->size): + if(!release_mutex(valuemutex, value)) return false; + + return true; +} + +inline static bool write_mutex_block(ValueMutex* valuemutex, void* data, size_t len) { + return write_mutex(valuemutex, data, len, OsWaitForever); +} +``` + +## Usage example + +```C +/* +MANIFEST +name="example-provider-app" +stack=128 +*/ +void provider_app(void* _p) { + // create record with mutex + uint32_t example_value = 0; + ValueMutex example_mutex; + if(!init_mutex(&example_mutex, (void*)&example_value, sizeof(uint32_t))) { + printf("critical error\n"); + flapp_exit(NULL); + } + + if(furi_create("provider/example", (void*)&example_mutex)) { + printf("critical error\n"); + flapp_exit(NULL); + } + + // we are ready to provide record to other apps + flapp_ready(); + + // get value and increment it + while(1) { + uint32_t* value = acquire_mutex(&example_mutex, OsWaitForever); + if(value != NULL) { + value++; + } + release_mutex(&example_mutex, value); + + osDelay(100); + } +} + +/* +MANIFEST +name="example-consumer-app" +stack=128 +require="example-provider-app" +*/ +void consumer_app(void* _p) { + // this app run after flapp_ready call in all requirements app + + // open mutex value + ValueMutex* counter_mutex = furi_open("provider/example"); + if(counter_mutex == NULL) { + printf("critical error\n"); + flapp_exit(NULL); + } + + // continously read value every 1s + uint32_t counter; + while(1) { + if(read_mutex(counter_mutex, &counter, sizeof(counter), OsWaitForever)) { + printf("counter value: %d\n", counter); + } + + osDelay(1000); + } +} +``` + + +# PubSub + +PubSub allows users to subscribe on notifies and notify subscribers. Notifier side can pass `void*` arg to subscriber callback, and also subscriber can set `void*` context pointer that pass into callback (you can see callback signature below). + +```C +typedef void(PubSubCallback*)(void*, void*); + +typedef struct { + PubSubCallback cb; + void* ctx; +} PubSubItem; + +typedef struct { + PubSub* self; + PubSubItem* item; +} PubSubId; + +typedef struct { + PubSubItem items[NUM_OF_CALLBACKS]; + PubSubId ids[NUM_OF_CALLBACKS]; ///< permanent links to item + size_t count; ///< count of callbacks +} PubSub; +``` + +To create PubSub you should create PubSub instance and call `init_pubsub`. + +```C +void init_pubsub(PubSub* pubsub) { + pubsub->count = 0; + + for(size_t i = 0; i < NUM_OF_CALLBACKS; i++) { + pubsub->items[i]. + } +} +``` + +Use `subscribe_pubsub` to register your callback. + +```C +// TODO add mutex to reconfigurate PubSub +PubSubId* subscribe_pubsub(PubSub* pubsub, PubSubCallback cb, void* ctx) { + if(pubsub->count >= NUM_OF_CALLBACKS) return NULL; + + pubsub->count++; + PubSubItem* current = pubsub->items[pubsub->count]; + + current->cb = cb; + currrnt->ctx = ctx; + + pubsub->ids[pubsub->count].self = pubsub; + pubsub->ids[pubsub->count].item = current; + + flapp_on_exit(unsubscribe_pubsub, &(pubsub->ids[pubsub->count])); + + return current; +} +``` + +Use `unsubscribe_pubsub` to unregister callback. + +```C +void unsubscribe_pubsub(PubSubId* pubsub_id) { + // TODO: add, and rearrange all items to keep subscribers item continuous + // TODO: keep ids link actual + // TODO: also add mutex on every pubsub changes + + // trivial implementation for NUM_OF_CALLBACKS = 1 + if(NUM_OF_CALLBACKS != 1) return; + + if(pubsub_id != NULL || pubsub_id->self != NULL || pubsub_id->item != NULL) return; + + pubsub_id->self->count = 0; + pubsub_id->item = NULL; +} + +``` + +Use `notify_pubsub` to notify subscribers. + +```C +void notify_pubsub(PubSub* pubsub, void* arg) { + // iterate over subscribers + for(size_t i = 0; i < pubsub->count; i++) { + pubsub->items[i]->cb(arg, pubsub->items[i]->ctx); + } +} +``` + +## Usage example + +```C +/* +MANIFEST +name="test" +stack=128 +*/ + +void example_pubsub_handler(void* arg, void* ctx) { + printf("get %d from %s\n", *(uint32_t*)arg, (const char*)ctx); +} + +void pubsub_test() { + const char* app_name = "test app"; + + PubSub example_pubsub; + init_pubsub(&example_pubsub); + + if(!subscribe_pubsub(&example_pubsub, example_pubsub_handler, (void*)app_name)) { + printf("critical error\n"); + flapp_exit(NULL); + } + + uint32_t counter = 0; + while(1) { + notify_pubsub(&example_pubsub, (void*)&counter); + counter++; + + osDelay(100); + } +} +``` + +# ValueComposer + +```C +typedef void(ValueComposerCallback)(void* ctx, void* state); + +void COPY_COMPOSE(void* ctx, void* state) { + read_mutex((ValueMutex*)ctx, state, 0); +} + +typedef enum { + UiLayerBelowNotify + UiLayerNotify, + UiLayerAboveNotify +} UiLayer; +``` + +```C +ValueComposerHandle* add_compose_layer( + ValueComposer* composer, ValueComposerCallback cb, void* ctx, uint32_t layer +); +``` + +```C +bool remove_compose_layer(ValueComposerHandle* handle); +``` + +```C +void request_compose(ValueComposerHandle* handle); +``` + +See [LED](LED-API) or [Display](Display-API) API for examples. + +# ValueManager + +More complicated concept is ValueManager. It is like ValueMutex, but user can subscribe to value updates. + +```C +typedef struct { + ValueMutex value; + PubSub pubsub; +} ValueManager; +``` + +First of all you can use value and pubsub part as showing above: aquire/release mutex, read value, subscribe/unsubscribe pubsub. There are two specific methods for ValueManager: + +`write_managed` acquire value, changes it and send notify with current value. + +```C +bool write_managed(ValueManager* managed, void* data, size_t len, uint32_t timeout) { + void* value = acquire_mutex(managed->mutex, timeout); + if(value == NULL) return false; + + memcpy(value, data, len): + + notify_pubsub(&managed->pubsub, value); + + if(!release_mutex(managed->mutex, value)) return false; + + return true; +} +``` + +`commit_managed` works as `release_mutex` but send notify with current value. + +```C +bool commit_managed(ValueManager* managed, void* value) { + if(value != managed->mutex->value) return false; + + notify_pubsub(&managed->pubsub, value); + + if(!osMutexGive(managed->mutex)) return false; + + return true; +} +``` diff --git a/wiki/fw/api/Display-API.md b/wiki/fw/api/Display-API.md new file mode 100644 index 00000000..146f464d --- /dev/null +++ b/wiki/fw/api/Display-API.md @@ -0,0 +1,68 @@ +All display operations based on [u8g2](https://github.com/olikraus/u8g2) library. + +API available as `ValueComposer`. + +Driver call render callback and pass API contains u8g2 functions, instance and fonts: + +```C +typedef struct { + u8g2_t* display; + + void (*u8g2_SetFont)(u8g2_t *u8g2, const uint8_t *font); + void (*u8g2_SetDrawColor)(u8g2_t *u8g2, uint8_t color); + void (*u8g2_SetFontMode)(u8g2_t *u8g2, uint8_t is_transparent); + u8g2_uint_t (*u8g2_DrawStr)(u8g2_t *u8g2, u8g2_uint_t x, u8g2_uint_t y, const char *str); + + Fonts fonts; +} DisplayApi; + +typedef struct { + const uint8_t* u8g2_font_6x10_mf; +} Fonts; +``` + +First of all you can open display API instance by calling `open_display` + +```C +/// Get display instance and API +inline Display* open_display(const char* name) { + return (Display*)furi_open(name); +} +``` + +Default display name is `/dev/display`. + +For draw something to display you need to register new layer in display composer: + +```C +typedef void (RenderCallback*)(void* ctx, DisplayApi* api); + +inline ValueComposerHandle* init_display_composer( + Display* api, RenderCallback render, void* ctx, uint32_t layer) { + return add_compose_layer(api->composer, (ValueComposerCallback)render, ctx, layer); +} +``` + +And then call `request_compose` every time you need to redraw your image. + +## Usage example + +```C + +void example_render(void* ctx, DisplayApi* api) { + api->u8g2_SetFont(api->display, display_api->fonts.u8g2_font_6x10_mf); + api->u8g2_SetDrawColor(api->display, 1); + api->u8g2_SetFontMode(api->display, 1); + api->u8g2_DrawStr(api->display, 2, 12, (char*)ctx); // ctx contains some static text +} + +void u8g2_example(void* p) { + Display* display_api = open_display("/dev/display"); + if(display_api == NULL) return; // display not available, critical error + + ValueComposerHandle display_handler = init_display_composer( + display_api, example_render, (void*)"Hello world", UiLayerBelowNotify); + + request_compose(display_handler); +} +``` \ No newline at end of file diff --git a/wiki/fw/api/HAL-API.md b/wiki/fw/api/HAL-API.md new file mode 100644 index 00000000..7575816f --- /dev/null +++ b/wiki/fw/api/HAL-API.md @@ -0,0 +1,158 @@ +# GPIO + +GPIO defined as struct `GpioPin`. + +GPIO functions: + +```C +// Init GPIO +void gpio_init(GpioPin* gpio, GpioMode mode); + +typedef enum { GpioModeInput, GpioModeOutput, GpioModeOpenDrain } GpioMode; + +// write value to GPIO +void gpio_write(GpioPin* gpio, bool state); + +// read value from GPIO, f = LOW, t = HIGH +bool gpio_read(GpioPin* gpio); +``` + +When application is exited, system place pin to Z-state by calling `gpio_disable`. + +```C +// put GPIO to Z-state (used for restore pin state on app exit) +void gpio_disable(ValueMutex* gpio_mutex) { + GpioPin* gpio = acquire_mutex(gpio_mutex, 0); + gpio_init(gpio, GpioModeInput); + release_mutex(gpio_mutex, gpio); +} +``` + +Available GPIO stored in FURI as `ValueMutex`. + +```C +inline static ValueMutex* open_gpio_mutex(const char* name) { + ValueMutex* gpio_mutex = (ValueMutex*)furi_open(name); + if(gpio_mutex != NULL) flapp_on_exit(gpio_disable, gpio_mutex); + + return gpio_mutex; +} + +// helper +inline static GpioPin* open_gpio(const char* name) { + ValueMutex* gpio_mutex = open_gpio(name); + return (GpioPin*)acquire_mutex(gpio_mutex, 0); +} +``` + +## Available GPIO (target F2) + +* PA4 +* PA5 +* PA6 +* PA7 +* PB2 +* PC3 +* PC0 +* PC1 +* PB6 +* PB7 +* PA13 +* PA14 +* RFID_PULL +* IR_TX +* IBUTTON +* VIBRO + +## Usage example + +```C +void gpio_example() { + GpioPin* pin = open_gpio("PB6"); + + if(pin == NULL) { + printf("pin not available\n"); + return; + } + + gpio_init(pin, GpioModeOutput); + + while(1) { + gpio_write(pin, true); + delay(100); + gpio_write(pin, false); + delay(100); + } +} +``` + +# PWM + +PWM defined as `PwmPin`. To set PWM channel: + +```C +void pwm_set(PwmPin* pwm, float value, float freq); +``` + +When application is exited, system disable pwm by calling `pwm_disable`. + +```C +// put GPIO to Z-state (used for restore pin state on app exit) +void pwm_disable(ValueMutex* pwm_mutex) { + PwmPin* pwm = acquire_mutex(pwm_mutex, 0); + pwm_set(pwm, 0., 0.); + release_mutex(pwm_mutex, pwm); +} +``` + +Available PWM stored in FURI as `ValueMutex`. + +```C +inline static ValueMutex* open_pwm_mutex(const char* name) { + ValueMutex* pwm_mutex = (ValueMutex*)furi_open(name); + if(pwm_mutex != NULL) flapp_on_exit(pwm_disable, pwm_mutex); + + return pwm_mutex; +} + +// helper +inline static PwmPin* open_pwm(const char* name) { + ValueMutex* pwm_mutex = open_gpio(name); + return (PwmPin*)acquire_mutex(pwm_mutex, 0); +} +``` + +## Available PWM (target F2) + +* SPEAKER +* RFID_OUT + +## Usage example + +```C +void sound_example() { + PwmPin* speaker = open_pwm("SPEAKER"); + + if(speaker == NULL) { + printf("speaker not available\n"); + return; + } + + while(1) { + pwm_set(speaker, 1000., 0.1); + delay(2); + pwm_set(speaker, 110., 0.5); + delay(198); + pwm_set(speaker, 330., 0.5); + delay(200); + } +} +``` + +# ADC + +Coming soon... + +# I2C + +Coming soon... diff --git a/wiki/fw/api/API-Input.md b/wiki/fw/api/Input-API.md similarity index 90% rename from wiki/fw/api/API-Input.md rename to wiki/fw/api/Input-API.md index 0fcea327..79789cbb 100644 --- a/wiki/fw/api/API-Input.md +++ b/wiki/fw/api/Input-API.md @@ -13,7 +13,7 @@ You can get API instance by calling `open_input`: ```C /// Get input struct inline Input* open_input(const char* name) { - return furi_open(name); + return (Input*)furi_open(name); } ``` @@ -37,8 +37,8 @@ To read buttons state you should use `read_state` function: ```C /// read current state of all buttons. Return true if success, false otherwise -inline bool read_state(ValueMutex* state, InputState* value, uint32_t timeout) { - return read_mutex(state, (void*)value, sizeof(InputState), timeout); +inline bool read_state(Input* api, InputState* value, uint32_t timeout) { + return read_mutex(api->state, (void*)value, sizeof(InputState), timeout); } ``` @@ -94,7 +94,7 @@ void input_example(void* p) { // blocking way InputState state; while(1) { - if(read_state(input->state, &state, OsWaitForever)) { + if(read_state(input, &state, OsWaitForever)) { if(state.up) { printf("up is pressed"); delay(1000); diff --git a/wiki/fw/api/LED-API.md b/wiki/fw/api/LED-API.md new file mode 100644 index 00000000..d09a7d74 --- /dev/null +++ b/wiki/fw/api/LED-API.md @@ -0,0 +1,110 @@ +LED state describes by struct: + +```C +typedef struct { + uint8_t red; + uint8_t green; + uint8_t blue; +} Rgb; +``` + +LED API provided by struct: + +```C +typedef struct { + ValueComposer* composer; /// every app add its value to compose, + ValueManager* state; /// LED value state and changes +} LedApi; +``` + +You can get API instance by calling `open_led`: + +```C +/// Add new layer to LED: +inline LedApi* open_led(const char* name) { + return (LedApi*)furi_open(name); +} +``` + +Default system led is `/dev/led`. + +To read current led state you should use `read_led` function: + +```C +/// return true if success, false otherwise +inline bool read_led(LedApi* led, Rgb* value, uint32_t timeout) { + return read_mutex(led->state->value, (void*)value, sizeof(Rgb), timeout); +} +``` + +Also you can subscribe to led state changes: + +Use `subscribe_led_changes` to register your callback: + +```C +/// return true if success, false otherwise +inline bool subscribe_led_changes(LedApi* led, void(*cb)(Rgb*, void*), void* ctx) { + return subscribe_pubsub(led->state->pubsub, void(*)(void*, void*)(cb), ctx); +} +``` + +Userspace helpers + +```C +typedef struct { + Rgb value; + ValueMutex value_mutex; + ValueComposerHandle* composer_handle; +} SystemLed; + +inline bool init_led_composer(SystemLed* led, LedApi* api, uint32_t layer) { + if(!init_mutex(&led->value_mutex, (void*)&led->value, sizeof(Rgb))) { + return false; + } + led->composer_handle = add_compose_layer( + api->composer, COPY_COMPOSE, &led->value_mutex, layer + ); // just copy led state on update + + return led->composer_handle != NULL; +} + +inline void write_led(SystemLed* led, Rgb* value) { + write_mutex(&led->value_mutex, (void*)value, sizeof(Rgb), OsWaitForever); + request_compose(led->composer_handle); +} +``` + + +## Usage example + +```C + +void handle_led_state(Rgb* rgb, void* _ctx) { + printf("led: #%02X%02X%02X\n", rgb->red, rgb->green, rgb->blue); +} + +void led_example(void* p) { + LedApi* led_api = open_led("/dev/led"); + if(led_api == NULL) return; // led not available, critical error + + // subscribe to led state updates + subscribe_led_changes(led_api, handle_led_state, NULL); + // get current led value + Rgb led_value; + if(read_led(led_api, &led_value, OsWaitForever)) { + printf( + "initial led: #%02X%02X%02X\n", + led_value->red, + led_value->green, + led_value->blue + ); + } + + // create compose to control led + SystemLed system_led; + if(!init_led_composer(&system_led, led_api, UiLayerBelowNotify)) return; + + // write RGB value + write_led(&system_led, &(Rgb{.red = 0xFA, green = 0xCE, .blue = 0x8D})); +} +``` diff --git a/wiki/fw/api/SPI-Devices-API.md b/wiki/fw/api/SPI-Devices-API.md new file mode 100644 index 00000000..6f7344f9 --- /dev/null +++ b/wiki/fw/api/SPI-Devices-API.md @@ -0,0 +1,130 @@ +# SPI + +HAL struct `SPI_HandleTypeDef*` used for handling SPI info. + +For transmit/receive data use `spi_xfer` function: + +```C +bool spi_xfer( + SPI_HandleTypeDef* spi, + uint8_t* tx_data, uint8_t* rx_data, size_t len, + PubSubCallback cb, void* ctx); +``` + +* `tx_data` and `rx_data` size must be equal (and equal `len`) +* `cb` called after spi operation is completed, `(NULL, ctx)` passed to callback. + +Blocking verison: + +```C +inline static bool spi_xfer_block(SPI_HandleTypeDef* spi, uint8_t* tx_data, uint8_t* rx_data, size_t len) { + semaphoreInfo s; + osSemaphore block = createSemaphoreStatic(s); + if(!spi_xfer(spi, tx_data, rx_data, len, RELEASE_SEMAPHORE, (void*)block)) { + osReleaseSemaphore(block); + return false; + } + osWaitSemaphore(block); + return false; +} +``` + +## SPI Bus + +Common implementation of SPI bus: serial interface + CS pin + +```C +typedef struct { + GpioPin* cs; ///< CS pin + ValueMutex* spi; ///< +} SpiBus; +``` + +## SPI device + +For dedicated work with one device there is `SpiDevice` entity. It contains ValueMutex around SpiBus: after you acquire device you can acquire spi to work with it (don't forget SPI bus is shared around many device, release it after every transaction as quick as possible). + +```C +typedef struct { + ValueMutex* bus; ///< +} SpiDevice; +``` + +## SPI IRQ device + +Many devices (like CC1101 and NFC) present as SPI bus and IRQ line. For work with it there is special entity `SpiIrqDevice`. Use `subscribe_pubsub` for subscribinq to irq events. + +```C +typedef struct { + ValueMutex* bus; ///< + PubSub* irq; +} SpiIrqDevice; +``` + +## Display device + +Special implementation of SPI bus: serial interface + CS, Res, D/I lines. + +```C +typedef struct { + GpioPin* cs; ///< CS pin + GpioPin* res; ///< reset pin + GpioPin* di; ///< D/I pin + ValueMutex* spi; ///< +} DisplayBus; + +```C +typedef struct { + ValueMutex* bus; ///< +} DisplayDevice; +``` + +# SPI devices (F2) + +* `/dev/sdcard` - SD card SPI, `SpiDevice` +* `/dev/cc1101_bus` - Sub-GHz radio (CC1101), `SpiIrqDevice` +* `/dev/nfc` - NFC (ST25R3916), `SpiIrqDevice` +* `/dev/display` - `DisplayDevice` +* `/dev/spiext` - External SPI (warning! Lock PA4, PA5, PA6, PA7) + +### Application example + +```C +// Be careful, this function called from IRQ context +void handle_irq(void* _arg, void* _ctx) { +} + +void cc1101_example() { + SpiIrqDevice* cc1101_device = open_input("/dev/cc1101_bus"); + if(cc1101_device == NULL) return; // bus not available, critical error + + subscribe_pubsub(cc1101_device->irq, handle_irq, NULL); + + { + // acquire device as device bus + SpiBus* spi_bus = acquire_mutex(cc1101_device->bus, 0); + if(spi_bus == NULL) { + printf("Device busy\n"); + // wait for device + spi_bus = acquire_mutex_block(cc1101_device->bus); + } + + // make transaction + uint8_t request[4] = {0xDE, 0xAD, 0xBE, 0xEF}; + uint8_t response[4]; + + { + SPI_HandleTypeDef* spi = acquire_mutex_block(spi_bus->spi); + + gpio_write(spi_bus->cs, false); + spi_xfer_block(spi, request, response, 4); + gpio_write(spi_bus->cs, true); + + release_mutex(cc1101_device->spi, spi); + } + + // release device (device bus) + release_mutex(cc1101_device->bus, spi_bus); + } +} +``` \ No newline at end of file