Skip to content

Commit

Permalink
Added driver for ADS1219 ADC and switched TD3 firmware to use it inst…
Browse files Browse the repository at this point in the history
…ead of ADS1115
  • Loading branch information
fsinapi committed Oct 5, 2023
1 parent 1a5de60 commit aa2fd4d
Show file tree
Hide file tree
Showing 6 changed files with 260 additions and 50 deletions.
214 changes: 214 additions & 0 deletions stm32-modules/include/common/core/ads1219.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
/**
* @file ads1219.hpp
* @brief Driver file for the ADS1219 ADC.
*/

#pragma once

#include <array>
#include <concepts>
#include <cstdint>
#include <optional>
#include <variant>

namespace ADS1219 {

/**
* @brief The way that the ADS1219 policy is structured assumes that each
* ADC instance is provided an instance of a Policy type that is aware of
* which ADC it is talking to, so there is no need to specify the address
* or any other enumeration of the ADC
*/
template <typename Policy>
concept ADS1219Policy = requires(Policy& p, uint8_t u8, uint32_t u32,
std::array<uint8_t, 1> one_byte,
std::array<uint8_t, 2> two_bytes,
std::array<uint8_t, 3> &three_bytes) {
// A function to mark that an ADS1219 was initialized.
{ p.ads1219_mark_initialized() } -> std::same_as<void>;
// A function to check that an ADS1219 was initialized
{ p.ads1219_check_initialized() } -> std::same_as<bool>;
// Acquire the mutex for this ADC. The mutex must be initialized with the
// policy, so it is always valid
{ p.ads1219_get_lock() } -> std::same_as<void>;
// Release the mutex for this ADC. The mutex must be initialized with the
// policy, so it is always valid
{ p.ads1219_release_lock() } -> std::same_as<void>;
// Arm this ADC for a read operation
{ p.ads1219_arm_for_read() } -> std::same_as<bool>;
// Send an array of data - must work for both 1 and 2 byte messages
{ p.ads1219_i2c_send_data(one_byte) } -> std::same_as<bool>;
{ p.ads1219_i2c_send_data(two_bytes) } -> std::same_as<bool>;
// Read a chunk of data
{ p.ads1219_i2c_read_data(three_bytes) } -> std::same_as<bool>;
// Waits for a pulse from the ADC that was armed by this task. Maximum
// wait time is passed as a parameter.
{ p.ads1219_wait_for_pulse(u32) } -> std::same_as<bool>;
};

enum class Error {
ADCTimeout = 1, /**< Timed out waiting for ADC.*/
I2CTimeout = 2, /**< Timed out waiting for I2C.*/
DoubleArm = 3, /**< ADC already armed.*/
ADCPin = 4, /**< Pin is not allowed.*/
ADCInit = 5 /**< ADC is not initialized.*/
};

template <ADS1219Policy Policy>
class ADC {
public:
using ReadVal = std::variant<uint32_t, Error>;

ADC() = delete;
/**
* @brief Construct a new ADS1219 ADC
* @param[in] addr The I2C address of the ADC
* @param[in] id The ID of the ADC, which will link to one of the
* interrupts defined in \ref thermal_hardware.h
*/
explicit ADC(Policy& policy) : _policy(policy) {}

/**
* @brief Initialize the ADC. If run multiple times on the same ADC,
* this returns success. Will assert on inability to communicate with
* the ADC.
* @note Thread safe
* @warning Only call this from a FreeRTOS thread context.
*/
void initialize() {
// If this function returns true, we need to perform the
// init routine for this ADC.
get_lock();
if (!_policy.ads1219_check_initialized()) {
std::array<uint8_t, 1> data = {RESET_COMMAND};
_policy.ads1219_i2c_send_data(data);

_policy.ads1219_mark_initialized();
}
release_lock();
}
/**
* @brief Read a value from the ADC.
* @note Thread safe
* @warning Only call this from a FreeRTOS thread context.
* @param[in] pin The pin to read. Must be a value in the range
* [0, \ref pin_count)
* @return The value read by the ADC in ADC counts, or an error.
*/
auto read(uint8_t pin) -> ReadVal {
if (!initialized()) {
return ReadVal(Error::ADCInit);
}
if (!(pin < pin_count)) {
return ReadVal(Error::ADCPin);
}
get_lock();

auto ret = _policy.ads1219_arm_for_read();
if (!ret) {
release_lock();
return ReadVal(Error::DoubleArm);
}

// Configure the input pin
std::array<uint8_t, 2> config_data = {
WREG_CONFIG_COMMAND, pin_to_config_reg(pin)
};
ret = _policy.ads1219_i2c_send_data(config_data);
if (!ret) {
release_lock();
return ReadVal(Error::I2CTimeout);
}

// Start the new reading
ret = send_command(START_COMMAND);
if (!ret) {
release_lock();
return ReadVal(Error::I2CTimeout);
}

ret = _policy.ads1219_wait_for_pulse(max_pulse_wait_ms);
if (!ret) {
release_lock();
return ReadVal(Error::ADCTimeout);
}

// Send command so that next read will give the conversion data
ret = send_command(RDATA_COMMAND);
if (!ret) {
release_lock();
return ReadVal(Error::I2CTimeout);
}

// Read the result
std::array<uint8_t, 3> result{};
ret = _policy.ads1219_i2c_read_data(result);

release_lock();
if (ret) {
uint32_t value =
(static_cast<uint32_t>(result[0]) << 16) |
(static_cast<uint32_t>(result[1]) << 8) |
(static_cast<uint32_t>(result[2]));
return ReadVal(value);
}
return ReadVal(Error::I2CTimeout);
}

/**
* @brief Check if this ADC is initialized
* @return True if initialized, false if not.
*/
auto initialized() -> bool { return _policy.ads1219_check_initialized(); }

private:
Policy& _policy;

auto inline get_lock() -> void { _policy.ads1219_get_lock(); }
auto inline release_lock() -> void { _policy.ads1219_release_lock(); }

/**
* @brief Given a pin input, return the value that should be OR'd to
* the configuration register to set the input pin mode correctly.
*/
static inline auto pin_to_config_reg(uint8_t pin) -> uint8_t {
// The pin mux value for reading Ch0. All other channels increment from here.
static constexpr uint8_t PIN_MUX_CH0 = 3;
// The amount to left-shift the pin mux mask
static constexpr uint8_t PIN_MUX_SHIFT = 5;
return static_cast<uint8_t>((PIN_MUX_CH0 + pin) << PIN_MUX_SHIFT) | CONFIG_REG_DEFAULT;
}

/**
* @brief Send a single byte command to the ADS1219
*/
auto inline send_command(uint8_t cmd) -> bool {
std::array<uint8_t, 1> data = {cmd};
return _policy.ads1219_i2c_send_data(data);
}

/** Send this byte to reset the IC.*/
static constexpr uint8_t RESET_COMMAND = 0x06;

/** Default settings include:
* - Data rate of 20Hz
* - Gain of 1x
* - Single-shot mode
* - Internal VRef
*/
static constexpr uint8_t CONFIG_REG_DEFAULT = 0x00;

/** Send this byte to start a new reading.*/
static constexpr uint8_t START_COMMAND = 0x08;
/** Send this command to read the conversion results.*/
static constexpr uint8_t RDATA_COMMAND = 0x10;
/** Send this command to write the configuration register.*/
static constexpr uint8_t WREG_CONFIG_COMMAND = 0x40;

/** Number of pins on the ADC.*/
static constexpr uint16_t pin_count = 4;
/** Maximum time to wait for the pulse, in milliseconds.*/
static constexpr int max_pulse_wait_ms = 500;
};

} // namespace ADS1219
16 changes: 8 additions & 8 deletions stm32-modules/include/common/core/thermistor_conversion.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ struct Conversion {
* tables.
*/
Conversion(double bias_resistance_nominal_kohm, uint8_t adc_max_bits,
uint16_t disconnect_threshold)
uint32_t disconnect_threshold)
: _adc_max(static_cast<double>((1U << adc_max_bits) - 1)),
_adc_max_result(disconnect_threshold),
_bias_resistance_kohm(bias_resistance_nominal_kohm) {}
Expand All @@ -62,26 +62,26 @@ struct Conversion {
* NOTE - the param is_signed is ignored for now, but is useful to
* force differentiation between constructors.
*/
Conversion(double bias_resistance_nominal_kohm, uint16_t adc_max_value,
Conversion(double bias_resistance_nominal_kohm, uint32_t adc_max_value,
bool is_signed)
: _adc_max(static_cast<double>(adc_max_value)),
_adc_max_result(
static_cast<uint16_t>(static_cast<uint32_t>(adc_max_value))),
static_cast<uint32_t>(static_cast<uint32_t>(adc_max_value))),
_bias_resistance_kohm(bias_resistance_nominal_kohm) {
static_cast<void>(is_signed);
}

Conversion() = delete;

[[nodiscard]] auto convert(uint16_t adc_reading) const -> Result {
[[nodiscard]] auto convert(uint32_t adc_reading) const -> Result {
auto resistance = resistance_from_adc(adc_reading);
if (std::holds_alternative<Error>(resistance)) {
return resistance;
}
return temperature_from_resistance(std::get<double>(resistance));
}

[[nodiscard]] auto backconvert(double temperature) const -> uint16_t {
[[nodiscard]] auto backconvert(double temperature) const -> uint32_t {
auto entries = temperature_table_lookup(temperature);
if (std::holds_alternative<TableError>(entries)) {
if (std::get<TableError>(entries) == TableError::TABLE_END) {
Expand All @@ -99,16 +99,16 @@ struct Conversion {
((after_res - before_res) / (after_temp - before_temp)) *
(temperature - before_temp) +
before_res;
return static_cast<uint16_t>(
return static_cast<uint32_t>(
_adc_max / ((_bias_resistance_kohm / resistance) + 1.0));
}

private:
const double _adc_max;
const uint16_t _adc_max_result;
const uint32_t _adc_max_result;
const double _bias_resistance_kohm;

[[nodiscard]] auto resistance_from_adc(uint16_t adc_count) const -> Result {
[[nodiscard]] auto resistance_from_adc(uint32_t adc_count) const -> Result {
if (adc_count >= _adc_max_result) {
return Result(Error::OUT_OF_RANGE_LOW);
}
Expand Down
30 changes: 21 additions & 9 deletions stm32-modules/include/tempdeck-gen3/firmware/thermistor_policy.hpp
Original file line number Diff line number Diff line change
@@ -1,29 +1,41 @@
#pragma once

#include <array>
#include <atomic>
#include <cstdint>
#include <optional>

#include "firmware/i2c_hardware.h"
#include "ot_utils/freertos/freertos_synchronization.hpp"

class ThermistorPolicy {
public:
static constexpr uint8_t ADC_ADDRESS = (0x48) << 1;
static constexpr uint8_t ADC_ADDRESS = (0x40) << 1;
explicit ThermistorPolicy()
: _initialized(false),
// NOLINTNEXTLINE(readability-redundant-member-init)
_mutex() {}

[[nodiscard]] auto get_time_ms() const -> uint32_t;
auto sleep_ms(uint32_t ms) -> void;
auto ads1115_mark_initialized() -> void;
auto ads1115_check_initialized() -> bool;
auto ads1115_get_lock() -> void;
auto ads1115_release_lock() -> void;
auto ads1115_arm_for_read() -> bool;
auto ads1115_i2c_write_16(uint8_t reg, uint16_t data) -> bool;
auto ads1115_i2c_read_16(uint8_t reg) -> std::optional<uint16_t>;
auto ads1115_wait_for_pulse(uint32_t max_wait) -> bool;
auto ads1219_mark_initialized() -> void;
auto ads1219_check_initialized() -> bool;
auto ads1219_get_lock() -> void;
auto ads1219_release_lock() -> void;
auto ads1219_arm_for_read() -> bool;

template <size_t N>
auto ads1219_i2c_send_data(std::array<uint8_t, N> &data) -> bool {
return i2c_hardware_write_data(I2C_BUS_THERMAL, ADC_ADDRESS,
data.data(), N);
}

template <size_t N>
auto ads1219_i2c_read_data(std::array<uint8_t, N> &data) -> bool {
return i2c_hardware_read_data(I2C_BUS_THERMAL, ADC_ADDRESS, data.data(),
N);
}
auto ads1219_wait_for_pulse(uint32_t max_wait) -> bool;
auto get_imeas_adc_reading() -> uint32_t;

private:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,9 @@ class ThermalTask {
// The circuit is configured such that 1.5v is the max voltage from the
// thermistor.
static constexpr double ADC_MAX_V = 1.5;
// ADC results are signed 16-bit integers
static constexpr uint16_t ADC_BIT_MAX = static_cast<uint16_t>(
(ADC_MAX_V * static_cast<double>(0x7FFF)) / ADC_VREF);
// ADC results are signed 24-bit integers
static constexpr uint32_t ADC_BIT_MAX = static_cast<uint32_t>(
(ADC_MAX_V * static_cast<double>(0x7FFFFF)) / ADC_VREF);

// The threshold at which the fan is turned on to cool the heatsink
// during idle periods.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#pragma once

#include "core/ads1115.hpp"
#include "core/ads1219.hpp"
#include "hal/message_queue.hpp"
#include "tempdeck-gen3/messages.hpp"
#include "tempdeck-gen3/tasks.hpp"
Expand Down Expand Up @@ -49,7 +49,7 @@ class ThermistorTask {
if (!_task_registry) {
return;
}
auto adc = ADS1115::ADC(policy);
auto adc = ADS1219::ADC(policy);

if (!adc.initialized()) {
adc.initialize();
Expand All @@ -68,24 +68,24 @@ class ThermistorTask {

private:
template <ThermistorPolicy Policy>
auto read_pin(ADS1115::ADC<Policy>& adc, uint16_t pin, Policy& policy)
-> uint16_t {
auto read_pin(ADS1219::ADC<Policy>& adc, uint16_t pin, Policy& policy)
-> uint32_t {
static constexpr uint8_t MAX_TRIES = 5;
static constexpr uint32_t RETRY_DELAY = 5;
uint8_t tries = 0;
auto result = typename ADS1115::ADC<Policy>::ReadVal();
auto result = typename ADS1219::ADC<Policy>::ReadVal();

while (true) {
result = adc.read(pin);
if (std::holds_alternative<uint16_t>(result)) {
return std::get<uint16_t>(result);
if (std::holds_alternative<uint32_t>(result)) {
return std::get<uint32_t>(result);
}
if (++tries < MAX_TRIES) {
// Short delay for reliability
policy.sleep_ms(RETRY_DELAY);
} else {
// Retries expired
return static_cast<uint16_t>(std::get<ADS1115::Error>(result));
return static_cast<uint32_t>(std::get<ADS1219::Error>(result));
}
}
}
Expand Down
Loading

0 comments on commit aa2fd4d

Please sign in to comment.