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

Долгое время писал код под МК на С. Друг пишет под МК на С++, посоветовал мне, вот я решил попробовать. Микроконтроллер у меня 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 — если регистры конфигурации разные для каждого таймера?
  • -1
  • 29 октября 2014, 12:18
  • Nemo

Комментарии (34)

RSS свернуть / развернуть
Между вкусно, быстро и недорого всегда удается выбрать только 2 варианта.
0
  • avatar
  • igorp
  • 29 октября 2014, 12:24
всегда удается выбрать только 2 варианта
Как-то можно и три выбрать. Можно попробывать использовать явную специализацию шаблона. Может что и получится (правда, на 100% я не уверен)…
0
int* pulse_width;


Т. к. в этой переменной хранится адрес регистра периферии – добавьте в объявление volatile, иначе оптимизатор может выбросить код обращения к этому регистру (переменной).

Как можно сделать чтобы получить независимую инициализацию для каждого обьекта (канала) PWM — если регистры конфигурации разные для каждого таймера?

Я бы сделал отдельный класс-обертку для таймера. Можно их использовать отдельно от класса Servo, можно связать, инкапсулировать 2 канала PWM в класс таймер. Тогда получим 3 экземпляра таймера, каждый из которых предоставляет интерфейс для двух «своих» PWM каналов.
0
  • avatar
  • e_mc2
  • 29 октября 2014, 12:58
Можно посмотреть, как сделана библиотека Servo для ардуино.
Обычно заводится фабрика или менеджер, который раздает ресурсы (скажем, выдает следующий свободный канал PWM) и при первом обращении инициализирует таймер.
0
  • avatar
  • Vga
  • 29 октября 2014, 14:02
Могу порекомендовать ознакомиться со статьёй комрада neiver о работе с мк под си++ easyelectronics.ru/rabota-s-portami-vvoda-vyvoda-mikrokontrollerov-na-si.html
Так же ознакомиться со списком статей в его блоге.
0
Как будет выглядеть код инициализации всех Servo? Т.е. прошу 6 кусков кода, каждый который инициализирует свой канал PWM независимо.
По этому коду можно поискать общее, что можно вынести в параметр шаблона или еще как.
И идея менеджера ресурсов мне нравится.
0
  • avatar
  • tugo
  • 30 октября 2014, 18:17
0. Читай первый коммент, он правильный

Смотри, ты сам уже вывел первый способ решения задачи:
1. Передать в метод класса адрес переменной, потом передать ее значение
у тебя было
OCR1A = 10;
а стало
Servo servo1(&OCR0A);servo1.write(10);

а с тем же успехом могло бы стать
Servo servo1();servo1.writeRegister(&OCR0A,10);

И то же самое с инициализацией — делаешь метод, в который передаешь указатель и значение, которое ему нужно присвоить (названия регистров вымышленные) —
было
TCCR0A = 0x11;TCCR0B = 0x22;
а станет
Servo servo1(&OCR0A);servo1.init(&TCCR0A,0x11,&TCCR0B,0x22);


2. Ну и конечно в ООП для этого есть наследование и переопределение.
Ты заводишь базовый класс, желательно абстрактный (чтобы не создать экземпляр ненароком) и напишешь в нем метод
class BaseServo
{
protected:
        int* pulse_width;  
public:
        Servo(int* PWM_Channel);
        void write(int data);
        virtual void init()=0;
}

Потом делаешь наследников, которые этот метод переопределяют:

class ServoAtChannel1:BaseServo
{
    public:
    virtual void init();
}
void ServoAtChannel1::init()
{
    TCCR0A = 0x11;
    TCCR0B = 0x22;
}
и так для каждого канала. Работаешь, само-собой с наследниками.

