【问题标题】:How to avoid virtual functions in this case?在这种情况下如何避免虚函数?
【发布时间】:2017-01-05 02:08:10
【问题描述】:

我有一种情况,我在处理脉冲宽度调制的微控制器上有一门课。极其简化的例子:

class MotorDriver
{
    int pin_;
public:
    MotorDriver(int pin);
    void init();
    void start();
    void stop();
    void changeDutyCycle(int dc);
};

它具有初始化、启动、停止和更改pwm的功能。如果我将 4 个电机连接到微控制器,我将创建该类的 4 个实例并将它们放在一个数组中,然后调用类似的函数

motors[0].changeDutyCycle(50);
motors[1].changeDutyCycle(40);
....

出现问题是因为没有通用的方法在所述微控制器上配置定时器。例如,一台电机必须使用 Timer3,而另一台电机必须使用 Timer4。不同的定时器有不同的位大小、寄存器、通道、引脚......我希望能够为每个定时器编写自定义函数,但仍然能够将所有对象放入同一个数组并在它们上调用函数,即

class MotorDriver
{
    void changeDutyCycle(int dc) = 0;
};

class MotorDriver1 : public MotorDriver
{
    void changeDutyCycle(int dc)
    {
        TIM3->CCR2 = dc;
    }
};

class MotorDriver2 : public MotorDriver
{
    void changeDutyCycle(int dc)
    {
        TIM4->CCR1 = dc;
    }
};

MotorDriver1 md1();
MotorDriver2 md2();
MotorDriver* mds[] = { &md1, &md2 };

int main()
{
    mds[0]->changeDutyCycle(10);
    mds[1]->changeDutyCycle(20);
}

我知道我可以通过虚函数实现我想要的。这个函数很短,而且会经常被调用,所以虚函数的价格很高。在这种情况下或不同的设计模式有没有办法避免它们?目标是拥有易于在外部使用的可重用代码。将我需要的所有东西都放在一个数组中会让很多事情变得更容易。

编辑:我知道这篇帖子Avoiding virtual functions,但与我需要的回答有关:

如果您处于每次调用的每个周期都很重要的情况,那就是 你在函数调用中做的工作很少而且你正在调用 它来自您在性能关键应用程序中的内部循环 可能需要完全不同的方法。

【问题讨论】:

  • 我建议在创建新电机时使用 MotorDriver id。 ID 是一个私有变量,我们使用 ID 来访问一个定时器数组。这意味着每个计时器映射到 1 个 ID。
  • 可能,计时器根本没有什么不同,而您夸大了区别对待它们的必要性?当您有 x 数量的硬件外设具有完全相同的寄存器布局时,另一种编写驱动程序的方法是类似于this,您只需将指针传递给寄存器基址偏移量。除此之外,不同计时器的驱动程序代码将 100% 相同。

标签: inheritance embedded virtual-functions


【解决方案1】:

定时器之间的差异通常很小,尤其是在配置实际输出宽度时 - 初始化可能不同,但您可以使用虚函数。只需将对底层 TIM 寄存器和通道索引的引用存储在您的类中,这似乎就是您所要做的。如果您使用诸如“互补”通道之类的东西,那么您可以将它们存储为负索引。

检查此代码 - 它在 STM32F4 上用于非常相似的目的(驱动步进电机),但应该会给你一个想法。

namespace
{

/// array with all CCR registers
const decltype(&TIM_TypeDef::CCR1) ccrs[]
{
        &TIM_TypeDef::CCR1,
        &TIM_TypeDef::CCR2,
        &TIM_TypeDef::CCR3,
        &TIM_TypeDef::CCR4
};

constexpr bool isAdvancedControlTimer(const TIM_TypeDef& tim)
{
    return &tim == TIM1 || &tim == TIM8;
}

}   // namespace

TIM_TypeDef& HardwareTimer::getTim() const
{
    //  "timBase_" is "uintptr_t timBase_;"
    // initialized with TIM1_BASE, TIM2_BASE, ...
    return *reinterpret_cast<TIM_TypeDef*>(timBase_);
}

