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