[FL-1984, FL-2004, FL-2010] USB CDC Fixes (#801)
* [FL-1984, FL-2004] USB-UART Fixes, test with high timer task priority * added forgotten file * switch from EventFlags to ThreadFlags * [FL-1984, FL-2010] USB-UART and furi-hal-vcp rework * Scripts: modernize string formatting. Co-authored-by: あく <alleteam@gmail.com>
This commit is contained in:
		
							parent
							
								
									3225f40870
								
							
						
					
					
						commit
						f8d3cc251c
					
				| @ -67,6 +67,9 @@ void gpio_scene_start_on_enter(void* context) { | |||||||
|     variable_item_list_add(var_item_list, "GPIO tester", 0, NULL, NULL); |     variable_item_list_add(var_item_list, "GPIO tester", 0, NULL, NULL); | ||||||
|     variable_item_list_add(var_item_list, "USB-UART bridge", 0, NULL, NULL); |     variable_item_list_add(var_item_list, "USB-UART bridge", 0, NULL, NULL); | ||||||
| 
 | 
 | ||||||
|  |     variable_item_list_set_selected_item( | ||||||
|  |         var_item_list, scene_manager_get_scene_state(app->scene_manager, GpioSceneStart)); | ||||||
|  | 
 | ||||||
|     view_dispatcher_switch_to_view(app->view_dispatcher, GpioAppViewVarItemList); |     view_dispatcher_switch_to_view(app->view_dispatcher, GpioAppViewVarItemList); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -80,8 +83,10 @@ bool gpio_scene_start_on_event(void* context, SceneManagerEvent event) { | |||||||
|         } else if(event.event == GPIO_SCENE_START_CUSTOM_EVENT_OTG_OFF) { |         } else if(event.event == GPIO_SCENE_START_CUSTOM_EVENT_OTG_OFF) { | ||||||
|             furi_hal_power_disable_otg(); |             furi_hal_power_disable_otg(); | ||||||
|         } else if(event.event == GPIO_SCENE_START_CUSTOM_EVENT_TEST) { |         } else if(event.event == GPIO_SCENE_START_CUSTOM_EVENT_TEST) { | ||||||
|  |             scene_manager_set_scene_state(app->scene_manager, GpioSceneStart, 1); | ||||||
|             scene_manager_next_scene(app->scene_manager, GpioSceneTest); |             scene_manager_next_scene(app->scene_manager, GpioSceneTest); | ||||||
|         } else if(event.event == GPIO_SCENE_START_CUSTOM_EVENT_USB_UART) { |         } else if(event.event == GPIO_SCENE_START_CUSTOM_EVENT_USB_UART) { | ||||||
|  |             scene_manager_set_scene_state(app->scene_manager, GpioSceneStart, 2); | ||||||
|             scene_manager_next_scene(app->scene_manager, GpioSceneUsbUart); |             scene_manager_next_scene(app->scene_manager, GpioSceneUsbUart); | ||||||
|         } |         } | ||||||
|         consumed = true; |         consumed = true; | ||||||
|  | |||||||
| @ -120,12 +120,19 @@ void gpio_scene_usb_uart_on_enter(void* context) { | |||||||
|     item = variable_item_list_add(var_item_list, "Enable", 0, NULL, NULL); |     item = variable_item_list_add(var_item_list, "Enable", 0, NULL, NULL); | ||||||
|     item = variable_item_list_add(var_item_list, "Disable", 0, NULL, NULL); |     item = variable_item_list_add(var_item_list, "Disable", 0, NULL, NULL); | ||||||
| 
 | 
 | ||||||
|  |     variable_item_list_set_selected_item( | ||||||
|  |         var_item_list, scene_manager_get_scene_state(app->scene_manager, GpioSceneUsbUart)); | ||||||
|  | 
 | ||||||
|     view_dispatcher_switch_to_view(app->view_dispatcher, GpioAppViewUsbUart); |     view_dispatcher_switch_to_view(app->view_dispatcher, GpioAppViewUsbUart); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void gpio_scene_usb_uart_on_exit(void* context) { | void gpio_scene_usb_uart_on_exit(void* context) { | ||||||
|     GpioApp* app = context; |     GpioApp* app = context; | ||||||
|     usb_uart_disable(); |     usb_uart_disable(); | ||||||
|  |     scene_manager_set_scene_state( | ||||||
|  |         app->scene_manager, | ||||||
|  |         GpioSceneUsbUart, | ||||||
|  |         variable_item_list_get_selected_item_index(app->var_item_list)); | ||||||
|     variable_item_list_clean(app->var_item_list); |     variable_item_list_clean(app->var_item_list); | ||||||
|     free(cfg_set); |     free(cfg_set); | ||||||
| } | } | ||||||
|  | |||||||
| @ -4,23 +4,19 @@ | |||||||
| #include <furi-hal-usb-cdc_i.h> | #include <furi-hal-usb-cdc_i.h> | ||||||
| #include "usb_cdc.h" | #include "usb_cdc.h" | ||||||
| 
 | 
 | ||||||
| #define USB_PKT_LEN CDC_DATA_SZ | #define USB_CDC_PKT_LEN CDC_DATA_SZ | ||||||
| #define USB_UART_RX_BUF_SIZE (USB_PKT_LEN * 3) | #define USB_UART_RX_BUF_SIZE (USB_CDC_PKT_LEN * 5) | ||||||
| #define USB_UART_TX_BUF_SIZE (USB_PKT_LEN * 3) |  | ||||||
| 
 | 
 | ||||||
| typedef enum { | typedef enum { | ||||||
|     WorkerEvtStop = (1 << 0), |     WorkerEvtStop = (1 << 0), | ||||||
|     WorkerEvtRxReady = (1 << 1), |     WorkerEvtRxDone = (1 << 1), | ||||||
| 
 | 
 | ||||||
|     WorkerEvtTxStop = (1 << 2), |     WorkerEvtTxStop = (1 << 2), | ||||||
|     WorkerEvtTxReady = (1 << 3), |     WorkerEvtCdcRx = (1 << 3), | ||||||
| 
 |  | ||||||
|     WorkerEvtSof = (1 << 4), |  | ||||||
| 
 |  | ||||||
| } WorkerEvtFlags; | } WorkerEvtFlags; | ||||||
| 
 | 
 | ||||||
| #define WORKER_ALL_RX_EVENTS (WorkerEvtStop | WorkerEvtRxReady) | #define WORKER_ALL_RX_EVENTS (WorkerEvtStop | WorkerEvtRxDone) | ||||||
| #define WORKER_ALL_TX_EVENTS (WorkerEvtTxStop | WorkerEvtTxReady) | #define WORKER_ALL_TX_EVENTS (WorkerEvtTxStop | WorkerEvtCdcRx) | ||||||
| 
 | 
 | ||||||
| typedef struct { | typedef struct { | ||||||
|     UsbUartConfig cfg; |     UsbUartConfig cfg; | ||||||
| @ -28,13 +24,13 @@ typedef struct { | |||||||
|     FuriThread* thread; |     FuriThread* thread; | ||||||
|     FuriThread* tx_thread; |     FuriThread* tx_thread; | ||||||
| 
 | 
 | ||||||
|     osEventFlagsId_t events; |  | ||||||
| 
 |  | ||||||
|     StreamBufferHandle_t rx_stream; |     StreamBufferHandle_t rx_stream; | ||||||
|     StreamBufferHandle_t tx_stream; |  | ||||||
| 
 | 
 | ||||||
|     uint8_t rx_buf[USB_PKT_LEN]; |     osMutexId_t usb_mutex; | ||||||
|     uint8_t tx_buf[USB_PKT_LEN]; | 
 | ||||||
|  |     osSemaphoreId_t tx_sem; | ||||||
|  | 
 | ||||||
|  |     uint8_t rx_buf[USB_CDC_PKT_LEN]; | ||||||
| 
 | 
 | ||||||
|     bool buf_full; |     bool buf_full; | ||||||
| } UsbUartParams; | } UsbUartParams; | ||||||
| @ -65,21 +61,18 @@ static void usb_uart_on_irq_cb(UartIrqEvent ev, uint8_t data) { | |||||||
| 
 | 
 | ||||||
|     if(ev == UartIrqEventRXNE) { |     if(ev == UartIrqEventRXNE) { | ||||||
|         xStreamBufferSendFromISR(usb_uart->rx_stream, &data, 1, &xHigherPriorityTaskWoken); |         xStreamBufferSendFromISR(usb_uart->rx_stream, &data, 1, &xHigherPriorityTaskWoken); | ||||||
| 
 |         portYIELD_FROM_ISR(xHigherPriorityTaskWoken); | ||||||
|         size_t ret = xStreamBufferBytesAvailable(usb_uart->rx_stream); |         osThreadFlagsSet(furi_thread_get_thread_id(usb_uart->thread), WorkerEvtRxDone); | ||||||
|         if(ret > USB_PKT_LEN) osEventFlagsSet(usb_uart->events, WorkerEvtRxReady); |  | ||||||
|     } else if(ev == UartIrqEventIDLE) { |  | ||||||
|         osEventFlagsSet(usb_uart->events, WorkerEvtRxReady); |  | ||||||
|     } |     } | ||||||
| 
 |  | ||||||
|     portYIELD_FROM_ISR(xHigherPriorityTaskWoken); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static int32_t usb_uart_worker(void* context) { | static int32_t usb_uart_worker(void* context) { | ||||||
|     memcpy(&usb_uart->cfg, context, sizeof(UsbUartConfig)); |     memcpy(&usb_uart->cfg, context, sizeof(UsbUartConfig)); | ||||||
| 
 | 
 | ||||||
|     usb_uart->rx_stream = xStreamBufferCreate(USB_UART_RX_BUF_SIZE, 1); |     usb_uart->rx_stream = xStreamBufferCreate(USB_UART_RX_BUF_SIZE, 1); | ||||||
|     usb_uart->tx_stream = xStreamBufferCreate(USB_UART_TX_BUF_SIZE, 1); | 
 | ||||||
|  |     usb_uart->tx_sem = osSemaphoreNew(1, 1, NULL); | ||||||
|  |     usb_uart->usb_mutex = osMutexNew(NULL); | ||||||
| 
 | 
 | ||||||
|     usb_uart->tx_thread = furi_thread_alloc(); |     usb_uart->tx_thread = furi_thread_alloc(); | ||||||
|     furi_thread_set_name(usb_uart->tx_thread, "usb_uart_tx"); |     furi_thread_set_name(usb_uart->tx_thread, "usb_uart_tx"); | ||||||
| @ -91,10 +84,10 @@ static int32_t usb_uart_worker(void* context) { | |||||||
|     if(usb_uart->cfg.vcp_ch == 0) { |     if(usb_uart->cfg.vcp_ch == 0) { | ||||||
|         furi_hal_usb_set_config(UsbModeVcpSingle); |         furi_hal_usb_set_config(UsbModeVcpSingle); | ||||||
|         furi_hal_vcp_disable(); |         furi_hal_vcp_disable(); | ||||||
|         osEventFlagsSet(usb_uart->events, WorkerEvtSof); |  | ||||||
|     } else { |     } else { | ||||||
|         furi_hal_usb_set_config(UsbModeVcpDual); |         furi_hal_usb_set_config(UsbModeVcpDual); | ||||||
|     } |     } | ||||||
|  |     osThreadFlagsSet(furi_thread_get_thread_id(usb_uart->tx_thread), WorkerEvtCdcRx); | ||||||
| 
 | 
 | ||||||
|     if(usb_uart->cfg.uart_ch == FuriHalUartIdUSART1) { |     if(usb_uart->cfg.uart_ch == FuriHalUartIdUSART1) { | ||||||
|         furi_hal_console_disable(); |         furi_hal_console_disable(); | ||||||
| @ -114,26 +107,25 @@ static int32_t usb_uart_worker(void* context) { | |||||||
|     furi_thread_start(usb_uart->tx_thread); |     furi_thread_start(usb_uart->tx_thread); | ||||||
| 
 | 
 | ||||||
|     while(1) { |     while(1) { | ||||||
|         uint32_t events = osEventFlagsWait( |         uint32_t events = osThreadFlagsWait(WORKER_ALL_RX_EVENTS, osFlagsWaitAny, osWaitForever); | ||||||
|             usb_uart->events, WORKER_ALL_RX_EVENTS, osFlagsWaitAny, osWaitForever); |  | ||||||
|         furi_check((events & osFlagsError) == 0); |         furi_check((events & osFlagsError) == 0); | ||||||
|         if(events & WorkerEvtStop) break; |         if(events & WorkerEvtStop) break; | ||||||
|         if(events & WorkerEvtRxReady) { |         if(events & WorkerEvtRxDone) { | ||||||
|             size_t len = 0; |             size_t len = | ||||||
|             do { |                 xStreamBufferReceive(usb_uart->rx_stream, usb_uart->rx_buf, USB_CDC_PKT_LEN, 0); | ||||||
|                 len = xStreamBufferReceive(usb_uart->rx_stream, usb_uart->rx_buf, USB_PKT_LEN, 0); |             if(len > 0) { | ||||||
|                 if(len > 0) { |                 if(osSemaphoreAcquire(usb_uart->tx_sem, 100) == osOK) { | ||||||
|                     if((osEventFlagsWait(usb_uart->events, WorkerEvtSof, osFlagsWaitAny, 100) & |                     furi_check(osMutexAcquire(usb_uart->usb_mutex, osWaitForever) == osOK); | ||||||
|                         osFlagsError) == 0) |                     furi_hal_cdc_send(usb_uart->cfg.vcp_ch, usb_uart->rx_buf, len); | ||||||
|                         furi_hal_cdc_send(usb_uart->cfg.vcp_ch, usb_uart->rx_buf, len); |                     furi_check(osMutexRelease(usb_uart->usb_mutex) == osOK); | ||||||
|                     else |                 } else { | ||||||
|                         xStreamBufferReset(usb_uart->rx_stream); |                     xStreamBufferReset(usb_uart->rx_stream); | ||||||
|                 } |                 } | ||||||
|             } while(len > 0); |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     osEventFlagsSet(usb_uart->events, WorkerEvtTxStop); |     osThreadFlagsSet(furi_thread_get_thread_id(usb_uart->tx_thread), WorkerEvtTxStop); | ||||||
|     furi_thread_join(usb_uart->tx_thread); |     furi_thread_join(usb_uart->tx_thread); | ||||||
|     furi_thread_free(usb_uart->tx_thread); |     furi_thread_free(usb_uart->tx_thread); | ||||||
| 
 | 
 | ||||||
| @ -147,35 +139,26 @@ static int32_t usb_uart_worker(void* context) { | |||||||
|     if(usb_uart->cfg.vcp_ch == 0) furi_hal_vcp_enable(); |     if(usb_uart->cfg.vcp_ch == 0) furi_hal_vcp_enable(); | ||||||
| 
 | 
 | ||||||
|     vStreamBufferDelete(usb_uart->rx_stream); |     vStreamBufferDelete(usb_uart->rx_stream); | ||||||
|     vStreamBufferDelete(usb_uart->tx_stream); |     osMutexDelete(usb_uart->usb_mutex); | ||||||
|  |     osSemaphoreDelete(usb_uart->tx_sem); | ||||||
| 
 | 
 | ||||||
|     return 0; |     return 0; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static int32_t usb_uart_tx_thread(void* context) { | static int32_t usb_uart_tx_thread(void* context) { | ||||||
|     uint8_t data[USB_PKT_LEN]; |     uint8_t data[USB_CDC_PKT_LEN]; | ||||||
|     while(1) { |     while(1) { | ||||||
|         uint32_t events = osEventFlagsWait( |         uint32_t events = osThreadFlagsWait(WORKER_ALL_TX_EVENTS, osFlagsWaitAny, osWaitForever); | ||||||
|             usb_uart->events, WORKER_ALL_TX_EVENTS, osFlagsWaitAny, osWaitForever); |  | ||||||
|         furi_check((events & osFlagsError) == 0); |         furi_check((events & osFlagsError) == 0); | ||||||
|         if(events & WorkerEvtTxStop) break; |         if(events & WorkerEvtTxStop) break; | ||||||
|         if(events & WorkerEvtTxReady) { |         if(events & WorkerEvtCdcRx) { | ||||||
|             size_t len = 0; |             furi_check(osMutexAcquire(usb_uart->usb_mutex, osWaitForever) == osOK); | ||||||
|             do { |             int32_t size = furi_hal_cdc_receive(usb_uart->cfg.vcp_ch, data, USB_CDC_PKT_LEN); | ||||||
|                 len = xStreamBufferReceive(usb_uart->tx_stream, &data, 1, 0); |             furi_check(osMutexRelease(usb_uart->usb_mutex) == osOK); | ||||||
|                 if(len > 0) { | 
 | ||||||
|                     furi_hal_uart_tx(usb_uart->cfg.uart_ch, data, len); |             if(size > 0) { | ||||||
|                 } |                 furi_hal_uart_tx(usb_uart->cfg.uart_ch, data, size); | ||||||
|                 if((usb_uart->buf_full == true) && |             } | ||||||
|                    (xStreamBufferBytesAvailable(usb_uart->tx_stream) == 0)) { |  | ||||||
|                     // Stream buffer was overflown, but now is free. Reading USB buffer to resume USB transfers
 |  | ||||||
|                     usb_uart->buf_full = false; |  | ||||||
|                     int32_t size = furi_hal_cdc_receive(usb_uart->cfg.vcp_ch, data, USB_PKT_LEN); |  | ||||||
|                     if(size > 0) { |  | ||||||
|                         furi_hal_uart_tx(usb_uart->cfg.uart_ch, data, size); |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } while(len > 0); |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|     return 0; |     return 0; | ||||||
| @ -184,26 +167,11 @@ static int32_t usb_uart_tx_thread(void* context) { | |||||||
| /* VCP callbacks */ | /* VCP callbacks */ | ||||||
| 
 | 
 | ||||||
| static void vcp_on_cdc_tx_complete() { | static void vcp_on_cdc_tx_complete() { | ||||||
|     osEventFlagsSet(usb_uart->events, WorkerEvtSof); |     osSemaphoreRelease(usb_uart->tx_sem); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void vcp_on_cdc_rx() { | static void vcp_on_cdc_rx() { | ||||||
|     BaseType_t xHigherPriorityTaskWoken = pdFALSE; |     osThreadFlagsSet(furi_thread_get_thread_id(usb_uart->tx_thread), WorkerEvtCdcRx); | ||||||
| 
 |  | ||||||
|     uint16_t max_len = xStreamBufferSpacesAvailable(usb_uart->tx_stream); |  | ||||||
|     if(max_len >= USB_PKT_LEN) { |  | ||||||
|         //if(max_len > USB_PKT_LEN) max_len = USB_PKT_LEN;
 |  | ||||||
|         int32_t size = furi_hal_cdc_receive(usb_uart->cfg.vcp_ch, usb_uart->tx_buf, USB_PKT_LEN); |  | ||||||
|         if(size > 0) { |  | ||||||
|             size_t ret = xStreamBufferSendFromISR( |  | ||||||
|                 usb_uart->tx_stream, usb_uart->tx_buf, size, &xHigherPriorityTaskWoken); |  | ||||||
|             furi_check(ret == size); |  | ||||||
|         } |  | ||||||
|     } else { |  | ||||||
|         usb_uart->buf_full = true; |  | ||||||
|     } |  | ||||||
|     osEventFlagsSet(usb_uart->events, WorkerEvtTxReady); |  | ||||||
|     portYIELD_FROM_ISR(xHigherPriorityTaskWoken); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void vcp_state_callback(uint8_t state) { | static void vcp_state_callback(uint8_t state) { | ||||||
| @ -228,18 +196,15 @@ void usb_uart_enable(UsbUartConfig* cfg) { | |||||||
|         furi_thread_set_context(usb_uart->thread, cfg); |         furi_thread_set_context(usb_uart->thread, cfg); | ||||||
|         furi_thread_set_callback(usb_uart->thread, usb_uart_worker); |         furi_thread_set_callback(usb_uart->thread, usb_uart_worker); | ||||||
| 
 | 
 | ||||||
|         usb_uart->events = osEventFlagsNew(NULL); |  | ||||||
| 
 |  | ||||||
|         furi_thread_start(usb_uart->thread); |         furi_thread_start(usb_uart->thread); | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void usb_uart_disable() { | void usb_uart_disable() { | ||||||
|     if(running == true) { |     if(running == true) { | ||||||
|         osEventFlagsSet(usb_uart->events, WorkerEvtStop); |         osThreadFlagsSet(furi_thread_get_thread_id(usb_uart->thread), WorkerEvtStop); | ||||||
|         furi_thread_join(usb_uart->thread); |         furi_thread_join(usb_uart->thread); | ||||||
|         furi_thread_free(usb_uart->thread); |         furi_thread_free(usb_uart->thread); | ||||||
|         osEventFlagsDelete(usb_uart->events); |  | ||||||
|         free(usb_uart); |         free(usb_uart); | ||||||
|         running = false; |         running = false; | ||||||
|     } |     } | ||||||
|  | |||||||
							
								
								
									
										36
									
								
								applications/gui/modules/variable-item-list.c
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							
							
						
						
									
										36
									
								
								applications/gui/modules/variable-item-list.c
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							| @ -84,6 +84,40 @@ static void variable_item_list_draw_callback(Canvas* canvas, void* _model) { | |||||||
|     elements_scrollbar(canvas, model->position, VariableItemArray_size(model->items)); |     elements_scrollbar(canvas, model->position, VariableItemArray_size(model->items)); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | void variable_item_list_set_selected_item(VariableItemList* variable_item_list, uint8_t index) { | ||||||
|  |     with_view_model( | ||||||
|  |         variable_item_list->view, (VariableItemListModel * model) { | ||||||
|  |             uint8_t position = index; | ||||||
|  |             if(position >= VariableItemArray_size(model->items)) { | ||||||
|  |                 position = 0; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             model->position = position; | ||||||
|  |             model->window_position = position; | ||||||
|  | 
 | ||||||
|  |             if(model->window_position > 0) { | ||||||
|  |                 model->window_position -= 1; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             if(VariableItemArray_size(model->items) <= 4) { | ||||||
|  |                 model->window_position = 0; | ||||||
|  |             } else { | ||||||
|  |                 if(model->window_position >= (VariableItemArray_size(model->items) - 4)) { | ||||||
|  |                     model->window_position = (VariableItemArray_size(model->items) - 4); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             return true; | ||||||
|  |         }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | uint8_t variable_item_list_get_selected_item_index(VariableItemList* variable_item_list) { | ||||||
|  |     VariableItemListModel* model = view_get_model(variable_item_list->view); | ||||||
|  |     uint8_t idx = model->position; | ||||||
|  |     view_commit_model(variable_item_list->view, false); | ||||||
|  |     return idx; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| static bool variable_item_list_input_callback(InputEvent* event, void* context) { | static bool variable_item_list_input_callback(InputEvent* event, void* context) { | ||||||
|     VariableItemList* variable_item_list = context; |     VariableItemList* variable_item_list = context; | ||||||
|     furi_assert(variable_item_list); |     furi_assert(variable_item_list); | ||||||
| @ -323,4 +357,4 @@ uint8_t variable_item_get_current_value_index(VariableItem* item) { | |||||||
| 
 | 
 | ||||||
| void* variable_item_get_context(VariableItem* item) { | void* variable_item_get_context(VariableItem* item) { | ||||||
|     return item->context; |     return item->context; | ||||||
| } | } | ||||||
|  | |||||||
| @ -70,6 +70,10 @@ void variable_item_list_set_enter_callback( | |||||||
|     VariableItemListEnterCallback callback, |     VariableItemListEnterCallback callback, | ||||||
|     void* context); |     void* context); | ||||||
| 
 | 
 | ||||||
|  | void variable_item_list_set_selected_item(VariableItemList* variable_item_list, uint8_t index); | ||||||
|  | 
 | ||||||
|  | uint8_t variable_item_list_get_selected_item_index(VariableItemList* variable_item_list); | ||||||
|  | 
 | ||||||
| /** Set item current selected index
 | /** Set item current selected index
 | ||||||
|  * |  * | ||||||
|  * @param      item                 VariableItem* instance |  * @param      item                 VariableItem* instance | ||||||
|  | |||||||
| @ -7,12 +7,6 @@ | |||||||
| extern "C" { | extern "C" { | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
| typedef enum { |  | ||||||
|     UartIrqEventRXNE, |  | ||||||
|     UartIrqEventIDLE, |  | ||||||
|     //TODO: more events
 |  | ||||||
| } UartIrqEvent; |  | ||||||
| 
 |  | ||||||
| void furi_hal_console_init(); | void furi_hal_console_init(); | ||||||
| 
 | 
 | ||||||
| void furi_hal_console_enable(); | void furi_hal_console_enable(); | ||||||
|  | |||||||
| @ -2,7 +2,6 @@ | |||||||
| 
 | 
 | ||||||
| #include <stddef.h> | #include <stddef.h> | ||||||
| #include <stdint.h> | #include <stdint.h> | ||||||
| #include "furi-hal-console.h" |  | ||||||
| 
 | 
 | ||||||
| #ifdef __cplusplus | #ifdef __cplusplus | ||||||
| extern "C" { | extern "C" { | ||||||
| @ -13,6 +12,11 @@ typedef enum { | |||||||
|     FuriHalUartIdLPUART1, |     FuriHalUartIdLPUART1, | ||||||
| } FuriHalUartId; | } FuriHalUartId; | ||||||
| 
 | 
 | ||||||
|  | typedef enum { | ||||||
|  |     UartIrqEventRXNE, | ||||||
|  |     UartIrqEventIDLE, | ||||||
|  |     //TODO: more events
 | ||||||
|  | } UartIrqEvent; | ||||||
| 
 | 
 | ||||||
| void furi_hal_uart_init(FuriHalUartId ch, uint32_t baud); | void furi_hal_uart_init(FuriHalUartId ch, uint32_t baud); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -7,12 +7,12 @@ | |||||||
| #include "usb_cdc.h" | #include "usb_cdc.h" | ||||||
| 
 | 
 | ||||||
| #define CDC0_RXD_EP      0x01 | #define CDC0_RXD_EP      0x01 | ||||||
| #define CDC0_TXD_EP      0x81 | #define CDC0_TXD_EP      0x82 | ||||||
| #define CDC0_NTF_EP      0x82 | #define CDC0_NTF_EP      0x83 | ||||||
| 
 | 
 | ||||||
| #define CDC1_RXD_EP      0x03 | #define CDC1_RXD_EP      0x04 | ||||||
| #define CDC1_TXD_EP      0x83 | #define CDC1_TXD_EP      0x85 | ||||||
| #define CDC1_NTF_EP      0x84 | #define CDC1_NTF_EP      0x86 | ||||||
| 
 | 
 | ||||||
| #define CDC_NTF_SZ      0x08 | #define CDC_NTF_SZ      0x08 | ||||||
| 
 | 
 | ||||||
| @ -446,10 +446,12 @@ void furi_hal_cdc_send(uint8_t if_num, uint8_t* buf, uint16_t len) { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| int32_t furi_hal_cdc_receive(uint8_t if_num, uint8_t* buf, uint16_t max_len) { | int32_t furi_hal_cdc_receive(uint8_t if_num, uint8_t* buf, uint16_t max_len) { | ||||||
|  |     int32_t len = 0; | ||||||
|     if (if_num == 0) |     if (if_num == 0) | ||||||
|         return usbd_ep_read(usb_dev, CDC0_RXD_EP, buf, max_len); |         len = usbd_ep_read(usb_dev, CDC0_RXD_EP, buf, max_len); | ||||||
|     else |     else | ||||||
|         return usbd_ep_read(usb_dev, CDC1_RXD_EP, buf, max_len); |         len = usbd_ep_read(usb_dev, CDC1_RXD_EP, buf, max_len); | ||||||
|  |     return ((len < 0) ? 0 : len); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void cdc_on_wakeup(usbd_device *dev) { | static void cdc_on_wakeup(usbd_device *dev) { | ||||||
|  | |||||||
| @ -3,18 +3,28 @@ | |||||||
| #include <furi.h> | #include <furi.h> | ||||||
| #include <stream_buffer.h> | #include <stream_buffer.h> | ||||||
| 
 | 
 | ||||||
| #define APP_RX_DATA_SIZE CDC_DATA_SZ | #define USB_CDC_PKT_LEN CDC_DATA_SZ | ||||||
| #define APP_TX_DATA_SIZE CDC_DATA_SZ |  | ||||||
| #define FURI_HAL_VCP_RX_BUFFER_SIZE (APP_RX_DATA_SIZE * 16) |  | ||||||
| #define VCP_IF_NUM 0 | #define VCP_IF_NUM 0 | ||||||
| 
 | 
 | ||||||
|  | typedef enum { | ||||||
|  |     VcpConnect, | ||||||
|  |     VcpDisconnect, | ||||||
|  | } VcpEvent; | ||||||
|  | 
 | ||||||
| typedef struct { | typedef struct { | ||||||
|     volatile bool connected; |     volatile bool connected; | ||||||
| 
 | 
 | ||||||
|     StreamBufferHandle_t rx_stream; |     uint8_t rx_buf[USB_CDC_PKT_LEN]; | ||||||
|     volatile bool rx_stream_full; |     uint8_t rx_buf_start; | ||||||
|  |     uint8_t rx_buf_len; | ||||||
|  | 
 | ||||||
|  |     osMessageQueueId_t event_queue; | ||||||
|  | 
 | ||||||
|  |     osMutexId_t usb_mutex; | ||||||
|  | 
 | ||||||
|  |     osSemaphoreId_t tx_sem; | ||||||
|  |     osSemaphoreId_t rx_sem; | ||||||
| 
 | 
 | ||||||
|     osSemaphoreId_t tx_semaphore; |  | ||||||
| } FuriHalVcp; | } FuriHalVcp; | ||||||
| 
 | 
 | ||||||
| static void vcp_on_cdc_tx_complete(); | static void vcp_on_cdc_tx_complete(); | ||||||
| @ -30,22 +40,21 @@ static CdcCallbacks cdc_cb = { | |||||||
|     NULL, |     NULL, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| static FuriHalVcp* furi_hal_vcp = NULL; | static FuriHalVcp* vcp = NULL; | ||||||
| 
 | 
 | ||||||
| static const uint8_t ascii_soh = 0x01; | static const uint8_t ascii_soh = 0x01; | ||||||
| static const uint8_t ascii_eot = 0x04; | static const uint8_t ascii_eot = 0x04; | ||||||
| 
 | 
 | ||||||
| static uint8_t* vcp_rx_buf; |  | ||||||
| 
 |  | ||||||
| void furi_hal_vcp_init() { | void furi_hal_vcp_init() { | ||||||
|     furi_hal_vcp = furi_alloc(sizeof(FuriHalVcp)); |     vcp = furi_alloc(sizeof(FuriHalVcp)); | ||||||
|     vcp_rx_buf = furi_alloc(APP_RX_DATA_SIZE); |     vcp->connected = false; | ||||||
|     furi_hal_vcp->connected = false; |  | ||||||
|      |  | ||||||
|     furi_hal_vcp->rx_stream = xStreamBufferCreate(FURI_HAL_VCP_RX_BUFFER_SIZE, 1); |  | ||||||
|     furi_hal_vcp->rx_stream_full = false; |  | ||||||
| 
 | 
 | ||||||
|     furi_hal_vcp->tx_semaphore = osSemaphoreNew(1, 1, NULL); |     vcp->usb_mutex = osMutexNew(NULL); | ||||||
|  | 
 | ||||||
|  |     vcp->tx_sem = osSemaphoreNew(1, 1, NULL); | ||||||
|  |     vcp->rx_sem = osSemaphoreNew(1, 0, NULL); | ||||||
|  | 
 | ||||||
|  |     vcp->event_queue = osMessageQueueNew(8, sizeof(VcpEvent), NULL); | ||||||
| 
 | 
 | ||||||
|     furi_hal_cdc_set_callbacks(VCP_IF_NUM, &cdc_cb); |     furi_hal_cdc_set_callbacks(VCP_IF_NUM, &cdc_cb); | ||||||
| 
 | 
 | ||||||
| @ -54,111 +63,149 @@ void furi_hal_vcp_init() { | |||||||
| 
 | 
 | ||||||
| void furi_hal_vcp_enable() { | void furi_hal_vcp_enable() { | ||||||
|     furi_hal_cdc_set_callbacks(VCP_IF_NUM, &cdc_cb); |     furi_hal_cdc_set_callbacks(VCP_IF_NUM, &cdc_cb); | ||||||
|     furi_hal_vcp->connected = true; |     VcpEvent evt = VcpConnect; | ||||||
|  |     osMessageQueuePut(vcp->event_queue, &evt, 0, 0); | ||||||
|  |     vcp->connected = true; | ||||||
|  |     osSemaphoreRelease(vcp->tx_sem); | ||||||
|  |     osSemaphoreRelease(vcp->rx_sem); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void furi_hal_vcp_disable() { | void furi_hal_vcp_disable() { | ||||||
|     furi_hal_cdc_set_callbacks(VCP_IF_NUM, NULL); |     furi_hal_cdc_set_callbacks(VCP_IF_NUM, NULL); | ||||||
|     furi_hal_vcp->connected = false; |     VcpEvent evt = VcpDisconnect; | ||||||
|     osSemaphoreRelease(furi_hal_vcp->tx_semaphore); |     osMessageQueuePut(vcp->event_queue, &evt, 0, 0); | ||||||
| } |     vcp->connected = false; | ||||||
| 
 |     osSemaphoreRelease(vcp->tx_sem); | ||||||
| size_t furi_hal_vcp_rx(uint8_t* buffer, size_t size) { |     osSemaphoreRelease(vcp->rx_sem); | ||||||
|     furi_assert(furi_hal_vcp); |  | ||||||
| 
 |  | ||||||
|     size_t received = xStreamBufferReceive(furi_hal_vcp->rx_stream, buffer, size, portMAX_DELAY); |  | ||||||
| 
 |  | ||||||
|     if(furi_hal_vcp->rx_stream_full |  | ||||||
|         && xStreamBufferSpacesAvailable(furi_hal_vcp->rx_stream) >= APP_RX_DATA_SIZE) { |  | ||||||
|         furi_hal_vcp->rx_stream_full = false; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return received; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| size_t furi_hal_vcp_rx_with_timeout(uint8_t* buffer, size_t size, uint32_t timeout) { | size_t furi_hal_vcp_rx_with_timeout(uint8_t* buffer, size_t size, uint32_t timeout) { | ||||||
|     furi_assert(furi_hal_vcp); |     furi_assert(vcp); | ||||||
|     return xStreamBufferReceive(furi_hal_vcp->rx_stream, buffer, size, timeout); |     furi_assert(buffer); | ||||||
|  | 
 | ||||||
|  |     size_t rx_cnt = 0; | ||||||
|  | 
 | ||||||
|  |     VcpEvent evt = VcpDisconnect; | ||||||
|  | 
 | ||||||
|  |     if (vcp->rx_buf_len > 0) { | ||||||
|  |         size_t len = (vcp->rx_buf_len > size) ? (size) : (vcp->rx_buf_len); | ||||||
|  |         memcpy(&buffer[rx_cnt], &vcp->rx_buf[vcp->rx_buf_start], len); | ||||||
|  |         vcp->rx_buf_len -= len; | ||||||
|  |         vcp->rx_buf_start += len; | ||||||
|  |         rx_cnt += len; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     while (rx_cnt < size) { | ||||||
|  |         if (osMessageQueueGet(vcp->event_queue, &evt, NULL, 0) == osOK) { | ||||||
|  |             if (evt == VcpConnect) | ||||||
|  |                 buffer[rx_cnt] = ascii_soh; | ||||||
|  |             else { | ||||||
|  |                 buffer[rx_cnt] = ascii_eot; | ||||||
|  |                 vcp->rx_buf_len = 0; | ||||||
|  |             } | ||||||
|  |             rx_cnt++; | ||||||
|  |             return rx_cnt; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if (osSemaphoreAcquire(vcp->rx_sem, timeout) == osErrorTimeout) | ||||||
|  |             return rx_cnt; | ||||||
|  |          | ||||||
|  |         furi_check(osMutexAcquire(vcp->usb_mutex, osWaitForever) == osOK); | ||||||
|  |         size_t len = furi_hal_cdc_receive(VCP_IF_NUM, vcp->rx_buf, USB_CDC_PKT_LEN); | ||||||
|  |         furi_check(osMutexRelease(vcp->usb_mutex) == osOK); | ||||||
|  | 
 | ||||||
|  |         vcp->rx_buf_len = len; | ||||||
|  |         vcp->rx_buf_start = 0; | ||||||
|  | 
 | ||||||
|  |         if (vcp->rx_buf_len > (size - rx_cnt)) { | ||||||
|  |             len = size - rx_cnt; | ||||||
|  |             memcpy(&buffer[rx_cnt], vcp->rx_buf, len); | ||||||
|  |             vcp->rx_buf_len -= len; | ||||||
|  |             vcp->rx_buf_start += len; | ||||||
|  |         } else { | ||||||
|  |             memcpy(&buffer[rx_cnt], vcp->rx_buf, vcp->rx_buf_len); | ||||||
|  |             vcp->rx_buf_len = 0; | ||||||
|  |         } | ||||||
|  |         rx_cnt += len; | ||||||
|  |     } | ||||||
|  |     return rx_cnt; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | size_t furi_hal_vcp_rx(uint8_t* buffer, size_t size) { | ||||||
|  |     furi_assert(vcp); | ||||||
|  | 
 | ||||||
|  |     return furi_hal_vcp_rx_with_timeout(buffer, size, portMAX_DELAY); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void furi_hal_vcp_tx(const uint8_t* buffer, size_t size) { | void furi_hal_vcp_tx(const uint8_t* buffer, size_t size) { | ||||||
|     furi_assert(furi_hal_vcp); |     furi_assert(vcp); | ||||||
| 
 | 
 | ||||||
|     while (size > 0 && furi_hal_vcp->connected) { |     while (size > 0 && vcp->connected) { | ||||||
|         furi_check(osSemaphoreAcquire(furi_hal_vcp->tx_semaphore, osWaitForever) == osOK); |         furi_check(osSemaphoreAcquire(vcp->tx_sem, osWaitForever) == osOK); | ||||||
|         if (!furi_hal_vcp->connected) |         if (!vcp->connected) | ||||||
|             break; |             break; | ||||||
| 
 | 
 | ||||||
|         size_t batch_size = size; |         size_t batch_size = size; | ||||||
|         if (batch_size > APP_TX_DATA_SIZE) { |         if (batch_size > USB_CDC_PKT_LEN) { | ||||||
|             batch_size = APP_TX_DATA_SIZE; |             batch_size = USB_CDC_PKT_LEN; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  |         furi_check(osMutexAcquire(vcp->usb_mutex, osWaitForever) == osOK); | ||||||
|         furi_hal_cdc_send(VCP_IF_NUM, (uint8_t*)buffer, batch_size); |         furi_hal_cdc_send(VCP_IF_NUM, (uint8_t*)buffer, batch_size); | ||||||
|  |         furi_check(osMutexRelease(vcp->usb_mutex) == osOK); | ||||||
|  | 
 | ||||||
|         size -= batch_size; |         size -= batch_size; | ||||||
|         buffer += batch_size; |         buffer += batch_size; | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void vcp_state_callback(uint8_t state) { | static void vcp_state_callback(uint8_t state) { | ||||||
|     if (state == 1) |     if (state == 1) { | ||||||
|         osSemaphoreRelease(furi_hal_vcp->tx_semaphore); |         osSemaphoreRelease(vcp->rx_sem); | ||||||
|     else if (furi_hal_vcp->connected) { |         //osSemaphoreRelease(vcp->tx_sem);
 | ||||||
|         furi_hal_vcp->connected = false; |     } | ||||||
|         osSemaphoreRelease(furi_hal_vcp->tx_semaphore); |     else if (vcp->connected) { | ||||||
|  |         vcp->connected = false; | ||||||
|  |         osSemaphoreRelease(vcp->rx_sem); | ||||||
|  |         VcpEvent evt = VcpDisconnect; | ||||||
|  |         osMessageQueuePut(vcp->event_queue, &evt, 0, 0); | ||||||
|  |         //osSemaphoreRelease(vcp->tx_sem);
 | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void vcp_on_cdc_control_line(uint8_t state) { | static void vcp_on_cdc_control_line(uint8_t state) { | ||||||
| 
 |  | ||||||
|     BaseType_t xHigherPriorityTaskWoken = pdFALSE; |  | ||||||
|     // bit 0: DTR state, bit 1: RTS state
 |     // bit 0: DTR state, bit 1: RTS state
 | ||||||
|     // bool dtr = state & 0b01;
 |  | ||||||
|     bool dtr = state & 0b1; |     bool dtr = state & 0b1; | ||||||
| 
 | 
 | ||||||
|     if (dtr) { |     if (dtr) { | ||||||
|         if (!furi_hal_vcp->connected) { |         if (!vcp->connected) { | ||||||
|             furi_hal_vcp->connected = true; |             vcp->connected = true; | ||||||
|             xStreamBufferSendFromISR(furi_hal_vcp->rx_stream, &ascii_soh, 1, &xHigherPriorityTaskWoken); // SOH 
 |             VcpEvent evt = VcpConnect; | ||||||
| 
 |             osMessageQueuePut(vcp->event_queue, &evt, 0, 0); | ||||||
|         } |         } | ||||||
|     } else { |     } else { | ||||||
|         if (furi_hal_vcp->connected) { |         if (vcp->connected) { | ||||||
|             xStreamBufferSendFromISR(furi_hal_vcp->rx_stream, &ascii_eot, 1, &xHigherPriorityTaskWoken); // EOT
 |             VcpEvent evt = VcpDisconnect; | ||||||
|             furi_hal_vcp->connected = false; |             osMessageQueuePut(vcp->event_queue, &evt, 0, 0); | ||||||
|  |             vcp->connected = false; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     osSemaphoreRelease(furi_hal_vcp->tx_semaphore); |     osSemaphoreRelease(vcp->tx_sem); | ||||||
| 
 |     osSemaphoreRelease(vcp->rx_sem); | ||||||
|     portYIELD_FROM_ISR(xHigherPriorityTaskWoken); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void vcp_on_cdc_rx() {  | static void vcp_on_cdc_rx() { | ||||||
|     BaseType_t xHigherPriorityTaskWoken = pdFALSE; |     if (vcp->connected == false) | ||||||
| 
 |         return; | ||||||
|     uint16_t max_len = xStreamBufferSpacesAvailable(furi_hal_vcp->rx_stream); |     osSemaphoreRelease(vcp->rx_sem); | ||||||
|     if (max_len > 0) { |  | ||||||
|         if (max_len > APP_RX_DATA_SIZE) |  | ||||||
|             max_len = APP_RX_DATA_SIZE; |  | ||||||
|         int32_t size = furi_hal_cdc_receive(VCP_IF_NUM, vcp_rx_buf, max_len); |  | ||||||
| 
 |  | ||||||
|         if (size > 0) { |  | ||||||
|             size_t ret = xStreamBufferSendFromISR(furi_hal_vcp->rx_stream, vcp_rx_buf, size, &xHigherPriorityTaskWoken); |  | ||||||
|             furi_check(ret == size); |  | ||||||
|         } |  | ||||||
|     } else { |  | ||||||
|         furi_hal_vcp->rx_stream_full = true; |  | ||||||
|     }; |  | ||||||
| 
 |  | ||||||
|     portYIELD_FROM_ISR(xHigherPriorityTaskWoken); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void vcp_on_cdc_tx_complete() { | static void vcp_on_cdc_tx_complete() { | ||||||
|     osSemaphoreRelease(furi_hal_vcp->tx_semaphore); |     osSemaphoreRelease(vcp->tx_sem); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool furi_hal_vcp_is_connected(void) { | bool furi_hal_vcp_is_connected(void) { | ||||||
|     return furi_hal_vcp->connected; |     return vcp->connected; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -7,12 +7,6 @@ | |||||||
| extern "C" { | extern "C" { | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
| typedef enum { |  | ||||||
|     UartIrqEventRXNE, |  | ||||||
|     UartIrqEventIDLE, |  | ||||||
|     //TODO: more events
 |  | ||||||
| } UartIrqEvent; |  | ||||||
| 
 |  | ||||||
| void furi_hal_console_init(); | void furi_hal_console_init(); | ||||||
| 
 | 
 | ||||||
| void furi_hal_console_enable(); | void furi_hal_console_enable(); | ||||||
|  | |||||||
| @ -182,6 +182,8 @@ void LPUART1_IRQHandler(void) { | |||||||
|     } else if (LL_LPUART_IsActiveFlag_IDLE(LPUART1)) { |     } else if (LL_LPUART_IsActiveFlag_IDLE(LPUART1)) { | ||||||
|         irq_cb[FuriHalUartIdLPUART1](UartIrqEventIDLE, 0); |         irq_cb[FuriHalUartIdLPUART1](UartIrqEventIDLE, 0); | ||||||
|         LL_LPUART_ClearFlag_IDLE(LPUART1); |         LL_LPUART_ClearFlag_IDLE(LPUART1); | ||||||
|  |     } else if (LL_LPUART_IsActiveFlag_ORE(LPUART1)) { | ||||||
|  |         LL_LPUART_ClearFlag_ORE(LPUART1); | ||||||
|     } |     } | ||||||
|     //TODO: more events
 |     //TODO: more events
 | ||||||
| } | } | ||||||
| @ -193,5 +195,7 @@ void USART1_IRQHandler(void) { | |||||||
|     } else if (LL_USART_IsActiveFlag_IDLE(USART1)) { |     } else if (LL_USART_IsActiveFlag_IDLE(USART1)) { | ||||||
|         irq_cb[FuriHalUartIdUSART1](UartIrqEventIDLE, 0); |         irq_cb[FuriHalUartIdUSART1](UartIrqEventIDLE, 0); | ||||||
|         LL_USART_ClearFlag_IDLE(USART1); |         LL_USART_ClearFlag_IDLE(USART1); | ||||||
|  |     } else if (LL_USART_IsActiveFlag_ORE(USART1)) { | ||||||
|  |         LL_USART_ClearFlag_ORE(USART1); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -2,7 +2,6 @@ | |||||||
| 
 | 
 | ||||||
| #include <stddef.h> | #include <stddef.h> | ||||||
| #include <stdint.h> | #include <stdint.h> | ||||||
| #include "furi-hal-console.h" |  | ||||||
| 
 | 
 | ||||||
| #ifdef __cplusplus | #ifdef __cplusplus | ||||||
| extern "C" { | extern "C" { | ||||||
| @ -13,6 +12,11 @@ typedef enum { | |||||||
|     FuriHalUartIdLPUART1, |     FuriHalUartIdLPUART1, | ||||||
| } FuriHalUartId; | } FuriHalUartId; | ||||||
| 
 | 
 | ||||||
|  | typedef enum { | ||||||
|  |     UartIrqEventRXNE, | ||||||
|  |     UartIrqEventIDLE, | ||||||
|  |     //TODO: more events
 | ||||||
|  | } UartIrqEvent; | ||||||
| 
 | 
 | ||||||
| void furi_hal_uart_init(FuriHalUartId ch, uint32_t baud); | void furi_hal_uart_init(FuriHalUartId ch, uint32_t baud); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -7,12 +7,12 @@ | |||||||
| #include "usb_cdc.h" | #include "usb_cdc.h" | ||||||
| 
 | 
 | ||||||
| #define CDC0_RXD_EP      0x01 | #define CDC0_RXD_EP      0x01 | ||||||
| #define CDC0_TXD_EP      0x81 | #define CDC0_TXD_EP      0x82 | ||||||
| #define CDC0_NTF_EP      0x82 | #define CDC0_NTF_EP      0x83 | ||||||
| 
 | 
 | ||||||
| #define CDC1_RXD_EP      0x03 | #define CDC1_RXD_EP      0x04 | ||||||
| #define CDC1_TXD_EP      0x83 | #define CDC1_TXD_EP      0x85 | ||||||
| #define CDC1_NTF_EP      0x84 | #define CDC1_NTF_EP      0x86 | ||||||
| 
 | 
 | ||||||
| #define CDC_NTF_SZ      0x08 | #define CDC_NTF_SZ      0x08 | ||||||
| 
 | 
 | ||||||
| @ -446,10 +446,12 @@ void furi_hal_cdc_send(uint8_t if_num, uint8_t* buf, uint16_t len) { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| int32_t furi_hal_cdc_receive(uint8_t if_num, uint8_t* buf, uint16_t max_len) { | int32_t furi_hal_cdc_receive(uint8_t if_num, uint8_t* buf, uint16_t max_len) { | ||||||
|  |     int32_t len = 0; | ||||||
|     if (if_num == 0) |     if (if_num == 0) | ||||||
|         return usbd_ep_read(usb_dev, CDC0_RXD_EP, buf, max_len); |         len = usbd_ep_read(usb_dev, CDC0_RXD_EP, buf, max_len); | ||||||
|     else |     else | ||||||
|         return usbd_ep_read(usb_dev, CDC1_RXD_EP, buf, max_len); |         len = usbd_ep_read(usb_dev, CDC1_RXD_EP, buf, max_len); | ||||||
|  |     return ((len < 0) ? 0 : len); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void cdc_on_wakeup(usbd_device *dev) { | static void cdc_on_wakeup(usbd_device *dev) { | ||||||
|  | |||||||
| @ -3,18 +3,28 @@ | |||||||
| #include <furi.h> | #include <furi.h> | ||||||
| #include <stream_buffer.h> | #include <stream_buffer.h> | ||||||
| 
 | 
 | ||||||
| #define APP_RX_DATA_SIZE CDC_DATA_SZ | #define USB_CDC_PKT_LEN CDC_DATA_SZ | ||||||
| #define APP_TX_DATA_SIZE CDC_DATA_SZ |  | ||||||
| #define FURI_HAL_VCP_RX_BUFFER_SIZE (APP_RX_DATA_SIZE * 16) |  | ||||||
| #define VCP_IF_NUM 0 | #define VCP_IF_NUM 0 | ||||||
| 
 | 
 | ||||||
|  | typedef enum { | ||||||
|  |     VcpConnect, | ||||||
|  |     VcpDisconnect, | ||||||
|  | } VcpEvent; | ||||||
|  | 
 | ||||||
| typedef struct { | typedef struct { | ||||||
|     volatile bool connected; |     volatile bool connected; | ||||||
| 
 | 
 | ||||||
|     StreamBufferHandle_t rx_stream; |     uint8_t rx_buf[USB_CDC_PKT_LEN]; | ||||||
|     volatile bool rx_stream_full; |     uint8_t rx_buf_start; | ||||||
|  |     uint8_t rx_buf_len; | ||||||
|  | 
 | ||||||
|  |     osMessageQueueId_t event_queue; | ||||||
|  | 
 | ||||||
|  |     osMutexId_t usb_mutex; | ||||||
|  | 
 | ||||||
|  |     osSemaphoreId_t tx_sem; | ||||||
|  |     osSemaphoreId_t rx_sem; | ||||||
| 
 | 
 | ||||||
|     osSemaphoreId_t tx_semaphore; |  | ||||||
| } FuriHalVcp; | } FuriHalVcp; | ||||||
| 
 | 
 | ||||||
| static void vcp_on_cdc_tx_complete(); | static void vcp_on_cdc_tx_complete(); | ||||||
| @ -30,22 +40,21 @@ static CdcCallbacks cdc_cb = { | |||||||
|     NULL, |     NULL, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| static FuriHalVcp* furi_hal_vcp = NULL; | static FuriHalVcp* vcp = NULL; | ||||||
| 
 | 
 | ||||||
| static const uint8_t ascii_soh = 0x01; | static const uint8_t ascii_soh = 0x01; | ||||||
| static const uint8_t ascii_eot = 0x04; | static const uint8_t ascii_eot = 0x04; | ||||||
| 
 | 
 | ||||||
| static uint8_t* vcp_rx_buf; |  | ||||||
| 
 |  | ||||||
| void furi_hal_vcp_init() { | void furi_hal_vcp_init() { | ||||||
|     furi_hal_vcp = furi_alloc(sizeof(FuriHalVcp)); |     vcp = furi_alloc(sizeof(FuriHalVcp)); | ||||||
|     vcp_rx_buf = furi_alloc(APP_RX_DATA_SIZE); |     vcp->connected = false; | ||||||
|     furi_hal_vcp->connected = false; |  | ||||||
|      |  | ||||||
|     furi_hal_vcp->rx_stream = xStreamBufferCreate(FURI_HAL_VCP_RX_BUFFER_SIZE, 1); |  | ||||||
|     furi_hal_vcp->rx_stream_full = false; |  | ||||||
| 
 | 
 | ||||||
|     furi_hal_vcp->tx_semaphore = osSemaphoreNew(1, 1, NULL); |     vcp->usb_mutex = osMutexNew(NULL); | ||||||
|  | 
 | ||||||
|  |     vcp->tx_sem = osSemaphoreNew(1, 1, NULL); | ||||||
|  |     vcp->rx_sem = osSemaphoreNew(1, 0, NULL); | ||||||
|  | 
 | ||||||
|  |     vcp->event_queue = osMessageQueueNew(8, sizeof(VcpEvent), NULL); | ||||||
| 
 | 
 | ||||||
|     furi_hal_cdc_set_callbacks(VCP_IF_NUM, &cdc_cb); |     furi_hal_cdc_set_callbacks(VCP_IF_NUM, &cdc_cb); | ||||||
| 
 | 
 | ||||||
| @ -54,111 +63,149 @@ void furi_hal_vcp_init() { | |||||||
| 
 | 
 | ||||||
| void furi_hal_vcp_enable() { | void furi_hal_vcp_enable() { | ||||||
|     furi_hal_cdc_set_callbacks(VCP_IF_NUM, &cdc_cb); |     furi_hal_cdc_set_callbacks(VCP_IF_NUM, &cdc_cb); | ||||||
|     furi_hal_vcp->connected = true; |     VcpEvent evt = VcpConnect; | ||||||
|  |     osMessageQueuePut(vcp->event_queue, &evt, 0, 0); | ||||||
|  |     vcp->connected = true; | ||||||
|  |     osSemaphoreRelease(vcp->tx_sem); | ||||||
|  |     osSemaphoreRelease(vcp->rx_sem); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void furi_hal_vcp_disable() { | void furi_hal_vcp_disable() { | ||||||
|     furi_hal_cdc_set_callbacks(VCP_IF_NUM, NULL); |     furi_hal_cdc_set_callbacks(VCP_IF_NUM, NULL); | ||||||
|     furi_hal_vcp->connected = false; |     VcpEvent evt = VcpDisconnect; | ||||||
|     osSemaphoreRelease(furi_hal_vcp->tx_semaphore); |     osMessageQueuePut(vcp->event_queue, &evt, 0, 0); | ||||||
| } |     vcp->connected = false; | ||||||
| 
 |     osSemaphoreRelease(vcp->tx_sem); | ||||||
| size_t furi_hal_vcp_rx(uint8_t* buffer, size_t size) { |     osSemaphoreRelease(vcp->rx_sem); | ||||||
|     furi_assert(furi_hal_vcp); |  | ||||||
| 
 |  | ||||||
|     size_t received = xStreamBufferReceive(furi_hal_vcp->rx_stream, buffer, size, portMAX_DELAY); |  | ||||||
| 
 |  | ||||||
|     if(furi_hal_vcp->rx_stream_full |  | ||||||
|         && xStreamBufferSpacesAvailable(furi_hal_vcp->rx_stream) >= APP_RX_DATA_SIZE) { |  | ||||||
|         furi_hal_vcp->rx_stream_full = false; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return received; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| size_t furi_hal_vcp_rx_with_timeout(uint8_t* buffer, size_t size, uint32_t timeout) { | size_t furi_hal_vcp_rx_with_timeout(uint8_t* buffer, size_t size, uint32_t timeout) { | ||||||
|     furi_assert(furi_hal_vcp); |     furi_assert(vcp); | ||||||
|     return xStreamBufferReceive(furi_hal_vcp->rx_stream, buffer, size, timeout); |     furi_assert(buffer); | ||||||
|  | 
 | ||||||
|  |     size_t rx_cnt = 0; | ||||||
|  | 
 | ||||||
|  |     VcpEvent evt = VcpDisconnect; | ||||||
|  | 
 | ||||||
|  |     if (vcp->rx_buf_len > 0) { | ||||||
|  |         size_t len = (vcp->rx_buf_len > size) ? (size) : (vcp->rx_buf_len); | ||||||
|  |         memcpy(&buffer[rx_cnt], &vcp->rx_buf[vcp->rx_buf_start], len); | ||||||
|  |         vcp->rx_buf_len -= len; | ||||||
|  |         vcp->rx_buf_start += len; | ||||||
|  |         rx_cnt += len; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     while (rx_cnt < size) { | ||||||
|  |         if (osMessageQueueGet(vcp->event_queue, &evt, NULL, 0) == osOK) { | ||||||
|  |             if (evt == VcpConnect) | ||||||
|  |                 buffer[rx_cnt] = ascii_soh; | ||||||
|  |             else { | ||||||
|  |                 buffer[rx_cnt] = ascii_eot; | ||||||
|  |                 vcp->rx_buf_len = 0; | ||||||
|  |             } | ||||||
|  |             rx_cnt++; | ||||||
|  |             return rx_cnt; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if (osSemaphoreAcquire(vcp->rx_sem, timeout) == osErrorTimeout) | ||||||
|  |             return rx_cnt; | ||||||
|  |          | ||||||
|  |         furi_check(osMutexAcquire(vcp->usb_mutex, osWaitForever) == osOK); | ||||||
|  |         size_t len = furi_hal_cdc_receive(VCP_IF_NUM, vcp->rx_buf, USB_CDC_PKT_LEN); | ||||||
|  |         furi_check(osMutexRelease(vcp->usb_mutex) == osOK); | ||||||
|  | 
 | ||||||
|  |         vcp->rx_buf_len = len; | ||||||
|  |         vcp->rx_buf_start = 0; | ||||||
|  | 
 | ||||||
|  |         if (vcp->rx_buf_len > (size - rx_cnt)) { | ||||||
|  |             len = size - rx_cnt; | ||||||
|  |             memcpy(&buffer[rx_cnt], vcp->rx_buf, len); | ||||||
|  |             vcp->rx_buf_len -= len; | ||||||
|  |             vcp->rx_buf_start += len; | ||||||
|  |         } else { | ||||||
|  |             memcpy(&buffer[rx_cnt], vcp->rx_buf, vcp->rx_buf_len); | ||||||
|  |             vcp->rx_buf_len = 0; | ||||||
|  |         } | ||||||
|  |         rx_cnt += len; | ||||||
|  |     } | ||||||
|  |     return rx_cnt; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | size_t furi_hal_vcp_rx(uint8_t* buffer, size_t size) { | ||||||
|  |     furi_assert(vcp); | ||||||
|  | 
 | ||||||
|  |     return furi_hal_vcp_rx_with_timeout(buffer, size, portMAX_DELAY); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void furi_hal_vcp_tx(const uint8_t* buffer, size_t size) { | void furi_hal_vcp_tx(const uint8_t* buffer, size_t size) { | ||||||
|     furi_assert(furi_hal_vcp); |     furi_assert(vcp); | ||||||
| 
 | 
 | ||||||
|     while (size > 0 && furi_hal_vcp->connected) { |     while (size > 0 && vcp->connected) { | ||||||
|         furi_check(osSemaphoreAcquire(furi_hal_vcp->tx_semaphore, osWaitForever) == osOK); |         furi_check(osSemaphoreAcquire(vcp->tx_sem, osWaitForever) == osOK); | ||||||
|         if (!furi_hal_vcp->connected) |         if (!vcp->connected) | ||||||
|             break; |             break; | ||||||
| 
 | 
 | ||||||
|         size_t batch_size = size; |         size_t batch_size = size; | ||||||
|         if (batch_size > APP_TX_DATA_SIZE) { |         if (batch_size > USB_CDC_PKT_LEN) { | ||||||
|             batch_size = APP_TX_DATA_SIZE; |             batch_size = USB_CDC_PKT_LEN; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  |         furi_check(osMutexAcquire(vcp->usb_mutex, osWaitForever) == osOK); | ||||||
|         furi_hal_cdc_send(VCP_IF_NUM, (uint8_t*)buffer, batch_size); |         furi_hal_cdc_send(VCP_IF_NUM, (uint8_t*)buffer, batch_size); | ||||||
|  |         furi_check(osMutexRelease(vcp->usb_mutex) == osOK); | ||||||
|  | 
 | ||||||
|         size -= batch_size; |         size -= batch_size; | ||||||
|         buffer += batch_size; |         buffer += batch_size; | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void vcp_state_callback(uint8_t state) { | static void vcp_state_callback(uint8_t state) { | ||||||
|     if (state == 1) |     if (state == 1) { | ||||||
|         osSemaphoreRelease(furi_hal_vcp->tx_semaphore); |         osSemaphoreRelease(vcp->rx_sem); | ||||||
|     else if (furi_hal_vcp->connected) { |         //osSemaphoreRelease(vcp->tx_sem);
 | ||||||
|         furi_hal_vcp->connected = false; |     } | ||||||
|         osSemaphoreRelease(furi_hal_vcp->tx_semaphore); |     else if (vcp->connected) { | ||||||
|  |         vcp->connected = false; | ||||||
|  |         osSemaphoreRelease(vcp->rx_sem); | ||||||
|  |         VcpEvent evt = VcpDisconnect; | ||||||
|  |         osMessageQueuePut(vcp->event_queue, &evt, 0, 0); | ||||||
|  |         //osSemaphoreRelease(vcp->tx_sem);
 | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void vcp_on_cdc_control_line(uint8_t state) { | static void vcp_on_cdc_control_line(uint8_t state) { | ||||||
| 
 |  | ||||||
|     BaseType_t xHigherPriorityTaskWoken = pdFALSE; |  | ||||||
|     // bit 0: DTR state, bit 1: RTS state
 |     // bit 0: DTR state, bit 1: RTS state
 | ||||||
|     // bool dtr = state & 0b01;
 |  | ||||||
|     bool dtr = state & 0b1; |     bool dtr = state & 0b1; | ||||||
| 
 | 
 | ||||||
|     if (dtr) { |     if (dtr) { | ||||||
|         if (!furi_hal_vcp->connected) { |         if (!vcp->connected) { | ||||||
|             furi_hal_vcp->connected = true; |             vcp->connected = true; | ||||||
|             xStreamBufferSendFromISR(furi_hal_vcp->rx_stream, &ascii_soh, 1, &xHigherPriorityTaskWoken); // SOH 
 |             VcpEvent evt = VcpConnect; | ||||||
| 
 |             osMessageQueuePut(vcp->event_queue, &evt, 0, 0); | ||||||
|         } |         } | ||||||
|     } else { |     } else { | ||||||
|         if (furi_hal_vcp->connected) { |         if (vcp->connected) { | ||||||
|             xStreamBufferSendFromISR(furi_hal_vcp->rx_stream, &ascii_eot, 1, &xHigherPriorityTaskWoken); // EOT
 |             VcpEvent evt = VcpDisconnect; | ||||||
|             furi_hal_vcp->connected = false; |             osMessageQueuePut(vcp->event_queue, &evt, 0, 0); | ||||||
|  |             vcp->connected = false; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     osSemaphoreRelease(furi_hal_vcp->tx_semaphore); |     osSemaphoreRelease(vcp->tx_sem); | ||||||
| 
 |     osSemaphoreRelease(vcp->rx_sem); | ||||||
|     portYIELD_FROM_ISR(xHigherPriorityTaskWoken); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void vcp_on_cdc_rx() {  | static void vcp_on_cdc_rx() { | ||||||
|     BaseType_t xHigherPriorityTaskWoken = pdFALSE; |     if (vcp->connected == false) | ||||||
| 
 |         return; | ||||||
|     uint16_t max_len = xStreamBufferSpacesAvailable(furi_hal_vcp->rx_stream); |     osSemaphoreRelease(vcp->rx_sem); | ||||||
|     if (max_len > 0) { |  | ||||||
|         if (max_len > APP_RX_DATA_SIZE) |  | ||||||
|             max_len = APP_RX_DATA_SIZE; |  | ||||||
|         int32_t size = furi_hal_cdc_receive(VCP_IF_NUM, vcp_rx_buf, max_len); |  | ||||||
| 
 |  | ||||||
|         if (size > 0) { |  | ||||||
|             size_t ret = xStreamBufferSendFromISR(furi_hal_vcp->rx_stream, vcp_rx_buf, size, &xHigherPriorityTaskWoken); |  | ||||||
|             furi_check(ret == size); |  | ||||||
|         } |  | ||||||
|     } else { |  | ||||||
|         furi_hal_vcp->rx_stream_full = true; |  | ||||||
|     }; |  | ||||||
| 
 |  | ||||||
|     portYIELD_FROM_ISR(xHigherPriorityTaskWoken); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void vcp_on_cdc_tx_complete() { | static void vcp_on_cdc_tx_complete() { | ||||||
|     osSemaphoreRelease(furi_hal_vcp->tx_semaphore); |     osSemaphoreRelease(vcp->tx_sem); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool furi_hal_vcp_is_connected(void) { | bool furi_hal_vcp_is_connected(void) { | ||||||
|     return furi_hal_vcp->connected; |     return vcp->connected; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,4 +1,5 @@ | |||||||
| import os | import os | ||||||
|  | import sys | ||||||
| import serial | import serial | ||||||
| import time | import time | ||||||
| import hashlib | import hashlib | ||||||
| @ -143,7 +144,7 @@ class FlipperStorage: | |||||||
|         walk_dirs = [] |         walk_dirs = [] | ||||||
| 
 | 
 | ||||||
|         path = path.replace("//", "/") |         path = path.replace("//", "/") | ||||||
|         self.send_and_wait_eol('storage list "' + path + '"\r') |         self.send_and_wait_eol(f'storage list "{path}"\r') | ||||||
|         data = self.read.until(self.CLI_PROMPT) |         data = self.read.until(self.CLI_PROMPT) | ||||||
|         lines = data.split(b"\r\n") |         lines = data.split(b"\r\n") | ||||||
| 
 | 
 | ||||||
| @ -198,9 +199,7 @@ class FlipperStorage: | |||||||
|             if size == 0: |             if size == 0: | ||||||
|                 break |                 break | ||||||
| 
 | 
 | ||||||
|             self.send_and_wait_eol( |             self.send_and_wait_eol(f'storage write_chunk "{filename_to}" {size}\r') | ||||||
|                 'storage write_chunk "' + filename_to + '" ' + str(size) + "\r" |  | ||||||
|             ) |  | ||||||
|             answer = self.read.until(self.CLI_EOL) |             answer = self.read.until(self.CLI_EOL) | ||||||
|             if self.has_error(answer): |             if self.has_error(answer): | ||||||
|                 self.last_error = self.get_error(answer) |                 self.last_error = self.get_error(answer) | ||||||
| @ -214,9 +213,8 @@ class FlipperStorage: | |||||||
|             percent = str(math.ceil(file.tell() / filesize * 100)) |             percent = str(math.ceil(file.tell() / filesize * 100)) | ||||||
|             total_chunks = str(math.ceil(filesize / buffer_size)) |             total_chunks = str(math.ceil(filesize / buffer_size)) | ||||||
|             current_chunk = str(math.ceil(file.tell() / buffer_size)) |             current_chunk = str(math.ceil(file.tell() / buffer_size)) | ||||||
|             print( |             sys.stdout.write(f"\r{percent}%, chunk {current_chunk} of {total_chunks}") | ||||||
|                 percent + "%, chunk " + current_chunk + " of " + total_chunks, end="\r" |             sys.stdout.flush() | ||||||
|             ) |  | ||||||
|         file.close() |         file.close() | ||||||
|         print() |         print() | ||||||
|         return True |         return True | ||||||
| @ -246,9 +244,8 @@ class FlipperStorage: | |||||||
|             percent = str(math.ceil(readed_size / size * 100)) |             percent = str(math.ceil(readed_size / size * 100)) | ||||||
|             total_chunks = str(math.ceil(size / buffer_size)) |             total_chunks = str(math.ceil(size / buffer_size)) | ||||||
|             current_chunk = str(math.ceil(readed_size / buffer_size)) |             current_chunk = str(math.ceil(readed_size / buffer_size)) | ||||||
|             print( |             sys.stdout.write(f"\r{percent}%, chunk {current_chunk} of {total_chunks}") | ||||||
|                 percent + "%, chunk " + current_chunk + " of " + total_chunks, end="\r" |             sys.stdout.flush() | ||||||
|             ) |  | ||||||
|         print() |         print() | ||||||
|         self.read.until(self.CLI_PROMPT) |         self.read.until(self.CLI_PROMPT) | ||||||
|         return filedata |         return filedata | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Nikolay Minaylov
						Nikolay Minaylov