#include #include #include "core/lv_disp.h" #include "core/lv_obj_style.h" #include "driver/gpio.h" #include "driver/gptimer.h" #include "driver/ledc.h" #include "driver/spi_common.h" #include "esp_attr.h" #include "esp_err.h" #include "esp_heap_caps.h" #include "esp_lcd_panel_interface.h" #include "esp_lcd_types.h" #include "esp_log.h" #include "esp_random.h" #include "esp_timer.h" #include "extra/layouts/flex/lv_flex.h" #include "font/lv_font.h" #include "freertos/FreeRTOS.h" #include "freertos/portmacro.h" #include "freertos/projdefs.h" #include "freertos/queue.h" #include "freertos/task.h" #include "hal/gpio_types.h" #include "hal/lcd_types.h" #include "hal/ledc_types.h" #include "hal/lv_hal_disp.h" #include "hal/spi_types.h" #include "hal/timer_types.h" #include "misc/lv_area.h" #include "misc/lv_color.h" #include "misc/lv_timer.h" #include "soc/clk_tree_defs.h" #include "driver/spi_master.h" #include "esp_lcd_panel_io.h" #include "esp_lcd_panel_vendor.h" #include "esp_lcd_panel_ops.h" #include "esp_lcd_ili9341.h" #include "lvgl.h" #include "widgets/lv_label.h" #define LV_LVGL_H_INCLUDE_SIMPLE #include "pixel_operator_24.c" /* Inputs */ /* Controls */ #define GPIO_MUTATE_BTN (9) /* Mutate Button In */ #define GPIO_TAP_TEMPO_BTN (10) /* Tap Tempo Button In */ static QueueHandle_t control_event_queue = NULL; /* Outputs */ #define GPIO_CLK_OUT (7) /* Clock Out */ #define GPIO_GATE_OUT (8) /* Gate CV Out */ /* LED GPIO Pins */ #define LEDC_CLK_OUT (5) #define LEDC_GATE_OUT (6) /* SPI LCD Config and Pins */ #define LCD_V_RES 240 #define LCD_H_RES 320 #define LCD_SCLK_PIN 40 #define LCD_MOSI_PIN 39 #define LCD_MISO_PIN 42 #define LCD_DC_PIN 38 #define LCD_RST_PIN 37 #define LCD_CS_PIN 36 #define LCD_BK_LIGHT_PIN 41 #define LVGL_TICK_MS 2 /* Timers */ static gptimer_handle_t tap_tempo_timer = NULL; static gptimer_handle_t gate_timer = NULL; static const uint16_t DEBOUNCE_TIMEOUT = 10; /* polling period length when debouncing in ms */ static uint64_t debounce_time = 0; /* system time of last intterupt */ static uint8_t btn_state = 1; /* state of gpio last recorded */ portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED; static const uint32_t DEFAULT_BPM = 120; static uint16_t current_bpm = DEFAULT_BPM; lv_obj_t * bpm_label = NULL; #define SEQ_MAX_LEN 8 static uint8_t sequence[SEQ_MAX_LEN] = {0}; static uint8_t seq_len = SEQ_MAX_LEN; lv_obj_t * sequence_label = NULL; lv_obj_t * sequence_tracker = NULL; static const char *TAG = "sequencer"; uint32_t bpm_to_ms(uint32_t bpm) { return 60000 / bpm; } uint32_t ms_to_bpm(uint32_t ms) { return 60000 / ms; } static void update_bpm(void) { lv_label_set_text_fmt(bpm_label, "BPM: %d", current_bpm); } /* updates the sequence for the GUI display */ static void update_seq(uint8_t *sequence) { char seq_str[seq_len]; for (uint8_t i = 0; i < seq_len; i++) { if (sequence[i] == 1) { seq_str[i] = 'x'; } else { seq_str[i] = 'o'; } } ESP_LOGI(TAG, "Sequence Updated to %s", seq_str); lv_label_set_text(sequence_label, seq_str); } static void randomize_seq(uint8_t *sequence) { for (uint8_t i = 0; i < SEQ_MAX_LEN; i++) { sequence[i] = esp_random() % 2; } update_seq(sequence); } static void mutate_seq(uint8_t *sequence) { ESP_LOGI(TAG, "Mutating Sequence"); /* pick a step to mutate */ uint8_t step = esp_random() % SEQ_MAX_LEN; if (sequence[step] == 1) { sequence[step] = 0; } else if (sequence[step] == 0) { sequence[step] = 1; } update_seq(sequence); } /* Handle tap tempo button presses */ static void tap_tempo_handler(){ /* check the count to see if we have already started the timer */ uint64_t count; ESP_ERROR_CHECK(gptimer_get_raw_count(tap_tempo_timer, &count)); if (count == 0) { /* the timer hasn't started yet */ ESP_ERROR_CHECK(gptimer_start(tap_tempo_timer)); ESP_LOGI(TAG, "Starting tap tempo monitoring"); } else { /* stop the timer and calc bpm */ ESP_ERROR_CHECK(gptimer_stop(tap_tempo_timer)); ESP_ERROR_CHECK(gptimer_set_raw_count(tap_tempo_timer, 0)); current_bpm = ms_to_bpm(count / 1000); update_bpm(); ESP_LOGI(TAG, "Stopping tap tempo monitoring at %" PRIu64 "ms", count / 1000); } } static void IRAM_ATTR gpio_isr_handler(void* arg) { uint32_t gpio_num = (uint32_t)arg; portENTER_CRITICAL_ISR(&mux); debounce_time = xTaskGetTickCount(); /* set time of interrupt */ btn_state = gpio_get_level(gpio_num); /* set state */ portEXIT_CRITICAL_ISR(&mux); xQueueSendFromISR(control_event_queue, &gpio_num, NULL); } static void control_event_handler(void* arg) { uint32_t gpio_num; uint8_t last_btn_state; uint64_t last_debounce_time; while (1) { /* save these so we can make comparisons */ portENTER_CRITICAL_ISR(&mux); last_btn_state = btn_state; last_debounce_time = debounce_time; portEXIT_CRITICAL_ISR(&mux); uint8_t current_state = gpio_get_level(gpio_num); if (xQueueReceive(control_event_queue, &gpio_num, portMAX_DELAY)) { /* Check if interrupt triggered stays the same for long enough */ if ((current_state == last_btn_state) && (xTaskGetTickCount() - last_debounce_time > DEBOUNCE_TIMEOUT)) { ESP_LOGI(TAG, "Handling control from GPIO %" PRIu32, gpio_num); switch (gpio_num) { case GPIO_MUTATE_BTN: mutate_seq(sequence); break; case GPIO_TAP_TEMPO_BTN: tap_tempo_handler(); break; } } } } } static void controls_init(void) { /* install gpio isr hservice */ gpio_install_isr_service(0); /* Setup Mutate Button */ gpio_reset_pin(GPIO_MUTATE_BTN); gpio_set_direction(GPIO_MUTATE_BTN, GPIO_MODE_INPUT); gpio_set_intr_type(GPIO_MUTATE_BTN, GPIO_INTR_NEGEDGE); /* Hook in handler for mutate button */ gpio_isr_handler_add(GPIO_MUTATE_BTN, gpio_isr_handler, (void*)GPIO_MUTATE_BTN); /* Setup tap tempo Button */ gpio_reset_pin(GPIO_TAP_TEMPO_BTN); gpio_set_direction(GPIO_TAP_TEMPO_BTN, GPIO_MODE_INPUT); gpio_set_intr_type(GPIO_TAP_TEMPO_BTN, GPIO_INTR_NEGEDGE); /* Hook in handler for tap tempo button */ gpio_isr_handler_add(GPIO_TAP_TEMPO_BTN, gpio_isr_handler, (void *)GPIO_TAP_TEMPO_BTN); /* Setup tap tempo timer */ gptimer_config_t tap_tempo_timer_config = { .clk_src = GPTIMER_CLK_SRC_DEFAULT, .direction = GPTIMER_COUNT_UP, .resolution_hz = 1 * 1000 * 1000 /* 1MHz; 1 tick = 1 µs */ }; ESP_ERROR_CHECK(gptimer_new_timer(&tap_tempo_timer_config, &tap_tempo_timer)); ESP_ERROR_CHECK(gptimer_enable(tap_tempo_timer)); } static void gate_off_alarm_cb(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_data) { gptimer_stop(gate_timer); gpio_set_level(GPIO_GATE_OUT, 0); gpio_set_level(GPIO_CLK_OUT, 0); ESP_ERROR_CHECK(ledc_set_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_1, 0)); ESP_ERROR_CHECK(ledc_update_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_1)); ESP_ERROR_CHECK(ledc_set_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, 0)); ESP_ERROR_CHECK(ledc_update_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0)); } static void gpio_init(void) { /* Setup gpio control event queue */ control_event_queue = xQueueCreate(10, sizeof(uint32_t)); /* Setup Clock CV Output */ gpio_reset_pin(GPIO_CLK_OUT); gpio_set_direction(GPIO_CLK_OUT, GPIO_MODE_OUTPUT); /* Setup Gate CV Output */ gpio_reset_pin(GPIO_GATE_OUT); gpio_set_direction(GPIO_GATE_OUT, GPIO_MODE_OUTPUT); /* Setup gate timer */ gptimer_config_t cv_out_timer_config = { .clk_src = GPTIMER_CLK_SRC_DEFAULT, .direction = GPTIMER_COUNT_UP, .resolution_hz = 1 * 1000 * 1000 /* 1MHz; 1 tick = 1 µs */ }; ESP_ERROR_CHECK(gptimer_new_timer(&cv_out_timer_config, &gate_timer)); /* Configure gate timer alarm */ gptimer_alarm_config_t cv_off_alarm_config = { .alarm_count = 20 * 1000, /* 20ms @ 1MHz */ .reload_count = 0, .flags.auto_reload_on_alarm = true }; ESP_ERROR_CHECK(gptimer_set_alarm_action(gate_timer, &cv_off_alarm_config)); /* Register gate timer callback */ gptimer_event_callbacks_t clk_cbs = { .on_alarm = gate_off_alarm_cb }; ESP_ERROR_CHECK(gptimer_register_event_callbacks(gate_timer, &clk_cbs, NULL)); ESP_ERROR_CHECK(gptimer_enable(gate_timer)); } static void ledc_init(void) { /* Setup LEDC PWM timer configuration */ ledc_timer_config_t ledc_timer = { .speed_mode = LEDC_LOW_SPEED_MODE, .timer_num = LEDC_TIMER_0, .duty_resolution = LEDC_TIMER_13_BIT, .freq_hz = 5000, .clk_cfg = LEDC_AUTO_CLK }; ESP_ERROR_CHECK(ledc_timer_config(&ledc_timer)); /* Setup Clock LED */ ledc_channel_config_t ledc_clk_channel = { .channel = LEDC_CHANNEL_0, .gpio_num = LEDC_CLK_OUT, .speed_mode = LEDC_LOW_SPEED_MODE, .timer_sel = LEDC_TIMER_0, .intr_type = LEDC_INTR_DISABLE, .duty = 0, .hpoint = 0 }; ESP_ERROR_CHECK(ledc_channel_config(&ledc_clk_channel)); /* Setup Gate LED */ ledc_channel_config_t ledc_gate_channel = { .channel = LEDC_CHANNEL_1, .gpio_num = LEDC_GATE_OUT, .speed_mode = LEDC_LOW_SPEED_MODE, .timer_sel = LEDC_TIMER_0, .intr_type = LEDC_INTR_DISABLE, .duty = 0, .hpoint = 0 }; ESP_ERROR_CHECK(ledc_channel_config(&ledc_gate_channel)); } static bool lvgl_notify_flush_ready(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx) { lv_disp_drv_t *disp_driver = (lv_disp_drv_t *)user_ctx; lv_disp_flush_ready(disp_driver); return false; } static void lvgl_flush_cb(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t *color_map) { esp_lcd_panel_handle_t panel_handle = (esp_lcd_panel_handle_t)drv->user_data; int offsetx1 = area->x1; int offsetx2 = area->x2; int offsety1 = area->y1; int offsety2 = area->y2; esp_lcd_panel_draw_bitmap(panel_handle, offsetx1, offsety1, offsetx2 + 1, offsety2 + 1, color_map); } static void lvgl_port_update_cb(lv_disp_drv_t *drv) { esp_lcd_panel_handle_t panel_handle = (esp_lcd_panel_handle_t)drv->user_data; } static void lvgl_increase_tick(void *arg) { lv_tick_inc(LVGL_TICK_MS); } /* Setup for TFT LCD Display and LVGL GUI */ static void display_init(void) { ESP_LOGI(TAG, "Initialing LCD"); /* Configure backlight */ ledc_channel_config_t ledc_lcd_bk_light_channel = { .channel = LEDC_CHANNEL_2, .gpio_num = LCD_BK_LIGHT_PIN, .speed_mode = LEDC_LOW_SPEED_MODE, .timer_sel = LEDC_TIMER_0, .intr_type = LEDC_INTR_DISABLE, .duty = 6000, .hpoint = 0 }; ESP_ERROR_CHECK(ledc_channel_config(&ledc_lcd_bk_light_channel)); /* Setup SPI Bus */ spi_bus_config_t spi_cfg = { .sclk_io_num = LCD_SCLK_PIN, .mosi_io_num = LCD_MOSI_PIN, .miso_io_num = LCD_MISO_PIN, .quadhd_io_num = -1, .quadwp_io_num = -1, .max_transfer_sz = LCD_H_RES * 80 * sizeof(uint16_t) }; ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &spi_cfg, SPI_DMA_CH_AUTO)); /* Setup Panel IO */ static lv_disp_drv_t disp_drv; esp_lcd_panel_io_handle_t lcd_io_handle = NULL; esp_lcd_panel_io_spi_config_t lcd_spi_config = { .dc_gpio_num = LCD_DC_PIN, .cs_gpio_num = LCD_CS_PIN, .pclk_hz = 20 * 1000 * 1000, .lcd_cmd_bits = 8, .lcd_param_bits = 8, .spi_mode = 0, .trans_queue_depth = 10, .on_color_trans_done = lvgl_notify_flush_ready, .user_ctx = &disp_drv }; ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)SPI2_HOST, &lcd_spi_config, &lcd_io_handle)); /* Setup ili9341 panel driver */ esp_lcd_panel_handle_t lcd_handle = NULL; esp_lcd_panel_dev_config_t lcd_config = { .reset_gpio_num = LCD_RST_PIN, .rgb_endian = LCD_RGB_ENDIAN_BGR, .bits_per_pixel = 16 }; ESP_ERROR_CHECK(esp_lcd_new_panel_ili9341(lcd_io_handle, &lcd_config, &lcd_handle)); ESP_ERROR_CHECK(esp_lcd_panel_reset(lcd_handle)); ESP_ERROR_CHECK(esp_lcd_panel_init(lcd_handle)); /* Turn on the panel */ ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(lcd_handle, true)); ESP_LOGI(TAG, "Initalizing LVGL"); lv_init(); /* Allocate draw buffers */ lv_color_t *buf1 = heap_caps_malloc(LCD_H_RES * 80 * sizeof(lv_color_t), MALLOC_CAP_DMA); lv_color_t *buf2 = heap_caps_malloc(LCD_H_RES * 80 * sizeof(lv_color_t), MALLOC_CAP_DMA); /* Initialize draw buffers */ static lv_disp_draw_buf_t disp_buf; lv_disp_draw_buf_init(&disp_buf, buf1, buf2, LCD_H_RES * 80); /* Initialize display driver then register it */ lv_disp_drv_init(&disp_drv); disp_drv.hor_res = LCD_H_RES; disp_drv.ver_res = LCD_V_RES; disp_drv.flush_cb = lvgl_flush_cb; disp_drv.drv_update_cb = lvgl_port_update_cb; disp_drv.draw_buf = &disp_buf; disp_drv.user_data = lcd_handle; lv_disp_t *disp = lv_disp_drv_register(&disp_drv); /* Install LVGL tick timer */ esp_timer_handle_t lvgl_tick_timer = NULL; const esp_timer_create_args_t lvgl_tick_timer_args = { .callback = &lvgl_increase_tick, .name = "lvgl_tick" }; ESP_ERROR_CHECK(esp_timer_create(&lvgl_tick_timer_args, &lvgl_tick_timer)); ESP_ERROR_CHECK(esp_timer_start_periodic(lvgl_tick_timer, LVGL_TICK_MS * 1000)); } static void gui_create(void) { /* Setup flex layout */ /* lv_obj_t * seq_row = lv_obj_create(lv_scr_act()); */ /* lv_obj_set_size(seq_row, 300, 70); */ /* lv_obj_set_flex_flow(seq_row, LV_FLEX_FLOW_ROW); */ /* Initialize style */ static lv_style_t gui_style; lv_style_init(&gui_style); /* Set background color */ /* lv_style_set_bg_color(&gui_style, lv_color_hex(0xa1e5d0)); */ /* Load Font https://www.dafont.com/pixel-operator.font */ /* Converted with https://lvgl.io/tools/fontconverter */ LV_FONT_DECLARE(pixel_operator_24); /* Set the font in the style */ lv_style_set_text_font(&gui_style, &pixel_operator_24); lv_style_set_text_color(&gui_style, lv_color_hex(0x000000)); /* Create and set Title lable text */ lv_obj_t * title_label = lv_label_create(lv_scr_act()); lv_label_set_text(title_label, "ESP32 Sequencer\n"); lv_obj_add_style(title_label, &gui_style, LV_STATE_DEFAULT); lv_obj_align(title_label, LV_ALIGN_TOP_MID, 0, 5); /* Create and set BPM lable text */ bpm_label = lv_label_create(lv_scr_act()); lv_label_set_text_fmt(bpm_label, "BPM: %d", current_bpm); lv_obj_add_style(bpm_label, &gui_style, LV_STATE_DEFAULT); lv_obj_align(bpm_label, LV_ALIGN_BOTTOM_LEFT, 5, -5); /* Create sequence text area */ sequence_label = lv_label_create(lv_scr_act()); lv_label_set_text(sequence_label, "oooooooo"); lv_obj_add_style(sequence_label, &gui_style, LV_STATE_DEFAULT); lv_obj_align(sequence_label, LV_ALIGN_CENTER, 0, 0); /* Create sequence tracker text area */ sequence_tracker = lv_label_create(lv_scr_act()); lv_obj_add_style(sequence_tracker, &gui_style, LV_STATE_DEFAULT); lv_label_set_text(sequence_tracker, "_ "); lv_obj_align(sequence_tracker, LV_ALIGN_CENTER, 0, 5); /* slightly lower than main sequence */ } static void gui_task(void *pvParameter) { while (1) { vTaskDelay(pdMS_TO_TICKS(10)); lv_task_handler(); } } static void pulse_cv(bool triggered) { ESP_ERROR_CHECK(gptimer_start(gate_timer)); ESP_ERROR_CHECK(ledc_set_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, 4095)); ESP_ERROR_CHECK(ledc_update_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0)); gpio_set_level(GPIO_CLK_OUT, 1); if (triggered) { gpio_set_level(GPIO_GATE_OUT, 1); ESP_ERROR_CHECK(ledc_set_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_1, 4095)); ESP_ERROR_CHECK(ledc_update_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_1)); } } void app_main(void) { /* Initialize Peripherals */ /* Initialize GPIO */ gpio_init(); controls_init(); /* Initialize LCD */ display_init(); gui_create(); xTaskCreatePinnedToCore(gui_task, "gui", 4096*2, NULL, 0, NULL, 1); /* Start task to handle control events */ xTaskCreate(control_event_handler, "control event handler", 2048, NULL, 10, NULL); /* Initialize Monitor LEDs */ ledc_init(); /* Initialize sequence */ randomize_seq(sequence); /* Main Loop */ while (1) { char tracker_str[seq_len]; for (uint8_t step = 0; step < seq_len; step++) { /* advance step tracker */ if (step == 0) { tracker_str[seq_len - 1] = ' '; } else { tracker_str[step - 1] = ' '; } tracker_str[step] = '_'; /* set the new sequence label */ lv_label_set_text(sequence_tracker, tracker_str); /* if step has been set for sequence */ if (sequence[step] == 1) { pulse_cv(true); } else { pulse_cv(false); } vTaskDelay(bpm_to_ms(current_bpm)/ portTICK_PERIOD_MS); } } }