Greetings,
I have successfully installed 1 EmonTx on my house , all is neat & clean. Now , inspired in learning the underground logic behind OEM system , i want to make a custom device based on a STM32F103C8Tx MCU (BluePill board) , i’m using the CT circuit adapted to 3V3 levels and I have accidentally ordered a big banch of ZMPT board, “the rubbish module” as stated by Robert in a comment ^^ , so as i understood from previous topics , the big problem of ZMPT module is the huge phase shift introduced with the electronics around the ZMPT CT. Which makes the phase calibration process tedious.
I did an order to bring some high voltage resistors to build the simple voltage sensing as explained by Robert in a comment.
Now , waiting the items to be received , i tried to bring it in phase with the method i will describe below.
First the hole system configuration of my setup is the following :
1- The timer clocks the ADCs at 12Khz , the ADCs are in dual mode , they start the sampling at the same time, then data is sent in a 32-bit word to a buffer , containing both raw values for current & voltage
2- As you can see the continuous mode is disabled so that only the timer can trigger the conversions , 12 khz is suited as it gives the ADC enough time to finish the conversion
3- To simulate phase shift correction , I’m using resistor load to play with the phase.To do the correction , I’m only introducing a specific order for power calculation , I shift the samples by a specific offset based on the phase error . Here is an example for 60° error :
4- My code is mainly based on the code in Emon3CT_CB_v2 , the main change is the offset in the voltage sample => (offset + i + 40)%2000 .
Now I’m not sure if the above is an accurate method , I will be helpful for any remark that i may have missed. Could I use this to stay with the ZMPT board and still have consistent values ?
#define VDD_APPLI ((uint32_t) 3300) /* Value of analog voltage supply Vdda (unit: mV) */
#define RANGE_12BITS ((uint32_t) 4095) /* Max digital value for a full range of 12 bits */
#define ADCCONVERTEDVALUES_BUFFER_SIZE ((uint32_t) 2000) /* Size of array containing ADC converted values */
#define true 1
#define false 0
#define MID_ADC_READING 2048
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
#define COMPUTATION_DUALMODE_ADCMASTER_RESULT(DATA) \
((DATA) & 0x0000FFFF)
#define COMPUTATION_DUALMODE_ADCSLAVE_RESULT(DATA) \
((DATA) >> 16)
uint32_t time2;
uint32_t time1;
uint32_t time_diff;
// Serial output buffer
char log_buffer[250];
// Flag
bool readings_ready = false;
// Calibration
float VCAL = 808.02;
float ICAL = 88.49;
// Number of waveforms to count
uint8_t waveforms = 250;
// accumulators
typedef struct dataMeter_
{
int64_t sum_P;
uint64_t sum_V_sq;
uint64_t sum_I_sq;
int32_t sum_V;
int32_t sum_I;
uint32_t count;
uint32_t positive_V;
uint32_t last_positive_V;
uint32_t cycles;
} dataMeter_t;
double Ws_acc = 0 ; // Watt second accumulator
static dataMeter_t dataMeter;
static dataMeter_t dataMeter_copy;
__IO uint32_t aADCDualConvertedValues[ADCCONVERTEDVALUES_BUFFER_SIZE];
void HAL_ADC_ConvHalfCpltCallback(ADC_HandleTypeDef* hadc)
{
process_frame(0);
}
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
process_frame(1000);
}
void process_frame(uint16_t offset)
{
int32_t sample_V, sample_I, signed_V, signed_I;
for (int i = 0; i < 1000; i++)
{
dataMeter_t *data = &dataMeter;
// ----------------------------------------
// Voltage
sample_V = COMPUTATION_DUALMODE_ADCMASTER_RESULT(aADCDualConvertedValues[offset + i ]);
//voltageValues[offset + i] = sample_V;
signed_V = sample_V - 1064;
data->sum_V += signed_V;
data->sum_V_sq += signed_V * signed_V;
// ----------------------------------------
// Current
sample_I = COMPUTATION_DUALMODE_ADCSLAVE_RESULT(aADCDualConvertedValues[(offset + i + 40)%2000]);
//currentValues[offset + i] = sample_I;
signed_I = sample_I - MID_ADC_READING;
data->sum_I += signed_I;
data->sum_I_sq += signed_I * signed_I;
// ----------------------------------------
// Power
data->sum_P += signed_V * signed_I;
// ----------------------------------------
// Sample Count
data->count++;
// ----------------------------------------
// Zero crossing detection
data->last_positive_V = data->positive_V;
if (signed_V > 5)
{
data->positive_V = true;
}
else if (signed_V < -5)
{
data->positive_V = false;
}
if (!data->last_positive_V && data->positive_V)
{
data->cycles++;
}
// ----------------------------------------
// Complete Waveform Cycles to count
if (data->cycles == waveforms)
{
data->cycles = 0;
dataMeter_t *data_copy = &dataMeter_copy;
// Copy accumulators for use in main loop
memcpy((void *)data_copy, (void *)data, sizeof(dataMeter_t));
// Reset accumulators to zero ready for next set of measurements
memset((void *)data, 0, sizeof(dataMeter_t));
readings_ready = true;
}
//float Vmeantemp = data->sum_V / (float)data->count;
//printf("mean V %f \r\n",Vmeantemp);
}
}
void TurnOnBuiltInLED(void) {
HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_RESET); // On LED
}
void TurnOffBuiltInLED(void) {
HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_SET); // On LED
}
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
float V_RATIO = VCAL * (3.3 / 4096.0);
float I_RATIO = ICAL * (3.3 / 4096.0);
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_DMA_Init();
MX_ADC1_Init();
MX_ADC2_Init();
MX_I2C1_Init();
MX_TIM3_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
HAL_ADCEx_Calibration_Start(&hadc1);
HAL_ADCEx_Calibration_Start(&hadc2);
HAL_TIM_Base_Start(&htim3);
HAL_ADC_Start(&hadc2);
HAL_ADCEx_MultiModeStart_DMA(&hadc1, aADCDualConvertedValues, ADCCONVERTEDVALUES_BUFFER_SIZE);
//to fix first time loop issue
time1 = HAL_GetTick();
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
if (readings_ready)
{
readings_ready = false;
time2 = time1;
time1 = HAL_GetTick();
time_diff = time1 - time2;
dataMeter_t *dt = &dataMeter_copy;
float Vmean = dt->sum_V / (float)dt->count;
float Imean = dt->sum_I / (float)dt->count;
dt->sum_V_sq /= (float)dt->count;
dt->sum_V_sq -= (Vmean * Vmean); // offset subtraction
if (dt->sum_V_sq < 0) // if offset removal cause a negative number,
dt->sum_V_sq = 0; // make it 0 to avoid a nan at sqrt.
float Vrms = V_RATIO * sqrtf((float)dt->sum_V_sq);
dt->sum_I_sq /= (float)dt->count;
dt->sum_I_sq -= (Imean * Imean);
if (dt->sum_I_sq < 0)
dt->sum_I_sq = 0;
float Irms = I_RATIO * sqrtf((float)dt->sum_I_sq);
float mean_P = (dt->sum_P / (float)dt->count) - (Vmean * Imean);
float realPower = V_RATIO * I_RATIO * mean_P;
float apparentPower = Vrms * Irms;
float powerFactor;
if (apparentPower != 0) // prevents 'inf' at division
{
powerFactor = realPower / apparentPower;
}
else
powerFactor = 0;
Ws_acc += ((float)(time_diff / 1000.0)) * realPower; // Watt second accumulator.
float Wh_acc = Ws_acc / 3600.0; // Wh_acc
float frequency = 250.0 / (float)(time_diff / 1000.0); // Hz
int _n = 1;
sprintf(log_buffer, "V%d:%.2f,I%d:%.3f,RP%d:%.1f,AP%d:%.1f,PF%d:%.3f,Wh%d:%.3f,Hz%d:%.2f,C%d:%ld", _n, Vrms, _n, Irms, _n, realPower, _n, apparentPower, _n, powerFactor, _n, Wh_acc, _n, frequency, _n, dt->count);
printf(log_buffer);
printf("\r\n");
}
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}