Как закончишь писать все классы — откомпилируй и посмотри внимательно на размер бинарника
и скажи, оно действительно того стОит?
0
Как закончишь писать все классы — откомпилируй и посмотри внимательно на размер бинарника
и скажи, оно действительно того стОит?
Если он будет заметно отличаться от варианта на С в худшую сторону — значит есть еще над чем поработать.
И твой метод не решает того, в чем вопрос — как инициализировать таймер только один раз, если на нем висит несколько серво.
+1
смотри, насколько я знаю принцип работы кода, скомпиленного из класса, в любом случае вызов метода — это вход в функцию, причем как правило будет использоваться стэк.
на сях инициализация делается просто присваиванием, например:
TCCR1A |= ((1<<COM1A1) | (1<<COM1A0) | (1<<WGM11) | (1<<WGM10));
TCCR1B |= ((1<<WGM13) | (1<<WGM12) | (1<<CS11));

на c++ будет класс и метод (или конструктор, не важно), я не знаю, как обойтись без метода в ООП.

ServoAtChannel1::initPWM()
{
    TCCR1A |= ((1<<COM1A1) | (1<<COM1A0) | (1<<WGM11) | (1<<WGM10));
    TCCR1B |= ((1<<WGM13) | (1<<WGM12) | (1<<CS11));
}
...
//потом вызов
myServo1.initPWM();


