mirror of
https://github.com/DarkFlippers/unleashed-firmware.git
synced 2025-12-12 04:34:43 +04:00
FuriHal: UART refactoring (#3211)
* FuriHal: UART refactoring * ApiSymbols: add furi_record_destroy * FuriHal: cleanup serial API, add logging configuration in RTC * FuriHal: hide private part in _i header. Toolbox: cleanup value index. SystemSettings: logging device and baudrate. * FuriHal: RTC logging method documentation * Synchronize API Symbols * Furi: mark HEAP_PRINT_DEBUG as broken * FuriHal: furi_hal_serial, add custom IRQ func * Fix PR review issues * FuriHal: UART add reception DMA (#3220) * FuriHal: add DMA serial rx mode * usb_uart_bridge: switch to working with DMA * Sync api symbol versions * FuriHal: update serial docs and api * FuriHal: Selial added similar API for simple reception mode as with DMA * FuriHal: Update API target H18 * API: ver API H7 * FuriHal: Serial error processing * FuriHal: fix furi_hal_serial set baudrate * Sync api symbols * FuriHal: cleanup serial isr and various flag handling procedures * FuriHal: cleanup and simplify serial API * Debug: update UART Echo serial related flags * FuriHal: update serial API symbols naming * FuriHalSerial: various improvements and PR review fixes * FuriHal: proper ISR function signatures --------- Co-authored-by: Aleksandr Kutuzov <alleteam@gmail.com> Co-authored-by: hedger <hedger@users.noreply.github.com> Co-authored-by: SkorP <skorpionm@yandex.ru> Co-authored-by: Skorpionm <85568270+Skorpionm@users.noreply.github.com>
This commit is contained in:
@@ -2,7 +2,6 @@
|
||||
#include "common_defines.h"
|
||||
|
||||
#include <stm32wbxx.h>
|
||||
#include <furi_hal_console.h>
|
||||
#include <furi_hal_power.h>
|
||||
#include <furi_hal_rtc.h>
|
||||
#include <furi_hal_debug.h>
|
||||
@@ -59,69 +58,69 @@ extern size_t xPortGetTotalHeapSize(void);
|
||||
static void __furi_put_uint32_as_text(uint32_t data) {
|
||||
char tmp_str[] = "-2147483648";
|
||||
itoa(data, tmp_str, 10);
|
||||
furi_hal_console_puts(tmp_str);
|
||||
furi_log_puts(tmp_str);
|
||||
}
|
||||
|
||||
static void __furi_put_uint32_as_hex(uint32_t data) {
|
||||
char tmp_str[] = "0xFFFFFFFF";
|
||||
itoa(data, tmp_str, 16);
|
||||
furi_hal_console_puts(tmp_str);
|
||||
furi_log_puts(tmp_str);
|
||||
}
|
||||
|
||||
static void __furi_print_register_info() {
|
||||
// Print registers
|
||||
for(uint8_t i = 0; i < 12; i++) {
|
||||
furi_hal_console_puts("\r\n\tr");
|
||||
furi_log_puts("\r\n\tr");
|
||||
__furi_put_uint32_as_text(i);
|
||||
furi_hal_console_puts(" : ");
|
||||
furi_log_puts(" : ");
|
||||
__furi_put_uint32_as_hex(__furi_check_registers[i]);
|
||||
}
|
||||
|
||||
furi_hal_console_puts("\r\n\tlr : ");
|
||||
furi_log_puts("\r\n\tlr : ");
|
||||
__furi_put_uint32_as_hex(__furi_check_registers[12]);
|
||||
}
|
||||
|
||||
static void __furi_print_stack_info() {
|
||||
furi_hal_console_puts("\r\n\tstack watermark: ");
|
||||
furi_log_puts("\r\n\tstack watermark: ");
|
||||
__furi_put_uint32_as_text(uxTaskGetStackHighWaterMark(NULL) * 4);
|
||||
}
|
||||
|
||||
static void __furi_print_bt_stack_info() {
|
||||
const FuriHalBtHardfaultInfo* fault_info = furi_hal_bt_get_hardfault_info();
|
||||
if(fault_info == NULL) {
|
||||
furi_hal_console_puts("\r\n\tcore2: not faulted");
|
||||
furi_log_puts("\r\n\tcore2: not faulted");
|
||||
} else {
|
||||
furi_hal_console_puts("\r\n\tcore2: hardfaulted.\r\n\tPC: ");
|
||||
furi_log_puts("\r\n\tcore2: hardfaulted.\r\n\tPC: ");
|
||||
__furi_put_uint32_as_hex(fault_info->source_pc);
|
||||
furi_hal_console_puts("\r\n\tLR: ");
|
||||
furi_log_puts("\r\n\tLR: ");
|
||||
__furi_put_uint32_as_hex(fault_info->source_lr);
|
||||
furi_hal_console_puts("\r\n\tSP: ");
|
||||
furi_log_puts("\r\n\tSP: ");
|
||||
__furi_put_uint32_as_hex(fault_info->source_sp);
|
||||
}
|
||||
}
|
||||
|
||||
static void __furi_print_heap_info() {
|
||||
furi_hal_console_puts("\r\n\t heap total: ");
|
||||
furi_log_puts("\r\n\t heap total: ");
|
||||
__furi_put_uint32_as_text(xPortGetTotalHeapSize());
|
||||
furi_hal_console_puts("\r\n\t heap free: ");
|
||||
furi_log_puts("\r\n\t heap free: ");
|
||||
__furi_put_uint32_as_text(xPortGetFreeHeapSize());
|
||||
furi_hal_console_puts("\r\n\t heap watermark: ");
|
||||
furi_log_puts("\r\n\t heap watermark: ");
|
||||
__furi_put_uint32_as_text(xPortGetMinimumEverFreeHeapSize());
|
||||
}
|
||||
|
||||
static void __furi_print_name(bool isr) {
|
||||
if(isr) {
|
||||
furi_hal_console_puts("[ISR ");
|
||||
furi_log_puts("[ISR ");
|
||||
__furi_put_uint32_as_text(__get_IPSR());
|
||||
furi_hal_console_puts("] ");
|
||||
furi_log_puts("] ");
|
||||
} else {
|
||||
const char* name = pcTaskGetName(NULL);
|
||||
if(name == NULL) {
|
||||
furi_hal_console_puts("[main] ");
|
||||
furi_log_puts("[main] ");
|
||||
} else {
|
||||
furi_hal_console_puts("[");
|
||||
furi_hal_console_puts(name);
|
||||
furi_hal_console_puts("] ");
|
||||
furi_log_puts("[");
|
||||
furi_log_puts(name);
|
||||
furi_log_puts("] ");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -140,9 +139,9 @@ FURI_NORETURN void __furi_crash_implementation() {
|
||||
__furi_check_message = "furi_check failed";
|
||||
}
|
||||
|
||||
furi_hal_console_puts("\r\n\033[0;31m[CRASH]");
|
||||
furi_log_puts("\r\n\033[0;31m[CRASH]");
|
||||
__furi_print_name(isr);
|
||||
furi_hal_console_puts(__furi_check_message);
|
||||
furi_log_puts(__furi_check_message);
|
||||
|
||||
__furi_print_register_info();
|
||||
if(!isr) {
|
||||
@@ -157,8 +156,8 @@ FURI_NORETURN void __furi_crash_implementation() {
|
||||
#ifdef FURI_NDEBUG
|
||||
if(debug) {
|
||||
#endif
|
||||
furi_hal_console_puts("\r\nSystem halted. Connect debugger for more info\r\n");
|
||||
furi_hal_console_puts("\033[0m\r\n");
|
||||
furi_log_puts("\r\nSystem halted. Connect debugger for more info\r\n");
|
||||
furi_log_puts("\033[0m\r\n");
|
||||
furi_hal_debug_enable();
|
||||
|
||||
RESTORE_REGISTERS_AND_HALT_MCU(debug);
|
||||
@@ -169,8 +168,8 @@ FURI_NORETURN void __furi_crash_implementation() {
|
||||
ptr = (uint32_t) "Check serial logs";
|
||||
}
|
||||
furi_hal_rtc_set_fault_data(ptr);
|
||||
furi_hal_console_puts("\r\nRebooting system.\r\n");
|
||||
furi_hal_console_puts("\033[0m\r\n");
|
||||
furi_log_puts("\r\nRebooting system.\r\n");
|
||||
furi_log_puts("\033[0m\r\n");
|
||||
furi_hal_power_reset();
|
||||
}
|
||||
#endif
|
||||
@@ -187,11 +186,11 @@ FURI_NORETURN void __furi_halt_implementation() {
|
||||
__furi_check_message = "System halt requested.";
|
||||
}
|
||||
|
||||
furi_hal_console_puts("\r\n\033[0;31m[HALT]");
|
||||
furi_log_puts("\r\n\033[0;31m[HALT]");
|
||||
__furi_print_name(isr);
|
||||
furi_hal_console_puts(__furi_check_message);
|
||||
furi_hal_console_puts("\r\nSystem halted. Bye-bye!\r\n");
|
||||
furi_hal_console_puts("\033[0m\r\n");
|
||||
furi_log_puts(__furi_check_message);
|
||||
furi_log_puts("\r\nSystem halted. Bye-bye!\r\n");
|
||||
furi_log_puts("\033[0m\r\n");
|
||||
|
||||
// Check if debug enabled by DAP
|
||||
// https://developer.arm.com/documentation/ddi0403/d/Debug-Architecture/ARMv7-M-Debug/Debug-register-support-in-the-SCS/Debug-Halting-Control-and-Status-Register--DHCSR?lang=en
|
||||
|
||||
107
furi/core/log.c
107
furi/core/log.c
@@ -2,17 +2,19 @@
|
||||
#include "check.h"
|
||||
#include "mutex.h"
|
||||
#include <furi_hal.h>
|
||||
#include <m-list.h>
|
||||
|
||||
LIST_DEF(FuriLogHandlersList, FuriLogHandler, M_POD_OPLIST)
|
||||
|
||||
#define FURI_LOG_LEVEL_DEFAULT FuriLogLevelInfo
|
||||
|
||||
typedef struct {
|
||||
FuriLogLevel log_level;
|
||||
FuriLogPuts puts;
|
||||
FuriLogTimestamp timestamp;
|
||||
FuriMutex* mutex;
|
||||
FuriLogHandlersList_t tx_handlers;
|
||||
} FuriLogParams;
|
||||
|
||||
static FuriLogParams furi_log;
|
||||
static FuriLogParams furi_log = {0};
|
||||
|
||||
typedef struct {
|
||||
const char* str;
|
||||
@@ -32,9 +34,77 @@ static const FuriLogLevelDescription FURI_LOG_LEVEL_DESCRIPTIONS[] = {
|
||||
void furi_log_init() {
|
||||
// Set default logging parameters
|
||||
furi_log.log_level = FURI_LOG_LEVEL_DEFAULT;
|
||||
furi_log.puts = furi_hal_console_puts;
|
||||
furi_log.timestamp = furi_get_tick;
|
||||
furi_log.mutex = furi_mutex_alloc(FuriMutexTypeNormal);
|
||||
furi_log.mutex = furi_mutex_alloc(FuriMutexTypeRecursive);
|
||||
FuriLogHandlersList_init(furi_log.tx_handlers);
|
||||
}
|
||||
|
||||
bool furi_log_add_handler(FuriLogHandler handler) {
|
||||
furi_check(handler.callback);
|
||||
|
||||
bool ret = true;
|
||||
|
||||
furi_check(furi_mutex_acquire(furi_log.mutex, FuriWaitForever) == FuriStatusOk);
|
||||
|
||||
FuriLogHandlersList_it_t it;
|
||||
FuriLogHandlersList_it(it, furi_log.tx_handlers);
|
||||
while(!FuriLogHandlersList_end_p(it)) {
|
||||
if(memcmp(FuriLogHandlersList_ref(it), &handler, sizeof(FuriLogHandler)) == 0) {
|
||||
ret = false;
|
||||
} else {
|
||||
FuriLogHandlersList_next(it);
|
||||
}
|
||||
}
|
||||
|
||||
if(ret) {
|
||||
FuriLogHandlersList_push_back(furi_log.tx_handlers, handler);
|
||||
}
|
||||
|
||||
furi_mutex_release(furi_log.mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool furi_log_remove_handler(FuriLogHandler handler) {
|
||||
bool ret = false;
|
||||
|
||||
furi_check(furi_mutex_acquire(furi_log.mutex, FuriWaitForever) == FuriStatusOk);
|
||||
|
||||
FuriLogHandlersList_it_t it;
|
||||
FuriLogHandlersList_it(it, furi_log.tx_handlers);
|
||||
while(!FuriLogHandlersList_end_p(it)) {
|
||||
if(memcmp(FuriLogHandlersList_ref(it), &handler, sizeof(FuriLogHandler)) == 0) {
|
||||
FuriLogHandlersList_remove(furi_log.tx_handlers, it);
|
||||
ret = true;
|
||||
} else {
|
||||
FuriLogHandlersList_next(it);
|
||||
}
|
||||
}
|
||||
|
||||
furi_mutex_release(furi_log.mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void furi_log_tx(const uint8_t* data, size_t size) {
|
||||
if(!FURI_IS_ISR()) {
|
||||
furi_check(furi_mutex_acquire(furi_log.mutex, FuriWaitForever) == FuriStatusOk);
|
||||
} else {
|
||||
if(furi_mutex_get_owner(furi_log.mutex)) return;
|
||||
}
|
||||
|
||||
FuriLogHandlersList_it_t it;
|
||||
FuriLogHandlersList_it(it, furi_log.tx_handlers);
|
||||
while(!FuriLogHandlersList_end_p(it)) {
|
||||
FuriLogHandlersList_ref(it)->callback(data, size, FuriLogHandlersList_ref(it)->context);
|
||||
FuriLogHandlersList_next(it);
|
||||
}
|
||||
|
||||
if(!FURI_IS_ISR()) furi_mutex_release(furi_log.mutex);
|
||||
}
|
||||
|
||||
void furi_log_puts(const char* data) {
|
||||
furi_check(data);
|
||||
furi_log_tx((const uint8_t*)data, strlen(data));
|
||||
}
|
||||
|
||||
void furi_log_print_format(FuriLogLevel level, const char* tag, const char* format, ...) {
|
||||
@@ -72,13 +142,8 @@ void furi_log_print_format(FuriLogLevel level, const char* tag, const char* form
|
||||
|
||||
// Timestamp
|
||||
furi_string_printf(
|
||||
string,
|
||||
"%lu %s[%s][%s] " _FURI_LOG_CLR_RESET,
|
||||
furi_log.timestamp(),
|
||||
color,
|
||||
log_letter,
|
||||
tag);
|
||||
furi_log.puts(furi_string_get_cstr(string));
|
||||
string, "%lu %s[%s][%s] " _FURI_LOG_CLR_RESET, furi_get_tick(), color, log_letter, tag);
|
||||
furi_log_puts(furi_string_get_cstr(string));
|
||||
furi_string_reset(string);
|
||||
|
||||
va_list args;
|
||||
@@ -86,10 +151,10 @@ void furi_log_print_format(FuriLogLevel level, const char* tag, const char* form
|
||||
furi_string_vprintf(string, format, args);
|
||||
va_end(args);
|
||||
|
||||
furi_log.puts(furi_string_get_cstr(string));
|
||||
furi_log_puts(furi_string_get_cstr(string));
|
||||
furi_string_free(string);
|
||||
|
||||
furi_log.puts("\r\n");
|
||||
furi_log_puts("\r\n");
|
||||
|
||||
furi_mutex_release(furi_log.mutex);
|
||||
}
|
||||
@@ -105,7 +170,7 @@ void furi_log_print_raw_format(FuriLogLevel level, const char* format, ...) {
|
||||
furi_string_vprintf(string, format, args);
|
||||
va_end(args);
|
||||
|
||||
furi_log.puts(furi_string_get_cstr(string));
|
||||
furi_log_puts(furi_string_get_cstr(string));
|
||||
furi_string_free(string);
|
||||
|
||||
furi_mutex_release(furi_log.mutex);
|
||||
@@ -123,16 +188,6 @@ FuriLogLevel furi_log_get_level(void) {
|
||||
return furi_log.log_level;
|
||||
}
|
||||
|
||||
void furi_log_set_puts(FuriLogPuts puts) {
|
||||
furi_assert(puts);
|
||||
furi_log.puts = puts;
|
||||
}
|
||||
|
||||
void furi_log_set_timestamp(FuriLogTimestamp timestamp) {
|
||||
furi_assert(timestamp);
|
||||
furi_log.timestamp = timestamp;
|
||||
}
|
||||
|
||||
bool furi_log_level_to_string(FuriLogLevel level, const char** str) {
|
||||
for(size_t i = 0; i < COUNT_OF(FURI_LOG_LEVEL_DESCRIPTIONS); i++) {
|
||||
if(level == FURI_LOG_LEVEL_DESCRIPTIONS[i].level) {
|
||||
|
||||
@@ -39,11 +39,44 @@ typedef enum {
|
||||
#define _FURI_LOG_CLR_D _FURI_LOG_CLR(_FURI_LOG_CLR_BLUE)
|
||||
#define _FURI_LOG_CLR_T _FURI_LOG_CLR(_FURI_LOG_CLR_PURPLE)
|
||||
|
||||
typedef void (*FuriLogPuts)(const char* data);
|
||||
typedef uint32_t (*FuriLogTimestamp)(void);
|
||||
typedef void (*FuriLogHandlerCallback)(const uint8_t* data, size_t size, void* context);
|
||||
|
||||
typedef struct {
|
||||
FuriLogHandlerCallback callback;
|
||||
void* context;
|
||||
} FuriLogHandler;
|
||||
|
||||
/** Initialize logging */
|
||||
void furi_log_init();
|
||||
void furi_log_init(void);
|
||||
|
||||
/** Add log TX callback
|
||||
*
|
||||
* @param[in] callback The callback
|
||||
*
|
||||
* @return true on success, false otherwise
|
||||
*/
|
||||
bool furi_log_add_handler(FuriLogHandler handler);
|
||||
|
||||
/** Remove log TX callback
|
||||
*
|
||||
* @param[in] callback The callback
|
||||
*
|
||||
* @return true on success, false otherwise
|
||||
*/
|
||||
bool furi_log_remove_handler(FuriLogHandler handler);
|
||||
|
||||
/** Transmit data through log IO callbacks
|
||||
*
|
||||
* @param[in] data The data
|
||||
* @param[in] size The size
|
||||
*/
|
||||
void furi_log_tx(const uint8_t* data, size_t size);
|
||||
|
||||
/** Transmit data through log IO callbacks
|
||||
*
|
||||
* @param[in] data The data, null-terminated C-string
|
||||
*/
|
||||
void furi_log_puts(const char* data);
|
||||
|
||||
/** Print log record
|
||||
*
|
||||
@@ -74,19 +107,7 @@ void furi_log_set_level(FuriLogLevel level);
|
||||
*
|
||||
* @return The furi log level.
|
||||
*/
|
||||
FuriLogLevel furi_log_get_level();
|
||||
|
||||
/** Set log output callback
|
||||
*
|
||||
* @param[in] puts The puts callback
|
||||
*/
|
||||
void furi_log_set_puts(FuriLogPuts puts);
|
||||
|
||||
/** Set timestamp callback
|
||||
*
|
||||
* @param[in] timestamp The timestamp callback
|
||||
*/
|
||||
void furi_log_set_timestamp(FuriLogTimestamp timestamp);
|
||||
FuriLogLevel furi_log_get_level(void);
|
||||
|
||||
/** Log level to string
|
||||
*
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <stm32wbxx.h>
|
||||
#include <furi_hal_console.h>
|
||||
#include <core/log.h>
|
||||
#include <core/common_defines.h>
|
||||
|
||||
/* Defining MPU_WRAPPERS_INCLUDED_FROM_API_FILE prevents task.h from redefining
|
||||
@@ -52,6 +52,10 @@ task.h is included from an application file. */
|
||||
|
||||
#undef MPU_WRAPPERS_INCLUDED_FROM_API_FILE
|
||||
|
||||
#ifdef HEAP_PRINT_DEBUG
|
||||
#error This feature is broken, logging transport must be replaced with RTT
|
||||
#endif
|
||||
|
||||
#if(configSUPPORT_DYNAMIC_ALLOCATION == 0)
|
||||
#error This file must not be used if configSUPPORT_DYNAMIC_ALLOCATION is 0
|
||||
#endif
|
||||
@@ -286,13 +290,13 @@ static void print_heap_init() {
|
||||
|
||||
// {PHStart|heap_start|heap_end}
|
||||
FURI_CRITICAL_ENTER();
|
||||
furi_hal_console_puts("{PHStart|");
|
||||
furi_log_puts("{PHStart|");
|
||||
ultoa(heap_start, tmp_str, 16);
|
||||
furi_hal_console_puts(tmp_str);
|
||||
furi_hal_console_puts("|");
|
||||
furi_log_puts(tmp_str);
|
||||
furi_log_puts("|");
|
||||
ultoa(heap_end, tmp_str, 16);
|
||||
furi_hal_console_puts(tmp_str);
|
||||
furi_hal_console_puts("}\r\n");
|
||||
furi_log_puts(tmp_str);
|
||||
furi_log_puts("}\r\n");
|
||||
FURI_CRITICAL_EXIT();
|
||||
}
|
||||
|
||||
@@ -305,15 +309,15 @@ static void print_heap_malloc(void* ptr, size_t size) {
|
||||
|
||||
// {thread name|m|address|size}
|
||||
FURI_CRITICAL_ENTER();
|
||||
furi_hal_console_puts("{");
|
||||
furi_hal_console_puts(name);
|
||||
furi_hal_console_puts("|m|0x");
|
||||
furi_log_puts("{");
|
||||
furi_log_puts(name);
|
||||
furi_log_puts("|m|0x");
|
||||
ultoa((unsigned long)ptr, tmp_str, 16);
|
||||
furi_hal_console_puts(tmp_str);
|
||||
furi_hal_console_puts("|");
|
||||
furi_log_puts(tmp_str);
|
||||
furi_log_puts("|");
|
||||
utoa(size, tmp_str, 10);
|
||||
furi_hal_console_puts(tmp_str);
|
||||
furi_hal_console_puts("}\r\n");
|
||||
furi_log_puts(tmp_str);
|
||||
furi_log_puts("}\r\n");
|
||||
FURI_CRITICAL_EXIT();
|
||||
}
|
||||
|
||||
@@ -326,12 +330,12 @@ static void print_heap_free(void* ptr) {
|
||||
|
||||
// {thread name|f|address}
|
||||
FURI_CRITICAL_ENTER();
|
||||
furi_hal_console_puts("{");
|
||||
furi_hal_console_puts(name);
|
||||
furi_hal_console_puts("|f|0x");
|
||||
furi_log_puts("{");
|
||||
furi_log_puts(name);
|
||||
furi_log_puts("|f|0x");
|
||||
ultoa((unsigned long)ptr, tmp_str, 16);
|
||||
furi_hal_console_puts(tmp_str);
|
||||
furi_hal_console_puts("}\r\n");
|
||||
furi_log_puts(tmp_str);
|
||||
furi_log_puts("}\r\n");
|
||||
FURI_CRITICAL_EXIT();
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -114,8 +114,10 @@ FuriThreadId furi_mutex_get_owner(FuriMutex* instance) {
|
||||
|
||||
hMutex = (SemaphoreHandle_t)((uint32_t)instance & ~1U);
|
||||
|
||||
if((FURI_IS_IRQ_MODE()) || (hMutex == NULL)) {
|
||||
if((hMutex == NULL)) {
|
||||
owner = 0;
|
||||
} else if(FURI_IS_IRQ_MODE()) {
|
||||
owner = (FuriThreadId)xSemaphoreGetMutexHolderFromISR(hMutex);
|
||||
} else {
|
||||
owner = (FuriThreadId)xSemaphoreGetMutexHolder(hMutex);
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
|
||||
#include "log.h"
|
||||
#include <furi_hal_rtc.h>
|
||||
#include <furi_hal_console.h>
|
||||
|
||||
#include <FreeRTOS.h>
|
||||
#include <task.h>
|
||||
@@ -570,7 +569,7 @@ static size_t __furi_thread_stdout_write(FuriThread* thread, const char* data, s
|
||||
if(thread->output.write_callback != NULL) {
|
||||
thread->output.write_callback(data, size);
|
||||
} else {
|
||||
furi_hal_console_tx((const uint8_t*)data, size);
|
||||
furi_log_tx((const uint8_t*)data, size);
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user