Compare commits

..

9 Commits

10 changed files with 254 additions and 146 deletions

View File

@@ -9,7 +9,7 @@ namespace {
typedef void (*jmp_fn)() __attribute__((noreturn)); typedef void (*jmp_fn)() __attribute__((noreturn));
jmp_fn boot = reinterpret_cast<jmp_fn>(0x0000); jmp_fn boot = reinterpret_cast<jmp_fn>(0x0000);
jmp_fn bootloader = reinterpret_cast<jmp_fn>(0x7E00 / 2); jmp_fn bootloader = reinterpret_cast<jmp_fn>(0x7800 / 2);
} // namespace } // namespace
@@ -31,7 +31,7 @@ void Bootloader::reset()
bool Bootloader::check() bool Bootloader::check()
{ {
if (pgm_read_byte(reinterpret_cast<uint16_t>(bootloader) * 2) == 0xF8) if (pgm_read_byte(reinterpret_cast<uint16_t>(bootloader) * 2) != 0xFF)
return true; return true;
return false; return false;

View File

@@ -1,6 +1,6 @@
#pragma once #pragma once
#define F_CPU 16000000 #define F_CPU 16'000'000
#include <util/delay.h> #include <util/delay.h>
#include <stdint.h> #include <stdint.h>

View File

@@ -8,6 +8,7 @@ double Controller::m_resistance;
double Controller::m_temperature; double Controller::m_temperature;
uint8_t Controller::m_fanSpeed; uint8_t Controller::m_fanSpeed;
bool Controller::m_dataAvailable = false; bool Controller::m_dataAvailable = false;
bool Controller::m_autoMode = true;
volatile uint32_t Controller::m_adcSampleSum; volatile uint32_t Controller::m_adcSampleSum;
volatile bool Controller::m_adcSampleReady = false; volatile bool Controller::m_adcSampleReady = false;
@@ -28,7 +29,9 @@ void Controller::callback()
m_resistance = m_thermistor.getResistance(m_adcSample); m_resistance = m_thermistor.getResistance(m_adcSample);
m_temperature = m_thermistor.getTemperature(m_resistance); m_temperature = m_thermistor.getTemperature(m_resistance);
m_fanSpeed = mapTemperature(m_temperature);
if (m_autoMode)
m_fanSpeed = mapTemperature(m_temperature);
pwm::setDuty(m_fanSpeed); pwm::setDuty(m_fanSpeed);
} }

View File

