Подключение нескольких устройств по SPI

Kulibin_

✩✩✩✩✩✩✩
13 Мар 2023
12
0
Всем доброго времени суток! Возник вопрос - использую на одной плате Nano два модуля, подключаемых по SPI, а именно TFT-дисплей и радиомодуль nRF24L01, и не могу понять, нужно ли как-то их разделять программно? Имею ввиду что-то вроде такого - получил данные с радио, отключил радио, включил дисплей, вывел на дисплей, отключил дисплей, включил радио, и так по кругу. Или взаимодействие с модулями будет происходить автоматически, как при подключении нескольких I2C? Знаю, что оба SCLK подключаются к 13 пину, оба MOSI к 11, CS разные, но нужны ли какие-то пометки на этот счёт в коде - в сети вообще не нашёл

Код:
C++:
#include <SPI.h>
#include "nRF24L01.h"
#include "RF24.h"
#include <Adafruit_GFX.h>
#include <Adafruit_ST7735.h>

RF24 radio(6, 7);

#define TFT_CS     10
#define TFT_RST    8   
#define TFT_DC     9     
#define TFT_SCLK   13   
#define TFT_MOSI   11
Adafruit_ST7735 tft = Adafruit_ST7735(TFT_CS,  TFT_DC, TFT_RST);

byte recieved_data[6];

byte address[][6] = {"1Node", "2Node", "3Node", "4Node", "5Node", "6Node"};

void setup(void) {
  Serial.begin(9600);

  radio.begin();
  radio.setAutoAck(1);
  radio.setRetries(0, 15);
  radio.enableAckPayload();
  radio.setPayloadSize(32);
  radio.openReadingPipe(1, address[0]);
  radio.setChannel(0x60);
  radio.setPALevel (RF24_PA_MAX);
  radio.setDataRate (RF24_250KBPS);
  radio.powerUp();
  radio.startListening();

  tft.initR(INITR_BLACKTAB);
  tft.fillScreen(ST7735_BLACK);
  tft.setRotation(3);
  tft.setTextSize(1);
  tft.setTextColor(ST7735_WHITE);
  tft.setCursor(5, 10);
  tft.println("Данные:");

}

void loop(void) {
  byte pipeNo;
  while ( radio.available(&pipeNo)) {
  radio.read(&recieved_data, sizeof(recieved_data));

 
  tft.fillRect(95, 0 , 50, 128, ST7735_BLACK);
    
  tft.setCursor(97, 10);
  tft.println(recieved_data[0]);
  Serial.println(recieved_data[0]);
  }
}
 

Геннадий П

★★★★★★✩
14 Апр 2021
1,975
634
45
Имею ввиду что-то вроде такого - получил данные с радио, отключил радио, включил дисплей, вывел на дисплей, отключил дисплей, включил радио, и так по кругу.
Именно так. Вся шина подключается параллельно, нужное устройство для работы активирается сигналом CS/SS.

1715182674004.png
 

Kulibin_

✩✩✩✩✩✩✩
13 Мар 2023
12
0
А как именно это реализовать? В плане кода
 

Геннадий П

★★★★★★✩
14 Апр 2021
1,975
634
45
@Kulibin_, Что именно?
Включаете SS0, работаете по SPI с первым устройством, выключаете SS0.
Включаете SS1, работаете по SPI с вторым устройством, выключаете SS1.
... и т.д.
 

poty

★★★★★★✩
19 Фев 2020
3,261
948
Все выходы на SS на контроллере поднимаете в 1. На выходе, подключённом к устройству, с которым нужно провести обмен, выставляете 0.
 

Kulibin_

✩✩✩✩✩✩✩
13 Мар 2023
12
0
Вот так?

C++:
#define Radio_CS     7

#define TFT_CS         10


