Класс-родитель для устройств на базе SPI, или веселый и удобный C++ для AVR

Мне всегда нравилась идея объектно-ориентированного программирования. Это очень удобно и легко, особенно, когда программа раздувается до больших размеров, или есть несколько очень похожих элементов, но с разными настройками. И меня всегда интересовали нестандартные, красивые решения и новинки языка — шаблоны, лямбда-функции, тернарные операторы… К сожалению, я все никак не мог к ним подобраться — то времени не было, то мозг был не готов. В общих чертах знал, что это, но сам никогда не пробовал. Но вдруг в одной из программ для AVR я увидел интересное использование шаблона, которое очень сильно облегчало работу. Мне стало интересно — и время нашлось, и желание… И вот родилась идея этой статьи. Результат — родительский класс для легкой работы с устройствами на базе SPI (сдвиговые регистры, трансиверы, Ethernet etc), в Hardware и Software реализации. Интересно — просьба под кат.

tl;dr — в конце все ссылки из статьи, в том числе готовый код и примеры.

WARNING 1: под катом много букв и о ужас ни одной картинки, а также огромная куча низкокачественного лапшеобразного замечательного кода.
WARNING 2: автор не крут в шаблонах, поэтому извиняется, если что-то реализовано ужасно.
WARNING 3: пока что — только Master, Slave-версия будет через год некоторое время.
WARNING 4: автор обожает варнинги и тег s неправда!!

Зарождение
Когда имеешь дело с большим количеством периферии, начинаешь тихо ненавидеть дефайны с портами и пинами. Их становится столько, что не протолкнуться. Повесим SlaveSelect в один порт с Hardware SPI. Ой, туда тяжело дорожку провести, переставим на другой. И с ним, конечно, сделаем дефайны для DDRx и PINx — мы же хотим, чтобы все само инициализировалось. И так для каждого… А если забивать железно, при любом изменении конструкции приходится лопатить весь код.

У меня возникла идея — почему бы не сделать все классом? Все порты указывать через аргументы конструктора. Удобно, красиво, мало места. Но. PORTx — не функция, не тип, это зубодробительный макрос, который фиг куда запишешь. Споткнувшись на этом, я забросил идею ООП для работы с портами AVR.

В один счастливый день на одном хорошем сайте (там, где живет создатель Pinboard ;) ) я наткнулся на один прекрасный листинг. Neiver использовал хитрый макрос и шаблон для работы с портами. Он с помощью макроса подставлял значения PORTx etc в методы нового класса, который был объявлен целиком в макросе. При вызове этого чуда создавался класс, который можно передавать куда угодно и который дает доступ до PORT, DDR и PIN, при этом реализуя часть операций типа Read, Set, Toggle и т.д.. Мне так понравилась эта идея, что я оформил ее в маленькую библиотеку, которую довольно часто использую. К сожалению, связаться с автором не было возможности, но я везде указываю его, так что думаю, он не обидится… Код находится тут, я старался хорошо прокомментировать каждый пункт, поэтому подробно рассказывать о работе этой библиотеки не буду.

Проблема с портами решена, теперь можно приступить к самому интересному. Написав пару классов с использованием этой фичи, я подумал: было бы здорово сделать класс-родитель, от которого будет наследоваться работа с интерфейсом. Решено — будем писать такую штуку для SPI.

Реализация Hardware SPI
У меня есть библиотека libSPI (основу взял у Tinkerer) в трех вариантах — Hardware (использует железный SPI), USI-based (работает на базе USI — например, для ATtiny24) и Software (полностью программный вариант). Откинув вторую (USI сейчас довольно редко встречается), я начал с самого легкого — Hardware. Накидал вот такой скелет класса-родителя:
template <class PORT, uint8_t PIN_SS> 
	class SPI_Base_Hardware {
	public:
		// в конструкторе инициализируем порт и SPI
		SPI_Base_Hardware() { spi_init();}
		~SPI_Base_Hardware(){;}

		void init();

		// выбор и освобождение устройства (select - SS=>LOW, release - SS=>HIGH)
		void select();
		void release();

		// чтение и запись одного байта
		uint8_t fast_shift(uint8_t dataout);
		// запись массива данных
		void transmit_sync(uint8_t *dataout, uint8_t len);
		// чтение и запись массива данных
		void transfer_sync(uint8_t *dataout, uint8_t * datain, uint8_t len);
};

