Зачем тащить (CRC_TypeDef* crc) в интерфейс каждого метода? Шаблоны
так шаблоны. Пример. template<uint32_t PI2C>
class TI2C
{
public:
constexpr TI2C() = default;
template<I2CTiming TR=I2CTiming::APBCLK_100kHz>
static inline void Init()
{
base()->TIMINGR = uint32_t(TR);
base()->CR1 = I2C_CR1_PE;
}
private:
static inline auto base() { return (I2C_TypeDef *)PI2C; }
};
using i2c1 = TI2C<(uint32_t)I2C1_BASE>;
using i2c2 = TI2C<(uint32_t)I2C2_BASE>;
Теперь можно i2c1 и i2c2 пользоваться напрямую
i2c1::Init<I2CTiming::SysCLK_1MHz>();
Либо вешать на них другие устройства
using EEPROM = EE_24C<i2c1, EEType::AT24C512, EE_HW_ADR>;
И без всякой словесной шелухи куда проще читается.