Skip to content

API guide

Look up pins and channels, filter them and turn ADC counts into volts, by task. Every block compiles against both boards.

The shape of the API

Task Call Returns
One pin's row GetGpioMapping(HfFunctionalGpioPin) const HfGpioMapping*, nullptr if out of range
One ADC channel's row GetAdcMapping(HfFunctionalAdcChannel) const HfAdcMapping* or nullptr
Every row HF_GPIO_MAPPING[], HF_ADC_MAPPING[] with ..._SIZE constexpr arrays
Name of a pin or channel to_string(pin), to_string(channel) std::string_view, "UNKNOWN_PIN" if out of range
Owner of a pin get_pin_category(pin), should_register_as_gpio(category) HfPinCategory, bool
Pins by owner or chip GetGpioPinsByCategory(), GetGpioPinsByChipType() count written to your array
Pull resistors RequiresPullUp(pin), RequiresPullDown(pin) bool
Availability IsGpioPinAvailable(pin), IsAdcChannelAvailable(ch) bool

Walk the pins a GPIO layer owns

#include "hf_functional_pin_config.hpp"  // the build defines one HARDFOC_BOARD_* macro

// Print where every pin that the GPIO layer should own lives on this board.
void list_gpio_pins() {
  for (size_t i = 0; i < HF_GPIO_MAPPING_SIZE; ++i) {
    const HfGpioMapping& m = HF_GPIO_MAPPING[i];
    if (!should_register_as_gpio(static_cast<HfPinCategory>(m.category))) {
      continue;  // core and comm pins belong to the system and the bus drivers
    }
    std::printf("%-28.*s chip %u pin %2u %s\n", static_cast<int>(m.name.size()), m.name.data(),
                m.chip_type, m.physical_pin, m.is_inverted ? "active-low" : "active-high");
  }
}

A board HAL does the same loop and creates a driver for each row on the chip chip_type names (static_cast<HfGpioChipType>(m.chip_type)).

Look up one pin

// Find one pin by function: the only place a pin number comes from.
bool chip_select_is_active_low(HfFunctionalGpioPin pin) {
  const HfGpioMapping* m = GetGpioMapping(pin);
  if (m == nullptr) {
    return false;  // not on this board
  }
  return m->is_inverted;
}

The enum value indexes the arrays directly, so the lookup is a bounds check and an array access.

Filter by chip

// Every pin a given chip carries, without a heap.
size_t pins_on_mcu(HfFunctionalGpioPin (&out)[HF_GPIO_MAPPING_SIZE]) {
  return GetGpioPinsByChipType(HfGpioChipType::ESP32_INTERNAL, out, HF_GPIO_MAPPING_SIZE);
}

GetGpioPinsByCategory() works the same way with an HfPinCategory. Both write at most max_count entries and return how many they wrote.

ADC channels

// ADC channels carry their scaling: full-scale voltage, resolution and divider.
float counts_to_volts(HfFunctionalAdcChannel ch, uint32_t counts) {
  const HfAdcMapping* m = GetAdcMapping(ch);
  if (m == nullptr) {
    return 0.0F;
  }
  const float full_scale = static_cast<float>((1U << m->resolution_bits) - 1U);
  return static_cast<float>(counts) / full_scale * (static_cast<float>(m->max_voltage_mv) / 1000.0F) *
         m->voltage_divider;
}

max_voltage_mv is the full-scale input of the converter and voltage_divider the ratio of the external divider in front of it (Flux V1 bus voltage: 11.0).

Names and categories

// Names for logs and consoles; categories decide who owns a pin.
void describe(HfFunctionalGpioPin pin) {
  const std::string_view name = to_string(pin);
  const bool owned_by_gpio = should_register_as_gpio(get_pin_category(pin));
  std::printf("%.*s: %s\n", static_cast<int>(name.size()), name.data(), owned_by_gpio ? "GPIO" : "reserved");
}

Board-specific constants

A board header may export derived constants for code that calls the MCU SDK directly. Flux V1 has kFluxHwUartFdo2TxGpio and kFluxHwUartFdo2RxGpio, read from the UART_FDO2_TX / UART_FDO2_RX rows, so they cannot disagree with the map.

Next: Adding a board.