Тут есть над чем работать? Оверхед от входа в функцию можно как-то исключить?
0
На сях инициализация делается функцией initBlahblahblah, в которой уже то самое присваивание выполняется, так что никакой разницы.
Впрочем, в C++ можно делать инлайновый метод.
А если делать на шаблонах — код работы с серво не только не будет содержать ни одного вызова, но еще и не будет задействовано ни одной переменной.
+1
моя не понимать. прошу пример.
0
Еще б я синтаксис шаблонов помнил.
На примере кода из топика: задаем нужные регистры как параметры шаблона, а конструктор и метод write делаем инлайновые. В конструктор (оставим проблему множественной инициализации таймера на потом) запихиваем код инициализации таймера, write оставляем как есть (только pulse_width теперь определено через шаблон).
Поскольку регистры заданы как константы (через параметры шаблона, конструктор вырождается в несколько LDI R0, IMM; OUT REG, R0, которые подставляются прямо туда, откуда конструктор вызывается, а write вырождается в OUT REG, Rx (опционально LD Rx, IMM/RAM перед ним) и опять же подставляется по месту вызова. Оптимальность сравнима с ассемблерным кодом.
0
Да, насчет инлайнов я за ночь сообразил — будет не хуже, чем на сях. Насчет шаблонов — тоже вроде похоже.
0
смотри, насколько я знаю принцип работы кода, скомпиленного из класса, в любом случае вызов метода — это вход в функцию, причем как правило будет использоваться стэк.

Это не так. Метод можно заинлайнить (как заметил Vga ), метод может быть статический и т д. И есть оптимизатор, который должен эффективно сгенерировать код. Накладные расходы однозначно возникают при использовании полиморфизма, но никто не заставляет его использовать. Плюс, есть возможность использования шаблонов…

не знаю, как обойтись без метода в ООП.
В смысле? Так и пишите:

void main() {
	TCCR1A |= ((1<<COM1A1) | (1<<COM1A0) | (1<<WGM11) | (1<<WGM10));
        TCCR1B |= ((1<<WGM13) | (1<<WGM12) | (1<<CS11));
	…
} 

Никто не заставляет вас создавать классы (если они вам не нужны). Можно использовать исключительно синтаксис С.
0
насчет инициализации один раз — я решил, что отвечаю на вопрос человека, который написал свой первый класс и немного запнулся — подсказывал нужное направление.
Если хочется чего-то более определенного

class BaseServo
{
    protected:
        int* pulse_width;  
        static uchar TCCR0A_buffer;  
        static uchar TCCR0B_buffer;  
        static uchar TCCR1A_buffer;  
        static uchar TCCR1B_buffer;  
    public:
        Servo(int* PWM_Channel);
        void write(int data);
        virtual void prepare()=0;
        static void init();
}
class ServoAtChannel0:BaseServo
{
    public:
    virtual void prepare();
}
class ServoAtChannel1:BaseServo
{
    public:
    virtual void prepare();
}
void ServoAtChannel1::prepare()
{
TCCR0A_buffer |= ((1<<COM0A1) | (1<<COM0A0) | (1<<WGM01) | (1<<WGM00));
TCCR0B_buffer |= ((1<<WGM03) | (1<<WGM02) | (1<<CS01));
}
void ServoAtChannel0::prepare()
{
TCCR1A_buffer |= ((1<<COM1A1) | (1<<COM1A0) | (1<<WGM11) | (1<<WGM10));
TCCR1B_buffer |= ((1<<WGM13) | (1<<WGM12) | (1<<CS11));
}

//этот метод инициализирует всё и запускает таймеры-счетчики
void BaseServo::init()
{
TCCR0A |= TCCR0A_buffer;
TCCR0B |= TCCR0B_buffer;
TCCR1A |= TCCR1A_buffer;
TCCR1B |= TCCR1B_buffer;
...
//соль/сахар - по вкусу
}

main()
{
...
    //готовим всех по одному
    servo1.prepare();
    servo2.prepare();
    //запускаем - всех сразу
    BaseServo::init();
...
}


компилить не пытался.
0
Касательно
2. Ну и конечно в ООП для этого есть наследование и переопределение.

Это прям какой-то антипаттерн программирования. Для каждого объекта, который отличается только значением пары полей (адресами регистров) создавать свой тип (класс).

Так мы дойдем до такого:

class AplusB
{
public:
        int GetSum() {
	return GetA() + GetB();
	}
        virtual int GetA()=0;
        virtual int GetB()=0;
};

class OnePlusTwo : public AplusB {
	int GetA() {return 1};
	int GetB() {return 2};
}	

class TwoPlusTwo : public AplusB {
 …
}

А потом будем удивляться размеру кода.

и так для каждого канала. Работаешь, само-собой с наследниками.

Если уж работаешь с только с наследниками, а не через базовый класс – зачем полиморфизм, зачем метод объявлять виртуальным? Чтобы увеличить размер кода?
+1
Чтобы увеличить размер кода?
а чиркните примерчик, мы и сравним размер кода
0
а чиркните примерчик, мы и сравним размер кода

Пример чего?

Если меряться размером кода – то есть смысл опуститься до уровня ассемблера и использования всяких извратов типа размещения кода в области векторов прерываний. На хабре была статья, где мигание светодиодом на AVR свели к одной инструкции.

Идея ООП – это повышения уровня абстракции, а не экономия бит. И, при грамотном использовании, этот инструмент позволяет исключать накладные расходы.
+1
И, при грамотном использовании, этот инструмент позволяет исключать накладные расходы.
Не всякий, к сожалению. С++ здесь, пожалуй, уникален — именно поэтому в некоторых областях, включая геймдев (уровня ААА, инди можно на чем угодно клепать), альтернатив ему нет.
+1
Не всякий, к сожалению. С++ здесь, пожалуй, уникален — именно поэтому в некоторых областях, включая геймдев (уровня ААА, инди можно на чем угодно клепать), альтернатив ему нет.

Ну, с этим не поспоришь. Хотя многое, ИМХО, объясняется не особенностями именно С++ а тем, что этот язык прочно укрепился в определенных областях став для них стандартом. Вот, например, Delphi обладает аналогичными характеристиками (исправьте меня, если я ошибаюсь), но в тоже геймдев ААА он «не зашел».
0
У Delphi нет таких богатых возможностей оптимизации. Это (на мой вкус) отличный язык, но если необходимо вылизывать производительность и при этом сохранять более-менее высокоуровневый код — он подходит мало.
Хотя некоторое количество тайтлов на нем есть. Небезызвестные «Космические Рейнджеры», например.
0
Я бы конфиг в параметр шаблона(класса Servo) вынес — скорее всего получится без оверхеда. Если шаблоны не хочется -можно в конструктор ссылкой инициализурующую структуру передавать. Указатель на OCR тоже бы сделал ссылкой (обязательно volatile) — код естественнее будет выглядеть.
0
Вообще такой простой класс можно полностью на шаблонных специализациях сделать, тогда нужные регистры выбираются через характеристики типов. Второй путь — выделение канала таймера в отдельный класс и передача его в конструктор вместо указателя на OCR.
+3
Короче, инлайн тут рулит. Все инициализационные методы вызываются однократно, поэтому смело ставим на них inline, подготовку prepare вызываем для каждой сервы последовательно, а запуск — методом init однократно после всех подготовок. Хотя, конечно, компилятор сам решит, будет там реально inline или нет. Ну, и volatile для указателья на регистр. Блин, эти конструкторы расписывать, устал, бросаю как есть.
class BaseServo
{
    protected:
        volatile int* pulse_width;  
        static uchar TCCR0A_buffer;  
        static uchar TCCR0B_buffer;  
        static uchar TCCR1A_buffer;  
        static uchar TCCR1B_buffer;  
    public:
        BaseServo(int* PWM_Channel);
        void write(int data);
        inline virtual void prepare()=0;
        inline static void init();
}
class ServoAtChannel0:BaseServo
{
    public:
    inline virtual void prepare();
}
BaseServo::BaseServo(int* PWM_Channel)
{
    pulse_width = PWM_Channel;
}
class ServoAtChannel1:BaseServo
{
    public:
    inline virtual void prepare();
}
void ServoAtChannel1::prepare()
{
TCCR0A_buffer |= ((1<<COM0A1) | (1<<COM0A0) | (1<<WGM01) | (1<<WGM00));
TCCR0B_buffer |= ((1<<WGM03) | (1<<WGM02) | (1<<CS01));
}
void ServoAtChannel0::prepare()
{
TCCR1A_buffer |= ((1<<COM1A1) | (1<<COM1A0) | (1<<WGM11) | (1<<WGM10));
TCCR1B_buffer |= ((1<<WGM13) | (1<<WGM12) | (1<<CS11));
}

//этот метод инициализирует всё и запускает таймеры-счетчики
void BaseServo::init()
{
TCCR0A |= TCCR0A_buffer;
TCCR0B |= TCCR0B_buffer;
TCCR1A |= TCCR1A_buffer;
TCCR1B |= TCCR1B_buffer;
...
//соль/сахар - по вкусу
}

main()
{
...
    //готовим всех по одному
    servo1.prepare();
    servo2.prepare();
    //запускаем - всех сразу
    BaseServo::init();
...
}
0
Исправьте меня если не прав, но для prepare быть виртуальной в данном случае — лишнее.
+1
Мне кажется это вопрос вкуса, и опять же кажется, что (особенно с учетом инлайнов) скомпилится всё в одинаковый размер,
но разве не логично, что несколько одинаковых классов лучше сделать наследниками от одного предка?
И если у всех классов есть одинаковый метод, разве ему не лучше быть объявленным в этом предке?
0
В этом случае можно просто переопределить, т.к. методы вызываются напрямую на объекте. И еще компилятор может продублировать эти функции отдельно, чтобы можно было обратиться к ним через указатель. Виртуальные функции требуются при косвенном вызове ссылок и указателей на базовый класс (при этом никакие inline работать не будут).
+2
Спасибо, теперь буду знать
0
Хм, всё-таки на С всё попроще… Наверное не буду юзать С++ в AVR :) Всем спасибо!!!
+1
  • avatar
  • Nemo
  • 31 октября 2014, 15:13
