361 lines
14 KiB
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
|
|
}
|
|
}
|