You are here
Home > embedded >

Smart Meter IoT Application Software

In this how to guide on Using ESP IDF with STPM32, we are going to build Smart Meter IoT Application Software. This will use SPI interface for communication between esp32 wroom32 and STPM32/33 energy metering application. Using this sample code I will show you how to build mobile application for smart energy meter with Metrology Accuracy of class 0.2. In Indian households most of the energy meters are of accuracy class 1. After reading this guide you will know how to build digital energy meter suitable for advanced metering infrastructure (AMI) and is compatible with smart grid communication technologies with support of distributed generation (DG).

Building smart meter IoT application software requires four things, hardware, firmware, mobile application software and webserver. Most of the chip providers will give you hardware schematic for smart meter like Renesas RL78 based single phase power meter, STM32L4 based meter and TI controller. For application software openEnergyMonitor is good place to start.

Contents

HW Connections

Lets get started with hardware block diagram. This example we are using ESP32 as main controller and STPM32 as metering IC.

ESP32 Interface With STPM32

ESP32 is having 3 SPI controllers. Please refer esp32 programming guide. We are using SPI3 (VSPI) for communication. We are going to use IO_MUX pins for SPI buses 3-> CS: 5, CLK:18 , MISO:19, MOSI:23.

For STPM33/32 we will have additional 2 pins- Enable and SYN. We can assign any GPIO which can be configured as output in esp32 for EN and SYN. For now we will use D16 and D17.

ESP32 Programming in IDF

ESP-IDF is Espressif’s official IoT Development Framework for the ESP32 and ESP32-S series of SoCs. It provides a self-sufficient SDK for any generic application development on these platforms, using programming languages such as C and C++. In this example we will be using ESP IDF to build and write C code.

We will start with defining the related pins of esp32 and initializing STPM32/33. STPM32 is having SPI and UART interfaces for communication. According to STPM32 datasheet, we can use SCS signal for selecting SPI/UART peripheral at initialization. Initialization sequence is given in Getting started guide for STPM3x here.

#define PIN_NUM_MISO 19
#define PIN_NUM_MOSI 23
#define PIN_NUM_CLK  18
#define PIN_NUM_CS   5
#define PIN_NUM_SYN  16
#define PIN_NUM_EN   17

To select SPI peripheral at startup, SCS signal must be set held low before VCC and EN rise. SCS must be kept low until a couple of clock period (16 MHz) reaches the device. The device is locked in SPI mode until a reset by EN pulse or a new power-on sequence is performed.

Create setup() function for above steps as follows:

void setup()
{
gpio_set_level(PIN_NUM_EN,false);
gpio_set_level(PIN_NUM_SYN,true);
gpio_set_level(PIN_NUM_CS, false);
ets_delay_us(10000);
gpio_set_level(PIN_NUM_EN,true);
ets_delay_us(5000);
gpio_set_level(PIN_NUM_CS, true);
}

We will write reset function next

SYN signal is used to reset the DSP. These reset pulses must be generated once, just after the power-on sequence.
void reset()
{
for(int i=0;i<3;i++)
{
gpio_set_level(PIN_NUM_SYN,false);
ets_delay_us(1000);
gpio_set_level(PIN_NUM_SYN,true);
ets_delay_us(1000);
}
gpio_set_level(PIN_NUM_CS, false);
ets_delay_us(1000);
gpio_set_level(PIN_NUM_CS,true);
}

Next thing is to initialize esp32 peripheral. We will initialize the spi bus.For that fill spi_bus_config_t structure as:

spi_bus_config_t buscfg={
.miso_io_num=PIN_NUM_MISO,//Assign MISO pin num
.mosi_io_num=PIN_NUM_MOSI,//Assign MOSI pin num
.sclk_io_num=PIN_NUM_CLK,//Assign CLK pin num
.quadwp_io_num=-1,//not used
.quadhd_io_num=-1,//not used
.max_transfer_sz=0//set to default 4096
};
ret=spi_bus_initialize(SPI3_HOST, &buscfg, 0); // 1 for DMA

