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

Ilya_Sh

✩✩✩✩✩✩✩
7 Апр 2025
24
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
829
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,514
1,004
Мне кажется тут неправильно. SPI это синхронная шина, которая на каждый переданный бит получает бит в ответ. А вы сначала передаете число, а когда передача закончена - пытаетесь что-то принять, когда в шине уже ничего нет.

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

EugeneFrol

★✩✩✩✩✩✩
17 Апр 2024
101
12
51
ИИ сказал, что надо разрешить прерывания в Slave - т.е. sei(); в setup()
 

Ilya_Sh

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

Ilya_Sh

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

Sergo_ST

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

Sergo_ST

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

В данном случае у вас пин MISO так и остаётся в режиме входа после старта мк(а нужно в режиме выхода), поэтому и не происходит передача, но происходит приём тк все остальные пины по умолчанию и так в режиме входа.
 
  • Лойс +1
Реакции: Ilya_Sh

bort707

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

Bruzzer

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

SpyHUNTERrzn

✩✩✩✩✩✩✩
24 Июл 2025
6
0
@Sergo_ST, извиняюсь за офтоп, но хотелось бы спросить, а вообще возможно программировать номера пинов в среде ArduinoIDE именно в виде PDn/PBn/PCn? Работаю в основном с голыми камнями, и было бы удобнее в виде PDn/PBn/PCn нежели искать распиновку и сравнивать.
 

bort707

★★★★★★★
21 Сен 2020
3,514
1,004
вообще возможно программировать номера пинов в среде ArduinoIDE именно в виде PDn/PBn/PCn? Работаю в основном с голыми камнями, и было бы удобнее в виде PDn/PBn/PCn
Это зависит от того пакета Ардуино, который вы используете для работы с конкретным камнем. Через что вы работаете с Атмегой48 - через стандартный АВР пакет или через что-то типа МиниКоре?

В МиниКоре вместо PDn/PBn/PCn используйте дефайны вида PIN_PBn / PIN_PCn - это будет работать как вы хотите.
 
Изменено:

SpyHUNTERrzn

✩✩✩✩✩✩✩
24 Июл 2025
6
0
Это зависит от того пакета Ардуино, который вы используете для работы с конкретным камнем. Через что вы работаете с Атмегой48 - через стандартный АВР пакет или через что-то типа МиниКоре?

В МиниКоре вместо PDn/PBn/PCn используйте дефайны вида PIN_PBn / PIN_PCn - это будет работать как вы хотите.
Я в основном работаю с 328й мегой, использую в основном GyverCore, реже стандартный пакет.
 

bort707

★★★★★★★
21 Сен 2020
3,514
1,004
Про ГайверКоре не скажу, не работаю с ним.
 

Ilya_Sh

✩✩✩✩✩✩✩
7 Апр 2025
24
0
@Bruzzer, спасибо, вы меня на решение проблемы подтолкнули: нужно было порты через регистры инициализировать.
Рабочий код слейва:
volatile uint8_t received_byte = 0;

void setup() {
    DDRB = (1 << PB4);
    DDRD |= (1 << PD5);
    digitalWrite(PD5,0);
    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=100;
}

void loop() {}
 

bort707

★★★★★★★
21 Сен 2020
3,514
1,004
нужно было порты через регистры инициализировать.
неверный вывод.
Регистры тут никаким боком, причина именно в неправильном обозначении пинов.
Просто в случае
C++:
DDRB = (1 << PB4);
тут сразу две ошибки, которые уравновешивают одна другую.

А вот ниже в коде как было неправильно, так и осталось:
if(received_byte==250)digitalWrite(PD5,1); else digitalWrite(PD5,0);
Прежде чем писать дальше, разберитесь, что означают записи типа PD5 и чем они отличаются от PIN_PD5.
 

Ilya_Sh

✩✩✩✩✩✩✩
7 Апр 2025
24
0
@bort707, я, наверное, зря не уточнил: у меня установлено ядро MiniCore. Да, а страничке github указано писать макросы с припиской PIN_ (т.е. как вы говорите PIN_PD5), но digitalWrite(PD5,...) работает отлично. Вероятно, макрос PD5 тоже определён, но замечание интересное.
тут сразу две ошибки, которые уравновешивают одна другую.
В чём же ошибка? Порт PB4 (MISO) становится выходом, все остальные порты PB — входами.
 

bort707

★★★★★★★
21 Сен 2020
3,514
1,004
Да, а страничке github указано писать макросы с припиской PIN_ (т.е. как вы говорите PIN_PD5), но digitalWrite(PD5,...) работает отлично. Вероятно, макрос PD5 тоже определён,
Вероятно? То есть вы не уверены?
Вот именно чтобы не гадать на кофейной гуще, и я советую вам окончательно разобраться в том, как работают эти макросы.
Пока же могу только сказать. что в обоих этих случаях , как в первом:
digitalWrite(PD5,...)
так и во втором
DDRB = (1 << PB4);
то что код работает - это всего лишь случайность
 

Ilya_Sh

✩✩✩✩✩✩✩
7 Апр 2025
24
0
@bort707, в файле iom8.h в avr-gсc нашёл строки:
/* PORTB */:
#define PB7    7
#define PB6    6
#define PB5    5
#define PB4    4
#define PB3    3
#define PB2    2
#define PB1    1
#define PB0    0
/* PORTD */:
#define PD7     7
#define PD6     6
#define PD5     5
#define PD4     4
#define PD3     3
#define PD2     2
#define PD1     1
#define PD0     0
Теперь я соглашусь, что digitalWrite(PD5,...) должен работать не так, как я этого ожидаю, если нигде макрос PD5 дополнительно не переназначается. Но что с DDRB = (1 << PB4) не так?
Дополнено: из напрямую относящегося к теме в файле pins_arduino.h внутри MiniCore нашёл это:
PCINT:
#define PIN_PD0 0
#define PIN_PD1 1
#define PIN_PD2 2
#define PIN_PD3 3
#define PIN_PD4 4
#define PIN_PD5 5
#define PIN_PD6 6
#define PIN_PD7 7
#define PIN_PB0 8
#define PIN_PB1 9
#define PIN_PB2 10
#define PIN_PB3 11
#define PIN_PB4 12
#define PIN_PB5 13
#define PIN_PC0 14 // A0
#define PIN_PC1 15 // A1
#define PIN_PC2 16 // A2
#define PIN_PC3 17 // A3
#define PIN_PC4 18 // A4
#define PIN_PC5 19 // A5
#define PIN_PB6 20 // XTAL1
#define PIN_PB7 21 // XTAL2
#define PIN_PC6 22 // RESET
 
Изменено: