Инициализация PWM конструктором класса ( C++ )

Блог им. Nemo
Долгое время писал код под МК на С. Друг пишет под МК на С++, посоветовал мне, вот я решил попробовать. Микроконтроллер у меня ATmega88А, IDE Atmel Studio 6.2. Буду использовать 6 апаратных PWM для управления сервоприводами, так как нужно писать 6 одинаковых кусков кода, решил сделать класс Servo.


class Servo
{
private:
	int* pulse_width;  

public:
	Servo(int* PWM_Channel);
	void write(int data);
}; //Servo


через конструктор передаем куда будем писать наше значение, тоесть регистр таймера для управления PWM

// default constructor
Servo::Servo(int* PWM_Channel)
{
	pulse_width = PWM_Channel;
} //Servo


void Servo:: write(int data)
{
	*pulse_width = data;
}


Используем:

int main(void)
{ 
    Servo servo1(&OCR0A);
    Servo servo2(&OCR0B);
	
    servo1.write(10);
    servo2.write(50);
    
    while(1)
    {
    }
}


Вроде код получился читабельней.

Но вот как быть с инициализацией. Писать просто функцию где будут инициализироваться 3 апаратных таймера как то не по ООП. Добавить в конструктор весь код, будет инициализировать всё 6 раз, тоесть делать то же самое. Как можно сделать чтобы получить независимую инициализацию для каждого обьекта (канала) PWM — если регистры конфигурации разные для каждого таймера?

14 комментариев

avatar
Если хочется ООП, то нужно сделать общий интерфейс и по классу — каналу, который реализует интерфейс. Пример:

class IPwmChannel {
public:
    virtual void Initialize() = 0;
    virtual void SetValue(int value) = 0;
}

class Pwm1 : public IPwmChannel {
public:
    void Initialize() { /* инициализация канала 1*/ };
    void SetValue(int value) { /* установка занчения канала 1*/};
}


А потом уже можно делать клас серво-машинки

class Servo
{
private:
        IPwmChannel *_pwm;  

public:
        Servo(IPwmChannel* pwm) { _pwm = pwm; }
        void Write(int data) { _pwm->SetValue = data; }
};


Ну и все. Как использвоать, думаю, очевидно.
avatar
а как быть с тем что указатели для OCR таймера0 и таймера2 — uint8_t*, а для таймера2 uint16_t*?
avatar
Скрыть эти различия за интерфейсом IPwmChannel. Самый простой вариант — претвориться, что все таймера 16 битные и использовать только старшие 8 бит в 8 битных.

