Двусторонний обмен по SPI

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Пришёл ребёнок с проблемой: не могу организовать двусторонний обмен по SPI. Мол в апноте AVR151 есть пример передачи от мастера к слейву и приёма там, а вот чтобы слейв мог вернуть информацию мастеру – никак».

Написал для него пример взаимодействия мастера и слейва с передачей данных туда и обратно. Мастер передаёт слейву строки, а тот возвращает их длины. При этом слейв печатает принятую строку, а мастер печатает переданную строку вместе, с полученной от слейва её длиной.

Пример использует штатную, «из IDE’шной коробки» библиотеку SPI как на стороне мастера, так и на стороне слейва. Проверялось только на Uno/Nano.

Публикую просто на случай, если кому-то надо.

Содинение: у двух ардуин содинить пины: Gnd, 10, 11, 12 и 13.

Скетч мастера

//
//	Мастер отсылает строки.
//	В ответ принимает длину переданной строки
//	Текущая длина возвращается после передачи каждого
//	байта. Т.о. после передачи всей строки, имеем её длину.
// Печатает переданную строку и принятую от слейва её длину
//
#include <SPI.h>
#include <Printing.h>

void setup(void) {
	Serial.begin(57600);
	Serial.println("I am master!");
	pinMode(SS, OUTPUT);
	digitalWrite(SS, HIGH);
	SPI.begin();
	delay(50); // Дадим слейву время проинициализироваться 
}

void loop(void) {
	static const int totalStrings = 4;
	static const char * strs[totalStrings] = {
		"123456",
		"1234567890123",
		"12",
		"12345678901234567890123456789012345678901234567890"
	};
	static int stringCounter = 0;
	//
	const char * ptr = strs[stringCounter];
	char sentByte; // только что отосланный байт
	//
	// Отправляем строку
	//
	SPI.beginTransaction(SPISettings());
	digitalWrite(SS, LOW);
	uint8_t strLen;
	do {
		strLen = SPI.transfer(sentByte = * ptr++);
		delay(1); // пусть там слейв свои дела сделает
	} while (sentByte);
	digitalWrite(SS, HIGH);
	SPI.endTransaction();
	//
	//	Печатаем отправленную строку и
	//	её длину, полученную от слейва
	//
	Serial << "\"" << strs[stringCounter] << "\" (" << strLen << ")\r\n";
	//
	//	Переход к следующей строке
	//
	stringCounter = (stringCounter + 1) % totalStrings;
	delay(100);	// Перекур
}

Скетч  слейва

//
//	Слейв, принимает строки от мастера
//	На каждый принятый байт отвечает числом - 
//	длиной принятой на данный момент строки
// Когда строка прията полностью (ограничена '\0'),\
//	строка печатается для визуального контроля.
//
#include <SPI.h>
#include <Printing.h>

void setup(void) {
	Serial.begin(57600);
	Serial.println("I am slave!");
	//
	//	Инициализируем SPI при SS в режиме INPUT (получаем slave режим)
	//	и вручную выставляем правильные направления пинов
	//
	SPI.begin();
	SPI.beginTransaction(SPISettings());
	pinMode(MISO, OUTPUT);
	pinMode(MOSI, INPUT);
	pinMode(SCK, INPUT);
	pinMode(SS, INPUT);
	SPI.attachInterrupt();	// Включаем прерывания
}

//
//	Буфер, указатель и флаг готовности строки для печати
//	Контроля переполнения буфера нет, чтобы не усложнять пример
//
static char receivedBuffer[128];
static volatile char * bufPtr = receivedBuffer;
static bool stringIsReady = false;

ISR(SPI_STC_vect) {
	//
	// принятый байт кладём в буфер, в br и продвигаем указатель буфера
	const char br = *bufPtr++ = SPDR;
	//
	// длина принятой на данный момент строки уйдёт 
	// мастеру при передаче следующего байта 
	SPDR = bufPtr - receivedBuffer;  
	//
	// Если приняли 0, сигнализируем, что строка принята
	// и восстанавливаем буфер на начало
	if (br == '\0') {
		bufPtr = receivedBuffer;
		stringIsReady = true;
	}
}

