2021-06-05 00:04:36 +02:00
|
|
|
#include <libopencm3/stm32/rcc.h>
|
|
|
|
#include <libopencm3/stm32/adc.h>
|
|
|
|
#include <libopencm3/stm32/gpio.h>
|
|
|
|
#include <libopencm3/stm32/dma.h>
|
|
|
|
#include <libopencm3/stm32/timer.h>
|
|
|
|
#include <libopencm3/stm32/rtc.h>
|
|
|
|
#include <libopencm3/stm32/pwr.h>
|
|
|
|
#include <libopencm3/stm32/exti.h>
|
|
|
|
#include <libopencm3/cm3/nvic.h>
|
|
|
|
#include <libopencm3/cm3/systick.h>
|
|
|
|
|
|
|
|
#include <libopencmsis/core_cm3.h>
|
|
|
|
|
|
|
|
#include <fxp.h>
|
|
|
|
#include <fxp_basic.h>
|
|
|
|
|
|
|
|
#include "led_chplex.h"
|
2021-06-06 12:16:46 +02:00
|
|
|
#include "rs485.h"
|
2021-06-05 15:49:29 +02:00
|
|
|
#include "charge_pump.h"
|
2021-06-05 16:33:21 +02:00
|
|
|
#include "power_switch.h"
|
2021-06-06 12:17:38 +02:00
|
|
|
#include "measurement.h"
|
2021-06-05 00:04:36 +02:00
|
|
|
|
|
|
|
volatile int wait_frame = 1;
|
|
|
|
|
|
|
|
|
|
|
|
static void init_clock(void)
|
|
|
|
{
|
|
|
|
/* Set STM32 to 48 MHz. */
|
|
|
|
rcc_clock_setup_in_hse_8mhz_out_48mhz(); // generate 48 MHz from external 8 MHz crystal
|
|
|
|
//rcc_clock_setup_in_hsi_out_48mhz(); // generate ~48 MHz from internal RC oscillator
|
|
|
|
|
2021-06-05 15:49:29 +02:00
|
|
|
// enable TIM1 for PWM generation
|
|
|
|
rcc_periph_clock_enable(RCC_TIM1);
|
|
|
|
|
2021-06-05 00:04:36 +02:00
|
|
|
// enable GPIO clocks:
|
2021-06-05 15:49:29 +02:00
|
|
|
// Port A is needed for the charge pump, extension port and analog input
|
|
|
|
rcc_periph_clock_enable(RCC_GPIOA);
|
|
|
|
|
2021-06-05 00:04:36 +02:00
|
|
|
// Port B is needed for the LEDs
|
|
|
|
rcc_periph_clock_enable(RCC_GPIOB);
|
2021-06-06 12:16:46 +02:00
|
|
|
|
|
|
|
// USART1 is used for RS485
|
|
|
|
rcc_periph_clock_enable(RCC_USART1);
|
2021-06-06 12:17:38 +02:00
|
|
|
|
|
|
|
// ADC1 for analog measuremnts
|
|
|
|
rcc_periph_clock_enable(RCC_ADC1);
|
|
|
|
|
|
|
|
// DMA1 is used for ADC data transfer
|
|
|
|
rcc_periph_clock_enable(RCC_DMA1);
|
2021-06-05 00:04:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Set up systick to fire freq times per second */
|
|
|
|
static void init_systick(int freq)
|
|
|
|
{
|
|
|
|
systick_set_clocksource(STK_CSR_CLKSOURCE_AHB);
|
|
|
|
/* clear counter so it starts right away */
|
|
|
|
STK_CVR = 0;
|
|
|
|
|
|
|
|
systick_set_reload(rcc_ahb_frequency / freq);
|
|
|
|
systick_counter_enable();
|
|
|
|
systick_interrupt_enable();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static bool ledtest(uint64_t timebase_ms)
|
|
|
|
{
|
|
|
|
if(timebase_ms == 0) {
|
|
|
|
led_chplex_mask(0x3F); // all on
|
|
|
|
} else if(timebase_ms == 1000) {
|
|
|
|
led_chplex_mask(0x01);
|
|
|
|
} else if(timebase_ms == 1200) {
|
|
|
|
led_chplex_mask(0x02);
|
|
|
|
} else if(timebase_ms == 1400) {
|
|
|
|
led_chplex_mask(0x04);
|
|
|
|
} else if(timebase_ms == 1600) {
|
|
|
|
led_chplex_mask(0x08);
|
|
|
|
} else if(timebase_ms == 1800) {
|
|
|
|
led_chplex_mask(0x10);
|
|
|
|
} else if(timebase_ms == 2000) {
|
|
|
|
led_chplex_mask(0x20);
|
|
|
|
} else if(timebase_ms == 2200) {
|
|
|
|
led_chplex_mask(0x10);
|
|
|
|
} else if(timebase_ms == 2400) {
|
|
|
|
led_chplex_mask(0x08);
|
|
|
|
} else if(timebase_ms == 2600) {
|
|
|
|
led_chplex_mask(0x04);
|
|
|
|
} else if(timebase_ms == 2800) {
|
|
|
|
led_chplex_mask(0x02);
|
|
|
|
} else if(timebase_ms == 3000) {
|
|
|
|
led_chplex_mask(0x01);
|
|
|
|
} else if(timebase_ms == 3200) {
|
|
|
|
led_chplex_mask(0x00);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-06-06 12:17:38 +02:00
|
|
|
static void report_status(struct MeasurementResult *meas_data)
|
|
|
|
{
|
|
|
|
char number[FXP_STR_MAXLEN];
|
|
|
|
|
|
|
|
rs485_enqueue("MEAS:");
|
|
|
|
|
|
|
|
fxp_format(meas_data->u_bat, number, 3);
|
|
|
|
rs485_enqueue(number);
|
|
|
|
rs485_enqueue(":");
|
|
|
|
|
|
|
|
fxp_format(meas_data->u_solar, number, 3);
|
|
|
|
rs485_enqueue(number);
|
|
|
|
rs485_enqueue(":");
|
|
|
|
|
|
|
|
fxp_format(meas_data->u_sw, number, 3);
|
|
|
|
rs485_enqueue(number);
|
|
|
|
rs485_enqueue(":");
|
|
|
|
|
|
|
|
fxp_format(meas_data->i_solar, number, 3);
|
|
|
|
rs485_enqueue(number);
|
|
|
|
rs485_enqueue(":");
|
|
|
|
|
|
|
|
fxp_format(meas_data->i_load, number, 3);
|
|
|
|
rs485_enqueue(number);
|
|
|
|
rs485_enqueue(":");
|
|
|
|
|
|
|
|
fxp_format(meas_data->temperature, number, 2);
|
|
|
|
rs485_enqueue(number);
|
|
|
|
rs485_enqueue("\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-06-05 00:04:36 +02:00
|
|
|
int main(void)
|
|
|
|
{
|
|
|
|
//uint32_t cpuload = 0;
|
|
|
|
uint64_t timebase_ms = 0;
|
|
|
|
bool ledtest_done = false;
|
2021-06-05 15:49:29 +02:00
|
|
|
bool startup_done = false;
|
2021-06-05 00:04:36 +02:00
|
|
|
|
2021-06-05 16:33:21 +02:00
|
|
|
uint8_t switchtest = 0; // FIXME: delme: just for testing
|
|
|
|
|
2021-06-06 12:17:38 +02:00
|
|
|
struct MeasurementResult meas_data;
|
|
|
|
|
2021-06-05 00:04:36 +02:00
|
|
|
init_clock();
|
|
|
|
|
2021-06-06 12:16:46 +02:00
|
|
|
rs485_init();
|
2021-06-05 15:49:29 +02:00
|
|
|
charge_pump_init();
|
2021-06-05 16:33:21 +02:00
|
|
|
power_switch_init();
|
2021-06-06 12:17:38 +02:00
|
|
|
measurement_init();
|
2021-06-05 15:49:29 +02:00
|
|
|
|
2021-06-05 00:04:36 +02:00
|
|
|
led_chplex_init();
|
|
|
|
led_chplex_on(LED_CHPLEX_IDX_SOLAR_ON);
|
|
|
|
|
|
|
|
init_systick(1000);
|
|
|
|
|
2021-06-06 12:16:46 +02:00
|
|
|
rs485_enqueue("LNSC-2420 v" VERSION " initialized.\n");
|
|
|
|
|
2021-06-05 00:04:36 +02:00
|
|
|
// triggered every 1 ms
|
|
|
|
while (1) {
|
|
|
|
|
|
|
|
if(!ledtest_done) {
|
|
|
|
ledtest_done = ledtest(timebase_ms);
|
2021-06-06 12:17:38 +02:00
|
|
|
led_chplex_periodic();
|
2021-06-05 15:49:29 +02:00
|
|
|
} else if(!startup_done) {
|
|
|
|
charge_pump_start();
|
2021-06-05 16:33:21 +02:00
|
|
|
power_switch_load_on(); // FIXME: just for testing!
|
|
|
|
|
2021-06-05 15:49:29 +02:00
|
|
|
startup_done = true;
|
2021-06-05 16:33:21 +02:00
|
|
|
} else {
|
2021-06-06 12:17:38 +02:00
|
|
|
measurement_start();
|
|
|
|
|
|
|
|
// measurement takes some time, so do other things before waiting for
|
|
|
|
// completion. This is a good place for tasks that are not critical in
|
|
|
|
// latency, such as updating the LEDs, sending the state over RS485 etc.
|
|
|
|
|
2021-06-05 16:33:21 +02:00
|
|
|
if(timebase_ms % 500 == 0) {
|
|
|
|
led_chplex_toggle(LED_CHPLEX_IDX_DISCHARGE_PULSE);
|
|
|
|
}
|
|
|
|
|
|
|
|
// FIXME: just for testing
|
2021-06-06 12:17:38 +02:00
|
|
|
if(timebase_ms % 10000 == 0) {
|
2021-06-05 16:33:21 +02:00
|
|
|
switchtest++;
|
|
|
|
|
|
|
|
if(switchtest & 0x01) {
|
|
|
|
power_switch_solar_on();
|
|
|
|
led_chplex_on(LED_CHPLEX_IDX_SOLAR_ON);
|
|
|
|
} else {
|
|
|
|
power_switch_solar_off();
|
|
|
|
led_chplex_off(LED_CHPLEX_IDX_SOLAR_ON);
|
|
|
|
}
|
|
|
|
|
|
|
|
if(switchtest & 0x02) {
|
|
|
|
power_switch_load_on();
|
|
|
|
led_chplex_on(LED_CHPLEX_IDX_LOAD_ON);
|
|
|
|
} else {
|
|
|
|
power_switch_load_off();
|
|
|
|
led_chplex_off(LED_CHPLEX_IDX_LOAD_ON);
|
|
|
|
}
|
|
|
|
}
|
2021-06-05 00:04:36 +02:00
|
|
|
|
2021-06-06 12:17:38 +02:00
|
|
|
led_chplex_periodic();
|
|
|
|
|
|
|
|
// Send the status data from the last cycle.
|
|
|
|
if(timebase_ms % 1000 == 0) {
|
|
|
|
report_status(&meas_data);
|
|
|
|
}
|
|
|
|
|
|
|
|
measurement_wait_for_completion();
|
|
|
|
if(timebase_ms % 1000 == 100) {
|
|
|
|
measurement_finalize(&meas_data);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check the protections directly after the measurement finishes. This
|
|
|
|
// ensures fast reaction time.
|
|
|
|
// TODO: check_protections(&meas_data);
|
|
|
|
}
|
2021-06-05 00:04:36 +02:00
|
|
|
|
|
|
|
timebase_ms++;
|
|
|
|
|
|
|
|
while(wait_frame) {
|
|
|
|
__WFI();
|
|
|
|
}
|
|
|
|
|
|
|
|
wait_frame = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Called when systick fires */
|
|
|
|
void sys_tick_handler(void)
|
|
|
|
{
|
|
|
|
wait_frame = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void hard_fault_handler(void)
|
|
|
|
{
|
|
|
|
while (1);
|
|
|
|
}
|