#include "logic_analyzer_app.h" #include "logic_analyzer_icons.h" #define COUNT(x) ((size_t)(sizeof(x) / sizeof((x)[0]))) static void render_callback(Canvas* const canvas, void* cb_ctx); static const GpioPin* gpios[] = { &gpio_ext_pc0, &gpio_ext_pc1, &gpio_ext_pc3, &gpio_ext_pb2, &gpio_ext_pb3, &gpio_ext_pa4, &gpio_ext_pa6, &gpio_ext_pa7}; // static const char* gpio_names[] = {"PC0", "PC1", "PC3", "PB2", "PB3", "PA4", "PA6", "PA7"}; static void render_callback(Canvas* const canvas, void* cb_ctx) { AppFSM* app = cb_ctx; furi_mutex_acquire(app->mutex, FuriWaitForever); if(app == NULL) { return; } if(!app->processing) { furi_mutex_release(app->mutex); return; } char buffer[64]; int y = 10; canvas_draw_frame(canvas, 0, 0, 128, 64); canvas_draw_str_aligned(canvas, 5, y, AlignLeft, AlignBottom, "State"); y += 10; if(app->uart) { UsbUartState st; usb_uart_get_state(app->uart, &st); snprintf(buffer, sizeof(buffer), "Rx %ld / Tx %ld", st.rx_cnt, st.tx_cnt); canvas_draw_str_aligned(canvas, 5, y, AlignLeft, AlignBottom, buffer); y += 20; } canvas_set_font(canvas, FontSecondary); if(app->sump) { snprintf( buffer, sizeof(buffer), "%02X %lX %ld %lX %lX %X", app->sump->flags, app->sump->divider, app->sump->delay_count, app->sump->trig_mask, app->sump->trig_values, app->sump->trig_config); canvas_draw_str_aligned(canvas, 5, y, AlignLeft, AlignBottom, buffer); y += 10; if(app->sump->armed) { snprintf( buffer, sizeof(buffer), "Captured: %u / %ld", app->capture_pos, app->sump->read_count); canvas_draw_str_aligned(canvas, 5, y, AlignLeft, AlignBottom, buffer); y += 20; } if(app->sump->armed) { elements_button_center(canvas, "Trigger"); } } furi_mutex_release(app->mutex); } static void input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) { furi_assert(event_queue); /* better skip than sorry */ if(furi_message_queue_get_count(event_queue) < QUEUE_SIZE) { AppEvent event = {.type = EventKeyPress, .input = *input_event}; furi_message_queue_put(event_queue, &event, 100); } } static bool message_process(AppFSM* app) { bool processing = true; AppEvent event; FuriStatus event_status = furi_message_queue_get(app->event_queue, &event, 100); if(event_status != FuriStatusOk) { return true; } switch(event.type) { case EventKeyPress: { if(event.input.type != InputTypePress) { break; } switch(event.input.key) { case InputKeyUp: break; case InputKeyDown: break; case InputKeyRight: break; case InputKeyLeft: break; case InputKeyOk: /* when armed, trigger by pressing the button */ if(app->sump->armed) { for(size_t pos = app->capture_pos; pos < app->sump->read_count; pos++) { app->capture_buffer[app->sump->read_count - 1 - pos] = 0; } app->sump->armed = false; AppEvent event = {.type = EventBufferFilled}; furi_message_queue_put(app->event_queue, &event, 100); } break; case InputKeyBack: processing = false; break; default: break; } break; } case EventBufferFilled: { usb_uart_tx_data(app->uart, app->capture_buffer, app->sump->read_count); break; } default: { break; } } return processing; } size_t data_received(void* ctx, uint8_t* data, size_t length) { AppFSM* app = (AppFSM*)ctx; snprintf( app->state_string, sizeof(app->state_string), "Rx: %02x '%c' (total %u)", data[0], data[0], length); return sump_handle(app->sump, data, length); } void tx_sump_tx(void* ctx, uint8_t* data, size_t length) { AppFSM* app = (AppFSM*)ctx; usb_uart_tx_data(app->uart, data, length); } static uint8_t levels_get(AppFSM* app) { UNUSED(app); uint32_t port_a = GPIOA->IDR; uint32_t port_b = GPIOB->IDR; uint32_t port_c = GPIOC->IDR; /* 7 6 5 4 3 2 1 0 A7 A6 A4 B3 B2 C3 C1 C0 */ uint8_t ret = (port_a & 0xC0) | ((port_a & 0x10) << 1) | ((port_b & 0x0C) << 1) | ((port_c & 0x08) >> 1) | (port_c & 0x03); return ret; } static int32_t capture_thread_worker(void* context) { AppFSM* app = (AppFSM*)context; uint8_t prev_levels = 0; while(app->processing) { app->current_levels = levels_get(app); if(app->sump->armed) { uint8_t relevant_levels = app->current_levels & app->sump->trig_mask; uint8_t prev_relevant_levels = prev_levels & app->sump->trig_mask; if(relevant_levels != prev_relevant_levels) { prev_levels = app->current_levels; app->capture_buffer[app->sump->read_count - 1 - app->capture_pos++] = app->current_levels; if(app->capture_pos >= app->sump->read_count) { app->sump->armed = false; AppEvent event = {.type = EventBufferFilled}; furi_message_queue_put(app->event_queue, &event, 100); } } } else { prev_levels = app->current_levels; app->capture_pos = 0; app->triggered = false; prev_levels = 0; furi_delay_ms(50); } } return 0; } static bool app_init(AppFSM* const app) { strcpy(app->state_string, "none"); app->mutex = furi_mutex_alloc(FuriMutexTypeNormal); if(!app->mutex) { FURI_LOG_E(TAG, "cannot create mutex\r\n"); free(app); return false; } app->processing = true; app->notification = furi_record_open(RECORD_NOTIFICATION); app->gui = furi_record_open(RECORD_GUI); app->dialogs = furi_record_open(RECORD_DIALOGS); app->storage = furi_record_open(RECORD_STORAGE); app->view_port = view_port_alloc(); app->event_queue = furi_message_queue_alloc(QUEUE_SIZE, sizeof(AppEvent)); view_port_draw_callback_set(app->view_port, render_callback, app); view_port_input_callback_set(app->view_port, input_callback, app->event_queue); gui_add_view_port(app->gui, app->view_port, GuiLayerFullscreen); UsbUartConfig uart_config; uart_config.vcp_ch = 1; uart_config.rx_data = &data_received; uart_config.rx_data_ctx = app; app->uart = usb_uart_enable(&uart_config); app->sump = sump_alloc(); app->sump->tx_data = tx_sump_tx; app->sump->tx_data_ctx = app; app->capture_buffer = malloc(MAX_SAMPLE_MEM); for(size_t io = 0; io < COUNT(gpios); io++) { furi_hal_gpio_init(gpios[io], GpioModeInput, GpioPullNo, GpioSpeedVeryHigh); } app->capture_thread = furi_thread_alloc_ex("capture_thread", 1024, capture_thread_worker, app); furi_thread_start(app->capture_thread); return true; } static void app_deinit(AppFSM* const app) { view_port_enabled_set(app->view_port, false); gui_remove_view_port(app->gui, app->view_port); view_port_free(app->view_port); furi_message_queue_free(app->event_queue); furi_mutex_free(app->mutex); furi_thread_join(app->capture_thread); furi_thread_free(app->capture_thread); free(app->capture_buffer); sump_free(app->sump); usb_uart_disable(app->uart); furi_record_close(RECORD_STORAGE); furi_record_close(RECORD_DIALOGS); furi_record_close(RECORD_GUI); furi_record_close(RECORD_NOTIFICATION); } int32_t logic_analyzer_app_main(void* p) { UNUSED(p); AppFSM* app = malloc(sizeof(AppFSM)); app_init(app); dolphin_deed(DolphinDeedPluginGameStart); notification_message_block(app->notification, &sequence_display_backlight_enforce_on); while(app->processing) { app->processing = message_process(app); view_port_update(app->view_port); } notification_message_block(app->notification, &sequence_display_backlight_enforce_auto); app_deinit(app); free(app); return 0; }