Compare commits
26 Commits
07be559e94
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 76d6b1583b | |||
| 5ec4a5441a | |||
| 7aa98a8ebd | |||
| 64d6df256d | |||
| 16ebed63c2 | |||
| eee2e6172a | |||
| d6269952a6 | |||
| 6a7213de60 | |||
| ed2fddc427 | |||
| 1694e3bbab | |||
| 02565c9396 | |||
| 00082617d1 | |||
| b8a40aed17 | |||
| cae18b98e7 | |||
| 6ba4a2ce3d | |||
| ea6a6bd218 | |||
| f3cf12db3b | |||
| 34428b76dd | |||
| 9ef4d2a737 | |||
| e29ee8c11f | |||
| dd42aebb16 | |||
| 8db7bde6e5 | |||
| 508f139f47 | |||
| 45a79adc56 | |||
| 67559642a3 | |||
| 9ab76f4ce5 |
6
.gitmodules
vendored
6
.gitmodules
vendored
@@ -10,3 +10,9 @@
|
|||||||
[submodule "fantemp/adc"]
|
[submodule "fantemp/adc"]
|
||||||
path = fantemp/adc
|
path = fantemp/adc
|
||||||
url = git@git.blackmark.me:avr/adc.git
|
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
|
||||||
|
|||||||
Submodule fantemp/adc updated: 5d38b97254...5e9dac872a
@@ -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;
|
||||||
|
|||||||
37
fantemp/clock.cpp
Normal file
37
fantemp/clock.cpp
Normal 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
|
||||||
@@ -1,4 +1,13 @@
|
|||||||
#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>
|
||||||
|
|
||||||
|
namespace clk {
|
||||||
|
|
||||||
|
void init();
|
||||||
|
uint64_t millis();
|
||||||
|
|
||||||
|
} // namespace clk
|
||||||
|
|||||||
@@ -7,19 +7,34 @@ double Controller::m_adcSample;
|
|||||||
double Controller::m_resistance;
|
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_autoMode = true;
|
||||||
|
|
||||||
|
volatile uint32_t Controller::m_adcSampleSum;
|
||||||
|
volatile bool Controller::m_adcSampleReady = false;
|
||||||
|
|
||||||
void Controller::init()
|
void Controller::init()
|
||||||
{
|
{
|
||||||
m_adcPin.init();
|
m_adcPin.init(sampleCallback);
|
||||||
pwm::init();
|
pwm::init();
|
||||||
pwm::setDuty(100);
|
pwm::setDuty(100);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Controller::callback()
|
void Controller::callback()
|
||||||
{
|
{
|
||||||
sample();
|
if (m_adcSampleReady) {
|
||||||
m_fanSpeed = mapTemperature(m_temperature);
|
m_adcSample = static_cast<double>(m_adcSampleSum) / NUM_ADC_SAMPLES;
|
||||||
pwm::setDuty(m_fanSpeed);
|
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)
|
uint8_t Controller::mapTemperature(double temperature)
|
||||||
@@ -35,9 +50,20 @@ uint8_t Controller::mapTemperature(double temperature)
|
|||||||
return clamp<uint8_t>(fanSpeed, 0, 100);
|
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);
|
static uint32_t s_sampleSum = 0;
|
||||||
m_resistance = m_thermistor.getResistance(m_adcSample);
|
static auto s_sampleCounter = NUM_ADC_SAMPLES;
|
||||||
m_temperature = m_thermistor.getTemperature(m_resistance);
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ class Controller {
|
|||||||
static double m_resistance;
|
static double m_resistance;
|
||||||
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_autoMode;
|
||||||
|
|
||||||
static void init();
|
static void init();
|
||||||
|
|
||||||
@@ -20,12 +22,17 @@ class Controller {
|
|||||||
static uint8_t mapTemperature(double temperature);
|
static uint8_t mapTemperature(double temperature);
|
||||||
|
|
||||||
private:
|
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 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 Thermistor m_thermistor;
|
||||||
|
|
||||||
static void sample();
|
static void sampleCallback(const uint16_t &adcSample);
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
static T clamp(double value, T lower, T upper)
|
static T clamp(double value, T lower, T upper)
|
||||||
|
|||||||
1
fantemp/eeprom
Submodule
1
fantemp/eeprom
Submodule
Submodule fantemp/eeprom added at 3bcba0a191
@@ -68,126 +68,150 @@
|
|||||||
<ToolNumber>J41800099437</ToolNumber>
|
<ToolNumber>J41800099437</ToolNumber>
|
||||||
<ToolName>Atmel-ICE</ToolName>
|
<ToolName>Atmel-ICE</ToolName>
|
||||||
</com_atmel_avrdbg_tool_atmelice>
|
</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*(?<address>[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>
|
||||||
@@ -206,6 +230,9 @@
|
|||||||
<Compile Include="bootloader.hpp">
|
<Compile Include="bootloader.hpp">
|
||||||
<SubType>compile</SubType>
|
<SubType>compile</SubType>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
<Compile Include="clock.cpp">
|
||||||
|
<SubType>compile</SubType>
|
||||||
|
</Compile>
|
||||||
<Compile Include="clock.hpp">
|
<Compile Include="clock.hpp">
|
||||||
<SubType>compile</SubType>
|
<SubType>compile</SubType>
|
||||||
</Compile>
|
</Compile>
|
||||||
@@ -215,6 +242,9 @@
|
|||||||
<Compile Include="controller.hpp">
|
<Compile Include="controller.hpp">
|
||||||
<SubType>compile</SubType>
|
<SubType>compile</SubType>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
<Compile Include="eeprom\eeprom.hpp">
|
||||||
|
<SubType>compile</SubType>
|
||||||
|
</Compile>
|
||||||
<Compile Include="flash\flash.hpp">
|
<Compile Include="flash\flash.hpp">
|
||||||
<SubType>compile</SubType>
|
<SubType>compile</SubType>
|
||||||
</Compile>
|
</Compile>
|
||||||
@@ -230,6 +260,12 @@
|
|||||||
<Compile Include="pwm.hpp">
|
<Compile Include="pwm.hpp">
|
||||||
<SubType>compile</SubType>
|
<SubType>compile</SubType>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
<Compile Include="statistics.cpp">
|
||||||
|
<SubType>compile</SubType>
|
||||||
|
</Compile>
|
||||||
|
<Compile Include="statistics.hpp">
|
||||||
|
<SubType>compile</SubType>
|
||||||
|
</Compile>
|
||||||
<Compile Include="terminal.hpp">
|
<Compile Include="terminal.hpp">
|
||||||
<SubType>compile</SubType>
|
<SubType>compile</SubType>
|
||||||
</Compile>
|
</Compile>
|
||||||
@@ -239,6 +275,9 @@
|
|||||||
<Compile Include="thermistor.hpp">
|
<Compile Include="thermistor.hpp">
|
||||||
<SubType>compile</SubType>
|
<SubType>compile</SubType>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
<Compile Include="type\type.hpp">
|
||||||
|
<SubType>compile</SubType>
|
||||||
|
</Compile>
|
||||||
<Compile Include="uart\config.hpp">
|
<Compile Include="uart\config.hpp">
|
||||||
<SubType>compile</SubType>
|
<SubType>compile</SubType>
|
||||||
</Compile>
|
</Compile>
|
||||||
@@ -257,14 +296,13 @@
|
|||||||
<Compile Include="uart\uart.hpp">
|
<Compile Include="uart\uart.hpp">
|
||||||
<SubType>compile</SubType>
|
<SubType>compile</SubType>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="uart\utils.hpp">
|
|
||||||
<SubType>compile</SubType>
|
|
||||||
</Compile>
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Folder Include="flash" />
|
<Folder Include="flash" />
|
||||||
<Folder Include="io" />
|
<Folder Include="io" />
|
||||||
<Folder Include="adc" />
|
<Folder Include="adc" />
|
||||||
|
<Folder Include="eeprom" />
|
||||||
|
<Folder Include="type" />
|
||||||
<Folder Include="uart" />
|
<Folder Include="uart" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<Import Project="$(AVRSTUDIO_EXE_PATH)\\Vs\\Compiler.targets" />
|
<Import Project="$(AVRSTUDIO_EXE_PATH)\\Vs\\Compiler.targets" />
|
||||||
|
|||||||
@@ -7,12 +7,15 @@
|
|||||||
|
|
||||||
#include "bootloader.hpp"
|
#include "bootloader.hpp"
|
||||||
#include "controller.hpp"
|
#include "controller.hpp"
|
||||||
|
#include "statistics.hpp"
|
||||||
#include "terminal.hpp"
|
#include "terminal.hpp"
|
||||||
|
|
||||||
int main()
|
int main()
|
||||||
{
|
{
|
||||||
Bootloader::init([]() {});
|
Bootloader::init([]() {});
|
||||||
|
|
||||||
|
clk::init();
|
||||||
|
|
||||||
using serial = uart::Uart0<uart::Config<115200>>;
|
using serial = uart::Uart0<uart::Config<115200>>;
|
||||||
Terminal<serial> terminal;
|
Terminal<serial> terminal;
|
||||||
terminal.init();
|
terminal.init();
|
||||||
@@ -20,9 +23,13 @@ int main()
|
|||||||
Controller controller;
|
Controller controller;
|
||||||
controller.init();
|
controller.init();
|
||||||
|
|
||||||
|
Statistics statistics;
|
||||||
|
statistics.init();
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
controller.callback();
|
controller.callback();
|
||||||
terminal.callback();
|
terminal.callback();
|
||||||
|
statistics.callback();
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
122
fantemp/statistics.cpp
Normal file
122
fantemp/statistics.cpp
Normal 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
31
fantemp/statistics.hpp
Normal 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);
|
||||||
|
};
|
||||||
@@ -2,13 +2,17 @@
|
|||||||
|
|
||||||
#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>
|
||||||
|
|
||||||
#include "flash/flash.hpp"
|
#include "flash/flash.hpp"
|
||||||
|
|
||||||
|
#include "clock.hpp"
|
||||||
#include "controller.hpp"
|
#include "controller.hpp"
|
||||||
|
#include "statistics.hpp"
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
|
||||||
GF(ENDL, "\r\n");
|
GF(ENDL, "\r\n");
|
||||||
GF(HELP_CMD, "help");
|
GF(HELP_CMD, "help");
|
||||||
@@ -16,9 +20,31 @@ GF(SHOW_CMD, "show");
|
|||||||
GF(CURVE_CMD, "curve");
|
GF(CURVE_CMD, "curve");
|
||||||
GF(MONITOR_CMD, "monitor");
|
GF(MONITOR_CMD, "monitor");
|
||||||
GF(BOOTLOADER_CMD, "bootloader");
|
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};
|
GF(VERSION_CMD, "version");
|
||||||
constexpr auto CTRL_C = uint8_t{0x03};
|
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>
|
template <class Uart>
|
||||||
class Terminal {
|
class Terminal {
|
||||||
@@ -27,13 +53,43 @@ class Terminal {
|
|||||||
{
|
{
|
||||||
m_serial.init();
|
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()
|
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;
|
uint8_t inputByte;
|
||||||
bool handleInput = false;
|
|
||||||
|
|
||||||
while (m_serial.rxByte(inputByte)) {
|
while (m_serial.rxByte(inputByte)) {
|
||||||
if (isprint(inputByte) || inputByte == CTRL_C) {
|
if (isprint(inputByte) || inputByte == CTRL_C) {
|
||||||
@@ -41,9 +97,8 @@ class Terminal {
|
|||||||
|
|
||||||
// Handle Ctrl + C
|
// Handle Ctrl + C
|
||||||
if (inputByte == CTRL_C) {
|
if (inputByte == CTRL_C) {
|
||||||
m_serial << F("^C") << ENDL;
|
m_serial << F("^C") << detail::ENDL;
|
||||||
handleInput = true;
|
return true;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
// Echo
|
// Echo
|
||||||
else {
|
else {
|
||||||
@@ -62,89 +117,119 @@ class Terminal {
|
|||||||
if (m_serial.peek(inputByte) && (inputByte == '\r' || inputByte == '\n')) {
|
if (m_serial.peek(inputByte) && (inputByte == '\r' || inputByte == '\n')) {
|
||||||
m_serial.rxByte(inputByte);
|
m_serial.rxByte(inputByte);
|
||||||
}
|
}
|
||||||
m_serial << ENDL;
|
m_serial << detail::ENDL;
|
||||||
handleInput = true;
|
return true;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_inputSize >= INPUT_BUFFER_SIZE) {
|
if (m_inputSize >= INPUT_BUFFER_SIZE) {
|
||||||
m_serial << ENDL << F("WARNING: Terminal input buffer overflow!") << ENDL;
|
m_serial << detail::ENDL << F("WARNING: Terminal input buffer overflow!") << detail::ENDL;
|
||||||
handleInput = true;
|
return true;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (handleInput) {
|
return false;
|
||||||
parseInput();
|
|
||||||
m_inputSize = 0;
|
|
||||||
m_serial << F("$ ");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_state == State::MONITOR)
|
|
||||||
showState();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
static uint8_t parseFanSpeed()
|
||||||
static constexpr auto INPUT_BUFFER_SIZE = 128;
|
{
|
||||||
|
const auto setCmdLen = strlen_P(reinterpret_cast<const char *>(detail::SET_CMD));
|
||||||
|
|
||||||
enum class State {
|
if (m_inputSize > setCmdLen && substringEquals(m_inputBuffer, detail::SET_CMD, setCmdLen) &&
|
||||||
NONE,
|
m_inputSize < INPUT_BUFFER_SIZE) {
|
||||||
MONITOR,
|
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);
|
||||||
|
|
||||||
static Uart m_serial;
|
if (fanSpeedStrEnd != fanSpeedStr && *fanSpeedStr != '\0' && *fanSpeedStrEnd == '\0') {
|
||||||
static char m_inputBuffer[INPUT_BUFFER_SIZE];
|
if (fanSpeed >= 0 && fanSpeed <= 100)
|
||||||
static uint16_t m_inputSize;
|
return static_cast<uint8_t>(fanSpeed);
|
||||||
static State m_state;
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0xFF;
|
||||||
|
}
|
||||||
|
|
||||||
static void parseInput()
|
static void parseInput()
|
||||||
{
|
{
|
||||||
if (m_inputSize) {
|
if (m_inputSize) {
|
||||||
if (m_inputBuffer[m_inputSize - 1] == CTRL_C) {
|
if (m_inputBuffer[m_inputSize - 1] == CTRL_C) {
|
||||||
handleCtrlC();
|
handleCtrlC();
|
||||||
} else if (strncmp_P(m_inputBuffer, reinterpret_cast<const char *>(HELP_CMD), m_inputSize) == 0) {
|
} else if (m_state == State::NONE) {
|
||||||
printHelp();
|
if (substringEquals(m_inputBuffer, detail::HELP_CMD, m_inputSize)) {
|
||||||
} else if (strncmp_P(m_inputBuffer, reinterpret_cast<const char *>(SHOW_CMD), m_inputSize) == 0) {
|
printHelp();
|
||||||
showState();
|
} else if (substringEquals(m_inputBuffer, detail::SHOW_CMD, m_inputSize)) {
|
||||||
} else if (strncmp_P(m_inputBuffer, reinterpret_cast<const char *>(CURVE_CMD), m_inputSize) == 0) {
|
showState();
|
||||||
printCurve();
|
} else if (substringEquals(m_inputBuffer, detail::CURVE_CMD, m_inputSize)) {
|
||||||
} else if (strncmp_P(m_inputBuffer, reinterpret_cast<const char *>(MONITOR_CMD), m_inputSize) == 0) {
|
printCurve();
|
||||||
m_state = State::MONITOR;
|
} else if (substringEquals(m_inputBuffer, detail::MONITOR_CMD, m_inputSize)) {
|
||||||
} else if (strncmp_P(m_inputBuffer, reinterpret_cast<const char *>(BOOTLOADER_CMD), m_inputSize) == 0) {
|
m_state = State::MONITOR;
|
||||||
handleBootloader();
|
} else if (substringEquals(m_inputBuffer, detail::BOOTLOADER_CMD, m_inputSize)) {
|
||||||
} else {
|
handleBootloader();
|
||||||
printUnknown();
|
} 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()
|
static void handleCtrlC()
|
||||||
{
|
{
|
||||||
m_serial << F("Abort!") << ENDL;
|
m_serial << F("Abort!") << detail::ENDL;
|
||||||
m_state = State::NONE;
|
m_state = State::NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void printHelp()
|
static void printHelp()
|
||||||
{
|
{
|
||||||
m_serial << F("FanTemp command overview: ") << ENDL;
|
m_serial << F("FanTemp command overview: ") << detail::ENDL;
|
||||||
m_serial << HELP_CMD << F(" .......: print this help message") << ENDL;
|
m_serial << detail::HELP_CMD << F(" .......: prints this help message") << detail::ENDL;
|
||||||
m_serial << SHOW_CMD << F(" .......: shows current temperature and fan speed") << ENDL;
|
m_serial << detail::SHOW_CMD << F(" .......: shows current temperature and fan speed") << detail::ENDL;
|
||||||
m_serial << CURVE_CMD << F(" ......: shows the curve used to map temperature to fan speed") << ENDL;
|
m_serial << detail::CURVE_CMD << F(" ......: shows mapping from temperature to fan speed") << detail::ENDL;
|
||||||
m_serial << MONITOR_CMD << F(" ....: loops the show command until Ctrl + C is pressed") << ENDL;
|
m_serial << detail::MONITOR_CMD << F(" ....: loops the show command until Ctrl + C is pressed") << detail::ENDL;
|
||||||
m_serial << BOOTLOADER_CMD << F(" .: enters the bootloader after 10 seconds") << 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()
|
static void showState()
|
||||||
{
|
{
|
||||||
char floatBuffer[16];
|
if (Controller::m_dataAvailable) {
|
||||||
|
char floatBuffer[16];
|
||||||
|
|
||||||
sprintf(floatBuffer, "%.2f", Controller::m_adcSample);
|
dtostrf(Controller::m_adcSample, 0, 2, floatBuffer);
|
||||||
m_serial << F("ADC value ...: ") << floatBuffer << F(" / 1023") << 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") << 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") << ENDL;
|
m_serial << F("Temperature .: ") << floatBuffer << F(" C") << detail::ENDL;
|
||||||
m_serial << F("Fan speed ...: ") << Controller::m_fanSpeed << F("%") << 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()
|
static void printCurve()
|
||||||
@@ -154,33 +239,128 @@ class Terminal {
|
|||||||
m_serial.template txNumber<uint8_t, 10, 3, ' '>(Controller::mapTemperature(i));
|
m_serial.template txNumber<uint8_t, 10, 3, ' '>(Controller::mapTemperature(i));
|
||||||
m_serial << F("%\t");
|
m_serial << F("%\t");
|
||||||
for (uint8_t s = 0; s < Controller::mapTemperature(i); ++s) {
|
for (uint8_t s = 0; s < Controller::mapTemperature(i); ++s) {
|
||||||
m_serial << "#";
|
m_serial << '#';
|
||||||
}
|
}
|
||||||
m_serial << ENDL;
|
m_serial << detail::ENDL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void handleBootloader()
|
static void handleBootloader()
|
||||||
{
|
{
|
||||||
for (int8_t i = 10; i >= 0; --i) {
|
m_serial << F("Saving statistics to EEPROM") << detail::ENDL;
|
||||||
m_serial << i << ENDL;
|
Statistics::saveTemperatureHistogram();
|
||||||
_delay_ms(1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
m_serial << F("Entering bootloader...") << ENDL;
|
m_serial << F("Entering bootloader...") << detail::ENDL;
|
||||||
m_serial.flushTx();
|
m_serial.flushTx();
|
||||||
|
|
||||||
_delay_ms(1000);
|
_delay_ms(3000);
|
||||||
|
|
||||||
Bootloader::enter();
|
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()
|
static void printUnknown()
|
||||||
{
|
{
|
||||||
m_serial << F("Unknown command \"");
|
m_serial << F("Unknown command \"");
|
||||||
for (uint16_t i = 0; i < m_inputSize; ++i)
|
for (uint16_t i = 0; i < m_inputSize; ++i)
|
||||||
m_serial << static_cast<char>(m_inputBuffer[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>
|
template <class Uart>
|
||||||
typename Terminal<Uart>::State Terminal<Uart>::m_state = State::NONE;
|
typename Terminal<Uart>::State Terminal<Uart>::m_state = State::NONE;
|
||||||
|
|
||||||
|
template <class Uart>
|
||||||
|
uint64_t Terminal<Uart>::m_monitorDelayLastUpdate = 0;
|
||||||
|
|||||||
@@ -4,17 +4,6 @@
|
|||||||
|
|
||||||
class Thermistor {
|
class Thermistor {
|
||||||
public:
|
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 getResistance(double adcSample);
|
||||||
|
|
||||||
static double getTemperature(double resistance);
|
static double getTemperature(double resistance);
|
||||||
|
|||||||
1
fantemp/type
Submodule
1
fantemp/type
Submodule
Submodule fantemp/type added at ce31ef017f
Submodule fantemp/uart updated: cb436b11a8...04b6782ec4
Reference in New Issue
Block a user