Skip to content

modm 的 GpioConnector

· 4 min

under construction!#

发现 modm 里 gpio 的 connect 非常神奇, 模板元编程, 以 uart 为例, connect 只需要:

using Uart1 = BufferedUart<UsartHal1>;
Uart1::connect<GpioA9::Tx, GpioA10::Rx>();

这个 connect 实现了顺序和个数都不要求, 并且配置错误就会给出 assert, 在看 connect 的定义前, 先看一下传进去的模板, GpioA9::TxGpioStatic<detail::DataA9>::Tx 的别名, 其中 DataA9data.hpp 中, 部分定义如下:

struct DataA9 {
static constexpr Gpio::Port port = Gpio::Port::A;
static constexpr uint8_t pin = 9;
struct Tx { using Data = DataA9; static constexpr Gpio::Signal Signal = Gpio::Signal::Tx; };
};
template<Peripheral p> struct SignalConnection<DataA9::Tx, p> {
static_assert((p == Peripheral::Usart1),"GpioA9::Tx only connects to Usart1!"); };
template<> struct SignalConnection<DataA9::Tx, Peripheral::Usart1> { static constexpr int8_t af = 7; };

这里只保留了与 usart1 有关的内容, 可以看到, 当匹配的外设不对时, 就会 assert. 下面是 connect 的定义:

template< class... Signals >
static void
connect(Gpio::InputType InputTypeRx = Gpio::InputType::PullUp,
Gpio::OutputType OutputTypeTx = Gpio::OutputType::PushPull)
{
using Connector = GpioConnector<Hal::UartPeripheral, Signals...>;
using Tx = typename Connector::template GetSignal< Gpio::Signal::Tx >;
using Rx = typename Connector::template GetSignal< Gpio::Signal::Rx >;
static_assert(((Connector::template IsValid<Tx> and Connector::template IsValid<Rx>) and sizeof...(Signals) == 2) or
((Connector::template IsValid<Tx> or Connector::template IsValid<Rx>) and sizeof...(Signals) == 1),
"BufferedUart::connect() requires one Tx and/or one Rx signal!");
Tx::setOutput(true);
Tx::setOutput(OutputTypeTx);
Rx::setInput(InputTypeRx);
Connector::connect();
}

在这里, 将模板参数匹配到 TxRx, 然后配置 GPIO 并 connect, 匹配和 connect 的过程均由 GpioConnector 这个类实现. 对于我们的例子, 可以 Connector 展开为如下的形式:

using Connector = GpioConnector<Usart1, GpioA9::Tx, GpioA10::Rx>;

先看其中的匹配部分的实现:

template< Gpio::Signal signal, class... Signals >
struct GpioGetSignal;
template< Gpio::Signal signal, class SignalT, class... Signals >
struct GpioGetSignal<signal, SignalT, Signals...>
{
using Gpio = std::conditional_t<
(SignalT::Signal == signal),
typename modm::platform::GpioStatic<typename SignalT::Data>,
typename GpioGetSignal<signal, Signals...>::Gpio
>;
};
template< Gpio::Signal signal >
struct GpioGetSignal<signal>
{
using Gpio = GpioUnused;
};
template< Peripheral peripheral, class... Signals >
struct GpioConnector
{
template< Gpio::Signal signal >
using GetSignal = typename detail::GpioGetSignal<signal, Signals...>::Gpio;
};

匹配由 GpioGetSignal 这个类实现. 上面在串口的匹配中有这样的代码:

using Tx = typename Connector::template GetSignal< Gpio::Signal::Tx >;

这里的 Connector::template 是为了显式表明它是一个模板, 避免编译错误. 在我们的例子里上面的代码可以依次展开为:

using Tx = typename GpioConnector<Usart1, GpioA9::Tx, GpioA10::Rx>::template GetSignal< Gpio::Signal::Tx >;
using Tx = typename GpioGetSignal<Gpio::Signal::Tx, GpioA9::Tx, GpioA10::Rx>::Gpio

经过 GpioGetSignal 的递归展开, 最终就会得到满足条件 SignalT::Signal == signal 的 Gpio 类, 如果没有满足条件的就会 得到 GpioUnused, 对它的所有处理都是空函数.

这之后调用了 Connector::connect() 函数, GpioConnector 的完整定义如下所示:

template< Peripheral peripheral, class... Signals >
struct GpioConnector
{
template< class GpioQuery >
static constexpr bool Contains = (
std::is_same_v<typename Signals::Data, typename GpioQuery::Data> or ...);
template< class GpioQuery >
static constexpr bool IsValid = not std::is_same_v<typename GpioQuery::Data, detail::DataUnused>;
template< Gpio::Signal signal >
using GetSignal = typename detail::GpioGetSignal<signal, Signals...>::Gpio;
template< class Signal >
static void connectSignal()
{
using Connection = detail::SignalConnection<Signal, peripheral>;
using Pin = GpioStatic<typename Signal::Data>;
if constexpr(Connection::af == -2) {
Pin::disconnect();
Pin::setAnalogInput();
}
if constexpr (Connection::af >= 0) {
Pin::setAlternateFunction(Connection::af);
}
}
static inline void connect()
{
(connectSignal<Signals>(), ...);
}
static inline void disconnect()
{
(GpioStatic<typename Signals::Data>::disconnect(), ...);
}
};

可以看到 connect() 函数是对每个 singal 展开调用 connectSignal<signal>() 函数. connectSignal<signal>() 函数中的 detail::SignalConnection<Signal, peripheral> 就在前面的 data.hpp 中定义, 此时如果发现外设不匹配, 就会 assert. 之后进行的操作就是写相关寄存器.