Fill the spi_device_interface_config_t structure, which is a configuration for a SPI slave device. STPM3x uses only SPI mode 3 , set same spi mode here. For communicating with STPM3x, we need to send 5bytes data in a single transaction ,dont use command bits and address bits. We are using 1Mhz clock speed for communication. For each SPI transaction, we need to reset and set CS pin as per SPI protocol. SCS pin will be used for data exchange activation. If we set spics_io_num> 0, then SPI hardware will control the Chip select. In this sample code we will program for every transaction .i.e. we are using software CS. STPM3x implements full duplex communication protocol. In esp32 also we will use full duplex mode. Don’t set half duplex flag.

Register a Device connected to the bus with the driver by calling the function spi_bus_add_device() . We are using VSPI(SPI3_HOST).

spi_device_interface_config_t devcfg={
.command_bits=0,
.address_bits=0,
.dummy_bits=0,
.flags = 0,
.mode=3,                  //SPI mode 3
.duty_cycle_pos=128,
.clock_speed_hz=1000000, //Clock out frequency (Hz)
.queue_size=1,
.spics_io_num=-1,        
};
spi_bus_add_device(SPI3_HOST, &devcfg, &spi);

STPM3x single communication session consists of 4 + 1 (optional CRC) bytes full-duplex datasequence – read address – write address – LS data byte – MS data byte – CRC. We will create array of 5 bytes for sending data and receicing data. Use tx_buffer member from config structure to point to writeBuf and rx_buffer member to point to readBuf. We will create and fill SPI transaction structure as:

spi_transaction_t transaction;
memset(&transaction, 0, sizeof(transaction));
transaction.flags = 0; 
transaction.cmd = 0;//not used
transaction.addr = 0;//nbot used
transaction.tx_buffer = &gSPIMEASURE.writeBuf;
transaction.rx_buffer = &gSPIMEASURE.readBuf;
transaction.length = 8*5;  // 5 bytes 
transaction.rxlength = 8*5; // 5 bytes

According to STPM3x SPI protocol,

Write-requests are executed immediately after the transaction has completed, while read- requests are fulfilled at the end of the next transaction only

We will write main function for SPI transfer. First we will fill the transaction.tx_buffer with our frame data and for read operation, we have to send dummy bytes to get the response.

/***********Sending_data********************/
gpio_set_level(PIN_NUM_CS,false);  
spi_transaction_t transaction;
memset(&transaction, 0, sizeof(transaction));
transaction.flags = 0; 
transaction.cmd = 0;
transaction.addr = 0;
transaction.tx_buffer = &gSPIMEASURE.writeBuf;
transaction.rx_buffer = &gSPIMEASURE.readBuf;
transaction.length = 8*5;  // 5 bytes
transaction.rxlength = 8*5; // 5 bytes
ret=spi_device_transmit(handle, &transaction);
assert(ret == ESP_OK); 
gpio_set_level(PIN_NUM_CS,true);  
/********Reading_data*****************/
gpio_set_level(PIN_NUM_CS,false);  
for (int i = 0; i < 5; i++)
{
gSPIMEASURE.readBuf[i] = 0;
}
for(int i=0;i<5;i++)
{
gSPIMEASURE.writeBuf[i] = 0xFF;//Writing dummy bytes
}
transaction.flags = 0; 
transaction.cmd = 0;
transaction.addr = 0;
transaction.tx_buffer = &gSPIMEASURE.writeBuf;
transaction.rx_buffer = &gSPIMEASURE.readBuf;
transaction.length = 8*5;     
transaction.rxlength = 8*5;        
ret=spi_device_transmit(handle, &transaction);
assert(ret == ESP_OK); 
gpio_set_level(PIN_NUM_CS,true);  
printf("\nrecvd data:\n ");                    
for (int i = 0; i < 5; i++)
{
printf("%x\t",gSPIMEASURE.readBuf[i] );
}
ets_delay_us(2000000);

Note that for every transaction, we have to reset and set CS. Finally, in readBuf we will get the response data . Result will be in MSB First format. We have to convert it to LSB first.

Example Code To Get Started With Smart Meter Application

