From b1105bef1a233ad3e5b61e4d87eb92ab9a23c01e Mon Sep 17 00:00:00 2001 From: Henrik Hose Date: Tue, 7 Mar 2023 23:30:13 +0100 Subject: [PATCH] [driver] Add MAX31865 driver with STM32F469 Discovery example --- README.md | 13 +- .../stm32f469_discovery/max31865/main.cpp | 87 +++++++ .../stm32f469_discovery/max31865/project.xml | 14 ++ src/modm/driver/temperature/max31865.hpp | 217 ++++++++++++++++++ src/modm/driver/temperature/max31865.lb | 32 +++ src/modm/driver/temperature/max31865_impl.hpp | 124 ++++++++++ 6 files changed, 481 insertions(+), 6 deletions(-) create mode 100644 examples/stm32f469_discovery/max31865/main.cpp create mode 100644 examples/stm32f469_discovery/max31865/project.xml create mode 100644 src/modm/driver/temperature/max31865.hpp create mode 100644 src/modm/driver/temperature/max31865.lb create mode 100644 src/modm/driver/temperature/max31865_impl.hpp diff --git a/README.md b/README.md index 6d93882896..eb77f5cfa7 100644 --- a/README.md +++ b/README.md @@ -738,47 +738,48 @@ you specific needs. LTC2984 MAX31855 +MAX31865 MAX6966 MAX7219 MCP23x17 MCP2515 -MCP7941x +MCP7941x MCP990X MMC5603 MS5611 MS5837 NOKIA5110 -NRF24 +NRF24 TFT-DISPLAY PAT9125EL PCA8574 PCA9535 PCA9548A -PCA9685 +PCA9685 SH1106 SIEMENS-S65 SIEMENS-S75 SK6812 SK9822 -SSD1306 +SSD1306 ST7586S ST7789 STTS22H STUSB4500 SX1276 -TCS3414 +TCS3414 TCS3472 TLC594x TMP102 TMP12x TMP175 -TOUCH2046 +TOUCH2046 VL53L0 VL6180 WS2812 diff --git a/examples/stm32f469_discovery/max31865/main.cpp b/examples/stm32f469_discovery/max31865/main.cpp new file mode 100644 index 0000000000..e1a3f0b3ba --- /dev/null +++ b/examples/stm32f469_discovery/max31865/main.cpp @@ -0,0 +1,87 @@ +// coding: utf-8 +/* + * Copyright (c) 2023, Henrik Hose + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include +#include +#include + +using namespace Board; + +using SpiMaster = modm::platform::SpiMaster2; + +using Cs = D9; +using Mosi = D11; +using Miso = D12; +using Sck = D13; + +class ThermocoupleThread : public modm::pt::Protothread +{ +public: + ThermocoupleThread() : data{}, pt100(data) {} + + bool + run() + { + PT_BEGIN(); + pt100.initialize(); + + while (true) + { + MODM_LOG_INFO << "\nNew readout:" << modm::endl; + RF_CALL_BLOCKING(pt100.readout()); + + MODM_LOG_INFO << " resistance : " << data.getResistance() << " Ohm" + << modm::endl; + MODM_LOG_INFO << " temperature fast: " << data.getTemperatureFast() << " degrees" + << modm::endl; + MODM_LOG_INFO << " temperature precise: " << data.getTemperaturePrecise() << " degrees" + << modm::endl; + + timeout.restart(std::chrono::milliseconds(1000)); + PT_WAIT_UNTIL(timeout.isExpired()); + } + + PT_END(); + } + +private: + modm::max31865pt100::Data data; + modm::Max31865 pt100; + + modm::ShortTimeout timeout; +}; + +ThermocoupleThread pt100Thread{}; + +int +main() +{ + Board::initialize(); + Cs::setOutput(modm::Gpio::High); + + SpiMaster::connect(); + SpiMaster::initialize(); + + MODM_LOG_INFO << "==========MAX 31865 Test==========" << modm::endl; + MODM_LOG_DEBUG << "Debug logging here" << modm::endl; + MODM_LOG_INFO << "Info logging here" << modm::endl; + MODM_LOG_WARNING << "Warning logging here" << modm::endl; + MODM_LOG_ERROR << "Error logging here" << modm::endl; + MODM_LOG_INFO << "===============================" << modm::endl; + + while (true) + { + pt100Thread.run(); + Board::LedOrange::toggle(); + } + + return 0; +} \ No newline at end of file diff --git a/examples/stm32f469_discovery/max31865/project.xml b/examples/stm32f469_discovery/max31865/project.xml new file mode 100644 index 0000000000..8c5e189536 --- /dev/null +++ b/examples/stm32f469_discovery/max31865/project.xml @@ -0,0 +1,14 @@ + + modm:disco-f469ni + + + + + modm:driver:max31865 + modm:platform:gpio + modm:platform:spi:2 + modm:processing:protothread + modm:processing:timer + modm:build:scons + + \ No newline at end of file diff --git a/src/modm/driver/temperature/max31865.hpp b/src/modm/driver/temperature/max31865.hpp new file mode 100644 index 0000000000..b4a4eea934 --- /dev/null +++ b/src/modm/driver/temperature/max31865.hpp @@ -0,0 +1,217 @@ +// coding: utf-8 +// ---------------------------------------------------------------------------- +/* + * Copyright (c) 2023, Henrik Hose + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#ifndef MODM_MAX31865_HPP +#define MODM_MAX31865_HPP + +#include +#include +#include +#include + +namespace modm +{ + +/// @ingroup modm_driver_max31865 +// Standard values for IEC 751 (PT100) with 400 Ohm reference +template +struct max31865 +{ + + enum class Fault : uint8_t + { + RtdHighThreshold = Bit7, + RtdLowThreshold = Bit6, + RefinMGreater85Percentbias = Bit5, + RefinMLess85PercentVbias = Bit4, + RtdinMLess85PercentVbias = Bit3, + OverUnderVoltage = Bit2 + }; + MODM_FLAGS8(Fault) + + enum class Config : uint8_t + { + VBias = Bit7, + ConversionModeAuto = Bit6, + OneShot = Bit5, + ThreeWire = Bit4, + FaultDetectionCycle1 = Bit3, + FaultDetectionCycle0 = Bit2, + FaultStatusClear = Bit1, + Rejection = Bit0 + }; + MODM_FLAGS8(Config) + + enum class FaultDetectionWrite : uint8_t + { + NoAction = 0b00, + FaultDetectionAutomaticDelay = 0b01, + RunFaultDetectionWithManualDelayCycle1 = 0b10, + FinishFaultDetectionWithManualDelayCycle2 = 0b11, + }; + typedef Configuration + FaultDetectionWrite_t; // Bit 8..10 + + enum class FaultDetectionRead : uint8_t + { + FaultDetectionFinished = 0b00, + AutomaticDetectionStillRunning = 0b01, + ManualCycle1Running = + 0b10, // waiting for user to write FinishFaultDetectionWithManualDelayCycle2 + ManualCycle2StillRunning = 0b11, + }; + typedef Configuration FaultDetectionRead_t; // Bit 8..10 + + enum class Rejection : uint8_t + { + Rejection60Hz = 0b0, + Rejection50Hz = 0b1, + }; + typedef Configuration Rejection_t; // Bit 8..10 + + struct FaultThreshold + { + typedef uint16_t RtdResistance_t; + }; + + static constexpr uint16_t + rtdfaultthresh(const max31865::FaultThreshold::RtdResistance_t res) + { + return static_cast(res << 1); + }; + + enum class Register : uint8_t + { + ReadConfiguration = 0x00, + ReadRtdMsb = 0x01, + ReadRtdLsb = 0x02, + ReadHighFaultThresholdMsb = 0x03, + ReadHighFaultThresholdLsb = 0x04, + ReadLowFaultThresholdMsb = 0x05, + ReadLowFaultThresholdLsb = 0x06, + ReadFaultStatus = 0x07, + + WriteConfiguration = 0x80, + WriteHighFaultThresholdMsb = 0x83, + WriteHighFaultThresholdLsb = 0x84, + WriteLowFaultThresholdMsb = 0x85, + WriteLowFaultThresholdLsb = 0x86, + }; + + struct modm_packed Data + { + /// @return measure resistance in ohm + constexpr float + getResistance() const + { + const uint16_t adccode = data >> 1; + return adccode / 32768.f * Rref; + } + + /// @return fast temperature in degrees celsius, about 0.3 degrees error between 0 and 100 + /// degrees celsius + constexpr float + getTemperatureFast() const + { + return (getResistance() - R0) / alpha / 100.f; + } + + /// @return slow but accurate temperature in degrees celsius + constexpr double + getTemperaturePrecise() const + { + const double res = getResistance(); + double T = getTemperatureFast(); + const double c0 = T <= 0 ? c : 0; + + // Do some fixed number of newton steps for root finding + // on Callendar Van Dusen equation: + // R = R0*(1+a*T+b*T*T+c*(T-100)*T*T*T) + // Newton seems to need double precision to achieve 1.e-10 residual?! + for (int i = 0; i < 10; i++) + { + const double R = + double{R0} * (1 + (a * T) + (b * T * T) + (c0 * (T - 100) * T * T * T)) - res; + const double Rdash = + double{R0} * (a + (2 * b * T) + c0 * (((4 * T) - 300) * T * T)); + T -= R / Rdash; + if (std::abs(R) <= 1.e-10) { break; } + } + + return T; + } + + uint16_t data; + }; +}; // struct max31865 + +using max31865pt100 = max31865<>; + +/** + * @tparam SpiMaster + * @tparam Cs + * + * @author Henrik Hose + * @ingroup modm_driver_max31865 + */ +template +class Max31865 : public max31865pt100, + public modm::SpiDevice, + protected modm::NestedResumable<3> +{ +public: + /** + * @param data pointer to buffer of the internal data of type Data + */ + Max31865(Data &data); + + /// Call this function once before using the device + void + initialize(); + + /// Read the raw data from the sensor + modm::ResumableResult + readout(); + +public: + /// Get the data object for this sensor + inline Data & + getData() + { + return data; + } + +private: + Data &data; + std::array buffer; + Config_t config; + + modm::ShortTimeout timeout; + + modm::ResumableResult + writeSingleRegister(Register address, uint8_t data); + + modm::ResumableResult + readSingleRegister(Register address); + + modm::ResumableResult + readTwoRegisters(Register address); +}; + +} // namespace modm + +#include "max31865_impl.hpp" + +#endif // MODM_MAX31865_HPP \ No newline at end of file diff --git a/src/modm/driver/temperature/max31865.lb b/src/modm/driver/temperature/max31865.lb new file mode 100644 index 0000000000..17ebc3b1ba --- /dev/null +++ b/src/modm/driver/temperature/max31865.lb @@ -0,0 +1,32 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Copyright (c) 2023, Henrik Hose +# +# This file is part of the modm project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# ----------------------------------------------------------------------------- + + +def init(module): + module.name = ":driver:max31865" + module.description = """\ +# MAX31865 RTD-to-Digital Converter + +[Datasheet](https://datasheets.maximintegrated.com/en/ds/MAX31865.pdf) +""" + +def prepare(module, options): + module.depends( + ":architecture:gpio", + ":architecture:spi.device", + ":processing:resumable") + return True + +def build(env): + env.outbasepath = "modm/src/modm/driver/temperature" + env.copy("max31865.hpp") + env.copy("max31865_impl.hpp") \ No newline at end of file diff --git a/src/modm/driver/temperature/max31865_impl.hpp b/src/modm/driver/temperature/max31865_impl.hpp new file mode 100644 index 0000000000..cfe6b98492 --- /dev/null +++ b/src/modm/driver/temperature/max31865_impl.hpp @@ -0,0 +1,124 @@ +// coding: utf-8 +// ---------------------------------------------------------------------------- +/* + * Copyright (c) 2023, Henrik Hose + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#ifndef MODM_MAX31865_HPP +#error "Don't include this file directly, use 'max31865.hpp' instead!" +#endif + +namespace modm +{ + +template +Max31865::Max31865(Data &data) : data(data) +{} + +template +void +Max31865::initialize() +{ + + this->attachConfigurationHandler([]() { + static_assert(std::is_same_v); + SPI2->CR1 &= ~SPI_CR1_SPE; + __DSB(); + SpiMaster::setDataMode(SpiMaster::DataMode::Mode3); + SPI2->CR1 |= SPI_CR1_SPE; + __DSB(); + }); + + Cs::setOutput(modm::Gpio::High); + + config = Config(); + config.set(Config::FaultStatusClear); + config.set(Rejection_t(Rejection::Rejection50Hz)); + RF_CALL_BLOCKING(writeSingleRegister(Register::WriteConfiguration, config.value)); + config = Config_t(RF_CALL_BLOCKING(readSingleRegister(Register::ReadConfiguration))); +} + +template +modm::ResumableResult +Max31865::readout() +{ + RF_BEGIN(); + config.set(Config::VBias); + RF_CALL(writeSingleRegister(Register::WriteConfiguration, config.value)); + timeout.restart(std::chrono::milliseconds(10)); + RF_WAIT_UNTIL(timeout.isExpired()); + + config.set(Config::OneShot); + RF_CALL(writeSingleRegister(Register::WriteConfiguration, config.value)); + timeout.restart(std::chrono::milliseconds(65)); + RF_WAIT_UNTIL(timeout.isExpired()); + + data.data = RF_CALL(readTwoRegisters(Register::ReadRtdMsb)); + + config.reset(Config::VBias); + RF_CALL(writeSingleRegister(Register::WriteConfiguration, config.value)); + + RF_END(); +} + +template +modm::ResumableResult +Max31865::readSingleRegister(Register address) +{ + RF_BEGIN(); + RF_WAIT_UNTIL(this->acquireMaster()); + + Cs::reset(); + buffer[0] = uint8_t(address); + RF_CALL_BLOCKING(SpiMaster::transfer(buffer.data(), nullptr, 1)); + RF_CALL_BLOCKING(SpiMaster::transfer(nullptr, buffer.data(), 1)); + + if (this->releaseMaster()) { Cs::set(); } + modm::delay(100ms); + + RF_END_RETURN(buffer[0]); +} + +template +modm::ResumableResult +Max31865::readTwoRegisters(Register address) +{ + RF_BEGIN(); + RF_WAIT_UNTIL(this->acquireMaster()); + + Cs::reset(); + buffer[0] = uint8_t(address); + RF_CALL_BLOCKING(SpiMaster::transfer(buffer.data(), nullptr, 1)); + RF_CALL_BLOCKING(SpiMaster::transfer(nullptr, buffer.data(), 2)); + + if (this->releaseMaster()) { Cs::set(); } + modm::delay(100ms); + + RF_END_RETURN(static_cast(buffer[0] << 8 | buffer[1])); +} + +template +modm::ResumableResult +Max31865::writeSingleRegister(Register address, uint8_t data) +{ + RF_BEGIN(); + RF_WAIT_UNTIL(this->acquireMaster()); + + Cs::reset(); + buffer[0] = uint8_t(address); + buffer[1] = data; + RF_CALL(SpiMaster::transfer(buffer.data(), nullptr, 2)); + + if (this->releaseMaster()) { Cs::set(); } + + RF_END(); +} + +} // namespace modm \ No newline at end of file