This repository has been archived on 2023-02-06. You can view files and clone it, but cannot push or open issues/pull-requests.
esp32-sequencer/main/esp32-sequencer.c

526 lines
16 KiB
C

#include <stdio.h>
#include <stdint.h>
#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);
}
}
}