/* SPDX-License-Identifier: GPL-3.0-or-later * Copyright 2022-2023 King Kévin */ #include #include #include #include #include "esp_log.h" #include "esp_mac.h" #include "esp_sleep.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/queue.h" #include "tinyusb.h" #include "tusb_cdc_acm.h" #include "tusb_dfu_rt.h" #include "tusb_console.h" #include "sdkconfig.h" #include "driver/gpio.h" #include "driver/spi_slave.h" // get the length of an array #define LENGTH(x) (sizeof(x) / sizeof((x)[0])) // GPIO to force DFU mode (on high) #define DFU_PIN 14 // GPIO for on-board LED (WEMOS S2 mini, source on) #define LED_BOARD 15 // connect GPIO to test pad 3 #define GPIO_POWER (16U) // connect GPIO to test pad DATA #define GPIO_MOSI (18U) // connect GPIO to test pad WR #define GPIO_SCLK (35U) // connect GPIO to test pad CS #define GPIO_CS (33U) static const char *TAG = "main"; /* the MCU sends 1 or 17 bytes frame the to LCD controller * the below described which bit corresponds to which segment * digit 1 is left most on the LCD, bit 7 is the most significant bit. * * byte 0: * - 0x80: initialize LCD controller (1 byte frame) * - 0xa0: set segments (17 bytes frame) * * byte 1: * - bit 7: no segment * - bit 6: no segment * - bit 5: battery low level * - bit 4: battery mid level * - bit 3: battery high level * - bit 2: OVER * - bit 1: USB * - bit 0: Bluetooth icon * * byte 2: * - bit 7: battery case * - bit 6: MAX * - bit 5: MIN * - bit 4: HOLD * - bit 3: digit 1, segment b and segment c * - bit 2: digit 2, segment f * - bit 1: digit 2, segment g * - bit 0: digit 2, segment e * * byte 3: * - bit 7: FAST * - bit 6: digit 2, segment a * - bit 5: digit 2, segment b * - bit 4: digit 2, segment c * - bit 3: digit 2, segment d * - bit 2: digit 3, segment f * - bit 1: digit 3, segment g * - bot 0: digit 3, segment e * * byte 4: * - bit 7: SLOW * - bit 6: digit 3, segment a * - bit 5: digit 3, segment b * - bit 4: digit 3, segment c * - bit 3: digit 3, segment d * - bit 2: no segment * - bit 1: no segment * - bit 0: digit 3, segment dp * * byte 5: * - bit 7: dBC * - bit 6: digit 4, segment f * - bit 5: digit 4, segment g * - bit 4: digit 4, segment e * - bit 3: dBA * - bit 2: digit 4, segment a * - bit 1: digit 4, segment b * - bit 0: digit 4, segment c * * byte 6: * - bit 7: digit 4, segment d * * all other bits can be set to any value */ // number of bytes in an LCD frame #define FRAME_SIZE (17U) /** which bit should be set to enable which segment in specific digits * @note 0 = byte 0 bit 0 */ static const uint8_t digit_segments[4][8] = { { // digit 1 0, // a (segment does not exist) 16 + 3, // b 16 + 3, // c 0, // d (segment does not exist) 0, // e (segment does not exist) 0, // f (segment does not exist) 0, // g (segment does not exist) 0, // dp (segment does not exist) }, { // digit 2 24 + 6, // a 24 + 5, // b 24 + 4, // c 24 + 3, // d 16 + 0, // e 16 + 2, // f 16 + 1, // g 0, // dp (segment does not exist) }, { // digit 3 32 + 6, // a 32 + 5, // b 32 + 4, // c 32 + 3, // d 24 + 0, // e 24 + 2, // f 24 + 1, // g 0, // dp (actually 24 + 0 but we don't use it) }, { // digit 4 40 + 2, // a 40 + 1, // b 40 + 0, // c 48 + 7, // d 40 + 4, // e 40 + 6, // f 40 + 5, // g 0, // dp (segment does not exist) }, }; /** the index corresponds to the digit to be displayed, the value represent the segments to be enabled * @note LSb = a, MSb = dp */ static const uint8_t number_segments[10] = { 0x3f, // 0: a b c d e f 0x06, // 1: b c 0x5b, // 2: a b d e g 0x4f, // 3: a b c d g 0x66, // 4: b c f g 0x6d, // 5: a c d f g 0x7d, // 6: a c d e f g 0x07, // 7: a b c 0x7f, // 8: a b c d e f g 0x6f, // 9; a c b d f g }; /** * @brief USB string descriptor */ static const char* usb_str_desc[USB_STRING_DESCRIPTOR_ARRAY_SIZE] = { (char[]){0x09, 0x04}, // 0: language: English CONFIG_TINYUSB_DESC_MANUFACTURER_STRING, // 1: Manufacturer CONFIG_TINYUSB_DESC_PRODUCT_STRING, // 2: Product CONFIG_TINYUSB_DESC_SERIAL_STRING, // 3: Serials, should use chip ID "GM1532 measurements", // 4: CDC Interface "", // 5: MSC Interface "DFU (runtime mode)" // 6: DFU RT }; static QueueHandle_t meas_queue; // queue for the measurement messages received #define MEAS_QUEUE_SIZE 4 // max number of control message received /* handle measurement received */ static void meas_task(void *pvParameter) { spi_slave_transaction_t t; while (xQueueReceive(meas_queue, &t, portMAX_DELAY) == pdTRUE) { const uint8_t* rx_buffer = t.rx_buffer; // t.rx_buffer is type-less if (t.trans_len > 6 && 0xa0 == rx_buffer[0]) { // frame long enough for the LCD data // extract digit segments uint8_t segments[4] = {0}; // which digit segments are enabled for (uint8_t digit = 0; digit < LENGTH(segments); digit++) { segments[digit] = 0; // clear all segments for (uint8_t seg = 0; seg < 8; seg++) { if (rx_buffer[digit_segments[digit][seg] / 8] & (1 << (digit_segments[digit][seg] % 8))) { segments[digit] |= (1 << seg); } } } // figure out digit value uint8_t digits[4] = {0}; for (uint8_t digit = 0; digit < LENGTH(digits); digit++) { for (uint8_t i = 0; i < LENGTH(number_segments); i++) { if (number_segments[i] == segments[digit]) { digits[digit] = i; } } } const uint16_t meas = digits[0] * 1000 + digits[1] * 100 + digits[2] * 10 + digits[3]; // in deci dBA if (meas < 1400) { // ignore all segments on out of range values printf("%u.%u dBA\n", meas / 10, meas % 10); } } } } // called after LCD frame (e.g. SPI transaction) is received static uint8_t timeout = 0; void post_trans_cb(spi_slave_transaction_t *t) { if (t) { ESP_ERROR_CHECK( xQueueSend(meas_queue, t, 512) != pdTRUE ); // send measurement to be displayed spi_slave_queue_trans(SPI2_HOST, t, portMAX_DELAY); // re-add transaction } timeout = 0; // reset timeout since we received data } void app_main(void) { // check DFU force gpio_config_t io_conf = {}; // to configure GPIO // GPIO0 can also be pressed while soft reboot io_conf.pin_bit_mask = (1ULL << 0); // GPIO to configure io_conf.intr_type = GPIO_INTR_DISABLE; // disable interrupt io_conf.mode = GPIO_MODE_INPUT; // set as input io_conf.pull_down_en = false; // disable pull-down mode io_conf.pull_up_en = true; // enable pull-up mode ESP_ERROR_CHECK( gpio_config(&io_conf) ); // configure GPIO // dedicated DFU button io_conf.pin_bit_mask = (1ULL << DFU_PIN); // GPIO to configure io_conf.intr_type = GPIO_INTR_DISABLE; // disable interrupt io_conf.mode = GPIO_MODE_INPUT; // set as input io_conf.pull_down_en = 1; // enable pull-down mode io_conf.pull_up_en = 0; // disable pull-up mode ESP_ERROR_CHECK( gpio_config(&io_conf) ); // configure GPIO if (!gpio_get_level(0) || gpio_get_level(DFU_PIN)) { // DFU mode asserted tud_dfu_runtime_reboot_to_dfu_cb(); // reboot to DFU mode } // configure LEDs io_conf.pin_bit_mask = (1ULL << LED_BOARD); // GPIO to configure io_conf.intr_type = GPIO_INTR_DISABLE; // disable interrupt io_conf.mode = GPIO_MODE_INPUT_OUTPUT; // set as output (push-pull), and input to read state io_conf.pull_down_en = 0; // disable pull-down mode io_conf.pull_up_en = 0; // disable pull-up mode ESP_ERROR_CHECK( gpio_config(&io_conf) ); // configure GPIO gpio_set_level(LED_BOARD, 0); // switch running LED off until all is ready // setup USB CDC ACM for printing ESP_LOGI(TAG, "USB initialization"); // get MAC uint8_t mac[6]; esp_read_mac(mac, ESP_MAC_ETH); static char usb_serial[13] = {0}; snprintf(usb_serial, sizeof(usb_serial), "%02X%02X%02X%02X%02X%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); usb_str_desc[3] = usb_serial; const tinyusb_config_t tusb_cfg = { .device_descriptor = NULL, // use default USB descriptor .string_descriptor = usb_str_desc, // use custom string description to set serial .external_phy = false, // use integrated phy .configuration_descriptor = NULL, }; ESP_ERROR_CHECK( tinyusb_driver_install(&tusb_cfg) ); // configure USB tinyusb_config_cdcacm_t amc_cfg = { 0 }; // the configuration uses default values ESP_ERROR_CHECK( tusb_cdc_acm_init(&amc_cfg) ); // configure CDC ACM ESP_ERROR_CHECK( tusb_dfu_rf_init() ); // configure DFU runtime (ensures we can use it) ESP_LOGI(TAG, "USB initialized"); // configure shutdown io_conf.pin_bit_mask = (1ULL << GPIO_POWER); // GPIO to configure io_conf.intr_type = GPIO_INTR_DISABLE; // disable interrupt io_conf.mode = GPIO_MODE_INPUT; // set as input io_conf.pull_down_en = 0; // don't use pull-down io_conf.pull_up_en = 0; // there is already an external pull-up ESP_ERROR_CHECK( gpio_config(&io_conf) ); // configure GPIO ESP_ERROR_CHECK( esp_sleep_disable_wakeup_source(ESP_SLEEP_WAKEUP_ALL) ); // make sure all sources are disabled ESP_ERROR_CHECK( esp_sleep_enable_ext0_wakeup(GPIO_POWER, 0) ); // use power button to wake up (low when pressed) // task to decode and show the measurements meas_queue = xQueueCreate(MEAS_QUEUE_SIZE, sizeof(spi_slave_transaction_t)); ESP_ERROR_CHECK( NULL == meas_queue ); xTaskCreate(meas_task, "meas_task", 2048, NULL, 4, NULL); // configure SPI for GM1352 LCD data frame reading const spi_bus_config_t buscfg = { // SPI bus configuration .mosi_io_num = GPIO_MOSI, .miso_io_num = -1, .sclk_io_num = GPIO_SCLK, .quadwp_io_num = -1, .quadhd_io_num = -1, }; spi_slave_interface_config_t slvcfg={ .mode = 0, .spics_io_num = GPIO_CS, .queue_size = 3, .flags = 0, .post_setup_cb = NULL, .post_trans_cb = post_trans_cb, }; ESP_ERROR_CHECK( spi_slave_initialize(SPI2_HOST, &buscfg, &slvcfg, SPI_DMA_CH_AUTO) ); spi_slave_transaction_t t; // for the SPI transaction memset(&t, 0, sizeof(t)); // clear transaction t.length = FRAME_SIZE * 8; // transaction size WORD_ALIGNED_ATTR uint8_t recvbuf[FRAME_SIZE]; // SPI receive buffer must be word aligned for DMA t.rx_buffer = recvbuf; // set buffer spi_slave_queue_trans(SPI2_HOST, &t, portMAX_DELAY); ESP_LOGI(TAG, "application ready"); esp_tusb_init_console(TINYUSB_CDC_ACM_0); // log to USB gpio_set_level(LED_BOARD, 0); // switch running LED on to indicate all is ready while (1) { vTaskDelay(1000 / portTICK_PERIOD_MS); // toggle LED as heart beat if (gpio_get_level(LED_BOARD)) { gpio_set_level(LED_BOARD, 0); } else { gpio_set_level(LED_BOARD, 1); } if (timeout > 0) { printf("."); fflush(stdout); fsync(fileno(stdout)); } timeout++; if (timeout > 10) { printf("\nshutting down due to inactivity\n"); fflush(stdout); fsync(fileno(stdout)); gpio_set_level(LED_BOARD, 1); // ensure run LED is off esp_deep_sleep_start(); // go to deep sleep } } }