Наверное не буду юзать С++ в AVR
Зачем себя ограничивать-то.
Наверное не буду юзать С++ в AVR
А на АРМ будете? Как будто есть какая-то принципиальная разница между этими типами МК…
+1
если уже написаны библиотекы дле переферии на С++ можно юзають и на AVR. на AVR можно юзать С для работы с переферией, а логику делать на С++ — мне кажеться так будет удобней.
тоже и с ARM.
0
если уже написаны библиотекы дле переферии на С++
Да кто ж их писать-то будет. Как правило производители МК на С++ библиотек не делают. Остаётся только самому писать.
а логику делать на С++ — мне кажеться так будет удобней.
Да, на плюсах логику делать удобней.
P.S. Для С++ лучше xmega подходит — у неё периферия удобнее сделана.
Приведу пару примеров для периферии.
SPI на Си++:

#ifndef spi_template_h__
#define spi_template_h__


#include <ioavr.h>
#include "avr_compiler.h"

template <char port> struct xm_spi_t;

template<> struct xm_spi_t<'C'>
{
	enum {
		SPI_BASE = 0x8C0
	};
	enum {
		SS_PORT_BASE = 0x0640
	};
};

template<> struct xm_spi_t<'D'>
{
	enum {
		SPI_BASE = 0x9C0
	};
	enum {
		SS_PORT_BASE = 0x0660
	};
};

