diff --git a/opt/tools b/opt/tools index 1ba0329..565c8b7 160000 --- a/opt/tools +++ b/opt/tools @@ -1 +1 @@ -Subproject commit 1ba0329c9967b3732b4c927534c5d7d73bb07895 +Subproject commit 565c8b74569b900b3de35cc5ecf16d4789525881 diff --git a/src/common.h b/src/common.h index 18412db..85e3556 100644 --- a/src/common.h +++ b/src/common.h @@ -5,6 +5,7 @@ #include "common/types.h" #include "common/watchdog.h" #include "common/memory.h" +#include "common/parser.h" #include #include diff --git a/src/common/parser.c b/src/common/parser.c new file mode 100644 index 0000000..42fa254 --- /dev/null +++ b/src/common/parser.c @@ -0,0 +1,72 @@ +#include "common.h" + +#include +#include +#include + +#define CMD_MAX_LEN 128 + +// TODO: Write documentation. +// TODO: Implement start and stop commands. +// TODO: Reset command buffer on timeout? + +static char cmdbuf[CMD_MAX_LEN + 1]; +static char *tail = cmdbuf + CMD_MAX_LEN - 1; +static char *head = cmdbuf; + +static bool ParseLine(cmd_t *out); + +bool CMD_Parse(char ch, cmd_t *out) +{ + bool is_valid; + + if (ch == '\n' || ch == '\r') { + is_valid = ParseLine(out); + tail = cmdbuf + CMD_MAX_LEN - 1; + head = cmdbuf; + return is_valid; + } + + if (head < tail) { + *head++ = ch; + *head = '\0'; + } + + return false; +} + +static bool ParseLine(cmd_t *out) +{ + head = cmdbuf; + + // Command SET + if (tolower(head[0] == 's')) { + if ((tolower(head[1]) != 'e') || + (tolower(head[2]) != 't')) { + return false; + } + + out->type = T_SET; + head += 3; + + // Parse arguments + for (int i = 0; i < 2; i++) { + errno = 0; + + // Try converting string to floating point + // Skips whitespace and moves tail pointer + out->args[i] = (float) strtod(head, &tail); + + if ((errno == ERANGE) || + (out->args[i] == 0 && head == tail)) { + return false; // Conversion failed + } + + head = tail; + } + + return true; + } + + return false; +} diff --git a/src/common/parser.h b/src/common/parser.h new file mode 100644 index 0000000..eafef2c --- /dev/null +++ b/src/common/parser.h @@ -0,0 +1,18 @@ +#ifndef MAD_CORE_COMMON_PARSER +#define MAD_CORE_COMMON_PARSER + +#include "common/types.h" + +// Command types +#define T_SET 0 + +struct cmd_s { + int type; + float args[2]; +}; + +typedef struct cmd_s cmd_t; + +bool CMD_Parse(char ch, cmd_t *out); + +#endif // MAD_CORE_COMMON_PARSER diff --git a/src/common/watchdog.h b/src/common/watchdog.h index c65856b..d23b7eb 100644 --- a/src/common/watchdog.h +++ b/src/common/watchdog.h @@ -3,13 +3,15 @@ #include "common/types.h" -// Timeout Flags -#define WDT2000 0x7 // 2000 ms -#define WDT1000 0x6 // 1000 ms -#define WDT500 0x5 // 500 ms -#define WDT250 0x4 // 250 ms -#define WDT100 0x3 // 100 ms -// XXX: WDT... +// Timeout flags +#define WDT2000 0x7 // 2000 ms +#define WDT1000 0x6 // 1000 ms +#define WDT500 0x5 // 500 ms +#define WDT250 0x4 // 250 ms +#define WDT100 0x3 // 100 ms +#define WDT64 0x2 // 64 ms +#define WDT32 0x1 // 32 ms +#define WDT16 0x0 // 16 ms void WDT_Enable(void); void WDT_SetTimeoutFlag(byte flag); diff --git a/src/main.c b/src/main.c index e9abfbf..be70ec0 100644 --- a/src/main.c +++ b/src/main.c @@ -7,14 +7,13 @@ #include // TODO: Config header for chip specifics like EEPROM size. -// TODO: Make sure wear leveling resets memory correctly. +// TODO: Fix huge serial command input triggering watchdog. +// TODO: Only update EEPROM if value differs from previous. +// TODO: Check thermistor conversion results /w thermometer. // 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: Implement optional CRC8 sensor measurement check. // TODO: Proper error handling and recovery (after testing). // TODO: Check why the MCUCSR EXTRF reset flag is set. @@ -27,8 +26,13 @@ enum state_e }; static enum state_e state; -static word temp, temp_target; -static word dewp, dewp_target; +static float temp, temp_target; +static float dewp, dewp_target; +static float relh; + +static int Init(void); +static void Update(void); +static void UpdateSensors(void); static int Init(void) { @@ -82,8 +86,11 @@ static int Init(void) if (MEM_Read(&mem) == 0) { Info("Found persistent configuration in EEPROM!"); - Info("Setting targets TEMP='%.2f', DEWP='%.2f'.", + Info("Setting targets TEMP='%.2fC', DEWP='%.2fC'.", mem.temp, mem.dewp); + + temp_target = mem.temp; + dewp_target = mem.dewp; } // There is a possiblity to use interrupt signals @@ -131,9 +138,47 @@ static int Init(void) static void Update(void) { - float t[6], rh[3], dp[3]; - float t_avg, rh_avg, dp_avg; + char ch; + cmd_t cmd; + mem_block_t mem; + + // Parse serial commands + while ((ch = USART_Getc()) >= 0) { + if (!CMD_Parse(ch, &cmd)) { + WDT_Reset(); + continue; + } + switch(cmd.type) { + case T_SET: // Configure new persistent target + Info("Setting new target TEMP='%.2fC'...", cmd.args[0]); + Info("Setting new target DEWP='%.2fC'...", cmd.args[1]); + mem.temp = temp_target = cmd.args[0]; + mem.dewp = dewp_target = cmd.args[1]; + MEM_Write(&mem); + return; + } + } + + // Poll sensor values + UpdateSensors(); + + // Handle state + switch (state) { + case S_IDLE: + break; + case S_HEAT_UP: + break; + case S_COOL_DOWN: + break; + case S_DEHUMIDIFY: + break; + } +} + +static void UpdateSensors(void) +{ word raw; + float t[6], rh[3], dp[3]; Info("Reading sensor values..."); @@ -159,33 +204,14 @@ static void Update(void) 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); + temp = (t[0] + t[1] + t[2]) / 3; + relh = (rh[0] + rh[1] + rh[2]) / 3; + dewp = Dewpoint(temp, relh); - 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); + Info("T1=%.2fC, RH1=%.2f%%, NT1=%.2fC, DP1=%.2fC", t[0], rh[0], t[3], dp[0]); + Info("T2=%.2fC, RH2=%.2f%%, NT2=%.2fC, DP2=%.2fC", t[1], rh[1], t[4], dp[1]); + Info("T3=%.2fC, RH3=%.2f%%, NT3=%.2fC, DP3=%.2fC", t[2], rh[2], t[5], dp[2]); + Info("T_AVG=%.2fC, RH_AVG=%.2f%%, DP_AVG=%.2fC", temp, relh, dewp); } int main(void)