Для тех, кто не знаком с шаблонами, кратко опишу суть этой штуки (те, кто знают — просьба не пинать за столь краткий и не совсем правильный вариант...). Шаблон позволяет подставлять разные типы в одну и ту же конструкцию. В данном случае при инициализации объекта SPI_Base_Hardware я указываю класс порта ввода-вывода (тот самый, который создаем макросом MAKE_PORT) и номер пина SlaveSelect (далее SS), с которыми потом могу оперировать в классе с помощью слов PORT и PIN_SS соответственно. Вот тут довольно неплохо описано, что это за зверь и с чем его едят, на примерах.

Первые грабли
Я люблю структурированный код. Описание в .h, реализация в .cpp — стараюсь делать так всегда. Первые грабли появились, когда я попытался вынести реализацию за объявление класса. Будучи плохо знакомым с шаблонами, я получил кучу непонятных ошибок, беглое гугление которых мало относилось к текущей проблеме. Мне все же удалось найти немного информации на каком-то форуме, оказавшееся довольно. Вынести реализацию в другой файл нельзя (печалька), но можно вынести ее за пределы объявления, оставив в этом же файле. Для этого нужно сделать следующее:
// тут скелет, который написан выше
...

template <class PORT, uint8_t PIN_SS>
void SPI_Base_Hardware<PORT, PIN_SS>::_spi_select(){
	PORT::Clear(1<<PIN_SS);
}


Такая конструкция мне все равно не понравилась, и еще через некоторое время был найден довольно простой вариант. Из основной инклюды (в нашем случае — spi_base_hardware.h) описание класса выносим в spi_base_hardware.hpp, всю реализацию — в spi_base_hardware.cpp. При этом сам файл spi_base_hardware.h становится вот таким:
#include "spi_base_hardware.hpp"
#include "spi_base_hardware.cpp"


Вот и все. Описание отдельно, реализация отдельно, я доволен :)

Продолжаем писать
Написав реализацию каждой функции, я решил пойти дальше. В Hardware-версии порт и пины интерфейса SPI меняться ну просто не могут (на то он и Hardware). Так давайте заведем на них дефайны, скажете Вы. Да, давайте, отвечу я, но при этом добавлю — только Вы их не увидите. Пускай они будут сами добавляться, для нужного камня — нужные дефайны. Открываем файлик include\io.h и начинаем перебирать все МК — гуглим pinout. Потом в стиле все того же io.h создаем файл, который будет проверять выбранную версию МК и создавать нужный дефайн. Вот что у меня получилось spi_hardware_defs.h. На все МК меня не хватило, список поддерживаемых (все их версии) — в начале того файла. Если хоть кому-то эта штука будет полезна — обязательно доведу до конца.

И что же получилось?
spi_hardware.h
#ifndef _LIB_SPI_HARDWARE_H_
#define _LIB_SPI_HARDWARE_H_

#include "spi_hardware_defs.h"
#include "spi_hardware.hpp"
#include "spi_hardware.cpp"

#endif


spi_hardware.hpp
#ifndef _LIB_SPI_HARDWARE_HPP_
#define _LIB_SPI_HARDWARE_HPP_

#include <avr/io.h>

template <class PORT, uint8_t PIN_SS>
class SPI_Base_Hardware {
	public:
		SPI_Base_Hardware() { init();}
		~SPI_Base_Hardware(){;}

		void init();

		void select();
		void release();

		uint8_t fast_shift(uint8_t dataout);
		void transmit_sync(uint8_t *dataout, uint8_t len);
		void transfer_sync(uint8_t *dataout, uint8_t * datain, uint8_t len);
};

#endif


spi_hardware.cpp
#include <avr/io.h>
#include "spi_hardware_defs.h"
#include "spi_hardware.hpp"

template <class PORT, uint8_t PIN_SS>
void SPI_Base_Hardware<PORT, PIN_SS>::
select(){
	PORT::Clear(1<<PIN_SS);
}

template <class PORT, uint8_t PIN_SS>
void SPI_Base_Hardware<PORT, PIN_SS>::
release(){
	PORT::Set(1<<PIN_SS);
}

template <class PORT, uint8_t PIN_SS>
void SPI_Base_Hardware<PORT, PIN_SS>::
init(){
	SPI_DDR|=1<<SPI_MOSI | 1<<SPI_SCK;
	SPI_DDR&=~(1<<SPI_MISO);
	PORT::DirSet(1<<PIN_SS);
	release();

	SPCR = ((1<<SPE)|			// SPI Enable
		   (0<<SPIE)|			// SPI Interupt Enable
		   (0<<DORD)|			// Data Order (0:MSB first / 1:LSB first)
		   (1<<MSTR)|			// Master/Slave select
		   (0<<SPR1)|(1<<SPR0)|	// SPI Clock Rate
		   (0<<CPOL)|			// Clock Polarity (0:SCK low / 1:SCK hi when idle)
		   (0<<CPHA));			// Clock Phase (0:leading / 1:trailing edge sampling)
	SPSR = (1<<SPI2X);			// Double Clock Rate
}