Более высокоуровневый подход — проценты заполнения ШИМ и из процентов пересчитывать в заполнение.
avatar
Мне кажется, или я этот пост уже где-то видел?
avatar
я думаю что видели…
avatar
Запретииииить!!!
Использование С++ для 8-битных контроллеров не надо, нельзя, нихт, кайн и т.д. и т.п.
Был опыт правда на ATmega128 (камень более жирный и по ОЗУ и по ПЗУ). Пытался делать некий пользовательский интерфейс на ЖК, с возможностью переконфигурирования (т.е. имелось несколько классов: Window, Label и т.д.). Больше 2-х окон было создать невозможно: тупо заканчивалась память. Вообще у меня сложилось мнение, что для меги надо использовать только С.
avatar
Мой друг использовал С++ для ATTINY1634-MU, там были несколько каналов PWM, чтение с ADC, чтение пинов для конвигурации. Всё работает. правда он не использовал new и delete. так что не могу с Вами согласиться…
avatar
Так я же не спорю с тем, что это не возможно в принципе. Но развернуться все равно не удастся. Тогда зачем расширять языковые рамки, если потом все время придется себя сдерживать. А еще хуже, что когда вы построите всю абстракцию C++ и дойдете до конкретной реализации, то можете вылететь за пределы ОЗУ и в этот момент поймете, что этот подход вам в принципе не годится (так и было у меня). Поэтому я ставлю себе заранее рамки: для AVR-8 только С и никакого С++.
avatar
Не стоит путать красное с пресным:
C++ это не только абстракция и наследование, но еще и как минимум(краткий список полезных для эмбеддера фич):
0. Пространства имен! Нет, правда, создавая большой проект на С я затрахиваюсь перед каждой функцией добавлять префикс. В С++ это решается с помощью namespace и классов. И когда ты ставишь точку, автодополнение дает тебе список ТОЛЬКО того, что тебе можно.
1. Разделение уровней доступа — если у меня и есть какая-то внутренняя переменная, то в С иногда сложно избежать соблазна не поставить костыль. В C++ внутри класса я просто делаю ее private. Отсюда — приходится продумывать архитектуру приложения — на поддержке кода это очень пригодится.
2. Перегрузка функций и шаблоны. Правда, просто обожаю в С продумывать функции для всех возможных типов данных. И для uint8_t и для int8_t и 16_t и 32_t и float и т.д. и т.п.
3. Безопасное кастование! Сравните static_cast и reinterpret_cast(аналог С-шных скобочек (type_t)var). Мне иногда попадаются просто фееричные преобразования типов в разгребаемом коде, когда они неявно перегоняются из знакового в беззнаковый, из short в long да по нескольку раз… static_cast в отличие от reinterpret_cast в неподобающих случаях вызовет ошибку компиляции и заставит хорошенько подумать. Причем в С ошибка может быть обнаружена только с помощью статического анализа кода(PVS Studio справляется, Clang и cppcheck — как правило нет). Ну может быть он ругнется на сравнение знаковой и беззнаковой переменной в if или в переходе от double к float… или вы скормили const в не cosnt…
4.Перечисляемые типы. Дефайны — зло. Есть тысяча и один кейсов, когда они могут привести к некоторым проблемам, ОСОБЕННО!!! при отсутствии пространства имен. Вы уверены, что своим дефайном вы ничего не переопределили? Или, например, не включили какую-нибудь опцию вида #if defined(FOO)? В том или ином виде enum есть со времен ANSI C, Classname.enum таки гораздо функциональнее.

Список можно продолжать до бесконечности. Самое главное — все что я описал не требует мегабайтов памяти ни в коде ни в оперативке, реализуется абсолютно всеми компиляторами C++ (в отличие от специфичных функций современных стандартов) и реально экономит время и силы.

Стек и память вообще очень просто контролируются.
avatar
Ого сколько интересного! Может есть книга вроде С++ для embedded?
avatar
Не встречал. Да и что embedded что не embedded — я лишь перечислил малую долю инструментов С++, которые улучшают качество кода. Об этом можно почитать у Макконекела «Совершенный код» — толстенный и полезный талмуд. Конкретно по С++ рекомендую прочесть статьи блога PVS Stduio на хабре и на сайте — там что ни статья — разбирается огромное число примеров простых но серьезных ошибок.

Для Embedded, ИМХО, достаточно не использовать дорогие операции. STL немножко можно, но аккуратно.
Если ARM и в запасе ОЗУ больше 64к — ни в чем себе не отказывайте :)
Сейчас пилю проект на 16МГц ARM Cortex-M3 и 8кбайт ОЗУ (из которых 6 съедают буферы для UART) — вот где боль :)

Для матерых С-шников вроде меня рекомендую книгу Ira Pohl C++ for C programmers. Все перечисленное выше — там подробно разбирается. Но конкретно перечисленное выше — то что реально облегчает мой эмбед-кодинг.
До сих пор эту книгу никак не закажу, но я проходил курс на Coursera в начале года.

А так, наиболее полный источник — это практика :) Практика разгребания чужого кода конечно же :)
avatar
Если Вы не в курсе, что созданные объекты занимают память это не значит что метод плох — это значит что в консерватории фальшивят. И настройки компилятора ещё никто не отменял. В общем учиться, учиться и учиться. Ваш Кэп.
avatar
И таки да: кривые руки запретить точно. При всём моём уважении. )
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.