void setup(void) {

  Serial.begin(9600);


  pinMode(Radio_CS, OUTPUT);

  pinMode(TFT_CS, OUTPUT);

  digitalWrite(Radio_CS, HIGH); // Выбор первого датчика

  digitalWrite(TFT_CS, HIGH);


  digitalWrite(Radio_CS, LOW);

  radio.begin();

  radio.setAutoAck(1);        // режим подтверждения приёма, 1 вкл 0 выкл

  radio.setRetries(0, 15);    // (время между попыткой достучаться, число попыток)

  radio.enableAckPayload();   // разрешить отсылку данных в ответ на входящий сигнал

  radio.setPayloadSize(32);   // размер пакета, в байтах

  radio.openReadingPipe(1, address[0]);     // хотим слушать трубу 0

  radio.setChannel(0x60);  // выбираем канал (в котором нет шумов!)

  radio.setPALevel (RF24_PA_MAX);   // уровень мощности передатчика. На выбор RF24_PA_MIN, RF24_PA_LOW, RF24_PA_HIGH, RF24_PA_MAX

  radio.setDataRate (RF24_250KBPS); // скорость обмена. На выбор RF24_2MBPS, RF24_1MBPS, RF24_250KBPS

  radio.powerUp();

  radio.startListening();

  digitalWrite(Radio_CS, HIGH);


  digitalWrite(TFT_CS, LOW);

  tft.initR(INITR_BLACKTAB);   //инициализация

  tft.fillScreen(ST7735_BLACK);

  tft.setRotation(3);//поворот экрана от 0 до 3

  tft.setTextSize(1);

  tft.setTextColor(ST7735_WHITE);

  tft.setCursor(5, 10);

  tft.println("Текст:");

  digitalWrite(TFT_CS, HIGH);

}
 

poty

★★★★★★✩
19 Фев 2020
3,261
948
Логика переключения правильная. Есть потенциальная проблема, что библиотеки будут мешать друг другу, поскольку ориентируются на сохранение состояния между включением управляемых ими устройств.
 

Эдуард Анисимов

★★★★★★✩
23 Сен 2019
2,412
978
58
Марий-Эл
@poty, Это решается не на уровне библиотек, а на уровне обращения к ним.
Так же это зависит от самой библиотеки. Предусмотрено там управление выводом CS или нет.
Но решается и в том и другом случае очень просто.
 

poty

★★★★★★✩
19 Фев 2020
3,261
948

@Эдуард Анисимов, это примечание ничем не отличается от того, что я написал. И решить иногда не получается. По факту приходится переписывать библиотеки на единую очередь. Не всегда, согласен, но часто.
 

Эдуард Анисимов

★★★★★★✩
23 Сен 2019
2,412
978
58
Марий-Эл
@poty, Не нужна никакая очередь в данном случае. Одновременный доступ к дисплею и nRF не возможен. Ни через прерывания, ни через DMA.
Поэтому доступ или к одному, или к другому в разные моменты времени. И, в этом случае, без разницы, сама библиотека будет дёргать CS или программист.
Обратился к nRF, сохранил данные. Обработал, вывел на дисплей.
 

poty

★★★★★★✩
19 Фев 2020
3,261
948
@Эдуард Анисимов, хорошо если есть возможность завершить действие. Только не всегда библиотека написана под такие атомарные, полностью законченные операции. Как пример, инициализация "радио" в коде выше заканчивается на:
radio.startListening();
т.е., "радио часть" ожидает, что порт будет в режиме приёма. В то же время
tft.println("Текст:");
предполагает, что порт, переключенный на "экран" уже находится в режиме передачи. Библиотеки "не знают", что в какие-то интервалы состояние портов изменяется и, вероятно, работать не будут. С другой стороны, я не знаю логики обработки потоков данных и есть вероятность, что пока осуществляется вывод на экран, потеряются принимаемые данные из радио...
Короче, я бы всё же завершённость операций перенёс в очередь, обеспечивающую асинхронную обработку и завершение операций без влияния на сами процессы.
 

Эдуард Анисимов

★★★★★★✩
23 Сен 2019
2,412
978
58
Марий-Эл
Это команда, которая просто включает приёмник. именно включает, а не ждёт чего либо, что на входе появится.

И пока она не завершится, другая строка не стартанёт.
Не нужно всё, что написано воспринимать так буквально.
А ещё лучше посмотреть эту функцию, что она делает. Что бы не писать такие комментарии.
Это библиотека для ардуино.
Здесь нет потоков и очередей.
 

poty

★★★★★★✩
19 Фев 2020
3,261
948
@Эдуард Анисимов, потоков и очередей нет потому, что их никто не написал (пока). Приём после включения приёмника кто осуществляет (или иначе говоря - куда передаются принятые "радио" байты)? Если эта строка не настраивает порт, то строка завершится моментально.
 

Геннадий П