@@ -13,6 +13,7 @@ class Controller {
static double m_temperature; static double m_temperature;
static uint8_t m_fanSpeed; static uint8_t m_fanSpeed;
static bool m_dataAvailable; static bool m_dataAvailable;
static bool m_autoMode;
static void init(); static void init();

View File

@@ -81,126 +81,137 @@
</ToolNumber> </ToolNumber>
<ToolName>Custom Programming Tool</ToolName> <ToolName>Custom Programming Tool</ToolName>
</custom> </custom>
<AAFDebugger>
<AAFDebugFiles>
<DebugFile>
<path>\Debug\fantemp.lss</path>
<AAFSetting>
<Label>Lss Files</Label>
<Extention>.lss</Extention>
<Regex>^\s*(?&lt;address&gt;[a-f0-9]*):\s*.*$</Regex>
<DebugEnabled>true</DebugEnabled>
<RegexGroups>address</RegexGroups>
<DebuggerExpression>$pc</DebuggerExpression>
</AAFSetting>
</DebugFile>
</AAFDebugFiles>
</AAFDebugger>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' "> <PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<ToolchainSettings> <ToolchainSettings>
<AvrGccCpp> <AvrGccCpp>
<avrgcc.common.Device>-mmcu=atmega328p</avrgcc.common.Device> <avrgcc.common.Device>-mmcu=atmega328p</avrgcc.common.Device>
<avrgcc.common.outputfiles.hex>True</avrgcc.common.outputfiles.hex> <avrgcc.common.outputfiles.hex>True</avrgcc.common.outputfiles.hex>
<avrgcc.common.outputfiles.lss>True</avrgcc.common.outputfiles.lss> <avrgcc.common.outputfiles.lss>True</avrgcc.common.outputfiles.lss>
<avrgcc.common.outputfiles.eep>True</avrgcc.common.outputfiles.eep> <avrgcc.common.outputfiles.eep>True</avrgcc.common.outputfiles.eep>
<avrgcc.common.outputfiles.srec>True</avrgcc.common.outputfiles.srec> <avrgcc.common.outputfiles.srec>True</avrgcc.common.outputfiles.srec>
<avrgcc.common.outputfiles.usersignatures>False</avrgcc.common.outputfiles.usersignatures> <avrgcc.common.outputfiles.usersignatures>False</avrgcc.common.outputfiles.usersignatures>
<avrgcc.compiler.general.ChangeDefaultCharTypeUnsigned>True</avrgcc.compiler.general.ChangeDefaultCharTypeUnsigned> <avrgcc.compiler.general.ChangeDefaultCharTypeUnsigned>True</avrgcc.compiler.general.ChangeDefaultCharTypeUnsigned>
<avrgcc.compiler.general.ChangeDefaultBitFieldUnsigned>True</avrgcc.compiler.general.ChangeDefaultBitFieldUnsigned> <avrgcc.compiler.general.ChangeDefaultBitFieldUnsigned>True</avrgcc.compiler.general.ChangeDefaultBitFieldUnsigned>
<avrgcc.compiler.symbols.DefSymbols> <avrgcc.compiler.symbols.DefSymbols>
<ListValues> <ListValues>
<Value>NDEBUG</Value> <Value>NDEBUG</Value>
</ListValues> </ListValues>
</avrgcc.compiler.symbols.DefSymbols> </avrgcc.compiler.symbols.DefSymbols>
<avrgcc.compiler.directories.IncludePaths> <avrgcc.compiler.directories.IncludePaths>
<ListValues> <ListValues>
<Value>%24(PackRepoDir)\Atmel\ATmega_DFP\1.4.346\include</Value> <Value>%24(PackRepoDir)\Atmel\ATmega_DFP\1.4.346\include</Value>
</ListValues> </ListValues>
</avrgcc.compiler.directories.IncludePaths> </avrgcc.compiler.directories.IncludePaths>
<avrgcc.compiler.optimization.level>Optimize for size (-Os)</avrgcc.compiler.optimization.level> <avrgcc.compiler.optimization.level>Optimize for size (-Os)</avrgcc.compiler.optimization.level>
<avrgcc.compiler.optimization.AllocateBytesNeededForEnum>True</avrgcc.compiler.optimization.AllocateBytesNeededForEnum> <avrgcc.compiler.optimization.AllocateBytesNeededForEnum>True</avrgcc.compiler.optimization.AllocateBytesNeededForEnum>
<avrgcc.compiler.warnings.AllWarnings>True</avrgcc.compiler.warnings.AllWarnings> <avrgcc.compiler.warnings.AllWarnings>True</avrgcc.compiler.warnings.AllWarnings>
<avrgcc.compiler.warnings.ExtraWarnings>True</avrgcc.compiler.warnings.ExtraWarnings> <avrgcc.compiler.warnings.ExtraWarnings>True</avrgcc.compiler.warnings.ExtraWarnings>
<avrgcc.compiler.warnings.Pedantic>True</avrgcc.compiler.warnings.Pedantic> <avrgcc.compiler.warnings.Pedantic>True</avrgcc.compiler.warnings.Pedantic>
<avrgcc.compiler.miscellaneous.OtherFlags>-fno-threadsafe-statics -std=c11</avrgcc.compiler.miscellaneous.OtherFlags> <avrgcc.compiler.miscellaneous.OtherFlags>-fno-threadsafe-statics -std=c11</avrgcc.compiler.miscellaneous.OtherFlags>
<avrgcccpp.compiler.general.ChangeDefaultCharTypeUnsigned>True</avrgcccpp.compiler.general.ChangeDefaultCharTypeUnsigned> <avrgcccpp.compiler.general.ChangeDefaultCharTypeUnsigned>True</avrgcccpp.compiler.general.ChangeDefaultCharTypeUnsigned>
<avrgcccpp.compiler.general.ChangeDefaultBitFieldUnsigned>True</avrgcccpp.compiler.general.ChangeDefaultBitFieldUnsigned> <avrgcccpp.compiler.general.ChangeDefaultBitFieldUnsigned>True</avrgcccpp.compiler.general.ChangeDefaultBitFieldUnsigned>
<avrgcccpp.compiler.symbols.DefSymbols> <avrgcccpp.compiler.symbols.DefSymbols>
<ListValues> <ListValues>
<Value>NDEBUG</Value> <Value>NDEBUG</Value>
</ListValues> </ListValues>
</avrgcccpp.compiler.symbols.DefSymbols> </avrgcccpp.compiler.symbols.DefSymbols>
<avrgcccpp.compiler.directories.IncludePaths> <avrgcccpp.compiler.directories.IncludePaths>
<ListValues> <ListValues>
<Value>%24(PackRepoDir)\Atmel\ATmega_DFP\1.4.346\include</Value> <Value>%24(PackRepoDir)\Atmel\ATmega_DFP\1.4.346\include</Value>
</ListValues> </ListValues>
</avrgcccpp.compiler.directories.IncludePaths> </avrgcccpp.compiler.directories.IncludePaths>
<avrgcccpp.compiler.optimization.level>Optimize for size (-Os)</avrgcccpp.compiler.optimization.level> <avrgcccpp.compiler.optimization.level>Optimize for size (-Os)</avrgcccpp.compiler.optimization.level>
<avrgcccpp.compiler.optimization.AllocateBytesNeededForEnum>True</avrgcccpp.compiler.optimization.AllocateBytesNeededForEnum> <avrgcccpp.compiler.optimization.AllocateBytesNeededForEnum>True</avrgcccpp.compiler.optimization.AllocateBytesNeededForEnum>
<avrgcccpp.compiler.warnings.AllWarnings>True</avrgcccpp.compiler.warnings.AllWarnings> <avrgcccpp.compiler.warnings.AllWarnings>True</avrgcccpp.compiler.warnings.AllWarnings>
<avrgcccpp.compiler.warnings.Pedantic>True</avrgcccpp.compiler.warnings.Pedantic> <avrgcccpp.compiler.warnings.Pedantic>True</avrgcccpp.compiler.warnings.Pedantic>
<avrgcccpp.compiler.miscellaneous.OtherFlags>-fno-threadsafe-statics -Wextra -std=c++17</avrgcccpp.compiler.miscellaneous.OtherFlags> <avrgcccpp.compiler.miscellaneous.OtherFlags>-fno-threadsafe-statics -Wextra -std=c++17</avrgcccpp.compiler.miscellaneous.OtherFlags>
<avrgcccpp.linker.general.UseVprintfLibrary>True</avrgcccpp.linker.general.UseVprintfLibrary> <avrgcccpp.linker.libraries.Libraries>
<avrgcccpp.linker.libraries.Libraries> <ListValues>
<ListValues> <Value>libm</Value>
<Value>libm</Value> </ListValues>
<Value>libprintf_flt</Value> </avrgcccpp.linker.libraries.Libraries>
</ListValues> <avrgcccpp.assembler.general.IncludePaths>
</avrgcccpp.linker.libraries.Libraries> <ListValues>
<avrgcccpp.assembler.general.IncludePaths> <Value>%24(PackRepoDir)\Atmel\ATmega_DFP\1.4.346\include</Value>
<ListValues> </ListValues>
<Value>%24(PackRepoDir)\Atmel\ATmega_DFP\1.4.346\include</Value> </avrgcccpp.assembler.general.IncludePaths>
</ListValues> </AvrGccCpp>
</avrgcccpp.assembler.general.IncludePaths>
</AvrGccCpp>
</ToolchainSettings> </ToolchainSettings>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' "> <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<ToolchainSettings> <ToolchainSettings>
<AvrGccCpp> <AvrGccCpp>
<avrgcc.common.Device>-mmcu=atmega328p</avrgcc.common.Device> <avrgcc.common.Device>-mmcu=atmega328p</avrgcc.common.Device>
<avrgcc.common.outputfiles.hex>True</avrgcc.common.outputfiles.hex> <avrgcc.common.outputfiles.hex>True</avrgcc.common.outputfiles.hex>
<avrgcc.common.outputfiles.lss>True</avrgcc.common.outputfiles.lss> <avrgcc.common.outputfiles.lss>True</avrgcc.common.outputfiles.lss>
<avrgcc.common.outputfiles.eep>True</avrgcc.common.outputfiles.eep> <avrgcc.common.outputfiles.eep>True</avrgcc.common.outputfiles.eep>
<avrgcc.common.outputfiles.srec>True</avrgcc.common.outputfiles.srec> <avrgcc.common.outputfiles.srec>True</avrgcc.common.outputfiles.srec>
<avrgcc.common.outputfiles.usersignatures>False</avrgcc.common.outputfiles.usersignatures> <avrgcc.common.outputfiles.usersignatures>False</avrgcc.common.outputfiles.usersignatures>
<avrgcc.compiler.general.ChangeDefaultCharTypeUnsigned>True</avrgcc.compiler.general.ChangeDefaultCharTypeUnsigned> <avrgcc.compiler.general.ChangeDefaultCharTypeUnsigned>True</avrgcc.compiler.general.ChangeDefaultCharTypeUnsigned>
<avrgcc.compiler.general.ChangeDefaultBitFieldUnsigned>True</avrgcc.compiler.general.ChangeDefaultBitFieldUnsigned> <avrgcc.compiler.general.ChangeDefaultBitFieldUnsigned>True</avrgcc.compiler.general.ChangeDefaultBitFieldUnsigned>
<avrgcc.compiler.symbols.DefSymbols> <avrgcc.compiler.symbols.DefSymbols>
<ListValues> <ListValues>
<Value>DEBUG</Value> <Value>DEBUG</Value>
</ListValues> </ListValues>
</avrgcc.compiler.symbols.DefSymbols> </avrgcc.compiler.symbols.DefSymbols>
<avrgcc.compiler.directories.IncludePaths> <avrgcc.compiler.directories.IncludePaths>
<ListValues> <ListValues>
<Value>%24(PackRepoDir)\Atmel\ATmega_DFP\1.4.346\include</Value> <Value>%24(PackRepoDir)\Atmel\ATmega_DFP\1.4.346\include</Value>
</ListValues> </ListValues>
</avrgcc.compiler.directories.IncludePaths> </avrgcc.compiler.directories.IncludePaths>
<avrgcc.compiler.optimization.level>Optimize (-O1)</avrgcc.compiler.optimization.level> <avrgcc.compiler.optimization.level>Optimize (-O1)</avrgcc.compiler.optimization.level>
<avrgcc.compiler.optimization.AllocateBytesNeededForEnum>True</avrgcc.compiler.optimization.AllocateBytesNeededForEnum> <avrgcc.compiler.optimization.AllocateBytesNeededForEnum>True</avrgcc.compiler.optimization.AllocateBytesNeededForEnum>
<avrgcc.compiler.optimization.DebugLevel>Maximum (-g3)</avrgcc.compiler.optimization.DebugLevel> <avrgcc.compiler.optimization.DebugLevel>Maximum (-g3)</avrgcc.compiler.optimization.DebugLevel>
<avrgcc.compiler.warnings.AllWarnings>True</avrgcc.compiler.warnings.AllWarnings> <avrgcc.compiler.warnings.AllWarnings>True</avrgcc.compiler.warnings.AllWarnings>
<avrgcc.compiler.warnings.ExtraWarnings>True</avrgcc.compiler.warnings.ExtraWarnings> <avrgcc.compiler.warnings.ExtraWarnings>True</avrgcc.compiler.warnings.ExtraWarnings>
<avrgcc.compiler.warnings.Pedantic>True</avrgcc.compiler.warnings.Pedantic> <avrgcc.compiler.warnings.Pedantic>True</avrgcc.compiler.warnings.Pedantic>
<avrgcc.compiler.miscellaneous.OtherFlags>-fno-threadsafe-statics -std=c11</avrgcc.compiler.miscellaneous.OtherFlags> <avrgcc.compiler.miscellaneous.OtherFlags>-fno-threadsafe-statics -std=c11</avrgcc.compiler.miscellaneous.OtherFlags>
<avrgcccpp.compiler.general.ChangeDefaultCharTypeUnsigned>True</avrgcccpp.compiler.general.ChangeDefaultCharTypeUnsigned> <avrgcccpp.compiler.general.ChangeDefaultCharTypeUnsigned>True</avrgcccpp.compiler.general.ChangeDefaultCharTypeUnsigned>
<avrgcccpp.compiler.general.ChangeDefaultBitFieldUnsigned>True</avrgcccpp.compiler.general.ChangeDefaultBitFieldUnsigned> <avrgcccpp.compiler.general.ChangeDefaultBitFieldUnsigned>True</avrgcccpp.compiler.general.ChangeDefaultBitFieldUnsigned>
<avrgcccpp.compiler.symbols.DefSymbols> <avrgcccpp.compiler.symbols.DefSymbols>
<ListValues> <ListValues>
<Value>DEBUG</Value> <Value>DEBUG</Value>
</ListValues> </ListValues>
</avrgcccpp.compiler.symbols.DefSymbols> </avrgcccpp.compiler.symbols.DefSymbols>
<avrgcccpp.compiler.directories.IncludePaths> <avrgcccpp.compiler.directories.IncludePaths>
<ListValues> <ListValues>
<Value>%24(PackRepoDir)\Atmel\ATmega_DFP\1.4.346\include</Value> <Value>%24(PackRepoDir)\Atmel\ATmega_DFP\1.4.346\include</Value>
</ListValues> </ListValues>
</avrgcccpp.compiler.directories.IncludePaths> </avrgcccpp.compiler.directories.IncludePaths>
<avrgcccpp.compiler.optimization.level>Optimize (-O1)</avrgcccpp.compiler.optimization.level> <avrgcccpp.compiler.optimization.level>Optimize (-O1)</avrgcccpp.compiler.optimization.level>
<avrgcccpp.compiler.optimization.AllocateBytesNeededForEnum>True</avrgcccpp.compiler.optimization.AllocateBytesNeededForEnum> <avrgcccpp.compiler.optimization.AllocateBytesNeededForEnum>True</avrgcccpp.compiler.optimization.AllocateBytesNeededForEnum>
<avrgcccpp.compiler.optimization.DebugLevel>Maximum (-g3)</avrgcccpp.compiler.optimization.DebugLevel> <avrgcccpp.compiler.optimization.DebugLevel>Maximum (-g3)</avrgcccpp.compiler.optimization.DebugLevel>
<avrgcccpp.compiler.warnings.AllWarnings>True</avrgcccpp.compiler.warnings.AllWarnings> <avrgcccpp.compiler.warnings.AllWarnings>True</avrgcccpp.compiler.warnings.AllWarnings>
<avrgcccpp.compiler.warnings.Pedantic>True</avrgcccpp.compiler.warnings.Pedantic> <avrgcccpp.compiler.warnings.Pedantic>True</avrgcccpp.compiler.warnings.Pedantic>
<avrgcccpp.compiler.miscellaneous.OtherFlags>-fno-threadsafe-statics -Wextra -std=c++17</avrgcccpp.compiler.miscellaneous.OtherFlags> <avrgcccpp.compiler.miscellaneous.OtherFlags>-fno-threadsafe-statics -Wextra -std=c++17</avrgcccpp.compiler.miscellaneous.OtherFlags>
<avrgcccpp.linker.general.UseVprintfLibrary>True</avrgcccpp.linker.general.UseVprintfLibrary> <avrgcccpp.linker.libraries.Libraries>
<avrgcccpp.linker.libraries.Libraries> <ListValues>
<ListValues> <Value>libm</Value>
<Value>libm</Value> </ListValues>
<Value>libprintf_flt</Value> </avrgcccpp.linker.libraries.Libraries>
</ListValues> <avrgcccpp.assembler.general.IncludePaths>
</avrgcccpp.linker.libraries.Libraries> <ListValues>
<avrgcccpp.assembler.general.IncludePaths> <Value>%24(PackRepoDir)\Atmel\ATmega_DFP\1.4.346\include</Value>
<ListValues> </ListValues>
<Value>%24(PackRepoDir)\Atmel\ATmega_DFP\1.4.346\include</Value> </avrgcccpp.assembler.general.IncludePaths>
</ListValues> <avrgcccpp.assembler.debugging.DebugLevel>Default (-Wa,-g)</avrgcccpp.assembler.debugging.DebugLevel>
</avrgcccpp.assembler.general.IncludePaths> </AvrGccCpp>
<avrgcccpp.assembler.debugging.DebugLevel>Default (-Wa,-g)</avrgcccpp.assembler.debugging.DebugLevel>
</AvrGccCpp>
</ToolchainSettings> </ToolchainSettings>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

View File

@@ -42,12 +42,20 @@ void Statistics::callback()
} }
if (clk::millis() >= m_lastTemperatureWriteback + TEMPERATURE_WRITEBACK_DELAY) { if (clk::millis() >= m_lastTemperatureWriteback + TEMPERATURE_WRITEBACK_DELAY) {
writeTemperatureHistogram(); saveTemperatureHistogram();
m_lastTemperatureWriteback = clk::millis(); m_lastTemperatureWriteback = clk::millis();
} }
} }
} }
void Statistics::reset()
{
for (uint8_t i = 0; i < TEMPERATURE_RANGE; ++i) {
m_temperatureHistogram[i] = 0;
g_eepTemperatureHistogram[i] = 0;
}
}
uint8_t Statistics::getMinTemperature() uint8_t Statistics::getMinTemperature()
{ {
for (uint8_t i = 0; i < TEMPERATURE_RANGE; ++i) { for (uint8_t i = 0; i < TEMPERATURE_RANGE; ++i) {
@@ -97,6 +105,13 @@ uint32_t Statistics::getHistogram(const int8_t &temperature)
return m_temperatureHistogram[clampTemperature(temperature)]; return m_temperatureHistogram[clampTemperature(temperature)];
} }
void Statistics::saveTemperatureHistogram()
{
for (uint8_t i = 0; i < g_eepTemperatureHistogram.size(); ++i) {
g_eepTemperatureHistogram[i] = m_temperatureHistogram[i];
}
}
uint8_t Statistics::clampTemperature(const int8_t &temperature) uint8_t Statistics::clampTemperature(const int8_t &temperature)
{ {
if (temperature < 0) if (temperature < 0)
@@ -105,10 +120,3 @@ uint8_t Statistics::clampTemperature(const int8_t &temperature)
return TEMPERATURE_RANGE - 1; return TEMPERATURE_RANGE - 1;
return static_cast<uint8_t>(temperature); return static_cast<uint8_t>(temperature);
} }
void Statistics::writeTemperatureHistogram()
{
for (uint8_t i = 0; i < g_eepTemperatureHistogram.size(); ++i) {
g_eepTemperatureHistogram[i] = m_temperatureHistogram[i];
}
}

View File

@@ -8,6 +8,7 @@ class Statistics {
static void init(); static void init();
static void callback(); static void callback();
static void reset();
static uint8_t getMinTemperature(); static uint8_t getMinTemperature();
static uint8_t getMaxTemperature(); static uint8_t getMaxTemperature();
@@ -16,6 +17,8 @@ class Statistics {
static uint32_t getHighestHistogramSamples(); static uint32_t getHighestHistogramSamples();
static uint32_t getHistogram(const int8_t &temperature); static uint32_t getHistogram(const int8_t &temperature);
static void saveTemperatureHistogram();
private: private:
static constexpr auto TEMPERATURE_WRITEBACK_DELAY = 1'800'000; static constexpr auto TEMPERATURE_WRITEBACK_DELAY = 1'800'000;
static constexpr auto TEMPERATURE_SAMPLE_DELAY = 1'000; static constexpr auto TEMPERATURE_SAMPLE_DELAY = 1'000;
@@ -25,5 +28,4 @@ class Statistics {
static uint32_t m_temperatureHistogram[TEMPERATURE_RANGE]; static uint32_t m_temperatureHistogram[TEMPERATURE_RANGE];
static uint8_t clampTemperature(const int8_t &temperature); static uint8_t clampTemperature(const int8_t &temperature);
static void writeTemperatureHistogram();
}; };

View File

@@ -2,7 +2,7 @@
#include <ctype.h> #include <ctype.h>
#include <stdint.h> #include <stdint.h>
#include <stdio.h> #include <stdlib.h>
#include <avr/pgmspace.h> #include <avr/pgmspace.h>
@@ -23,9 +23,12 @@ GF(BOOTLOADER_CMD, "bootloader");
GF(UPTIME_CMD, "uptime"); GF(UPTIME_CMD, "uptime");
GF(STATISTICS_CMD, "statistics"); GF(STATISTICS_CMD, "statistics");
GF(HISTOGRAM_CMD, "histogram"); GF(HISTOGRAM_CMD, "histogram");
GF(RESET_CMD, "reset");
GF(SET_CMD, "set");
GF(AUTO_CMD, "auto");
GF(VERSION_CMD, "version"); GF(VERSION_CMD, "version");
GF(VERSION, "1.5"); GF(VERSION, "1.8");
static inline bool substringEquals(const char *str, const ::detail::FlashString *flashStr, const size_t &size) static inline bool substringEquals(const char *str, const ::detail::FlashString *flashStr, const size_t &size)
{ {
@@ -127,6 +130,26 @@ class Terminal {
return false; return false;
} }
static uint8_t parseFanSpeed()
{
const auto setCmdLen = strlen_P(reinterpret_cast<const char *>(detail::SET_CMD));
if (m_inputSize > setCmdLen && substringEquals(m_inputBuffer, detail::SET_CMD, setCmdLen) &&
m_inputSize < INPUT_BUFFER_SIZE) {
m_inputBuffer[m_inputSize] = '\0'; // Null terminate to be parsable by stdlib
const auto *fanSpeedStr = m_inputBuffer + setCmdLen;
auto *fanSpeedStrEnd = m_inputBuffer + setCmdLen;
const auto fanSpeed = strtol(fanSpeedStr, &fanSpeedStrEnd, 10);
if (fanSpeedStrEnd != fanSpeedStr && *fanSpeedStr != '\0' && *fanSpeedStrEnd == '\0') {
if (fanSpeed >= 0 && fanSpeed <= 100)
return static_cast<uint8_t>(fanSpeed);
}
}
return 0xFF;
}
static void parseInput() static void parseInput()
{ {
if (m_inputSize) { if (m_inputSize) {
@@ -149,6 +172,12 @@ class Terminal {
printStatistics(); printStatistics();
} else if (substringEquals(m_inputBuffer, detail::HISTOGRAM_CMD, m_inputSize)) { } else if (substringEquals(m_inputBuffer, detail::HISTOGRAM_CMD, m_inputSize)) {
printHistogram(); printHistogram();
} else if (stringEquals(m_inputBuffer, detail::RESET_CMD, m_inputSize)) {
handleReset();
} else if (uint8_t targetFanSpeed = parseFanSpeed(); targetFanSpeed <= 100) {
handleSet(targetFanSpeed);
} else if (substringEquals(m_inputBuffer, detail::AUTO_CMD, m_inputSize)) {
handleAuto();
} else if (substringEquals(m_inputBuffer, detail::VERSION_CMD, m_inputSize)) { } else if (substringEquals(m_inputBuffer, detail::VERSION_CMD, m_inputSize)) {
printVersion(); printVersion();
} else { } else {
@@ -179,6 +208,9 @@ class Terminal {
m_serial << detail::UPTIME_CMD << F(" .....: shows system uptime") << detail::ENDL; m_serial << detail::UPTIME_CMD << F(" .....: shows system uptime") << detail::ENDL;
m_serial << detail::STATISTICS_CMD << F(" .: prints overall statistics like min and max temp") << detail::ENDL; m_serial << detail::STATISTICS_CMD << F(" .: prints overall statistics like min and max temp") << detail::ENDL;
m_serial << detail::HISTOGRAM_CMD << F(" ..: prints a histogram of the temperature") << detail::ENDL; m_serial << detail::HISTOGRAM_CMD << F(" ..: prints a histogram of the temperature") << detail::ENDL;
m_serial << detail::RESET_CMD << F(" ......: resets statistics to 0 in EEPROM and RAM") << detail::ENDL;
m_serial << detail::SET_CMD << F(" ........: sets the fan speed to the provided value") << detail::ENDL;
m_serial << detail::AUTO_CMD << F(" .......: turns on automatic fan control") << detail::ENDL;
m_serial << detail::VERSION_CMD << F(" ....: displays firmware version") << detail::ENDL; m_serial << detail::VERSION_CMD << F(" ....: displays firmware version") << detail::ENDL;
} }
@@ -187,13 +219,14 @@ class Terminal {
if (Controller::m_dataAvailable) { if (Controller::m_dataAvailable) {
char floatBuffer[16]; char floatBuffer[16];
sprintf(floatBuffer, "%.2f", Controller::m_adcSample); dtostrf(Controller::m_adcSample, 0, 2, floatBuffer);
m_serial << F("ADC value ...: ") << floatBuffer << F(" / 1023") << detail::ENDL; m_serial << F("ADC value ...: ") << floatBuffer << F(" / 1023") << detail::ENDL;
sprintf(floatBuffer, "%.2f", Controller::m_resistance); dtostrf(Controller::m_resistance, 0, 2, floatBuffer);
m_serial << F("Resistance ..: ") << floatBuffer << F(" Ohm") << detail::ENDL; m_serial << F("Resistance ..: ") << floatBuffer << F(" Ohm") << detail::ENDL;
sprintf(floatBuffer, "%.2f", Controller::m_temperature); dtostrf(Controller::m_temperature, 0, 2, floatBuffer);
m_serial << F("Temperature .: ") << floatBuffer << F(" C") << detail::ENDL; m_serial << F("Temperature .: ") << floatBuffer << F(" C") << detail::ENDL;
m_serial << F("Fan speed ...: ") << Controller::m_fanSpeed << F("%") << detail::ENDL; m_serial << F("Fan speed ...: ") << Controller::m_fanSpeed << F("%")
<< (Controller::m_autoMode ? F(" auto") : F(" manual")) << detail::ENDL;
} else { } else {
m_serial << F("No data available yet!") << detail::ENDL; m_serial << F("No data available yet!") << detail::ENDL;
} }
@@ -214,6 +247,9 @@ class Terminal {
static void handleBootloader() static void handleBootloader()
{ {
m_serial << F("Saving statistics to EEPROM") << detail::ENDL;
Statistics::saveTemperatureHistogram();
m_serial << F("Entering bootloader...") << detail::ENDL; m_serial << F("Entering bootloader...") << detail::ENDL;
m_serial.flushTx(); m_serial.flushTx();
@@ -243,30 +279,77 @@ class Terminal {
static void printStatistics() static void printStatistics()
{ {
m_serial << F("Minimum temperature .: ") << Statistics::getMinTemperature() << F(" C") << detail::ENDL; const auto minTemp = Statistics::getMinTemperature();
m_serial << F("Maximum temperature .: ") << Statistics::getMaxTemperature() << F(" C") << detail::ENDL; const auto maxTemp = Statistics::getMaxTemperature();
m_serial << F("Minimum temperature .: ");
if (minTemp != Statistics::TEMPERATURE_RANGE) {
m_serial << minTemp << F(" C");
} else {
m_serial << F("Not available");
}
m_serial << detail::ENDL << F("Maximum temperature .: ");
if (maxTemp != Statistics::TEMPERATURE_RANGE) {
m_serial << maxTemp << F(" C");
} else {
m_serial << F("Not available");
}
m_serial << detail::ENDL;
} }
static void printHistogram() static void printHistogram()
{ {
const auto totalSamples = Statistics::getTotalHistogramSamples(); const auto totalSamples = Statistics::getTotalHistogramSamples();
const auto maximumSamples = Statistics::getHighestHistogramSamples();
const auto normalizationFactor = (maximumSamples / 100 > 1) ? (maximumSamples / 100) : 1;
for (uint8_t t = Statistics::getMinTemperature(); t <= Statistics::getMaxTemperature(); ++t) { if (totalSamples > 0) {
const auto histogramSamples = Statistics::getHistogram(t); const auto maximumSamples = Statistics::getHighestHistogramSamples();
m_serial.template txNumber<uint8_t, 10, 2>(t); auto normalizationFactor = (maximumSamples / 100 > 1) ? (maximumSamples / 100) : 1;
m_serial << F(" C = ");
m_serial.template txNumber<uint8_t, 10, 3, ' '>((histogramSamples * 100) / totalSamples); while (maximumSamples / normalizationFactor > 100)
m_serial << F("%\t"); ++normalizationFactor;
const auto normalizedSamples = static_cast<uint8_t>(histogramSamples / normalizationFactor);
for (uint8_t i = 0; i < normalizedSamples; ++i) { for (uint8_t t = Statistics::getMinTemperature(); t <= Statistics::getMaxTemperature(); ++t) {
m_serial << '#'; const auto histogramSamples = Statistics::getHistogram(t);
m_serial.template txNumber<uint8_t, 10, 2>(t);
m_serial << F(" C = ");
const auto percent =
static_cast<uint8_t>((2 * 100 * histogramSamples + totalSamples) / (2 * totalSamples));
m_serial.template txNumber<uint8_t, 10, 3, ' '>(percent);
m_serial << F("%\t");
const auto normalizedSamples = static_cast<uint8_t>(histogramSamples / normalizationFactor);
for (uint8_t i = 0; i < normalizedSamples; ++i) {
m_serial << '#';
}
m_serial << detail::ENDL;
} }
m_serial << detail::ENDL; } else {
m_serial << F("There is no data yet!") << detail::ENDL;
} }
} }
static void handleReset()
{
m_serial << F("Resetting statistics in EEPROM and RAM") << detail::ENDL;
Statistics::reset();
m_serial << F("Reset statistics") << detail::ENDL;
}
static void handleSet(uint8_t targetFanSpeed)
{
m_serial << F("Setting fan speed to ");
m_serial.txNumber(targetFanSpeed);
m_serial << detail::ENDL;
Controller::m_autoMode = false;
Controller::m_fanSpeed = targetFanSpeed;
}
static void handleAuto()
{
m_serial << F("Turning on automatic fan control") << detail::ENDL;
Controller::m_autoMode = true;
}
static void printVersion() static void printVersion()
{ {
m_serial << F("FanTemp v") << detail::VERSION << detail::ENDL; m_serial << F("FanTemp v") << detail::VERSION << detail::ENDL;