lib/adc: Implement FPGA to ADC interface - Read and write to individual or all ADCs - Continuous read mode function - Move Arduino SPI library build deps to //third_party/BUILD.gn - Switch sample bus pins from QSPI to the SPI1 peripheral Change-Id: I40733f251590022793a09d68a0f53d22924d430b Reviewed-on: https://pigweed-review.googlesource.com/c/gonk/+/185810 Reviewed-by: Eric Holland <hollande@google.com> Commit-Queue: Anthony DiGirolamo <tonymd@google.com>
diff --git a/README.md b/README.md index 2dc6ba0..c2ce2ad 100644 --- a/README.md +++ b/README.md
@@ -192,16 +192,16 @@ ## FPGA Connection -| Net | Function | FPGA IO# | STM32 Pin | STM32 Function | Notes | -|-------------------|----------|----------|-----------|-----------------|---------------------| -| FPGA_IO_SPARE_0_2 | rst_i | 135 | PA0 | GPIO_Output | Active high | -| FPGA_IO_SPARE_0_0 | mode_i | 137 | PB11 | GPIO_Output | FPGA operation mode | -| FPGA_IO_SPARE_0_1 | valid_o | 136 | PB10 | GPIO_Output | Data/Transfer Valid | -| DSPI_IO0 | miso_i | 73 | PC9 | QUADSPI_BK1_IO0 | | -| DSPI_IO1 | mosi_i | 74 | PC10 | QUADSPI_BK1_IO1 | | -| DSPI_CS | cs_n | 75 | PB6 | QUADSPI_BK1_NCS | | -| DSPI_SCK | sclk_i | 76 | PB2 | QUADSPI_CLK | | -| FPGA_IO_SPARE_2_0 | | 49 | PA7 | SPI1_MOSI | | -| FPGA_IO_SPARE_2_1 | | 48 | PA6 | SPI1_MISO | | -| FPGA_IO_SPARE_2_2 | | 47 | PA5 | SPI1_SCK | | -| FPGA_IO_SPARE_2_3 | | 45 | PA4 | SPI1_NSS | | +| Net | Function | FPGA IO# | STM32 Pin | STM32 Function | Notes | +|-------------------|----------|----------|-----------|----------------|---------------------| +| FPGA_IO_SPARE_0_2 | rst_i | 135 | PA0 | GPIO_Output | Active high | +| FPGA_IO_SPARE_0_0 | mode_i | 137 | PB11 | GPIO_Output | FPGA operation mode | +| DSPI_CS | valid_o | 75 | PB6 | GPIO_Output | Data/Transfer Valid | +| FPGA_IO_SPARE_1_1 | miso_i | 101 | PC2 | SPI1_MISO | | +| FPGA_IO_SPARE_1_0 | mosi_i | 99 | PC3 | SPI1_MOSI | | +| I2C_O_SDA | cs_n | 79 | PB9 | SPI1_NSS | | +| FPGA_IO_SPARE_0_1 | sclk_i | 136 | PB10 | SPI1_SCK | | +| FPGA_IO_SPARE_2_0 | | 49 | PA7 | | | +| FPGA_IO_SPARE_2_1 | | 48 | PA6 | | | +| FPGA_IO_SPARE_2_2 | | 47 | PA5 | | | +| FPGA_IO_SPARE_2_3 | | 45 | PA4 | | |
diff --git a/applications/fpga_config/BUILD.gn b/applications/fpga_config/BUILD.gn index 934f794..8e42192 100644 --- a/applications/fpga_config/BUILD.gn +++ b/applications/fpga_config/BUILD.gn
@@ -20,16 +20,13 @@ pw_executable("fpga_config") { include_dirs = [ "//third_party/stm32duino/arduino-core/libraries/SPI/src" ] - sources = [ - "//third_party/stm32duino/arduino-core/libraries/SPI/src/SPI.cpp", - "//third_party/stm32duino/arduino-core/libraries/SPI/src/utility/spi_com.c", - "main.cc", - ] + sources = [ "main.cc" ] deps = [ "$dir_pw_log", "$dir_pw_string", "$dir_pw_third_party/arduino:arduino_core_sources", + "//lib/adc", "//lib/fpga_control", "//lib/pin_config", "//lib/spi_flash",
diff --git a/applications/fpga_config/main.cc b/applications/fpga_config/main.cc index a2828a9..41bc091 100644 --- a/applications/fpga_config/main.cc +++ b/applications/fpga_config/main.cc
@@ -18,16 +18,25 @@ #include <Arduino.h> #include <SPI.h> +#define PW_LOG_LEVEL PW_LOG_LEVEL_INFO +#define PW_LOG_MODULE_NAME "main" + #include "pw_log/log.h" #include "pw_result/result.h" #include "pw_string/format.h" +#include "gonk/adc.h" #include "gonk/fpga_control.h" #include "gonk/pin_config.h" #include "gonk/spi_flash.h" +using gonk::adc::Adc; using gonk::fpga_control::FpgaControl; using gonk::pin_config::FlashCS; +using gonk::pin_config::FpgaDspiCS; +using gonk::pin_config::FpgaIoMode; +using gonk::pin_config::FpgaIoReset; +using gonk::pin_config::FpgaIoValid; using gonk::pin_config::ICE40Done; using gonk::pin_config::PinConfig; using gonk::pin_config::StatusLed; @@ -39,6 +48,8 @@ FpgaControl fpga_control = FpgaControl(&pin_config); SpiFlash spi_flash = SpiFlash(FlashCS, /*baudrate=*/1000000, pin_config.flash_spi); +Adc fpga_adc = Adc(pin_config.fpga_spi, /*fpga_spi_baudrate=*/1000000, + FpgaDspiCS, FpgaIoMode, FpgaIoReset, FpgaIoValid); void ice40_done_rising_isr() { PW_LOG_DEBUG("ICE40 Done: HIGH"); } @@ -48,6 +59,7 @@ void IdleTask(); void FpgaConfigTask(); +void ADCTask(); void IdleTask() { static uint32_t last_update = millis(); @@ -82,6 +94,52 @@ } } +void ADCTask() { + static uint32_t last_update = millis(); + static uint32_t this_update = millis(); + static uint16_t update_count = 0; + + this_update = millis(); + + // Check for serial input. + if (Serial.available()) { + int data_in = Serial.read(); + // Press enter to restart the FPGA config. + if (data_in == '\n') { + PW_LOG_INFO("Restarting FPGA Config."); + current_task = &FpgaConfigTask; + } + } + + if (this_update > last_update + 1000) { + PW_LOG_INFO("ADC update: %d", update_count); + + pw::Status update_result = fpga_adc.UpdateContinuousMeasurements(); + if (!update_result.ok()) { + PW_LOG_ERROR("UpdateContinuousMeasurements() failed"); + } + + PW_LOG_INFO(" vbus: %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, ", + fpga_adc.vbus_measurements_[0], fpga_adc.vbus_measurements_[1], + fpga_adc.vbus_measurements_[2], fpga_adc.vbus_measurements_[3], + fpga_adc.vbus_measurements_[4], fpga_adc.vbus_measurements_[5], + fpga_adc.vbus_measurements_[6], fpga_adc.vbus_measurements_[7], + fpga_adc.vbus_measurements_[8], fpga_adc.vbus_measurements_[9], + fpga_adc.vbus_measurements_[10]); + PW_LOG_INFO( + "vshunt: %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, ", + fpga_adc.vshunt_measurements_[0], fpga_adc.vshunt_measurements_[1], + fpga_adc.vshunt_measurements_[2], fpga_adc.vshunt_measurements_[3], + fpga_adc.vshunt_measurements_[4], fpga_adc.vshunt_measurements_[5], + fpga_adc.vshunt_measurements_[6], fpga_adc.vshunt_measurements_[7], + fpga_adc.vshunt_measurements_[8], fpga_adc.vshunt_measurements_[9], + fpga_adc.vshunt_measurements_[10]); + + last_update = this_update; + update_count = (update_count + 1) % UINT16_MAX; + } +} + void CheckSpiFlash() { PW_LOG_INFO("Checking SPI Flash"); spi_flash.StartFlashSpi(); @@ -97,9 +155,13 @@ auto result = fpga_control.StartConfig(); if (result.ok()) { // FPGA has been configure successfully. - current_task = &IdleTask; + current_task = &ADCTask; // Check if SPI flash is readable. CheckSpiFlash(); + // Init ADCs + fpga_adc.InitAdcs(); + fpga_adc.CheckAllAdcs(); + fpga_adc.SetContinuousReadMode(); } else { PW_LOG_INFO("Restarting FPGA Config."); current_task = &FpgaConfigTask;
diff --git a/applications/spi_flash_test/BUILD.gn b/applications/spi_flash_test/BUILD.gn index 6714ab6..22f31d2 100644 --- a/applications/spi_flash_test/BUILD.gn +++ b/applications/spi_flash_test/BUILD.gn
@@ -18,13 +18,7 @@ import("$dir_pw_build/target_types.gni") pw_executable("spi_flash_test") { - include_dirs = [ "//third_party/stm32duino/arduino-core/libraries/SPI/src" ] - - sources = [ - "//third_party/stm32duino/arduino-core/libraries/SPI/src/SPI.cpp", - "//third_party/stm32duino/arduino-core/libraries/SPI/src/utility/spi_com.c", - "main.cc", - ] + sources = [ "main.cc" ] deps = [ "$dir_pw_log", @@ -32,6 +26,7 @@ "$dir_pw_third_party/arduino:arduino_core_sources", "//lib/pin_config", "//lib/spi_flash", + "//third_party:stm32duino_spi", ] ldflags = [ "-Wl,--print-memory-usage" ]
diff --git a/fpga/gonk/top.pcf b/fpga/gonk/top.pcf index 20d0727..586c938 100644 --- a/fpga/gonk/top.pcf +++ b/fpga/gonk/top.pcf
@@ -17,12 +17,12 @@ set_io ice_prog_spi_io0 67 set_io ice_prog_spi_io1 68 set_io rst_i 135 -set_io data_valid_o 136 +set_io data_valid_o 75 set_io op_mode_i 137 -set_io sl_cs_i_0 75 -set_io sl_mosi_i_0 74 -set_io sl_miso_o_0 73 -set_io sl_sclk_i_0 76 +set_io sl_cs_i_0 79 +set_io sl_mosi_i_0 99 +set_io sl_miso_o_0 101 +set_io sl_sclk_i_0 136 set_io cs_o_3 19 set_io miso_i_4 2 set_io alertl_i_6 10
diff --git a/lib/adc/BUILD.gn b/lib/adc/BUILD.gn new file mode 100644 index 0000000..0068028 --- /dev/null +++ b/lib/adc/BUILD.gn
@@ -0,0 +1,40 @@ +# Copyright 2023 The Pigweed Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + +import("//build_overrides/pigweed.gni") + +import("$dir_pw_build/target_types.gni") + +config("default_config") { + include_dirs = [ "public" ] +} + +pw_source_set("adc") { + public_configs = [ ":default_config" ] + public = [ "public/gonk/adc.h" ] + + sources = [ "adc.cc" ] + + deps = [ + "$dir_pw_log", + "$dir_pw_result", + "$dir_pw_span", + "$dir_pw_status", + ] + public_deps = [ + "$dir_pw_bytes", + "$dir_pw_third_party/arduino:arduino_core_sources", + "//third_party:stm32duino_spi", + ] +}
diff --git a/lib/adc/adc.cc b/lib/adc/adc.cc new file mode 100644 index 0000000..96d1613 --- /dev/null +++ b/lib/adc/adc.cc
@@ -0,0 +1,483 @@ +#include <Arduino.h> +#include <SPI.h> +#include <bitset> +#include <cstddef> +#include <stdint.h> + +#include "gonk/adc.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; + PW_LOG_DEBUG("IO Valid: HIGH"); +} + +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(SPIClass &fpga_spi, uint16_t fpga_spi_baudrate, uint16_t fpga_cs_pin, + uint16_t fpga_mode_pin, uint16_t fpga_reset_pin, + uint16_t fpga_valid_pin) + : 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; + sample_read_time_.fill(0); + vbus_measurements_.fill(0); + vshunt_measurements_.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::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() { + digitalWrite(fpga_cs_, LOW); + fpga_spi_.beginTransaction(spi_settings_); +} + +void Adc::EndSpiTransaction() { + digitalWrite(fpga_cs_, HIGH); + 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 = WaitForFpgaIOValidPulse(); + if (!wait_result.ok()) { + EndSpiTransaction(); + return wait_result; + } + + for (int i = 0; i < kStreamingAdcCount; i++) { + // 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()); + vbus_measurements_[i] = vbus_value; + + // 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()); + vshunt_measurements_[i] = vshunt_value; + } + + // 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(); +} + +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 #%d: ShuntVoltageMeasurement = %d", adc_number, 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 #%d: BusVoltageMeasurement = %d", adc_number, 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>((uint32_t)read_buffer[0] << 12 | + (uint32_t)read_buffer[1] << 4 | + (uint32_t)read_buffer[2] >> 4); +} + +Status Adc::InitAdcs() { + attachInterrupt(/*pin=*/fpga_valid_, /*callback=*/&io_valid_rising_isr, + /*mode=*/HIGH); + + 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), + }; + WriteRegisterAll(INA229_ADC_CONFIG, adc_config); + + // 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), + }; + WriteRegisterAll(INA229_DIAG_ALERT, diag_alert); + + 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 #%d: ADC Config = %x", adc_number, adc_value); + } + + // Read and log the VSHUNT register. + GetShuntVoltageMeasurement(adc_number); + // Read and log the VBUS register. + GetBusVoltageMeasurement(adc_number); + } + + return pw::OkStatus(); +} + +} // namespace gonk::adc
diff --git a/lib/adc/public/gonk/adc.h b/lib/adc/public/gonk/adc.h new file mode 100644 index 0000000..510b9a3 --- /dev/null +++ b/lib/adc/public/gonk/adc.h
@@ -0,0 +1,84 @@ +#pragma once + +#include "SPI.h" +#include <Arduino.h> +#include <stdint.h> + +#include "pw_bytes/span.h" +#include "pw_result/result.h" +#include "pw_span/span.h" +#include "pw_status/status.h" + +using pw::span; +using pw::Status; + +namespace gonk::adc { + +const uint8_t kTotalAdcCount = 11; +const uint8_t kStreamingAdcCount = 11; + +class Adc { +public: + Adc(SPIClass &fpga_spi, uint16_t fpga_spi_baudrate, uint16_t fpga_cs_pin, + uint16_t fpga_mode_pin, uint16_t fpga_reset_pin, uint16_t fpga_valid_pin); + + Status InitAdcs(); + Status CheckAllAdcs(); + + void SetReadWriteMode(); + void SetContinuousReadMode(); + Status UpdateContinuousMeasurements(); + + // Writing and reading data over SPI. + void StartSpiTransaction(); + void EndSpiTransaction(); + void WriteAddress(uint32_t adc_address); + Status WriteData(pw::ByteSpan write_buffer); + pw::Result<pw::ConstByteSpan> ReadData(pw::ByteSpan read_buffer); + + // ADC Register Manipulation. + pw::Result<pw::ConstByteSpan> GetRegister(uint8_t adc_number, + uint8_t adc_register); + Status WriteRegister(uint8_t adc_number, uint8_t adc_register, + pw::ByteSpan write_buffer); + Status WriteRegisterAll(uint8_t adc_register, pw::ByteSpan write_buffer); + + // ADC Specific functions. + pw::Result<pw::ConstByteSpan> GetManufacturerID(uint8_t adc_number); + pw::Result<pw::ConstByteSpan> GetADCConfiguration(uint8_t adc_number); + Status SetADCConfiguration(uint8_t adc_number, pw::ByteSpan write_buffer); + pw::Result<pw::ConstByteSpan> GetShuntCalibration(uint8_t adc_number); + pw::Result<int32_t> GetShuntVoltageMeasurement(uint8_t adc_number); + pw::Result<int32_t> GetBusVoltageMeasurement(uint8_t adc_number); + + std::array<int32_t, kStreamingAdcCount> vbus_measurements_; + std::array<int32_t, kStreamingAdcCount> vshunt_measurements_; + +private: + uint16_t fpga_reset_; + uint16_t fpga_mode_; + uint16_t fpga_valid_; + uint16_t fpga_cs_; + + SPIClass &fpga_spi_; + SPISettings spi_settings_; + + uint8_t sample_read_index_; + std::array<uint32_t, 16> sample_read_time_; + + Status WaitForFpgaIOValid(uint32_t timeout_ms); + Status WaitForFpgaIOValidPulse(uint32_t timeout_ms); + + // FPGA ADC Address selection. + uint32_t ADCAddress(uint8_t adc_number, uint8_t adc_command, uint8_t mode); + uint32_t ADCAddressWrite(uint8_t adc_number, uint8_t adc_command); + uint32_t ADCAddressWriteAll(uint8_t adc_command); + + pw::Result<pw::ConstByteSpan> GetThreeBytes(); + + // INA229 register value interpretation. + pw::Result<uint8_t> RegisterSize(uint8_t adc_register); + int32_t VoltageMeasurement(pw::ConstByteSpan read_buffer); +}; + +} // namespace gonk::adc
diff --git a/lib/pin_config/BUILD.gn b/lib/pin_config/BUILD.gn index 42cbc01..1e3dc85 100644 --- a/lib/pin_config/BUILD.gn +++ b/lib/pin_config/BUILD.gn
@@ -17,10 +17,7 @@ import("$dir_pw_build/target_types.gni") config("default_config") { - include_dirs = [ - "public", - "//third_party/stm32duino/arduino-core/libraries/SPI/src", - ] + include_dirs = [ "public" ] } pw_source_set("pin_config") { @@ -29,8 +26,9 @@ sources = [ "pin_config.cc" ] - deps = [ - "$dir_pw_log", + deps = [ "$dir_pw_log" ] + public_deps = [ "$dir_pw_third_party/arduino:arduino_core_sources", + "//third_party:stm32duino_spi", ] }
diff --git a/lib/pin_config/pin_config.cc b/lib/pin_config/pin_config.cc index 9debc76..ae70ebe 100644 --- a/lib/pin_config/pin_config.cc +++ b/lib/pin_config/pin_config.cc
@@ -3,6 +3,8 @@ #include "gonk/pin_config.h" +#include "stm32f7xx_hal_qspi.h" + #define PW_LOG_LEVEL PW_LOG_LEVEL_DEBUG #define PW_LOG_MODULE_NAME "PinConfig" @@ -10,7 +12,9 @@ namespace gonk::pin_config { -PinConfig::PinConfig() { flash_spi = SPIClass(FlashMOSI, FlashMISO, FlashCLK); } +PinConfig::PinConfig() : fpga_spi(FpgaDspiMOSI, FpgaDspiMISO, FpgaDspiCLK) { + flash_spi = SPIClass(FlashMOSI, FlashMISO, FlashCLK); +} void PinConfig::Init() { // Set STATUS LED mode and initial state. @@ -29,8 +33,21 @@ // Set FPGA configure done signal pin as an input. pinMode(ICE40Done, INPUT); + + // FPGA Application IO pins + pinMode(FpgaIoReset, OUTPUT); + digitalWrite(FpgaIoReset, LOW); + + pinMode(FpgaIoMode, OUTPUT); + digitalWrite(FpgaIoMode, LOW); + + pinMode(FpgaIoValid, INPUT); + + InitSampleBus(); } +void PinConfig::InitSampleBus() { fpga_spi.begin(); } + void PinConfig::FpgaHalt() { // Set FlashCS to signal the FPGA it should load the bitstream from external // flash.
diff --git a/lib/pin_config/public/gonk/pin_config.h b/lib/pin_config/public/gonk/pin_config.h index c3488ec..bdb4398 100644 --- a/lib/pin_config/public/gonk/pin_config.h +++ b/lib/pin_config/public/gonk/pin_config.h
@@ -16,6 +16,14 @@ constexpr uint16_t FlashCLK = PB3; // ICE_SPI_SCK constexpr uint16_t StatusLed = PB13; // STATUS +constexpr uint16_t FpgaDspiCS = PB9; // was PB6; +constexpr uint16_t FpgaDspiMOSI = PC3; // was PC10; +constexpr uint16_t FpgaDspiMISO = PC2; // was PC9; +constexpr uint16_t FpgaDspiCLK = PB10; // was PB2; +constexpr uint16_t FpgaIoReset = PA0; +constexpr uint16_t FpgaIoMode = PB11; +constexpr uint16_t FpgaIoValid = PB6; // was PB10; + class PinConfig { public: PinConfig(); @@ -24,6 +32,7 @@ void Init(); // Init FPGA specific pins. void InitFpgaPins(); + void InitSampleBus(); // Halt the FPGA with the SPI bus released so SPI flash is accessible from the // MCU. @@ -34,6 +43,7 @@ void SPIDisable(); SPIClass flash_spi; + SPIClass fpga_spi; private: };
diff --git a/lib/spi_flash/BUILD.gn b/lib/spi_flash/BUILD.gn index 902e66e..5873e4a 100644 --- a/lib/spi_flash/BUILD.gn +++ b/lib/spi_flash/BUILD.gn
@@ -33,6 +33,9 @@ "$dir_pw_result", "$dir_pw_span", "$dir_pw_status", + ] + public_deps = [ "$dir_pw_third_party/arduino:arduino_core_sources", + "//third_party:stm32duino_spi", ] }
diff --git a/third_party/BUILD.gn b/third_party/BUILD.gn new file mode 100644 index 0000000..1f6a568 --- /dev/null +++ b/third_party/BUILD.gn
@@ -0,0 +1,34 @@ +# Copyright 2023 The Pigweed Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + +import("//build_overrides/pigweed.gni") + +import("$dir_pw_build/target_types.gni") + +config("stm32duino_spi_config") { + include_dirs = [ "//third_party/stm32duino/arduino-core/libraries/SPI/src" ] +} + +pw_source_set("stm32duino_spi") { + public_configs = [ ":stm32duino_spi_config" ] + public = [ "//third_party/stm32duino/arduino-core/libraries/SPI/src/SPI.h" ] + + sources = [ + "//third_party/stm32duino/arduino-core/libraries/SPI/src/SPI.cpp", + "//third_party/stm32duino/arduino-core/libraries/SPI/src/utility/spi_com.c", + "//third_party/stm32duino/arduino-core/libraries/SPI/src/utility/spi_com.h", + ] + + public_deps = [ "$dir_pw_third_party/arduino:arduino_core_sources" ] +}