2017-01-05 5 views
1

У меня есть ситуация, когда у меня есть класс на микроконтроллере, который имеет дело с широтно-импульсной модуляцией. Чрезвычайно упрощенный пример:Как избежать виртуальных функций в этом случае?

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); 
} 

Я знаю, что могу достичь того, чего хочу с помощью виртуальных функций. Эта функция короткая и будет вызываться часто, поэтому цена виртуальных функций высока. Есть ли способ избежать их в этом случае или другой шаблон дизайна? Цель состояла в том, чтобы иметь многоразовый код, который легко использовать снаружи. Наличие всего, что мне нужно в массиве, намного облегчает многое.

Edit: Я знаю об этом сообщении Avoiding virtual functions но ответ, который относится к тому, что мне нужно состояние:

Если вы находитесь в ситуации, когда каждый цикл за подсчеты вызовов, то есть вы делаете очень небольшая работа в вызове функции, и вы вызываете из своего внутреннего цикла в критическом для производительности приложении, вам, возможно, нужен совсем другой подход.

+0

Я предлагаю использовать идентификатор MotorDriver при создании нового двигателя. ID является частной переменной, мы используем ID для доступа к массиву таймера. Это означает, что каждый таймер отображает 1 идентификатор. –

+1

Наверное, таймеры совсем не такие, и вы преувеличиваете необходимость относиться к ним по-другому? Альтернативный способ записи драйверов, когда у вас есть x аппаратных периферийных устройств с точно таким же макетом регистра, - это что-то вроде [this] (http://stackoverflow.com/questions/29034417/c-preprocessor-generate-macros-by- concatenation-and-stringification/29035658 # 29035658), где вы просто передаете указатель на смещение базы регистров. Тогда код драйвера для разных таймеров будет на 100% идентичным. – Lundin

ответ

-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; 
} 

Не слишком беспокоиться о производительности - в реальности микроконтроллеры очень быстро и просто с помощью правильной архитектуры (например, ОСРВ или событие ведомой) заставит их скучать 80-90% времени!

Если вы реализуете простой код, и на самом деле это приведет к тому, что ваше приложение будет слишком медленным, то - предположим, что вы не можете улучшить алгоритм или общую архитектуру - просто предкоммутите большинство значений от start() в своем конструкторе и, возможно, отбросьте проверка ошибок (или перемещение в другом месте, из цикла).

Или просто используйте виртуальные функции, влияние косвенного вызова обычно незначительно.

+0

«на самом деле микроконтроллеры очень быстрые». Это довольно упрощение. Даже в 2017 году некоторые люди по-прежнему используют древние вещи, такие как PIC, 8051 или AVR. Ужасно медленные вещи. – Lundin

+0

@ Lundin Все сводится к архитектуре программного обеспечения. Вы не можете ожидать, что такие вещи, как FFT сжатия, будут работать быстро на таких чипах, но прошивка все еще может быть достаточно отзывчивой. Аппаратное обеспечение, которое использовалось при посадке на луну, работало на частоте 2 МГц, и они не сработали (все доступные в настоящее время микроконтроллеры - даже «древние вещи» - в 10 раз быстрее. –

+0

Я использую каналы таймеров в парах, потому что мне также нужен обратный вывод из PWM для заблокированной антифазы. Для инициализации мне пришлось бы передавать шину, предварительный делитель, таймер, контакты и gpio контактов, но самой большой проблемой является инициализация канала, так как каждый канал имеет свою собственную функцию: TIM_OC1Init, TIM_OC2Init, TIM_OC3Init, TIM_OC4Init и каждый мотор использует два, для одного класса нужна первая пара, вторая пара второго сорта, третья - первая пара следующего таймера, ... Я знаю, что таймеры 1 и 8 имеют встроенные обратные каналы, но мне нужно больше из них, поэтому мне нужен был способ для использования всех таймеров с несколькими каналами. –

-1

Вы можете использовать однократное прерывание таймера для всех, тогда вы не будете ограничены количеством таймеров. Вместо того, чтобы изменять настройку таймера в рабочем цикле, вы просто измените переменную, которая скажет, что каждый X тикает, переключает/устанавливает/восстанавливает контакт, соответствующий этому двигателю.И в рутине таймера вы просто создали бы простой цикл с итерациями, равными количеству подключенных моторов и проверяли каждую операцию, например, по модулю, если теперь самое время изменить контакт. Программный PWM с использованием прерывания таймера является хорошим вариантом в этом сценарии.

+0

Это очень плохая идея. Вы окончательно представляете джиттер и можете столкнуться с проблемой производительности и надежности. Не забывать о задержке прерывания для других перехватов и самого программного обеспечения PWM. Если есть аппаратные таймеры, используйте их. – Olaf