under construction!#
发现 modm 里 gpio 的 connect 非常神奇, 模板元编程, 以 uart 为例, connect 只需要:
using Uart1 = BufferedUart<UsartHal1>;Uart1::connect<GpioA9::Tx, GpioA10::Rx>();
这个 connect 实现了顺序和个数都不要求, 并且配置错误就会给出 assert, 在看 connect 的定义前, 先看一下传进去的模板, GpioA9::Tx
是 GpioStatic<detail::DataA9>::Tx
的别名, 其中 DataA9
在 data.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 voidconnect(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();}
在这里, 将模板参数匹配到 Tx
和 Rx
, 然后配置 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. 之后进行的操作就是写相关寄存器.