2020-02-15 01:04:40 +01:00
|
|
|
/* Reflow Oven Controller
|
|
|
|
*
|
|
|
|
* Copyright (C) 2020 Mario Hüttel <mario.huettel@gmx.net>
|
|
|
|
*
|
2020-02-15 22:09:55 +01:00
|
|
|
* This file is part of the Reflow Oven Controller Project.
|
2020-02-15 01:04:40 +01:00
|
|
|
*
|
|
|
|
* The reflow oven controller is free software: you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
|
|
* published by the Free Software Foundation.
|
|
|
|
*
|
2020-02-21 21:22:01 +01:00
|
|
|
* The Reflow Oven Control Firmware is distributed in the hope that it will be useful,
|
2020-02-15 01:04:40 +01:00
|
|
|
* 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
|
2020-02-15 22:09:55 +01:00
|
|
|
* along with the reflow oven controller project.
|
|
|
|
* If not, see <http://www.gnu.org/licenses/>.
|
2020-02-15 01:04:40 +01:00
|
|
|
*/
|
|
|
|
|
2020-02-15 22:09:55 +01:00
|
|
|
|
2020-02-15 01:04:40 +01:00
|
|
|
#include <reflow-controller/calibration.h>
|
|
|
|
#include <reflow-controller/adc-meas.h>
|
2020-02-15 17:53:15 +01:00
|
|
|
#include <stm-periph/uart.h>
|
2020-02-15 01:04:40 +01:00
|
|
|
#include <helper-macros/helper-macros.h>
|
|
|
|
#include <stdlib.h>
|
2020-02-15 17:53:15 +01:00
|
|
|
#include <float.h>
|
2020-07-27 22:15:01 +02:00
|
|
|
#include <reflow-controller/safety/safety-controller.h>
|
2020-02-15 01:04:40 +01:00
|
|
|
|
2020-05-11 21:59:08 +02:00
|
|
|
enum calibration_shell_state {CAL_START = 0, CAL_WAIT_RES1, CAL_MEAS_RES1, CAL_WAIT_RES2, CAL_MEAS_RES2};
|
2020-02-24 18:50:09 +01:00
|
|
|
|
2020-02-15 01:04:40 +01:00
|
|
|
void calibration_calculate(float low_measured, float low_setpoint, float high_measured, float high_setpoint,
|
2020-11-15 23:36:52 +01:00
|
|
|
float *sens_deviation, float *offset)
|
2020-02-15 01:04:40 +01:00
|
|
|
{
|
2020-11-15 23:36:52 +01:00
|
|
|
if (!sens_deviation || !offset)
|
2020-02-15 01:04:40 +01:00
|
|
|
return;
|
|
|
|
|
|
|
|
float delta_y;
|
|
|
|
float delta_x;
|
|
|
|
float sens_corr_mult;
|
|
|
|
|
|
|
|
delta_y = high_measured - low_measured;
|
|
|
|
delta_x = high_setpoint - low_setpoint;
|
|
|
|
|
|
|
|
sens_corr_mult = delta_x / delta_y;
|
|
|
|
*sens_deviation = sens_corr_mult - 1.0f;
|
|
|
|
|
2020-11-15 23:36:52 +01:00
|
|
|
*offset = (high_setpoint * low_measured - low_setpoint * high_measured) / (high_setpoint - low_setpoint);
|
2020-02-15 01:04:40 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2020-05-11 21:59:08 +02:00
|
|
|
float *calibration_acquire_data_start(uint32_t count, volatile int *flag)
|
2020-02-15 01:04:40 +01:00
|
|
|
{
|
|
|
|
int status;
|
|
|
|
float *stream_mem;
|
|
|
|
|
2020-05-11 21:59:08 +02:00
|
|
|
if (!count)
|
|
|
|
return NULL;
|
2020-02-15 01:04:40 +01:00
|
|
|
|
|
|
|
stream_mem = (float *)calloc(count, sizeof(float));
|
|
|
|
if (!stream_mem)
|
2020-05-11 21:59:08 +02:00
|
|
|
return stream_mem;
|
2020-02-15 01:04:40 +01:00
|
|
|
|
2020-05-11 21:59:08 +02:00
|
|
|
*flag = 0;
|
|
|
|
status = adc_pt1000_stream_raw_value_to_memory(stream_mem, count, flag);
|
2020-02-15 01:04:40 +01:00
|
|
|
if (status)
|
2020-05-11 21:59:08 +02:00
|
|
|
goto free_mem;
|
2020-02-15 01:04:40 +01:00
|
|
|
|
|
|
|
|
2020-05-11 21:59:08 +02:00
|
|
|
return stream_mem;
|
|
|
|
free_mem:
|
|
|
|
free(stream_mem);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2020-11-01 20:55:57 +01:00
|
|
|
static float calculate_mean(float *values, uint32_t count)
|
|
|
|
{
|
|
|
|
uint32_t loop_cnt = (count + 7) / 8;
|
|
|
|
uint32_t remainder = count % 8;
|
|
|
|
float sum = 0;
|
|
|
|
|
|
|
|
switch (remainder) {
|
|
|
|
case 0: do { sum += *values++; /* FALLTHRU */
|
|
|
|
case 7: sum += *values++; /* FALLTHRU */
|
|
|
|
case 6: sum += *values++; /* FALLTHRU */
|
|
|
|
case 5: sum += *values++; /* FALLTHRU */
|
|
|
|
case 4: sum += *values++; /* FALLTHRU */
|
|
|
|
case 3: sum += *values++; /* FALLTHRU */
|
|
|
|
case 2: sum += *values++; /* FALLTHRU */
|
|
|
|
case 1: sum += *values++;
|
|
|
|
} while (--loop_cnt > 0);
|
|
|
|
}
|
|
|
|
|
2020-11-02 18:20:17 +01:00
|
|
|
return sum / (float)count;
|
2020-11-01 20:55:57 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
static float calculate_standard_deviation(float *values, uint32_t count, float mean)
|
|
|
|
{
|
|
|
|
uint32_t loop_cnt = (count + 7) / 8;
|
|
|
|
uint32_t remainder = count % 8;
|
|
|
|
float sum = 0;
|
|
|
|
float res;
|
|
|
|
|
|
|
|
switch (remainder) {
|
2020-11-02 18:20:17 +01:00
|
|
|
case 0: do {
|
|
|
|
sum += (*values - mean) * (*values - mean);
|
|
|
|
values++;
|
|
|
|
/* FALLTHRU */
|
|
|
|
case 7: sum += (*values - mean) * (*values - mean);
|
|
|
|
values++;
|
|
|
|
/* FALLTHRU */
|
|
|
|
case 6: sum += (*values - mean) * (*values - mean);
|
|
|
|
values++;
|
|
|
|
/* FALLTHRU */
|
|
|
|
case 5: sum += (*values - mean) * (*values - mean);
|
|
|
|
values++;
|
|
|
|
/* FALLTHRU */
|
|
|
|
case 4: sum += (*values - mean) * (*values - mean);
|
|
|
|
values++;
|
|
|
|
/* FALLTHRU */
|
|
|
|
case 3: sum += (*values - mean) * (*values - mean);
|
|
|
|
values++;
|
|
|
|
/* FALLTHRU */
|
|
|
|
case 2: sum += (*values - mean) * (*values - mean);
|
|
|
|
values++;
|
|
|
|
/* FALLTHRU */
|
|
|
|
case 1: sum += (*values - mean) * (*values - mean);
|
|
|
|
values++;
|
|
|
|
/* FALLTHRU */
|
2020-11-01 20:55:57 +01:00
|
|
|
} while (--loop_cnt > 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
sum /= (float)count;
|
2020-11-02 18:20:17 +01:00
|
|
|
/* Compute the square root using the FPU.
|
2020-11-01 20:55:57 +01:00
|
|
|
* The constraint 't' tells GCC to use a floating point register
|
|
|
|
*/
|
|
|
|
__asm__ __volatile__("vsqrt.f32 %0, %1" : "=t"(res) : "t"(sum));
|
|
|
|
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
2020-11-01 20:58:32 +01:00
|
|
|
static int calibration_poll_data_acquisition(float *mem_array, uint32_t count, volatile int *flag, float *mu, float *std_dev)
|
2020-05-11 21:59:08 +02:00
|
|
|
{
|
|
|
|
int ret_val = 0;
|
|
|
|
|
2020-11-01 20:58:32 +01:00
|
|
|
if (!flag || !mem_array || !mu || !std_dev)
|
2020-05-11 21:59:08 +02:00
|
|
|
return -1000;
|
|
|
|
|
|
|
|
if (*flag == 0) {
|
|
|
|
/* Continue polling */
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (*flag != 1) {
|
2020-02-15 01:04:40 +01:00
|
|
|
/* Error */
|
2020-02-15 17:53:15 +01:00
|
|
|
ret_val = -1;
|
|
|
|
goto ret_free_mem;
|
2020-02-15 01:04:40 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Convert the stream memory to Ohm readings */
|
2020-05-11 21:59:08 +02:00
|
|
|
adc_pt1000_convert_raw_value_array_to_resistance(NULL, mem_array, count);
|
2020-02-15 01:04:40 +01:00
|
|
|
|
2020-11-01 20:55:57 +01:00
|
|
|
/* Do not compute std-deviation. Too imprecise */
|
|
|
|
*mu = calculate_mean(mem_array, count);
|
2020-11-01 20:58:32 +01:00
|
|
|
*std_dev = calculate_standard_deviation(mem_array, count, *mu);
|
2020-02-15 17:53:15 +01:00
|
|
|
|
|
|
|
ret_free_mem:
|
2020-05-11 21:59:08 +02:00
|
|
|
free(mem_array);
|
2020-02-15 17:53:15 +01:00
|
|
|
return ret_val;
|
|
|
|
}
|
|
|
|
|
2020-05-11 21:59:08 +02:00
|
|
|
shellmatta_retCode_t calibration_sequence_shell_cmd(shellmatta_handle_t shell, const char *arg, uint32_t len)
|
2020-02-15 17:53:15 +01:00
|
|
|
{
|
2020-05-11 21:59:08 +02:00
|
|
|
(void)arg;
|
|
|
|
(void)len;
|
2020-07-27 22:15:01 +02:00
|
|
|
bool error_occured;
|
|
|
|
const enum safety_flag meas_adc_err_mask = ERR_FLAG_MEAS_ADC_OFF | ERR_FLAG_MEAS_ADC_WATCHDOG;
|
2020-02-15 17:53:15 +01:00
|
|
|
|
2020-05-11 21:59:08 +02:00
|
|
|
/* This stores the current state of the calibration process */
|
|
|
|
static enum calibration_shell_state cal_state = CAL_START;
|
|
|
|
shellmatta_retCode_t ret_val = SHELLMATTA_BUSY;
|
|
|
|
uint32_t i;
|
|
|
|
int res;
|
2020-02-15 17:53:15 +01:00
|
|
|
|
2020-05-11 21:59:08 +02:00
|
|
|
static float mu = 0.0f, mu2 = 0.0f, dev = 0.0f, dev2 = 0.0f;
|
|
|
|
float sens_dev, offset;
|
|
|
|
static float *data_buffer = NULL;
|
|
|
|
static volatile int flag;
|
|
|
|
char *stdin_data;
|
|
|
|
uint32_t stdin_len;
|
2020-11-17 00:53:21 +01:00
|
|
|
bool cal_active;
|
2020-05-11 21:59:08 +02:00
|
|
|
|
|
|
|
switch (cal_state) {
|
|
|
|
case CAL_START:
|
|
|
|
/* Clear errors of PT1000 reading */
|
2020-07-27 22:15:01 +02:00
|
|
|
safety_controller_ack_flag(ERR_FLAG_MEAS_ADC_WATCHDOG);
|
2020-11-17 00:53:21 +01:00
|
|
|
adc_pt1000_get_resistance_calibration(&offset, &sens_dev, &cal_active);
|
|
|
|
if (cal_active) {
|
|
|
|
shellmatta_printf(shell, "Already calibrated.\r\n\tOffset: %f\r\n\tSens: %f\r\n", offset, sens_dev);
|
|
|
|
shellmatta_printf(shell, "Press CTRL-C to abort new calibration.\r\n");
|
|
|
|
}
|
2020-05-11 21:59:08 +02:00
|
|
|
shellmatta_printf(shell, "Starting calibration: Insert 1000 Ohm calibration resistor and press ENTER\r\n");
|
|
|
|
cal_state = CAL_WAIT_RES1;
|
|
|
|
ret_val = SHELLMATTA_CONTINUE;
|
|
|
|
break;
|
|
|
|
case CAL_WAIT_RES1:
|
|
|
|
cal_state = CAL_WAIT_RES1;
|
|
|
|
ret_val = SHELLMATTA_CONTINUE;
|
|
|
|
shellmatta_read(shell, &stdin_data, &stdin_len);
|
|
|
|
if (stdin_len > 0) {
|
|
|
|
for (i = 0; i < stdin_len; i++) {
|
|
|
|
if (stdin_data[i] == '\r') {
|
|
|
|
cal_state = CAL_MEAS_RES1;
|
|
|
|
ret_val = SHELLMATTA_BUSY;
|
|
|
|
shellmatta_printf(shell, "Measurement...\r\n");
|
2020-07-27 22:15:01 +02:00
|
|
|
safety_controller_ack_flag(ERR_FLAG_MEAS_ADC_WATCHDOG);
|
2020-05-11 21:59:08 +02:00
|
|
|
data_buffer = calibration_acquire_data_start(512UL, &flag);
|
|
|
|
break;
|
|
|
|
} else if (stdin_data[i] == '\x03') {
|
|
|
|
cal_state = CAL_START;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-02-15 17:53:15 +01:00
|
|
|
|
2020-05-11 21:59:08 +02:00
|
|
|
break;
|
|
|
|
case CAL_MEAS_RES1:
|
|
|
|
if (!data_buffer) {
|
|
|
|
shellmatta_printf(shell, "Data acquisition failed!\r\n");
|
|
|
|
ret_val = SHELLMATTA_OK;
|
|
|
|
cal_state = CAL_START;
|
|
|
|
break;
|
|
|
|
}
|
2020-02-15 17:53:15 +01:00
|
|
|
|
2020-05-11 21:59:08 +02:00
|
|
|
res = calibration_poll_data_acquisition(data_buffer, 512UL, &flag, &mu, &dev);
|
|
|
|
/* Stay in this state until the measurements are finished */
|
|
|
|
if (res == 1) {
|
|
|
|
ret_val = SHELLMATTA_BUSY;
|
|
|
|
cal_state = CAL_MEAS_RES1;
|
|
|
|
} else if (res == 0) {
|
2020-11-01 20:58:32 +01:00
|
|
|
shellmatta_printf(shell, "R=%.2f, Std-Dev: %.2f\r\n", mu, dev);
|
2020-07-27 22:15:01 +02:00
|
|
|
error_occured = safety_controller_get_flags_by_mask(meas_adc_err_mask);
|
|
|
|
if (error_occured) {
|
|
|
|
shellmatta_printf(shell, "Error in resistance measurement");
|
2020-05-11 21:59:08 +02:00
|
|
|
ret_val = SHELLMATTA_OK;
|
|
|
|
cal_state = CAL_START;
|
|
|
|
} else {
|
|
|
|
ret_val = SHELLMATTA_CONTINUE;
|
|
|
|
shellmatta_printf(shell, "Insert 2000 Ohm calibration resistor and press ENTER\r\n");
|
|
|
|
cal_state = CAL_WAIT_RES2;
|
|
|
|
}
|
|
|
|
} else {
|
2020-07-27 22:15:01 +02:00
|
|
|
shellmatta_printf(shell, "Error in resistance measurement");
|
2020-05-11 21:59:08 +02:00
|
|
|
ret_val = SHELLMATTA_OK;
|
|
|
|
cal_state = CAL_START;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case CAL_WAIT_RES2:
|
|
|
|
cal_state = CAL_WAIT_RES2;
|
|
|
|
ret_val = SHELLMATTA_CONTINUE;
|
|
|
|
shellmatta_read(shell, &stdin_data, &stdin_len);
|
|
|
|
if (stdin_len > 0) {
|
|
|
|
for (i = 0; i < stdin_len; i++) {
|
|
|
|
if (stdin_data[i] == '\r') {
|
|
|
|
cal_state = CAL_MEAS_RES2;
|
|
|
|
ret_val = SHELLMATTA_BUSY;
|
|
|
|
shellmatta_printf(shell, "Measurement...\r\n");
|
2020-07-27 22:15:01 +02:00
|
|
|
safety_controller_ack_flag(ERR_FLAG_MEAS_ADC_WATCHDOG);
|
2020-05-11 21:59:08 +02:00
|
|
|
data_buffer = calibration_acquire_data_start(512UL, &flag);
|
|
|
|
break;
|
|
|
|
} else if (stdin_data[i] == '\x03') {
|
|
|
|
cal_state = CAL_START;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-02-15 17:53:15 +01:00
|
|
|
|
2020-05-11 21:59:08 +02:00
|
|
|
break;
|
|
|
|
case CAL_MEAS_RES2:
|
|
|
|
if (!data_buffer) {
|
|
|
|
shellmatta_printf(shell, "Data acquisition failed!\r\n");
|
|
|
|
ret_val = SHELLMATTA_OK;
|
|
|
|
cal_state = CAL_START;
|
|
|
|
break;
|
|
|
|
}
|
2020-02-15 17:53:15 +01:00
|
|
|
|
2020-05-11 21:59:08 +02:00
|
|
|
res = calibration_poll_data_acquisition(data_buffer, 512UL, &flag, &mu2, &dev2);
|
|
|
|
/* Stay in this state until the measurements are finished */
|
|
|
|
if (res == 1) {
|
|
|
|
ret_val = SHELLMATTA_BUSY;
|
|
|
|
cal_state = CAL_MEAS_RES2;
|
|
|
|
} else if (res == 0) {
|
2020-11-01 20:58:32 +01:00
|
|
|
shellmatta_printf(shell, "R=%.2f, Std-Dev: %.2f\r\n", mu2, dev2);
|
2020-07-27 22:15:01 +02:00
|
|
|
error_occured = safety_controller_get_flags_by_mask(meas_adc_err_mask);
|
|
|
|
if (error_occured) {
|
|
|
|
shellmatta_printf(shell, "Error in resistance measurement");
|
2020-05-11 21:59:08 +02:00
|
|
|
ret_val = SHELLMATTA_OK;
|
|
|
|
cal_state = CAL_START;
|
|
|
|
} else {
|
|
|
|
ret_val = SHELLMATTA_OK;
|
|
|
|
cal_state = CAL_START;
|
|
|
|
|
2020-11-01 20:58:32 +01:00
|
|
|
if (dev > CALIBRATION_MAX_NOISE_OHM ||
|
|
|
|
dev2 > CALIBRATION_MAX_NOISE_OHM) {
|
2020-05-11 21:59:08 +02:00
|
|
|
shellmatta_printf(shell, "Calibration failed! Too much noise. Check your hardware.\r\n");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
shellmatta_printf(shell, "Calibartion finished successfully!\r\n");
|
|
|
|
/* Calculate calibration */
|
|
|
|
calibration_calculate(mu, 1000.0f, mu2, 2000.0f, &sens_dev, &offset);
|
|
|
|
|
|
|
|
shellmatta_printf(shell, "\r\n\tSENS_DEVIATION: %.4f\r\n\tOFFSET_CORR: %.2f\r\n", sens_dev, offset);
|
|
|
|
adc_pt1000_set_resistance_calibration(offset, sens_dev, true);
|
|
|
|
}
|
|
|
|
} else {
|
2020-07-27 22:15:01 +02:00
|
|
|
shellmatta_printf(shell, "Error in resistance measurement");
|
2020-05-11 21:59:08 +02:00
|
|
|
ret_val = SHELLMATTA_OK;
|
|
|
|
cal_state = CAL_START;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
shellmatta_printf(shell, "Undefined state reached in calibration. Aborting\r\n");
|
|
|
|
cal_state = CAL_START;
|
|
|
|
ret_val = SHELLMATTA_OK;
|
|
|
|
break;
|
2020-02-15 17:53:15 +01:00
|
|
|
}
|
|
|
|
|
2020-05-11 21:59:08 +02:00
|
|
|
return ret_val;
|
2020-02-15 01:04:40 +01:00
|
|
|
}
|