Двустороння связь по SPI

Ilya_Sh

✩✩✩✩✩✩✩
7 Апр 2025
18
0
Доброго времени суток. Я пытаюсь организовать двустороннюю связь по SPI между NodeMCU esp-12e (esp8266) в качестве мастера и atmega48 в качестве слейва. Данные от мастера слейв получает без проблем, однако ответ от слейва на мастере записать не получается: в мониторе порта всегда 255.
Код мастера:
#include <SPI.h>
uint8_t value = 250;
void setup() {
  pinMode(D8,OUTPUT);
  digitalWrite(D8,1);
  Serial.begin(9600);
  delay(1000);
  SPI.begin();
  SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE0));
}

void loop() {
  digitalWrite(D8,0);
  SPI.transfer(value);
  delayMicroseconds (20);
  Serial.println(SPI.transfer(0));
  digitalWrite(D8,1);
  delay(1000);
}
Код слейва:
#define PIN_SS   PB2
#define PIN_MOSI PB3
#define PIN_MISO PB4
#define PIN_SCK  PB5

volatile uint8_t received_byte = 0;

void setup() {

    pinMode(PIN_SS, INPUT);
    pinMode(PIN_MOSI, INPUT);
    pinMode(PIN_SCK, INPUT);
    pinMode(PD5, OUTPUT);
    digitalWrite(PD5,0);
    pinMode(PIN_MISO, OUTPUT);
    SPCR  |= _BV(SPE);
    SPCR |= (1 << SPIE);
}

ISR(SPI_STC_vect) {
    received_byte = SPDR;
    if(received_byte==250)digitalWrite(PD5,1);
    else digitalWrite(PD5,0);
    SPDR=128;
}

void loop() {}
Подскажите, пожалуйста, в чём у меня может быть проблема. Не исключаю, что проблема может быть в аппаратной части:
Мастер
Слейв
D8 (HCS)PB2 (SS)
D7 (HMOSI)PB3 (MOSI)
D6 (HMISO)PB4 (MISO)
D5 (HSCLK)PB5 (SCK)
1785951613588.png
1785951680622.png
Порты соеденены напрямую, без какаких-либо подтяжек. Логические уровни согласованы, земля общая. Спасибо
 

Bruzzer

★★★★✩✩✩
23 Май 2020
826
257
#define PIN_SS PB2
#define PIN_MOSI PB3
#define PIN_MISO PB4
#define PIN_SCK PB5
Я не знаю, в этом ли причина, но частая причина ошибок в AVR, это когда используют записи вида "PB2" в стандартных функциях pinMode и digitalWrite digitalRead. Попробуйте вместо PB2 - PB5 написать номера в нумерации Arduino т.е. 10 - 13

Добавлено позже. При использовании стандартных пинов в UNO подобных можно в pinMode и digitalWrite digitalRead просто сразу писать SS MOSI MISO SCK
 
Изменено:

bort707

★★★★★★★
21 Сен 2020
3,505
1,003
Мне кажется тут неправильно. SPI это синхронная шина, которая на каждый переданный бит получает бит в ответ. А вы сначала передаете число, а когда передача закончена - пытаетесь что-то принять, когда в шине уже ничего нет.

Кроме того, будет ли передача двухсторонней или нет, зависит от библиотеки SPI на конкретной плате. В некоторых реализациях SPI.transfer(value); ответ не сохраняет
 

Ilya_Sh

✩✩✩✩✩✩✩
7 Апр 2025
18
0
@bort707, разве аппаратная шина SPI слейва на каждый бит мастера не отвечает битом из SPDR? Фиктивная передача нуля — всего лишь попытка от безысходности, если пытаться прочитать значение, возвращаемое SPI.transfer(value) без дополнительных передач, то там будут те же 255. Вы не могли бы привести пример кода двусторонней реализации аппаратного SPI, если я не прав? А что касается реализаций SPI.transfer(), которые не возвращают ответ слейва, то это проблема в каких-то определённых версиях SPI.h?
 
Изменено:

Ilya_Sh

✩✩✩✩✩✩✩
7 Апр 2025
18
0
@EugeneFrol, насколько я понял, SPCR |= (1 << SPIE) как раз таки включает прерывание по SPI, которое у меня, вроде, нормально работает
 
Изменено:

Sergo_ST

★★★★★★✩
15 Мар 2020
1,306
955
@Ilya_Sh, Попробуйте устанавить SPDR=128; сразу после received_byte = SPDR;. Скорее всего digitalWrite() выполняется дольше 20мкс и возникает коллизия регистра данных.
 
  • Лойс +1
