#include "common.h" #include "bus/usart.h" #include "bus/mosfet.h" #include "bus/pwm.h" #include "bus/i2c.h" #include // 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; }