template <class PORT, uint8_t PIN_SS>
uint8_t SPI_Base_Hardware<PORT, PIN_SS>::
fast_shift(uint8_t dataout){
	SPDR = dataout;
	while((SPSR & (1<<SPIF))==0);
	return SPDR;
}

template <class PORT, uint8_t PIN_SS>
void SPI_Base_Hardware<PORT, PIN_SS>::
transmit_sync(uint8_t *dataout, uint8_t len){
	uint8_t i;
	for (i = 0; i < len; i++) {
		SPDR = dataout[i];
		while((SPSR & (1<<SPIF))==0);
	}
}

template <class PORT, uint8_t PIN_SS>
void SPI_Base_Hardware<PORT, PIN_SS>::
transfer_sync(uint8_t *dataout, uint8_t * datain, uint8_t len){
	uint8_t i;
	for (i = 0; i < len; i++) {
		SPDR = dataout[i];
		while((SPSR & (1<<SPIF))==0);
		datain[i] = SPDR;
	}
}


Примеры
Получившийся класс можно использовать несколькими способами — как драйвер для интерфейса SPI, как родитель для другого класса, как родитель для другого класса-шаблона и как драйвер, содержащийся внутри класса-шаблона. Покажу все виды.
Пример 1. Hardware SPI, использование в качестве драйвера
#include <avr/io.h>
#include "port.h"
#include "spi_hardware.h"

MAKE_PORT(PORTB, DDRB, PINB, Portb, 'B');
MAKE_PORT(PORTD, DDRD, PIND, Portd, 'D');

int main(){
	// создаем интерфейс Hardware SPI с SlaveSelect на ноге PB0
	// при этом мы нигде не указываем ноги MOSI MOSI SCK - они находятся сами
	SPI_Base_Hardware<Portb, 0> driver1;
	// он уже готов - ноги инициализированы, SS=HIGH, регистры настроены
	driver1.fast_shift(0x10); // пишем байт

	// создаем еще интерфейс, SS на ноге PD5
	SPI_Base_Hardware<Portd, 5> driver2;
	driver2.fast_shift(0x20);
		
	while (1) {}
	return 0;
}


Пример 2. Hardware SPI, создание класса-наследника
#include <avr/io.h>
#include "port.h"
#include "spi_hardware.h"

MAKE_PORT(PORTB, DDRB, PINB, Portb, 'B');

class Device : public SPI_Base_Hardware<Portb, 0>{
public:
	void init(){
		fast_shift(0x31);
	}
};

int main(){
	// создаем объект Device - Hardware SPI, SlaveSelect на ноге PB0
	Device monster;
	// SPI уже готов - вызван конструктор от SPI_Base_Hardware
	// вызываем метод нашего класса - "инициализация"
	monster.init();

	while (1) {}
	return 0;
}


И, наконец, самое интересное…
Пример 3. Hardware SPI, создание шаблона класса-наследника
#include <avr/io.h>
#include "port.h"
#include "spi_hardware.h"

MAKE_PORT(PORTB, DDRB, PINB, Portb, 'B');
MAKE_PORT(PORTD, DDRD, PIND, Portd, 'D');

template <class PORT, uint8_t PIN_SS>
class Creature : public SPI_Base_Hardware<PORT, PIN_SS>{
public:
	void init(){
		this->fast_shift(0x31);
	}
};

int main(){
	// создаем объект Device - Hardware SPI, SlaveSelect на ноге PB0
	Creature<Portb, 0> monster1;
	// SPI уже готов - вызван конструктор от SPI_Base_Hardware
	// вызываем метод нашего класса - "инициализация"
	monster1.init();

	// проделываем то же самое для монстра с SS на ноге PD5
	Creature<Portd, 5> monster2;
	monster2.init();

	// управлялись бы все монстры по SPI... Я бы завоевал мир ;)

	while (1) {}
	return 0;
}

Тут нужно сделать оговорку. Вы наверняка заметили, что в методе init я использовал this->spi… Если этого не сделать, компилятор будет громко вопить, что он не может найти эти методы. Это касается всех методов, которые есть в классе SPI_Base_Hardware.

