#include "interface.h"
#include "time.h"
#include "uSD.h"
+#include "data.h"
/***************************************************************************** */
}
+/***************************************************************************** */
/* Communication with slave modules over UART1 */
+
+/* Rx interrupt handler */
void comm_rx(uint8_t c)
{
}
+void comm_tx(struct scialys_data* data)
+{
+ serial_write(UART1, (char*)data, sizeof(struct scialys_data));
+}
#define COMM_H
+/***************************************************************************** */
/* Rx interrupt handler for system configuration over USB */
void config_rx(uint8_t c);
+/***************************************************************************** */
/* Communication with slave modules over UART1 */
+
+
+/* Rx interrupt handler */
void comm_rx(uint8_t c);
+void comm_tx(struct scialys_data* data);
+
+
#endif /* COMM_H */
void update_max_intensity(void)
{
max_intensity = sc_conf.grid_power_limit * 1000 * 1000 / 230;
+ uprintf(UART0, "Max intensity is %dmA\n", max_intensity);
}
/* Update sunny_days_prod_value (Watt to mA) */
void update_sunny_days_prod_value(void)
sc_conf.conf_version = CONFIG_VERSION;
sc_conf.config_ok = CONFIG_OK;
}
-
- update_sunny_days_prod_value();
- update_max_intensity();
}
/* Read main configuration - or set to default one */
/* Check that config read from flash is OK */
if (sc_conf.config_ok == CONFIG_OK) {
uprintf(UART0, "Internal config read OK\n");
- return;
+ } else {
+ /* Config is either blank or not up to date, update missing parts with defaults */
+ scialys_check_config();
}
- /* Config is either blank or not up to date, update missing parts with defaults */
- scialys_check_config();
+
+ update_sunny_days_prod_value();
+ update_max_intensity();
}
--- /dev/null
+/****************************************************************************
+ * apps/scialys/v10/data.c
+ *
+ * Copyright 2016-2023 Nathael Pajani <nathael.pajani@ed3l.fr>
+ *
+ *
+ * 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/>.
+ *
+ *************************************************************************** */
+
+#include "stdint.h"
+#include "data.h"
+
+void scialys_add_header(struct scialys_data* data)
+{
+ uint8_t sum = 0;
+ uint8_t* ptr = (uint8_t*)data + DATA_HEADER_LEN;
+
+ /* Fill fixed header fields */
+ data->start = DATA_START;
+ data->version = DATA_VERSION;
+
+ /* Compute data checksum */
+ while (ptr < ((uint8_t *)data + sizeof(struct scialys_data))) {
+ sum += *(ptr++);
+ }
+ data->data_cksum = sum;
+
+ /* Compute header checksum */
+ sum = (data->start + data->version + data->data_cksum) & 0xFF;
+ data->cksum = (256 - sum) & 0xFF;
+}
+
+
--- /dev/null
+/****************************************************************************
+ * apps/scialys/v10/data.h
+ *
+ * Copyright 2016-2023 Nathael Pajani <nathael.pajani@ed3l.fr>
+ *
+ *
+ * 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/>.
+ *
+ *************************************************************************** */
+
+#ifndef DATA_H
+#define DATA_H
+
+
+/* Scialys data, as sent on UART1 or stored on SD card.
+ *
+ * Header fields :
+ * The start byte is dedicated to UART communication and is always '#"
+ * Note that this byte may be repeated within the data packet, so the rest of the header
+ * should be checked before handling a packet
+ * The first checksum byte is so that the sum of all header bytes is zero
+ * The version byte indicates the version of this structure.
+ * The length is fixed so knowing the version gives the length.
+ * The data checksum is equal to the sum of all the data bytes
+ *
+ * Data fields :
+ * ...
+ * The flags can be expanded to a "struct flags" (see below) by shifting one bit to each byte.
+ *
+ */
+
+#define DATA_START '#'
+#define DATA_VERSION 0x01
+#define DATA_HEADER_LEN 4
+
+struct scialys_data {
+ /* Header */
+ uint8_t start;
+ uint8_t cksum;
+ uint8_t version;
+ uint8_t data_cksum;
+ /* Data */
+ uint32_t solar_prod_value;
+ uint32_t home_conso_value;
+ int water_centi_degrees;
+ int deci_degrees_power;
+ int deci_degrees_disp;
+ uint16_t load_power_lowest;
+ uint16_t load_power_highest;
+ uint8_t command_val;
+ uint8_t act_cmd;
+ uint8_t mode;
+ uint8_t flags;
+} __attribute__ ((__packed__));
+
+/*
+struct flags {
+ uint8_t fan_on;
+ uint8_t force_fan;
+ uint8_t error_shutdown;
+ uint8_t temp_shutdown;
+ uint8_t overvoltage;
+ uint8_t external_disable;
+ uint8_t forced_heater_mode;
+ uint8_t manual_activation_request;
+};
+*/
+
+void scialys_add_header(struct scialys_data* data);
+
+
+#endif /* DATA_H */
+
#include "interface.h"
#include "time.h"
#include "sensors.h"
+#include "data.h"
+#include "comm.h"
#include "uSD.h"
uint8_t manual_forced_heater = 0; /* Flag only */
int manual_activation_request = 0; /* Flag and counter */
int8_t internal_temp_error_shutdown = 0; /* Flag and error code */
-#define EXTERNAL_DISABLE_FORCE 0 /* Input is pulled low when external disable is ON */
uint8_t external_disable = 0; /* Flag only */
uint8_t mosfet_temp_shutdown = 0; /* Flag only */
uint8_t overvoltage = 0; /* Flag and counter. Used to create a delay when overvoltage is detected,
set to OVERVOLTAGE_PROTECTION_CYCLES and decreases to 0 */
enum modes {
- heat = 'C', /* Normal heating */
- ext_disable = 'E', /* Forced heating disabled by external input */
- forced = 'F', /* Auto Forced heating */
- manual = 'M', /* Manual Forced heating */
- delayed_heat_prod = 'P', /* Pause */
- overprod = 'O', /* Over production, try to start other loads */
- temp_OK = 'T', /* Max temperature reached */
+ mode_heat = 'C', /* Normal heating */
+ mode_ext_disable = 'E', /* Forced heating disabled by external input */
+ mode_forced = 'F', /* Auto Forced heating */
+ mode_manual = 'M', /* Manual Forced heating */
+ mode_delayed_heat_prod = 'P', /* Pause */
+ mode_overprod = 'O', /* Over production, try to start other loads */
+ mode_temp_OK = 'T', /* Max temperature reached */
+ mode_overvoltage = 'V', /* Overvoltage detected */
+ mode_overtemp = 'H', /* Mosfet over-temperature detected */
};
/* Water temperature */
int8_t old_cmd = 0; /* Used to store cmd value when entering over-voltage protection mode */
#define FAN_COUNTER_START_VAL (15 * 1000)
static uint16_t fan_on = 0;
-uint8_t force_fan = 0; /* Request to force fan ON from test menu */
+uint8_t force_fan = 0;
+extern uint8_t test_force_fan; /* Request to force fan ON from test menu */
#define PROTECTION_CMD_VAL 100
static uint8_t linky_disable = 0;
old_cmd = -1;
}
+ /* Reset force_fan to current test value */
+ force_fan = test_force_fan;
+
+ /* Mosfet temperature limit reached, turn ON all mosfets to prevent internal damage of board */
+ if (mosfet_temp_shutdown == 1) {
+ cmd = PROTECTION_CMD_VAL;
+ force_fan = 1;
+ goto cmd_update_end;
+ }
+
/* Water max temperature protection with hysteresys */
if ((water_centi_degrees > ABSOLUTE_MAX_WATER_TEMP) ||
(water_centi_degrees > sc_conf.conf_max_temp) ||
cmd = 0;
max_temp_hysteresys = (ABSOLUTE_MAX_WATER_TEMP - 150); /* 1.5 °C */
if (max_temp_hysteresys > sc_conf.conf_max_temp) {
- max_temp_hysteresys = (sc_conf.conf_max_temp - 150); /* still 1.5°C */
+ max_temp_hysteresys = (sc_conf.conf_max_temp - 150); /* 1.5 °C */
}
goto cmd_update_end;
} else if (water_centi_degrees < max_temp_hysteresys) {
max_temp_hysteresys = ABSOLUTE_MAX_WATER_TEMP;
}
- /* Unable to read internal temperature : turn off heating */
- if ((mosfet_temp_shutdown == 1) || (internal_temp_error_shutdown < 0)) {
+ /* Unable to read internal temperature or temperature above max internal temp : turn off heating */
+ if (internal_temp_error_shutdown < 0) {
cmd = 0;
force_fan = 1;
goto cmd_update_end;
}
}
}
- /* Linky temporary update */
- linky_disable = 0;
- if ((cmd > 48) && (cmd < 100)) {
- if (water_centi_degrees > (50 * 100)) {
- if (delta > 6000) {
- linky_disable = 1;
- cmd = 100;
- } else {
- linky_disable = 2;
- cmd = 48;
- }
- } else {
- linky_disable = 4;
- cmd = 100;
- }
- }
-
cmd_update_limited_end:
command_val = cmd;
- /* Fixme : remove this when implementing target temperature forced heating */
/* Limit external power used when in force mode ! */
if (delta < -max_intensity) {
cmd = 0;
manual_activation_request++;
}
}
+ /* Test for Linky - FIXME */
+#if 0
+ linky_disable = 0;
+ if ((cmd > 48) && (cmd < 100)) {
+ if (water_centi_degrees > (50 * 100)) {
+ if (delta > 6000) {
+ linky_disable = 2;
+ cmd = 100;
+ } else {
+ linky_disable = 4;
+ cmd = 48;
+ }
+ } else {
+ linky_disable = 8;
+ cmd = 100;
+ }
+ }
+#endif
cmd_update_end:
/* Update FAN command (start or stop FAN) */
{
gpio_clear(ac_ctrl); /* Turn mosfets ON */
timer_stop(LPC_TIMER_32B1); /* Stop mosfets timer */
- set_ctrl_duty_cycle(100); /* Update "actual command" */
+ set_ctrl_duty_cycle(PROTECTION_CMD_VAL); /* Update "actual command" */
overvoltage = OVERVOLTAGE_PROTECTION_CYCLES;
gpio_set(fan_ctrl); /* Turn on FAN immediatly */
old_cmd = command_val; /* Save current command value (to be restored after overvoltage protection */
/* Update current working mode depending on environnement */
-volatile char mode = heat; /* Debug info */
+volatile char mode = mode_heat; /* Debug info */
volatile char* msg = NULL;
void mode_update(void)
{
/* Default mode : try to heat the water tank */
- mode = heat;
+ mode = mode_heat;
/* Need to enter Forced heating mode ? */
if ((water_centi_degrees < sc_conf.enter_forced_mode_temp) && (sc_conf.auto_force_type != FORCE_TYPE_OFF)) {
forced_heater_mode = 1;
}
status_led(red_on);
- mode = forced;
+ mode = mode_forced;
} else if (forced_heater_mode == 1) {
int exit_forced = 0;
/* End of forced heating ? (target reached) ? */
status_led(red_off);
msg = "Water temp OK, forced mode exit\n";
forced_heater_mode = 0;
- mode = temp_OK;
+ mode = mode_temp_OK;
+ } else {
+ mode = mode_forced;
}
}
* 'sunny_days_prod_value' is computed from 'sc_conf.source_has_power_ok'
*/
if ((solar_prod_value > sunny_days_prod_value) && (forced_heater_mode == 1)) {
- mode = delayed_heat_prod;
+ mode = mode_delayed_heat_prod;
forced_heater_mode = 0;
}
/* Do not force heating if disabled by external command */
- external_disable = (gpio_read(ext_disable_in_pin) ? 1 : 0);
- if ((external_disable == EXTERNAL_DISABLE_FORCE) && (forced_heater_mode != 0)) {
+ external_disable = (gpio_read(ext_disable_in_pin) ? 0 : 1); /* Invert : input is pulled low when external disable is ON */
+ if ((external_disable == 1) && (forced_heater_mode != 0)) {
forced_heater_mode = 0;
- mode = ext_disable;
+ mode = mode_ext_disable;
msg = "Forced mode disabled by external input\n";
}
/* Get Over-Temperature information */
- mosfet_temp_shutdown = (gpio_read(overtemperature_pin) ? 0 : 1);
- /* Update Over-Voltage information */
+ mosfet_temp_shutdown = (gpio_read(overtemperature_pin) ? 0 : 1); /* Invert : Temp switch is NC type */
+ if (mosfet_temp_shutdown != 0) {
+ mode = mode_overtemp;
+ }
+
+ /* Update Over-Voltage information - Only used to exit over-voltage protection mode */
if (overvoltage > 0) {
- uint8_t ov_tmp = (gpio_read(overvoltage_pin) ? 0 : 1);
+ uint8_t ov_tmp = (gpio_read(overvoltage_pin) ? 1 : 0);
if (ov_tmp == 0) {
overvoltage--;
}
+ mode = mode_overvoltage;
}
if (sc_conf.never_force == 1) {
/* Did the user request a forced heating ? */
if (manual_activation_request != 0) {
forced_heater_mode = 1;
- mode = manual;
+ mode = mode_manual;
if (manual_activation_request == -1) {
msg = "Entering manual forced mode\n";
manual_activation_request = sc_conf.manual_activation_duration * T_MULT;
/* Command at 100% and still more production than energy used ? */
if ((command_val == 100) && (solar_prod_value > home_conso_value)) {
- mode = overprod;
+ mode = mode_overprod;
}
}
void store_data(uint32_t cur_tick)
{
static uint32_t last_tick_store = 0;
- static struct sd_data_blob data;
+ static struct scialys_data data;
static uint8_t nb_val = 0;
if (nb_val == 0) {
- memset(&data, 0, sizeof(struct sd_data_blob));
+ memset(&data, 0, sizeof(struct scialys_data));
}
/* simple tick wrapping handling */
if ((cur_tick < last_tick_store) || ((~0 - last_tick_store) < (NB_SEC_STORE * 1000))) {
data.flags |= (forced_heater_mode << 6) | (manual_activation_request << 7);
/* Add current value to the storage value */
- /* FIXME */
+ data.solar_prod_value += solar_prod_value;
+ data.home_conso_value += home_conso_value;
+ data.water_centi_degrees += water_centi_degrees;
+ data.deci_degrees_power += deci_degrees_power;
+ data.deci_degrees_disp += deci_degrees_disp;
+ data.load_power_lowest += load_power_lowest;
+ data.load_power_highest += load_power_highest;
nb_val++;
if (cur_tick > (last_tick_store + (NB_SEC_STORE * 1000))) {
/* Divide by the number of values */
- /* FIXME */
+ data.solar_prod_value = data.solar_prod_value / nb_val;
+ data.home_conso_value = data.home_conso_value / nb_val;
+ data.water_centi_degrees = data.water_centi_degrees / nb_val;
+ data.deci_degrees_power = data.deci_degrees_power / nb_val;
+ data.deci_degrees_disp = data.deci_degrees_disp / nb_val;
+ data.load_power_lowest = data.load_power_lowest / nb_val;
+ data.load_power_highest = data.load_power_highest / nb_val;
+ /* Add "fixed ones */
+ data.command_val = command_val;
+ data.act_cmd = act_cmd;
+ data.mode = mode;
+ /* Add header */
+ scialys_add_header(&data);
+ /* Store on uSD */
scialys_uSD_append_data(UART0, &data);
- uprintf(UART0, "Saved 5s data\n");
+ uprintf(UART0, "Saved %ds data\n", NB_SEC_STORE);
nb_val = 0;
last_tick_store = cur_tick;
}
}
+void slave_send_data(void)
+{
+ static struct scialys_data data;
+
+ data.solar_prod_value = solar_prod_value;
+ data.home_conso_value = home_conso_value;
+ data.water_centi_degrees = water_centi_degrees;
+ data.deci_degrees_power = deci_degrees_power;
+ data.deci_degrees_disp = deci_degrees_disp;
+ data.load_power_lowest = load_power_lowest;
+ data.load_power_highest = load_power_highest;
+ data.command_val = command_val;
+ data.act_cmd = act_cmd;
+ data.mode = mode;
+
+ data.flags = (fan_on ? 1 : 0) | (force_fan << 1);
+ data.flags |= (((internal_temp_error_shutdown < 0) ? 1 : 0) << 2);
+ data.flags |= (mosfet_temp_shutdown << 3) | (overvoltage << 4);
+ data.flags |= (external_disable << 5);
+ data.flags |= (forced_heater_mode << 6) | (manual_activation_request << 7);
+
+ scialys_add_header(&data);
+
+ comm_tx(&data);
+}
+
/***************************************************************************** */
int main(void)
{
/* Store data on uSD once every N seconds. */
store_data(cur_tick);
+ /* Send data to slave modules */
+ slave_send_data();
+
/* Data Log */
if (1) {
external_disable |= linky_disable;
static uint8_t conf_cur_menu = SAVE_CONFIG;
static uint8_t conf_cur_entry = SAVE_CONFIG;
-extern uint8_t force_fan;
+uint8_t test_force_fan;
static const char* force_types_str[] = {
[FORCE_TYPE_OFF] = "Off",
int i = 0;
uint8_t states[3];
- states[0] = force_fan; /* TEST_FAN */
+ states[0] = test_force_fan; /* TEST_FAN */
for (i = 0; i < CONF_NB_MENU; i++) {
if (i != conf_cur_entry) {
display_line(1, 1, conf_titles[conf_cur_menu]);
switch (conf_cur_menu) {
case TEST_FAN:
- force_fan = !force_fan;
- dprintf(4, 1, "Fan turned %s", ((force_fan == 0) ? "OFF" : "ON"));
+ test_force_fan = !test_force_fan;
+ dprintf(4, 1, "Fan turned %s", ((test_force_fan == 0) ? "OFF" : "ON"));
sub_menu_level = 1;
break;
case SAVE_CONFIG: {
#! /usr/bin/perl -w
+# This file is used to generate the "power_delay" table as a C source code file.
+# This power delay table is used to select the right delay for mosfets
+# activation depending on the percentage of energy required (represented by the
+# area under the sin wave curve).
+
+# This script requires one or two arguments : "clock cycles" and "output file"
+# - When used with only one argument, the argument is the number of clock cycles
+# which produces a 10 kHz tick (100 Hz (half sin wave of 50Hz)cut in 100 chunks)
+# This valus is 4800 for the LPC1224 running at 48MHz clock.
+# It will then print the table with each line preceded by the corresponding
+# timing on the standard output.
+# - When used with a second argument, it must be the file name for the C source
+# code produced by the script. It should be "power_delay.c" unless you want to
+# generate a different file for tests (in which case you should avoid the ".c"
+# as the Makefile will then find multiple definitions of the table)
+
use strict;
use warnings;
/* Buffer to read / write uSD card */
uint8_t mmc_data[MMC_BUF_SIZE];
-#define SCIALYS_MAGIC "Scialys data bloc"
+#define SCIALYS_MAGIC "Scialys data"
/* Read 1 block of 512 bytes */
int scialys_uSD_read(uint32_t block_num)
memcpy(mmc_data + idx, (uint8_t*)&now, sizeof(struct rtc_time));
idx += sizeof(struct rtc_time);
+ /* Align on 32 bytes boundary */
+ if (idx & 0x1F) {
+ idx = ( ((idx >> 5) + 1) << 5 );
+ }
return idx;
}
}
/* Append data to temporary data buffer, and possibly flush data to uSD when buffer is full */
-int scialys_uSD_append_data(int uart, struct sd_data_blob* data)
+int scialys_uSD_append_data(int uart, struct scialys_data* data)
{
static uint16_t index = 0;
}
/* Store data to buffer */
- memcpy(mmc_data + index, data, sizeof(struct sd_data_blob));
- index += sizeof(struct sd_data_blob);
+ memcpy(mmc_data + index, data, sizeof(struct scialys_data));
+ index += sizeof(struct scialys_data);
/* Flush buffer to uSD ?
* Do it when there's not enough room in buffer for another chunk of data. */
- if ((index + sizeof(struct sd_data_blob)) >= MMC_BUF_SIZE) {
+ if ((index + sizeof(struct scialys_data)) >= MMC_BUF_SIZE) {
int ret = scialys_uSD_write(last_block.block_num + 1);
if (ret != 0) {
uprintf(uart, "Write to uSD returned %d\n", ret);
#define USD_H
#include "extdrv/sdmmc.h"
+#include "data.h"
-/* A 512 bytes block stores 17 data blobs of 28 bytes with a header of up to 36 bytes */
-/* Header is :
- * 18 bytes : SCIALYS_MAGIC : "Scialys data bloc"
+/* A 512 bytes block stores 15 data blobs of 32 bytes with a header of up to 32 bytes */
+/* Header is 31 bytes :
+ * 13 bytes : SCIALYS_MAGIC : "Scialys data"
* 6 bytes : MODULE_VERSION_STR : "v0.00"
* 4 bytes: SOFT_VERSION_STR : "T" or "P" + COMPILE_VERSION : "XX"
* 8 bytes : date
*/
#define MMC_BUF_SIZE 512
-struct sd_data_blob {
- uint32_t solar_prod_value;
- uint32_t home_conso_value;
- int water_centi_degrees;
- int deci_degrees_power;
- int deci_degrees_disp;
- uint16_t load_power_lowest;
- uint16_t load_power_highest;
- uint8_t command_val;
- uint8_t act_cmd;
- uint8_t mode;
- uint8_t flags;
-} __attribute__ ((__packed__));
-
-/*
-struct flags {
- uint8_t fan_on;
- uint8_t force_fan;
- uint8_t error_shutdown;
- uint8_t temp_shutdown;
- uint8_t overvoltage;
- uint8_t external_disable;
- uint8_t forced_heater_mode;
- uint8_t manual_activation_request;
-};
-*/
extern struct sdmmc_card micro_sd;
extern uint8_t mmc_data[MMC_BUF_SIZE];
int scialys_uSD_write(uint32_t block_num);
/* Append data to temporary data buffer, and possibly flush data to uSD when buffer is full */
-int scialys_uSD_append_data(int uart, struct sd_data_blob* data);
+int scialys_uSD_append_data(int uart, struct scialys_data* data);
/* Check that the first part of a data buffer is a valid data block */
int scialys_uSD_data_buffer_is_valid(void);
-#define MODULE_VERSION_STR "v0.10.1"
+#define MODULE_VERSION_STR "v0.10.2"
#define SOFT_VERSION_STR "T"
-#define COMPILE_VERSION "67"
+#define COMPILE_VERSION "75"