★★★★★★✩
14 Апр 2021
1,975
634
45
Приём после включения приёмника кто осуществляет (или иначе говоря - куда передаются принятые "радио" байты)?
У NRF24 есть буфер. На сколько помнится, при появлении (или заполнении) буфера поднимается на выходе сигнал прерывания, который сигнализирует контроллеру что "уже пора".
 

Эдуард Анисимов

★★★★★★✩
23 Сен 2019
2,412
978
58
Марий-Эл
@poty, На TFT дисплей зае.. потоки писать. Там только работа напрямую, а поток можно использовать только в двух случаях. При выводе картинки и заполнении экрана одним цветом.
 

poty

★★★★★★✩
19 Фев 2020
3,261
948
@Геннадий П, и что с этим сигналом делать, если в этот момент производится вывод на дисплей, например? И как будет вести себя NRF24 (вернее, его библиотека), когда вернётся управление с перенастроенным SPI на передачу? Здесь ранее уже была описана такая проблема.
@Эдуард Анисимов, вообще нерелевантно теме обсуждения.
 

Эдуард Анисимов

★★★★★★✩
23 Сен 2019
2,412
978
58
Марий-Эл
и что с этим сигналом делать, если в этот момент производится вывод на дисплей, например?
Использовать прерывание от модуля.
Для чтения с него, если шло общение с дисплеем, убедиться что это так, дождаться окончания, отключить выборку дисплея, пообщаться с nRF, включить выборку дисплея, выйти из прерывания.
Если возразите, что может передаваться массив в дисплей, он тут же прекратится, и восстановится передача только по выходу из прерывания.
 

Геннадий П

★★★★★★✩
14 Апр 2021
1,975
634
45
и что с этим сигналом делать, если в этот момент производится вывод на дисплей, например?
Смотря что важней.
Если потеря данных не критична - выводить изображение дальше и терять принятые данные.
Если потеря данных критична, то прервать вывод данных на прочие SPI устройства и начать чтение буфера NRF24. Если производился вывод на дисплей, то запомнить место до которого произведена запись и продолжить с того же места.
 

poty

★★★★★★✩
19 Фев 2020
3,261
948

@Эдуард Анисимов,
@Геннадий П, интересно, а как всё это будет делаться, если состояние шины неизвестно? То есть произошло прерывание, что сейчас происходит с SPI? Свободно, занято? На сколько занято? Какая библиотека и на каком уровне сейчас это обрабатывает? Как прекратить обработку, чтобы перейти к приёму (напоминаю, что одна библиотека ничего не знает о другой и не обязана предоставлять статус своей работы), а потом корректно вернутьсяк передаче? А если библиотека использует прерывания, которые запрещены в текущем прерывании или зависит от функций времени, которые тоже на прерываниях завязаны? И много всяких "если".
Библиотеки, не имея представления о том, что происходит между вызовами их методов, часто просто не могут работать правильно. Для этого и нужен элемент асинхронности: очередь с приоритетами, библиотека управления шиной с API для управления ей...
 

Геннадий П

★★★★★★✩
14 Апр 2021
1,975
634
45
интересно, а как всё это будет делаться, если состояние шины неизвестно? То есть произошло прерывание, что сейчас происходит с SPI? Свободно, занято? На сколько занято? Какая библиотека и на каком уровне сейчас это обрабатывает?
Сделать флаг, который поднимать при начале работы с шиной.

Как прекратить обработку, чтобы перейти к приёму (напоминаю, что одна библиотека ничего не знает о другой и не обязана предоставлять статус своей работы), а потом корректно вернутьсяк передаче? А если библиотека использует прерывания, которые запрещены в текущем прерывании или зависит от функций времени, которые тоже на прерываниях завязаны?
Сделать флаг, который проверять при передачи данных. Если брать из других языков программирования, то первое что приходит в голову это наподобие CancellationToken в C#.
 

poty

★★★★★★✩
19 Фев 2020
3,261
948
Сделать флаг, который поднимать при начале работы с шиной.
напомню, что работа с шиной ведётся внутри библиотеки, никакие внешние флаги ей неизвестны, и не выставляются. Поэтому и проверять/сообщать/реагировать библиотека ничего и ни на что не обязана. Всякие токены и СИ шарпы - в микропроцессорах используются редко, память едят не по детски, найти библиотеку с таким функционалом практически невозможно, тем более - несколько библиотек для разных устройств.
 

Геннадий П

