/* 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 0x00000001, // a (segment does not exist, use fake bit to avoid false positive) 0x00000040, // b 0x00000040, // c 0x00000001, // d (segment does not exist, use fake bit to avoid false positive) 0x00000001, // e (segment does not exist, use fake bit to avoid false positive) 0x00000001, // f (segment does not exist, use fake bit to avoid false positive) 0x00000001, // g (segment does not exist, use fake bit to avoid false positive) 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 */ 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 (0 == digit) { 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 } }