Following is the full test code which have been explained above: flash esp32 with this program and do the connections as per hw connection diagram. In spi_readRegister(addr,0) function, pass the address of STPM3x register which you want to read. You will get the register values in LSB first format.

/*
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "driver/spi_master.h"
#include "soc/gpio_struct.h"
#include "driver/gpio.h"
#include "sdkconfig.h"
#include "esp_log.h"
#define CRC_8 (0x07)
#define STPM3x_FRAME_LEN (5)
// HSPI GPIO definition
#define PIN_NUM_MISO 19
#define PIN_NUM_MOSI 23
#define PIN_NUM_CLK  18
#define PIN_NUM_CS   5
#define PIN_NUM_SYN  16
#define PIN_NUM_EN   17
#define NUMBYTES     5
uint8_t data[NUMBYTES];
typedef struct{
int SCS_arr[1];
uint8_t readBuf[5],writeBuf[5],CRC_u8Checksum;
}spi_measure_t;
spi_device_handle_t spi;
spi_measure_t gSPIMEASURE;
static void Crc8Calc (uint8_t u8Data)
{
int loc_u8Idx,loc_u8Temp;
loc_u8Idx=0;
while(loc_u8Idx<8)
{
loc_u8Temp = u8Data^gSPIMEASURE.CRC_u8Checksum;
gSPIMEASURE.CRC_u8Checksum<<=1;
if(loc_u8Temp&0x80)
{
gSPIMEASURE.CRC_u8Checksum^=CRC_8;
}
u8Data<<=1;
loc_u8Idx++;
}
}
static uint8_t CalcCRC8(uint8_t *pBuf)
{
int i;
gSPIMEASURE.CRC_u8Checksum = 0x00;
for (i=0; i<STPM3x_FRAME_LEN-1; i++)
{
Crc8Calc(pBuf[i]);
}
return gSPIMEASURE.CRC_u8Checksum;
}
void setup()
{
gpio_set_level(PIN_NUM_EN,false);
gpio_set_level(PIN_NUM_SYN,true);
gpio_set_level(PIN_NUM_CS, false);
ets_delay_us(10000);
gpio_set_level(PIN_NUM_EN,true);
ets_delay_us(5000);
gpio_set_level(PIN_NUM_CS, true);
}
void reset()
{
for(int i=0;i<3;i++)
{
gpio_set_level(PIN_NUM_SYN,false);
ets_delay_us(1000);
gpio_set_level(PIN_NUM_SYN,true);
ets_delay_us(1000);
}
gpio_set_level(PIN_NUM_CS, false);
ets_delay_us(1000);
gpio_set_level(PIN_NUM_CS,true);
}
esp_err_t spi_write_bytes(spi_device_handle_t handle, const uint8_t *txdata, size_t txlength){
if(txlength == 0 ) return ESP_ERR_INVALID_SIZE;
esp_err_t ret=ESP_OK;
/**********************Sending data**************************************************************************************/
for (int i = 0; i < 5; i++)
{
gSPIMEASURE.readBuf[i] = 0;
}
gpio_set_level(PIN_NUM_CS,false);               
spi_transaction_t transaction;
memset(&transaction, 0, sizeof(transaction));
transaction.flags = 0; 
transaction.cmd = 0;
transaction.addr = 0;
transaction.tx_buffer = &gSPIMEASURE.writeBuf;
transaction.rx_buffer = &gSPIMEASURE.readBuf;
transaction.length = 8*5;                         // 5 bytes
transaction.rxlength = 8*5;                       // 5 bytes
ret=spi_device_transmit(handle, &transaction);
assert(ret == ESP_OK); 
gpio_set_level(PIN_NUM_CS,true);  
/**********************************Reading data***************************************************************************/
gpio_set_level(PIN_NUM_CS,false);  
for (int i = 0; i < 5; i++)
{
gSPIMEASURE.readBuf[i] = 0;
}
for(int i=0;i<5;i++)
{
gSPIMEASURE.writeBuf[i] = 0xFF;
}
transaction.flags = 0; 
transaction.cmd = 0;
transaction.addr = 0;
transaction.tx_buffer = &gSPIMEASURE.writeBuf;
transaction.rx_buffer = &gSPIMEASURE.readBuf;
transaction.length = 8*5;                         // 1 byte
transaction.rxlength = 8*5;        
ret=spi_device_transmit(handle, &transaction);
assert(ret == ESP_OK); 
gpio_set_level(PIN_NUM_CS,true);  
printf("\nrecvd data:\n ");                    
for (int i = 0; i < 5; i++)
{
printf("%x\n",gSPIMEASURE.readBuf[i] );
}
ets_delay_us(2000000);
return ret;
}
void sendFrameFunc(uint8_t rd_addr,uint8_t wr_addr,uint8_t data_1,uint8_t data_2)
{
uint32_t ret = 0;
gSPIMEASURE.writeBuf[0] = rd_addr;
gSPIMEASURE.writeBuf[1] = wr_addr;
gSPIMEASURE.writeBuf[2] = data_1;
gSPIMEASURE.writeBuf[3] = data_2;
gSPIMEASURE.writeBuf[4] =CalcCRC8(gSPIMEASURE.writeBuf);
ret = spi_write_bytes(spi,gSPIMEASURE.writeBuf,5);
if (ret == ESP_OK){
printf ("spi_send successful...\n");
}
else{
printf("spi_send FAILED\n");
}
}
uint32_t spi_readRegister(uint8_t addr)
{
uint32_t ret = 0;
sendFrameFunc(addr,0xFF,0xFF,0xFF);
return ret;
}
void app_main()
{
esp_err_t ret;
// Prepare GPIO
gpio_set_direction(PIN_NUM_SYN, GPIO_MODE_OUTPUT);
gpio_set_direction(PIN_NUM_CS, GPIO_MODE_OUTPUT); 
gpio_set_direction(PIN_NUM_EN, GPIO_MODE_OUTPUT); 
spi_bus_config_t buscfg={
.miso_io_num=PIN_NUM_MISO,
.mosi_io_num=PIN_NUM_MOSI,
.sclk_io_num=PIN_NUM_CLK,
.quadwp_io_num=-1,
.quadhd_io_num=-1,
.max_transfer_sz=0
};
spi_device_interface_config_t devcfg={
.command_bits=0,
.address_bits=0,
.dummy_bits=0,
.flags = 0,
.mode=3,                       //SPI mode 3
.duty_cycle_pos=128,
.clock_speed_hz=1000000,   //Clock out frequency (Hz)
.queue_size=1,
.spics_io_num=-1,        
};
//Initialize the SPI bus
printf ("Initializing SPI bus\n");
ret=spi_bus_initialize(SPI3_HOST, &buscfg, 0); // 1 for DMA
ESP_ERROR_CHECK(ret);
printf ("-------------------------------------\n\n");
printf ("Adding device\n");
ret=spi_bus_add_device(SPI3_HOST, &devcfg, &spi);
ESP_ERROR_CHECK(ret);
printf ("-------------------------------------\n\n");
setup();
ets_delay_us(30000);
reset();
while(1)
{
spi_readRegister(0x08);
ets_delay_us(200000);
}
}
If you send register address 0x08 then you will get following output.

Hope you have enjoyed this mini How Guide On STPM33/32 SPI Interface With ESP32 Using ESP IDF for how to build Smart Meter IoT Application Software.

Expanding This Knowhow To Build Smart Meter

After I get the metrology data, calibrate the meter for first time and it is easy to build the algorithms for energy calculation and management, theft detection, data logging etc. Remote load connect / disconnect, firmware upgrade, which is called as metering communication and DLMS- Device Language Message specification is being used for metering communication. With little modification in hardware schematic, Anti tamper and fraud detection can be implemented.

Recently Microsoft had launched IoT based end-to-end smart meter monitoring solution. This solution is available on Azure IoT Central, where you can monitor energy consumption, network status, and identify trends to improve customer support and smart meter management operations. This solution is available in USA for now, hopefully it will be available in India sometime, for now we have build Smart Meter IoT Application Software along with dashboard for our smart meters.

Neudeep’s Smart Energy Meter Dashboard Application
Top