template<> struct xm_spi_t<'E'>
{
	enum {
		SPI_BASE = 0x9C0
	};
	enum {
		SS_PORT_BASE = 0x0680
	};
};

template<> struct xm_spi_t<'F'>
{
	enum {
		SPI_BASE = 0xAC0
	};
	enum {
		SS_PORT_BASE = 0x06A0
	};
};

template<char port> struct Spi;

template<char port>
struct Spi
{
	enum { SPI_BASE = xm_spi_t<port>::SPI_BASE };
	
	static struct
	{
		SPI_t* operator-> () { return (SPI_t *)SPI_BASE; }
	} SPIx;

	enum { SS_PORT_BASE = xm_spi_t<port>::SS_PORT_BASE };
	static struct 
	{
		PORT_t* operator-> () { return (PORT_t *)SS_PORT_BASE; }

	} SS_PORTx;

	static void SendByte(unsigned char byte_for_send)
	{
		SPIx->DATA=byte_for_send;
		WaitWhileBusy();
	}
	static unsigned char ReadByte()
	{
		SPIx->DATA=0;
		WaitWhileBusy();
		return SPIx->DATA;
	}
	static void WaitWhileBusy() {
		while ((SPIx->STATUS & SPI_IF_bm) == 0) ;
	}
	static void SendBuffer(unsigned char *data_for_send, unsigned short data_length)
	{
		do {
			SPIx->DATA=*data_for_send++;
			data_length--;
			WaitWhileBusy();
		} while (data_length);
	}
	static void TranceiveData(unsigned char *data_for_send, unsigned char send_data_length, unsigned char * data_buffer_for_read, unsigned short read_data_length)
	{
		SendBuffer(data_for_send, send_data_length);

		do {
			SPIx->DATA=0x00;
			read_data_length--;
			WaitWhileBusy();
			*data_buffer_for_read++=SPIx->DATA;
		} while (read_data_length);
	}
	static void ClearSS()
	{
		SS_PORTx->OUTCLR=PIN4_bm;
		nop();
		nop();
	}
	static void SetSS()
	{
		SS_PORTx->OUTSET=PIN4_bm;
		nop();
		nop();
	}
	static void InitSpiMaster(SPI_PRESCALER_t prescaler)
	{
		/* Init SS pin as output with wired AND and pull-up. */
		SS_PORTx->DIRSET = PIN4_bm;
		SS_PORTx->PIN4CTRL = PORT_OPC_TOTEM_gc;
		/* Set SS output to high. (No slave addressed). */
		SS_PORTx->OUTSET = PIN4_bm;

		/* MOSI and SCK as output. */
		//SS_PORTx->DIRSET  = SPI_MOSI_bm | SPI_SCK_bm;
		SS_PORTx->DIRSET  = PIN5_bm | PIN7_bm;

		/* Instantiate pointer to ssPort. */
		SPIx->CTRL   = 
			SPI_MASTER_bm |
			SPI_CLK2X_bm |
			SPI_ENABLE_bm |prescaler;
		SPIx->INTCTRL=SPI_INTLVL_OFF_gc;
	}
};

