Compare commits
9 Commits
master
...
gm1352_rea
Author | SHA1 | Date |
---|---|---|
King Kévin | 10bf54edb9 | |
King Kévin | d823f18526 | |
King Kévin | 7d8a98d0ea | |
King Kévin | 3a9cd32391 | |
King Kévin | dd8a3263c6 | |
King Kévin | 0756bb8fe5 | |
King Kévin | 66816abc9f | |
King Kévin | 66a6149fdf | |
King Kévin | 102bc6d038 |
43
README.md
43
README.md
|
@ -1,9 +1,48 @@
|
|||
this is a template firmware for [ESP32-S2](https://www.espressif.com/en/products/socs/esp32-s2)-based micro-controller projects.
|
||||
this project is about interfacing the GM-1352 sound level meter using an ESP32-S2-based board, so to read the sound level measurements digitally.
|
||||
|
||||
GM-1352
|
||||
=======
|
||||
|
||||
the GM-1352 is the successor of the GM-1351, and very similar to it.
|
||||
the main difference is that the power is not delivered by a 9V battery, but by 3x1.5V AA batteries.
|
||||
|
||||
[CuVoodoo #030](https://www.cuvoodoo.info/cuvoodoo-030-audio-is-killing-the-music/) already explained how to interface it.
|
||||
basically just tap on the lines between the MCU (a EFM8BB10F8G-QFN20) and LCD controller (a Chip-on-Board under a blob of epoxy).
|
||||
there are test points available for that:
|
||||
|
||||
- CS: chip select, to start data communication
|
||||
- DATA: the actual data bits
|
||||
- WR: the data clock (not periodic)
|
||||
|
||||
there are additional test points to read (or assert) button presses:
|
||||
|
||||
- 1: HOLD button (press low)
|
||||
- 2: MIN/MAX button (press low)
|
||||
- 3: POWER button (press low)
|
||||
|
||||
there is another test point marked S, but I don't know what this is for.
|
||||
|
||||
also note that the LCD has segments for Bluetooth and USB.
|
||||
there might be variants of the GM-1352 with these options.
|
||||
|
||||
board
|
||||
=====
|
||||
|
||||
the board used is a [WEMOS](https://www.wemos.cc/en/latest/index.html) [S2 mini](https://www.wemos.cc/en/latest/s2/s2_mini.html).
|
||||
this repository is the firmware for [ESP32-S2](https://www.espressif.com/en/products/socs/esp32-s2)-based [WEMOS](https://www.wemos.cc/en/latest/index.html) [S2 mini](https://www.wemos.cc/en/latest/s2/s2_mini.html) board.
|
||||
|
||||
connect the GM-1352 to the S2 mini as follows:
|
||||
|
||||
- B+ to 5V: to power or get power from the device
|
||||
- B- to GND: ground reference
|
||||
- POWER to 16: to wake up the S2 when the device is powered (the S2 will shut down shortly after the GM-1352)
|
||||
- DATA to 18: this will be the SPI MOSI line to read the measurments
|
||||
- CS to 33: this will be the SPI CS line to identify transactions
|
||||
- WR to 35: this will be the SPI SCK line to clock the data bits (not periodic)
|
||||
|
||||
I soldered wires to the pads, and broke them out on a 8-pin female header, located above the battery compartment.
|
||||
this also to easily connect on row of the S2 mini to it on the back of the GM-1352.
|
||||
|
||||
now you can power the sound level meter through the S2 mini USB port, and read the measurements over its serial CDC ACM interface.
|
||||
|
||||
compile and flash
|
||||
=================
|
||||
|
|
261
main/main.c
261
main/main.c
|
@ -3,11 +3,16 @@
|
|||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/reent.h>
|
||||
#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"
|
||||
|
@ -15,14 +20,155 @@
|
|||
#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
|
||||
*/
|
||||
|
@ -31,26 +177,80 @@ static const char* usb_str_desc[USB_STRING_DESCRIPTOR_ARRAY_SIZE] = {
|
|||
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
|
||||
CONFIG_TINYUSB_DESC_CDC_STRING, // 4: CDC Interface
|
||||
"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(DFU_PIN)) { // DFU mode asserted
|
||||
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
|
||||
|
@ -78,10 +278,49 @@ void app_main(void)
|
|||
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_tusb_init_console(TINYUSB_CDC_ACM_0); // log to USB
|
||||
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);
|
||||
|
@ -91,5 +330,19 @@ void app_main(void)
|
|||
} 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue