Compare commits

...

26 Commits

Author SHA1 Message Date
76d6b1583b Indicate auto/manual fan speed 2021-02-12 17:12:56 +01:00
5ec4a5441a Implement setting fan speed manually 2021-02-12 16:38:41 +01:00
7aa98a8ebd Replace sprintf with dtostrf to save about 1.5k of flash 2020-04-13 01:13:44 +02:00
64d6df256d Fix string being located in RAM 2020-04-13 00:54:44 +02:00
16ebed63c2 Change to stk500v2 compatible bootloader 2020-04-13 00:49:21 +02:00
eee2e6172a Fix rounding of percentage calculation 2020-04-10 17:21:39 +02:00
d6269952a6 Add saving to EEPROM on bootloader entry 2020-04-09 13:21:53 +02:00
6a7213de60 Add statistics reset command 2020-04-08 13:45:14 +02:00
ed2fddc427 Fix handling of statistics before data is available and fix normalization factor 2020-04-08 13:44:54 +02:00
1694e3bbab Change histogram to only print from min to max 2020-04-08 12:58:59 +02:00
02565c9396 Add percentages to histogram 2020-04-08 12:41:53 +02:00
00082617d1 Make help messages consistent 2020-04-08 09:52:39 +02:00
b8a40aed17 Implement eeprom stored persistent histogram 2020-04-08 02:17:34 +02:00
cae18b98e7 Reduce code duplication 2020-04-07 21:54:15 +02:00
6ba4a2ce3d Add eeprom saved statistics 2020-04-07 21:09:27 +02:00
ea6a6bd218 Add check to prevent printing data before it's available 2020-04-07 21:05:40 +02:00
f3cf12db3b Refactor terminal callback 2020-04-06 22:17:32 +02:00
34428b76dd Add uptime command 2020-04-06 22:12:11 +02:00
9ef4d2a737 Add millis timer for timekeeping 2020-04-06 21:36:47 +02:00
e29ee8c11f Fix commands being accepted in monitor mode 2020-04-06 19:53:46 +02:00
dd42aebb16 Make adc sampling interrupt driven 2020-04-06 19:43:32 +02:00
8db7bde6e5 Remove bootloader countdown and change timeout to 3s 2020-04-06 17:50:17 +02:00
508f139f47 Refactor substring comparison 2020-04-06 17:46:10 +02:00
45a79adc56 Fix leaking details into global namespace 2020-04-06 17:05:48 +02:00
67559642a3 Change boot-up message to include version 2020-04-06 16:36:33 +02:00
9ab76f4ce5 Add version number 2020-04-06 16:33:57 +02:00
16 changed files with 665 additions and 208 deletions

6
.gitmodules vendored
View File

@@ -10,3 +10,9 @@
[submodule "fantemp/adc"]
path = fantemp/adc
url = git@git.blackmark.me:avr/adc.git
[submodule "fantemp/type"]
path = fantemp/type
url = git@git.blackmark.me:avr/type.git
[submodule "fantemp/eeprom"]
path = fantemp/eeprom
url = git@git.blackmark.me:avr/eeprom.git

View File

