From 605181cd26c5c592bfd4cb6b97d36cc12172b69d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?King=20K=C3=A9vin?= Date: Thu, 20 Feb 2020 11:40:28 +0100 Subject: [PATCH] sensor_gm1351: add library to read GM1351 sound level meter measurment by tapping on the LCD controller input --- lib/sensor_gm1351.c | 362 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 362 insertions(+) create mode 100644 lib/sensor_gm1351.c diff --git a/lib/sensor_gm1351.c b/lib/sensor_gm1351.c new file mode 100644 index 0000000..e041029 --- /dev/null +++ b/lib/sensor_gm1351.c @@ -0,0 +1,362 @@ +/* This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +/** library to read measurements from GM1351 sound level meter + * @file + * @author King Kévin + * @date 2020 + * @note peripherals used: GPIO @ref sensor_gm1351_gpio, SPI @ref sensor_gm1351_spi + */ +/* standard libraries */ +#include // standard integer types +#include // general utilities + +/* STM32 (including CM3) libraries */ +#include // Cortex M3 utilities +#include // real-time control clock library +#include // general purpose input output library +#include // SPI library +#include // external interrupt defines + +/* own libraries */ +#include "global.h" // global utilities +#include "sensor_gm1351.h" // own definitions + +/** @defgroup sensor_gm1351_gpio GM1351 pin connections + * @{ + */ +/** power on button, controlled through a n-channel MOSFET, simulating a button press by connecting to ground when activated high + * @note connect to D7/D8 pads on the text side. 9V is output when the MCU is off, 3.3V is output when it is on. pulling it low simulates the key press. + */ +#define SENSOR_GM1351_ONOFF PB0 +/** chip select (active low) + * @note connect to LCD_CS test point + */ +#define SENSOR_GM1351_CS PA8 +/** @} */ + +/** SPI interface used to read LCD interface + * connect CLK/SCK to LCD_WR test point, and MOSI to LCR_DATA test point. + * @note SPI1 on port A is not 5V tolerant, while the signal level is at 4V + */ +#define SENSOR_GM1351_SPI 2 + +uint16_t sensor_gm1351_decidba = 0; +volatile bool sensor_gm1351_received_flag = false; + +/** data frame sent by F330 MCU to LCD */ +static uint8_t sensor_gm1351_data[17]; // 17 bytes are sent per transaction +/** data frame index */ +static uint8_t sensor_gm1351_frame_i = 0; + +/* there is a lot of commented out code. + * it was used to find out which bit corresponds to which segment + * for that cut the trace going to the LCD controller and connect is to the MISO pin + * this way we replace the data transmitted to the LCD + * the F330 still controls the clock and operates the rest of the LCD + * + * the F330 MCU sends 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: 0xa0, set segments ? + * + * byte 1: + * - bit 7: battery + * - bit 6: MAX + * - bit 5: MIN + * - bit 4: HOLD + * - bit 3: no segment + * - bit 2: - + * - bit 1: dBA + * - bit 0: no segment + * + * byte 2: + * - bit 7: dBC + * - bit 6: no segment + * - bit 5: digit 3, segment e + * - bit 4: digit 3, segment g + * - bit 3: digit 3, segment f + * - bit 2: digit 4, segment d + * - bit 1: digit 4, segment c + * - bit 0: digit 4, segment b + * + * byte 3: + * - bit 7: digit 4, segment a + * - bit 6: digit 3, segment dp + * - bit 5: digit 4, segment e + * - bit 4: digit 4, segment g + * - bit 3: digit 4, segment f + * - bit 2: digit 3, segment d + * - bit 1: digit 3, segment c + * - bot 0: digit 3, segment b + * + * byte 4: + * - bit 7: digit 3, segment a + * - bit 6: digit 2, segment d + * - bit 5: digit 2, segment c + * - bit 4: digit 2, segment b + * - bit 3: digit 2, segment a + * - bit 2: digit 1, segment dp + * - bit 1: digit 2, segment e + * - bit 0: digit 2, segment g + * + * byte 5: + * - bit 7: digit 2, segment f + * - bit 6: digit 1, segment c and b + * + * all other bits can be set to any value + */ + +/* +// the frame to be transmitted +//static const uint8_t frame[17] = {0xa0, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; // enable all segments +//static const uint8_t frame[17] = {0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; // clear display +//static const uint8_t frame[17] = {0xa0, 0x01, 0x1f, 0xff, 0xf9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x00, 0x00 }; // 39.8 dBA +//static const uint8_t frame[17] = {0xa0, 0x01, 0x16, 0xdf, 0xe9, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x00, 0x00 }; // 53.5 bBA +static uint8_t frame[17] = {0xa0, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; // all off +// the current index of the fame to be transmitted +static volatile uint8_t frame_i = 0; +*/ + +/** which bit should be set to enable which segment in specific digits + * @note this are the 4 byte to be used starting with byte 2 in the fame + */ +static const uint32_t digit_segments[4][8] = { + { // digit 1 + 0x00000000, // a + 0x00000040, // b + 0x00000040, // c + 0x00000000, // d + 0x00000000, // e + 0x00000000, // f + 0x00000000, // g + 0x00000400, // dp + }, + { // digit 2 + 0x00000800, // a + 0x00001000, // b + 0x00002000, // c + 0x00004000, // d + 0x00000200, // e + 0x00000080, // f + 0x00000100, // g + 0x00000000, // dp + }, + { // digit 3 + 0x00008000, // a + 0x00010000, // b + 0x00020000, // c + 0x00040000, // d + 0x20000000, // e + 0x08000000, // f + 0x10000000, // g + 0x00400000, // dp + }, + { // digit 4 + 0x00800000, // a + 0x01000000, // b + 0x02000000, // c + 0x04000000, // d + 0x00200000, // e + 0x00080000, // f + 0x00100000, // g + 0x00000000, // dp + }, +}; + +/** 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 +}; + +/** the final bit pattern for each number in the digit + * @note this table will be filled during setup + */ +static uint32_t digit_number[4][10]; + + +void sensor_gm1351_setup(void) +{ + sensor_gm1351_frame_i = 0; // reset frame index + sensor_gm1351_received_flag = false; // reset receive flag + //frame_i = 0; + + // generate the digit patterns + for (uint8_t digit = 0; digit < LENGTH(digit_number) && digit < LENGTH(digit_segments); digit++) { + for (uint8_t number = 0; number < LENGTH(number_segments) && number < LENGTH(digit_number[digit]); number++) { + digit_number[digit][number] = 0; + for (uint8_t segment = 0; segment < 8; segment++) { + if (number_segments[number] & (1 << segment)) { + digit_number[digit][number] |= digit_segments[digit][segment]; + } + } + } + } + /* to test a specific number on a specific digit + uint32_t segs = digit_number[3][9]; + frame[2] = (segs >> 24); + frame[3] = (segs >> 16); + frame[4] = (segs >> 8); + frame[5] = (segs >> 0); + */ + + // configure GPIO for on/off button + rcc_periph_clock_enable(GPIO_RCC(SENSOR_GM1351_ONOFF)); // enable clock for GPIO peripheral for on/off button + gpio_clear(GPIO_PORT(SENSOR_GM1351_ONOFF), GPIO_PIN(SENSOR_GM1351_ONOFF)); // low = unpressed + gpio_set_mode(GPIO_PORT(SENSOR_GM1351_ONOFF), GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO_PIN(SENSOR_GM1351_ONOFF)); // set output as push-pull to control gate + + // configure SPI peripheral + rcc_periph_clock_enable(RCC_SPI_SCK_PORT(SENSOR_GM1351_SPI)); // enable clock for GPIO peripheral for clock signal + gpio_set_mode(SPI_SCK_PORT(SENSOR_GM1351_SPI), GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, SPI_SCK_PIN(SENSOR_GM1351_SPI)); // set SCK as input (float to not interfere with communication) + rcc_periph_clock_enable(RCC_SPI_MOSI_PORT(SENSOR_GM1351_SPI)); // enable clock for GPIO peripheral for MOSI signal + gpio_set_mode(SPI_MOSI_PORT(SENSOR_GM1351_SPI), GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, SPI_MOSI_PIN(SENSOR_GM1351_SPI)); // set MOSI as input so we can read the data sent (float to not interfere with communication) + //rcc_periph_clock_enable(RCC_SPI_MISO_PORT(SENSOR_GM1351_SPI)); // enable clock for GPIO peripheral for MISO signal + //gpio_set_mode(SPI_MISO_PORT(SENSOR_GM1351_SPI), GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_OPENDRAIN, SPI_MISO_PIN(SENSOR_GM1351_SPI)); // set MISO as output + rcc_periph_clock_enable(RCC_AFIO); // enable clock for SPI alternate function + rcc_periph_clock_enable(RCC_SPI(SENSOR_GM1351_SPI)); // enable clock for SPI peripheral + spi_reset(SPI(SENSOR_GM1351_SPI)); // clear SPI values to default + spi_set_dff_8bit(SPI(SENSOR_GM1351_SPI)); // use 8-bit frame since there are 17x8 bytes send + spi_set_clock_polarity_1(SPI(SENSOR_GM1351_SPI)); // clock is idle high + spi_set_clock_phase_1(SPI(SENSOR_GM1351_SPI)); // data is valid on rising edge + spi_send_msb_first(SPI(SENSOR_GM1351_SPI)); // bit order does not really matter + spi_set_full_duplex_mode(SPI(SENSOR_GM1351_SPI)); + spi_set_slave_mode(SPI(SENSOR_GM1351_SPI)); // set to slave mode to only monitor the LCD data the F330 is sending + spi_enable_software_slave_management(SPI(SENSOR_GM1351_SPI)); // we will handle the chip select so we know when a transmission starts and stops + spi_disable_ss_output(SPI(SENSOR_GM1351_SPI)); // disable NSS output since we are only a slave + spi_set_nss_high(SPI(SENSOR_GM1351_SPI)); // set CS high to disable receive + spi_set_receive_only_mode(SPI(SENSOR_GM1351_SPI)); // we only want to receive (comment out to transmit) + spi_enable_rx_buffer_not_empty_interrupt(SPI(SENSOR_GM1351_SPI)); // enable receive interrupt + //spi_enable_tx_buffer_empty_interrupt(SPI(SENSOR_GM1351_SPI)); // enable transmit interrupt + nvic_enable_irq(SPI_IRQ(SENSOR_GM1351_SPI)); // enable SPI interrupt + spi_enable(SPI(SENSOR_GM1351_SPI)); // enable SPI + // DMA could be used to decrease the load, but the clock is so slow and load so low that this is not required + + /* configure chip select so we can know start and end of transmission */ + rcc_periph_clock_enable(GPIO_RCC(SENSOR_GM1351_CS)); // enable clock for GPIO peripheral for chip select signal + gpio_set_mode(GPIO_PORT(SENSOR_GM1351_CS), GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO_PIN(SENSOR_GM1351_CS)); // set CS as input (float to not interfere with communication) + rcc_periph_clock_enable(RCC_AFIO); // enable alternate function clock for external interrupt + exti_select_source(GPIO_EXTI(SENSOR_GM1351_CS), GPIO_PORT(SENSOR_GM1351_CS)); // mask external interrupt of this pin only for this port + exti_set_trigger(GPIO_EXTI(SENSOR_GM1351_CS), EXTI_TRIGGER_BOTH); // trigger on both edges + exti_enable_request(GPIO_EXTI(SENSOR_GM1351_CS)); // enable external interrupt + nvic_enable_irq(GPIO_NVIC_EXTI_IRQ(SENSOR_GM1351_CS)); // enable EXTI interrupt +} + +void sensor_gm1351_power_toggle(void) +{ + gpio_set(GPIO_PORT(SENSOR_GM1351_ONOFF), GPIO_PIN(SENSOR_GM1351_ONOFF)); // connect to ground to simulate ON/OFF button press + sleep_ms(100); // wait a bit for MCU to register button press (50 ms did not work) + gpio_clear(GPIO_PORT(SENSOR_GM1351_ONOFF), GPIO_PIN(SENSOR_GM1351_ONOFF)); // release button +} + +/** decode LCD pattern to displayed number + * @return if number has been decoded correctly + */ +static bool sensor_gm1351_decode(void) +{ + // do some sanity check + if (sensor_gm1351_data[0] != 0xa0) { // set LCD segments header + return false; + } + if ((sensor_gm1351_data[1] & 0x01) == 0) { // dBA not set + return false; + } + if ((sensor_gm1351_data[3] & 0x40) == 0) { // 3dp not set + return false; + } + if ((sensor_gm1351_data[4] & 0x04) != 0) { // 1dp set + return false; + } + + uint32_t pattern = (sensor_gm1351_data[2] << 24) + (sensor_gm1351_data[3] << 16) + (sensor_gm1351_data[4] << 8) + (sensor_gm1351_data[5] << 0); + uint16_t decoded = 0; + for (uint8_t digit = 0; digit < LENGTH(digit_number); digit++) { + uint8_t number; + for (number = 0; number < LENGTH(digit_number[digit]); number++) { + if ((pattern & digit_number[digit][8]) == digit_number[digit][number]) { + break; + } + } + if (number >= LENGTH(digit_number[digit])) { + if (digit == 0) { + number = 0; + } else { + return false; + } + } + decoded *= 10; + decoded += number; + } + + sensor_gm1351_decidba = decoded; // save decoded pattern + return true; +} + +/** interrupt service routine called when chip is (de-)selected */ +void GPIO_EXTI_ISR(SENSOR_GM1351_CS)(void) +{ + exti_reset_request(GPIO_EXTI(SENSOR_GM1351_CS)); // reset interrupt + if (gpio_get(GPIO_PORT(SENSOR_GM1351_CS), GPIO_PIN(SENSOR_GM1351_CS))) { // chip select high -> end of transaction + spi_set_nss_high(SPI(SENSOR_GM1351_SPI)); // disable SPI receive + if (sensor_gm1351_decode()) { // try to decode pattern + sensor_gm1351_received_flag = true; // notify user + } + } else { // chip select low = active -> start of transaction + spi_set_nss_low(SPI(SENSOR_GM1351_SPI)); // enable SPI receive + sensor_gm1351_frame_i = 0; // restart frame + //frame_i = 0; + //spi_write(SPI(SENSOR_GM1351_SPI), frame[frame_i++]); + } +} + +/** interrupt service routine when SPI data has been received */ +void SPI_ISR(SENSOR_GM1351_SPI)(void) +{ + uint8_t sr = SPI_SR(SPI(SENSOR_GM1351_SPI)); // read once, since this clears some stuff + /* + if (sr & SPI_SR_TXE) { // data received + if (frame_i >= LENGTH(frame)) { + frame_i = 0; + } + spi_write(SPI(SENSOR_GM1351_SPI), frame[frame_i++]); + } + */ + if (sr & SPI_SR_RXNE) { // data received + uint8_t data = spi_read(SPI(SENSOR_GM1351_SPI)); // read data and clear flag + if (sensor_gm1351_frame_i < LENGTH(sensor_gm1351_data)) { + sensor_gm1351_data[sensor_gm1351_frame_i] = data; + sensor_gm1351_frame_i++; + } + } + if (sr & SPI_SR_MODF) { + SPI_CR1(SPI(SENSOR_GM1351_SPI)) = SPI_CR1(SPI(SENSOR_GM1351_SPI)); // write CR1 to clear flag + sensor_gm1351_frame_i = 0; // restart frame + } + if (sr & SPI_SR_OVR) { + SPI_DR((SPI(SENSOR_GM1351_SPI))); // read to clear flag + SPI_SR(SPI(SENSOR_GM1351_SPI)); // read to clear flag + sensor_gm1351_frame_i = 0; // restart frame + } +}