#endif // spi_template_h__

ДМА:

#ifndef DMA_TEMPLATE_H_
#define DMA_TEMPLATE_H_

#include "avr_compiler.h"

class critical_section_t
{
public:
	critical_section_t() : StatusReg( SREG ) { cli(); }
	~critical_section_t() { SREG = StatusReg; }

private:
	unsigned char StatusReg;

};

#define CRITICAL_SECTION() critical_section_t cs




template<char channel_number> struct dma_channel
{
	enum {
		DMA_CH0_BASE = 0x110,
		DMA_CH1_BASE = 0x120,
		DMA_CH2_BASE = 0x130,
		DMA_CH3_BASE = 0x140,
	};
	static struct
	{
		DMA_CH_t* operator-> () {
			switch (channel_number) {
			case '0':
				return (DMA_CH_t *)DMA_CH0_BASE;
				break;
			case '1':
				return (DMA_CH_t *)DMA_CH1_BASE;
				break;
			case '2':
				return (DMA_CH_t *)DMA_CH2_BASE;
				break;
			case '3':
				return (DMA_CH_t *)DMA_CH3_BASE;
				break;
			}
			while (1) ;
			return 0;
		}

	} DMA_CH;
	
	INLINE static void ResetChannel()
	{
		DMA_CH->CTRLA = DMA_CH_RESET_bm;
	}
	INLINE static void SetSrcAddress(uint16_t address)
	{
		CRITICAL_SECTION();

		DMA_CH->SRCADDR0=address & 0xFF;
		DMA_CH->SRCADDR1=address>>8;
		DMA_CH->SRCADDR2=0;
	}
	INLINE static void SetDestAddress(uint16_t address)
	{
		CRITICAL_SECTION();
		
		DMA_CH->DESTADDR0=address & 0xFF;
		DMA_CH->DESTADDR1=address>>8;
		DMA_CH->DESTADDR2=0;
	}
	INLINE static void SetTransferSize(uint16_t length)
	{
		CRITICAL_SECTION();
		DMA_CH->TRFCNT=length;
	}
	INLINE static void SetRepeatCount(uint8_t block_number)
	{
		DMA_CH->REPCNT=block_number;
	}
	INLINE static void SetTriggerSource(DMA_CH_TRIGSRC_enum trigger)
	{
		DMA_CH->TRIGSRC=trigger;
	}
	INLINE static void SetAddressControl(DMA_CH_SRCRELOAD_enum src_reload, DMA_CH_SRCDIR_enum src_dir, DMA_CH_DESTRELOAD_enum dest_reload, DMA_CH_DESTDIR_enum dest_dir)
	{
		DMA_CH->ADDRCTRL= 0
			| (uint8_t)src_reload
			| (uint8_t)src_dir
			| (uint8_t)dest_reload
			| (uint8_t)dest_dir
			;
	}
	INLINE static void StartPeripheralTransfer(DMA_CH_BURSTLEN_enum burst_size, bool repeat_enable)
	{
		DMA_CH->CTRLA= 0
			| (1<<DMA_CH_ENABLE_bp)
			| (0<<DMA_CH_RESET_bp)
			| (repeat_enable ? (1<<DMA_CH_REPEAT_bp) : (0<<DMA_CH_REPEAT_bp))
			| (0<<DMA_CH_TRFREQ_bp)
			| 1<<DMA_CH_SINGLE_bp
			| (uint8_t) burst_size
			;
	}
	INLINE static void StartRamToRamTransfer(DMA_CH_BURSTLEN_enum burst_size)
	{
		DMA_CH->CTRLA= 0
			| (1<<DMA_CH_ENABLE_bp)
			| (0<<DMA_CH_RESET_bp)
			| (0<<DMA_CH_REPEAT_bp)
			| (1<<DMA_CH_TRFREQ_bp)
			| (0<<DMA_CH_SINGLE_bp)
			| (uint8_t) burst_size
			;
	}
	INLINE static void ClearIntFlags(bool clear_error_flag, bool clear_trnif)
	{
		CRITICAL_SECTION();

		uint8_t ctrlb = DMA_CH->CTRLB;
		ctrlb = ctrlb 
			| (clear_error_flag ? (1<<DMA_CH_ERRIF_bp) : (0<<DMA_CH_ERRIF_bp))
			| (clear_trnif ? (1<<DMA_CH_TRNIF_bp) : (0<<DMA_CH_TRNIF_bp))
			;
		DMA_CH->CTRLB =ctrlb;
	}
	INLINE static void SetInterruptLevel(DMA_CH_ERRINTLVL_enum error_int_level, DMA_CH_TRNINTLVL_enum complete_int_level)
	{
		DMA_CH->CTRLB = 0
			| (1<<DMA_CH_ERRIF_bp)
			| (1<<DMA_CH_TRNIF_bp)
			| (uint8_t) error_int_level
			| (uint8_t) complete_int_level
			;
	}
	INLINE static void ClearIntFlags(DMA_CH_ERRINTLVL_enum error_int_level, DMA_CH_TRNINTLVL_enum complete_int_level)
	{
		DMA_CH->CTRLB = 0
			| (1<<DMA_CH_ERRIF_bp)
			| (1<<DMA_CH_TRNIF_bp)
			| (uint8_t) error_int_level
			| (uint8_t) complete_int_level
			;
	}
	INLINE static bool TransferComplete()
	{
		return (DMA_CH->CTRLA & DMA_CH_ENABLE_bm) ? false : true;
	}
	INLINE static void SetSrcAddressFast(uint16_t address)
	{
		DMA_CH->SRCADDR0=address & 0xFF;
		DMA_CH->SRCADDR1=address>>8;
		DMA_CH->SRCADDR2=0;
	}
	INLINE static void SetDestAddressFast(uint16_t address)
	{
		DMA_CH->DESTADDR0=address & 0xFF;
		DMA_CH->DESTADDR1=address >> 8;
		DMA_CH->DESTADDR2=0;
	}
	INLINE static void SetTransferSizeFast(uint16_t length)
	{
		DMA_CH->TRFCNT=length;
	}
	INLINE static bool MultiBlockTransferComplete()
	{
		return DMA_CH->REPCNT ? false : true;
	}
};

INLINE static void DmaReset()
{
	DMA.CTRL=DMA_RESET_bm;
}

INLINE static void DmaEnable(DMA_DBUFMODE_enum double_buffer_mode, DMA_PRIMODE_enum priority_mode)
{
	DMA.CTRL = 0
		| (1<<DMA_ENABLE_bp)
		| (0<<DMA_RESET_bp)
		| ((uint8_t)double_buffer_mode)
		| ((uint8_t)priority_mode)
		;
}

#endif /* DMA_TEMPLATE_H_ */
+1
Правильный вывод.
Каждый инструмент должен быть соразмерен поставленной задаче. С++ решает задачи в несколько иной плоскости — это вопросы, связанные с повторным использованием наработок и вопросы, связанные с работой коллектива над одной задачей. Там, где эти вопросы не возникли, скорее всего большого выигрыша от с++ не получится.
+2
На Си++ получаются более масштабируемые решения, но порог вхождения выше. Советую все же перебороть себя и освоить Си++. Также считаю, что не стоит привязывать язык к архитектуре. На AVR нет никаких проблем с Си++, если немного ограничить себя. Я не использую динамический полиморфизм на этой платформе, только статический.
+1
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.