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
+ }
+}