stm32f1/lib/sensor_gm1351.c

361 lines
14 KiB
C

/* 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 <http://www.gnu.org/licenses/>.
*
*/
/** library to read measurements from GM1351 sound level meter
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @date 2020
* @note peripherals used: GPIO @ref sensor_gm1351_gpio, SPI @ref sensor_gm1351_spi
*/
/* standard libraries */
#include <stdint.h> // standard integer types
#include <stdlib.h> // general utilities
/* STM32 (including CM3) libraries */
#include <libopencmsis/core_cm3.h> // Cortex M3 utilities
#include <libopencm3/stm32/rcc.h> // real-time control clock library
#include <libopencm3/stm32/gpio.h> // general purpose input output library
#include <libopencm3/stm32/spi.h> // SPI library
#include <libopencm3/stm32/exti.h> // 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
}
}