--- /dev/null
+dump
+logs
--- /dev/null
+# Makefile for apps
+
+MODULE = $(shell basename $(shell cd .. && pwd && cd -))
+NAME = $(shell basename $(CURDIR))
+
+# Add this to your ~/.vimrc in order to get proper function of :make in vim :
+# let $COMPILE_FROM_IDE = 1
+ifeq ($(strip $(COMPILE_FROM_IDE)),)
+ PRINT_DIRECTORY = --no-print-directory
+else
+ PRINT_DIRECTORY =
+ LANG = C
+endif
+
+.PHONY: $(NAME).bin
+$(NAME).bin:
+ @make -C ../../.. ${PRINT_DIRECTORY} NAME=$(NAME) MODULE=$(MODULE) apps/$(MODULE)/$(NAME)/$@
+
+clean mrproper:
+ @make -C ../../.. ${PRINT_DIRECTORY} $@
+
+appclean:
+ rm ../../../objs/apps/$(MODULE)/$(NAME)/* $(NAME).bin $(NAME).elf
+
+temp:
+ @$(shell ../../../update_version.sh && touch interface.c)
--- /dev/null
+/****************************************************************************
+ * apps/scialys/test_elec/comm.c
+ *
+ * Copyright 2016-2022 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 "core/system.h"
+#include "core/systick.h"
+#include "core/pio.h"
+
+#include "drivers/serial.h"
+#include "drivers/gpio.h"
+
+#include "extdrv/status_led.h"
+
+#include "core/user_information_block.h"
+#include "core/iap.h"
+
+#include "config.h"
+#include "interface.h"
+#include "time.h"
+#include "uSD.h"
+
+
+/***************************************************************************** */
+/* Rx interrupt handler for system configuration over USB */
+void config_rx(uint8_t c)
+{
+ /* FAN control */
+ if (c == 'f') {
+ gpio_set(fan_ctrl);
+ } else {
+ gpio_clear(fan_ctrl);
+ }
+}
+
+
+/* Communication with slave modules over UART1 */
+void comm_rx(uint8_t c)
+{
+}
+
+
+
--- /dev/null
+/****************************************************************************
+ * apps/scialys/test_elec/comm.h
+ *
+ * Copyright 2016-2022 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 COMM_H
+#define COMM_H
+
+
+/* Rx interrupt handler for system configuration over USB */
+void config_rx(uint8_t c);
+
+
+/* Communication with slave modules over UART1 */
+void comm_rx(uint8_t c);
+
+
+#endif /* COMM_H */
+
--- /dev/null
+/****************************************************************************
+ * apps/scialys/test_elec/config.c
+ *
+ * Copyright 2016-2021 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 "core/system.h"
+#include "core/systick.h"
+#include "core/pio.h"
+
+#include "drivers/serial.h"
+#include "drivers/gpio.h"
+#include "drivers/i2c.h"
+#include "drivers/adc.h"
+#include "drivers/ssp.h"
+#include "drivers/timers.h"
+
+
+#include "extdrv/status_led.h"
+#include "extdrv/tmp101_temp_sensor.h"
+#include "extdrv/max31855_thermocouple.h"
+
+#include "core/user_information_block.h"
+#include "core/iap.h"
+
+#include "config.h"
+#include "interface.h"
+#include "comm.h"
+#include "time.h"
+#include "uSD.h"
+
+
+
+/***************************************************************************** */
+/* Pins configuration */
+/* This pins definition block is passed to set_pins() for pins configuration.
+ */
+const struct pio_config common_pins[] = {
+ /* UART 0 */
+ { LPC_UART0_RX_PIO_0_1, LPC_IO_DIGITAL },
+ { LPC_UART0_TX_PIO_0_2, LPC_IO_DIGITAL },
+ /* UART 1 */
+ { LPC_UART1_RX_PIO_0_8, LPC_IO_DIGITAL },
+ { LPC_UART1_TX_PIO_0_9, LPC_IO_DIGITAL },
+ /* I2C 0 */
+ { LPC_I2C0_SCL_PIO_0_10, (LPC_IO_DIGITAL | LPC_IO_OPEN_DRAIN_ENABLE) },
+ { LPC_I2C0_SDA_PIO_0_11, (LPC_IO_DIGITAL | LPC_IO_OPEN_DRAIN_ENABLE) },
+ /* SPI */
+ { LPC_SSP0_SCLK_PIO_0_14, LPC_IO_DIGITAL },
+ { LPC_SSP0_MOSI_PIO_0_17, LPC_IO_DIGITAL },
+ { LPC_SSP0_MISO_PIO_0_16, LPC_IO_DIGITAL },
+ /* ADC */
+ { LPC_ADC_AD0_PIO_0_30, LPC_IO_ANALOG }, /* Home */
+ { LPC_ADC_AD1_PIO_0_31, LPC_IO_ANALOG }, /* Production */
+ { LPC_ADC_AD2_PIO_1_0, LPC_IO_ANALOG }, /* Load */
+ /* GPIO */
+ { LPC_GPIO_0_0, LPC_IO_DIGITAL }, /* Over Voltage protection input */
+ { LPC_GPIO_0_3, LPC_IO_DIGITAL }, /* External switch input */
+ { LPC_GPIO_0_4, LPC_IO_DIGITAL }, /* Zero crossing detection input */
+ { LPC_GPIO_0_5, LPC_IO_DIGITAL }, /* Fan control */
+ { LPC_GPIO_0_6, LPC_IO_DIGITAL }, /* Mosfet control */
+ { LPC_GPIO_0_7, LPC_IO_DIGITAL }, /* Thermocouple chip select */
+ { LPC_GPIO_0_12, LPC_IO_DIGITAL }, /* ISP / User button OK */
+ { LPC_GPIO_0_18, LPC_IO_DIGITAL }, /* Uext Chip select / Module eeprom select */
+ { LPC_GPIO_0_20, LPC_IO_DIGITAL }, /* RGB Leds */
+ { LPC_GPIO_0_21, LPC_IO_DIGITAL }, /* Oled Reset */
+ { LPC_GPIO_0_28, LPC_IO_DIGITAL }, /* Over Temperature protection input */
+ { LPC_GPIO_1_1, LPC_IO_DIGITAL }, /* uSD Card SPI Chip Select */
+ { LPC_GPIO_1_2, LPC_IO_DIGITAL }, /* Button 4 */
+ { LPC_GPIO_1_3, LPC_IO_DIGITAL }, /* Button 3 */
+ { LPC_GPIO_1_4, LPC_IO_DIGITAL }, /* Button 2 */
+ { LPC_GPIO_1_5, LPC_IO_DIGITAL }, /* Button 1 */
+ ARRAY_LAST_PIO,
+};
+
+/* Internal status leds */
+const struct pio status_light_green = LPC_GPIO_0_26;
+const struct pio status_light_red = LPC_GPIO_0_27;
+
+/* Inputs */
+/* External signals */
+const struct pio zero_cross_in_pin = LPC_GPIO_0_4;
+const struct pio ext_disable_in_pin = LPC_GPIO_0_3;
+const struct pio overvoltage_pin = LPC_GPIO_0_0;
+const struct pio overtemperature_pin = LPC_GPIO_0_28;
+
+/* Outputs */
+/* AC output control (Mosfet) */
+const struct pio ac_ctrl = LPC_GPIO_0_6;
+const struct pio fan_ctrl = LPC_GPIO_0_5;
+/* Chip selects */
+const struct pio uSD_cs = LPC_GPIO_1_1; /* uSD card - used in uSD.c !! */
+const struct pio th_cs = LPC_GPIO_0_7; /* Thermocouple */
+const struct pio uext_cs = LPC_GPIO_0_18; /* UEXT module (optionnal) */
+
+
+/***************************************************************************** */
+/* Basic system init and configuration */
+static volatile int got_wdt_int = 0;
+void wdt_callback(void)
+{
+ got_wdt_int = 1;
+}
+
+const struct wdt_config wdconf = {
+ .clk_sel = WDT_CLK_IRC,
+ .intr_mode_only = 0,
+ .callback = wdt_callback,
+ .locks = 0,
+ .nb_clk = 0x03FFFFFF, /* 0x3FF to 0x03FFFFFF */
+ .wdt_window = 0,
+ .wdt_warn = 0x3FF,
+};
+
+void system_init()
+{
+ /* Configure the Watchdog */
+ watchdog_config(&wdconf);
+ system_set_default_power_state();
+ clock_config(SELECTED_FREQ);
+ set_pins(common_pins);
+ gpio_on();
+ status_led_config(&status_light_green, &status_light_red);
+ /* System tick timer MUST be configured and running in order to use the sleeping
+ * functions */
+ systick_timer_on(1); /* 1ms */
+ systick_start();
+}
+
+/* Define our fault handler. This one is not mandatory, the dummy fault handler
+ * will be used when it's not overridden here.
+ * Note : The default one does a simple infinite loop. If the watchdog is deactivated
+ * the system will hang.
+ */
+void fault_info(const char* name, uint32_t len)
+{
+ uprintf(UART0, name);
+ while (1);
+}
+
+
+/***************************************************************************** */
+/* GPIO */
+
+extern void zero_cross_detect(uint32_t gpio);
+extern void overvoltage_protect(uint32_t gpio);
+
+void board_io_config(void)
+{
+ /* Immediatly turn off Mosfet / Triac */
+ config_gpio(&ac_ctrl, 0, GPIO_DIR_OUT, 1);
+ /* Start with FAN ON */
+ config_gpio(&fan_ctrl, 0, GPIO_DIR_OUT, 1);
+
+ /* Configure Input GPIO */
+ config_gpio(&ext_disable_in_pin, 0, GPIO_DIR_IN, 0);
+ config_gpio(&overtemperature_pin, 0, GPIO_DIR_IN, 0);
+
+ /* Over-voltage protection */
+ set_gpio_callback(overvoltage_protect, &overvoltage_pin, EDGE_RISING);
+
+ /* Zero-crossing detection */
+ set_gpio_callback(zero_cross_detect, &zero_cross_in_pin, EDGE_FALLING);
+}
+
+
+/***************************************************************************** */
+/* Internal modules */
+
+const struct lpc_tc_config ac_timer_conf_zc = {
+ .mode = LPC_TIMER_MODE_TIMER | LPC_TIMER_MODE_MATCH,
+ .match_control = { LPC_TIMER_INT_RESET_ON_MATCH, 0, 0, 0, },
+};
+const struct lpc_tc_config ac_timer_conf_delay = {
+ .mode = LPC_TIMER_MODE_TIMER | LPC_TIMER_MODE_MATCH,
+ .match_control = { LPC_TIMER_INT_RESET_AND_STOP_ON_MATCH, 0, 0, 0, },
+};
+const struct lpc_tc_config ac_timer_conf_power_track = {
+ .mode = LPC_TIMER_MODE_TIMER | LPC_TIMER_MODE_MATCH,
+ .match_control = { LPC_TIMER_INT_RESET_ON_MATCH, 0, 0, 0, },
+};
+
+extern void config_rx(uint8_t c);
+extern void comm_rx(uint8_t c);
+extern void ac_switch_on(uint32_t flags);
+extern void zero_cross(uint32_t flags);
+extern void power_track(uint32_t flags);
+
+/* Configure micro-controller internal modules */
+void modules_config(void)
+{
+ uart_on(UART0, 115200, config_rx);
+ uart_on(UART1, 115200, comm_rx);
+ i2c_on(I2C0, I2C_CLK_100KHz, I2C_MASTER);
+ ssp_master_on(SSP_BUS_0, LPC_SSP_FRAME_SPI, 8, 4*1000*1000);
+ adc_on(NULL);
+ adc_start_burst_conversion(ADC_MCH(0) | ADC_MCH(1) | ADC_MCH(2), LPC_ADC_SEQ(0));
+ /* Activate and configure timers */
+ /* This one is used to generate the second zero-crossing, which is not seen by the ZC detector */
+ timer_on(LPC_TIMER_32B0, 0, zero_cross);
+ timer_counter_config(LPC_TIMER_32B0, &ac_timer_conf_zc);
+ /* This one is used to generate the power PMW */
+ timer_on(LPC_TIMER_32B1, 0, ac_switch_on);
+ timer_counter_config(LPC_TIMER_32B1, &ac_timer_conf_delay);
+ /* This one is used to call the load power tracking ADC sampler */
+ timer_on(LPC_TIMER_16B0, 0, power_track);
+ timer_counter_config(LPC_TIMER_16B0, &ac_timer_conf_power_track);
+}
+
+
+extern void handle_cmd_update(uint32_t curent_tick);
+extern void track_isnail_values(uint32_t curent_tick);
+extern void handle_dec_request(uint32_t curent_tick);
+/* Period (in ms) of the handler for the command value update */
+#define CMD_UPD_PERIOD 50
+/* Period (in ms) of the handler for the isnail (tores) ADC update
+ * Do not use a multiple of 10 in order not to be in sync with possible glitchs from the mains
+ */
+#define ADC_UPD_PERIOD 17
+
+/* Add systick callbacks and update and start timers */
+void scialys_systick_and_timers_config(void)
+{
+ uint32_t clk_cycles_ac_zc = 0;
+ /* We want 100 Hz (50 Hz but two zero crossings) with 1% granularity */
+ clk_cycles_ac_zc = get_main_clock() / (100 * 100);
+ uprintf(UART0, "clk_cycles_ac_zc: %d\n", clk_cycles_ac_zc);
+
+ /* Configure the fake zero-cross generation timer to 100Hz.
+ * Do not start it now, it will be started by the first real zero-cross detected in order to
+ * generate the other half zc (not seen by system due to hardware design)
+ * Also sarted by config when DC mode is set */
+ timer_set_match(LPC_TIMER_32B0, CHAN0, (clk_cycles_ac_zc * 100));
+
+ /* Configure the power tracking timer.
+ * 20 points per half sin wave should be enough to get the min and max */
+ timer_set_match(LPC_TIMER_16B0, CHAN0, (clk_cycles_ac_zc * 5));
+ timer_start(LPC_TIMER_16B0);
+
+ /* Add a systick callback to handle command value update */
+ add_systick_callback(handle_cmd_update, CMD_UPD_PERIOD);
+
+ /* Add a systick callback to track ismail values (power consumption and production) */
+ add_systick_callback(track_isnail_values, ADC_UPD_PERIOD);
+
+ /* Add a systick callback to handle time counting */
+ add_systick_callback(handle_dec_request, DEC_PERIOD);
+}
+
+
+/***************************************************************************** */
+/* External components */
+
+uint8_t interface_board_present = 0;
+uint8_t power_board_present = 0;
+
+
+/* Thermocouple reading */
+const struct max31855_sensor_config thermocouple = {
+ .ssp_bus_num = 0,
+ .chip_select = LPC_GPIO_0_7,
+};
+
+/* RTC and time */
+#define RTC_ADDR 0xA2
+
+/* TMP101 I2C temperature sensor on Power board */
+#define TMP101_ADDR1 0x94 /* Pin Addr0 (pin5 of tmp101) connected to VCC */
+struct tmp101_sensor_config tmp101_sensor_power = {
+ .bus_num = I2C0,
+ .addr = TMP101_ADDR1,
+ .resolution = TMP_RES_ELEVEN_BITS,
+};
+
+/* TMP101 I2C temperature sensor on display board */
+#define TMP101_ADDR0 0x90 /* Pin Addr0 (pin5 of tmp101) connected to GND */
+struct tmp101_sensor_config tmp101_sensor_display = {
+ .bus_num = I2C0,
+ .addr = TMP101_ADDR0,
+ .resolution = TMP_RES_ELEVEN_BITS,
+};
+
+int temp_read(uint32_t uart, int* deci_degrees_disp, int* deci_degrees_power)
+{
+ int ret = 0;
+ int conv = 0;
+
+ if (interface_board_present != 0) {
+ ret = tmp101_sensor_read(&tmp101_sensor_display, NULL, deci_degrees_disp);
+ if (ret == 0) {
+ conv |= CONV_DISPLAY_OK;
+ } else {
+ uprintf(uart, "TMP101 read error on display board : %d\n", ret);
+ }
+ }
+ if (power_board_present != 0) {
+ ret = tmp101_sensor_read(&tmp101_sensor_power, NULL, deci_degrees_power);
+ if (ret == 0) {
+ conv |= CONV_POWER_OK;
+ } else {
+ uprintf(uart, "TMP101 read error on power board : %d\n", ret);
+ }
+ }
+ return conv;
+}
+
+
+/* Configure external components */
+int external_components_config(uint32_t uart)
+{
+ int ret = 0;
+ int retcode = 0;
+
+ /* RTC : must be done first to be able to access RTC RAM for uSD init */
+ scialys_time_config(I2C0, RTC_ADDR);
+ scialys_time_init_check(uart);
+
+ /* uSD card */
+ scialys_uSD_detect(uart);
+ scialys_uSD_logs_init(uart);
+
+ /* Thermocouple configuration */
+ max31855_sensor_config(&thermocouple);
+
+ /* TMP101 sensor config on power board */
+ power_board_present = 1;
+ ret = tmp101_sensor_config(&tmp101_sensor_power);
+ if (ret != 0) {
+ uprintf(uart, "Temp config error on power board: %d\n", ret);
+ power_board_present = 0;
+ retcode -= 1;
+ } else {
+ ret = tmp101_sensor_set_continuous_conversion(&tmp101_sensor_power);
+ if (ret != 0) {
+ uprintf(uart, "Temp config part 2 error on power board: %d\n", ret);
+ power_board_present = 0;
+ retcode -= 2;
+ }
+ }
+
+ /* TMP101 sensor config on display board */
+ ret = tmp101_sensor_config(&tmp101_sensor_display);
+ if (ret != 0) {
+ uprintf(uart, "Temp config error on display board: %d\n", ret);
+ interface_board_present = 0;
+ retcode -= 4;
+ } else {
+ ret = tmp101_sensor_set_continuous_conversion(&tmp101_sensor_display);
+ if (ret != 0) {
+ uprintf(uart, "Temp config part 2 error on display board: %d\n", ret);
+ interface_board_present = 0;
+ retcode -= 8;
+ } else {
+ interface_board_present = 1;
+ }
+ }
+
+ /* Configure interface board if present */
+ if (interface_board_present == 1) {
+ ret = interface_config(uart);
+ if (ret != 0) {
+ retcode -= 16;
+ }
+ }
+
+ return retcode;
+}
+
+
+/***************************************************************************** */
+/* App config and/or default values :
+ * Load or update user configuration from internal User Flash */
+
+/* The configuration is stored in the first user info page.
+ * The third user info page is used to store the "last used" uSD block only once per
+ * week in order to prevent deterioration of the flash block. This information is
+ * stored in the RTC RAM backed by the supercapa each time a new block is used.
+ */
+
+struct scialys_config sc_conf;
+
+/* This function sets the pointer to the user information block in flash and makes a copy of
+ * the board config part to RAM so that access to these is quicker.
+ * If in need of memory, the config could be used from flash at the expense of execution time
+ */
+void scialys_read_internal_config(void)
+{
+ struct scialys_config* conf;
+ uint8_t* mem = NULL;
+ uint8_t sum = 0, i = 0;
+
+ conf = get_user_info();
+ memcpy(&sc_conf, conf, sizeof(struct scialys_config));
+ if (sc_conf.config_ok != CONFIG_OK) {
+ uprintf(UART0, "User config read from flash does not have magic : 0x%04x.\n", sc_conf.config_ok);
+ memset(&sc_conf, 0, sizeof(struct scialys_config));
+ }
+ /* Checksum */
+ mem = (uint8_t*)(&sc_conf);
+ for (i = 0, sum = 0; i < sizeof(struct scialys_config); i++) {
+ sum += mem[i];
+ }
+ if (sum != 0) {
+ /* Checksum error : erase config in order to start with a default one */
+ memset(&sc_conf, 0, sizeof(struct scialys_config));
+ uprintf(UART0, "User config read from flash has bad checksum.\n");
+ }
+}
+
+/* Update the board config in user flash */
+int scialys_user_flash_update(void)
+{
+ uint8_t* mem = NULL;
+ int ret = 0;
+ uint8_t sum = 0, i = 0;
+ uint32_t size = 0;
+
+ /* Compute and store checksum */
+ sc_conf.checksum = 0;
+ mem = (uint8_t*)(&sc_conf);
+ for (i = 0, sum = 0; i < sizeof(struct scialys_config); i++) {
+ sum += mem[i];
+ }
+ sc_conf.checksum = 256 - sum;
+
+ /* Erase the first user flash information page */
+ ret = iap_erase_info_page(0, 0);
+ if (ret != 0) {
+ uprintf(UART0, "User config update error: %d\n", ret);
+ return -1;
+ }
+ size = ((sizeof(struct scialys_config) + 3) & ~0x03);
+ ret = iap_copy_ram_to_flash((uint32_t)get_user_info(), (uint32_t)&sc_conf, size);
+ if (ret != 0) {
+ uprintf(UART0, "User config update error: %d\n", ret);
+ return -2;
+ }
+ uprintf(UART0, "Scialys module user config update done\n");
+ return 0;
+}
+
+
+/* Maximum power which can be used from the external (paid) power source, specified in kW or kVA */
+#define GRID_POWER_LIMIT 6
+
+#define SOURCE_POWER_MAX 3000 /* in Watt */
+
+#define LOAD_POWER_MAX 2700 /* in Watt */
+
+/* mA prod value above which the system will not enter forced mode, waiting for home
+ * to stop using power in order to start automatic heating */
+uint32_t sunny_days_prod_value = 0;
+
+#define MAX_TEMP_CONFIG (80 * 100) /* 80 °C */
+
+/* If temperature falls bellow ENTER_FORCE_HEATER_TEMP value, we enter forced heater mode, until
+ * TARGET_FORCED_HEATER_TEMP is reached.
+ * When in forced heater mode, the heater is controlled to heat at FORCED_MODE_VALUE which
+ * is between 20 and 90 °C.
+ */
+#define ENTER_FORCE_HEATER_TEMP (30 * 100) /* 30 °C */
+#define TARGET_FORCED_HEATER_TEMP (50 * 100) /* 50 °C */
+#define FORCED_MODE_VALUE 100 /* A fraction of 100 */
+/* Delay before automatic forced heating */
+#define FORCED_HEATER_DELAY 2 /* Two hours */
+/* Duration of automatic forced heating */
+#define FORCED_HEATER_DURATION 3 /* Three hours */
+
+/* The following values are used tp control manual forced heating
+ */
+#define MANUAL_FORCED_MODE_VALUE 100 /* A fraction of 100 */
+/* Duration of manual forced heating */
+#define MANUAL_ACTIVATION_DURATION 3 /* Three hours */
+/* Target temperature for manual force heating */
+#define MANUAL_TARGET_FORCED_HEATER_TEMP (60 * 100) /* 60 °C */
+
+/* Compute max_intensity from config grid_power_limit.
+ * grid_power_limit is in kVA, max_intensity in mA */
+void update_max_intensity(void)
+{
+ max_intensity = sc_conf.grid_power_limit * 1000 * 1000 / 230;
+}
+/* Update sunny_days_prod_value (Watt to mA) */
+void update_sunny_days_prod_value(void)
+{
+ sunny_days_prod_value = (((uint32_t)(sc_conf.source_has_power_ok) * 1000) / 230);
+}
+
+void scialys_check_config(void)
+{
+ /* If config version error (old config) then only update new fields */
+ switch (sc_conf.conf_version) {
+ /* Do not change order.
+ * Do not add breaks.
+ */
+ default:
+ case 0x00:
+ /* Set base default config */
+ uprintf(UART0, "Fallback to default config\n");
+ sc_conf.grid_power_limit = GRID_POWER_LIMIT;
+ sc_conf.source_power_max = SOURCE_POWER_MAX;
+ sc_conf.source_has_power_ok = SOURCE_POWER_MAX / 2;
+ sc_conf.load_power_max = LOAD_POWER_MAX;
+ sc_conf.conf_max_temp = MAX_TEMP_CONFIG;
+ sc_conf.enter_forced_mode_temp = ENTER_FORCE_HEATER_TEMP;
+ sc_conf.auto_forced_mode_value = FORCED_MODE_VALUE;
+ sc_conf.auto_forced_target_heater_temp = TARGET_FORCED_HEATER_TEMP;
+ sc_conf.auto_forced_heater_delay = FORCED_HEATER_DELAY;
+ sc_conf.auto_forced_heater_duration = FORCED_HEATER_DURATION;
+ sc_conf.manual_forced_mode_value = MANUAL_FORCED_MODE_VALUE;
+ sc_conf.manual_target_heater_temp = MANUAL_TARGET_FORCED_HEATER_TEMP;
+ sc_conf.manual_activation_duration = MANUAL_ACTIVATION_DURATION;
+ sc_conf.manual_force_type = FORCE_TYPE_TIMER;
+ sc_conf.never_force = 0;
+ /* Fallthrough : set additionnal config */
+ case 0x01: /* Old config is 0x01 add parts from 0x02 and newer */
+ sc_conf.load_type = LOAD_TYPE_AC_RES;
+ sc_conf.auto_force_type = FORCE_TYPE_MIN;
+ case 0x02: /* Old config is 0x02 add parts from 0x03 and newer */
+ 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 */
+void read_scialys_main_config(void)
+{
+ scialys_read_internal_config();
+ /* Config version error ? */
+ if (sc_conf.conf_version != CONFIG_VERSION) {
+ sc_conf.config_ok = 0;
+ uprintf(UART0, "User config read from flash has bad config version : %d.\n",
+ sc_conf.conf_version);
+ }
+ /* Check that config read from flash is OK */
+ if (sc_conf.config_ok == CONFIG_OK) {
+ uprintf(UART0, "Internal config read OK\n");
+ return;
+ }
+ /* Config is either blank or not up to date, update missing parts with defaults */
+ scialys_check_config();
+}
+
--- /dev/null
+/****************************************************************************
+ * apps/scialys/test_elec/config.h
+ *
+ * Copyright 2016-2021 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 CONFIG_H
+#define CONFIG_H
+
+#include "core/system.h"
+#include "core/systick.h"
+#include "core/pio.h"
+
+#include "drivers/serial.h"
+#include "drivers/gpio.h"
+#include "drivers/i2c.h"
+#include "drivers/adc.h"
+#include "drivers/ssp.h"
+#include "drivers/timers.h"
+
+#include "extdrv/status_led.h"
+#include "extdrv/max31855_thermocouple.h"
+
+#include "lib/stdio.h"
+
+
+/* Main frequency (core) */
+#define SELECTED_FREQ FREQ_SEL_48MHz
+
+/* Period (in ms) of the decrementer handler from the systick interrupt */
+#define DEC_PERIOD 100
+
+
+/***************************************************************************** */
+/* Configuration storage */
+
+/* This struct is stored to user flash when user validates new configuration
+ * and read from user flash upon statup
+ * Default values are defined within config.c
+ *
+ * Meaning of fields :
+ *
+ * grid_power_limit :
+ * Maximum power which can be used from the external (paid) power source, specified (in kWatt)
+ * source_power_max :
+ * Maximum power which can be produced by installation (in Watts)
+ * source_has_power_ok :
+ * Power production value to be considered as enough to trigger delayed force heating (in Watts)
+ * load_power_max :
+ * Maximum power used by the load. Currently unused by code (in Watts)
+ * conf_max_temp :
+ * Maximum temperature at which heating stops, whatever the condition (production or manual
+ * force included) [20 .. 90]
+ *
+ * enter_forced_mode_temp :
+ * temperature bellow which the system enters auto forced heating mode
+ * degrees centigrade [0 .. 50] (best not to set above 40)
+ * auto_forced_mode_value :
+ * command value to be used when in auto forced mode [10 .. 100]
+ * auto_forced_target_heater_temp :
+ * temperature up to which the water is heated when in auto forced mode [20 .. 90]
+ * auto_forced_heater_delay :
+ * time to wait before starting auto forced heating, in order to wait for production to start
+ * (sunrise, wind, ...)
+ * auto_forced_heater_duration :
+ * duration (hours) of auto forced heating [1 .. 10]
+ * auto_force_type :
+ * type of termination condition for manual force mode (off (no auto force) temp, time, both (first))
+ * manual_forced_mode_value :
+ * command value to be used when in manual forced mode [10 .. 100]
+ * manual_target_heater_temp :
+ * temperature up to which the water is heated when in manual forced mode [20 .. 90]
+ * manual_activation_duration :
+ * duration (hours) of manual forced heating [1 .. 10]
+ * manual_force_type :
+ * type of termination condition for manual force mode (temp, time, both (first))
+ * never_force :
+ *
+ */
+#define CONFIG_OK 0x4F4B /* "OK" in ASCII */
+#define CONFIG_VERSION 0x02
+struct scialys_config {
+ /* Config version and info */
+ uint8_t conf_version;
+ uint8_t checksum;
+ uint16_t config_ok;
+ /* Config limits */
+ uint16_t grid_power_limit; /* specified in kWatt */
+ uint16_t source_power_max; /* specified in Watt */
+ uint16_t source_has_power_ok; /* specified in Watt */
+ uint16_t load_power_max; /* specified in Watt */
+ uint16_t conf_max_temp; /* degrees centigrade (0 - 90°C) */
+ /* Auto Force limits */
+ uint16_t enter_forced_mode_temp; /* degrees centigrade (2 - 60°C) */
+ uint16_t auto_forced_mode_value; /* % : 25 - 100 */
+ uint16_t auto_forced_target_heater_temp; /* degrees centigrade (0 - 90°C) */
+ uint16_t auto_forced_heater_delay; /* Hours : 0 - 5 */
+ uint16_t auto_forced_heater_duration; /* Hours : 1 - 5 */
+ uint16_t auto_force_type; /* Off, Min, Max, Timer, Target */
+ /* Manual Force limits */
+ uint16_t manual_forced_mode_value; /* % : 25 - 100 */
+ uint16_t manual_target_heater_temp; /* degrees centigrade (0 - 90°C) */
+ uint16_t manual_activation_duration;
+ uint16_t manual_force_type; /* Off, Min, Max, Timer, Target */
+ /* Other */
+ uint16_t load_type; /* LOAD_TYPE_AC_RES, LOAD_TYPE_AC_IND or LOAD_TYPE_DC */
+ uint16_t never_force; /* 0 or 1 */
+} __attribute__((packed));
+
+extern struct scialys_config sc_conf;
+
+enum force_types {
+ FORCE_TYPE_OFF,
+ FORCE_TYPE_MIN,
+ FORCE_TYPE_MAX,
+ FORCE_TYPE_TIMER,
+ FORCE_TYPE_TARGET,
+ NB_FORCE_TYPE,
+};
+
+enum load_types {
+ LOAD_TYPE_AC_RES,
+ LOAD_TYPE_AC_IND,
+ LOAD_TYPE_DC,
+ NB_LOAD_TYPES,
+};
+
+/* sunny_days_prod_value : converted to milli Amps from scialys_config.source_has_sun_power_ok */
+extern uint32_t sunny_days_prod_value;
+void update_sunny_days_prod_value(void);
+
+extern int32_t max_intensity;
+void update_max_intensity(void);
+
+/* Check that current config is valid */
+void scialys_check_config(void);
+
+/* Read main configuration from internal user flash - or set to default one */
+void read_scialys_main_config(void);
+
+int scialys_user_flash_update(void);
+
+extern uint8_t interface_board_present;
+extern uint8_t power_board_present;
+
+
+/***************************************************************************** */
+/* Pins configuration */
+extern const struct pio status_led_green;
+extern const struct pio status_led_red;
+
+/* Inputs */
+/* External signals */
+extern const struct pio zero_cross_in_pin;
+extern const struct pio ext_disable_in_pin;
+extern const struct pio overvoltage_pin;
+extern const struct pio overtemperature_pin;
+
+
+/* Outputs */
+/* AC output control (Mosfet) */
+extern const struct pio ac_ctrl;
+extern const struct pio fan_ctrl;
+/* Chip selects */
+extern const struct pio uSD_cs; /* uSD card */
+extern const struct pio th_cs; /* Thermocouple */
+extern const struct pio uext_cs; /* UEXT module (optionnal) */
+
+
+
+/***************************************************************************** */
+extern const struct max31855_sensor_config thermocouple;
+
+
+/***************************************************************************** */
+/* Configuration */
+
+/* Configure the watchdog, clocks, systick, power and common pins */
+void system_init();
+
+/* Define our fault handler. This one is not mandatory, the dummy fault handler
+ * will be used when it's not overridden here.
+ * Note : The default one does a simple infinite loop. If the watchdog is deactivated
+ * the system will hang.
+ */
+void fault_info(const char* name, uint32_t len);
+
+
+/* Configure micro-controller internal modules */
+void modules_config(void);
+
+/* Configure GPIO for specific board usage */
+void board_io_config(void);
+
+/* Configure external components */
+int external_components_config(uint32_t uart);
+
+/* Add systick callbacks and update and start timers */
+void scialys_systick_and_timers_config(void);
+
+
+/***************************************************************************** */
+/* Other parts ... */
+
+/* Internal Temperature sensors */
+#define CONV_DISPLAY_OK (0x01 << 0)
+#define CONV_POWER_OK (0x01 << 1)
+#define CONV_OK (CONV_POWER_OK | CONV_DISPLAY_OK)
+int temp_read(uint32_t uart, int* deci_degrees_disp, int* deci_degrees_power);
+
+#endif /* CONFIG_H */
+
--- /dev/null
+/****************************************************************************
+ * apps/scialys/test_elec/interface.c
+ *
+ * Copyright 2016-2021 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 "core/system.h"
+#include "core/systick.h"
+#include "core/pio.h"
+
+#include "drivers/serial.h"
+#include "drivers/gpio.h"
+#include "drivers/i2c.h"
+
+#include "extdrv/status_led.h"
+#include "extdrv/ws2812.h"
+#include "extdrv/ssd130x_oled_driver.h"
+#include "extdrv/ssd130x_oled_buffer.h"
+
+#include "lib/stdio.h"
+#include "lib/font.h"
+#include "lib/time.h"
+
+#include "config.h"
+#include "interface.h"
+#include "version.h"
+#include "time.h"
+#include "param.h"
+
+/***************************************************************************** */
+/* Buttons inputs on front panel */
+const struct pio button_up = LPC_GPIO_1_5;
+#define GPIO_BUTTON_UP 5
+const struct pio button_right = LPC_GPIO_1_3;
+#define GPIO_BUTTON_RIGHT 3
+const struct pio button_left = LPC_GPIO_1_4;
+#define GPIO_BUTTON_LEFT 4
+const struct pio button_down = LPC_GPIO_1_2;
+#define GPIO_BUTTON_DOWN 2
+/* ISP /User button OK */
+const struct pio button_ok = LPC_GPIO_0_12;
+#define GPIO_BUTTON_OK 12
+
+
+/* Led control data pin */
+const struct pio ws2812_data_out_pin = LPC_GPIO_0_20;
+/* Oled Reset */
+const struct pio oled_reset = LPC_GPIO_0_21;
+
+
+/***************************************************************************** */
+/* Oled Display */
+static uint8_t gddram[ 4 + GDDRAM_SIZE ];
+
+#define DISPLAY_ADDR 0x78
+struct oled_display display = {
+ .bus_type = SSD130x_BUS_I2C,
+ .address = DISPLAY_ADDR,
+ .bus_num = I2C0,
+ .charge_pump = SSD130x_INTERNAL_PUMP,
+ .video_mode = SSD130x_DISP_NORMAL,
+ .contrast = 128,
+ .scan_dir = SSD130x_SCAN_BOTTOM_TOP,
+ .read_dir = SSD130x_RIGHT_TO_LEFT,
+ .display_offset_dir = SSD130x_MOVE_TOP,
+ .display_offset = 4,
+ .gddram = gddram,
+};
+
+#define ROW(x) VERTICAL_REV(x)
+DECLARE_FONT(font);
+
+void display_char(uint8_t line, uint8_t col, uint8_t c)
+{
+ uint8_t tile = (c > FIRST_FONT_CHAR) ? (c - FIRST_FONT_CHAR) : 0;
+ uint8_t* tile_data = (uint8_t*)(&font[tile]);
+ ssd130x_buffer_set_tile(gddram, col, line, tile_data);
+}
+int display_line(uint8_t line, uint8_t col, const char* text)
+{
+ int len = strlen((char*)text);
+ int i = 0;
+
+ for (i = 0; i < len; i++) {
+ uint8_t tile = (text[i] > FIRST_FONT_CHAR) ? (text[i] - FIRST_FONT_CHAR) : 0;
+ uint8_t* tile_data = (uint8_t*)(&font[tile]);
+ ssd130x_buffer_set_tile(gddram, col++, line, tile_data);
+ if (col >= (OLED_LINE_CHAR_LENGTH)) {
+ col = 0;
+ line++;
+ if (line >= SSD130x_NB_PAGES) {
+ len = i;
+ break;
+ }
+ }
+ }
+ return len;
+}
+
+int erase_line(uint8_t line)
+{
+ uint8_t* tile_data = (uint8_t*)(&font[0]);
+ uint8_t col = 0;
+ int i = 0;
+
+ for (i = 0; i < OLED_LINE_CHAR_LENGTH; i++) {
+ ssd130x_buffer_set_tile(gddram, col++, line, tile_data);
+ }
+ /* Update Oled display */
+ return ssd130x_display_full_screen(&display);
+}
+
+void erase_screen_content(void)
+{
+ /* Erase screen data ram */
+ ssd130x_buffer_set(gddram, 0x00);
+}
+
+
+/***************************************************************************** */
+/* Display the arrows above and below a value which can be changed in
+ * param menu
+ */
+void display_arrows(uint8_t line1, uint8_t line2, uint8_t col)
+{
+ char text[DISP_LLEN];
+ snprintf(text, DISP_LLEN, "%c", 0x60);
+ display_line(line1, col, text);
+ snprintf(text, DISP_LLEN, "%c", 0x7f);
+ display_line(line2, col, text);
+}
+
+
+
+/***************************************************************************** */
+volatile uint8_t button_pressed = 0;
+void button_callback(uint32_t gpio)
+{
+ switch (gpio) {
+ case GPIO_BUTTON_OK:
+ button_pressed |= BUTTON_OK;
+ break;
+ case GPIO_BUTTON_UP:
+ button_pressed |= BUTTON_UP;
+ break;
+ case GPIO_BUTTON_DOWN:
+ button_pressed |= BUTTON_DOWN;
+ break;
+ case GPIO_BUTTON_LEFT:
+ button_pressed |= BUTTON_LEFT;
+ break;
+ case GPIO_BUTTON_RIGHT:
+ button_pressed |= BUTTON_RIGHT;
+ break;
+ }
+}
+
+#define SCIALYS_WS2812_NB_LEDS 2
+uint8_t ws2812_leds_data[SCIALYS_WS2812_NB_LEDS * 3];
+struct ws2812_conf ws2812_leds = {
+ .nb_leds = SCIALYS_WS2812_NB_LEDS,
+ .led_data = ws2812_leds_data,
+ .inverted = 0,
+};
+
+int interface_config(uint32_t uart)
+{
+ /* Buttons inputs on front panel */
+ /* Activate on Falling edge (button press) */
+ set_gpio_callback(button_callback, &button_up, EDGE_FALLING);
+ set_gpio_callback(button_callback, &button_left, EDGE_FALLING);
+ set_gpio_callback(button_callback, &button_right, EDGE_FALLING);
+ set_gpio_callback(button_callback, &button_down, EDGE_FALLING);
+ set_gpio_callback(button_callback, &button_ok, EDGE_FALLING);
+
+ /* WS2812B Leds on display board */
+ ws2812_config(&ws2812_leds, &ws2812_data_out_pin);
+
+ /* Configure and start display */
+ config_gpio(&oled_reset, 0, GPIO_DIR_OUT, 1); /* Release reset signal */
+ ssd130x_display_on(&display);
+ erase_screen_content();
+ ssd130x_display_full_screen(&display);
+
+ ws2812_set_pixel(&ws2812_leds, 0, 0x05, 0x15, 0x08);
+ ws2812_send_frame(&ws2812_leds, 0);
+
+ uprintf(uart, "Interface config OK\n");
+
+ return 0;
+}
+
+
+/***************************************************************************** */
+ /* usual menu and switch to config menu */
+
+extern int water_centi_degrees;
+extern uint32_t solar_prod_value;
+extern uint32_t home_conso_value;
+extern volatile uint8_t command_val;
+extern uint8_t manual_cmd;
+
+static int interface_mode = MODE_RUN;
+
+char line[DISP_LLEN];
+
+void interface_update(char heat_mode)
+{
+ int abs_centi = water_centi_degrees;
+
+ if (interface_board_present == 0) {
+ /* DEBUG */ uprintf(UART0, "No interface board ??\n"); /* DEBUG */
+ return;
+ }
+
+ if (water_centi_degrees < 0) {
+ abs_centi = -water_centi_degrees;
+ }
+
+ /* Debug */
+ if (button_pressed != 0) {
+ uprintf(UART0, "Button : 0x%02x\n", button_pressed);
+ }
+
+ if (interface_mode == MODE_RUN) {
+ if (button_pressed != 0) {
+ if (button_pressed & BUTTON_UP) {
+ interface_mode = MODE_CONFIG;
+ }
+ if ((button_pressed & BUTTON_RIGHT) && (manual_cmd <= 95)) {
+ manual_cmd += 5;
+ }
+ if ((button_pressed & BUTTON_LEFT) && (manual_cmd >= 5)) {
+ manual_cmd -= 5;
+ }
+ button_pressed = 0;
+ }
+
+ /* Start with a blank screen */
+ erase_screen_content();
+
+ /* Update time and time display on internal memory */
+ rtc_pcf85363_time_read(&rtc_conf, &now);
+ dprintf(0, 0, "%02xh%02x:%02x", now.hour, now.min, now.sec);
+
+ /* Display info */
+ dprintf(2, 0, "Water:% 2d.%03d %cC", (water_centi_degrees / 100), (abs_centi % 100), 0x24);
+ dprintf(3, 0, "Prod :% 2d,%03dA", (solar_prod_value / 1000), ((solar_prod_value % 1000) / 10));
+ dprintf(4, 0, "Conso:% 2d,%03dA", (home_conso_value / 1000), ((home_conso_value % 1000) / 10));
+ dprintf(5, 0, "Command: %d%%", command_val);
+ dprintf(6, 0, "Mode: %c", heat_mode);
+ dprintf(7, 0, MODULE_VERSION_STR " - " SOFT_VERSION_STR "." COMPILE_VERSION);
+
+ /* Update RGB leds */
+ /* FIXME : use for error signal */
+ ws2812_set_pixel(&ws2812_leds, 0, (home_conso_value / 2000), (solar_prod_value / 2000), 0);
+ ws2812_set_pixel(&ws2812_leds, 1, 0, 0, command_val);
+ ws2812_send_frame(&ws2812_leds, 0);
+ } else {
+ /* Config mode. Mode entered by button action, which implies that interface board is present. */
+ interface_mode = config_interface_handle();
+ }
+ /* Update Oled display */
+ ssd130x_display_full_screen(&display);
+}
+
+
--- /dev/null
+/****************************************************************************
+ * apps/scialys/test_elec/interface.h
+ *
+ * Copyright 2016-2021 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 INTERFACE_H
+#define INTERFACE_H
+
+#include "core/system.h"
+#include "core/pio.h"
+
+#include "extdrv/ssd130x_oled_driver.h"
+
+#define OLED_LINE_CHAR_LENGTH (SSD130x_NB_COL / 8)
+#define DISP_LLEN (OLED_LINE_CHAR_LENGTH + 1)
+
+
+/***************************************************************************** */
+/* Pins configuration */
+/* Buttons inputs on front panel */
+extern const struct pio button_up;
+extern const struct pio button_left;
+extern const struct pio button_right;
+extern const struct pio button_down;
+extern const struct pio button_ok;
+
+
+/* Exported vars */
+extern volatile uint8_t button_pressed;
+#define BUTTON_OK (0x01 << 0)
+#define BUTTON_UP (0x01 << 1)
+#define BUTTON_DOWN (0x01 << 2)
+#define BUTTON_RIGHT (0x01 << 3)
+#define BUTTON_LEFT (0x01 << 4)
+
+/***************************************************************************** */
+/* Configuration */
+
+/* Configure interface board */
+int interface_config(uint32_t uart);
+
+enum interface_modes {
+ MODE_RUN = 0,
+ MODE_CONFIG,
+ MODE_DISPLAY,
+};
+
+extern char line[];
+#define dprintf(l, c, format, ...) \
+ snprintf(line, DISP_LLEN, format, ##__VA_ARGS__); \
+ display_line(l, c, line);
+
+/* Interface content update */
+void interface_update(char mode);
+
+void erase_screen_content(void);
+
+int display_line(uint8_t line, uint8_t col, const char* text);
+
+int erase_line(uint8_t line);
+
+/* Display the arrows above and below a value which can be changed in
+ * param menu
+ */
+void display_arrows(uint8_t line1, uint8_t line2, uint8_t col);
+
+#endif /* INTERFACE_H */
+
--- /dev/null
+/****************************************************************************
+ * apps/scialys/test_elec/main.c
+ *
+ * Scialys system for solar-panel power generation tracking and fair use.
+ *
+ * Copyright 2016-2021 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 "config.h"
+#include "interface.h"
+#include "time.h"
+#include "uSD.h"
+
+
+#define MODULE_VERSION 0x0A
+#define MODULE_NAME "Scialys uC"
+
+
+
+/***************************************************************************** */
+/* System configuration */
+
+/* This multiplier is used for all durations and delays which are stored in number of hours */
+#define T_MULT (3600 * 1000 / DEC_PERIOD)
+
+/* Max water temperature. Internal protection which cannot be overriden by configuration */
+#define ABSOLUTE_MAX_WATER_TEMP (90 * 100) /* Hundredth of degrees C : 90°C */
+/* Internal system max temperature : turn off heating when reached */
+#define MAX_INTERNAL_TEMP (80 * 10) /* Tenth of degrees C : 80°C */
+
+/* Number of main loops during which we keep the mosfets ON in order to keep them safe */
+#define OVERVOLTAGE_PROTECTION_CYCLES 100
+
+
+/* Flags and counters */
+uint8_t forced_heater_mode = 0; /* Flag only */
+uint8_t error_shutdown = 0; /* Flag only */
+#define EXTERNAL_DISABLE_FORCE 0 /* Input is pulled low when external disable is ON */
+uint8_t external_disable = 0; /* Flag only */
+uint8_t 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 */
+};
+
+/* Water temperature */
+int water_centi_degrees = 0;
+
+/* Set to ABSOLUTE_MAX_WATER_TEMP - 1.5°C when enbling max temp hysteresis */
+int max_temp_hysteresys = 0;
+
+/* Internal temperatures */
+int deci_degrees_power = 0; /* Power board sensor */
+int deci_degrees_disp = 0; /* Display board sensor */
+
+/* Value in mA computed upon startup from config ext_source_power_limit */
+int32_t max_intensity = 0;
+
+/* RTC and time */
+struct rtc_time now;
+
+
+
+/***************************************************************************** */
+/* Decrementer for forced heating modes timers */
+
+void handle_dec_request(uint32_t curent_tick)
+{
+ /* Electric Test version : Nothing to do. */
+}
+
+
+/***************************************************************************** */
+/* Power Tracking */
+
+/* Average value computed on the last "NB_VAL" ADC values */
+uint32_t solar_prod_value = 0;
+uint32_t home_conso_value = 0;
+#define NB_VAL 4 /* MUST be a power of 2 */
+#define NB_SHIFT 2
+
+/*
+ * Track power production and usage
+ * These are obtained by reading the Tore outputs (i-Snail output through ADC 0 and 1)
+ */
+void track_isnail_values(uint32_t curent_tick)
+{
+ static uint8_t idx = 0;
+ static uint16_t solar[NB_VAL];
+ static uint16_t home[NB_VAL];
+ uint16_t snapval_solar = 0;
+ uint16_t snapval_home = 0;
+ int cnt, sum_solar = 0, sum_home = 0;
+
+ /* Get new values */
+ adc_get_value(&snapval_solar, LPC_ADC(1));
+ adc_get_value(&snapval_home, LPC_ADC(0));
+
+ /* Store values */
+ solar[idx] = snapval_solar;
+ home[idx] = snapval_home;
+ idx++;
+ if (idx >= NB_VAL) {
+ idx = 0;
+ }
+
+ /* Compute average value */
+ for (cnt = 0; cnt < NB_VAL; cnt++) {
+ sum_solar += solar[cnt];
+ sum_home += home[cnt];
+ }
+
+ /* Convert to mA value.
+ * ADC range is 0 to 1024 - approx 3.2mV / ADC step
+ * Coil convertion is 1000mA -> 50mV : multily mV value by 20 to get mA value
+ * x * 3.2 * 20 == x * 32 * 2 == x << 6
+ * Increment is 64mA / ADC step */
+ solar_prod_value = sum_solar << (6 - NB_SHIFT);
+ home_conso_value = sum_home << (6 - NB_SHIFT);
+}
+
+
+/*
+ * Track power used by load.
+ * These are obtained from ACS711 through ADC 2
+ * ADC values are between 0 and 1023, no load value is at about 520
+ */
+#define TRACK_MAX 1
+#define TRACK_MIN 0
+static volatile uint16_t load_power_highest = 520;
+static volatile uint16_t load_power_lowest = 520;
+
+void power_track(uint32_t flags)
+{
+ /* Start tracking the higest value */
+ static int tracking = TRACK_MAX;
+ static uint16_t last_value = 0;
+ uint16_t val = 0;
+
+ /* Get load power usage */
+ adc_get_value(&val, LPC_ADC(2));
+
+ /* Four possibilities :
+ * tracking max and value increasing : nothing to do
+ * tracking max and value decreasing : we found the max, store it and change tracking
+ * tracking min and value decreasing : nothing to do
+ * tracking min and value increasing : we found the min, store it and change tracking
+ */
+ if (tracking == TRACK_MAX) {
+ if (last_value > val) {
+ /* Found max */
+ load_power_highest = last_value;
+ tracking = TRACK_MIN;
+ }
+ } else {
+ if (last_value < val) {
+ /* Found min */
+ load_power_lowest = last_value;
+ tracking = TRACK_MAX;
+ }
+ }
+ last_value = val;
+}
+
+
+/***************************************************************************** */
+/* AC control */
+
+volatile int act_cmd = 0; /* Start off */
+
+void set_ctrl_duty_cycle(uint8_t value)
+{
+ act_cmd = value;
+ /* Below 3% and above 97% there are triggering problems which
+ * lead to 50% instead of desired command */
+ if (act_cmd >= 97) {
+ act_cmd = 100;
+ } else if (act_cmd <= 3) {
+ act_cmd = 0;
+ }
+}
+
+void ac_switch_on(uint32_t flags)
+{
+ gpio_clear(ac_ctrl);
+}
+
+
+extern uint32_t power_delay[101];
+volatile uint32_t zc_cnt = 0;
+
+/*
+ * Zero crossing handler (either detected or fake one)
+ * If command is at 100%, only set (keep ?) Mosfets ON (gpio_clear)
+ * Else, start with mosfets OFF, in order not to have to break established current.
+ * FIXME : If U and I not in sync, we should sync to I nul, not V nul.
+ */
+void zero_cross(uint32_t unused)
+{
+ uint32_t delay = 0;
+
+ zc_cnt++;
+ /* Command at 100% ? set Mosfets ON and return. */
+ if (act_cmd == 100) {
+ gpio_clear(ac_ctrl);
+ return;
+ }
+ /* Set Mosfet OFF */
+ gpio_set(ac_ctrl);
+ /* Command at 0% ? leave Mosfets OFF (do not start timer) and return. */
+ if (act_cmd == 0) {
+ return;
+ }
+ /* Set timer to trigger ac out ON at given delay */
+ delay = power_delay[100 - act_cmd];
+ timer_set_match(LPC_TIMER_32B1, CHAN0, delay);
+ timer_restart(LPC_TIMER_32B1);
+}
+
+/* This one is used to synchronize to the real zero-crossing detection, if any. */
+void zero_cross_detect(uint32_t unused)
+{
+ /* Prepare to generate the fake second zero crossing */
+ timer_restart(LPC_TIMER_32B0);
+ /* And call zero cross handler for this one */
+ zero_cross(0);
+}
+
+
+
+/* Handle the power command */
+volatile uint8_t command_val = 0;
+uint8_t manual_cmd = 100; /* Forced command value */
+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 */
+#define PROTECTION_CMD_VAL 100
+
+void handle_cmd_update(uint32_t curent_tick)
+{
+ static uint8_t cmd = 0;
+
+ /* Forced over-voltage protection or test mode */
+ if (overvoltage != 0) {
+ cmd = PROTECTION_CMD_VAL;
+ goto cmd_update_end;
+ } else if (old_cmd != -1) {
+ /* Get back to normal after over-voltage protection */
+ cmd = old_cmd;
+ old_cmd = -1;
+ }
+
+ /* Unable to read internal temperature : turn off heating */
+ if ((temp_shutdown == 1) || (error_shutdown == 1)) {
+ cmd = 0;
+ force_fan = 1;
+ goto cmd_update_end;
+ }
+
+ /* Electric Test version : Force command value to manual mode value */
+ cmd = manual_cmd;
+
+cmd_update_end:
+ /* Update FAN command (start or stop FAN) */
+ if ((cmd > 0) || (force_fan == 1)) {
+ fan_on = FAN_COUNTER_START_VAL;
+ gpio_set(fan_ctrl);
+ } else {
+ if (fan_on > 0) {
+ fan_on--;
+ } else {
+ gpio_clear(fan_ctrl);
+ }
+ }
+ /* Set Control Output duty cycle */
+ set_ctrl_duty_cycle(cmd);
+ command_val = cmd;
+}
+
+/* Over-voltage protection */
+void overvoltage_protect(uint32_t gpio)
+{
+ gpio_clear(ac_ctrl); /* Turn mosfets ON */
+ timer_stop(LPC_TIMER_32B1); /* Stop mosfets timer */
+ set_ctrl_duty_cycle(100); /* 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 */
+void mode_update(void)
+{
+ /* Get Over-Temperature information */
+ temp_shutdown = (gpio_read(overtemperature_pin) ? 0 : 1);
+ /* Update Over-Voltage information */
+ if (overvoltage > 0) {
+ uint8_t ov_tmp = (gpio_read(overvoltage_pin) ? 0 : 1);
+ if (ov_tmp == 0) {
+ overvoltage--;
+ }
+ }
+
+ /* Electric Test version : Force manual mode */
+ forced_heater_mode = 1;
+ mode = manual;
+}
+
+/***************************************************************************** */
+
+/* Add a false zero-cross, but make sure to do it only once and only if we are not AC powered */
+void DC_switch_start(void)
+{
+ static int already_done = 0;
+ if ((already_done == 0) && (zc_cnt == 0)) {
+ int ret = add_systick_callback(zero_cross, 10); /* 2 zero-crossing at 50Hz -> 100Hz -> 10ms */
+ uprintf(UART0, "Entering forced DC mode on systick callback %d\n", ret);
+ already_done = 1;
+ } else {
+ uprintf(UART0, "DC forced mode not available. On: %d, Zc: %d\n", already_done, zc_cnt);
+ }
+}
+
+/***************************************************************************** */
+int main(void)
+{
+ uint32_t loop = 0;
+ uint32_t last_tick_update = 0, cur_tick = 0;
+ int ret = 0;
+
+ /* Order is important here */
+ system_init();
+ board_io_config();
+ modules_config();
+ external_components_config(UART0);
+
+ read_scialys_main_config();
+
+ scialys_systick_and_timers_config();
+
+ uprintf(UART0, "System setup complete.\n");
+ msleep(100);
+ status_led(green_only);
+
+ while (1) {
+ /* Feed the dog */
+ if ((solar_prod_value != 0) && (home_conso_value != 0)) {
+ watchdog_feed();
+ }
+
+ /* Update internal temperatures */
+ if (1) {
+ /* Get internal temperatures */
+ ret = temp_read(UART0, &deci_degrees_disp, &deci_degrees_power);
+ if (ret != CONV_OK) {
+ if ((ret & CONV_POWER_OK) == 0) {
+ force_fan = 1;
+ }
+ if ((ret & CONV_DISPLAY_OK) == 0) {
+ force_fan = 1;
+ }
+ }
+ /* Internal temperature protection */
+ if ((deci_degrees_power > MAX_INTERNAL_TEMP) || (deci_degrees_disp > MAX_INTERNAL_TEMP)) {
+ error_shutdown = 1;
+ uprintf(UART0, "Error shutdown !\n");
+ } else if ((deci_degrees_power < ((MAX_INTERNAL_TEMP) / 2)) &&
+ (deci_degrees_disp < ((MAX_INTERNAL_TEMP) / 2))) {
+ error_shutdown = 0;
+ }
+ }
+
+ /* Update water tank temperature */
+ #define WTA_NB_VAL 8 /* Choose a multiple of 2 */
+ #define WTA_SHIFT 3 /* Set shift according to WTA_NB_VAL */
+ if (1) {
+ static int water_centi_average[WTA_NB_VAL];
+ static int wta_idx = 0;
+ int ret = 0, i = 0;
+ /* Get thermocouple value, store in table only if valid */
+ ret = max31855_sensor_read(&thermocouple, NULL, &water_centi_degrees);
+ if (ret != 0) {
+ uprintf(UART0, "Water Temp read error : %d\n", ret);
+ } else {
+ water_centi_average[wta_idx++] = water_centi_degrees;
+ if (wta_idx >= WTA_NB_VAL) {
+ wta_idx = 0;
+ }
+ }
+ water_centi_degrees = 0;
+ /* Average water tank temperature */
+ for (i = 0; i < WTA_NB_VAL; i++) {
+ water_centi_degrees += water_centi_average[i];
+ }
+ water_centi_degrees >>= WTA_SHIFT;
+ }
+
+ /* Update current mode */
+ mode_update();
+
+ /* Display */
+ /* only update twice per second */
+ cur_tick = systick_get_tick_count();
+ if ((cur_tick < last_tick_update) || ((~0 - last_tick_update) < 500)) {
+ /* simple tick wrapping handling */
+ last_tick_update = 0;
+ }
+ if (cur_tick > (last_tick_update + 500)) {
+ interface_update(mode);
+ last_tick_update = cur_tick;
+ }
+
+ /* Store data on uSD once every N seconds.
+ * Note : Once every second requires about 1Go per year.
+ */
+ #define NB_SEC_STORE 5
+ if (1) {
+ static uint32_t last_tick_store = 0;
+ static struct sd_data_blob data;
+ static uint8_t nb_val = 0;
+
+ if (nb_val == 0) {
+ memset(&data, 0, sizeof(struct sd_data_blob));
+ }
+ /* simple tick wrapping handling */
+ if ((cur_tick < last_tick_store) || ((~0 - last_tick_store) < (NB_SEC_STORE * 1000))) {
+ last_tick_store = 0;
+ }
+
+ /* Each flag is set if seen once within the interval */
+ data.flags |= (fan_on ? 1 : 0) | (force_fan << 1);
+ data.flags |= (error_shutdown << 2) | (temp_shutdown << 3) | (overvoltage << 4);
+ data.flags |= (external_disable << 5);
+ data.flags |= (forced_heater_mode << 6);
+
+ /* Add current value to the storage value */
+ nb_val++;
+
+ if (cur_tick > (last_tick_store + (NB_SEC_STORE * 1000))) {
+ /* Divide by the number of values */
+ /* FIXME */
+ scialys_uSD_append_data(&data);
+ uprintf(UART0, "Saved 5s data\n");
+ nb_val = 0;
+ last_tick_store = cur_tick;
+ }
+ }
+
+ /* Debug */
+ if (1) {
+ uprintf(UART0, "#%c:%d:%d:%d:%d:%d:%d:",
+ mode, loop++, solar_prod_value, home_conso_value,
+ water_centi_degrees, deci_degrees_power, deci_degrees_disp);
+ uprintf(UART0, "%d:%d:%d:%d:%d:%d:%d:%d:%d:%d:%d:%d:%d$\n",
+ load_power_lowest, load_power_highest,
+ zc_cnt, command_val, act_cmd, fan_on, force_fan,
+ error_shutdown, temp_shutdown, overvoltage,
+ external_disable, forced_heater_mode, 1);
+ }
+ }
+ return 0;
+}
+
+
+
--- /dev/null
+/****************************************************************************
+ * apps/scialys/tests_elec/param.c
+ *
+ * Copyright 2016-2022 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 "core/system.h"
+#include "core/systick.h"
+#include "core/pio.h"
+
+#include "drivers/serial.h"
+#include "drivers/gpio.h"
+#include "drivers/i2c.h"
+
+#include "extdrv/status_led.h"
+#include "extdrv/ws2812.h"
+#include "extdrv/ssd130x_oled_driver.h"
+#include "extdrv/ssd130x_oled_buffer.h"
+
+#include "lib/stdio.h"
+#include "lib/font.h"
+#include "lib/time.h"
+
+#include "config.h"
+#include "interface.h"
+#include "version.h"
+#include "time.h"
+
+
+/***************************************************************************** */
+ /* Menu part */
+
+enum menu_list {
+ MAIN_MENU,
+ AUTO_FORCE,
+ MANUAL_FORCE,
+ LIMITS,
+ DATE_CONFIG,
+ SAVE_RAZ,
+ NB_MENU, /* This one must be the last */
+};
+static const char* menu_titles[] = {
+ [MAIN_MENU] = "Configuration",
+ [AUTO_FORCE] = "MF auto.",
+ [MANUAL_FORCE] = "MF manuelle",
+ [LIMITS] = "Regl. Limites",
+ [DATE_CONFIG] = "Regl. Heure",
+ [SAVE_RAZ] = "Enreg. / RaZ",
+};
+static uint8_t current_menu = MAIN_MENU;
+static uint8_t current_entry = AUTO_FORCE;
+
+/* Auto Force */
+enum auto_force_list {
+ AUTO_FORCE_START_TEMP,
+ AUTO_FORCE_TARGET_TEMP,
+ AUTO_FORCE_DELAY,
+ AUTO_FORCE_DURATION,
+ AUTO_FORCE_VALUE,
+ AUTO_FORCE_TYPE,
+ AUTO_FORCE_NB_MENU, /* This one must be the last */
+};
+static const char* auto_force_titles[] = {
+ [AUTO_FORCE_START_TEMP] = "Temp. Min",
+ [AUTO_FORCE_TARGET_TEMP] = "Temp. Cible",
+ [AUTO_FORCE_DELAY] = "Delais",
+ [AUTO_FORCE_DURATION] = "Duree",
+ [AUTO_FORCE_VALUE] = "Valeur Cmd",
+ [AUTO_FORCE_TYPE] = "Type Cible",
+};
+static uint8_t auto_force_cur_menu = AUTO_FORCE_START_TEMP;
+static uint8_t auto_force_cur_entry = AUTO_FORCE_START_TEMP;
+
+
+/* Manual Force */
+enum manual_force_list {
+ MANUAL_FORCE_TARGET_TEMP,
+ MANUAL_FORCE_DURATION,
+ MANUAL_FORCE_VALUE,
+ MANUAL_FORCE_TYPE,
+ MANUAL_FORCE_NB_MENU, /* This one must be the last */
+};
+static const char* manual_force_titles[] = {
+ [MANUAL_FORCE_TARGET_TEMP] = "Temp. Cible",
+ [MANUAL_FORCE_DURATION] = "Duree",
+ [MANUAL_FORCE_VALUE] = "Valeur Cmd",
+ [MANUAL_FORCE_TYPE] = "Type Cible",
+};
+static uint8_t manual_force_cur_menu = MANUAL_FORCE_TARGET_TEMP;
+static uint8_t manual_force_cur_entry = MANUAL_FORCE_TARGET_TEMP;
+
+/* Limits */
+enum limits_menu_list {
+ TEMP_MAX,
+ PMAX_GRID,
+ PMAX_PROD,
+ POK_PROD,
+ PMAX_CONSO,
+ LOAD_TYPE,
+ LIM_NB_MENU, /* This one must be the last */
+};
+static const char* limits_modes_titles[] = {
+ [TEMP_MAX] = "Temp. Max",
+ [PMAX_GRID] = "P Max Abon.",
+ [PMAX_PROD] = "P Max Prod.",
+ [POK_PROD] = "P OK Prod.",
+ [PMAX_CONSO] = "P Charge",
+ [LOAD_TYPE] = "Type Charge",
+};
+static uint8_t limits_cur_menu = TEMP_MAX;
+static uint8_t limits_cur_entry = TEMP_MAX;
+
+enum conf_menu_list {
+ SAVE_CONFIG,
+ RESET_CONFIG,
+ TEST_FAN,
+ CONF_NB_MENU, /* This one must be the last */
+};
+static const char* conf_titles[] = {
+ [SAVE_CONFIG] = "Enregistrer",
+ [RESET_CONFIG] = "RaZ config",
+ [TEST_FAN] = "T. Fan",
+};
+static uint8_t conf_cur_menu = SAVE_CONFIG;
+static uint8_t conf_cur_entry = SAVE_CONFIG;
+
+extern uint8_t force_fan;
+extern uint32_t zc_cnt;
+extern void DC_switch_start();
+
+static const char* force_types_str[] = {
+ [FORCE_TYPE_OFF] = "Off",
+ [FORCE_TYPE_MIN] = "Min",
+ [FORCE_TYPE_MAX] = "Max",
+ [FORCE_TYPE_TIMER] = "Timer",
+ [FORCE_TYPE_TARGET] = "Cible",
+};
+
+static const char* load_types_str[] = {
+ [LOAD_TYPE_AC_RES] = "AC Res",
+ [LOAD_TYPE_AC_IND] = "AC Ind",
+ [LOAD_TYPE_DC] = "DC",
+};
+
+
+/* Current sub-menu level. main menu is level 0 */
+int sub_menu_level = 0;
+
+#define BUTTONS_ACTS_DEFAULT() \
+ if (button & BUTTON_LEFT) { sub_menu_level = 1; } \
+ if (button & BUTTON_OK) { sub_menu_level = 0; current_menu = MAIN_MENU; }
+
+/* Configuraton menu handler
+ * Return 0 to get back to
+ */
+int config_interface_handle(void)
+{
+ int interface_mode = MODE_CONFIG;
+ uint8_t button = 0;
+
+ if (current_menu >= NB_MENU) {
+ current_menu = MAIN_MENU;
+ }
+ button = button_pressed;
+ button_pressed = 0;
+ /* Start config with a blank screen */
+ erase_screen_content();
+
+ /* Display current menu title */
+ display_line(0, 1, menu_titles[current_menu]);
+
+ switch (current_menu) {
+ case MAIN_MENU: {
+ int i = 0;
+ /* Display main menu */
+ for (i = 1; i < NB_MENU; i++) {
+ if (i != current_entry) {
+ display_line(i + 1, 2, menu_titles[i]);
+ } else {
+ dprintf(i + 1, 0, " >%s", menu_titles[i]);
+ }
+ }
+ /* Button up/down : select next/previous entry */
+ if (button & BUTTON_UP) {
+ current_entry -= 1;
+ }
+ if (button & BUTTON_DOWN) {
+ current_entry += 1;
+ }
+ if (current_entry >= NB_MENU) {
+ current_entry = 1;
+ } else if (current_entry == 0) {
+ current_entry = NB_MENU - 1;
+ }
+ /* Enter sub-menu */
+ if (button & BUTTON_RIGHT) {
+ current_menu = current_entry;
+ sub_menu_level = 1;
+ }
+ /* Exit menu */
+ if (button & (BUTTON_LEFT | BUTTON_OK)) {
+ interface_mode = MODE_RUN;
+ }
+ }
+ break;
+ case DATE_CONFIG: {
+ static uint8_t date_idx = 0; /* Where to put the arrows */
+ uint8_t line_num = 0;
+ /* Read current date and time */
+ rtc_pcf85363_time_read_dec(&rtc_conf, &now);
+ /* Display date and time */
+ dprintf(3, 4, "20%02u-%02u-%02u", now.year, now.month, now.day);
+ dprintf(5, 6, "%02uh%02u:%02u", now.hour, now.min, now.sec);
+ /* Display arrows on top/bottom of current field */
+ line_num = 2 + ((date_idx > 2) ? 2 : 0);
+ display_arrows(line_num, (line_num + 2), (7 + ((date_idx % 3) * 3)));
+ /* Update current field ? */
+ if ((button & BUTTON_RIGHT) && (date_idx < 5)) {
+ date_idx++;
+ }
+ if ((button & BUTTON_LEFT) && (date_idx > 0)) {
+ date_idx--;
+ }
+ if (button & (BUTTON_UP | BUTTON_DOWN)) {
+ int8_t add = 1;
+ if (button & BUTTON_DOWN) {
+ add = -1;
+ }
+ switch (date_idx) {
+ case 0:
+ now.year += add;
+ if (now.year > 99) { now.year = 0; }
+ break;
+ case 1:
+ now.month += add;
+ if (now.month > 12) { now.month = 1; }
+ if (now.month == 0) { now.month = 12; }
+ break;
+ case 2:
+ now.day += add;
+ if (now.day > 31) { now.day = 1; }
+ if (now.day == 0) { now.day = 31; }
+ break;
+ case 3:
+ now.hour += add;
+ if (now.hour > 100) { now.hour = 23; }
+ if (now.hour > 23) { now.hour = 0; }
+ break;
+ case 4:
+ now.min += add;
+ if (now.min > 100) { now.min = 59; }
+ if (now.min > 59) { now.min = 0; }
+ break;
+ case 5:
+ now.sec += add;
+ if (now.sec > 59) { now.sec = 0; }
+ break;
+ }
+ rtc_pcf85363_time_write_dec(&rtc_conf, &now);
+ }
+ if (button & BUTTON_OK) {
+ sub_menu_level = 0;
+ current_menu = MAIN_MENU;
+ }
+ }
+ break;
+ case LIMITS: {
+ if (sub_menu_level == 1) {
+ int i = 0;
+ for (i = 0; i < LIM_NB_MENU; i++) {
+ if (i != limits_cur_entry) {
+ display_line(i + 2, 3, limits_modes_titles[i]);
+ } else {
+ dprintf(i + 2, 0, " >%s", limits_modes_titles[i]);
+ }
+ }
+ if (button & BUTTON_UP) {
+ limits_cur_entry -= 1;
+ }
+ if (button & BUTTON_DOWN) {
+ limits_cur_entry += 1;
+ }
+ if (limits_cur_entry == 0xFF) {
+ limits_cur_entry = LIM_NB_MENU - 1;
+ } else if (limits_cur_entry >= LIM_NB_MENU) {
+ limits_cur_entry = 0;
+ }
+ if (button & (BUTTON_OK | BUTTON_RIGHT)) {
+ limits_cur_menu = limits_cur_entry;
+ sub_menu_level = 2;
+ }
+ if (button & BUTTON_LEFT) {
+ sub_menu_level = 0;
+ current_menu = MAIN_MENU;
+ }
+ } else { /* sub_menu_level == 2 */
+ display_line(1, 1, limits_modes_titles[limits_cur_menu]);
+ switch (limits_cur_menu) {
+ case TEMP_MAX:
+ display_arrows(4, 6, 12);
+ dprintf(5, 1, "Abs. max: %2u %cC", (sc_conf.conf_max_temp / 100), 0x24);
+ if (button & BUTTON_UP) {
+ if (sc_conf.conf_max_temp <= 8900) {
+ sc_conf.conf_max_temp += 100;
+ } else {
+ sc_conf.conf_max_temp = 9000;
+ }
+ }
+ if (button & BUTTON_DOWN) {
+ if (sc_conf.conf_max_temp > 2000) {
+ sc_conf.conf_max_temp -= 100;
+ } else {
+ sc_conf.conf_max_temp = 2000;
+ }
+ }
+ BUTTONS_ACTS_DEFAULT();
+ break;
+ case PMAX_GRID:
+ display_arrows(4, 6, 11);
+ dprintf(5, 1, "P. abo.: %2u kW", sc_conf.grid_power_limit);
+ if (button & BUTTON_UP) {
+ sc_conf.grid_power_limit++;
+ if (sc_conf.grid_power_limit > 36) {
+ sc_conf.grid_power_limit = 0;
+ }
+ }
+ if (button & BUTTON_DOWN) {
+ sc_conf.grid_power_limit--;
+ if (sc_conf.grid_power_limit > 36) {
+ sc_conf.grid_power_limit = 36;
+ }
+ }
+ update_max_intensity();
+ BUTTONS_ACTS_DEFAULT();
+ break;
+ case PMAX_PROD:
+ display_arrows(4, 6, 8);
+ dprintf(5, 1, "P. : %4u W", sc_conf.source_power_max);
+ if (button & BUTTON_UP) {
+ sc_conf.source_power_max += 50;
+ if (sc_conf.source_power_max > 36000) {
+ sc_conf.source_power_max = 0;
+ }
+ }
+ if (button & BUTTON_DOWN) {
+ sc_conf.source_power_max -= 50;
+ if (sc_conf.source_power_max > 36000) {
+ sc_conf.source_power_max = 36000;
+ }
+ }
+ BUTTONS_ACTS_DEFAULT();
+ break;
+ case POK_PROD:
+ display_arrows(4, 6, 8);
+ dprintf(5, 1, "P. : %4u W", sc_conf.source_has_power_ok);
+ if (button & BUTTON_UP) {
+ sc_conf.source_has_power_ok += 50;
+ if (sc_conf.source_has_power_ok > 36000) {
+ sc_conf.source_has_power_ok = 0;
+ }
+ }
+ if (button & BUTTON_DOWN) {
+ sc_conf.source_has_power_ok -= 50;
+ if (sc_conf.source_has_power_ok > 36000) {
+ sc_conf.source_has_power_ok = 36000;
+ }
+ }
+ update_sunny_days_prod_value();
+ BUTTONS_ACTS_DEFAULT();
+ break;
+ case PMAX_CONSO:
+ display_arrows(4, 6, 8);
+ dprintf(5, 1, "P. : %4u W", sc_conf.load_power_max);
+ if (button & BUTTON_UP) {
+ sc_conf.load_power_max += 50;
+ if (sc_conf.load_power_max > 3000) {
+ sc_conf.load_power_max = 0;
+ }
+ }
+ if (button & BUTTON_DOWN) {
+ sc_conf.load_power_max -= 50;
+ if (sc_conf.load_power_max > 3000) {
+ sc_conf.load_power_max = 3000;
+ }
+ }
+ BUTTONS_ACTS_DEFAULT();
+ break;
+ case LOAD_TYPE:
+ display_arrows(4, 6, 11);
+ dprintf(5, 1, "Type: %s", load_types_str[sc_conf.load_type]);
+ if (button & BUTTON_UP) {
+ sc_conf.load_type++;
+ if (sc_conf.load_type >= NB_LOAD_TYPES) {
+ sc_conf.load_type = 0;
+ }
+ }
+ if (button & BUTTON_DOWN) {
+ sc_conf.load_type--;
+ if (sc_conf.load_type >= NB_LOAD_TYPES) {
+ sc_conf.load_type = (NB_LOAD_TYPES - 1);
+ }
+ }
+ BUTTONS_ACTS_DEFAULT();
+ break;
+ }
+ }
+ }
+ break;
+ case AUTO_FORCE: {
+ if (sub_menu_level == 1) {
+ int i = 0;
+ for (i = 0; i < AUTO_FORCE_NB_MENU; i++) {
+ if (i != auto_force_cur_entry) {
+ display_line(i + 2, 3, auto_force_titles[i]);
+ } else {
+ dprintf(i + 2, 0, " ->%s", auto_force_titles[i]);
+ }
+ }
+ if (button & BUTTON_UP) {
+ auto_force_cur_entry -= 1;
+ }
+ if (button & BUTTON_DOWN) {
+ auto_force_cur_entry += 1;
+ }
+ if (auto_force_cur_entry >= AUTO_FORCE_NB_MENU) {
+ auto_force_cur_entry = 0;
+ } else if (auto_force_cur_entry == 0xFF) {
+ auto_force_cur_entry = AUTO_FORCE_NB_MENU - 1;
+ }
+ if (button & (BUTTON_OK | BUTTON_RIGHT)) {
+ auto_force_cur_menu = auto_force_cur_entry;
+ sub_menu_level = 2;
+ }
+ if (button & BUTTON_LEFT) {
+ sub_menu_level = 0;
+ current_menu = MAIN_MENU;
+ }
+ } else { /* sub_menu_level == 2 */
+ display_line(1, 1, auto_force_titles[auto_force_cur_menu]);
+ switch (auto_force_cur_menu) {
+ case AUTO_FORCE_START_TEMP:
+ display_arrows(4, 6, 11);
+ dprintf(5, 1, "T. min.: %2u %cC", (sc_conf.enter_forced_mode_temp / 100), 0x24);
+ if (button & BUTTON_UP) {
+ sc_conf.enter_forced_mode_temp += 100;
+ /* 60°C is already too high for this module to be of any use */
+ if (sc_conf.enter_forced_mode_temp >= 6000) {
+ sc_conf.enter_forced_mode_temp = 200;
+ }
+ }
+ if (button & BUTTON_DOWN) {
+ sc_conf.enter_forced_mode_temp -= 100;
+ /* 2°C : kind of frost protection */
+ if (sc_conf.enter_forced_mode_temp < 200) {
+ sc_conf.enter_forced_mode_temp = 6000;
+ }
+ }
+ /* Make sure the target temp is at least 5°C above enter temp. */
+ if (sc_conf.enter_forced_mode_temp > (sc_conf.auto_forced_target_heater_temp - 500)) {
+ sc_conf.auto_forced_target_heater_temp = sc_conf.enter_forced_mode_temp + 500;
+ }
+ BUTTONS_ACTS_DEFAULT();
+ break;
+ case AUTO_FORCE_TARGET_TEMP:
+ display_arrows(4, 6, 12);
+ dprintf(5, 1, "T. cible: %2u %cC", (sc_conf.auto_forced_target_heater_temp / 100), 0x24);
+ if (button & BUTTON_UP) {
+ sc_conf.auto_forced_target_heater_temp += 100;
+ if (sc_conf.auto_forced_target_heater_temp >= 9000) {
+ sc_conf.auto_forced_target_heater_temp = sc_conf.enter_forced_mode_temp + 500;
+ }
+ }
+ if (button & BUTTON_DOWN) {
+ sc_conf.auto_forced_target_heater_temp -= 100;
+ /* Make sure the target temp is at least 5°C above enter temp. */
+ if (sc_conf.auto_forced_target_heater_temp < (sc_conf.enter_forced_mode_temp + 500)) {
+ sc_conf.auto_forced_target_heater_temp = 9000;
+ }
+ }
+ BUTTONS_ACTS_DEFAULT();
+ break;
+ case AUTO_FORCE_DELAY:
+ display_arrows(4, 6, 9);
+ dprintf(5, 1, "Delais: %d h", sc_conf.auto_forced_heater_delay);
+ if (button & BUTTON_UP) {
+ sc_conf.auto_forced_heater_delay++;
+ if (sc_conf.auto_forced_heater_delay >= 5) {
+ sc_conf.auto_forced_heater_delay = 0;
+ }
+ }
+ if (button & BUTTON_DOWN) {
+ sc_conf.auto_forced_heater_delay--;
+ if (sc_conf.auto_forced_heater_delay > 5) {
+ sc_conf.auto_forced_heater_delay = 5;
+ }
+ }
+ BUTTONS_ACTS_DEFAULT();
+ break;
+ case AUTO_FORCE_DURATION:
+ display_arrows(4, 6, 9);
+ dprintf(5, 1, "Duree : %d h", sc_conf.auto_forced_heater_duration);
+ if (button & BUTTON_UP) {
+ sc_conf.auto_forced_heater_duration++;
+ if (sc_conf.auto_forced_heater_duration > 5) {
+ sc_conf.auto_forced_heater_duration = 1;
+ }
+ }
+ if (button & BUTTON_DOWN) {
+ sc_conf.auto_forced_heater_duration--;
+ if (sc_conf.auto_forced_heater_duration < 1 ) {
+ sc_conf.auto_forced_heater_duration = 5;
+ }
+ }
+ BUTTONS_ACTS_DEFAULT();
+ break;
+ case AUTO_FORCE_VALUE:
+ display_arrows(4, 6, 10);
+ dprintf(5, 1, "Cmd. : %3u %", sc_conf.auto_forced_mode_value);
+ if (button & BUTTON_UP) {
+ sc_conf.auto_forced_mode_value += 5;
+ if (sc_conf.auto_forced_mode_value > 100) {
+ sc_conf.auto_forced_mode_value = 25;
+ }
+ }
+ if (button & BUTTON_DOWN) {
+ sc_conf.auto_forced_mode_value -= 5;
+ if (sc_conf.auto_forced_mode_value < 25 ) {
+ sc_conf.auto_forced_mode_value = 100;
+ }
+ }
+ BUTTONS_ACTS_DEFAULT();
+ break;
+ case AUTO_FORCE_TYPE:
+ display_arrows(4, 6, 10);
+ dprintf(5, 1, "Type : %s", force_types_str[sc_conf.auto_force_type]);
+ if (button & BUTTON_UP) {
+ sc_conf.auto_force_type++;
+ if (sc_conf.auto_force_type >= NB_FORCE_TYPE) {
+ sc_conf.auto_force_type = 0;
+ }
+ }
+ if (button & BUTTON_DOWN) {
+ sc_conf.auto_force_type--;
+ if (sc_conf.auto_force_type >= NB_FORCE_TYPE ) {
+ sc_conf.auto_force_type = NB_FORCE_TYPE - 1;
+ }
+ }
+ BUTTONS_ACTS_DEFAULT();
+ break;
+ }
+ }
+ }
+ break;
+ case MANUAL_FORCE: {
+ if (sub_menu_level == 1) {
+ int i = 0;
+ for (i = 0; i < MANUAL_FORCE_NB_MENU; i++) {
+ if (i != manual_force_cur_entry) {
+ display_line(i + 2, 3, manual_force_titles[i]);
+ } else {
+ dprintf(i + 2, 0, " ->%s", manual_force_titles[i]);
+ }
+ }
+ if (button & BUTTON_UP) {
+ manual_force_cur_entry -= 1;
+ }
+ if (button & BUTTON_DOWN) {
+ manual_force_cur_entry += 1;
+ }
+ if (manual_force_cur_entry >= MANUAL_FORCE_NB_MENU) {
+ manual_force_cur_entry = 0;
+ } else if (manual_force_cur_entry == 0xFF) {
+ manual_force_cur_entry = MANUAL_FORCE_NB_MENU - 1;
+ }
+ if (button & (BUTTON_OK | BUTTON_RIGHT)) {
+ manual_force_cur_menu = manual_force_cur_entry;
+ sub_menu_level = 2;
+ }
+ if (button & BUTTON_LEFT) {
+ sub_menu_level = 0;
+ current_menu = MAIN_MENU;
+ }
+ } else { /* sub_menu_level == 2 */
+ display_line(1, 1, manual_force_titles[manual_force_cur_menu]);
+ switch (manual_force_cur_menu) {
+ case MANUAL_FORCE_TARGET_TEMP:
+ display_arrows(4, 6, 12);
+ dprintf(5, 1, "T. cible: %2u %cC", (sc_conf.manual_target_heater_temp / 100), 0x24);
+ if (button & BUTTON_UP) {
+ sc_conf.manual_target_heater_temp += 100;
+ if (sc_conf.manual_target_heater_temp >= 9000) {
+ sc_conf.manual_target_heater_temp = 1000;
+ }
+ }
+ if (button & BUTTON_DOWN) {
+ sc_conf.manual_target_heater_temp -= 100;
+ if (sc_conf.manual_target_heater_temp < 1000) {
+ sc_conf.manual_target_heater_temp = 9000;
+ }
+ }
+ BUTTONS_ACTS_DEFAULT();
+ break;
+ case MANUAL_FORCE_DURATION:
+ display_arrows(4, 6, 9);
+ dprintf(5, 1, "Duree : %d h", sc_conf.manual_activation_duration);
+ if (button & BUTTON_UP) {
+ sc_conf.manual_activation_duration++;
+ if (sc_conf.manual_activation_duration > 5) {
+ sc_conf.manual_activation_duration = 1;
+ }
+ }
+ if (button & BUTTON_DOWN) {
+ sc_conf.manual_activation_duration--;
+ if (sc_conf.manual_activation_duration < 1 ) {
+ sc_conf.manual_activation_duration = 5;
+ }
+ }
+ BUTTONS_ACTS_DEFAULT();
+ break;
+ case MANUAL_FORCE_VALUE:
+ display_arrows(4, 6, 10);
+ dprintf(5, 1, "Cmd. : %3u %", sc_conf.manual_forced_mode_value);
+ if (button & BUTTON_UP) {
+ sc_conf.manual_forced_mode_value += 5;
+ if (sc_conf.manual_forced_mode_value > 100) {
+ sc_conf.manual_forced_mode_value = 25;
+ }
+ }
+ if (button & BUTTON_DOWN) {
+ sc_conf.manual_forced_mode_value -= 5;
+ if (sc_conf.manual_forced_mode_value < 25 ) {
+ sc_conf.manual_forced_mode_value = 100;
+ }
+ }
+ BUTTONS_ACTS_DEFAULT();
+ break;
+ case MANUAL_FORCE_TYPE:
+ display_arrows(4, 6, 10);
+ dprintf(5, 1, "Type : %s", force_types_str[sc_conf.manual_force_type]);
+ if (button & BUTTON_UP) {
+ sc_conf.manual_force_type++;
+ if (sc_conf.manual_force_type >= NB_FORCE_TYPE) {
+ sc_conf.manual_force_type = 0;
+ }
+ }
+ if (button & BUTTON_DOWN) {
+ sc_conf.manual_force_type--;
+ if (sc_conf.manual_force_type >= NB_FORCE_TYPE ) {
+ sc_conf.manual_force_type = NB_FORCE_TYPE - 1;
+ }
+ }
+ BUTTONS_ACTS_DEFAULT();
+ break;
+ }
+ }
+ }
+ break;
+ case SAVE_RAZ: {
+ if (sub_menu_level == 1) {
+ int i = 0;
+ uint8_t states[3];
+
+ states[0] = force_fan; /* TEST_FAN */
+
+ for (i = 0; i < CONF_NB_MENU; i++) {
+ if (i != conf_cur_entry) {
+ display_line(i + 2, 3, conf_titles[i]);
+ } else if (i >= TEST_FAN) {
+ dprintf(i + 2, 0, " ->%s : %d", conf_titles[i], states[i - TEST_FAN]);
+ } else {
+ dprintf(i + 2, 0, " ->%s", conf_titles[i]);
+ }
+ }
+ if (button & BUTTON_UP) {
+ conf_cur_entry -= 1;
+ }
+ if (button & BUTTON_DOWN) {
+ conf_cur_entry += 1;
+ }
+ if (conf_cur_entry >= CONF_NB_MENU) {
+ conf_cur_entry = 0;
+ } else if (conf_cur_entry == 0xFF) {
+ conf_cur_entry = CONF_NB_MENU - 1;
+ }
+ if (button & (BUTTON_OK | BUTTON_RIGHT)) {
+ conf_cur_menu = conf_cur_entry;
+ sub_menu_level = 2;
+ }
+ if (button & BUTTON_LEFT) {
+ sub_menu_level = 0;
+ current_menu = MAIN_MENU;
+ }
+ } else { /* sub_menu_level == 2 */
+ 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"));
+ sub_menu_level = 1;
+ break;
+ case SAVE_CONFIG: {
+ display_line(2, 1, "Sauver config ?");
+ display_line(5, 1, "Valider = OK");
+ display_line(6, 1, "Gauche = Annul");
+ if (button & BUTTON_LEFT) {
+ sub_menu_level = 0;
+ current_menu = MAIN_MENU;
+ }
+ if (button & BUTTON_OK) {
+ int ret = 0;
+ erase_screen_content();
+ display_line(4, 2, "Saving ...");
+ ret = scialys_user_flash_update();
+ uprintf(UART0, "\nFlash update from menu: %d\n", ret);
+ msleep(1500);
+ sub_menu_level = 0;
+ current_menu = MAIN_MENU;
+ }
+ }
+ break;
+ case RESET_CONFIG: {
+ display_line(2, 1, "Efface Config ?");
+ display_line(5, 1, "Valider = OK");
+ display_line(6, 1, "Gauche = Annul");
+ if (button & BUTTON_LEFT) {
+ sub_menu_level = 0;
+ current_menu = MAIN_MENU;
+ }
+ if (button & BUTTON_OK) {
+ erase_screen_content();
+ display_line(4, 8, "Chargement ...");
+ sc_conf.conf_version = 0x00;
+ scialys_check_config();
+ uprintf(UART0, "\nSystem defaults reloaded from menu\n");
+ msleep(500);
+ sub_menu_level = 0;
+ current_menu = MAIN_MENU;
+ }
+ }
+ break;
+ }
+ }
+ }
+ break;
+ default:
+ interface_mode = MODE_RUN;
+ current_menu = MAIN_MENU;
+ break;
+ }
+ return interface_mode;
+}
+
+
--- /dev/null
+/****************************************************************************
+ * apps/scialys/test_elec/param.h
+ *
+ * Copyright 2016-2022 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 PARAM_H
+#define PARAM_H
+
+
+/***************************************************************************** */
+/* Configuration menu */
+
+/* Configuraton menu handler
+ * Return 0 to get back to
+ */
+int config_interface_handle(void);
+
+#endif /* PARAM_H */
+
--- /dev/null
+#include "lib/stdint.h"
+
+uint32_t power_delay[101] = {
+ [0] = 0,
+ [1] = 30608,
+ [2] = 43360,
+ [3] = 53195,
+ [4] = 61530,
+ [5] = 68911,
+ [6] = 75620,
+ [7] = 81822,
+ [8] = 87626,
+ [9] = 93107,
+ [10] = 98319,
+ [11] = 103305,
+ [12] = 108095,
+ [13] = 112716,
+ [14] = 117188,
+ [15] = 121527,
+ [16] = 125750,
+ [17] = 129867,
+ [18] = 133888,
+ [19] = 137823,
+ [20] = 141680,
+ [21] = 145465,
+ [22] = 149184,
+ [23] = 152843,
+ [24] = 156447,
+ [25] = 159999,
+ [26] = 163505,
+ [27] = 166967,
+ [28] = 170389,
+ [29] = 173774,
+ [30] = 177124,
+ [31] = 180443,
+ [32] = 183732,
+ [33] = 186995,
+ [34] = 190232,
+ [35] = 193446,
+ [36] = 196639,
+ [37] = 199813,
+ [38] = 202969,
+ [39] = 206109,
+ [40] = 209234,
+ [41] = 212347,
+ [42] = 215448,
+ [43] = 218539,
+ [44] = 221621,
+ [45] = 224695,
+ [46] = 227763,
+ [47] = 230827,
+ [48] = 233886,
+ [49] = 236944,
+ [50] = 240000,
+ [51] = 243055,
+ [52] = 246113,
+ [53] = 249172,
+ [54] = 252236,
+ [55] = 255304,
+ [56] = 258378,
+ [57] = 261460,
+ [58] = 264551,
+ [59] = 267652,
+ [60] = 270765,
+ [61] = 273890,
+ [62] = 277030,
+ [63] = 280186,
+ [64] = 283360,
+ [65] = 286553,
+ [66] = 289767,
+ [67] = 293004,
+ [68] = 296267,
+ [69] = 299556,
+ [70] = 302875,
+ [71] = 306225,
+ [72] = 309610,
+ [73] = 313032,
+ [74] = 316494,
+ [75] = 320000,
+ [76] = 323552,
+ [77] = 327156,
+ [78] = 330815,
+ [79] = 334534,
+ [80] = 338319,
+ [81] = 342176,
+ [82] = 346111,
+ [83] = 350132,
+ [84] = 354249,
+ [85] = 358472,
+ [86] = 362811,
+ [87] = 367283,
+ [88] = 371904,
+ [89] = 376694,
+ [90] = 381680,
+ [91] = 386892,
+ [92] = 392373,
+ [93] = 398177,
+ [94] = 404379,
+ [95] = 411088,
+ [96] = 418469,
+ [97] = 426804,
+ [98] = 436639,
+ [99] = 449391,
+ [100] = 480000,
+};
--- /dev/null
+#! /usr/bin/perl -w
+
+use strict;
+use warnings;
+
+use Math::Trig;
+
+my ($clocks, $outfile) = @ARGV;
+my ($fh, $angle, $v, $w, $idx);
+
+if (defined $outfile) {
+ open($fh, '>', $outfile);
+ print $fh "#include \"lib/stdint.h\"\n\n";
+ print $fh "uint32_t power_delay[101] = {\n";
+}
+
+for (my $i = 0; $i <= 100; $i++) {
+ $angle = $i * 0.02;
+ $v = (acos(1 - $angle) / pi * 100);
+ $w = int($v * $clocks);
+ if (not defined $outfile) {
+ print "$v - [$i] = $w,\n";
+ } else {
+ print $fh "\t[$i] = $w,\n";
+ }
+}
+
+if (defined $outfile) {
+ print $fh "};\n";
+ close($fh);
+}
+print "Done\n";
+
+
--- /dev/null
+/****************************************************************************
+ * apps/scialys/test_elec/time.c
+ *
+ * Copyright 2016-2021 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 "core/system.h"
+
+#include "drivers/serial.h"
+#include "drivers/i2c.h"
+
+#include "lib/stdio.h"
+#include "lib/errno.h"
+
+#include "time.h"
+
+
+/***************************************************************************** */
+/* RTC */
+struct rtc_pcf85363a_config rtc_conf = {
+ .mode = PCF85363A_MODE_RTC,
+ .config_marker = PCF85363A_CONFIGURED_1,
+ .batt_ctrl = PCF85363A_CONF_BATT_TH_2_8V,
+};
+/* Oldest acceptable time in RTC. BCD coded. */
+const struct rtc_time oldest = {
+ .year = 0x22,
+ .month = 0x11,
+ .day = 0x10,
+ .hour = 0x13,
+ .min = 0x37,
+};
+int time_valid = 0;
+int rtc_conf_ok = 0;
+
+int scialys_time_init_check(uint32_t uart)
+{
+ int ret = 0;
+ ret = rtc_pcf85363a_config(&rtc_conf);
+ if (ret < 0) {
+ uprintf(uart, "RTC config error: %d\n", ret);
+ return -1;
+ }
+ rtc_conf_ok = 1;
+
+ ret = rtc_pcf85363a_is_up(&rtc_conf, &oldest);
+ if (ret == 1) {
+ /* Time is valid (newer than source code time) */
+ char buff[30];
+ rtc_pcf85363_time_read(&rtc_conf, &now);
+ rtc_pcf85363_time_to_str(&now, buff, 30);
+ /* Debug */
+ uprintf(uart, "Using time from RTC: %s\n", buff);
+ time_valid = 1;
+ } else if (ret == -EFAULT) {
+ /* Time is older than source code time */
+ time_valid = 0;
+ uprintf(uart, "RTC time too old, provide valid time.\n");
+ /* FIXME: remove this, time should come from control master */
+ memcpy(&now, &oldest, sizeof(struct rtc_time));
+ rtc_pcf85363_time_write(&rtc_conf, &now);
+ } else {
+ /* RTC config error */
+ rtc_conf_ok = 0;
+ }
+ return ret;
+}
+
+int scialys_time_update(uint32_t uart, struct rtc_time* now)
+{
+ rtc_pcf85363_time_write(&rtc_conf, now);
+ return 0;
+}
+
+
+/***************************************************************************** */
+/* RTC init */
+void scialys_time_config(uint32_t i2c_bus_num, uint8_t rtc_addr)
+{
+ rtc_conf.bus_num = i2c_bus_num;
+ rtc_conf.addr = rtc_addr;
+}
+
--- /dev/null
+/****************************************************************************
+ * apps/scialys/test_elec/time.h
+ *
+ * Copyright 2016-2021 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 RTC_TIME_H
+#define RTC_TIME_H
+
+#include "extdrv/rtc_pcf85363a.h"
+
+extern struct rtc_pcf85363a_config rtc_conf;
+extern struct rtc_time now;
+
+int scialys_time_init_check(uint32_t uart);
+void scialys_time_config(uint32_t i2c_bus_num, uint8_t rtc_addr);
+
+#endif /* RTC_TIME_H */
+
--- /dev/null
+/****************************************************************************
+ * apps/scialys/test_elec/uSD.c
+ *
+ * Copyright 2016-2021 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 "core/system.h"
+#include "core/user_information_block.h"
+#include "core/iap.h"
+
+#include "drivers/serial.h"
+#include "drivers/ssp.h"
+
+#include "lib/stdio.h"
+#include "lib/errno.h"
+
+#include "uSD.h"
+#include "time.h"
+#include "version.h"
+
+
+/***************************************************************************** */
+/* SD/MMC Card */
+struct sdmmc_card micro_sd = {
+ .ssp_bus_num = SSP_BUS_0,
+ .card_type = MMC_CARDTYPE_UNKNOWN,
+ .block_size = 64,
+ .chip_select = LPC_GPIO_1_1,
+};
+
+static uint8_t got_uSD = 0;
+
+/* Buffer to read / write uSD card */
+uint8_t mmc_data[MMC_BUF_SIZE];
+#define SCIALYS_MAGIC "Scialys data bloc"
+
+/* Read 1 block of 512 bytes */
+int scialys_uSD_read(uint32_t block_num)
+{
+ int ret = 0;
+ if (got_uSD == 0) {
+ return -1;
+ }
+ ret = sdmmc_read_block(µ_sd, block_num, mmc_data);
+ /* FIXME : check return value */
+ return ret;
+}
+
+
+/* Write 1 block of 512 bytes */
+int scialys_uSD_write(uint32_t block_num)
+{
+ int ret = 0;
+ if (got_uSD == 0) {
+ return -1;
+ }
+ ret = sdmmc_write_block(µ_sd, block_num, mmc_data);
+ /* Check return value */
+ if (ret == -EBUSY) {
+ /* FIXME : do not loop, use flag to retry wait later */
+ uint8_t cnt = 10;
+ do {
+ ret = sdmmc_wait_write_end(µ_sd);
+ } while ((ret != 0) && (cnt-- > 0));
+ } else if (ret == -ENODEV) {
+ /* uSD stalled ??? */
+ /* FIXME : Try reset ? */
+ got_uSD = 0;
+ }
+ return ret;
+}
+
+
+/* Setup the first part of a data buffer (a 512 bytes uSD block) */
+int scialys_uSD_init_buffer(void)
+{
+ int idx = 0;
+ memset((char*)mmc_data, 0, MMC_BUF_SIZE);
+
+ /* Store magic to uSD data block head to mark as a valid data block */
+ memcpy(mmc_data, SCIALYS_MAGIC, strlen(SCIALYS_MAGIC) + 1);
+ idx = strlen(SCIALYS_MAGIC) + 1;
+
+ /* Add current version string */
+ memcpy(mmc_data + idx, MODULE_VERSION_STR, strlen(MODULE_VERSION_STR) + 1);
+ idx += strlen(MODULE_VERSION_STR) + 1;
+ mmc_data[idx++] = SOFT_VERSION_STR[0];
+ memcpy(mmc_data + idx, COMPILE_VERSION, strlen(COMPILE_VERSION) + 1);
+ idx += strlen(COMPILE_VERSION) + 1;
+
+ /* Add date */
+ memcpy(mmc_data + idx, (uint8_t*)&now, sizeof(struct rtc_time));
+ idx += sizeof(struct rtc_time);
+
+ return idx;
+}
+
+
+/* Check that the first part of a data buffer is a valid data block */
+int scialys_uSD_data_buffer_is_valid(void)
+{
+ return strncmp((char*)mmc_data, SCIALYS_MAGIC, strlen(SCIALYS_MAGIC));
+}
+
+/* Last used uSD block. Store new data on the next one */
+struct last_block_info {
+ uint32_t magic;
+ uint32_t block_num;
+};
+#define LAST_BLOCK_MAGIC 0x4C415354 /* LAST in ascii, big endian */
+static struct last_block_info last_block = {
+ .magic = LAST_BLOCK_MAGIC,
+ .block_num = 1,
+};
+#define LAST_BLOCK_INFO_OFFSET_IN_USER_FLASH (2 * 512) /* Use third page in user info block */
+
+
+/* Find last log entry (last used uSD block) */
+int scialys_uSD_logs_init(int uart)
+{
+ int ret = 0;
+
+ /* First, check RAM from RTC to get last block used info */
+ ret = rtc_pcf85363a_read_ram(&rtc_conf, 0, (uint8_t*)&last_block, sizeof(struct last_block_info));
+ if (ret != 0) {
+ uprintf(uart, "Error when reading RAM from RTC : %d\n", ret);
+ return -1;
+ }
+ if (last_block.magic == LAST_BLOCK_MAGIC) {
+ /* Consider that the last block number is valid too :) */
+ uprintf(uart, "Last block info from RAM : %d\n", last_block.block_num);
+ return 0;
+ }
+ /* Invalid magic in RTC RAM ... */
+ uprintf(uart, "Last block info not valid in RAM\n");
+ /* FIXME : read info at offset LAST_BLOCK_INFO_OFFSET_IN_USER_FLASH to get the last block used info */
+ last_block.block_num = 1;
+ last_block.magic = LAST_BLOCK_MAGIC;
+ return 0;
+}
+
+/* Append data to temporary data buffer, and possibly flush data to uSD when buffer is full */
+int scialys_uSD_append_data(struct sd_data_blob* data)
+{
+ static uint16_t index = 0;
+
+ if (index == 0) {
+ index = scialys_uSD_init_buffer();
+ }
+
+ /* Store data to buffer */
+ memcpy(mmc_data + index, data, sizeof(struct sd_data_blob));
+ index += sizeof(struct sd_data_blob);
+
+ /* 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) {
+ int ret = scialys_uSD_write(last_block.block_num + 1);
+ if (ret != 0) {
+ uprintf(UART0, "Write to uSD returned %d\n", ret);
+ /* Drop old data ... we do not have that much RAM available !!! */
+ index = 0;
+ return ret;
+ }
+ last_block.block_num++;
+ /* Save block number to RTC RAM */
+ rtc_pcf85363a_write_ram(&rtc_conf, 0, (uint8_t*)&last_block, sizeof(struct last_block_info));
+ /* Store last used uSD block in flash once every 2048 blocks */
+ if ((last_block.block_num & 0x3FF) == 0x00) {
+ /* FIXME */
+ }
+ index = 0;
+ uprintf(UART0, "Wrote 512 bytes to SD card\n");
+ return 1;
+ }
+
+ return 0;
+}
+
+
+/***************************************************************************** */
+/* micro SD card init */
+#define DEBUG 1
+
+#define NB_SD_INIT_LOOPS 3
+#define NB_SD_INIT_WAIT_RETRIES 10
+int scialys_uSD_detect(int uart)
+{
+ int ret = 0;
+ uint8_t step = 0, loop = 0;
+ uint8_t retries = NB_SD_INIT_WAIT_RETRIES;
+
+ /* Get card to SPI mode */
+ ret = sdmmc_reset(µ_sd);
+
+ /* Let some time for uSD card to power up */
+ do {
+ ret = sdmmc_init_single(µ_sd, &step, &retries);
+#ifdef DEBUG
+ uprintf(uart, "uSD init (%d): step:%d, retries: %d, ret: %d, type: %d, bs: %d\n",
+ loop, step, retries, ret, micro_sd.card_type, micro_sd.block_size);
+#endif
+ if (ret != 0) {
+ msleep(20);
+ }
+ loop++;
+ } while ((ret != 0) && (loop < NB_SD_INIT_LOOPS));
+
+ /* Got uSD ? */
+ if (loop >= NB_SD_INIT_LOOPS) {
+#ifdef DEBUG
+ uprintf(uart, "uSD init failed, no uSD card present.\n");
+#endif
+ got_uSD = 0;
+ return -1;
+ }
+
+ /* uSD card detected */
+ got_uSD = 1;
+ /* got_uSD MUST be set to 1 from here on ! */
+ ret = scialys_uSD_read(0); /* Read 1 block at start of card */
+ /* FIXME : check that the card magic is present */
+#ifdef DEBUG
+ uprintf(uart, "uSD read (ret : %d) : %s\n", ret, mmc_data);
+#endif
+
+ return 0;
+}
+
+
--- /dev/null
+/****************************************************************************
+ * apps/scialys/test_elec/usSD.h
+ *
+ * Copyright 2016-2021 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 USD_H
+#define USD_H
+
+#include "extdrv/sdmmc.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"
+ * 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];
+
+/* Read 1 block of 512 bytes */
+int scialys_uSD_read(uint32_t block_num);
+
+/* Write 1 block of 512 bytes */
+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(struct sd_data_blob* data);
+
+/* Check that the first part of a data buffer is a valid data block */
+int scialys_uSD_data_buffer_is_valid(void);
+
+
+
+/***************************************************************************** */
+/* microSD card init */
+
+/* Find last log entry (last used uSD block) */
+int scialys_uSD_logs_init(int uart);
+
+int scialys_uSD_detect(int uart);
+
+
+#endif /* USD_H */
+
--- /dev/null
+#define MODULE_VERSION_STR "v0.10.1"
+#define SOFT_VERSION_STR "Telec"
+#define COMPILE_VERSION "01"