@@ -9,7 +9,7 @@ namespace {
typedef void (*jmp_fn)() __attribute__((noreturn));
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
@@ -31,7 +31,7 @@ void Bootloader::reset()
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 false;

37
fantemp/clock.cpp Normal file
View File

@@ -0,0 +1,37 @@
#include "clock.hpp"
#include <avr/interrupt.h>
#include <avr/io.h>
namespace clk {
namespace detail {
volatile uint64_t sm_millisCounter = 0;
ISR(TIMER2_COMPA_vect)
{
++sm_millisCounter;
}
} // namespace detail
void init()
{
TCCR2A |= (1 << WGM21);
TCCR2B |= (1 << CS22) | (1 << CS20);
OCR2A = 124;
TIMSK2 |= (1 << OCIE2A);
}
uint64_t millis()
{
const auto oldSreg = SREG;
cli();
const auto millisCounter = detail::sm_millisCounter;
SREG = oldSreg;
return millisCounter;
}
} // namespace clk

View File

@@ -1,4 +1,13 @@
#pragma once
#define F_CPU 16000000
#define F_CPU 16'000'000
#include <util/delay.h>
#include <stdint.h>
namespace clk {
void init();
uint64_t millis();
} // namespace clk

View File

@@ -7,20 +7,35 @@ double Controller::m_adcSample;
double Controller::m_resistance;
double Controller::m_temperature;
uint8_t Controller::m_fanSpeed;
bool Controller::m_dataAvailable = false;
bool Controller::m_autoMode = true;
volatile uint32_t Controller::m_adcSampleSum;
volatile bool Controller::m_adcSampleReady = false;
void Controller::init()
{
m_adcPin.init();
m_adcPin.init(sampleCallback);
pwm::init();
pwm::setDuty(100);
}
void Controller::callback()
{
sample();
if (m_adcSampleReady) {
m_adcSample = static_cast<double>(m_adcSampleSum) / NUM_ADC_SAMPLES;
m_dataAvailable = true;
m_adcSampleReady = false;
m_resistance = m_thermistor.getResistance(m_adcSample);
m_temperature = m_thermistor.getTemperature(m_resistance);
if (m_autoMode)
m_fanSpeed = mapTemperature(m_temperature);
pwm::setDuty(m_fanSpeed);
}
}
uint8_t Controller::mapTemperature(double temperature)
{
@@ -35,9 +50,20 @@ uint8_t Controller::mapTemperature(double temperature)
return clamp<uint8_t>(fanSpeed, 0, 100);
}
void Controller::sample()
void Controller::sampleCallback(const uint16_t &adcSample)
{
m_adcSample = m_thermistor.sampleAdc(m_adcPin, 1000);
m_resistance = m_thermistor.getResistance(m_adcSample);
m_temperature = m_thermistor.getTemperature(m_resistance);
static uint32_t s_sampleSum = 0;
static auto s_sampleCounter = NUM_ADC_SAMPLES;
s_sampleSum += adcSample;
if (--s_sampleCounter <= 0) {
if (!m_adcSampleReady) {
m_adcSampleSum = s_sampleSum;
m_adcSampleReady = true;
}
// else lose this sample, which happens during long running commands like "curve", but has no impact
s_sampleSum = 0;
s_sampleCounter = NUM_ADC_SAMPLES;
}
}

View File

@@ -12,6 +12,8 @@ class Controller {
static double m_resistance;
static double m_temperature;
static uint8_t m_fanSpeed;
static bool m_dataAvailable;
static bool m_autoMode;
static void init();
@@ -20,12 +22,17 @@ class Controller {
static uint8_t mapTemperature(double temperature);
private:
using adc_conf = adc::Config<adc::SingleMode>;
using adc_conf = adc::Config<adc::FreeRunningMode>;
static adc::Adc<adc_conf, io::P, io::P::C0> m_adcPin;
static constexpr auto NUM_ADC_SAMPLES = 1000;
static volatile uint32_t m_adcSampleSum;
static volatile bool m_adcSampleReady;
static Thermistor m_thermistor;
static void sample();
static void sampleCallback(const uint16_t &adcSample);
template <typename T>
static T clamp(double value, T lower, T upper)

1
fantemp/eeprom Submodule

Submodule fantemp/eeprom added at 3bcba0a191

View File

@@ -68,6 +68,34 @@
<ToolNumber>J41800099437</ToolNumber>
<ToolName>Atmel-ICE</ToolName>
</com_atmel_avrdbg_tool_atmelice>
<custom>
<ToolOptions>
<InterfaceProperties>
<IspClock>125000</IspClock>
</InterfaceProperties>
<InterfaceName>
</InterfaceName>
</ToolOptions>
<ToolType>custom</ToolType>
<ToolNumber>
</ToolNumber>
<ToolName>Custom Programming Tool</ToolName>
</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 Condition=" '$(Configuration)' == 'Release' ">
<ToolchainSettings>
@@ -113,11 +141,9 @@
<avrgcccpp.compiler.warnings.AllWarnings>True</avrgcccpp.compiler.warnings.AllWarnings>
<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.linker.general.UseVprintfLibrary>True</avrgcccpp.linker.general.UseVprintfLibrary>
<avrgcccpp.linker.libraries.Libraries>
<ListValues>
<Value>libm</Value>
<Value>libprintf_flt</Value>
</ListValues>
</avrgcccpp.linker.libraries.Libraries>
<avrgcccpp.assembler.general.IncludePaths>
@@ -174,11 +200,9 @@
<avrgcccpp.compiler.warnings.AllWarnings>True</avrgcccpp.compiler.warnings.AllWarnings>
<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.linker.general.UseVprintfLibrary>True</avrgcccpp.linker.general.UseVprintfLibrary>
<avrgcccpp.linker.libraries.Libraries>
<ListValues>
<Value>libm</Value>
<Value>libprintf_flt</Value>
</ListValues>
</avrgcccpp.linker.libraries.Libraries>
<avrgcccpp.assembler.general.IncludePaths>
@@ -206,6 +230,9 @@
<Compile Include="bootloader.hpp">
<SubType>compile</SubType>
</Compile>
<Compile Include="clock.cpp">
<SubType>compile</SubType>
</Compile>
<Compile Include="clock.hpp">
<SubType>compile</SubType>
</Compile>
@@ -215,6 +242,9 @@
<Compile Include="controller.hpp">
<SubType>compile</SubType>
</Compile>
<Compile Include="eeprom\eeprom.hpp">
<SubType>compile</SubType>
</Compile>
<Compile Include="flash\flash.hpp">
<SubType>compile</SubType>
</Compile>
@@ -230,6 +260,12 @@
<Compile Include="pwm.hpp">
<SubType>compile</SubType>
</Compile>
<Compile Include="statistics.cpp">
<SubType>compile</SubType>
</Compile>
<Compile Include="statistics.hpp">
<SubType>compile</SubType>
</Compile>
<Compile Include="terminal.hpp">
<SubType>compile</SubType>
</Compile>
@@ -239,6 +275,9 @@
<Compile Include="thermistor.hpp">
<SubType>compile</SubType>
</Compile>
<Compile Include="type\type.hpp">
<SubType>compile</SubType>
</Compile>
<Compile Include="uart\config.hpp">
<SubType>compile</SubType>
</Compile>
@@ -257,14 +296,13 @@
<Compile Include="uart\uart.hpp">
<SubType>compile</SubType>
</Compile>
<Compile Include="uart\utils.hpp">
<SubType>compile</SubType>
</Compile>
</ItemGroup>
<ItemGroup>
<Folder Include="flash" />
<Folder Include="io" />
<Folder Include="adc" />
<Folder Include="eeprom" />
<Folder Include="type" />
<Folder Include="uart" />
</ItemGroup>
<Import Project="$(AVRSTUDIO_EXE_PATH)\\Vs\\Compiler.targets" />

View File

@@ -7,12 +7,15 @@
#include "bootloader.hpp"
#include "controller.hpp"
#include "statistics.hpp"
#include "terminal.hpp"
int main()
{
Bootloader::init([]() {});
clk::init();
using serial = uart::Uart0<uart::Config<115200>>;
Terminal<serial> terminal;
terminal.init();
@@ -20,9 +23,13 @@ int main()
Controller controller;
controller.init();
Statistics statistics;
statistics.init();
while (true) {
controller.callback();
terminal.callback();
statistics.callback();
}
return 0;

122
fantemp/statistics.cpp Normal file
View File

@@ -0,0 +1,122 @@
#include "statistics.hpp"
#include <math.h>
#include "eeprom/eeprom.hpp"
#include "type/type.hpp"
#include "clock.hpp"
#include "controller.hpp"
uint64_t Statistics::m_lastTemperatureWriteback = 0;
uint64_t Statistics::m_lastTemperatureSample = 0;
uint32_t Statistics::m_temperatureHistogram[TEMPERATURE_RANGE];
namespace {
// Must be in translation unit and cannot be forward declared
EEARRAY(uint32_t, e_temperatureHistogram, Statistics::TEMPERATURE_RANGE);
// Could be declared in every function that uses it, but that would be code duplication
EepromArray<e_temperatureHistogram, EEARRAY_SIZE(e_temperatureHistogram), true> g_eepTemperatureHistogram;
} // namespace
void Statistics::init()
{
for (uint8_t i = 0; i < g_eepTemperatureHistogram.size(); ++i) {
m_temperatureHistogram[i] = g_eepTemperatureHistogram[i];
if (m_temperatureHistogram[i] == type::numeric_limits<uint32_t>::max()) {
m_temperatureHistogram[i] = 0;
}
}
}
void Statistics::callback()
{
if (Controller::m_dataAvailable) {
if (clk::millis() >= m_lastTemperatureSample + TEMPERATURE_SAMPLE_DELAY) {
const auto temperature = clampTemperature(static_cast<int8_t>(round(Controller::m_temperature)));
++m_temperatureHistogram[temperature];
m_lastTemperatureSample = clk::millis();
}
if (clk::millis() >= m_lastTemperatureWriteback + TEMPERATURE_WRITEBACK_DELAY) {
saveTemperatureHistogram();
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()
{
for (uint8_t i = 0; i < TEMPERATURE_RANGE; ++i) {
if (m_temperatureHistogram[i] > 0)
return i;
}
return TEMPERATURE_RANGE;
}
uint8_t Statistics::getMaxTemperature()
{
for (int8_t i = TEMPERATURE_RANGE - 1; i >= 0; --i) {
if (m_temperatureHistogram[i] > 0)
return i;
}
return TEMPERATURE_RANGE;
}
uint64_t Statistics::getTotalHistogramSamples()
{
uint64_t totalSamples = 0;
for (uint8_t i = 0; i < TEMPERATURE_RANGE; ++i) {
totalSamples += m_temperatureHistogram[i];
}
return totalSamples;
}
uint32_t Statistics::getHighestHistogramSamples()
{
uint32_t max = 0;
for (uint8_t i = 0; i < TEMPERATURE_RANGE; ++i) {
if (m_temperatureHistogram[i] > max) {
max = m_temperatureHistogram[i];
}
}
return max;
}
uint32_t Statistics::getHistogram(const int8_t &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)
{
if (temperature < 0)
return 0;
if (temperature >= TEMPERATURE_RANGE)
return TEMPERATURE_RANGE - 1;
return static_cast<uint8_t>(temperature);
}

31
fantemp/statistics.hpp Normal file
View File

@@ -0,0 +1,31 @@
#pragma once
#include <stdint.h>
class Statistics {
public:
static constexpr auto TEMPERATURE_RANGE = 100;
static void init();
static void callback();
static void reset();
static uint8_t getMinTemperature();
static uint8_t getMaxTemperature();
static uint64_t getTotalHistogramSamples();
static uint32_t getHighestHistogramSamples();
static uint32_t getHistogram(const int8_t &temperature);
static void saveTemperatureHistogram();
private:
static constexpr auto TEMPERATURE_WRITEBACK_DELAY = 1'800'000;
static constexpr auto TEMPERATURE_SAMPLE_DELAY = 1'000;
static uint64_t m_lastTemperatureWriteback;
static uint64_t m_lastTemperatureSample;
static uint32_t m_temperatureHistogram[TEMPERATURE_RANGE];
static uint8_t clampTemperature(const int8_t &temperature);
};

View File

@@ -2,13 +2,17 @@
#include <ctype.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <avr/pgmspace.h>
#include "flash/flash.hpp"
#include "clock.hpp"
#include "controller.hpp"
#include "statistics.hpp"
namespace detail {
GF(ENDL, "\r\n");
GF(HELP_CMD, "help");
@@ -16,9 +20,31 @@ GF(SHOW_CMD, "show");
GF(CURVE_CMD, "curve");
GF(MONITOR_CMD, "monitor");
GF(BOOTLOADER_CMD, "bootloader");
GF(UPTIME_CMD, "uptime");
GF(STATISTICS_CMD, "statistics");
GF(HISTOGRAM_CMD, "histogram");
GF(RESET_CMD, "reset");
GF(SET_CMD, "set");
GF(AUTO_CMD, "auto");
constexpr auto BACKSPACE = uint8_t{0x7f};
constexpr auto CTRL_C = uint8_t{0x03};
GF(VERSION_CMD, "version");
GF(VERSION, "1.8");
static inline bool substringEquals(const char *str, const ::detail::FlashString *flashStr, const size_t &size)
{
return (strncmp_P(str, reinterpret_cast<const char *>(flashStr), size) == 0);
}
static inline bool stringEquals(const char *str, const ::detail::FlashString *flashStr, const size_t &size)
{
if (size == strlen_P(reinterpret_cast<const char *>(flashStr))) {
return substringEquals(str, flashStr, size);
}
return false;
}
} // namespace detail
template <class Uart>
class Terminal {
@@ -27,13 +53,43 @@ class Terminal {
{
m_serial.init();
m_serial << ENDL << F("FanTemp Control Panel") << ENDL << ENDL << F("$ ");
m_serial << detail::ENDL;
printVersion();
m_serial << detail::ENDL << F("$ ");
}
static void callback()
{
if (receiveInput()) {
parseInput();
}
if (m_state == State::MONITOR && clk::millis() >= m_monitorDelayLastUpdate + MONITOR_DELAY) {
showState();
m_monitorDelayLastUpdate = clk::millis();
}
}
private:
static constexpr auto INPUT_BUFFER_SIZE = 128;
static constexpr auto BACKSPACE = uint8_t{0x7f};
static constexpr auto CTRL_C = uint8_t{0x03};
static constexpr auto MONITOR_DELAY = 500;
enum class State {
NONE,
MONITOR,
};
static Uart m_serial;
static char m_inputBuffer[INPUT_BUFFER_SIZE];
static uint16_t m_inputSize;
static State m_state;
static uint64_t m_monitorDelayLastUpdate;
static bool receiveInput()
{
uint8_t inputByte;
bool handleInput = false;
while (m_serial.rxByte(inputByte)) {
if (isprint(inputByte) || inputByte == CTRL_C) {
@@ -41,9 +97,8 @@ class Terminal {
// Handle Ctrl + C
if (inputByte == CTRL_C) {
m_serial << F("^C") << ENDL;
handleInput = true;
break;
m_serial << F("^C") << detail::ENDL;
return true;
}
// Echo
else {
@@ -62,89 +117,119 @@ class Terminal {
if (m_serial.peek(inputByte) && (inputByte == '\r' || inputByte == '\n')) {
m_serial.rxByte(inputByte);
}
m_serial << ENDL;
handleInput = true;
break;
m_serial << detail::ENDL;
return true;
}
if (m_inputSize >= INPUT_BUFFER_SIZE) {
m_serial << ENDL << F("WARNING: Terminal input buffer overflow!") << ENDL;
handleInput = true;
break;
m_serial << detail::ENDL << F("WARNING: Terminal input buffer overflow!") << detail::ENDL;
return true;
}
}
if (handleInput) {
parseInput();
m_inputSize = 0;
m_serial << F("$ ");
return false;
}
if (m_state == State::MONITOR)
showState();
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);
}
}
private:
static constexpr auto INPUT_BUFFER_SIZE = 128;
enum class State {
NONE,
MONITOR,
};
static Uart m_serial;
static char m_inputBuffer[INPUT_BUFFER_SIZE];
static uint16_t m_inputSize;
static State m_state;
return 0xFF;
}
static void parseInput()
{
if (m_inputSize) {
if (m_inputBuffer[m_inputSize - 1] == CTRL_C) {
handleCtrlC();
} else if (strncmp_P(m_inputBuffer, reinterpret_cast<const char *>(HELP_CMD), m_inputSize) == 0) {
} else if (m_state == State::NONE) {
if (substringEquals(m_inputBuffer, detail::HELP_CMD, m_inputSize)) {
printHelp();
} else if (strncmp_P(m_inputBuffer, reinterpret_cast<const char *>(SHOW_CMD), m_inputSize) == 0) {
} else if (substringEquals(m_inputBuffer, detail::SHOW_CMD, m_inputSize)) {
showState();
} else if (strncmp_P(m_inputBuffer, reinterpret_cast<const char *>(CURVE_CMD), m_inputSize) == 0) {
} else if (substringEquals(m_inputBuffer, detail::CURVE_CMD, m_inputSize)) {
printCurve();
} else if (strncmp_P(m_inputBuffer, reinterpret_cast<const char *>(MONITOR_CMD), m_inputSize) == 0) {
} else if (substringEquals(m_inputBuffer, detail::MONITOR_CMD, m_inputSize)) {
m_state = State::MONITOR;
} else if (strncmp_P(m_inputBuffer, reinterpret_cast<const char *>(BOOTLOADER_CMD), m_inputSize) == 0) {
} else if (substringEquals(m_inputBuffer, detail::BOOTLOADER_CMD, m_inputSize)) {
handleBootloader();
} else if (substringEquals(m_inputBuffer, detail::UPTIME_CMD, m_inputSize)) {
printUptime();
} else if (substringEquals(m_inputBuffer, detail::STATISTICS_CMD, m_inputSize)) {
printStatistics();
} else if (substringEquals(m_inputBuffer, detail::HISTOGRAM_CMD, m_inputSize)) {
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)) {
printVersion();
} else {
printUnknown();
}
}
}
m_inputSize = 0;
if (m_state == State::NONE)
m_serial << F("$ ");
}
static void handleCtrlC()
{
m_serial << F("Abort!") << ENDL;
m_serial << F("Abort!") << detail::ENDL;
m_state = State::NONE;
}
static void printHelp()
{
m_serial << F("FanTemp command overview: ") << ENDL;
m_serial << HELP_CMD << F(" .......: print this help message") << ENDL;
m_serial << SHOW_CMD << F(" .......: shows current temperature and fan speed") << ENDL;
m_serial << CURVE_CMD << F(" ......: shows the curve used to map temperature to fan speed") << ENDL;
m_serial << MONITOR_CMD << F(" ....: loops the show command until Ctrl + C is pressed") << ENDL;
m_serial << BOOTLOADER_CMD << F(" .: enters the bootloader after 10 seconds") << ENDL;
m_serial << F("FanTemp command overview: ") << detail::ENDL;
m_serial << detail::HELP_CMD << F(" .......: prints this help message") << detail::ENDL;
m_serial << detail::SHOW_CMD << F(" .......: shows current temperature and fan speed") << detail::ENDL;
m_serial << detail::CURVE_CMD << F(" ......: shows mapping from temperature to fan speed") << detail::ENDL;
m_serial << detail::MONITOR_CMD << F(" ....: loops the show command until Ctrl + C is pressed") << detail::ENDL;
m_serial << detail::BOOTLOADER_CMD << F(" .: enters the bootloader after 3 seconds") << 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::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;
}
static void showState()
{
if (Controller::m_dataAvailable) {
char floatBuffer[16];
sprintf(floatBuffer, "%.2f", Controller::m_adcSample);
m_serial << F("ADC value ...: ") << floatBuffer << F(" / 1023") << ENDL;
sprintf(floatBuffer, "%.2f", Controller::m_resistance);
m_serial << F("Resistance ..: ") << floatBuffer << F(" Ohm") << ENDL;
sprintf(floatBuffer, "%.2f", Controller::m_temperature);
m_serial << F("Temperature .: ") << floatBuffer << F(" C") << ENDL;
m_serial << F("Fan speed ...: ") << Controller::m_fanSpeed << F("%") << ENDL;
dtostrf(Controller::m_adcSample, 0, 2, floatBuffer);
m_serial << F("ADC value ...: ") << floatBuffer << F(" / 1023") << detail::ENDL;
dtostrf(Controller::m_resistance, 0, 2, floatBuffer);
m_serial << F("Resistance ..: ") << floatBuffer << F(" Ohm") << detail::ENDL;
dtostrf(Controller::m_temperature, 0, 2, floatBuffer);
m_serial << F("Temperature .: ") << floatBuffer << F(" C") << detail::ENDL;
m_serial << F("Fan speed ...: ") << Controller::m_fanSpeed << F("%")
<< (Controller::m_autoMode ? F(" auto") : F(" manual")) << detail::ENDL;
} else {
m_serial << F("No data available yet!") << detail::ENDL;
}
}
static void printCurve()
@@ -154,33 +239,128 @@ class Terminal {
m_serial.template txNumber<uint8_t, 10, 3, ' '>(Controller::mapTemperature(i));
m_serial << F("%\t");
for (uint8_t s = 0; s < Controller::mapTemperature(i); ++s) {
m_serial << "#";
m_serial << '#';
}
m_serial << ENDL;
m_serial << detail::ENDL;
}
}
static void handleBootloader()
{
for (int8_t i = 10; i >= 0; --i) {
m_serial << i << ENDL;
_delay_ms(1000);
}
m_serial << F("Saving statistics to EEPROM") << detail::ENDL;
Statistics::saveTemperatureHistogram();
m_serial << F("Entering bootloader...") << ENDL;
m_serial << F("Entering bootloader...") << detail::ENDL;
m_serial.flushTx();
_delay_ms(1000);
_delay_ms(3000);
Bootloader::enter();
}
static void printUptime()
{
constexpr auto delimiter = ':';
const auto uptime = clk::millis();
const auto hours = static_cast<uint16_t>(uptime / 1000 / 60 / 60);
const auto minutes = static_cast<uint8_t>((uptime / 1000 / 60) % 60);
const auto seconds = static_cast<uint8_t>((uptime / 1000) % 60);
m_serial << F("System uptime: ");
m_serial.template txNumber<uint16_t, 10, 2>(hours);
m_serial << delimiter;
m_serial.template txNumber<uint8_t, 10, 2>(minutes);
m_serial << delimiter;
m_serial.template txNumber<uint8_t, 10, 2>(seconds);
m_serial << detail::ENDL;
}
static void printStatistics()
{
const auto minTemp = Statistics::getMinTemperature();
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()
{
const auto totalSamples = Statistics::getTotalHistogramSamples();
if (totalSamples > 0) {
const auto maximumSamples = Statistics::getHighestHistogramSamples();
auto normalizationFactor = (maximumSamples / 100 > 1) ? (maximumSamples / 100) : 1;
while (maximumSamples / normalizationFactor > 100)
++normalizationFactor;
for (uint8_t t = Statistics::getMinTemperature(); t <= Statistics::getMaxTemperature(); ++t) {
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;
}
} 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()
{
m_serial << F("FanTemp v") << detail::VERSION << detail::ENDL;
}
static void printUnknown()
{
m_serial << F("Unknown command \"");
for (uint16_t i = 0; i < m_inputSize; ++i)
m_serial << static_cast<char>(m_inputBuffer[i]);
m_serial << F("\"") << ENDL;
m_serial << F("\"") << detail::ENDL;
}
};
@@ -192,3 +372,6 @@ uint16_t Terminal<Uart>::m_inputSize = 0;
template <class Uart>
typename Terminal<Uart>::State Terminal<Uart>::m_state = State::NONE;
template <class Uart>
uint64_t Terminal<Uart>::m_monitorDelayLastUpdate = 0;

View File

@@ -4,17 +4,6 @@
class Thermistor {
public:
template <typename Adc>
static double sampleAdc(Adc &adcPin, uint16_t numSamples = 100)
{
double samples = 0;
for (uint16_t i = 0; i < numSamples; ++i)
samples += adcPin.read();
return samples / numSamples;
}
static double getResistance(double adcSample);
static double getTemperature(double resistance);

1
fantemp/type Submodule

Submodule fantemp/type added at ce31ef017f