Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -348,8 +348,6 @@ set(BUILD_OBJ_DIR ${BUILD_TARGET_DIR}/obj)

if(USE_PLATFORM_UBUNTU)
add_definitions(-DLIBPARAMS_PARAMS_DIR="${BUILD_SRC_DIR}")
add_definitions(-DLIBPARAMS_INIT_PARAMS_FILE_NAME="default_params")
add_definitions(-DLIBPARAMS_TEMP_PARAMS_FILE_NAME="temp_params")
endif()

include(${LIBPARAMS_PATH}/${LIBPARAMS_CMAKE})
Expand Down
3 changes: 2 additions & 1 deletion Src/drivers/board_monitor/board_monitor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ uint16_t BoardMonitor::temperature() {

auto adc_12b = HAL::Adc::get(BoardAdc::RANK_TEMPERATURE);
uint16_t temperature_kelvin;
#ifdef STM32G0B1xx
#if defined(STM32G0B1xx) || defined(STM32H753xx)
// Factory-calibrated (TS_CAL1/TS_CAL2) conversion.
temperature_kelvin = __HAL_ADC_CALC_TEMPERATURE(3300, adc_12b, ADC_RESOLUTION_12B) + 273;
#else // STM32F103xB
static const uint16_t TEMP_REF = 25;
Expand Down
9 changes: 7 additions & 2 deletions Src/drivers/board_monitor/board_monitor.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,13 @@ class BoardMonitor{
return std::numeric_limits<float>::quiet_NaN();
}

if (auto hw_version = hardware_version(); hw_version < 2403 || hw_version > 2450) {
return std::numeric_limits<float>::quiet_NaN();
// The INA169 calibration below only holds for the kirpi revisions that carry a
// hardware-version divider. Boards without one (RANK_VERSION unmapped) have their
// own current sense, so the version gate would reject them forever.
if constexpr (BoardAdc::RANK_VERSION != BoardAdc::INVALID_RANK) {
if (auto hw_version = hardware_version(); hw_version < 2403 || hw_version > 2450) {
return std::numeric_limits<float>::quiet_NaN();
}
}

// Current sensor: INA169NA/3K, R = 33K ohm
Expand Down
110 changes: 104 additions & 6 deletions Src/peripheral/adc/adc_stm32.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,112 @@
*/

#include "peripheral/adc/adc.hpp"
#include "peripheral/adc/adc_stm32.hpp"
#include "main.h"

extern ADC_HandleTypeDef hadc1;

static constexpr uint8_t ADC_MAX_DMA_CHANNELS = 16;
static uint16_t adc_dma_buffer[ADC_MAX_DMA_CHANNELS];

// Long enough for the internal temperature sensor, which needs a far greater
// acquisition time than a resistor divider (~9us minimum on the STM32H7).
#if defined(STM32H753xx)
static constexpr uint32_t ADC_POLL_SAMPLETIME = ADC_SAMPLETIME_387CYCLES_5;
#elif defined(STM32G0B1xx)
static constexpr uint32_t ADC_POLL_SAMPLETIME = ADC_SAMPLETIME_79CYCLES_5;
#else
static constexpr uint32_t ADC_POLL_SAMPLETIME = ADC_SAMPLETIME_55CYCLES_5;
#endif

static constexpr uint32_t ADC_POLL_TIMEOUT_MS = 10;

namespace HAL {

int8_t Adc::init(uint8_t channel_count) {
if (channel_count == 0 || channel_count > ADC_MAX_DMA_CHANNELS) {
// Weak default: no board-provided channels -> legacy DMA-scanned ADC1.
// A board enables polled reads (and extra ADCs) by defining HAL::adc_channels().
[[gnu::weak]] std::span<const AdcChannel> adc_channels() {
return {};
}

namespace {

int8_t calibrate(ADC_HandleTypeDef* hadc) {
#ifdef STM32H753xx
if (HAL_ADCEx_Calibration_Start(hadc, ADC_CALIB_OFFSET, ADC_SINGLE_ENDED) != HAL_OK) {
#else
if (HAL_ADCEx_Calibration_Start(hadc) != HAL_OK) {
#endif
return -1;
}
return 0;
}

// Single blocking conversion. Cheap enough for the slow rates the monitors use.
uint16_t read_channel(const AdcChannel& input) {
ADC_ChannelConfTypeDef config{};
config.Channel = input.channel;
config.Rank = ADC_REGULAR_RANK_1;
config.SamplingTime = ADC_POLL_SAMPLETIME;
#ifdef STM32H753xx
if (HAL_ADCEx_Calibration_Start(&hadc1, ADC_CALIB_OFFSET, ADC_SINGLE_ENDED) != HAL_OK) {
#else
if (HAL_ADCEx_Calibration_Start(&hadc1) != HAL_OK) {
config.SingleDiff = ADC_SINGLE_ENDED;
config.OffsetNumber = ADC_OFFSET_NONE;
config.Offset = 0;
#endif

if (HAL_ADC_ConfigChannel(input.hadc, &config) != HAL_OK) {
return 0;
}

if (HAL_ADC_Start(input.hadc) != HAL_OK) {
return 0;
}

uint16_t value = 0;
if (HAL_ADC_PollForConversion(input.hadc, ADC_POLL_TIMEOUT_MS) == HAL_OK) {
value = static_cast<uint16_t>(HAL_ADC_GetValue(input.hadc));
}

HAL_ADC_Stop(input.hadc);
return value;
}

} // namespace

int8_t Adc::init(uint8_t channel_count) {
if (channel_count == 0 || channel_count > ADC_MAX_DMA_CHANNELS) {
return -1;
}

const auto channels = adc_channels();

if (!channels.empty()) {
// Calibrate every distinct ADC referenced by the board's channel table.
for (size_t idx = 0; idx < channels.size(); idx++) {
auto* hadc = channels[idx].hadc;
if (hadc == nullptr) {
return -1;
}

bool already_done = false;
for (size_t prev = 0; prev < idx; prev++) {
if (channels[prev].hadc == hadc) {

Check failure on line 98 in Src/peripheral/adc/adc_stm32.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this code to not nest more than 3 if|for|do|while|switch statements.

See more on https://sonarcloud.io/project/issues?id=RaccoonlabDev_mini_v2_node&issues=AZ9u-eTQrDtczAXabl8k&open=AZ9u-eTQrDtczAXabl8k&pullRequest=137
already_done = true;
break;
}
}

if (!already_done && calibrate(hadc) != 0) {
return -1;
}
}

_channel_count = static_cast<uint8_t>(channels.size());
_is_adc_already_inited = true;
return 0;
}

if (calibrate(&hadc1) != 0) {
return -1;
}

Expand All @@ -41,16 +128,27 @@
if (!_is_adc_already_inited || rank >= _channel_count) {
return 0;
}

const auto channels = adc_channels();
if (!channels.empty()) {
return read_channel(channels[rank]);
}

return adc_dma_buffer[rank];
}

} // namespace HAL

#ifdef HAL_ADC_MODULE_ENABLED
/**
* @note We assume that hadc->Instance == ADC1 always!
* @note Only used by the legacy DMA-scanned ADC1 path; polled boards never start a
* DMA transfer, so this stays a no-op for them.
*/
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) {
if (!HAL::adc_channels().empty()) {
return;
}

auto channel_count = HAL::Adc::channel_count();
if (channel_count == 0) {
return;
Expand Down
34 changes: 34 additions & 0 deletions Src/peripheral/adc/adc_stm32.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/**
* This program is free software under the GNU General Public License v3.
* See <https://www.gnu.org/licenses/> for details.
* Author: Dmitry Ponomarev <ponomarevda96@gmail.com>
*/

#ifndef SRC_PERIPHERAL_ADC_ADC_STM32_HPP_
#define SRC_PERIPHERAL_ADC_ADC_STM32_HPP_

#include <span>
#include "main.h"

namespace HAL {

// One measured input: which ADC peripheral and which channel on it.
struct AdcChannel {
ADC_HandleTypeDef* hadc;
uint32_t channel; // ADC_CHANNEL_x
};

// Boards that cannot use the default single-ADC1 DMA scan override this in a
// board source file, returning one entry per BoardAdc rank (rank == index).
// Such inputs are read by polling a single conversion on demand, which keeps
// the driver free of DMA/domain constraints — on the STM32H7 in particular,
// ADC3 lives in D3 and could otherwise only be served by BDMA out of SRAM4.
//
// The default returns an empty span: the driver then keeps the legacy
// DMA-scanned ADC1 behaviour driven by Adc::init(channel_count), so existing
// boards are unaffected.
std::span<const AdcChannel> adc_channels();

} // namespace HAL

#endif // SRC_PERIPHERAL_ADC_ADC_STM32_HPP_
30 changes: 21 additions & 9 deletions Src/peripheral/i2c/i2c_stm32.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,56 +12,68 @@ namespace HAL {

static const constexpr uint32_t I2C_TIMEOUT = 30;

// The I2C instance can be overridden per board via -DNC_I2C_INSTANCE=<hi2cX>
// (e.g. am/node_h7 puts the INA228 sensors on I2C3). Defaults to hi2c1.
#if defined(NC_I2C_INSTANCE)
static I2C_HandleTypeDef* const nc_hi2c = &NC_I2C_INSTANCE;
#else
static I2C_HandleTypeDef* const nc_hi2c = &hi2c1;
#endif

int8_t I2C::init() {
if (auto res = HAL_I2C_DeInit(&hi2c1); res != 0) {
if (auto res = HAL_I2C_DeInit(nc_hi2c); res != 0) {
return -res;
}

if (auto res = HAL_I2C_Init(&hi2c1); res != 0) {
if (auto res = HAL_I2C_Init(nc_hi2c); res != 0) {
return -res;
}

return 0;
}

int8_t I2C::is_device_ready(uint16_t address, uint8_t trials) {
auto res = HAL_I2C_IsDeviceReady(&hi2c1, address, trials, I2C_TIMEOUT);
auto res = HAL_I2C_IsDeviceReady(nc_hi2c, address, trials, I2C_TIMEOUT);
return (res == HAL_OK) ? 0 : -res;
}

int8_t I2C::transmit(uint16_t id, uint8_t tx[], uint8_t len) {
auto res = HAL_I2C_Master_Transmit(&hi2c1, id, tx, len, I2C_TIMEOUT);
auto res = HAL_I2C_Master_Transmit(nc_hi2c, id, tx, len, I2C_TIMEOUT);
return (res == HAL_OK) ? 0 : -res;
}

int8_t I2C::receive(uint16_t id, uint8_t *rx, uint8_t len) {
auto res = HAL_I2C_Master_Receive(&hi2c1, id, rx, len, I2C_TIMEOUT);
auto res = HAL_I2C_Master_Receive(nc_hi2c, id, rx, len, I2C_TIMEOUT);
return (res == HAL_OK) ? 0 : -res;
}

int32_t I2C::read_register_1_byte(uint16_t device_id, uint8_t reg_address) {
std::array<uint8_t, 1> tx_buffer = {{reg_address}};
// Propagate the negative error code as documented: negating it here would make a
// failure indistinguishable from a valid register value and defeat every caller's
// `if (reg_value < 0)` check.
if (auto res = HAL::I2C::transmit(device_id, tx_buffer.data(), tx_buffer.size()); res < 0) {
return -res;
return res;
}

std::array<uint8_t, 1> reg_value = {};
if (auto res = HAL::I2C::receive(device_id, reg_value.data(), reg_value.size()); res < 0) {
return -res;
return res;
}

return reg_value[0];
}

int32_t I2C::read_register_2_bytes(uint16_t device_id, uint8_t reg_address) {
std::array<uint8_t, 1> tx_buffer = {{reg_address}};
// See read_register_1_byte(): the error code must stay negative.
if (auto res = HAL::I2C::transmit(device_id, tx_buffer.data(), tx_buffer.size()); res < 0) {
return -res;
return res;
}

std::array<uint8_t, 2> reg_value = {};
if (auto res = HAL::I2C::receive(device_id, reg_value.data(), reg_value.size()); res < 0) {
return -res;
return res;
}

return (reg_value[0] << 8) | reg_value[1];
Expand Down
15 changes: 12 additions & 3 deletions Src/peripheral/spi/spi_stm32.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,13 @@
static constexpr uint32_t TRANSMIT_DELAY = 100; // 100 is experimental value. Need to prove it
static constexpr std::byte SPI_READ{0x80};

#if defined(STM32F103xB) || defined(STM32F103xE)
// The SPI instance and chip-select GPIO can be overridden per board via
// -DNC_SPI_INSTANCE=<hspiX> and -DNC_SPI_NSS_GPIO_Port / -DNC_SPI_NSS_Pin
// (e.g. am/node_h7 puts the MAX14906 on SPI6 with nCS on PI10). Defaults keep
// the historical behaviour for boards that don't override.
#if defined(NC_SPI_INSTANCE)
static SPI_HandleTypeDef* hspi = &NC_SPI_INSTANCE;
#elif defined(STM32F103xB) || defined(STM32F103xE)
static SPI_HandleTypeDef* hspi = &hspi1;
#else
static SPI_HandleTypeDef* hspi = &hspi2;
Expand All @@ -22,12 +28,15 @@ static SPI_HandleTypeDef* hspi = &hspi2;
namespace HAL {

static void spi_set_nss(bool nss_state) {
#ifdef SPI2_NSS_GPIO_Port
auto state = nss_state ? GPIO_PIN_SET : GPIO_PIN_RESET;
#if defined(NC_SPI_NSS_GPIO_Port)
HAL_GPIO_WritePin(NC_SPI_NSS_GPIO_Port, NC_SPI_NSS_Pin, state);
#elif defined(SPI2_NSS_GPIO_Port)
HAL_GPIO_WritePin(SPI2_NSS_GPIO_Port, SPI2_NSS_Pin, state);
#elif defined(SPI_SS_GPIO_Port)
auto state = nss_state ? GPIO_PIN_SET : GPIO_PIN_RESET;
HAL_GPIO_WritePin(SPI_SS_GPIO_Port, SPI_SS_Pin, state);
#else
(void)state;
#endif
}

Expand Down
5 changes: 5 additions & 0 deletions cmake/params.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ elseif(NOT APPLICATION_DIR)
message(SEND_ERROR "APPLICATION_DIR is unknown.")
endif()

# The generators below run at configure time, so CMake must re-configure whenever a
# params.yaml changes. Without this a yaml edit is silently ignored: the build reuses
# the previously generated params.cpp and the firmware keeps the old defaults.
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS ${LIBPARAMS_PARAMS})

execute_process(
COMMAND python ${LIBPARAMS_PATH}/scripts/generate_params.py --out-dir ${BUILD_SRC_DIR} -f ${LIBPARAMS_PARAMS}
RESULT_VARIABLE result
Expand Down
Loading