Класс-родитель для устройств на базе 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. Данные не собираются, только статистика (количество) визитов, честно! И это никоим образом не реклама… 🙂


Добавить комментарий

Arduino

Что такое Arduino?
Зачем мне Arduino?
Начало работы с Arduino
Для начинающих ардуинщиков
Радиодетали (точка входа для начинающих ардуинщиков)
Первые шаги с Arduino

Разделы

  1. Преимуществ нет, за исключением читабельности: тип bool обычно имеет размер 1 байт, как и uint8_t. Думаю, компилятор в обоих случаях…

  2. Добрый день! Я недавно начал изучать программирование под STM32 и ваши уроки просто бесценны! Хотел узнать зачем использовать переменную типа…

3D-печать AI Arduino Bluetooth CraftDuino DIY Google IDE iRobot Kinect LEGO OpenCV Open Source Python Raspberry Pi RoboCraft ROS swarm ИК автоматизация андроид балансировать бионика версия видео военный датчик дрон интерфейс камера кибервесна манипулятор машинное обучение наше нейронная сеть подводный пылесос работа распознавание робот робототехника светодиод сервомашинка собака управление ходить шаг за шагом шаговый двигатель шилд юмор

OpenCV
Робототехника
Будущее за бионическими роботами?
Нейронная сеть - введение