Двусторонний обмен по SPI
- Войдите на сайт для отправки комментариев
Пришёл ребёнок с проблемой: не могу организовать двусторонний обмен по 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 volatile 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";
}
}
Спасибо, Евгений! Познавательно.
А как то же организовать в полудуплексном режиме - не подскажете?
А что такое "полудуплексный режим" в данном контексте?
А что такое "полудуплексный режим" в данном контексте?
SPI только по MOSI туда и обратно
Подлежащие передаче данные ведущее и ведомое устройства помещают в сдвиговые регистры.
После этого ведущее устройство начинает генерировать импульсы синхронизации на линии SCLK,
что приводит к взаимному обмену данными.
https://ru.wikipedia.org/wiki/Serial_Peripheral_Interface
Ну, аппаратно это не поддерживается. Может быть можно на тиньке в USI, не знаю, а здесь, вроде, нет.
Ну, аппаратно это не поддерживается. Может быть можно на тиньке в USI, не знаю, а здесь, вроде, нет.
это как, а например посмотрите библиотеку nrf24l01, там все делается регистрами
На esp32 работающей как слейв обменивался фреймами фиксированной длины 1.5K с другим mcu, на 12MHz и провода 20см.
Каждые две с половиной миллисекунды выполнял транзакцию, мастер и слейв укладывали во фрейм свои порции данных.
Начиналось всё хорошо, данные в обеих направлениях путешествовали успешно. Но неожиданно на слейве периодически на приеме начал ловить мусор а не данные от мастера.
При запуске таска, что с spi работал, не явно не указывал на каком ядре его запускать, иногда он запускался на нулевом ядре, на том же ядре работает и wifi драйвер, он то мне и гадил.
После запуска таска на первом ядре проблемы решились.
Ну, аппаратно это не поддерживается. Может быть можно на тиньке в USI, не знаю, а здесь, вроде, нет.
Извините, Евгений. На STM32 такой режим есть, но чего-то с ним мутно (короче не запустился) или я где-то ошибаюсь. Думал на дуньке тоже есть и хотел посмотреть на мастер-класс :)
Меня всегда доставляли вот такие конструкции: сначала в строке №30 мастера «const char * ptr», а потом, в строке №39 «ptr++».
Когда-то напрягся, разобрался и запомнил, так что вроде и знаю что там как и почему, но глаз всегда спотыкается :(
Меня всегда доставляли вот такие конструкции: сначала в строке №30 мастера «const char * ptr», а потом, в строке №39 «ptr++».
Ну дык это ж указатель на константный массив символов, а не константный указатель на массив символов, или, не дай бог - константный указатель на константный массив символов :))))
Евгений, можно вопрос? Строка #40 - зачем такая огромная пауза? Скорости обмена получаются уж больно печальными, пмсм. Я так понял - код чисто для демонстрации подхода, правильно?
Строка #40 - зачем такая огромная пауза? Скорости обмена получаются уж больно печальными, пмсм. Я так понял - код чисто для демонстрации подхода, правильно?
Да, чисто для демонстрации, там бы пары микросекунд хватило с огромным запасом, даже с учётом того, что слейв у ребенка работает на 8МГц, а мастер - на 16МГц.