UART write example (#53)
* Rename test functions * rewrite furi API, segfault * make fixes in FURI, log through FURI * add uart write example blank * implement fuprintf instead of fopencookie * add gif, blank page * UART write example description Co-authored-by: Vadim Kaushan <admin@disasm.info>
This commit is contained in:
		
							parent
							
								
									5094623d04
								
							
						
					
					
						commit
						4dc82b68d1
					
				
							
								
								
									
										33
									
								
								applications/examples/uart_write.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								applications/examples/uart_write.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,33 @@ | ||||
| #include "flipper.h" | ||||
| #include <string.h> | ||||
| #include "log.h" | ||||
| 
 | ||||
| void application_uart_write(void* p) { | ||||
|     // Red led for showing progress
 | ||||
|     GpioPin led = {.pin = GPIO_PIN_8, .port = GPIOA}; | ||||
|     pinMode(led, GpioModeOpenDrain); | ||||
| 
 | ||||
|     // get_default_log open "tty" record
 | ||||
|     FuriRecordSubscriber* log = get_default_log(); | ||||
| 
 | ||||
|     // create buffer
 | ||||
|     const char test_string[] = "test\n"; | ||||
|     furi_write(log, test_string, strlen(test_string)); | ||||
| 
 | ||||
|     // for example, create counter and show its value
 | ||||
|     uint8_t counter = 0; | ||||
| 
 | ||||
|     while(1) { | ||||
|         // continously write it to UART
 | ||||
|         fuprintf(log, "counter: %d\n", counter); | ||||
|         counter++; | ||||
| 
 | ||||
|         // flash at every send
 | ||||
|         digitalWrite(led, LOW); | ||||
|         delay(50); | ||||
|         digitalWrite(led, HIGH); | ||||
| 
 | ||||
|         // delay with overall perion of 1s
 | ||||
|         delay(950); | ||||
|     } | ||||
| } | ||||
| @ -12,6 +12,7 @@ void flipper_test_app(void* p); | ||||
| #endif | ||||
| 
 | ||||
| void application_blink(void* p); | ||||
| void application_uart_write(void* p); | ||||
| 
 | ||||
| const FlipperStartupApp FLIPPER_STARTUP[] = { | ||||
|     #ifdef TEST | ||||
| @ -21,4 +22,7 @@ const FlipperStartupApp FLIPPER_STARTUP[] = { | ||||
|     #ifdef EXAMPLE_BLINK | ||||
|     {.app = application_blink, .name = "blink"}, | ||||
|     #endif | ||||
|     #ifdef EXAMPLE_UART_WRITE | ||||
|     {.app = application_uart_write, .name = "uart write"}, | ||||
|     #endif | ||||
| }; | ||||
| @ -1,7 +1,7 @@ | ||||
| #include <stdio.h> | ||||
| #include <string.h> | ||||
| #include "flipper.h" | ||||
| #include "debug.h" | ||||
| #include "log.h" | ||||
| 
 | ||||
| /*
 | ||||
| TEST: pipe record | ||||
| @ -22,48 +22,48 @@ void pipe_record_cb(const void* value, size_t size) { | ||||
|     pipe_record_value = *((uint8_t*)value); | ||||
| } | ||||
| 
 | ||||
| bool test_furi_pipe_record(FILE* debug_uart) { | ||||
| bool test_furi_pipe_record(FuriRecordSubscriber* log) { | ||||
|     // 1. create pipe record
 | ||||
|     if(!furi_create("test/pipe", NULL, 0)) { | ||||
|         fprintf(debug_uart, "cannot create record\n"); | ||||
|         fuprintf(log, "cannot create record\n"); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     // 2. Open/subscribe to it 
 | ||||
|     FuriRecordHandler pipe_record = furi_open( | ||||
|     FuriRecordSubscriber* pipe_record = furi_open( | ||||
|         "test/pipe", false, false, pipe_record_cb, NULL | ||||
|     ); | ||||
|     if(pipe_record.record == NULL) { | ||||
|         fprintf(debug_uart, "cannot open record\n"); | ||||
|     if(pipe_record == NULL) { | ||||
|         fuprintf(log, "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))) { | ||||
|         fprintf(debug_uart, "cannot write to record\n"); | ||||
|     if(!furi_write(pipe_record, &WRITE_VALUE, sizeof(uint8_t))) { | ||||
|         fuprintf(log, "cannot write to record\n"); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     // 4. check that subscriber get data
 | ||||
|     if(pipe_record_value != WRITE_VALUE) { | ||||
|         fprintf(debug_uart, "wrong value (get %d, write %d)\n", pipe_record_value, WRITE_VALUE); | ||||
|         fuprintf(log, "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))) { | ||||
|         fprintf(debug_uart, "reading from pipe record not allowed\n"); | ||||
|     if(furi_read(pipe_record, &read_value, sizeof(uint8_t))) { | ||||
|         fuprintf(log, "reading from pipe record not allowed\n"); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     // 6. close record
 | ||||
|     furi_close(&pipe_record); | ||||
|     furi_close(pipe_record); | ||||
| 
 | ||||
|     // 7. try to write, get error
 | ||||
|     if(furi_write(&pipe_record, &WRITE_VALUE, sizeof(uint8_t))) { | ||||
|         fprintf(debug_uart, "writing to closed record not allowed\n"); | ||||
|     if(furi_write(pipe_record, &WRITE_VALUE, sizeof(uint8_t))) { | ||||
|         fuprintf(log, "writing to closed record not allowed\n"); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
| @ -88,56 +88,56 @@ void holding_record_cb(const void* value, size_t size) { | ||||
|     holding_record_value = *((uint8_t*)value); | ||||
| } | ||||
| 
 | ||||
| bool test_furi_holding_data(FILE* debug_uart) { | ||||
| bool test_furi_holding_data(FuriRecordSubscriber* log) { | ||||
|     // 1. Create holding record
 | ||||
|     uint8_t holder = 0; | ||||
|     if(!furi_create("test/holding", (void*)&holder, sizeof(holder))) { | ||||
|         fprintf(debug_uart, "cannot create record\n"); | ||||
|         fuprintf(log, "cannot create record\n"); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     // 2. Open/Subscribe on it
 | ||||
|     FuriRecordHandler holding_record = furi_open( | ||||
|     FuriRecordSubscriber* holding_record = furi_open( | ||||
|         "test/holding", false, false, holding_record_cb, NULL | ||||
|     ); | ||||
|     if(holding_record.record == NULL) { | ||||
|         fprintf(debug_uart, "cannot open record\n"); | ||||
|     if(holding_record == NULL) { | ||||
|         fuprintf(log, "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))) { | ||||
|         fprintf(debug_uart, "cannot write to record\n"); | ||||
|     if(!furi_write(holding_record, &WRITE_VALUE, sizeof(uint8_t))) { | ||||
|         fuprintf(log, "cannot write to record\n"); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     // 4. check that subscriber get data
 | ||||
|     if(holding_record_value != WRITE_VALUE) { | ||||
|         fprintf(debug_uart, "wrong sub value (get %d, write %d)\n", holding_record_value, WRITE_VALUE); | ||||
|         fuprintf(log, "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))) { | ||||
|         fprintf(debug_uart, "cannot read from record\n"); | ||||
|     if(!furi_read(holding_record, &read_value, sizeof(uint8_t))) { | ||||
|         fuprintf(log, "cannot read from record\n"); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     if(read_value != WRITE_VALUE) { | ||||
|         fprintf(debug_uart, "wrong read value (get %d, write %d)\n", read_value, WRITE_VALUE); | ||||
|         fuprintf(log, "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)) { | ||||
|         fprintf(debug_uart, "overflowed write not allowed\n"); | ||||
|     if(furi_write(holding_record, &WRITE_VALUE, 100)) { | ||||
|         fuprintf(log, "overflowed write not allowed\n"); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     if(furi_read(&holding_record, &read_value, 100)) { | ||||
|         fprintf(debug_uart, "overflowed read not allowed\n"); | ||||
|     if(furi_read(holding_record, &read_value, 100)) { | ||||
|         fuprintf(log, "overflowed read not allowed\n"); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
| @ -161,21 +161,22 @@ typedef struct { | ||||
| } ConcurrentValue; | ||||
| 
 | ||||
| void furi_concurent_app(void* p) { | ||||
|     FILE* debug_uart = (FILE*)p; | ||||
|     FuriRecordSubscriber* log = (FuriRecordSubscriber*)p; | ||||
| 
 | ||||
|     FuriRecordHandler holding_record = furi_open( | ||||
|     FuriRecordSubscriber* holding_record = furi_open( | ||||
|         "test/concurrent", false, false, NULL, NULL | ||||
|     ); | ||||
|     if(holding_record.record == NULL) { | ||||
|         fprintf(debug_uart, "cannot open record\n"); | ||||
|     if(holding_record == NULL) { | ||||
|         fuprintf(log, "cannot open record\n"); | ||||
|         furiac_exit(NULL); | ||||
|     } | ||||
| 
 | ||||
|     for(size_t i = 0; i < 10; i++) { | ||||
|         ConcurrentValue* value = (ConcurrentValue*)furi_take(&holding_record); | ||||
|         ConcurrentValue* value = (ConcurrentValue*)furi_take(holding_record); | ||||
| 
 | ||||
|         if(value == NULL) { | ||||
|             fprintf(debug_uart, "cannot take record\n"); | ||||
|             fuprintf(log, "cannot take record\n"); | ||||
|             furi_give(holding_record); | ||||
|             furiac_exit(NULL); | ||||
|         } | ||||
|         // emulate read-modify-write broken by context switching
 | ||||
| @ -186,40 +187,41 @@ void furi_concurent_app(void* p) { | ||||
|         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); | ||||
|         furi_give(holding_record); | ||||
|     } | ||||
| 
 | ||||
|     furiac_exit(NULL); | ||||
| } | ||||
| 
 | ||||
| bool test_furi_concurrent_access(FILE* debug_uart) { | ||||
| bool test_furi_concurrent_access(FuriRecordSubscriber* log) { | ||||
|     // 1. Create holding record
 | ||||
|     ConcurrentValue holder = {.a = 0, .b = 0}; | ||||
|     if(!furi_create("test/concurrent", (void*)&holder, sizeof(ConcurrentValue))) { | ||||
|         fprintf(debug_uart, "cannot create record\n"); | ||||
|         fuprintf(log, "cannot create record\n"); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     // 2. Open it
 | ||||
|     FuriRecordHandler holding_record = furi_open( | ||||
|     FuriRecordSubscriber* holding_record = furi_open( | ||||
|         "test/concurrent", false, false, NULL, NULL | ||||
|     ); | ||||
|     if(holding_record.record == NULL) { | ||||
|         fprintf(debug_uart, "cannot open record\n"); | ||||
|     if(holding_record == NULL) { | ||||
|         fuprintf(log, "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", (void*)debug_uart | ||||
|         furi_concurent_app, "furi concurent app", (void*)log | ||||
|     ); | ||||
| 
 | ||||
|     // 4. multiply ConcurrentValue::a
 | ||||
|     for(size_t i = 0; i < 4; i++) { | ||||
|         ConcurrentValue* value = (ConcurrentValue*)furi_take(&holding_record); | ||||
|         ConcurrentValue* value = (ConcurrentValue*)furi_take(holding_record); | ||||
| 
 | ||||
|         if(value == NULL) { | ||||
|             fprintf(debug_uart, "cannot take record\n"); | ||||
|             fuprintf(log, "cannot take record\n"); | ||||
|             furi_give(holding_record); | ||||
|             return false; | ||||
|         } | ||||
|         // emulate read-modify-write broken by context switching
 | ||||
| @ -230,18 +232,18 @@ bool test_furi_concurrent_access(FILE* debug_uart) { | ||||
|         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); | ||||
|         furi_give(holding_record); | ||||
|     } | ||||
| 
 | ||||
|     delay(20); | ||||
| 
 | ||||
|     if(second_app->handler != NULL) { | ||||
|         fprintf(debug_uart, "second app still alive\n"); | ||||
|         fuprintf(log, "second app still alive\n"); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     if(holder.a != holder.b) { | ||||
|         fprintf(debug_uart, "broken integrity: a=%d, b=%d\n", holder.a, holder.b); | ||||
|         fuprintf(log, "broken integrity: a=%d, b=%d\n", holder.a, holder.b); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
| @ -256,7 +258,7 @@ TEST: non-existent data | ||||
| 
 | ||||
| TODO: implement this test | ||||
| */ | ||||
| bool test_furi_nonexistent_data(FILE* debug_uart) { | ||||
| bool test_furi_nonexistent_data(FuriRecordSubscriber* log) { | ||||
| 
 | ||||
|     return true; | ||||
| } | ||||
| @ -315,20 +317,20 @@ void mute_record_state_cb(FlipperRecordState state) { | ||||
| } | ||||
| 
 | ||||
| void furi_mute_parent_app(void* p) { | ||||
|     FILE* debug_uart = (FILE*)p; | ||||
|     FuriRecordSubscriber* log = (FuriRecordSubscriber*)p; | ||||
| 
 | ||||
|     // 1. Create pipe record
 | ||||
|     if(!furi_create("test/mute", NULL, 0)) { | ||||
|         fprintf(debug_uart, "cannot create record\n"); | ||||
|         fuprintf(log, "cannot create record\n"); | ||||
|         furiac_exit(NULL); | ||||
|     } | ||||
| 
 | ||||
|     // 2. Open watch handler: solo=false, no_mute=false, subscribe to data
 | ||||
|     FuriRecordHandler watch_handler = furi_open( | ||||
|     FuriRecordSubscriber* watch_handler = furi_open( | ||||
|         "test/mute", false, false, mute_record_cb, NULL | ||||
|     ); | ||||
|     if(watch_handler.record == NULL) { | ||||
|         fprintf(debug_uart, "cannot open watch handler\n"); | ||||
|     if(watch_handler == NULL) { | ||||
|         fuprintf(log, "cannot open watch handler\n"); | ||||
|         furiac_exit(NULL); | ||||
|     } | ||||
| 
 | ||||
| @ -338,61 +340,61 @@ void furi_mute_parent_app(void* p) { | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| bool test_furi_mute_algorithm(FILE* debug_uart) { | ||||
| bool test_furi_mute_algorithm(FuriRecordSubscriber* log) { | ||||
|     // 1. Create "parent" application:
 | ||||
|     FuriApp* parent_app = furiac_start( | ||||
|         furi_mute_parent_app, "parent app", (void*)debug_uart | ||||
|         furi_mute_parent_app, "parent app", (void*)log | ||||
|     ); | ||||
| 
 | ||||
|     delay(2); // wait creating record
 | ||||
| 
 | ||||
|     // 2. Open handler A: solo=false, no_mute=false, NULL subscriber. Subscribe to state.
 | ||||
|     FuriRecordHandler handler_a = furi_open( | ||||
|     FuriRecordSubscriber* handler_a = furi_open( | ||||
|         "test/mute", false, false, NULL, mute_record_state_cb | ||||
|     ); | ||||
|     if(handler_a.record == NULL) { | ||||
|         fprintf(debug_uart, "cannot open handler A\n"); | ||||
|     if(handler_a == NULL) { | ||||
|         fuprintf(log, "cannot open handler A\n"); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     uint8_t test_counter = 1; | ||||
| 
 | ||||
|     // Try to write data to A and check subscriber
 | ||||
|     if(!furi_write(&handler_a, &test_counter, sizeof(uint8_t))) { | ||||
|         fprintf(debug_uart, "write to A failed\n"); | ||||
|     if(!furi_write(handler_a, &test_counter, sizeof(uint8_t))) { | ||||
|         fuprintf(log, "write to A failed\n"); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     if(mute_last_value != test_counter) { | ||||
|         fprintf(debug_uart, "value A mismatch: %d vs %d\n", mute_last_value, test_counter); | ||||
|         fuprintf(log, "value A mismatch: %d vs %d\n", mute_last_value, test_counter); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     // 3. Open handler B: solo=true, no_mute=true, NULL subscriber.
 | ||||
|     FuriRecordHandler handler_b = furi_open( | ||||
|     FuriRecordSubscriber* handler_b = furi_open( | ||||
|         "test/mute", true, true, NULL, NULL | ||||
|     ); | ||||
|     if(handler_b.record == NULL) { | ||||
|         fprintf(debug_uart, "cannot open handler B\n"); | ||||
|     if(handler_b == NULL) { | ||||
|         fuprintf(log, "cannot open handler B\n"); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     // Check A state cb get FlipperRecordStateMute.
 | ||||
|     if(mute_last_state != FlipperRecordStateMute) { | ||||
|         fprintf(debug_uart, "A state is not FlipperRecordStateMute: %d\n", mute_last_state); | ||||
|         fuprintf(log, "A state is not FlipperRecordStateMute: %d\n", mute_last_state); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     test_counter = 2; | ||||
| 
 | ||||
|     // Try to write data to A and check that subscriber get no data. (muted)
 | ||||
|     if(furi_write(&handler_a, &test_counter, sizeof(uint8_t))) { | ||||
|         fprintf(debug_uart, "A not muted\n"); | ||||
|     if(furi_write(handler_a, &test_counter, sizeof(uint8_t))) { | ||||
|         fuprintf(log, "A not muted\n"); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     if(mute_last_value == test_counter) { | ||||
|         fprintf(debug_uart, "value A must be muted\n"); | ||||
|         fuprintf(log, "value A must be muted\n"); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
| @ -400,23 +402,23 @@ bool test_furi_mute_algorithm(FILE* debug_uart) { | ||||
| 
 | ||||
| 
 | ||||
|     // Try to write data to B and check that subscriber get data.
 | ||||
|     if(!furi_write(&handler_b, &test_counter, sizeof(uint8_t))) { | ||||
|         fprintf(debug_uart, "write to B failed\n"); | ||||
|     if(!furi_write(handler_b, &test_counter, sizeof(uint8_t))) { | ||||
|         fuprintf(log, "write to B failed\n"); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     if(mute_last_value != test_counter) { | ||||
|         fprintf(debug_uart, "value B mismatch: %d vs %d\n", mute_last_value, test_counter); | ||||
|         fuprintf(log, "value B mismatch: %d vs %d\n", mute_last_value, test_counter); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     // 4. Open hadler C: solo=true, no_mute=false, NULL subscriber.
 | ||||
|     FuriRecordHandler handler_c = furi_open( | ||||
|     FuriRecordSubscriber* handler_c = furi_open( | ||||
|         "test/mute", true, false, NULL, NULL | ||||
|     ); | ||||
|     if(handler_c.record == NULL) { | ||||
|         fprintf(debug_uart, "cannot open handler C\n"); | ||||
|     if(handler_c == NULL) { | ||||
|         fuprintf(log, "cannot open handler C\n"); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
| @ -425,11 +427,11 @@ bool test_furi_mute_algorithm(FILE* debug_uart) { | ||||
|     // TODO: Try to write data to C and check that subscriber get data.
 | ||||
| 
 | ||||
|     // 5. Open handler D: solo=false, no_mute=false, NULL subscriber.
 | ||||
|     FuriRecordHandler handler_d = furi_open( | ||||
|     FuriRecordSubscriber* handler_d = furi_open( | ||||
|         "test/mute", false, false, NULL, NULL | ||||
|     ); | ||||
|     if(handler_d.record == NULL) { | ||||
|         fprintf(debug_uart, "cannot open handler D\n"); | ||||
|     if(handler_d == NULL) { | ||||
|         fuprintf(log, "cannot open handler D\n"); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
| @ -445,7 +447,7 @@ bool test_furi_mute_algorithm(FILE* debug_uart) { | ||||
| 
 | ||||
|     // 7. Exit "parent application"
 | ||||
|     if(!furiac_kill(parent_app)) { | ||||
|         fprintf(debug_uart, "kill parent_app fail\n"); | ||||
|         fuprintf(log, "kill parent_app fail\n"); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -1,7 +1,7 @@ | ||||
| #include <stdio.h> | ||||
| #include <string.h> | ||||
| #include "flipper.h" | ||||
| #include "debug.h" | ||||
| #include "log.h" | ||||
| 
 | ||||
| /*
 | ||||
| Test: creating and killing task | ||||
| @ -23,26 +23,26 @@ void create_kill_app(void* p) { | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| bool test_furi_ac_create_kill(FILE* debug_uart) { | ||||
| bool test_furi_ac_create_kill(FuriRecordSubscriber* log) { | ||||
|     uint8_t counter = 0; | ||||
| 
 | ||||
|     uint8_t value_a = counter; | ||||
| 
 | ||||
|     FuriApp* widget = furiac_start(create_kill_app, "create_kill_app", (void*)&counter); | ||||
|     if(widget == NULL) { | ||||
|         fprintf(debug_uart, "create widget fail\n"); | ||||
|         fuprintf(log, "create widget fail\n"); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     delay(10); | ||||
| 
 | ||||
|     if(!furiac_kill(widget)) { | ||||
|         fprintf(debug_uart, "kill widget fail\n"); | ||||
|         fuprintf(log, "kill widget fail\n"); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     if(value_a == counter) { | ||||
|         fprintf(debug_uart, "counter unchanged\n"); | ||||
|         fuprintf(log, "counter unchanged\n"); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
| @ -51,7 +51,7 @@ bool test_furi_ac_create_kill(FILE* debug_uart) { | ||||
|     delay(10); | ||||
| 
 | ||||
|     if(value_a != counter) { | ||||
|         fprintf(debug_uart, "counter changes after kill (counter = %d vs %d)\n", value_a, counter); | ||||
|         fuprintf(log, "counter changes after kill (counter = %d vs %d)\n", value_a, counter); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
| @ -111,7 +111,7 @@ void task_b(void* p) { | ||||
|     furiac_exit(p); | ||||
| } | ||||
| 
 | ||||
| bool test_furi_ac_switch_exit(FILE* debug_uart) { | ||||
| bool test_furi_ac_switch_exit(FuriRecordSubscriber* log) { | ||||
|     // init sequence
 | ||||
|     TestSwitchSequence seq; | ||||
|     seq.count = 0; | ||||
| @ -124,7 +124,7 @@ bool test_furi_ac_switch_exit(FILE* debug_uart) { | ||||
|     seq.sequence[seq.count] = '\0'; | ||||
| 
 | ||||
|     if(strcmp(seq.sequence, "ABA/") != 0) { | ||||
|         fprintf(debug_uart, "wrong sequence: %s\n", seq.sequence); | ||||
|         fuprintf(log, "wrong sequence: %s\n", seq.sequence); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -1,67 +1,67 @@ | ||||
| #include <stdio.h> | ||||
| #include "flipper.h" | ||||
| #include "debug.h" | ||||
| #include "log.h" | ||||
| 
 | ||||
| #include "flipper-core.h" | ||||
| 
 | ||||
| bool test_furi_ac_create_kill(FILE* debug_uart); | ||||
| bool test_furi_ac_switch_exit(FILE* debug_uart); | ||||
| bool test_furi_ac_create_kill(FuriRecordSubscriber* log); | ||||
| bool test_furi_ac_switch_exit(FuriRecordSubscriber* log); | ||||
| 
 | ||||
| bool test_furi_pipe_record(FILE* debug_uart); | ||||
| bool test_furi_holding_data(FILE* debug_uart); | ||||
| bool test_furi_concurrent_access(FILE* debug_uart); | ||||
| bool test_furi_nonexistent_data(FILE* debug_uart); | ||||
| bool test_furi_mute_algorithm(FILE* debug_uart); | ||||
| bool test_furi_pipe_record(FuriRecordSubscriber* log); | ||||
| bool test_furi_holding_data(FuriRecordSubscriber* log); | ||||
| bool test_furi_concurrent_access(FuriRecordSubscriber* log); | ||||
| bool test_furi_nonexistent_data(FuriRecordSubscriber* log); | ||||
| bool test_furi_mute_algorithm(FuriRecordSubscriber* log); | ||||
| 
 | ||||
| void flipper_test_app(void* p) { | ||||
|     FILE* debug_uart = get_debug(); | ||||
| 
 | ||||
|     if(test_furi_ac_create_kill(debug_uart)) { | ||||
|         fprintf(debug_uart, "[TEST] test_furi_ac_create_kill PASSED\n"); | ||||
|     FuriRecordSubscriber* log = get_default_log(); | ||||
|      | ||||
|     if(test_furi_ac_create_kill(log)) { | ||||
|         fuprintf(log, "[TEST] test_furi_ac_create_kill PASSED\n"); | ||||
|     } else { | ||||
|         fprintf(debug_uart, "[TEST] test_furi_ac_create_kill FAILED\n"); | ||||
|         fuprintf(log, "[TEST] test_furi_ac_create_kill FAILED\n"); | ||||
|     } | ||||
| 
 | ||||
|     if(test_furi_ac_switch_exit(debug_uart)) { | ||||
|         fprintf(debug_uart, "[TEST] test_furi_ac_switch_exit PASSED\n"); | ||||
|     if(test_furi_ac_switch_exit(log)) { | ||||
|         fuprintf(log, "[TEST] test_furi_ac_switch_exit PASSED\n"); | ||||
|     } else { | ||||
|         fprintf(debug_uart, "[TEST] test_furi_ac_switch_exit FAILED\n"); | ||||
|         fuprintf(log, "[TEST] test_furi_ac_switch_exit FAILED\n"); | ||||
|     } | ||||
| 
 | ||||
|     if(test_furi_pipe_record(debug_uart)) { | ||||
|         fprintf(debug_uart, "[TEST] test_furi_pipe_record PASSED\n"); | ||||
|     if(test_furi_pipe_record(log)) { | ||||
|         fuprintf(log, "[TEST] test_furi_pipe_record PASSED\n"); | ||||
|     } else { | ||||
|         fprintf(debug_uart, "[TEST] test_furi_pipe_record FAILED\n"); | ||||
|         fuprintf(log, "[TEST] test_furi_pipe_record FAILED\n"); | ||||
|     } | ||||
| 
 | ||||
|     if(test_furi_holding_data(debug_uart)) { | ||||
|         fprintf(debug_uart, "[TEST] test_furi_holding_data PASSED\n"); | ||||
|     if(test_furi_holding_data(log)) { | ||||
|         fuprintf(log, "[TEST] test_furi_holding_data PASSED\n"); | ||||
|     } else { | ||||
|         fprintf(debug_uart, "[TEST] test_furi_holding_data FAILED\n"); | ||||
|         fuprintf(log, "[TEST] test_furi_holding_data FAILED\n"); | ||||
|     } | ||||
| 
 | ||||
|     if(test_furi_concurrent_access(debug_uart)) { | ||||
|         fprintf(debug_uart, "[TEST] test_furi_concurrent_access PASSED\n"); | ||||
|     if(test_furi_concurrent_access(log)) { | ||||
|         fuprintf(log, "[TEST] test_furi_concurrent_access PASSED\n"); | ||||
|     } else { | ||||
|         fprintf(debug_uart, "[TEST] test_furi_concurrent_access FAILED\n"); | ||||
|         fuprintf(log, "[TEST] test_furi_concurrent_access FAILED\n"); | ||||
|     } | ||||
| 
 | ||||
|     if(test_furi_nonexistent_data(debug_uart)) { | ||||
|         fprintf(debug_uart, "[TEST] test_furi_nonexistent_data PASSED\n"); | ||||
|     if(test_furi_nonexistent_data(log)) { | ||||
|         fuprintf(log, "[TEST] test_furi_nonexistent_data PASSED\n"); | ||||
|     } else { | ||||
|         fprintf(debug_uart, "[TEST] test_furi_nonexistent_data FAILED\n"); | ||||
|         fuprintf(log, "[TEST] test_furi_nonexistent_data FAILED\n"); | ||||
|     } | ||||
| 
 | ||||
|     if(test_furi_mute_algorithm(debug_uart)) { | ||||
|         fprintf(debug_uart, "[TEST] test_furi_mute_algorithm PASSED\n"); | ||||
|     if(test_furi_mute_algorithm(log)) { | ||||
|         fuprintf(log, "[TEST] test_furi_mute_algorithm PASSED\n"); | ||||
|     } else { | ||||
|         fprintf(debug_uart, "[TEST] test_furi_mute_algorithm FAILED\n"); | ||||
|         fuprintf(log, "[TEST] test_furi_mute_algorithm FAILED\n"); | ||||
|     } | ||||
| 
 | ||||
|     if(add(1, 2) == 3) { | ||||
|         fprintf(debug_uart, "[TEST] Rust add PASSED\n"); | ||||
|         fuprintf(log, "[TEST] Rust add PASSED\n"); | ||||
|     } else { | ||||
|         fprintf(debug_uart, "[TEST] Rust add FAILED\n"); | ||||
|         fuprintf(log, "[TEST] Rust add FAILED\n"); | ||||
|     } | ||||
| 
 | ||||
|     furiac_exit(NULL); | ||||
|  | ||||
| @ -4,10 +4,16 @@ | ||||
| extern "C" { | ||||
|     #include "startup.h" | ||||
|     #include "furi.h" | ||||
|     #include "debug.h" | ||||
|     #include "log.h" | ||||
|     #include "tty_uart.h" | ||||
| } | ||||
| 
 | ||||
| extern "C" void app() { | ||||
|     register_tty_uart(); | ||||
| 
 | ||||
|     FuriRecordSubscriber* log = get_default_log(); | ||||
|     fuprintf(log, "\n=== Welcome to Flipper Zero! ===\n\n"); | ||||
| 
 | ||||
|     // FURI startup
 | ||||
|     FuriApp* handlers[sizeof(FLIPPER_STARTUP)/sizeof(FLIPPER_STARTUP[0])]; | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										31
									
								
								core/debug.c
									
									
									
									
									
								
							
							
						
						
									
										31
									
								
								core/debug.c
									
									
									
									
									
								
							| @ -1,31 +0,0 @@ | ||||
| #define _GNU_SOURCE | ||||
| #include "main.h" | ||||
| #include <stdio.h> | ||||
| 
 | ||||
| extern UART_HandleTypeDef DEBUG_UART; | ||||
| 
 | ||||
| ssize_t uart_write(void* cookie, const char * buffer, size_t size) { | ||||
|     if (buffer == 0) { | ||||
|         /*
 | ||||
|          * This means that we should flush internal buffers.  Since we | ||||
|          * don't we just return.  (Remember, "handle" == -1 means that all | ||||
|          * handles should be flushed.) | ||||
|          */ | ||||
|         return 0; | ||||
|     } | ||||
|      | ||||
|     return (ssize_t)HAL_UART_Transmit(&DEBUG_UART, (uint8_t*)buffer, (uint16_t)size, HAL_MAX_DELAY); | ||||
| } | ||||
| 
 | ||||
| FILE* get_debug() { | ||||
|     FILE* fp = fopencookie(NULL, "w+", (cookie_io_functions_t){ | ||||
|         .read  = NULL, | ||||
|         .write = uart_write, | ||||
|         .seek  = NULL, | ||||
|         .close = NULL | ||||
|     }); | ||||
| 
 | ||||
|     setvbuf(fp, NULL, _IONBF, 0); | ||||
| 
 | ||||
|     return fp; | ||||
| } | ||||
| @ -1 +0,0 @@ | ||||
| FILE* get_debug(); | ||||
							
								
								
									
										81
									
								
								core/furi.c
									
									
									
									
									
								
							
							
						
						
									
										81
									
								
								core/furi.c
									
									
									
									
									
								
							| @ -35,7 +35,7 @@ bool furi_create(const char* name, void* value, size_t size) { | ||||
|     if(current_buffer_idx >= MAX_RECORD_COUNT) { | ||||
|         // max record count exceed
 | ||||
|         #ifdef FURI_DEBUG | ||||
|             printf("[FURI] max record count exceed\n"); | ||||
|             printf("[FURI] create: max record count exceed\n"); | ||||
|         #endif | ||||
|         return NULL; | ||||
|     } | ||||
| @ -52,10 +52,12 @@ bool furi_create(const char* name, void* value, size_t size) { | ||||
|         records[current_buffer_idx].subscribers[i].allocated = false; | ||||
|     } | ||||
| 
 | ||||
|     current_buffer_idx++; | ||||
| 
 | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| FuriRecordHandler furi_open( | ||||
| FuriRecordSubscriber* furi_open( | ||||
|     const char* name, | ||||
|     bool solo, | ||||
|     bool no_mute, | ||||
| @ -75,16 +77,15 @@ FuriRecordHandler furi_open( | ||||
|             printf("[FURI] cannot find record %s\n", name); | ||||
|         #endif | ||||
| 
 | ||||
|         FuriRecordHandler res = {.record = NULL, .subscriber = NULL}; | ||||
|         return res; | ||||
|         return NULL; | ||||
|     } | ||||
| 
 | ||||
|     // allocate subscriber
 | ||||
|     FuriRecordSubscriber* subscriber = NULL; | ||||
| 
 | ||||
|     for(size_t i = 0; i < MAX_RECORD_SUBSCRIBERS; i++) { | ||||
|         if(!records[current_buffer_idx].subscribers[i].allocated) { | ||||
|             subscriber = &records[current_buffer_idx].subscribers[i]; | ||||
|         if(!record->subscribers[i].allocated) { | ||||
|             subscriber = &record->subscribers[i]; | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
| @ -92,11 +93,10 @@ FuriRecordHandler furi_open( | ||||
|     if(subscriber == NULL) { | ||||
|         // cannot add subscriber (full)
 | ||||
|         #ifdef FURI_DEBUG | ||||
|             printf("[FURI] cannot add subscriber (full)\n"); | ||||
|             printf("[FURI] open: cannot add subscriber (full)\n"); | ||||
|         #endif | ||||
| 
 | ||||
|         FuriRecordHandler res = {.record = NULL, .subscriber = NULL}; | ||||
|         return res; | ||||
|          | ||||
|         return NULL; | ||||
|     } | ||||
| 
 | ||||
|     // increase mute_counter
 | ||||
| @ -110,25 +110,31 @@ FuriRecordHandler furi_open( | ||||
|     subscriber->no_mute = no_mute; | ||||
|     subscriber->cb = value_callback; | ||||
|     subscriber->state_cb = state_callback; | ||||
|     subscriber->record = record; | ||||
| 
 | ||||
|     // register record in application
 | ||||
|     FuriApp* current_task = find_task(xTaskGetCurrentTaskHandle()); | ||||
| 
 | ||||
|     current_task->records[current_task->records_count] = record; | ||||
|     current_task->records_count++; | ||||
|     if(current_task != NULL) { | ||||
|         current_task->records[current_task->records_count] = record; | ||||
|         current_task->records_count++; | ||||
|     } else { | ||||
|         #ifdef FURI_DEBUG | ||||
|             printf("[FURI] open: no current task\n"); | ||||
|         #endif | ||||
|     } | ||||
| 
 | ||||
|     FuriRecordHandler res = {.record = record, .subscriber = subscriber}; | ||||
|     return res; | ||||
|     return subscriber; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| void furi_close(FuriRecordHandler* handler) { | ||||
| void furi_close(FuriRecordSubscriber* handler) { | ||||
|     #ifdef FURI_DEBUG | ||||
|         printf("[FURI] closing %s record\n", handler->record->name); | ||||
|     #endif | ||||
| 
 | ||||
|     // deallocate subscriber
 | ||||
|     handler->subscriber->allocated = false; | ||||
|     handler->allocated = false; | ||||
| 
 | ||||
|     // set mute counter to next max value
 | ||||
|     uint8_t max_mute_counter = 0; | ||||
| @ -142,7 +148,7 @@ void furi_close(FuriRecordHandler* handler) { | ||||
|     handler->record->mute_counter = max_mute_counter; | ||||
| } | ||||
| 
 | ||||
| static void furi_notify(FuriRecordHandler* handler, const void* value, size_t size) { | ||||
| static void furi_notify(FuriRecordSubscriber* handler, const void* value, size_t size) { | ||||
|     for(size_t i = 0; i < MAX_RECORD_SUBSCRIBERS; i++) { | ||||
|         if(handler->record->subscribers[i].allocated) { | ||||
|             if(handler->record->subscribers[i].cb != NULL) { | ||||
| @ -152,17 +158,17 @@ static void furi_notify(FuriRecordHandler* handler, const void* value, size_t si | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void* furi_take(FuriRecordHandler* handler) { | ||||
| void* furi_take(FuriRecordSubscriber* handler) { | ||||
|     // take mutex
 | ||||
| 
 | ||||
|     return handler->record->value; | ||||
| } | ||||
| 
 | ||||
| void furi_give(FuriRecordHandler* handler) { | ||||
| void furi_give(FuriRecordSubscriber* handler) { | ||||
|     // release mutex
 | ||||
| } | ||||
| 
 | ||||
| bool furi_read(FuriRecordHandler* handler, void* value, size_t size) { | ||||
| bool furi_read(FuriRecordSubscriber* handler, void* value, size_t size) { | ||||
|     #ifdef FURI_DEBUG | ||||
|         printf("[FURI] read from %s\n", handler->record->name); | ||||
|     #endif | ||||
| @ -182,23 +188,44 @@ bool furi_read(FuriRecordHandler* handler, void* value, size_t size) { | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| bool furi_write(FuriRecordHandler* handler, const void* value, size_t size) { | ||||
| bool furi_write(FuriRecordSubscriber* handler, const void* value, size_t size) { | ||||
|     #ifdef FURI_DEBUG | ||||
|         printf("[FURI] write to %s\n", handler->record->name); | ||||
|     #endif | ||||
| 
 | ||||
|     if(handler == NULL || handler->record == NULL || value == NULL) return false; | ||||
|     if(handler == NULL || handler->record == NULL || value == NULL) { | ||||
|         #ifdef FURI_DEBUG | ||||
|             printf("[FURI] write: null param %x %x\n", (uint32_t)(size_t)handler, (uint32_t)(size_t)value); | ||||
|         #endif | ||||
| 
 | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     // check if closed
 | ||||
|     if(!handler->subscriber->allocated) return false; | ||||
|     if(!handler->allocated) { | ||||
|         #ifdef FURI_DEBUG | ||||
|             printf("[FURI] write: handler closed\n"); | ||||
|         #endif | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     if(handler->record->value != NULL && size > handler->record->size) return false; | ||||
|     if(handler->record->value != NULL && size > handler->record->size) { | ||||
|         #ifdef FURI_DEBUG | ||||
|             printf("[FURI] write: wrong size %d\n", (uint32_t)size); | ||||
|         #endif | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     // check mute
 | ||||
|     if( | ||||
|         handler->record->mute_counter != handler->subscriber->mute_counter | ||||
|         && !handler->subscriber->no_mute | ||||
|     ) return false; | ||||
|         handler->record->mute_counter != handler->mute_counter | ||||
|         && !handler->no_mute | ||||
|     ) { | ||||
|         #ifdef FURI_DEBUG | ||||
|             printf("[FURI] write: muted\n"); | ||||
|         #endif | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     if(handler->record->value != NULL) { | ||||
|         // real write to value
 | ||||
|  | ||||
							
								
								
									
										25
									
								
								core/furi.h
									
									
									
									
									
								
							
							
						
						
									
										25
									
								
								core/furi.h
									
									
									
									
									
								
							| @ -22,16 +22,19 @@ typedef enum { | ||||
| /// pointer to state callback function
 | ||||
| typedef void(*FlipperRecordStateCallback)(FlipperRecordState); | ||||
| 
 | ||||
| struct _FuriRecord; | ||||
| 
 | ||||
| typedef struct { | ||||
|     bool allocated; | ||||
|     FlipperRecordCallback cb; ///< value cb
 | ||||
|     FlipperRecordStateCallback state_cb; ///< state cb
 | ||||
|     uint8_t mute_counter; ///< see "wiki/FURI#mute-algorithm"
 | ||||
|     bool no_mute; | ||||
|     struct _FuriRecord* record; ///< parent record
 | ||||
| } FuriRecordSubscriber; | ||||
| 
 | ||||
| /// FURI record handler
 | ||||
| typedef struct { | ||||
| struct _FuriRecord { | ||||
|     const char* name; | ||||
|     void* value; | ||||
|     size_t size; | ||||
| @ -39,13 +42,9 @@ typedef struct { | ||||
|     SemaphoreHandle_t mutex; | ||||
|     uint8_t mute_counter; | ||||
|     FuriRecordSubscriber subscribers[MAX_RECORD_SUBSCRIBERS]; | ||||
| } FuriRecord; | ||||
| }; | ||||
| 
 | ||||
| /// FURI record handler for use after open
 | ||||
| typedef struct { | ||||
|     FuriRecord* record; ///< full record (for read/write/take/give value)
 | ||||
|     FuriRecordSubscriber* subscriber; ///< current handler info
 | ||||
| } FuriRecordHandler; | ||||
| typedef struct _FuriRecord FuriRecord; | ||||
| 
 | ||||
| /// store info about active task
 | ||||
| typedef struct { | ||||
| @ -110,7 +109,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. | ||||
| */ | ||||
| FuriRecordHandler furi_open( | ||||
| FuriRecordSubscriber* furi_open( | ||||
|     const char* name, | ||||
|     bool solo, | ||||
|     bool no_mute, | ||||
| @ -121,7 +120,7 @@ FuriRecordHandler furi_open( | ||||
| /*!
 | ||||
| 
 | ||||
| */ | ||||
| void furi_close(FuriRecordHandler* handler); | ||||
| void furi_close(FuriRecordSubscriber* handler); | ||||
| 
 | ||||
| /*!
 | ||||
| read message from record. | ||||
| @ -130,7 +129,7 @@ Also return false if you try to read from FURI pipe | ||||
| 
 | ||||
| TODO: enum return value with execution status | ||||
| */ | ||||
| bool furi_read(FuriRecordHandler* record, void* data, size_t size); | ||||
| bool furi_read(FuriRecordSubscriber* record, void* data, size_t size); | ||||
| 
 | ||||
| /*!
 | ||||
| write message to record. | ||||
| @ -138,7 +137,7 @@ Returns true if success, false otherwise (closed/non-existent record or muted). | ||||
| 
 | ||||
| TODO: enum return value with execution status | ||||
| */ | ||||
| bool furi_write(FuriRecordHandler* record, const void* data, size_t size); | ||||
| bool furi_write(FuriRecordSubscriber* record, const void* data, size_t size); | ||||
| 
 | ||||
| /*!
 | ||||
| lock value mutex. | ||||
| @ -150,9 +149,9 @@ Returns pointer to data, NULL if closed/non-existent record or muted | ||||
| 
 | ||||
| TODO: enum return value with execution status | ||||
| */ | ||||
| void* furi_take(FuriRecordHandler* record); | ||||
| void* furi_take(FuriRecordSubscriber* record); | ||||
| 
 | ||||
| /*!
 | ||||
| unlock value mutex. | ||||
| */ | ||||
| void furi_give(FuriRecordHandler* record); | ||||
| void furi_give(FuriRecordSubscriber* record); | ||||
|  | ||||
							
								
								
									
										25
									
								
								core/log.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								core/log.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,25 @@ | ||||
| #define _GNU_SOURCE | ||||
| 
 | ||||
| #include <stdio.h> | ||||
| #include <stdarg.h> | ||||
| #include <string.h> | ||||
| 
 | ||||
| #include "log.h" | ||||
| #include "furi.h" | ||||
| 
 | ||||
| #define PRINT_STR_SIZE 64 | ||||
| 
 | ||||
| void fuprintf(FuriRecordSubscriber* f, const char * format, ...) { | ||||
|     char buffer[PRINT_STR_SIZE]; | ||||
| 
 | ||||
|     va_list args; | ||||
|     va_start(args, format); | ||||
|     vsprintf(buffer, format, args); | ||||
|     va_end(args); | ||||
| 
 | ||||
|     furi_write(f, buffer, strlen(buffer)); | ||||
| } | ||||
| 
 | ||||
| FuriRecordSubscriber* get_default_log() { | ||||
|     return furi_open("tty", false, false, NULL, NULL); | ||||
| } | ||||
							
								
								
									
										6
									
								
								core/log.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								core/log.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,6 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include "furi.h" | ||||
| 
 | ||||
| FuriRecordSubscriber* get_default_log(); | ||||
| void fuprintf(FuriRecordSubscriber* f, const char * format, ...); | ||||
							
								
								
									
										20
									
								
								core/tty_uart.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								core/tty_uart.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,20 @@ | ||||
| #include "furi.h" | ||||
| #include "main.h" | ||||
| 
 | ||||
| extern UART_HandleTypeDef DEBUG_UART; | ||||
| 
 | ||||
| void handle_uart_write(const void* data, size_t size) { | ||||
| 	HAL_UART_Transmit(&DEBUG_UART, (uint8_t*)data, (uint16_t)size, HAL_MAX_DELAY); | ||||
| } | ||||
| 
 | ||||
| bool register_tty_uart() { | ||||
| 	if(!furi_create("tty", NULL, 0)) { | ||||
| 		return false; | ||||
| 	} | ||||
| 	 | ||||
| 	if(furi_open("tty", false, false, handle_uart_write, NULL) == NULL) { | ||||
| 		return false; | ||||
| 	} | ||||
| 
 | ||||
| 	return true; | ||||
| } | ||||
							
								
								
									
										5
									
								
								core/tty_uart.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								core/tty_uart.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,5 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include <stdbool.h> | ||||
| 
 | ||||
| bool register_tty_uart(); | ||||
| @ -111,7 +111,8 @@ startup_stm32l476xx.s | ||||
| 
 | ||||
| CPP_SOURCES += ../core/app.cpp | ||||
| 
 | ||||
| C_SOURCES += ../core/debug.c | ||||
| C_SOURCES += ../core/log.c | ||||
| C_SOURCES += ../core/tty_uart.c | ||||
| C_SOURCES += ../core/furi.c | ||||
| C_SOURCES += ../core/furi_ac.c | ||||
| 
 | ||||
| @ -131,6 +132,11 @@ C_SOURCES += ../applications/examples/blink.c | ||||
| C_DEFS += -DEXAMPLE_BLINK | ||||
| endif | ||||
| 
 | ||||
| ifeq ($(EXAMPLE_UART_WRITE), 1) | ||||
| C_SOURCES += ../applications/examples/uart_write.c | ||||
| C_DEFS += -DEXAMPLE_UART_WRITE | ||||
| endif | ||||
| 
 | ||||
| # User application
 | ||||
| 
 | ||||
| # Add C_SOURCES +=, C_DEFS += or CPP_SOURCES += here
 | ||||
| @ -253,6 +259,9 @@ all: $(BUILD_DIR)/$(TARGET).elf $(BUILD_DIR)/$(TARGET).hex $(BUILD_DIR)/$(TARGET | ||||
| example_blink: | ||||
| 	EXAMPLE_BLINK=1 make | ||||
| 
 | ||||
| example_uart_write: | ||||
| 	EXAMPLE_UART_WRITE=1 make | ||||
| 
 | ||||
| test: | ||||
| 	TEST=1 make | ||||
| 
 | ||||
|  | ||||
| @ -32,11 +32,13 @@ C_SOURCES += Src/lo_os.c | ||||
| C_SOURCES += Src/lo_hal.c | ||||
| 
 | ||||
| C_DEFS += -DFURI_DEBUG | ||||
| 
 | ||||
| # Core
 | ||||
| 
 | ||||
| CPP_SOURCES += ../core/app.cpp | ||||
| 
 | ||||
| C_SOURCES += ../core/debug.c | ||||
| C_SOURCES += ../core/log.c | ||||
| C_SOURCES += ../core/tty_uart.c | ||||
| C_SOURCES += ../core/furi.c | ||||
| C_SOURCES += ../core/furi_ac.c | ||||
| 
 | ||||
| @ -56,6 +58,11 @@ C_SOURCES += ../applications/examples/blink.c | ||||
| C_DEFS += -DEXAMPLE_BLINK | ||||
| endif | ||||
| 
 | ||||
| ifeq ($(EXAMPLE_UART_WRITE), 1) | ||||
| C_SOURCES += ../applications/examples/uart_write.c | ||||
| C_DEFS += -DEXAMPLE_UART_WRITE | ||||
| endif | ||||
| 
 | ||||
| # User application
 | ||||
| 
 | ||||
| # Add C_SOURCES +=, C_DEFS += or CPP_SOURCES += here
 | ||||
| @ -130,10 +137,18 @@ all: $(BUILD_DIR)/$(TARGET) | ||||
| 
 | ||||
| example_blink: | ||||
| 	EXAMPLE_BLINK=1 make | ||||
| 	rm $(BUILD_DIR)/app.o | ||||
| 	$(BUILD_DIR)/$(TARGET) | ||||
| 
 | ||||
| 
 | ||||
| example_uart_write: | ||||
| 	EXAMPLE_UART_WRITE=1 make | ||||
| 	rm $(BUILD_DIR)/app.o | ||||
| 	$(BUILD_DIR)/$(TARGET) | ||||
| 
 | ||||
| test: | ||||
| 	TEST=1 make | ||||
| 	rm $(BUILD_DIR)/app.o | ||||
| 	$(BUILD_DIR)/$(TARGET) | ||||
| 
 | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										71
									
								
								wiki/examples/UART-write.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								wiki/examples/UART-write.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,71 @@ | ||||
| In this example we try to use FURI for interacting between user application and core subsystem. | ||||
| 
 | ||||
| First of all, we open FURI record by name "tty". This record is used for send some debug/logging info and interact with user by kind-of-TTY (like UART or USB CDC). By default on Flipper target all writes to tty record handled by debug UART (configured by `DEBUG_UART` define). On local target all writes simply prints to stdout. | ||||
| 
 | ||||
| Open record: | ||||
| 
 | ||||
| ```C | ||||
| FuriRecordSubscriber* log = get_default_log(); | ||||
| ``` | ||||
| 
 | ||||
| This is just wrapper on common FURI method: | ||||
| 
 | ||||
| ```C | ||||
| furi_open("tty", false, false, NULL, NULL); | ||||
| ``` | ||||
| 
 | ||||
| "tty" is FURI pipe record. It means that there is no "data" hold in record, it only manage callbacks: when you call `furi_write`, all subscriber's callback is called. You can find default implementation in `core/tty_uart.c`. | ||||
| 
 | ||||
| Let's get a look at full example code: | ||||
| 
 | ||||
| ```C | ||||
| #include "flipper.h" | ||||
| #include <string.h> | ||||
| #include "log.h" | ||||
| 
 | ||||
| void application_uart_write(void* p) { | ||||
|     // Red led for showing progress | ||||
|     GpioPin led = {.pin = GPIO_PIN_8, .port = GPIOA}; | ||||
|     pinMode(led, GpioModeOpenDrain); | ||||
| 
 | ||||
|     // get_default_log open "tty" record | ||||
|     FuriRecordSubscriber* log = get_default_log(); | ||||
| 
 | ||||
|     // create buffer | ||||
|     const char test_string[] = "test\n"; | ||||
|     furi_write(log, test_string, strlen(test_string)); | ||||
| 
 | ||||
|     // for example, create counter and show its value | ||||
|     uint8_t counter = 0; | ||||
| 
 | ||||
|     while(1) { | ||||
|         // continously write it to UART | ||||
|         fuprintf(log, "counter: %d\n", counter); | ||||
|         counter++; | ||||
| 
 | ||||
|         // flash at every send | ||||
|         digitalWrite(led, LOW); | ||||
|         delay(50); | ||||
|         digitalWrite(led, HIGH); | ||||
| 
 | ||||
|         // delay with overall perion of 1s | ||||
|         delay(950); | ||||
|     } | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| This code demonstrates two way to work with record: | ||||
| 
 | ||||
| 1. Directly writes some data by `furi_write` | ||||
| 2. Uses `fuprintf` wrapper on `printf`. | ||||
| 
 | ||||
| For creating application and set it to autorun, read [Blink example](Blink-app). | ||||
| 
 | ||||
| _You can also find source of this example in `applications/examples/uart_write.c` and run it by `docker-compose exec dev make -C target_lo example_uart_write`_ | ||||
| 
 | ||||
|  | ||||
| 
 | ||||
| _Code for target F1 can be compiled by `docker-compose exec dev make -C target_f1 example_uart_write`_ | ||||
| 
 | ||||
|  | ||||
| 
 | ||||
| @ -25,4 +25,5 @@ void application_name(void* p) { | ||||
| 
 | ||||
| # Application examples | ||||
| 
 | ||||
| * **[Blink](Blink-app)** | ||||
| * **[Blink](Blink-app)** show how to create app and control GPIO | ||||
| * **[UART write](UART-write)** operate with FURI pipe and print some messages | ||||
|  | ||||
							
								
								
									
										3
									
								
								wiki_static/application_examples/example_uart_write.gif
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								wiki_static/application_examples/example_uart_write.gif
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,3 @@ | ||||
| version https://git-lfs.github.com/spec/v1 | ||||
| oid sha256:818b3ef2a3b10fdade1be9459904f8295f75efd16fb532927d5ef6ff187e60e6 | ||||
| size 265769 | ||||
| @ -0,0 +1,3 @@ | ||||
| version https://git-lfs.github.com/spec/v1 | ||||
| oid sha256:76e5e8205a6cec14f5cc34f9b48a12133e0a8d79347f3286eb4bb28aacde4337 | ||||
| size 772608 | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 coreglitch
						coreglitch