void loop(void) {
	//
	//	Если принята строка, то печатем её
	if (stringIsReady) {
		stringIsReady = false;
		// в начале печтаем : - чтобы было видно,
		//	если приняли пустую строку
		Serial << ':' << receivedBuffer << "\r\n";
	}
}

 

mykaida
mykaida аватар
Offline
Зарегистрирован: 12.07.2018

Спасибо, Евгений! Познавательно.

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

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

А что такое "полудуплексный режим" в данном контексте?

mykaida
mykaida аватар
Offline
Зарегистрирован: 12.07.2018

ЕвгенийП пишет:

А что такое "полудуплексный режим" в данном контексте?

SPI только по MOSI туда и обратно

trembo
trembo аватар
Offline
Зарегистрирован: 08.04.2011

Подлежащие передаче данные ведущее и ведомое устройства помещают в сдвиговые регистры.
После этого ведущее устройство начинает генерировать импульсы синхронизации на линии SCLK,
что приводит к взаимному обмену данными.

https://ru.wikipedia.org/wiki/Serial_Peripheral_Interface

 

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Ну, аппаратно это не поддерживается. Может быть можно на тиньке в USI, не знаю, а здесь, вроде, нет.

Valera19701
Valera19701 аватар
Offline
Зарегистрирован: 18.10.2015

ЕвгенийП пишет:

Ну, аппаратно это не поддерживается. Может быть можно на тиньке в USI, не знаю, а здесь, вроде, нет.

это как, а например посмотрите библиотеку nrf24l01, там все делается регистрами

Алексей.
Алексей. аватар
Offline
Зарегистрирован: 02.02.2018

На esp32 работающей как слейв обменивался фреймами фиксированной длины 1.5K с другим mcu, на 12MHz и провода 20см.
Каждые две с половиной миллисекунды выполнял транзакцию, мастер и слейв укладывали во фрейм свои порции данных.
Начиналось всё хорошо, данные в обеих направлениях путешествовали успешно. Но неожиданно на слейве периодически на приеме начал ловить мусор а не данные от мастера.
При запуске таска, что с spi работал, не явно не указывал на каком ядре его запускать, иногда он запускался на нулевом ядре, на том же ядре работает и wifi драйвер, он то мне и гадил.
После запуска таска на первом ядре проблемы решились.

mykaida
mykaida аватар
Offline
Зарегистрирован: 12.07.2018

ЕвгенийП пишет:

Ну, аппаратно это не поддерживается. Может быть можно на тиньке в USI, не знаю, а здесь, вроде, нет.

Извините, Евгений. На STM32 такой режим есть, но чего-то с ним мутно (короче не запустился) или я где-то ошибаюсь. Думал на дуньке тоже есть и хотел посмотреть на мастер-класс :)

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

mykaida пишет:
На STM32 такой режим есть
Ну, мало ли где и что есть. Здесь нету.

Ворота
Ворота аватар
Offline
Зарегистрирован: 10.01.2016

Меня всегда доставляли вот такие конструкции: сначала в строке №30 мастера «const char * ptr», а потом, в строке №39 «ptr++».

Когда-то напрягся, разобрался и запомнил, так что вроде и знаю что там как и почему, но глаз всегда спотыкается :(

DIYMan
DIYMan аватар
Offline
Зарегистрирован: 23.11.2015

Ворота пишет:

Меня всегда доставляли вот такие конструкции: сначала в строке №30 мастера «const char * ptr», а потом, в строке №39 «ptr++».

Ну дык это ж указатель на константный массив символов, а не константный указатель на массив символов, или, не дай бог - константный указатель на константный массив символов :))))

DIYMan
DIYMan аватар
Offline
Зарегистрирован: 23.11.2015

Евгений, можно вопрос? Строка #40 - зачем такая огромная пауза? Скорости обмена получаются уж больно печальными, пмсм. Я так понял - код чисто для демонстрации подхода, правильно?

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

DIYMan пишет:

Строка #40 - зачем такая огромная пауза? Скорости обмена получаются уж больно печальными, пмсм. Я так понял - код чисто для демонстрации подхода, правильно?

Да, чисто для демонстрации, там бы пары микросекунд хватило с огромным запасом, даже с учётом того, что слейв у ребенка работает на 8МГц, а мастер - на 16МГц.