From: Nathael Pajani Date: Sun, 20 Nov 2022 16:00:37 +0000 (+0100) Subject: Adding test version used for Electrical safety tests X-Git-Url: http://git.techno-innov.fr/?a=commitdiff_plain;h=99fb090778f10ac309bedd366575422a27623172;p=soft%2Flpc122x%2Fscialys Adding test version used for Electrical safety tests --- diff --git a/tests_elec/.gitignore b/tests_elec/.gitignore new file mode 100644 index 0000000..6361591 --- /dev/null +++ b/tests_elec/.gitignore @@ -0,0 +1,2 @@ +dump +logs diff --git a/tests_elec/.version b/tests_elec/.version new file mode 100644 index 0000000..69a893a --- /dev/null +++ b/tests_elec/.version @@ -0,0 +1 @@ +66 diff --git a/tests_elec/Makefile b/tests_elec/Makefile new file mode 100644 index 0000000..d3cc3d6 --- /dev/null +++ b/tests_elec/Makefile @@ -0,0 +1,26 @@ +# 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) diff --git a/tests_elec/comm.c b/tests_elec/comm.c new file mode 100644 index 0000000..f6891ad --- /dev/null +++ b/tests_elec/comm.c @@ -0,0 +1,59 @@ +/**************************************************************************** + * apps/scialys/test_elec/comm.c + * + * Copyright 2016-2022 Nathael Pajani + * + * + * 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 . + * + *************************************************************************** */ + +#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) +{ +} + + + diff --git a/tests_elec/comm.h b/tests_elec/comm.h new file mode 100644 index 0000000..16a4cf0 --- /dev/null +++ b/tests_elec/comm.h @@ -0,0 +1,35 @@ +/**************************************************************************** + * apps/scialys/test_elec/comm.h + * + * Copyright 2016-2022 Nathael Pajani + * + * + * 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 . + * + *************************************************************************** */ + +#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 */ + diff --git a/tests_elec/config.c b/tests_elec/config.c new file mode 100644 index 0000000..36cd09d --- /dev/null +++ b/tests_elec/config.c @@ -0,0 +1,561 @@ +/**************************************************************************** + * apps/scialys/test_elec/config.c + * + * Copyright 2016-2021 Nathael Pajani + * + * + * 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 . + * + *************************************************************************** */ + +#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(); +} + diff --git a/tests_elec/config.h b/tests_elec/config.h new file mode 100644 index 0000000..8b9108c --- /dev/null +++ b/tests_elec/config.h @@ -0,0 +1,227 @@ +/**************************************************************************** + * apps/scialys/test_elec/config.h + * + * Copyright 2016-2021 Nathael Pajani + * + * + * 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 . + * + *************************************************************************** */ + +#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 */ + diff --git a/tests_elec/interface.c b/tests_elec/interface.c new file mode 100644 index 0000000..089941e --- /dev/null +++ b/tests_elec/interface.c @@ -0,0 +1,282 @@ +/**************************************************************************** + * apps/scialys/test_elec/interface.c + * + * Copyright 2016-2021 Nathael Pajani + * + * + * 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 . + * + *************************************************************************** */ + +#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); +} + + diff --git a/tests_elec/interface.h b/tests_elec/interface.h new file mode 100644 index 0000000..bd6c40e --- /dev/null +++ b/tests_elec/interface.h @@ -0,0 +1,84 @@ +/**************************************************************************** + * apps/scialys/test_elec/interface.h + * + * Copyright 2016-2021 Nathael Pajani + * + * + * 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 . + * + *************************************************************************** */ + +#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 */ + diff --git a/tests_elec/main.c b/tests_elec/main.c new file mode 100644 index 0000000..cdf3b49 --- /dev/null +++ b/tests_elec/main.c @@ -0,0 +1,492 @@ +/**************************************************************************** + * apps/scialys/test_elec/main.c + * + * Scialys system for solar-panel power generation tracking and fair use. + * + * Copyright 2016-2021 Nathael Pajani + * + * + * 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 . + * + *************************************************************************** */ + + +#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; +} + + + diff --git a/tests_elec/param.c b/tests_elec/param.c new file mode 100644 index 0000000..5b6fbd1 --- /dev/null +++ b/tests_elec/param.c @@ -0,0 +1,768 @@ +/**************************************************************************** + * apps/scialys/tests_elec/param.c + * + * Copyright 2016-2022 Nathael Pajani + * + * + * 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 . + * + *************************************************************************** */ + +#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; +} + + diff --git a/tests_elec/param.h b/tests_elec/param.h new file mode 100644 index 0000000..b9936e4 --- /dev/null +++ b/tests_elec/param.h @@ -0,0 +1,35 @@ +/**************************************************************************** + * apps/scialys/test_elec/param.h + * + * Copyright 2016-2022 Nathael Pajani + * + * + * 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 . + * + *************************************************************************** */ + +#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 */ + diff --git a/tests_elec/power_delay.c b/tests_elec/power_delay.c new file mode 100644 index 0000000..fdbe3b0 --- /dev/null +++ b/tests_elec/power_delay.c @@ -0,0 +1,105 @@ +#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, +}; diff --git a/tests_elec/sin.pl b/tests_elec/sin.pl new file mode 100755 index 0000000..063ef81 --- /dev/null +++ b/tests_elec/sin.pl @@ -0,0 +1,34 @@ +#! /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"; + + diff --git a/tests_elec/time.c b/tests_elec/time.c new file mode 100644 index 0000000..61aaeef --- /dev/null +++ b/tests_elec/time.c @@ -0,0 +1,98 @@ +/**************************************************************************** + * apps/scialys/test_elec/time.c + * + * Copyright 2016-2021 Nathael Pajani + * + * + * 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 . + * + *************************************************************************** */ + +#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; +} + diff --git a/tests_elec/time.h b/tests_elec/time.h new file mode 100644 index 0000000..e0064b0 --- /dev/null +++ b/tests_elec/time.h @@ -0,0 +1,34 @@ +/**************************************************************************** + * apps/scialys/test_elec/time.h + * + * Copyright 2016-2021 Nathael Pajani + * + * + * 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 . + * + *************************************************************************** */ + +#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 */ + diff --git a/tests_elec/uSD.c b/tests_elec/uSD.c new file mode 100644 index 0000000..3a4550e --- /dev/null +++ b/tests_elec/uSD.c @@ -0,0 +1,245 @@ +/**************************************************************************** + * apps/scialys/test_elec/uSD.c + * + * Copyright 2016-2021 Nathael Pajani + * + * + * 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 . + * + *************************************************************************** */ + +#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; +} + + diff --git a/tests_elec/uSD.h b/tests_elec/uSD.h new file mode 100644 index 0000000..aa81390 --- /dev/null +++ b/tests_elec/uSD.h @@ -0,0 +1,91 @@ +/**************************************************************************** + * apps/scialys/test_elec/usSD.h + * + * Copyright 2016-2021 Nathael Pajani + * + * + * 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 . + * + *************************************************************************** */ + +#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 */ + diff --git a/tests_elec/version.h b/tests_elec/version.h new file mode 100644 index 0000000..b4df198 --- /dev/null +++ b/tests_elec/version.h @@ -0,0 +1,3 @@ +#define MODULE_VERSION_STR "v0.10.1" +#define SOFT_VERSION_STR "Telec" +#define COMPILE_VERSION "01"