★★★★★★✩
14 Апр 2021
1,975
634
45
работа с шиной ведётся внутри библиотеки, никакие внешние флаги ей неизвестны, и не выставляются
Модифицировать библиотеку под свои потребности. Либо мучаться и использовать стандартные библиотеки.
 

Эдуард Анисимов

★★★★★★✩
23 Сен 2019
2,412
978
58
Марий-Эл
@poty, Ты похоже программист в операционных системах, а не железячник, раз этого не понимаешь.
как всё это будет делаться, если состояние шины неизвестно?
В микроконтроллерах состояние шины можно узнать всегда!!!!
То есть произошло прерывание, что сейчас происходит с SPI? Свободно, занято?
Читаешь флаги контроллера SPI и узнаёшь его состояние.
Какая библиотека и на каком уровне сейчас это обрабатывает?
Абсолютно фиолетово, если ты знаешь что ты там навключал, а не лазишь в чёрном ящике.
Как прекратить обработку, чтобы перейти к приёму (напоминаю, что одна библиотека ничего не знает о другой и не обязана предоставлять статус своей работы), а потом корректно вернутьсяк передаче?
Это решается на уровне железа, а не библиотек.
А если библиотека использует прерывания, которые запрещены в текущем прерывании или зависит от функций времени, которые тоже на прерываниях завязаны? И много всяких "если".
Если возникают такие если, значит программист говно и ничего не знает о приоритете прерываний.
Библиотеки, не имея представления о том, что происходит между вызовами их методов, часто просто не могут работать правильно. Для этого и нужен элемент асинхронности: очередь с приоритетами, библиотека управления шиной с API для управления ей...
Нет такого в микроконтроллерах.

И я уже писал как это делается.
Повторю.

TFT Дисплей работающий по SPI не может так вот просто работать по прерываниям. Это слишком сложно написать. Поэтому никто этим не заморачивается. Поэтому вывод на TFT дисплей функция блокирующая.
Если непонятно, почему так. Я пару лет назад писал об этом в Дискорд канале. Если ты это прочитал бы, вопросов у тебя не возникло.

Но nRF имеет выход, который можно использовать для прерывания в разных случаях. Какие прерывания, зависит от одного регистра в чипе. Так что смотрите даташит.
Далее. Условимся, что мы настроили прерывание по приёму пакета.
У nRF, как говорил @Геннадий П, есть свой буфер. Она после приёма пакета проверяет его на целостность, и только после этого поднимает прерывание.

Теперь дисплей у нас выводит инфу, как он выводит? Пихает в SPI байт за байтом, не используя прерывания. Так как говорилось раньше, это очень сложно написать.
И тут как чёрт из коробочки - прерывание от nRF. Мы заходим в него и ничего не делаем. Просто смотрим, что происходит, а железо продолжает работать. Ведь по SPI передавался байт.

И мы это можем проверить. Если программист, а НЕ БИБЛИОТЕКА !!!!!!!!!!!!!!!!!!!!!!! знает что как подключено.

Мы знаем (а не какое то там выдуманное API, ну нет его в МК, если операционку не поставить, что очень сомнительно), что на SPI порт подключено два устройства nRF и TFT. И только nRF может вызвать прерывание.

И мы знаем на какой вывод подключен CS дисплея. Мы можем его состояние проверить. Если он активен, значит идёт процесс обмена с дисплеем.
Мы ждём окончания передачи контроллером SPI. Для этого у него есть флаги. Как только SPI освободился, мы отключаем CS дисплея, Включаем CS nRF и начинаем сеанс.

После окончания сеанса, мы отключаем CS nRF и восстанавливаем состояние CS дисплея, выходим из прерывания и драйвер дисплея начинает передачу снова, с того места где его прервали, ничего не заметив. А дисплею пофигу когда придёт следующий байт, через микросекунду или через час. Он просто будет ждать или продолжения или отмены.

Если после входа мы обнаружим что CS дисплея не активно, значит обмена с ним нет и можно не обращать внимание чем занимается SPI до тех под, пока какой нибудь придурок не повесит на него ещё чего нибудь. Тогда алгоритм немного усложнится.

Не подходи к программированию МК с точки зрения ОС и тогда будет всё просто. Ведь драйвера и библиотеки на МК это просто функции пишущие или читающие какие либо регистры и больше ничего.
 
  • Лойс +1
Реакции: Геннадий П