int HardwareTimer::start(const int8_t channel, const uint16_t compare) const
{
    if (channel == 0)
        return EINVAL;
    const auto complementaryChannel = channel < 0;
    const auto channelShift = (complementaryChannel == true ? -channel : channel) - 1;
    if (channelShift >= 4)
        return EINVAL;
    auto& tim = getTim();
    const auto advancedControlTimer = isAdvancedControlTimer(tim);
    if (complementaryChannel == true && advancedControlTimer == false)
        return EINVAL;

    tim.*ccrs[channelShift] = compare;
    if (advancedControlTimer == true)
        tim.BDTR |= TIM_BDTR_MOE;
    tim.CR1 |= TIM_CR1_CEN | TIM_CR1_URS;

    return 0;
}

不要太担心性能 - 实际上微控制器速度非常快,仅使用适当的架构(如 RTOS 或事件驱动)就会导致它们在 80-90% 的时间里感到无聊!

如果你实现了简单的代码,它实际上会导致你的应用程序太慢,那么 - 假设你不能改进算法或整体架构 - 只需在你的构造函数中预先计算来自 start() 的大部分值,也许删除错误检查(或将其移到其他地方,从循环中移出)。

或者只是使用虚函数,间接调用的影响通常可以忽略不计。

【讨论】:

  • “实际上微控制器的速度非常快” 这有点过于简单化了。即使在 2017 年,有些人仍在使用 PIC、8051 或 AVR 等古老的东西。非常缓慢的事情。
  • @Lundin 这一切都归结为软件的架构。你不能指望诸如压缩的 FFT 之类的东西在这样的芯片上运行得很快,但固件仍然可以有足够的响应能力。登月时使用的硬件以 2MHz 的频率运行并且它们没有崩溃(;所有当前可用的 MCU——即使是“古老的东西”也快 10 倍。
  • 我正在成对使用定时器通道,因为我还需要 PWM 的反向输出来锁定反相。对于初始化,我必须通过总线、预分频器、定时器、引脚和引脚的 gpio,但最大的问题是通道初始化,因为每个通道都有自己的功能:TIM_OC1Init、TIM_OC2Init、TIM_OC3Init、TIM_OC4Init,每个电机使用两个,一个类需要第一对,第二类第二对,第三类需要第一对下一个定时器,......我知道定时器 1 和 8 已经内置了反向通道,但我需要更多,所以我需要一种方法来利用所有定时器更多不止一个频道。
  • 但是你说得有道理!我只能将虚函数用于初始化,并将定时器及其 CCR 寄存器用于将脉冲更改为私有变量。
  • @Terraviper-5 通道的初始化实际上几乎相同(从上面的代码中可以看到)。问题出在 ST 的垃圾软件中,它以绝对无能为力的方式编写,所以这里有 4 个几乎相同的函数 TIM_OCxInit(),执行完全相同但在不同的寄存器上......如果你关心性能,第一步甚至在考虑替换虚拟功能之前,将转储那个毫无意义的“库”并编写自己的。
【解决方案2】:

你可以使用单个定时器中断,那么你就不会受限于定时器的数量。无需更改定时器的占空比设置,您只需更改变量,即每个 X 滴答声切换/设置/重置与该电机对应的引脚。在计时器例程中,您只需创建简单的 for 循环,其迭代次数等于连接的电机数量,并检查每个即模运算,如果现在是更改引脚的时间。在这种情况下,使用定时器中断的软件 PWM 是不错的选择。

【讨论】:

  • 这是一个非常糟糕的主意。您肯定会引入抖动,并且也可能在性能和可靠性方面存在问题。不要忘记其他中断和软件 PWM 本身的中断延迟。如果有硬件计时器,请使用它们。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-04-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-07-21
  • 1970-01-01
  • 2013-07-17
相关资源
最近更新 更多