У всех предыдущих примеров есть один недостаток — имена методов fast_shift etc заняты, что не критично, но как-то нехорошо… Поэтому я считаю самым оптимальным вариантом…

Пример 4. Hardware SPI, создание шаблона класса, содержащим внутри себя драйвер
#include <avr/io.h>
#include "port.h"
#include "spi_hardware.h"

MAKE_PORT(PORTB, DDRB, PINB, Portb, 'B');
MAKE_PORT(PORTD, DDRD, PIND, Portd, 'D');

template <class PORT, uint8_t PIN_SS>
class Creature {
private:
	SPI_Base_Hardware<PORT, PIN_SS> spi;
public:
	void init(){
		spi.fast_shift(0x31);
	}
};

int main(){
	// создаем объект Device - Hardware SPI, SlaveSelect на ноге PB0
	Creature<Portb, 0> monster1;
	// SPI уже готов - вызван конструктор от SPI_Base_Hardware
	// вызываем метод нашего класса - "инициализация"
	monster1.init();

	// проделываем то же самое для монстра с SS на ноге PD5
	Creature<Portd, 5> monster2;
	monster2.init();

	while (1) {}
	return 0;
}
Драйвер инкапсулирован, теперь все вообще круто :)

А что дальше?
Наигравшись с Hardware SPI, я решил заняться программной версией. Тут уже открываются большие горизонты: повесить несколько устройств на разные порты, не обращая внимания на распиновку контроллера — куда легко вести, туда и провел. Красота!

Скелет будет таким же, кроме названия класса и шаблона:
template <class PORT, uint8_t PIN_MOSI, uint8_t PIN_MISO, uint8_t PIN_SCK, uint8_t PIN_SS>
class SPI_Base_Software {
	public:
		SPI_Base_Software() { spi_init();}
		~SPI_Base_Software(){;}

		void init();
		// дальше все то же самое, что в скелете SPI_Base_Hardware

};

Теперь передается не только порт и пин SS, а также пины MISO, MOSI и SCK. Но что, если у нас нет свободных ног в одном порту? Правильно, раскидываем по разным — у нас же программная реализация, делаем что хотим. Шаблон меняется до такого вида:
template <class PORT_MOSI, uint8_t PIN_MOSI, class PORT_MISO, uint8_t PIN_MISO,
		class PORT_SCK,  uint8_t PIN_SCK,  class PORT_SS,   uint8_t PIN_SS>
class SPI_Base_Software_Separate {
	...
};

Он вырос и превратился в огромную строку. В реализации придется писать так:
template <class PORT_MOSI, uint8_t PIN_MOSI, class PORT_MISO, uint8_t PIN_MISO,
		class PORT_SCK,  uint8_t PIN_SCK,  class PORT_SS,   uint8_t PIN_SS>
void SPI_Base_Software_Separate<PORT_MOSI,PIN_MOSI, PORT_MISO,PIN_MISO, PORT_SCK,PIN_SCK, PORT_SS,PIN_SS>::
select(){
	PORT_SS::Clear(1<<PIN_SS);
}

Жуть… Но если сделать два макроса, то все смотрится очень даже гармонично:
template _SPI_SOFT_SEP_TEMPLATE_
void SPI_Base_Software_Separate _SPI_SOFT_SEP_REALIZATION_
select(){
	PORT_SS::Clear(1<<PIN_SS);
}
Так ведь лучше? Эти макросы я буду активно использовать в дальнейшем.

Пишем дальше: реализация Software SPI
Я оставил два варианта — Software SPI и Separate Software SPI. Первый висит на одном порту и принимает в шаблоне класс порта и 4 пина — MOSI, MISO, SCK и SS. Второй принимает порт для каждого пина, выглядит это так:
SPI_Base_Software_Separate<Portb, 0, Portc, 1, Portb, 4, Portd, 2> device;

Чтобы не делать два одинаковых класса, я сделал SPI_Base_Software наследником от SPI_Base_Software_Separate. Вот так:
template _SPI_SOFT_TEMPLATE_
class SPI_Base_Software : public SPI_Base_Software_Separate<PORT,PIN_MOSI, PORT,PIN_MISO, PORT,PIN_SCK, PORT,PIN_SS> {};


И что получилось?
spi_software.h
#ifndef _LIB_SPI_SOFTWARE_H_
#define _LIB_SPI_SOFTWARE_H_

