spark_abacus/main.c

726 lines
33 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/>.
*
*/
/** STM32F1 example
* @file main.c
* @author King Kévin <kingkevin@cuvoodoo.info>
* @date 2016
*/
/* standard libraries */
#include <stdint.h> // standard integer types
#include <stdio.h> // standard I/O facilities
#include <stdlib.h> // standard utilities
#include <unistd.h> // standard streams
#include <string.h> // string utilities
#include <math.h> // mathematical utilities
/* STM32 (including CM3) libraries */
#include <libopencmsis/core_cm3.h> // Cortex M3 utilities
#include <libopencm3/cm3/scb.h> // vector table definition
#include <libopencm3/cm3/nvic.h> // interrupt utilities
#include <libopencm3/stm32/gpio.h> // general purpose input output library
#include <libopencm3/stm32/rcc.h> // real-time control clock library
#include <libopencm3/stm32/exti.h> // external interrupt utilities
#include <libopencm3/stm32/rtc.h> // real time clock utilities
#include <libopencm3/stm32/iwdg.h> // independent watchdog utilities
#include <libopencm3/stm32/dbgmcu.h> // debug utilities
#include <libopencm3/stm32/flash.h> // flash utilities
#include <libopencm3/stm32/timer.h> // timer utilities
#include <libopencm3/stm32/f1/bkp.h> // backup utilities
/* own libraries */
#include "global.h" // board definitions
//#include "usart.h" // USART utilities
#include "usb_cdcacm.h" // USB CDC ACM utilities
#include "sensor_pzem.h" // PZEM electricity meter utilities
#include "sensor_sdm120.h" // SDM120 electricity meter utilities
#include "radio_esp8266.h" // ESP8266 WiFi SoC utilities
#define WATCHDOG_PERIOD 10000 /**< watchdog period in ms */
/** @defgroup main_flags flag set in interrupts to be processed in main task
* @{
*/
volatile bool rtc_internal_tick_flag = false; /**< flag set when internal RTC ticked */
/** @} */
#define QUERY_PERIOD 10 /**< period in seconds to query meter measurements */
/** @defgroup main_leds LED to indicate status
* @{
*/
#define LED_HEARTBEAT_PORT A /**< port for heart beat LED (green, on on low) */
#define LED_HEARTBEAT_PIN 5 /**< pin for heart beat LED (green, on on low) */
#define LED_QUERY_PORT A /**< port for query LED (yellow, on on low) */
#define LED_QUERY_PIN 6 /**< pin for query LED (yellow, on on low) */
#define LED_SUBMIT_PORT A /**< port for submit LED (blue, on on low) */
#define LED_SUBMIT_PIN 7 /**< pin for submit LED (blue, on on low) */
/** @} */
/** @defgroup main_ddm100tc resources to capture pulses from DDM100TC electricity meter
* @{
*/
#define DDM100TC_TIMER 4 /**< timer to measure time between pulses **/
#define DDM100TC_PORT B /**< timer ipnut capture port (TIM4_CH1=PB6) **/
#define DDM100TC_CAPTURE TIM4_CH1 /**< time input capture used to detect pulse **/
volatile uint32_t ddm100tc_interval = 0; /**< last time interval between pulses **/
/** @} */
/** @defgroup main_database influxDB information to submit measurements
* @{
*/
#define DATABASE_HOST "opi" /**< influxDB host (or IP) */
#define DATABASE_PORT 8086 /**< influxDB port for HTTP API */
#define DATABASE_USER "meter" /**< name of user with permission to write to database */
#define DATABASE_PASSWORD "password" /**< password of user with permission to write to database */
#define DATABASE_NAME "spark_abacus" /**< name of database where to write the measurement values to */
/** @} */
int _write(int file, char *ptr, int len)
{
int i; // how much data has been sent
static char newline = 0; // what newline has been sent
if (file == STDOUT_FILENO || file == STDERR_FILENO) {
for (i = 0; i < len; i++) {
if (ptr[i] == '\r' || ptr[i] == '\n') { // send CR+LF newline for most carriage return and line feed combination
if (newline==0 || (newline==ptr[i])) { // newline has already been detected
//usart_putchar_nonblocking('\r'); // send newline over USART
//usart_putchar_nonblocking('\n'); // send newline over USART
cdcacm_putchar('\r'); // send newline over USB
cdcacm_putchar('\n'); // send newline over USB
newline = ptr[i]; // remember the newline
}
if (ptr[i] == '\n') { // line feed are always considered to end a line (the LF+CR combination is not supported to better support the others)
newline = 0; // clear new line
}
} else { // non-newline character
//usart_putchar_nonblocking(ptr[i]); // send byte over USART
cdcacm_putchar(ptr[i]); // send byte over USB
newline = 0; // clear new line
}
}
return i;
}
return -1;
}
/** user input command */
static char command[32] = {0};
/** user input command index */
uint8_t command_i = 0;
/** process user command
* @param[in] str user command string (\0 ended)
*/
static void process_command(char* str)
{
// split command
const char* delimiter = " ";
char* word = strtok(str,delimiter);
if (!word) {
goto error;
}
// parse command
if (0==strcmp(word,"help")) {
printf("available commands:\n");
printf("led [on|off|toggle]\n");
printf("time [HH:MM:SS]\n");
} else if (0==strcmp(word,"led")) {
word = strtok(NULL,delimiter);
if (!word) {
goto error;
} else if (0==strcmp(word,"on")) {
led_on(); // switch LED on
printf("LED switched on\n"); // notify user
} else if (0==strcmp(word,"off")) {
led_off(); // switch LED off
printf("LED switched off\n"); // notify user
} else if (0==strcmp(word,"toggle")) {
led_toggle(); // toggle LED
printf("LED toggled\n"); // notify user
} else {
goto error;
}
} else if (0==strcmp(word,"time")) {
word = strtok(NULL,delimiter);
if (!word) {
printf("current time: %02lu:%02lu:%02lu\n", rtc_get_counter_val()/(60*60), (rtc_get_counter_val()%(60*60))/60, (rtc_get_counter_val()%60)); // get and print time from internal RTC
} else if (strlen(word)!=8 || word[0]<'0' || word[0]>'2' || word[1]<'0' || word[1]>'9' || word[3]<'0' || word[3]>'5' || word[4]<'0' || word[4]>'9' || word[6]<'0' || word[6]>'5' || word[7]<'0' || word[7]>'9') { // time format is incorrect
goto error;
} else {
rtc_set_counter_val(((word[0]-'0')*10+(word[1]-'0')*1)*(60*60)+((word[3]-'0')*10+(word[4]-'0')*1)*60+((word[6]-'0')*10+(word[7]-'0')*1)); // set time in internal RTC counter
printf("time set\n");
}
} else {
goto error;
}
return; // command successfully processed
error:
printf("command not recognized. enter help to list commands\n");
return;
}
/** send HTTP data
* @warning blocking until a response has been received
* @param[in] data data to be send
* @param[in] length number of bytes to be sent, set to 0 to use the string length
* @return if data has been sent
*/
static bool http_send(uint8_t* data, size_t length)
{
if (length==0) {
radio_esp8266_send(data, strlen((char*)data)); // send string data
} else {
radio_esp8266_send(data, length); // send raw data
}
while (!radio_esp8266_activity) { // wait until response has been received
__WFI(); // wait until something happens
}
if (!radio_esp8266_success) {
fprintf(stderr,"could not send data\n");
return false;
}
return true;
}
/** end HTTP connection
* @warning blocking until a response has been received
* @return if connection has been closed
*/
static bool http_end(void)
{
radio_esp8266_close(); // close connection
while (!radio_esp8266_activity) { // wait until response has been received
__WFI(); // wait until something happens
}
return radio_esp8266_success;
}
/** open HTTP connection and send POST header
* @warning blocking until a response has been received
* @param[in] host host name or IP of HTTP server to connect to
* @param[in] port port number of HTTP server to connect to
* @param[in] length number of bytes to POST
* @return if HTTP POST succeeded
*/
static bool http_post_header(char* host, uint16_t port, size_t length)
{
char http_line[256] = {0}; // generated lines
radio_esp8266_tcp_open(host, port); // open connection
while (!radio_esp8266_activity) { // wait until response has been received
__WFI(); // wait until something happens
}
if (!radio_esp8266_success) {
fprintf(stderr,"TCP connection failed\n");
return false;
}
if (snprintf(http_line, LENGTH(http_line), "POST /write?db=%s&u=%s&p=%s HTTP/1.1\r\n", DATABASE_NAME, DATABASE_USER, DATABASE_PASSWORD)<=0) {
fprintf(stderr,"could not create POST line\n");
return false;
}
if (!http_send((uint8_t*)http_line, 0)) {
fprintf(stderr,"could not send POST line\n");
}
if (snprintf(http_line, LENGTH(http_line), "Content-Length: %u\r\n", length)<0) { // set content length (for measurements)
fprintf(stderr,"could not create line\n");
return false;
}
if (!http_send((uint8_t*)http_line, 0)) { // send data
return false;
}
if (!http_send((uint8_t*)"Host: influx\r\n", 0)) { // send data
return false;
}
if (!http_send((uint8_t*)"\r\n", 0)) { // send data
return false;
}
return true;
}
/** program entry point
* this is the firmware function started by the micro-controller
*/
void main(void);
void main(void)
{
rcc_clock_setup_in_hse_8mhz_out_72mhz(); // use 8 MHz high speed external clock to generate 72 MHz internal clock
#if DEBUG
DBGMCU_CR |= DBGMCU_CR_IWDG_STOP; // stop independent watchdog counter when code is halted
DBGMCU_CR |= DBGMCU_CR_WWDG_STOP; // stop window watchdog counter when code is halted
DBGMCU_CR |= DBGMCU_CR_STANDBY; // allow debug also in standby mode (keep digital part and clock powered)
DBGMCU_CR |= DBGMCU_CR_STOP; // allow debug also in stop mode (keep clock powered)
DBGMCU_CR |= DBGMCU_CR_SLEEP; // allow debug also in sleep mode (keep clock powered)
#else
//setup watchdog to reset in case we get stuck (i.e. when an error occurred)
iwdg_set_period_ms(WATCHDOG_PERIOD); // set independent watchdog period
iwdg_start(); // start independent watchdog
#endif
// setup board
board_setup();
// setup USART and USB for user communication
//usart_setup(); // setup USART (for printing)
cdcacm_setup(); // setup USB CDC ACM (for printing)
setbuf(stdout, NULL); // set standard out buffer to NULL to immediately print
setbuf(stderr, NULL); // set standard error buffer to NULL to immediately print
// minimal setup ready
printf("welcome to the spark abacus electricity monitoring system\n"); // print welcome message
#if !(DEBUG)
printf("watchdog set to %.2fs\n",WATCHDOG_PERIOD/1000.0);
if (FLASH_OBR&FLASH_OBR_OPTERR) {
printf("option bytes not set in flash: software wachtdog used (not started at reset)\n");
} else if (FLASH_OBR&FLASH_OBR_WDG_SW) {
printf("software wachtdog used (not started at reset)\n");
} else {
printf("hardware wachtdog used (started at reset)\n");
}
#endif
// setup LEDs
printf("setup status LEDs: ");
rcc_periph_clock_enable(RCC_GPIO(LED_HEARTBEAT_PORT)); // enable clock for LED
gpio_set_mode(GPIO(LED_HEARTBEAT_PORT), GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO(LED_HEARTBEAT_PIN)); // set LED pin to 'output push-pull'
gpio_set(GPIO(LED_HEARTBEAT_PORT), GPIO(LED_HEARTBEAT_PIN)); // switch off LED per default
rcc_periph_clock_enable(RCC_GPIO(LED_QUERY_PORT)); // enable clock for LED
gpio_set_mode(GPIO(LED_QUERY_PORT), GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO(LED_QUERY_PIN)); // set LED pin to 'output push-pull'
gpio_set(GPIO(LED_QUERY_PORT), GPIO(LED_QUERY_PIN)); // switch off LED per default
rcc_periph_clock_enable(RCC_GPIO(LED_SUBMIT_PORT)); // enable clock for LED
gpio_set_mode(GPIO(LED_SUBMIT_PORT), GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO(LED_SUBMIT_PIN)); // set LED pin to 'output push-pull'
gpio_set(GPIO(LED_SUBMIT_PORT), GPIO(LED_SUBMIT_PIN)); // switch off LED per default
printf("OK\n");
// setup RTC
printf("setup internal RTC: ");
rtc_auto_awake(RCC_LSE, 0x8000-1); // ensure internal RTC is on, uses the 32.768 kHz LSE, and the prescale is set to our tick speed, else update backup registers accordingly (power off the micro-controller for the change to take effect)
rtc_interrupt_enable(RTC_SEC); // enable RTC interrupt on "seconds"
nvic_enable_irq(NVIC_RTC_IRQ); // allow the RTC to interrupt
printf("OK\n");
uint32_t ticks_time = rtc_get_counter_val(); // get time from internal RTC (since first start/power up)
printf("uptime: %02lu:%02lu:%02lu\n", ticks_time/(60*60), (ticks_time%(60*60))/60, (ticks_time%60)); // display time
// setup DDM100TC electricity meter
printf("setup DDM100TC electricity meter: ");
rcc_periph_clock_enable(RCC_PWR); // enable clock for the power domain
rcc_periph_clock_enable(RCC_BKP); // enable clock for the backup domain to access backups register, where the number of pulses is stored
rcc_periph_clock_enable(RCC_GPIO(DDM100TC_PORT)); // enable clock for GPIO block
gpio_set_mode(GPIO_BANK_(DDM100TC_CAPTURE), GPIO_MODE_INPUT, GPIO_CNF_INPUT_PULL_UPDOWN, GPIO_(DDM100TC_CAPTURE)); // setup GPIO pin as input
gpio_clear(GPIO_BANK_(DDM100TC_CAPTURE), GPIO_(DDM100TC_CAPTURE)); // pull down since the meter will set VCC when pulsing
rcc_periph_clock_enable(RCC_AFIO); // enable pin alternate function (timer capture)
rcc_periph_clock_enable(RCC_TIM(DDM100TC_TIMER)); // enable clock for timer block
timer_reset(TIM(DDM100TC_TIMER)); // reset timer state
timer_set_mode(TIM(DDM100TC_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_set_prescaler(TIM(DDM100TC_TIMER), 0xffff); // set the prescaler to the maximum ( 1/(72E6/(2**16))=0.91ms which is a good enough resolution for this purpose)
timer_set_ti1_ch1(TIM(DDM100TC_TIMER)); // connect TIMx_CH1 to TI1 (this depends on the input capture pin you selected)
timer_ic_set_input(TIM(DDM100TC_TIMER), TIM_IC1, TIM_IC_IN_TI1); // configure IC1 to use TI1
timer_ic_set_filter(TIM(DDM100TC_TIMER), TIM_IC1, TIM_IC_CK_INT_N_8); // use 8 sample to filter input (remove noise)
timer_ic_set_filter(TIM(DDM100TC_TIMER), TIM_IC1, TIM_IC_DTF_DIV_32_N_8);
timer_ic_set_polarity(TIM(DDM100TC_TIMER), TIM_IC1, TIM_IC_RISING); // capture on rising edge
timer_ic_set_prescaler(TIM(DDM100TC_TIMER), TIM_IC1, TIM_IC_PSC_OFF); // don't use any prescaler since we want to capture every pulse
timer_slave_set_trigger(TIM(DDM100TC_TIMER), TIM_SMCR_TS_TI1FP1); // set filtered TI1 as trigger
timer_slave_set_mode(TIM(DDM100TC_TIMER), TIM_SMCR_SMS_RM); // reinitialise counter on rising edge of trigger
timer_clear_flag(TIM(DDM100TC_TIMER), TIM_SR_UIF); // clear update (UEv) flag
timer_update_on_overflow(TIM(DDM100TC_TIMER)); // only use counter overflow as UEV source
timer_enable_irq(TIM(DDM100TC_TIMER), TIM_DIER_UIE); // enable update event interrupt
timer_clear_flag(TIM(DDM100TC_TIMER), TIM_SR_CC1IF); // clear input compare flag
timer_enable_irq(TIM(DDM100TC_TIMER), TIM_DIER_CC1IE); // enable capture interrupt
nvic_enable_irq(NVIC_TIM_IRQ(DDM100TC_TIMER)); // catch interrupt in service routine
timer_ic_enable(TIM(DDM100TC_TIMER), TIM_IC1); // enable capture
timer_set_counter(TIM(DDM100TC_TIMER), 0); // reset timer counter
timer_enable_counter(TIM(DDM100TC_TIMER)); // enable timer
printf("OK\n");
// setup PZEM electricity meter
printf("setup PZEM-004 electricity meter: ");
sensor_pzem_setup(); // setup PZEM electricity meter
printf("OK\n");
// setup SDM120 electricity meter
printf("setup SDM120 electricity meter: ");
sensor_sdm120_setup(9600); // setup SDM120 electricity meter (get baud rate by scrolling through the menu on the device)
printf("OK\n");
//setup ESP8266 WiFi SoC
printf("setup ESP8266 WiFi SoC: ");
radio_esp8266_setup();
printf("OK\n");
// main loop
printf("command input: ready\n");
bool action = false; // if an action has been performed don't go to sleep
button_flag = false; // reset button flag
char c = '\0'; // to store received character
bool char_flag = false; // a new character has been received
led_on(); // indicate setup is complete
// variables for PZEM-004T meter measurements
struct sensor_pzem_measurement_t pzem_measurements[3][SENSOR_PZEM_MAX]; // PZEM-004T measurements (2 meters, all measurements)
uint8_t pzem_meter = 0; // PZEM-004T meter index (add to prefix)
uint8_t pzem_measurement = 0; // PZEM-004T measurement index (matches the type)
// variables for SDM120 meter measurements
float sdm120_measurements[3][SENSOR_SDM120_MEASUREMENT_MAX]; // SDM120 measurements (2 meters, all measurements)
uint8_t sdm120_meter = 0; // SDM120 meter index (add to 1 to get ID)
uint8_t sdm120_measurement = 0; // SDM120 measurement index
// variables for DDM100TC meter measurements
uint32_t ddm100tc_value_energy = 0;
uint32_t ddm100tc_value_power = 0;
while (true) { // infinite loop
iwdg_reset(); // kick the dog
while (cdcacm_received) { // data received over USB
action = true; // action has been performed
c = cdcacm_getchar(); // store receive character
char_flag = true; // notify character has been received
}
while (char_flag) { // user data received
char_flag = false; // reset flag
action = true; // action has been performed
printf("%c",c); // echo receive character
if (c=='\r' || c=='\n') { // end of command received
if (command_i>0) { // there is a command to process
command[command_i] = 0; // end string
command_i = 0; // prepare for next command
process_command(command); // process user command
}
} else { // user command input
command[command_i] = c; // save command input
if (command_i<LENGTH(command)-2) { // verify if there is place to save next character
command_i++; // save next character
}
}
}
while (sensor_pzem_measurement_received) { // measurement from electricity meter received
sensor_pzem_measurement_received = false; // clear flag
struct sensor_pzem_measurement_t measurement = sensor_pzem_measurement_decode(); // decode measurement
if (measurement.type>=SENSOR_PZEM_MAX) {
fprintf(stderr,"unknown measurement type: %u\n", measurement.type);
while (true); // unhandled error
}
if (measurement.valid) { // only show valid measurement
printf("PZEM-004T meter %u ", pzem_meter);
switch (measurement.type) {
case SENSOR_PZEM_VOLTAGE:
printf("voltage: %.01f V\n", measurement.value.voltage); // display measurement
break;
case SENSOR_PZEM_CURRENT:
printf("current: %.02f A\n", measurement.value.current);
break;
case SENSOR_PZEM_POWER:
printf("power: %u W\n", measurement.value.power);
break;
case SENSOR_PZEM_ENERGY:
printf("energy: %lu Wh\n", measurement.value.energy);
break;
/* not used for this application
case SENSOR_PZEM_ADDRESS:
printf("address set\n");
break;
case SENSOR_PZEM_ALARM:
printf("alarm threshold set\n");
break;
*/
default:
break;
}
if (measurement.type!=pzem_measurement) {
fprintf(stderr, "PZEM-004T measurement mismatch: expected %u, got %u\n", pzem_measurement, measurement.type);
sensor_pzem_measurement_request(0xc0a80100+pzem_meter, pzem_measurement); // request same measurement
} else if (pzem_measurement<SENSOR_PZEM_MAX-1) { // not all measurement types requested
pzem_measurements[pzem_meter][pzem_measurement] = measurement; // save measurement (the type matches the index)
pzem_measurement++; // go to next measurement
sensor_pzem_measurement_request(0xc0a80100+pzem_meter, pzem_measurement); // request next measurement
} else { // all measurement types requested
pzem_measurements[pzem_meter][pzem_measurement] = measurement; // save measurement (the type matches the index)
pzem_meter++; // got to next meter
pzem_measurement = 0; // restart measurements
if (pzem_meter<LENGTH(pzem_measurements)) { // ensure next meter exists
sensor_pzem_measurement_request(0xc0a80100+pzem_meter, pzem_measurement); // request measurement for next meter
}
}
} else { // measurement not valid
fprintf(stderr, "PZEM-004T measurement invalid\n");
sensor_pzem_measurement_request(0xc0a80100+pzem_meter, pzem_measurement); // request same measurement
}
}
while (sensor_sdm120_measurement_received) { // measurement from electricity meter received
float measurement = sensor_sdm120_measurement_decode(); // decode measurement
if (isnan(measurement)) {
printf("error in SDM120 response\n");
sensor_sdm120_measurement_request(1+sdm120_meter, sdm120_measurement); // request same measurement
} else if (isinf(measurement)) {
printf("error SDM120 message received\n");
while (true); // unhandled error
} else {
sdm120_measurements[sdm120_meter][sdm120_measurement] = measurement; // save measurement
printf("SDM120 meter %u ", sdm120_meter); // display measurement
switch (sdm120_measurement) {
case SENSOR_SDM120_VOLTAGE:
printf("voltage: %.01f V\n", measurement);
break;
case SENSOR_SDM120_CURRENT:
printf("current: %.02f A\n", measurement);
break;
case SENSOR_SDM120_POWER_ACTIVE:
printf("power (active): %.0f W\n", measurement);
break;
case SENSOR_SDM120_POWER_APPARENT:
printf("power (apparent): %.0f VA\n", measurement);
break;
case SENSOR_SDM120_POWER_REACTIVE:
printf("power (reactive): %.0f VAr\n", measurement);
break;
case SENSOR_SDM120_POWER_FACTOR:
printf("power factor: %.02f\n", measurement);
break;
case SENSOR_SDM120_FREQUENCY:
printf("frequency: %.02f Hz\n", measurement);
break;
case SENSOR_SDM120_ENERGY_ACTIVE_IMPORT:
printf("energy (import,active): %.02f KWh\n", measurement);
break;
case SENSOR_SDM120_ENERGY_ACTIVE_EXPORT:
printf("energy (export,active): %.02f kWh\n", measurement);
break;
case SENSOR_SDM120_ENERGY_REACTIVE_IMPORT:
printf("energy (import,reactive): %.02f kVArh\n", measurement);
break;
case SENSOR_SDM120_ENERGY_REACTIVE_EXPORT:
printf("energy (export,reactive): %.02f kVArh\n", measurement);
break;
case SENSOR_SDM120_ENERGY_ACTIVE_TOTAL:
printf("energy (active,total): %.02f kWh\n", measurement);
break;
case SENSOR_SDM120_ENERGY_REACTIVE_TOTAL:
printf("energy (reactive,total): %.02f kVArh\n", measurement);
break;
default:
break;
}
if (sdm120_measurement<SENSOR_SDM120_MEASUREMENT_MAX-1) { // not all measurement type required
sdm120_measurement++; // go to next measurement
sensor_sdm120_measurement_request(1+sdm120_meter, sdm120_measurement); // request next measurement
} else { // all measurement types requested
sdm120_meter++; // got to next meter (sending to none existing meter will just end in void)
sdm120_measurement = 0; // start requesting all measurement
if (sdm120_meter<LENGTH(sdm120_measurements)) { // ensure next meter exists
sensor_sdm120_measurement_request(1+sdm120_meter, sdm120_measurement); // request measurement for next meter
}
}
}
}
while (rtc_internal_tick_flag) { // the internal RTC ticked
rtc_internal_tick_flag = false; // reset flag
gpio_toggle(GPIO(LED_HEARTBEAT_PORT), GPIO(LED_HEARTBEAT_PIN)); // toggle heart beat LED to indicate if main function is stuck (do not toggle onboard the LED on PC13 on the blue pill board since this heavily influences the RTC)
ticks_time = rtc_get_counter_val(); // copy time from internal RTC for processing
action = true; // action has been performed
if ((ticks_time%(60))==0) { // one minute passed
printf("uptime: %lu.%02lu:%02lu:%02lu\n", ticks_time/(60*60*24), (ticks_time/(60*60))%24, (ticks_time%(60*60))/60, (ticks_time%60)); // display external time
}
if ((ticks_time%(QUERY_PERIOD))==0) { // query period passed
printf("query meter measurements (%lu.%02lu:%02lu:%02lu)\n", ticks_time/(60*60*24), (ticks_time/(60*60))%24, (ticks_time%(60*60))/60, (ticks_time%60));
gpio_clear(GPIO(LED_QUERY_PORT), GPIO(LED_QUERY_PIN)); // switch on query LED
// start getting all PZEM-004T measurements from all meters
pzem_meter = 0; // reset PZEM meter number
pzem_measurement = 0; // reset PZEM measurement index
sensor_pzem_measurement_request(0xc0a80100+pzem_meter, pzem_measurement); // request first measurement
// start getting all SDM120 measurements from all meters
sdm120_meter = 0; // reset SDM120 meter number
sdm120_measurement = 0; // reset SDM120 measurement index
sensor_sdm120_measurement_request(1+sdm120_meter, sdm120_measurement); // request first measurement
// calculate and show DDM100TC measurements (base on number of pulses and interval)
ddm100tc_value_energy = (uint32_t)((((BKP_DR1<<16)+BKP_DR2)*(uint64_t)1000)/1600); // the meter has 1600 impulses/kWh (use 64-bit calculation to not overflow after 2684354 Wh)
if (ddm100tc_interval==0) { // no measurements received yet
ddm100tc_value_power = 0;
} else {
ddm100tc_value_power = (uint32_t)(((rcc_ahb_frequency*(double)1.0)/((uint32_t)TIM_PSC(TIM(DDM100TC_TIMER))+1))*(3600*1000/1600)/ddm100tc_interval); // calculate with floating point for precision
}
printf("DDM100TC meter energy: %lu Wh\n", ddm100tc_value_energy);
printf("DDM100TC meter power: %lu W\n", ddm100tc_value_power);
}
}
while (pzem_meter>=LENGTH(pzem_measurements) && sdm120_meter>=LENGTH(sdm120_measurements)) { // all measurements received for all meter
action = true; // action has been performed
printf("saving measurements to database: ");
gpio_set(GPIO(LED_QUERY_PORT), GPIO(LED_QUERY_PIN)); // switch off query LED
gpio_clear(GPIO(LED_SUBMIT_PORT), GPIO(LED_SUBMIT_PIN)); // switch off submit LED
const char* pzem_strings[SENSOR_PZEM_MAX] = {
"voltage,meter=PZEM-004T,phase=%u value=%.1f\n",
"current,meter=PZEM-004T,phase=%u value=%.2f\n",
"power,meter=PZEM-004T,phase=%u value=%u\n",
"energy,meter=PZEM-004T,phase=%u value=%lu\n"
};
const char* sdm120_strings[SENSOR_SDM120_MEASUREMENT_MAX] = {
"voltage,meter=SDM120,phase=%u value=%.3f\n",
"current,meter=SDM120,phase=%u value=%.3f\n",
"power,meter=SDM120,phase=%u,type=active value=%.3f\n",
"power,meter=SDM120,phase=%u,type=apparent value=%.3f\n",
"power,meter=SDM120,phase=%u,type=reactive value=%.3f\n",
"power,meter=SDM120,phase=%u,type=factor value=%.3f\n",
"frequency,meter=SDM120,phase=%u value=%.3f\n",
"energy,meter=SDM120,phase=%u,type=active,direction=import value=%.3f\n",
"energy,meter=SDM120,phase=%u,type=active,direction=export value=%.3f\n",
"energy,meter=SDM120,phase=%u,type=reactive,direction=import value=%.3f\n",
"energy,meter=SDM120,phase=%u,type=reactive,direction=export value=%.3f\n",
"energy,meter=SDM120,phase=%u,type=active,direction=total value=%.3f\n",
"energy,meter=SDM120,phase=%u,type=reactive,direction=total value=%.3f\n"
};
const char* ddm100tc_string_energy = "energy,meter=DDM100TC value=%lu\n";
const char* ddm100tc_string_power = "power,meter=DDM100TC value=%lu\n";
// calculate length for text to POST
char line[256] = {0}; // measurement line to send
size_t data_length = 0; /**< length of the data string to send */
for (pzem_meter = 0; pzem_meter<LENGTH(pzem_measurements); pzem_meter++) {
for (pzem_measurement = 0; pzem_measurement<SENSOR_PZEM_MAX; pzem_measurement++) {
struct sensor_pzem_measurement_t measurement = pzem_measurements[pzem_meter][pzem_measurement]; // get measurement
if (measurement.valid) { // only use valid measurements
switch (measurement.type) { // get the size (hope no error is occurring)
case SENSOR_PZEM_VOLTAGE:
data_length += snprintf(line, LENGTH(line), pzem_strings[pzem_measurement], pzem_meter, measurement.value.voltage);
break;
case SENSOR_PZEM_CURRENT:
data_length += snprintf(line, LENGTH(line), pzem_strings[pzem_measurement], pzem_meter, measurement.value.current);
break;
case SENSOR_PZEM_POWER:
data_length += snprintf(line, LENGTH(line), pzem_strings[pzem_measurement], pzem_meter, measurement.value.power);
break;
case SENSOR_PZEM_ENERGY:
data_length += snprintf(line, LENGTH(line), pzem_strings[pzem_measurement], pzem_meter, measurement.value.energy);
break;
default:
break;
}
}
}
}
for (sdm120_meter = 0; sdm120_meter<LENGTH(sdm120_measurements); sdm120_meter++) {
for (sdm120_measurement = 0; sdm120_measurement<SENSOR_SDM120_MEASUREMENT_MAX; sdm120_measurement++) {
if (sdm120_measurement<SENSOR_SDM120_ENERGY_ACTIVE_IMPORT) {
data_length += snprintf(line, LENGTH(line), sdm120_strings[sdm120_measurement], sdm120_meter, sdm120_measurements[sdm120_meter][sdm120_measurement]); // get the size (hope no error is occurring)
} else {
data_length += snprintf(line, LENGTH(line), sdm120_strings[sdm120_measurement], sdm120_meter, sdm120_measurements[sdm120_meter][sdm120_measurement]*1000.0); // get the size (hope no error is occurring)
}
}
}
data_length += snprintf(line, LENGTH(line), ddm100tc_string_energy, ddm100tc_value_energy); // get the size (hope no error is occurring)
data_length += snprintf(line, LENGTH(line), ddm100tc_string_power, ddm100tc_value_power); // get the size (hope no error is occurring)
// send HTTP POST request
if (!http_post_header(DATABASE_HOST, DATABASE_PORT, data_length)) { // send header
fprintf(stderr,"could not sent HTTP POST header\n");
} else {
// send PZEM-004T values
for (pzem_meter = 0; pzem_meter<LENGTH(pzem_measurements); pzem_meter++) {
for (pzem_measurement = 0; pzem_measurement<SENSOR_PZEM_MAX; pzem_measurement++) {
struct sensor_pzem_measurement_t measurement = pzem_measurements[pzem_meter][pzem_measurement]; // get measurement
if (measurement.valid) { // only use valid measurements
switch (measurement.type) { // make line (hope no error is occurring)
case SENSOR_PZEM_VOLTAGE:
snprintf(line, LENGTH(line), pzem_strings[pzem_measurement], pzem_meter, measurement.value.voltage);
break;
case SENSOR_PZEM_CURRENT:
snprintf(line, LENGTH(line), pzem_strings[pzem_measurement], pzem_meter, measurement.value.current);
break;
case SENSOR_PZEM_POWER:
snprintf(line, LENGTH(line), pzem_strings[pzem_measurement], pzem_meter, measurement.value.power);
break;
case SENSOR_PZEM_ENERGY:
snprintf(line, LENGTH(line), pzem_strings[pzem_measurement], pzem_meter, measurement.value.energy);
break;
default:
break;
}
http_send((uint8_t*)line, 0); // don't care about the result
}
}
}
// send SDM120 values
for (sdm120_meter = 0; sdm120_meter<LENGTH(sdm120_measurements); sdm120_meter++) {
for (sdm120_measurement = 0; sdm120_measurement<SENSOR_SDM120_MEASUREMENT_MAX; sdm120_measurement++) {
if (sdm120_measurement<SENSOR_SDM120_ENERGY_ACTIVE_IMPORT) {
if (snprintf(line, LENGTH(line), sdm120_strings[sdm120_measurement], sdm120_meter, sdm120_measurements[sdm120_meter][sdm120_measurement])>0) {
http_send((uint8_t*)line, 0); // don't care about the result
}
} else {
if (snprintf(line, LENGTH(line), sdm120_strings[sdm120_measurement], sdm120_meter, sdm120_measurements[sdm120_meter][sdm120_measurement]*1000.0)>0) {
http_send((uint8_t*)line, 0); // don't care about the result
}
}
}
}
if (snprintf(line, LENGTH(line), ddm100tc_string_energy, ddm100tc_value_energy)>0) {
http_send((uint8_t*)line, 0); // don't care about the result
}
if (snprintf(line, LENGTH(line), ddm100tc_string_power, ddm100tc_value_power)>0) {
http_send((uint8_t*)line, 0); // don't care about the result
}
http_end(); // end HTTP request (don't care about the result)
gpio_set(GPIO(LED_SUBMIT_PORT), GPIO(LED_SUBMIT_PIN)); // switch off submit LED
printf("OK\n");
}
pzem_meter = 0; // reset meter
sdm120_meter = 0; // reset meter
}
if (action) { // go to sleep if nothing had to be done, else recheck for activity
action = false;
} else {
__WFI(); // go to sleep
}
}
}
/** @brief interrupt service routine called when tick passed on RTC */
void rtc_isr(void)
{
rtc_clear_flag(RTC_SEC); // clear flag
rtc_internal_tick_flag = true; // notify to show new time
}
/** interrupt service routine called for DDM100TC timer */
void TIM_ISR(DDM100TC_TIMER)(void)
{
static uint32_t long_time = 0; // large value of time, compared to the 16 bits counters
if (timer_get_flag(TIM(DDM100TC_TIMER), TIM_SR_UIF)) { // overflow update event happened
timer_clear_flag(TIM(DDM100TC_TIMER), TIM_SR_UIF); // clear flag
long_time += 0x10000; // count timer overflow for large time value
} else if (timer_get_flag(TIM(DDM100TC_TIMER), TIM_SR_CC1IF)) { // pulse detected
long_time += TIM_CCR1(TIM(DDM100TC_TIMER)); // get time (reading also clears the flag)
if (long_time>90) { // pulse is 90ms long, thus a new pulse before this time is probably just noise)
ddm100tc_interval = long_time; // save new time
pwr_disable_backup_domain_write_protect(); // enable backup register write
BKP_DR2++; // increment number of pulses detected
if (BKP_DR2==0) { // 16-bit register overflow
BKP_DR1++; // same 16-bit bit is second register
}
pwr_enable_backup_domain_write_protect(); // protect backup register from write
long_time = 0; // reset time (slave mode should also have reset the counter)
}
}
}