Двусторонний обмен по 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МГц.