#include "spi_software.hpp"
#include "spi_software.cpp"

#endif


spi_software.hpp
#ifndef _LIB_SPI_SOFTWARE_HPP_
#define _LIB_SPI_SOFTWARE_HPP_

#include <avr/io.h>

// macros for easy operating
// ex: template _SPI_SOFT_SEP_TEMPLATE_
#define _SPI_SOFT_SEP_TEMPLATE_ <class PORT_MOSI, uint8_t PIN_MOSI, class PORT_MISO, uint8_t PIN_MISO,\
									 class PORT_SCK,  uint8_t PIN_SCK,  class PORT_SS,   uint8_t PIN_SS>
// class nameOfClass : _SPI_SOFT_SEP_PARENT_
#define _SPI_SOFT_SEP_PARENT_ public SPI_Base_Software_Separate<PORT_MOSI, PIN_MOSI, PORT_MISO, PIN_MISO, PORT_SCK, PIN_SCK, PORT_SS, PIN_SS>
// nameOfClass _SPI_SOFT_SEP_REALIZATION_ func
#define _SPI_SOFT_SEP_REALIZATION_ <PORT_MOSI,PIN_MOSI, PORT_MISO,PIN_MISO, PORT_SCK,PIN_SCK, PORT_SS,PIN_SS>::

// same for non-separate
#define _SPI_SOFT_TEMPLATE_ <class PORT,uint8_t PIN_MOSI,uint8_t PIN_MISO,uint8_t PIN_SCK,uint8_t PIN_SS>
#define _SPI_SOFT_PARENT_ public SPI_Base_Software<PORT, PIN_MOSI, PIN_MISO, PIN_SCK, PIN_SS>
#define _SPI_SOFT_REALIZATION_ <PORT, PIN_MOSI, PIN_MISO, PIN_SCK, PIN_SS>::

template <class PORT_MOSI, uint8_t PIN_MOSI, class PORT_MISO, uint8_t PIN_MISO,
		  class PORT_SCK,  uint8_t PIN_SCK,  class PORT_SS,   uint8_t PIN_SS>
class SPI_Base_Software_Separate {
	public:
		SPI_Base_Software_Separate() { init();}

		

		~SPI_Base_Software_Separate(){;}

		void init();

		void select();
		void release();

		uint8_t fast_shift(uint8_t dataout);
		void transmit_sync(uint8_t *dataout, uint8_t len);
		void transfer_sync(uint8_t *dataout, uint8_t *datain, uint8_t len);
};

template _SPI_SOFT_TEMPLATE_
class SPI_Base_Software : public SPI_Base_Software_Separate<PORT, PIN_MOSI, PORT, PIN_MISO, PORT, PIN_SCK, PORT, PIN_SS> {};

#endif


spi_software.cpp
#include "spi_software.hpp"


template _SPI_SOFT_SEP_TEMPLATE_
void SPI_Base_Software_Separate _SPI_SOFT_SEP_REALIZATION_
select(){
	PORT_SS::Clear(1<<PIN_SS);
}

template _SPI_SOFT_SEP_TEMPLATE_
void SPI_Base_Software_Separate _SPI_SOFT_SEP_REALIZATION_
release(){
	PORT_SS::Set(1<<PIN_SS);
}

template _SPI_SOFT_SEP_TEMPLATE_
void SPI_Base_Software_Separate _SPI_SOFT_SEP_REALIZATION_
init(){
	PORT_SCK::Clear(1<<PIN_SCK);
	PORT_MOSI::Clear(1<<PIN_MOSI);
	PORT_MISO::Set(1<<PIN_MISO);

	PORT_SCK::DirSet(1<<PIN_SCK);
	PORT_MOSI::DirSet(1<<PIN_MOSI);
	PORT_SS::DirSet(1<<PIN_SS);
	PORT_MISO::DirClear(1<<PIN_MISO);
	release();
}

template _SPI_SOFT_SEP_TEMPLATE_
uint8_t SPI_Base_Software_Separate _SPI_SOFT_SEP_REALIZATION_
fast_shift(uint8_t dataout){
	uint8_t recv=0;
	for(signed char i=7; i>=0; i--){
 		(((dataout&(1<<i))>>i)==1)? PORT_MOSI::Set(1<<PIN_MOSI) : PORT_MOSI::Clear(1<<PIN_MOSI);
		PORT_SCK::Set(1<<PIN_SCK);
		if ((PORT_MISO::Read()&(1<<PIN_MISO))>>PIN_MISO==1) recv|=1<<i;
		PORT_SCK::Clear(1<<PIN_SCK);
	}
	return recv;
}