Реакции: EugeneFrol

Sergo_ST

★★★★★★✩
15 Мар 2020
1,306
955
@Ilya_Sh, Кстати да, @Bruzzer прав. Под масками PDn/PBn/PCn скрываются номера бит, значения которых от 0 до 7.
Укажите порядковый номер пина для плат Ардуино нано/уно(если хотите использовать pinMode()), или инициализируйте пины через регистры с использованием масок PDn/PBn/PCn.

В данном случае у вас пин MISO так и остаётся в режиме входа после старта мк(а нужно в режиме выхода), поэтому и не происходит передача, но происходит приём тк все остальные пины по умолчанию и так в режиме входа.
 
28 Окт 2018
3
-1
Проблема в вашем коде скорее всего связана с тем, что вы неправильно организовали обмен данными при двусторонней SPI-связи.

Основные проблемы в коде мастера:

  1. Два вызова SPI.transfer() вместо одного: SPI - полнодуплексный протокол, данные передаются одновременно в обоих направлениях. За один вызов SPI.transfer() вы одновременно отправляете данные и получаете ответ.
  2. Неправильная последовательность операций: Вы сначала отправляете данные, делаете паузу, а потом отправляете 0 для получения ответа. Но ответ слейва должен быть получен во время отправки данных.
Исправленный код мастера:
C++:
#include <SPI.h>

uint8_t value = 250;
uint8_t received_byte = 0;

void setup() {
pinMode(D8, OUTPUT);
digitalWrite(D8, HIGH); // SS неактивен
Serial.begin(9600);
delay(1000);
SPI.begin();
SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE0));
}

void loop() {
digitalWrite(D8, LOW); // Выбор слейва

// За один вызов отправляем данные и получаем ответ
received_byte = SPI.transfer(value); // Отправляем value, слейв должен вернуть 128

Serial.println(received_byte); // Выводим полученные данные

digitalWrite(D8, HIGH); // Отключаем слейв
delay(1000);
}
Исправленный код слейва:
C++:
#define PIN_SS PB2
#define PIN_MOSI PB3
#define PIN_MISO PB4
#define PIN_SCK PB5

volatile uint8_t received_byte = 0;
volatile uint8_t response_byte = 128; // Будет отправлено мастеру

void setup() {
// Настройка пинов
pinMode(PIN_SS, INPUT);
pinMode(PIN_MOSI, INPUT);
pinMode(PIN_SCK, INPUT);
pinMode(PD5, OUTPUT);
digitalWrite(PD5, LOW);
pinMode(PIN_MISO, OUTPUT);

// Настройка SPI
SPCR = (1 << SPE) | (1 << SPIE); // Включить SPI и прерывания
}

ISR(SPI_STC_vect) {
received_byte = SPDR; // Читаем полученные данные

// Обрабатываем полученные данные
if (received_byte == 250) {
digitalWrite(PD5, HIGH);
} else {
digitalWrite(PD5, LOW);
}

// Загружаем данные для отправки мастеру
SPDR = response_byte;
}

void loop() {
// Можно менять response_byte в основном цикле при необходимости
}
Важные моменты:
Скорость SPI

  • ESP8266 работает на 80MHz, ATMega48 - на 8-16MHz
  • Ваша скорость 1MHz должна быть нормальной, но проверьте частоту ATMega48
После внесения этих изменений вы должны увидеть в мониторе порта значение 128 вместо 255. Если все еще получаете 255, проверьте соединения и настройки тактовой частоты обоих микроконтроллеров.
 
  • Аррр! -2
Реакции: Bruzzer

bort707

★★★★★★★
21 Сен 2020
3,505
1,003
@Bruzzer,
Стм32
Довольно часто мы используем SPI с устройствами, которые только принимают данные и никогда не шлют ничего назад - например ТФТ экраны. Для таких случаев в библиотеке СПИ может быть предусмотрен отдельный трансфер-онли метод, который, конечно же, принимает байты из шины (иначе никак), но никуда их не сохраняет. В частности, в пакете ардуино для Стм32 такое есть
 

Bruzzer

★★★★✩✩✩
23 Май 2020
826
257
В частности, в пакете ардуино для Стм32 такое есть
Я не смог найти.
Если под
>В некоторых реализациях SPI.transfer(value); ответ не сохраняет
Вы имели в виду "ответ не возвращает", то непонятно, зачем делать специально функцию ничего не возвращающую, если можно просто проигнорировать возвращаемое значение. Если вы имели в виду именно "ответ не сохраняет", то это "обычное поведение SPI.h" на отправку одного байта, не сохранять ответ, а возвращать его как результат.