blob: 8b1f740cc396b7552acc4971bdc92a5426d1f5cb [file] [log] [blame]
#include <Arduino.h>
#include <SPI.h>
#include <bitset>
#include <cstddef>
#include <stdint.h>
#include "gonk/adc.h"
#include "gonk_adc/adc_measurement.pwpb.h"
#define PW_LOG_LEVEL PW_LOG_LEVEL_INFO
#define PW_LOG_MODULE_NAME "Adc"
#include "pw_bytes/bit.h"
#include "pw_bytes/endian.h"
#include "pw_bytes/span.h"
#include "pw_log/log.h"
#include "pw_result/result.h"
#include "pw_span/span.h"
#include "pw_status/status.h"
namespace gonk::adc {
namespace {
volatile uint8_t fpga_valid_pulse_ = 0;
void io_valid_rising_isr() { fpga_valid_pulse_ = 1; }
void ClearValidPulse() { fpga_valid_pulse_ = 0; }
} // namespace
constexpr uint8_t INA229_CONFIG = 0x0;
constexpr uint8_t INA229_ADC_CONFIG = 0x1;
constexpr uint8_t INA229_SHUNT_CALIBRATION = 0x2;
constexpr uint8_t INA229_SHUNT_TEMP_COEFFICIENT = 0x3;
constexpr uint8_t INA229_VSHUNT = 0x4;
constexpr uint8_t INA229_VBUS = 0x5;
constexpr uint8_t INA229_DIETEMP = 0x6;
constexpr uint8_t INA229_CURRENT = 0x7;
constexpr uint8_t INA229_POWER = 0x8;
constexpr uint8_t INA229_ENERGY = 0x9;
constexpr uint8_t INA229_CHARGE = 0xA;
constexpr uint8_t INA229_DIAG_ALERT = 0xB;
constexpr uint8_t INA229_SHUNT_OVERVOLT_THRESHOLD = 0xC;
constexpr uint8_t INA229_SHUNT_UNDERVOLT_THRESHOLD = 0xD;
constexpr uint8_t INA229_BUS_OVERVOLT_THRESHOLD = 0xE;
constexpr uint8_t INA229_BUS_UNDERVOLT_THRESHOLD = 0xF;
constexpr uint8_t INA229_TEMP_LIMIT = 0x10;
constexpr uint8_t INA229_POWER_LIMIT = 0x11;
constexpr uint8_t INA229_MANUFACTURER_ID = 0x3e;
constexpr uint8_t INA229_DEVICE_ID = 0x3f;
constexpr uint8_t INA229RegisterByteSize[]{
[INA229_CONFIG] = 2,
[INA229_ADC_CONFIG] = 2,
[INA229_SHUNT_CALIBRATION] = 2,
[INA229_SHUNT_TEMP_COEFFICIENT] = 2,
[INA229_VSHUNT] = 3,
[INA229_VBUS] = 3,
[INA229_DIETEMP] = 2,
[INA229_CURRENT] = 3,
[INA229_POWER] = 3,
[INA229_ENERGY] = 5,
[INA229_CHARGE] = 5,
[INA229_DIAG_ALERT] = 2,
[INA229_SHUNT_OVERVOLT_THRESHOLD] = 2,
[INA229_SHUNT_UNDERVOLT_THRESHOLD] = 2,
[INA229_BUS_OVERVOLT_THRESHOLD] = 2,
[INA229_BUS_UNDERVOLT_THRESHOLD] = 2,
[INA229_TEMP_LIMIT] = 2,
[INA229_POWER_LIMIT] = 2,
};
constexpr pw::span<const uint8_t>
kINA229RegisterByteSize(INA229RegisterByteSize);
Adc::Adc(Stream &serial_stream, SPIClass &fpga_spi, uint32_t fpga_spi_baudrate,
uint16_t fpga_cs_pin, uint16_t fpga_mode_pin, uint16_t fpga_reset_pin,
uint16_t fpga_valid_pin)
: serial_(serial_stream), fpga_spi_(fpga_spi), fpga_reset_(fpga_reset_pin),
fpga_mode_(fpga_mode_pin), fpga_valid_(fpga_valid_pin),
fpga_cs_(fpga_cs_pin),
spi_settings_(fpga_spi_baudrate, MSBFIRST, SPI_MODE1) {
sample_read_index_ = 0;
sampled_adc_count_ = kMaxStreamingAdcCount;
measurement_timestamp_ = 0;
previous_timestamp_ = 0;
sample_read_time_.fill(0);
}
void Adc::SetReadWriteMode() {
// Reset FPGA logic
digitalWrite(fpga_reset_, HIGH);
delay(10);
// Toggle mode to 0
digitalWrite(fpga_mode_, LOW);
delay(10);
// Release reset
digitalWrite(fpga_reset_, LOW);
delay(10);
}
void Adc::SetContinuousReadMode() {
// Reset FPGA logic
digitalWrite(fpga_reset_, HIGH);
delay(10);
// Valid signal can go high from the FPGA side as soon as mode is set to 1.
ClearValidPulse();
// Toggle mode to 1
digitalWrite(fpga_mode_, HIGH);
delay(10);
// Release reset
digitalWrite(fpga_reset_, LOW);
delay(10);
}
Status Adc::WaitForFpgaIOValid(uint32_t timeout_ms = 2000) {
PW_LOG_DEBUG("FPGA Valid Signal: Waiting");
uint32_t last_update = millis();
uint32_t this_update = millis();
bool done = false;
int valid = 0;
while (!done) {
valid = digitalRead(fpga_valid_);
if (valid == 1) {
PW_LOG_DEBUG("FPGA Valid Signal: Result Ready");
break;
}
this_update = millis();
// If more than two seconds have passed something likely went wrong.
if (this_update > last_update + timeout_ms) {
PW_LOG_ERROR("FPGA Valid Signal: Timeout");
last_update = this_update;
return pw::Status::DeadlineExceeded();
}
}
return pw::OkStatus();
}
Status Adc::WaitForFpgaIOValidPulse(uint32_t timeout_ms = 2000) {
PW_LOG_DEBUG("FPGA Valid Pulse: Waiting");
uint32_t last_update = millis();
uint32_t this_update = millis();
bool done = false;
while (!done) {
if (fpga_valid_pulse_ == 1) {
PW_LOG_DEBUG("FPGA Valid Pulse: Detected");
break;
}
this_update = millis();
// If more than two seconds have passed something likely went wrong.
if (this_update > last_update + timeout_ms) {
PW_LOG_ERROR("FPGA Valid Pulse: Timeout");
last_update = this_update;
return pw::Status::DeadlineExceeded();
}
}
return pw::OkStatus();
}
uint32_t Adc::ADCAddress(uint8_t adc_number, uint8_t adc_command,
uint8_t mode = 1) {
// Address + Read/Write bit (24 bits)
//
// [23:18] - Dont Care bits
// [17:7] - ADC Select. These 11 bits are used to select one or multiple ADCs
// for a write operation.
// [17] - ADC11 Select (ADC # from schematics)
// [16] - ADC10 Select
// ...
// [8] - ADC2 Select
// [7] - ADC1 Select
// NoTE: Select only one ADC for a read operation.
// [6:1] - Register offset address for INA229.
// [0] - R/W bit. 1: READ, 0: WRITE
const uint8_t adc_index = adc_number - 1;
return (
// ADC selection
1 << (adc_index + 7)
// ADC Register
| ((adc_command & 0x3f) << 1)
// 1=read, 0=write
| mode);
}
uint32_t Adc::ADCAddressWriteSelection(uint16_t adc_selection,
uint8_t adc_command) {
return (
// Set adc selection bits
(adc_selection & 0x7FF) << 7
// ADC Register
| ((adc_command & 0x3f) << 1)
// 1=read, 0=write
| 0);
}
Status Adc::SelectContinuousReadAdcs(uint16_t adc_selection) {
// Count the number of selected ADCs.
uint16_t selected_bits = adc_selection;
uint16_t set_bits_count;
for (set_bits_count = 0; selected_bits; selected_bits >>= 1) {
set_bits_count += selected_bits & 1;
}
sampled_adc_count_ = set_bits_count;
if (sampled_adc_count_ > 11 || sampled_adc_count_ == 0) {
PW_LOG_ERROR("Invalid ADC channel selection: %x", adc_selection);
return pw::Status::OutOfRange();
}
PW_LOG_INFO("Selected ADC count: %d", sampled_adc_count_);
uint32_t adc_address =
ADCAddressWriteSelection(adc_selection, /*adc_command=*/0);
// Set bit 18 to signal to the FPGA to treat this as a continuous read mode
// update.
adc_address = 1 << 18 | adc_address;
StartSpiTransaction();
// Write the ADC address selection.
WriteAddress(adc_address);
// NOTE: The FPGA does not generate a valid signal for this write.
EndSpiTransaction();
// return wait_result;
return pw::OkStatus();
}
uint32_t Adc::ADCAddressWrite(uint8_t adc_number, uint8_t adc_command) {
// Address with mode=0 for a write.
return ADCAddress(adc_number, adc_command, 0);
}
uint32_t Adc::ADCAddressWriteAll(uint8_t adc_command) {
return (
// Select all 11 ADCs
0x7FF << 7
// ADC Register
| ((adc_command & 0x3f) << 1)
// 1=read, 0=write
| 0);
}
pw::Result<uint8_t> Adc::RegisterSize(uint8_t adc_register) {
if (adc_register == INA229_MANUFACTURER_ID ||
adc_register == INA229_DEVICE_ID) {
return 2;
}
if (adc_register >= kINA229RegisterByteSize.size()) {
PW_LOG_ERROR("Invalid register: %x", adc_register);
return pw::Status::OutOfRange();
}
return INA229RegisterByteSize[adc_register];
}
void Adc::StartSpiTransaction() { fpga_spi_.beginTransaction(spi_settings_); }
void Adc::EndSpiTransaction() { fpga_spi_.endTransaction(); }
void Adc::WriteAddress(uint32_t adc_address) {
uint8_t address[3];
address[0] = (adc_address >> 16) & 0xff;
address[1] = (adc_address >> 8) & 0xff;
address[2] = adc_address & 0xff;
PW_LOG_DEBUG("WriteAddress: %x %x %x", address[2], address[1], address[0]);
fpga_spi_.transfer(address, 3);
}
pw::Result<pw::ConstByteSpan> Adc::ReadData(pw::ByteSpan read_buffer) {
fpga_spi_.transfer(read_buffer.data(), read_buffer.size());
return pw::ConstByteSpan(read_buffer);
}
Status Adc::WriteData(pw::ByteSpan write_buffer) {
fpga_spi_.transfer(write_buffer.data(), write_buffer.size());
return pw::OkStatus();
}
pw::Result<pw::ConstByteSpan> Adc::GetManufacturerID(uint8_t adc_number) {
return GetRegister(adc_number, INA229_MANUFACTURER_ID);
}
pw::Result<pw::ConstByteSpan> Adc::GetRegister(uint8_t adc_number,
uint8_t adc_register) {
uint32_t adc_address = ADCAddress(adc_number, adc_register);
StartSpiTransaction();
WriteAddress(adc_address);
Status wait_result = WaitForFpgaIOValid();
if (!wait_result.ok()) {
EndSpiTransaction();
return wait_result;
}
pw::Result<uint8_t> register_size_result = RegisterSize(adc_register);
if (!register_size_result.ok()) {
return register_size_result.status();
}
std::array<std::byte, 5> read_buffer;
pw::ByteSpan read_span =
pw::ByteSpan(read_buffer.data(), register_size_result.value());
const pw::Result<pw::ConstByteSpan> read_result = ReadData(read_span);
if (!read_result.ok()) {
return read_result.status();
}
EndSpiTransaction();
return read_result.value();
}
pw::Result<pw::ConstByteSpan> Adc::GetThreeBytes() {
std::array<std::byte, 3> read_buffer;
pw::ByteSpan read_span = pw::ByteSpan(read_buffer);
return ReadData(read_span);
}
Status Adc::UpdateContinuousMeasurements() {
uint32_t start_time = micros();
StartSpiTransaction();
Status wait_result = WaitForFpgaIOValid();
if (!wait_result.ok()) {
EndSpiTransaction();
return wait_result;
}
previous_timestamp_ = measurement_timestamp_;
measurement_timestamp_ = micros();
measurement_delta_micros_ = measurement_timestamp_ - previous_timestamp_;
for (int i = 0; i < sampled_adc_count_; i++) {
// Read VSHUNT
std::array<std::byte, 3> vshunt_read_buffer;
const pw::Result<pw::ConstByteSpan> vshunt_read_result =
ReadData(pw::ByteSpan(vshunt_read_buffer));
if (!vshunt_read_result.ok()) {
PW_LOG_ERROR("vshunt_read failed i=%d", i);
return vshunt_read_result.status();
}
int32_t vshunt_value = VoltageMeasurement(vshunt_read_result.value());
PW_LOG_DEBUG("Continuous Read ADC #%02d: VSHUNT = %02x %02x %02x = %d", i,
vshunt_read_result.value()[0], vshunt_read_result.value()[1],
vshunt_read_result.value()[2], vshunt_value);
// Read VBUS
std::array<std::byte, 3> vbus_read_buffer;
const pw::Result<pw::ConstByteSpan> vbus_read_result =
ReadData(pw::ByteSpan(vbus_read_buffer));
if (!vbus_read_result.ok()) {
PW_LOG_ERROR("vbus_read failed i=%d", i);
return vbus_read_result.status();
}
int32_t vbus_value = VoltageMeasurement(vbus_read_result.value());
PW_LOG_DEBUG("Continuous Read ADC #%02d: VBUS = %02x %02x %02x = %d", i,
vbus_read_result.value()[0], vbus_read_result.value()[1],
vbus_read_result.value()[2], vbus_value);
// Save this measurement.
measurements_[i].vbus_value_ = vbus_value;
measurements_[i].vshunt_value_ = vshunt_value;
for (size_t i = 0; i < 3; i++) {
measurements_[i].vbus_bytes_[i] = vbus_read_result.value()[i];
measurements_[i].vshunt_bytes_[i] = vshunt_read_result.value()[i];
}
}
// All data has been read: clear the pulse signal variable.
ClearValidPulse();
EndSpiTransaction();
uint32_t end_time = micros();
sample_read_time_[sample_read_index_] = end_time - start_time;
sample_read_index_ = (sample_read_index_ + 1) % sample_read_time_.size();
return pw::OkStatus();
}
// Ensure the proto max size is == the expected kMaxStreamingAdcCount
static_assert(Payload::kAdcMeasurementsMaxSize == kMaxStreamingAdcCount);
Status Adc::WriteMeasurementPacket() {
std::array<std::byte, FramedProto::kMaxEncodedSizeBytes +
(AdcMeasure::kMaxEncodedSizeBytes *
Payload::kAdcMeasurementsMaxSize)>
packet_buffer;
FramedProto::MemoryEncoder packet(packet_buffer);
Status status = packet.WriteMagicStart(kFramedProtoMagicConstant);
if (!status.ok()) {
PW_LOG_ERROR("WriteMagicStart %d", status);
return status;
}
// Controls lifetime of payload.
{
auto payload = packet.GetPayloadEncoder();
status = payload.WriteTimestamp(measurement_delta_micros_);
if (!status.ok()) {
PW_LOG_ERROR("WriteTimestamp: %d", status);
return status;
}
for (size_t i = 0; i < sampled_adc_count_; i++) {
auto adc_encoder = payload.GetAdcMeasurementsEncoder();
status = adc_encoder.WriteVbusValue(measurements_[i].vbus_value_);
if (!status.ok()) {
PW_LOG_ERROR("WriteVbusValue: #%d %d", i, status);
return status;
}
status = adc_encoder.WriteVshuntValue(measurements_[i].vshunt_value_);
if (!status.ok()) {
PW_LOG_ERROR("WriteVshuntValue: #%d %d", i, status);
return status;
}
}
}
if (!packet.status().ok()) {
PW_LOG_ERROR("packet.status %d", status);
return packet.status();
}
// Write proto packet bytes over serial.
serial_.write(reinterpret_cast<const uint8_t *>(packet.data()),
packet.size());
serial_.flush();
return pw::OkStatus();
}
Status Adc::WriteRegister(uint8_t adc_number, uint8_t adc_register,
pw::ByteSpan write_buffer) {
uint32_t adc_address = ADCAddressWrite(adc_number, adc_register);
StartSpiTransaction();
ClearValidPulse();
WriteAddress(adc_address);
WriteData(write_buffer);
Status wait_result = WaitForFpgaIOValidPulse();
EndSpiTransaction();
return wait_result;
}
Status Adc::WriteRegisterAll(uint8_t adc_register, pw::ByteSpan write_buffer) {
uint32_t adc_address = ADCAddressWriteAll(adc_register);
StartSpiTransaction();
ClearValidPulse();
WriteAddress(adc_address);
WriteData(write_buffer);
Status wait_result = WaitForFpgaIOValidPulse();
EndSpiTransaction();
return wait_result;
}
pw::Result<pw::ConstByteSpan> Adc::GetADCConfiguration(uint8_t adc_number) {
return GetRegister(adc_number, INA229_ADC_CONFIG);
}
Status Adc::SetADCConfiguration(uint8_t adc_number, pw::ByteSpan write_buffer) {
return WriteRegister(adc_number, INA229_ADC_CONFIG, write_buffer);
}
pw::Result<pw::ConstByteSpan> Adc::GetShuntCalibration(uint8_t adc_number) {
return GetRegister(adc_number, INA229_SHUNT_CALIBRATION);
}
pw::Result<int32_t> Adc::GetShuntVoltageMeasurement(uint8_t adc_number) {
pw::Result<pw::ConstByteSpan> read_result =
GetRegister(adc_number, INA229_VSHUNT);
if (!read_result.ok()) {
return read_result.status();
}
int32_t value = VoltageMeasurement(read_result.value());
PW_LOG_INFO("ADC #%02d: VSHUNT = %02x %02x %02x = %d", adc_number,
read_result.value()[0], read_result.value()[1],
read_result.value()[2], value);
return value;
}
pw::Result<int32_t> Adc::GetBusVoltageMeasurement(uint8_t adc_number) {
pw::Result<pw::ConstByteSpan> read_result =
GetRegister(adc_number, INA229_VBUS);
if (!read_result.ok()) {
return read_result.status();
}
int32_t value = VoltageMeasurement(read_result.value());
PW_LOG_INFO("ADC #%02d: VBUS = %02x %02x %02x = %d", adc_number,
read_result.value()[0], read_result.value()[1],
read_result.value()[2], value);
return value;
}
int32_t Adc::VoltageMeasurement(pw::ConstByteSpan read_buffer) {
// Convert bits 23 to 4 to a signed integer. Bits 3 to 0 are discarded.
//
// read_buffer[0] << 12 76543210<-----------|
// read_buffer[1] << 4 76543210<---|
// read_buffer[2] >> 4 76543210--->
// result 98765432109876543210|
//
return pw::bytes::SignExtend<20>(static_cast<uint32_t>(read_buffer[0]) << 12 |
static_cast<uint32_t>(read_buffer[1]) << 4 |
static_cast<uint32_t>(read_buffer[2]) >> 4);
}
void Adc::InitInterrupts() {
attachInterrupt(/*pin=*/fpga_valid_, /*callback=*/&io_valid_rising_isr,
/*mode=*/HIGH);
}
Status Adc::InitAdcs() {
SetReadWriteMode();
PW_LOG_INFO("Init ADCs");
// Set all ADC_CONFIG registers.
std::array<std::byte, 2> adc_config = {
// [15-12] MODE=0xB: Continuous shunt and bus voltage
// [8-6] VBUSCT=0x0: 50us conversion time
// [5-3] VSHCT=0x0: 50us conversion time
// [0-2] AVG=0x0: 1 sample averaging count
std::byte(0b10110000),
std::byte(0b00000000),
};
Status config_result = WriteRegisterAll(INA229_ADC_CONFIG, adc_config);
if (!config_result.ok()) {
PW_LOG_ERROR("INA229_ADC_CONFIG set failed. Code: %d",
config_result.code());
return config_result;
}
// Set all DIAG_ALERT registers
std::array<std::byte, 2> diag_alert = {
// CNVR=1: Enablne conversion ready flag
std::byte(0b01000000),
// MEMSTAT=1: Normal operation
std::byte(0b00000000),
};
config_result = WriteRegisterAll(INA229_DIAG_ALERT, diag_alert);
if (!config_result.ok()) {
PW_LOG_ERROR("INA229_DIAG_ALERT set failed. Code: %d",
config_result.code());
return config_result;
}
return pw::OkStatus();
}
Status Adc::CheckAllAdcs() {
// Check all ADCs are reachable
for (int adc_number = 1; adc_number <= kTotalAdcCount; adc_number++) {
// Read and log the ADC_CONFIG register.
const pw::Result<pw::ConstByteSpan> adc_config_result =
GetADCConfiguration(adc_number);
if (adc_config_result.ok()) {
uint16_t adc_value = pw::bytes::ReadInOrder<uint16_t>(
pw::endian::big, adc_config_result.value().data());
PW_LOG_INFO("ADC #%02d: ADC Config = %x", adc_number, adc_value);
} else {
return adc_config_result.status();
}
// Read and log the VSHUNT register.
const pw::Result<int32_t> vshunt_result =
GetShuntVoltageMeasurement(adc_number);
if (!vshunt_result.ok()) {
PW_LOG_ERROR("GetShuntVoltageMeasurement failed: %d",
vshunt_result.status());
}
// Read and log the VBUS register.
const pw::Result<int32_t> vbus_result =
GetBusVoltageMeasurement(adc_number);
if (!vbus_result.ok()) {
PW_LOG_ERROR("GetBusVoltageMeasurement failed: %d", vbus_result.status());
}
}
return pw::OkStatus();
}
} // namespace gonk::adc