2016-09-11 17:21:15 +02: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 query measurements from peacefair PZEM-004 and PZEM-004T electricity meter (code)
* @ file sensor_pzem . c
* @ author King Kévin < kingkevin @ cuvoodoo . info >
* @ date 2016
2017-01-21 12:59:16 +01:00
* @ note peripherals used : USART @ ref sensor_pzem_usart , timer @ ref sensor_pzem_timer
2016-09-11 17:21:15 +02:00
*/
/* standard libraries */
# include <stdint.h> // standard integer types
# include <stdlib.h> // general utilities
/* STM32 (including CM3) libraries */
2017-01-21 12:59:16 +01:00
# include <libopencmsis/core_cm3.h> // Cortex M3 utilities
# include <libopencm3/cm3/nvic.h> // interrupt handler
2016-09-11 17:21:15 +02:00
# include <libopencm3/stm32/rcc.h> // real-time control clock library
# include <libopencm3/stm32/gpio.h> // general purpose input output library
# include <libopencm3/stm32/usart.h> // universal synchronous asynchronous receiver transmitter library
2017-01-21 12:59:16 +01:00
# include <libopencm3/stm32/timer.h> // timer utilities
2016-09-11 17:21:15 +02:00
2017-01-21 12:59:16 +01:00
/* own libraries */
2016-09-11 17:21:15 +02:00
# include "sensor_pzem.h" // PZEM electricity meter header and definitions
# include "global.h" // common methods
/** @defgroup sensor_pzem_usart USART peripheral used for communication with electricity meter
* @ {
*/
# define SENSOR_PZEM_USART 2 /**< USART peripheral */
/** @} */
2017-01-21 12:59:16 +01:00
/** @defgroup sensor_pzem_timer timer peripheral used for waiting before sending the next request
* @ {
*/
# define SENSOR_PZEM_TIMER 2 /**< timer peripheral */
/** @} */
2016-09-11 17:21:15 +02:00
/* input and output ring buffer, indexes, and available memory */
static uint8_t rx_buffer [ 7 ] = { 0 } ; /**< buffer for received response */
static volatile uint8_t rx_i = 0 ; /**< current position of read received data */
static uint8_t tx_buffer [ 7 ] = { 0 } ; /**< buffer for request to transmit */
2016-10-03 12:09:24 +02:00
static volatile uint8_t tx_i = 0 ; /**< current position of transmitted data */
2016-09-11 17:21:15 +02:00
volatile bool sensor_pzem_measurement_received = false ;
void sensor_pzem_setup ( void )
{
/* enable USART I/O peripheral */
2016-09-13 22:41:31 +02:00
rcc_periph_clock_enable ( RCC_AFIO ) ; // enable pin alternate function (USART)
2016-09-11 17:21:15 +02:00
rcc_periph_clock_enable ( USART_PORT_RCC ( SENSOR_PZEM_USART ) ) ; // enable clock for USART port peripheral
rcc_periph_clock_enable ( USART_RCC ( SENSOR_PZEM_USART ) ) ; // enable clock for USART peripheral
gpio_set_mode ( USART_PORT ( SENSOR_PZEM_USART ) , GPIO_MODE_OUTPUT_2_MHZ , GPIO_CNF_OUTPUT_ALTFN_PUSHPULL , USART_PIN_TX ( SENSOR_PZEM_USART ) ) ; // setup GPIO pin USART transmit
gpio_set_mode ( USART_PORT ( SENSOR_PZEM_USART ) , GPIO_MODE_INPUT , GPIO_CNF_INPUT_PULL_UPDOWN , USART_PIN_RX ( SENSOR_PZEM_USART ) ) ; // setup GPIO pin USART receive
gpio_set ( USART_PORT ( SENSOR_PZEM_USART ) , USART_PIN_RX ( SENSOR_PZEM_USART ) ) ; // pull up to avoid noise when not connected
/* setup USART parameters for electricity meter: 9600 8N1 */
usart_set_baudrate ( USART ( SENSOR_PZEM_USART ) , 9600 ) ; // the electricity meter uses a fixed baud rate of 9600 bps
usart_set_databits ( USART ( SENSOR_PZEM_USART ) , 8 ) ;
usart_set_stopbits ( USART ( SENSOR_PZEM_USART ) , USART_STOPBITS_1 ) ;
usart_set_mode ( USART ( SENSOR_PZEM_USART ) , USART_MODE_TX_RX ) ;
usart_set_parity ( USART ( SENSOR_PZEM_USART ) , USART_PARITY_NONE ) ;
usart_set_flow_control ( USART ( SENSOR_PZEM_USART ) , USART_FLOWCONTROL_NONE ) ;
nvic_enable_irq ( USART_IRQ ( SENSOR_PZEM_USART ) ) ; // enable the USART interrupt
usart_enable_rx_interrupt ( USART ( SENSOR_PZEM_USART ) ) ; // enable receive interrupt
usart_enable ( USART ( SENSOR_PZEM_USART ) ) ; // enable USART
2017-01-21 12:59:16 +01:00
// setup timer to wait for minimal time before next transmission (after previous transmission or reception)
rcc_periph_clock_enable ( RCC_TIM ( SENSOR_PZEM_TIMER ) ) ; // enable clock for timer block
timer_reset ( TIM ( SENSOR_PZEM_TIMER ) ) ; // reset timer state
timer_set_mode ( TIM ( SENSOR_PZEM_TIMER ) , TIM_CR1_CKD_CK_INT , TIM_CR1_CMS_EDGE , TIM_CR1_DIR_UP ) ; // set timer mode, use undivided timer clock,edge alignment (simple count), and count up
timer_one_shot_mode ( TIM ( SENSOR_PZEM_TIMER ) ) ; // stop counter after update event (we only need to count down once)
timer_set_prescaler ( TIM ( SENSOR_PZEM_TIMER ) , 550 - 1 ) ; // set the prescaler so this 16 bits timer allows to wait for maximum 500 ms ( 1/(72E6/550/(2**16))=500.62ms )
timer_set_period ( TIM ( SENSOR_PZEM_TIMER ) , 0xffff / 2 ) ; // the timing is not defined in the specification. I tested until the communication was reliable (all requests get an response)
timer_clear_flag ( TIM ( SENSOR_PZEM_TIMER ) , TIM_SR_UIF ) ; // clear flag
timer_enable_irq ( TIM ( SENSOR_PZEM_TIMER ) , TIM_DIER_UIE ) ; // enable update interrupt for timer
nvic_enable_irq ( NVIC_TIM_IRQ ( SENSOR_PZEM_TIMER ) ) ; // catch interrupt in service routine
2016-09-11 17:21:15 +02:00
/* reset buffer states */
2017-01-21 12:59:16 +01:00
tx_i = LENGTH ( tx_buffer ) ;
2016-09-11 17:21:15 +02:00
rx_i = 0 ;
sensor_pzem_measurement_received = false ;
}
void sensor_pzem_measurement_request ( uint32_t address , enum sensor_pzem_measurement_type_t type )
{
2017-01-21 12:59:16 +01:00
if ( tx_i < LENGTH ( tx_buffer ) ) { // transmission is ongoing
2016-09-11 17:21:15 +02:00
return ;
}
2016-09-12 21:42:37 +02:00
if ( type > = SENSOR_PZEM_MAX ) { // invalid type
2016-09-11 17:21:15 +02:00
return ;
}
tx_buffer [ 0 ] = 0xB0 + type ; // set request nibble and type nibble
tx_buffer [ 1 ] = ( address > > 24 ) & 0xff ; // set address
tx_buffer [ 2 ] = ( address > > 16 ) & 0xff ; // set address
tx_buffer [ 3 ] = ( address > > 8 ) & 0xff ; // set address
tx_buffer [ 4 ] = ( address > > 0 ) & 0xff ; // set address
tx_buffer [ 5 ] = 0 ; // only used to set alarm
tx_buffer [ 6 ] = 0 ; // to calculate checksum (sum of all previous bytes)
for ( uint8_t i = 0 ; i < LENGTH ( tx_buffer ) - 1 ; i + + ) {
tx_buffer [ 6 ] + = tx_buffer [ i ] ; // calculate buffer
}
2017-01-21 12:59:16 +01:00
tx_i = 0 ; // remember we have a message to send
2016-09-11 17:21:15 +02:00
2017-01-21 12:59:16 +01:00
if ( TIM_CR1 ( TIM ( SENSOR_PZEM_TIMER ) ) & TIM_CR1_CEN ) { // timer is already running
// at the end of the timer the transmission will start automatically
} else { // no timer is running
usart_enable_tx_interrupt ( USART ( SENSOR_PZEM_USART ) ) ; // enable interrupt to start sending bytes
//usart_send(USART(SENSOR_PZEM_USART),tx_buffer[tx_i++]); // start transmission
}
2017-01-20 17:07:34 +01:00
sensor_pzem_measurement_received = false ; // reset flag
rx_i = 0 ; // prepare buffer to receive next measurement
2016-09-11 17:21:15 +02:00
}
struct sensor_pzem_measurement_t sensor_pzem_measurement_decode ( void )
{
struct sensor_pzem_measurement_t measurement ; // decoded measurement to return
measurement . valid = false ; // wait until the end to ensure validity
2017-01-20 17:07:34 +01:00
if ( rx_i < LENGTH ( rx_buffer ) ) { // buffer is not full, thus no measurement received
2016-09-11 17:21:15 +02:00
return measurement ;
}
if ( ( rx_buffer [ 0 ] & 0xf0 ) ! = 0xa0 ) { // not a response received
return measurement ;
}
2016-09-12 21:42:37 +02:00
if ( ( rx_buffer [ 0 ] & 0x0f ) > = SENSOR_PZEM_MAX ) { // not a valid response type received (actually 4 and 5 are valid, but should not happen when using this code
2016-09-11 17:21:15 +02:00
return measurement ;
}
uint8_t checksum = 0 ; // calculate checksum (sum of all other bytes)
for ( uint8_t i = 0 ; i < LENGTH ( rx_buffer ) - 1 ; i + + ) {
checksum + = rx_buffer [ i ] ; // calculate buffer
}
if ( checksum ! = rx_buffer [ 6 ] ) { // checksum does not match
return measurement ;
}
measurement . valid = true ; // all checks passed
measurement . type = rx_buffer [ 0 ] & 0x0f ; // save type
switch ( measurement . type ) { // decode value depending on type
2016-09-12 21:42:37 +02:00
case SENSOR_PZEM_VOLTAGE :
2016-09-11 17:21:15 +02:00
measurement . value . voltage = ( ( uint16_t ) rx_buffer [ 1 ] < < 8 ) + rx_buffer [ 2 ] + rx_buffer [ 3 ] * 0.1 ;
break ;
2016-09-12 21:42:37 +02:00
case SENSOR_PZEM_CURRENT :
2017-01-19 13:29:38 +01:00
measurement . value . current = rx_buffer [ 2 ] + rx_buffer [ 3 ] * 0.01 ;
2016-09-11 17:21:15 +02:00
break ;
2016-09-12 21:42:37 +02:00
case SENSOR_PZEM_POWER :
2016-09-11 17:21:15 +02:00
measurement . value . power = ( ( uint16_t ) rx_buffer [ 1 ] < < 8 ) + rx_buffer [ 2 ] ;
break ;
2016-09-12 21:42:37 +02:00
case SENSOR_PZEM_ENERGY :
2016-10-09 17:18:31 +02:00
measurement . value . energy = ( ( uint32_t ) rx_buffer [ 1 ] < < 16 ) + ( ( uint16_t ) rx_buffer [ 2 ] < < 8 ) + rx_buffer [ 3 ] ;
2016-09-11 17:21:15 +02:00
break ;
2016-10-14 10:45:58 +02:00
/* not used in this application
2016-10-03 12:09:24 +02:00
case SENSOR_PZEM_ADDRESS :
case SENSOR_PZEM_ALARM :
break ; // no value is returned
2016-10-14 10:45:58 +02:00
*/
2016-09-11 17:21:15 +02:00
default :
measurement . valid = false ; // unexpected type
}
sensor_pzem_measurement_received = false ; // reset flag
rx_i = 0 ; // prepare buffer to receive next measurement
return measurement ;
}
/** USART interrupt service routine called when data has been transmitted or received */
void USART_ISR ( SENSOR_PZEM_USART ) ( void )
{
if ( usart_get_interrupt_source ( USART ( SENSOR_PZEM_USART ) , USART_SR_TXE ) ) { // data has been transmitted
if ( tx_i < LENGTH ( tx_buffer ) ) { // not all bytes transmitted
usart_send ( USART ( SENSOR_PZEM_USART ) , tx_buffer [ tx_i + + ] ) ; // transmit next byte
} else { // request transmitted
usart_disable_tx_interrupt ( USART ( SENSOR_PZEM_USART ) ) ; // disable transmit interrupt
2017-01-21 12:59:16 +01:00
timer_set_counter ( TIM ( SENSOR_PZEM_TIMER ) , 0 ) ; // reset timer counter to get preset waiting time
timer_enable_counter ( TIM ( SENSOR_PZEM_TIMER ) ) ; // start timer between requests
2016-09-11 17:21:15 +02:00
}
}
if ( usart_get_interrupt_source ( USART ( SENSOR_PZEM_USART ) , USART_SR_RXNE ) ) { // data has been received
if ( rx_i < LENGTH ( rx_buffer ) ) { // receiving response
rx_buffer [ rx_i + + ] = usart_recv ( USART ( SENSOR_PZEM_USART ) ) ; // put received byte in buffer
if ( rx_i > = LENGTH ( rx_buffer ) ) { // buffer full
sensor_pzem_measurement_received = true ; // notify used response has been received
}
} else { // previous response not read before receiving the next
usart_recv ( USART ( SENSOR_PZEM_USART ) ) ; // drop received buffer
}
2017-01-21 12:59:16 +01:00
timer_set_counter ( TIM ( SENSOR_PZEM_TIMER ) , 0 ) ; // reset timer counter to get preset waiting time
timer_enable_counter ( TIM ( SENSOR_PZEM_TIMER ) ) ; // start timer between requests
2016-09-11 17:21:15 +02:00
}
}
2017-01-21 12:59:16 +01:00
/** interrupt service routine called on timeout */
void TIM_ISR ( SENSOR_PZEM_TIMER ) ( void )
{
if ( timer_get_flag ( TIM ( SENSOR_PZEM_TIMER ) , TIM_SR_UIF ) ) { // update event happened
timer_clear_flag ( TIM ( SENSOR_PZEM_TIMER ) , TIM_SR_UIF ) ; // clear flag
if ( tx_i < LENGTH ( tx_buffer ) ) { // bytes are waiting to be sent
usart_enable_tx_interrupt ( USART ( SENSOR_PZEM_USART ) ) ; // enable interrupt to start sending bytes
}
}
}