How to declare constant pins across different boards (stm32)

As you mention, conditional compilation (#[cfg(...)]) is probably the best way to do this.

The stm32f1xx-hal crate's blinky example shows how this can be done:

    #[cfg(feature = "stm32f100")]
    gpioc
        .pc9
        .into_push_pull_output(&mut gpioc.crh)
        .set_high()
        .unwrap();

    #[cfg(feature = "stm32f101")]
    gpioc
        .pc9
        .into_push_pull_output(&mut gpioc.crh)
        .set_high()
        .unwrap();

    #[cfg(any(feature = "stm32f103", feature = "stm32f105", feature = "stm32f107"))]
    gpioc
        .pc13
        .into_push_pull_output(&mut gpioc.crh)
        .set_low()
        .unwrap();

As you start to do more complex things, this kind of duplication can get annoying but in my experience it's usually possible to factor out the bits that are board specific so you don't have to duplicate as much stuff.

For example:

#[cfg(any(feature = "stm32f100", feature = "stm32f101"))]
type LedPin = PC9<Output<PushPull>>;

#[cfg(any(feature = "stm32f100", feature = "stm32f101"))]
fn get_pin(parts: &gpioc::Parts) -> LedPin { ... }


#[cfg(any(feature = "stm32f103", feature = "stm32f105", feature = "stm32f107"))]
type LedPin = PC13<Output<PushPull>>;

#[cfg(any(feature = "stm32f103", feature = "stm32f105", feature = "stm32f107"))]
fn get_pin(parts: &gpioc::Parts) -> LedPin { ... }

fn turn_on(pin: &mut LedPin) { pin.set_low().unwrap(); }

let mut pin = get_pin();
turn_on(&mut pin);

There are also traits in embedded-hal and in HAL crates that make it easy to generalize over pins in a zero-cost fashion without having to manually get all the types to align like we do above. For example:

use embedded_hal::digital::v2::OutputPin;
use core::convert::Infallible;

#[cfg(any(feature = "stm32f100", feature = "stm32f101"))]
fn get_pin(parts: &gpioc::Parts) -> PC9<Output<PushPull>> { ... }

#[cfg(any(feature = "stm32f103", feature = "stm32f105", feature = "stm32f107"))]
fn get_pin(parts: &gpioc::Parts) -> PC13<Output<PushPull>> { ... }

fn turn_on(pin: &mut impl OutputPin<Error = Infallible>) { pin.set_low().unwrap(); }

let mut pin = get_pin();
turn_on(&mut pin);

Finally, some HAL crates (like stm32f1xx-hal) offer type erased pin types (like this) that let you sidestep the problem altogether.