2020-02-20 11:40:28 +01:00
/* 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 . 9 V is output when the MCU is off , 3.3 V 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 5 V tolerant , while the signal level is at 4 V
*/
# 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
2020-02-23 14:19:52 +01:00
0x00000001 , // a (segment does not exist, use fake bit to avoid false positive)
2020-02-20 11:40:28 +01:00
0x00000040 , // b
0x00000040 , // c
2020-02-23 14:19:52 +01:00
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)
2020-02-20 11:40:28 +01:00
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
*/
2021-01-28 11:12:32 +01:00
bool sensor_gm1351_decode ( void )
2020-02-20 11:40:28 +01:00
{
// 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 ] ) ) {
2020-02-23 14:19:52 +01:00
if ( 0 = = digit ) {
2020-02-20 11:40:28 +01:00
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
}
}