【发布时间】:2020-07-01 20:53:13
【问题描述】:
我正在尝试学习/评估用于嵌入式编程的 C++,但遇到了一个问题,我似乎无法自己解决。我想使用类型特征来描述特定微控制器引脚的不同可能用途。然后我想让编译器停止编译我的项目,如果一个引脚用于它实际上不能做的事情。我的问题是它要么只在运行时工作,要么编译器产生误报:即它坚持一个引脚不能用于某些功能,即使我从未尝试使用它,但只有作为其他东西(比如驱动 LED)......
请原谅这篇冗长的帖子 - 我希望你能忍受我的解释并帮助我找到解决方案。
我已经使用模板为 MCU 引脚创建了一个实现,如下所示:
enum class pin_funciton {
OUTPUT, INPUT, INTERRUPT_FALLING,
INTERRUPT_RISING, INTERRUPT_LEVEL,
ANALOG, SPI, UART, };
// base class is needed so instance can be passed as function arguments
class gpio {
public:
virtual void configure(pin_function, unsigned char startupLogic) = 0;
virtual void set() {}
virtual void reset() {}
/* ... other functions like read() etc. */
};
template<unsigned int GPIO_BASE, unsigned int pin>
class gpio_pin : public gpio {
public:
void configure(pin_function, unsigned char startupLogic = 0U) override {
/* actual implementation based on options */
}
// other methods are implemented as well
};
void do_stuff(gpio* signalPin) {
// do some very super duper useful work
}
// other usage
gpio_pin<GPIOB, 4> redLed;
redLed.set();
到目前为止,这一切都很好。输入类型特征。当引脚被配置为使用时,类型特征应该通知编译器引脚是否可以这样使用。这是一个可能的 trait 实现示例:
template<unsigned int port, unsigned int pin>
struct is_spi_pin {
static constexpr bool allow() {
switch (port) {
case GPIOE_BASE: if (pin == 1 || pin == 2) return true; break;
case GPIOC_BASE: if (pin == 1 || pin == 2 || pin == 3) return true; break;
case GPIOB_BASE: if (pin == 10 || pin == 12 || pin == 14) return true; break;
default: return false;
}
}
};
这是我遇到的第一个并发症。我知道典型的约定是通过模板专业化来实现这一点,即首选方式是这样的:
template<unsigned int port, unsigned int pin>
struct is_spi_pin {
static const bool value = false;
};
template<>
struct is_spi_pin<GPIOE_BASE, 1> {
static const bool value = true;
};
template<>
struct is_spi_pin<GPIOE_BASE, 2> {
static const bool value = true;
};
/* rest of the SPI pins specializations ... */
但是,我不想为 MCU 的所有引脚的所有可能使用类型实现专门化,所以我认为我可以使用 constexpr 函数。我可以为每种使用类型设置一个功能,这样可以节省一些时间和工作量。请告知,如果我的解决方案是不可取的,出于什么原因......
然后,这是让我很适合的用例......
我有另一个类似的 SPI 外设模板类:
enum class spi_type {
SPI_TYPE0, SPI_TYPE1, SPI_TYPE2, SPI_TYPE4
};
class spi {
public:
virtual void configure(unsigned int speed, spi_type spiType) = 0;
virtual unsigned char write(unsigned char dat) = 0;
virtual void write(unsigned char* buffer, size_t bufferSz) = 0;
virtual void read(unsigned char* dest, size_t destSz) = 0;
};
template<unsigned int SPI_BASE>
class spi_periph : public spi {
public:
spi_periph(gpio* clkPin, gpio* mosiPin, gpio* misoPin)
: clk_(clkPin), mosi_(mosiPin), miso_(misoPin) { }
void configure(unsigned int speed = 2000000LU,
spi_type spiType = spi_type::SPI_TYPE0) override {
/* ... */
}
// other functions ...
private:
gpio *clk_{ nullptr }, *mosi_{ nullptr }, *miso_{ nullptr };
};
// usage
gpio_pin<GPIOA, 5> errorLed{};
errorLed.configure(pin_function::OUTPUT);
gpio_pin<GPIOC, 0> clk{};
gpio_pin<GPIOC, 3> mosi{};
spi_periph<SPI0> spi0{ &clk, &mosi, nullptr };
spi0.configure(/*...*/);
在spi::configure 函数中,实现调用gpio::confugure 在所有引脚上使用pin_function::SPI(如果不是nullptr)。如果 gpio 类型特征表明时钟、mosi 或 miso 引脚不能是 SPI 引脚,我希望编译器抛出错误。
这是我对gpio::configure 函数的实现:
template<unsigned int port, unsigned int pin>
void gpio_pin<port, pin>::configure(pin_function useAs, unsigned char initialLogic) {
switch (useAs) {
case pin_function::SPI:
assert(is_spi_pin<port, pin>::allow());
// configure as SPI pin
break;
case pin_function::OUTPUT:
// configure as output
break;
/* other cases */
}
}
不幸的是,这仅在运行时有效。如果我用 static_assert 替换断言,则编译器不会将 clk 和 mosi 实例标记为错误,如果类型特征表明它们是 SPI 引脚。但是 它会将 errorLed.configure 的调用标记为错误,如果 GPIOA 引脚 5 不是 SPI 引脚,即使我没有尝试使用它!
这可以解决吗???如果是,那么如何?
感谢您阅读所有这些内容,当然,感谢您提供的所有帮助!
【问题讨论】:
-
我认为问题的症结在于
void do_stuff(gpio* signalPin)。你有什么神奇的功能可以在 all 引脚上工作?我会将其作为实现细节隐藏起来,并且没有gpio类。 -
如果你绝对必须有一个基类,那么问题就是
spi_periph(gpio* clkPin, gpio* mosiPin, gpio* misoPin)。如果clkPin必须是SPI引脚,则使其仅接受SPI引脚,而不是所有gpio引脚。 -
但是如果没有minimal reproducible example,就很难理解您面临的问题,因此也很难提出解决方案
-
@MooingDuck,感谢您的 cmets。
void do_stuff(gpio* signalPin)和template<unsigned int SPI_BASE> spi_periph<SPI_BASE>::spi_periph(gpio* clk, gpio* mosi, gpio* miso)没有太大区别。由于template<unsigned port, unsigned pin> class gpio_pin在实例化之前不是一个类型,所以我不能使用 is 作为 spi 类构造函数的参数。据我了解,我必须为此使用基类... -
template<unsigned int SPI_BASE> template<unsigned port, unsigned pin> spi_periph<SPI_BASE>::spi_periph(gpio_pin<port, pin>* clk, gpio* mosi, gpio* miso)工作得很好。
标签: c++ templates embedded template-specialization typetraits