Files
drybox-core/src/main.c

205 lines
4.9 KiB
C

#include "common.h"
#include "bus/usart.h"
#include "bus/mosfet.h"
#include "bus/pwm.h"
#include "bus/i2c.h"
#include <avr/interrupt.h>
// TODO: Config header for chip specifics like EEPROM size.
// TODO: Make sure wear leveling resets memory correctly.
// TODO: Implement primary state machine for update loop.
// TODO: Migrate to ATMega 1284P-PU for 2nd 16-bit timer.
// TODO: Check thermistor conversion results /w thermometer.
// TODO: Implement optional CRC8 sensor measurement check.
// TODO: Use 18.432MHz quarz crystal, burn required fuses.
// TODO: Make sure nothing breaks with negative temperatures.
// TODO: Write an improved command parser (low priority).
// TODO: Proper error handling and recovery (after testing).
// TODO: Check why the MCUCSR EXTRF reset flag is set.
enum state_e
{
S_IDLE,
S_HEAT_UP,
S_COOL_DOWN,
S_DEHUMIDIFY
};
static enum state_e state;
static word temp, temp_target;
static word dewp, dewp_target;
static int Init(void)
{
mem_block_t mem;
state = S_IDLE;
// MOSFETS control things like the heating element
// so they are the highest priority to initialize
// to a default state via MOS_Init().
MOS_Init();
// The serial interface is required for output
// functions like Info() and Error() and it uses
// IRQs, so we need to initialize it as soon as
// possible and make sure to enable interrupts.
USART_Init();
sei();
Info("Initializing...");
// The watchdog timer is clocked from a separate
// on-chip oscillator which runs at 1 MHz. Eight
// different clock cycle periods can be selected
// to determine the reset period. If the reset
// period expires, the chip resets and executes
// from the reset vector.
// The update loop must call WDT_Reset to reset
// the timer, kind of like a dead man's switch
// allowing us to detect infinite loops and any
// other error that halts execution.
if (WDT_HasTriggered())
Info("Unexpected system reset.");
WDT_Enable();
WDT_SetTimeoutFlag(WDT2000); // 2 seconds
// Test persistent settings from EEPROM. There must
// be some sanity checking before these values are
// used.
MEM_Init();
// mem.temp = 30.50f;
// mem.dewp = 15.25f;
// MEM_Write(&mem);
// MEM_Free();
if (MEM_Read(&mem) == 0) {
Info("Found persistent configuration in EEPROM!");
Info("Setting targets TEMP='%.2f', DEWP='%.2f'.",
mem.temp, mem.dewp);
}
// There is a possiblity to use interrupt signals
// for I2C communication but only as one large
// branching routine for the whole I2C system.
// The blocking approach used right now is fine.
I2C_Init();
PWM_Init();
MOS_Enable(MOS03); // Lights
// MOS_Enable(MOS01); // Peltier
// MOS_Disable(MOS02); // Heating
// Only FAN01 and FAN02 are receiving the correct
// frequency (25 KHz) right now. The 16-bit timer on
// the ATMega32A has two outputs so it would require
// software PWM to have a variable frequency on PD7.
// A simple implementation will take up around 30-50
// percent of CPU time. Faster approaches are quite
// complicated so it might be worth it to switch to
// something like an ATmega328PB.
PWM_SetValue(FAN01, 50); // Fan Peltier Hot side
PWM_SetValue(FAN02, 50); // Fan Peltier Cold Side
// PWM_SetValue(FAN03, 20); // Fan Heating
// The I2C_SetChannel command changes the channel
// setting of the PCA9546 I2C multiplexer. Any
// command after it will be sent to the device
// listening on that channel.
I2C_SetChannel(AHT01);
I2C_AHT20_Init();
I2C_SetChannel(AHT02);
I2C_AHT20_Init();
I2C_SetChannel(AHT03);
I2C_AHT20_Init();
return 0;
}
static void Update(void)
{
float t[6], rh[3], dp[3];
float t_avg, rh_avg, dp_avg;
word raw;
Info("Reading sensor values...");
I2C_SetChannel(AHT01);
I2C_AHT20_Read(&t[0], &rh[0]);
I2C_SetChannel(AHT02);
I2C_AHT20_Read(&t[1], &rh[1]);
I2C_SetChannel(AHT03);
I2C_AHT20_Read(&t[2], &rh[2]);
raw = I2C_ADS1115_ReadRaw(ADS01);
t[3] = SteinhartHart(Resistance(raw));
raw = I2C_ADS1115_ReadRaw(ADS02);
t[4] = SteinhartHart(Resistance(raw));
raw = I2C_ADS1115_ReadRaw(ADS03);
t[5] = SteinhartHart(Resistance(raw));
dp[0] = Dewpoint(t[0], rh[0]);
dp[1] = Dewpoint(t[1], rh[1]);
dp[2] = Dewpoint(t[2], rh[2]);
t_avg = (t[0] + t[1] + t[2]) / 3;
rh_avg = (rh[0] + rh[1] + rh[2]) / 3;
dp_avg = Dewpoint(t_avg, rh_avg);
Info("T1=%.2fC, RH1=%.2f%%, NT1=%.2fC, DEW1=%.2fC", t[0], rh[0], t[3], dp[0]);
Info("T2=%.2fC, RH2=%.2f%%, NT2=%.2fC, DEW2=%.2fC", t[1], rh[1], t[4], dp[1]);
Info("T3=%.2fC, RH3=%.2f%%, NT3=%.2fC, DEW3=%.2fC", t[2], rh[2], t[5], dp[2]);
Info("T_AVG=%.2fC, RH_AVG=%.2f%%, DP_AVG=%.2fC", t_avg, rh_avg, dp_avg);
// TODO: Implement state machine
// TODO: Handle serial commands
switch (state) {
case S_IDLE:
break;
case S_HEAT_UP:
break;
case S_COOL_DOWN:
break;
case S_DEHUMIDIFY:
break;
}
UNUSED(temp);
UNUSED(dewp);
UNUSED(temp_target);
UNUSED(dewp_target);
}
int main(void)
{
Init();
for (;;) {
WDT_Reset();
Update();
WDT_Reset();
Sleep(1000);
}
return 0;
}