template _SPI_SOFT_SEP_TEMPLATE_
void SPI_Base_Software_Separate _SPI_SOFT_SEP_REALIZATION_
transmit_sync(uint8_t *dataout, uint8_t len){
	for (uint8_t i = 0; i < len; i++) {
		fast_shift(dataout[i]);
	}
}

template _SPI_SOFT_SEP_TEMPLATE_
void SPI_Base_Software_Separate _SPI_SOFT_SEP_REALIZATION_
transfer_sync(uint8_t *dataout, uint8_t *datain, uint8_t len){
	for (uint8_t i = 0; i < len; i++) {
		datain[i]=fast_shift(dataout[i]);
	}
}


Примеры
Способов использования — столько же сколько и у хардварного варианта.
Пример 5. Software SPI, драйвер.
#include <avr/io.h>

#include "port.h"
#include "spi_software.h"

MAKE_PORT(PORTB, DDRB, PINB, Portb, 'B');
MAKE_PORT(PORTC, DDRC, PINC, Portc, 'C');
MAKE_PORT(PORTD, DDRD, PIND, Portd, 'D');

int main(){
	SPI_Base_Software<Portb, 0, 1, 2, 4> driver1;
	// SPI уже настроен - MOSI на PB0, MISO на PB1, SCK - PB2 и SS - PB4
	driver1.fast_shift(0x30);

	// теперь Separate-версия
	SPI_Base_Software_Separate<Portc, 0, Portb, 5, Portd, 3, Portc, 2> driver2;
	driver2.fast_shift(0x12);

	while (1){}
	return 0;
}


Пример 6. Создание класса-наследника
#include <avr/io.h>

#include "port.h"
#include "spi_software.h"

MAKE_PORT(PORTB, DDRB, PINB, Portb, 'B');

class Device : public SPI_Base_Software<Portb, 0,1,2,3>{
public:
	void init(){
		fast_shift(0x31);
	}
};

int main(){
	// создаем объект Device
	Device monster;
	// SPI уже готов - вызван конструктор от SPI_Base_Software
	// вызываем метод нашего класса - "инициализация"
	monster.init();

	while (1) {}
	return 0;
}


Пример 7. Создание шаблона класса-наследника
#include <avr/io.h>
#include "port.h"
#include "spi_software.h"

MAKE_PORT(PORTB, DDRB, PINB, Portb, 'B');
MAKE_PORT(PORTD, DDRD, PIND, Portd, 'D');

template _SPI_SOFT_TEMPLATE_
class Creature : _SPI_SOFT_PARENT_{
public:
	void init(){
		this->fast_shift(0x31);
	}
};

int main(){
	Creature<Portb, 0, 1, 2, 3> monster1;
	// SPI уже готов - вызван конструктор от SPI_Base_Software
	// вызываем метод нашего класса - "инициализация"
	monster1.init();

	// проделываем то же самое для монстра с SS на ноге PD5
	Creature<Portd, 0, 1, 2, 5> monster2;
	monster2.init();

	while (1) {}
	return 0;
}


Пример 8. Создание шаблона класса, содержащим внутри себя драйвер
#include <avr/io.h>
#include "port.h"
#include "spi_software.h"

MAKE_PORT(PORTB, DDRB, PINB, Portb, 'B');
MAKE_PORT(PORTD, DDRD, PIND, Portd, 'D');

template _SPI_SOFT_TEMPLATE_
class Creature {
private:
	SPI_Base_Software _SPI_SOFT_REALIZATION_ spi;
public:
	void init(){
		spi.fast_shift(0x31);
	}
};

int main(){
	// создаем объект Device
	Creature<Portb, 0, 1, 2, 3> monster1;
	// SPI уже готов
	// вызываем метод нашего класса - "инициализация"
	monster1.init();

	// проделываем то же самое для монстра с SS на ноге PD5
	Creature<Portd, 0, 1, 2, 5> monster2;
	monster2.init();

	while (1) {}
	return 0;
}


Критика принимается. Только пожалуйста, бейте не очень сильно… )


Ссылки из статьи:
Библиотека port.h
Описание работы шаблонов
spi_hardware_defs.h
Обе версии библиотеки с примерами

P.S: Все ссылки сжаты с помощью моего ресурса urler.tk. Данные не собираются, только статистика (количество) визитов, честно! И это никоим образом не реклама… :)

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

RSS свернуть / развернуть

Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.