отправлять более одного байта за раз SPI Arduino
Недавно я пытался отправить целые числа через SPI между двумя Arduino Uno. Я использую первый пример учебника Ника Гэммонса и пытаюсь изменить его, чтобы получить базовое представление о том, как общаться через SPI.
http://www.gammon.com.au/spi
МАСТЕР
// Автор: Ник Гаммон
// февраль 2011 г.
#include <SPI.h>
void setup (void)
{
digitalWrite(SS, HIGH); // обеспечиваем, чтобы SS оставался высоким на данный момент
// Переводим выводы SCK, MOSI, SS в режим вывода
// также переводим SCK, MOSI в состояние LOW и SS в состояние HIGH.
// Затем переводим оборудование SPI в режим Master и включаем SPI
SPI.begin ();
// Немного замедляем мастер
SPI.setClockDivider(SPI_CLOCK_DIV8);
} // конец настройки
void loop (void)
{
char c;
// включаем выбор подчиненного
digitalWrite(SS, LOW); // SS — это контакт 10
// отправляем тестовую строку
for (const char * p = "Hello, world!\n" ; c = *p; p++)
SPI.transfer (c);
// отключаем выбор подчиненного
digitalWrite(SS, HIGH);
delay (1000); // задержка 1 секунда
} // конец цикла
ВЕДОМЫЙ - БЕЗ ПЕРЕРЫВОВ
// Автор: Ник Гаммон
// февраль 2011 г.
#include <SPI.h>
char buf [100];
volatile byte pos;
volatile boolean process_it;
void setup (void)
{
Serial.begin (115200); // отладка
// включаем SPI в подчиненном режиме
SPCR |= bit (SPE);
// нужно отправить на главный вход, *ведомый на выход*
pinMode(MISO, OUTPUT);
// готовимся к прерыванию
pos = 0; // буфер пуст
process_it = false;
// теперь включаем прерывания
SPI.attachInterrupt();
} // конец настройки
// процедура прерывания SPI
ISR (SPI_STC_vect)
{
byte c = SPDR; // захват байта из регистра данных SPI
// добавляем в буфер, если есть место
if (pos < (sizeof (buf) - 1))
buf [pos++] = c;
// пример: новая строка означает время обработки буфера
if (c == '\n')
process_it = true;
} // конец процедуры прерывания SPI_STC_vect
// основной цикл - ожидание установки флага в процедуре прерывания
void loop (void)
{
if (process_it)
{
buf [pos] = 0;
Serial.println (buf);
pos = 0;
process_it = false;
} // конец набора флагов
} // конец цикла
ВЕДОМЫЙ - С ПРЕРЫВАНИЯМИ
// Автор: Ник Гаммон
// апрель 2011 г.
// что делать с входящими данными
byte command = 0;
// начало транзакции, команды еще нет
void ss_falling ()
{
command = 0;
} // конец процедуры обработки прерывания (ISR) ss_falling
void setup (void)
{
// нужно отправить на главный вход, *ведомый на выход*
pinMode(MISO, OUTPUT);
// включаем SPI в подчиненном режиме
SPCR |= _BV(SPE);
// включаем прерывания
SPCR |= _BV(SPIE);
// прерывание по заднему фронту SS
attachInterrupt (0, ss_falling, FALLING);
} // конец настройки
// процедура прерывания SPI
ISR (SPI_STC_vect)
{
byte c = SPDR;
switch (command)
{
// нет команды? тогда это команда
case 0:
command = c;
SPDR = 0;
break;
// добавляем к входящему байту, возвращаем результат
case 'a':
SPDR = c + 15; // добавляем 15
break;
// вычитаем из входящего байта, возвращаем результат
case 's':
SPDR = c - 8; // вычитаем 8
break;
} // конец переключателя
} // конец процедуры обслуживания прерывания (ISR) SPI_STC_vect
void loop (void)
{
// все сделано с прерываниями
} // конец цикла
ОРИГИНАЛЬНАЯ ПРОБЛЕМА. Мне удалось заставить это работать, поэтому проводка правильная, однако я не могу заставить его отправлять что-либо, кроме строки. Я могу изменить строку, и это работает, но меня путают указатели в C. Любой код, который я пробовал, компилируется неправильно или вообще не отправляется, если компилируется. Может ли кто-нибудь подсказать мне, с чего начать? Мои лучшие усилия приведены ниже
МАСТЕР
#include <SPI.h>
int x = 10000;
int y = 20000;
int z = 30000;
byte High_x = highByte(x);
byte Low_x = lowByte(x);
byte High_y = highByte(y);
byte Low_y = lowByte(y);
byte High_z = highByte(z);
byte Low_z = lowByte(z);
int X = word(High_x<<8|Low_x);
int Y = word(High_y<<8|Low_y);
int Z = word(High_z<<8|Low_z);
byte myArray[7];
void setup (void)
{
Serial.begin(115200);
digitalWrite(SS, HIGH); // обеспечиваем, чтобы SS оставался высоким на данный момент
// Переводим выводы SCK, MOSI, SS в режим вывода
// также переводим SCK, MOSI в состояние LOW и SS в состояние HIGH.
// Затем переводим оборудование SPI в режим Master и включаем SPI
SPI.begin ();
// Немного замедляем мастер
SPI.setClockDivider(SPI_CLOCK_DIV8);
myArray[0] = High_x;
myArray[1] = Low_x;
myArray[2] = High_y;
myArray[3] = Low_y;
myArray[4] = High_z;
myArray[5] = Low_z;
myArray[6] = 0;
Serial.println(X);
Serial.println(Y);
Serial.println(Z);
delay(2000);
}
void loop (void)
{
byte data;
// включаем выбор подчиненного
digitalWrite(SS, LOW); // SS — это контакт 10
// отправляем тестовую строку
for (byte * p = myArray[0]; data = *p; p++)
SPI.transfer (data);
Serial.println("sending data");
// отключаем выбор подчиненного
digitalWrite(SS, HIGH);
delay (1000); // задержка 1 секунда
} // конец цикла
РАБ
#include <SPI.h>
byte buf [100];
volatile byte pos;
volatile boolean process_it;
void setup (void)
{
Serial.begin (115200); // отладка
// включаем SPI в подчиненном режиме
SPCR |= bit (SPE);
// нужно отправить на главный вход, *ведомый на выход*
pinMode(MISO, OUTPUT);
// готовимся к прерыванию
pos = 0; // буфер пуст
process_it = false;
// теперь включаем прерывания
SPI.attachInterrupt();
Serial.println("End of Setup");
} // конец настройки
// процедура прерывания SPI
ISR (SPI_STC_vect)
{
Serial.println("Beginning of Interrupt Routine");
byte data = SPDR; // захват байта из регистра данных SPI
// добавляем в буфер, если есть место
if (pos < (sizeof (buf) - 1))
buf [pos++] = data;
// пример: новая строка означает время обработки буфера
if (data == '0')
process_it = true;
Serial.println("End of Interrupt Routine");
} // конец процедуры прерывания SPI_STC_vect
// основной цикл - ожидание установки флага в процедуре прерывания
void loop (void)
{
Serial.println("Beggining of Loop");
for(int i = 0; i < 100; i++)
if (process_it)
{
buf [pos] = 0;
Serial.println (buf[i]);
pos = 0;
process_it = false;
} // конец набора флагов
Serial.println("End of Loop");
} // конец цикла
В будущем я надеюсь отправить 10 16-битных целых чисел через SPI с Arduino на Arduino или, возможно, с Arduino на Raspberry Pi.
Спасибо
РЕДАКТИРОВАНИЕ. Я начал использовать код, который использует прерывания, и мне удалось передать нужные мне данные, однако я могу получить правильные данные, только установив для первого и второго элемента массива значение «High_x», в противном случае он пропускает первую часть данных?
НОВЫЙ МАСТЕР
#include <SPI.h>
int x = 10000;
int y = 20000;
int z = 30000;
byte High_x = highByte(x);
byte Low_x = lowByte(x);
byte High_y = highByte(y);
byte Low_y = lowByte(y);
byte High_z = highByte(z);
byte Low_z = lowByte(z);
int X = word(High_x<<8|Low_x);
int Y = word(High_y<<8|Low_y);
int Z = word(High_z<<8|Low_z);
byte myArray[8];
void setup (void)
{
digitalWrite(SS, HIGH); // обеспечиваем, чтобы SS оставался высоким на данный момент
// Переводим выводы SCK, MOSI, SS в режим вывода
// также переводим SCK, MOSI в состояние LOW и SS в состояние HIGH.
// Затем переводим оборудование SPI в режим Master и включаем SPI
SPI.begin ();
// Немного замедляем мастер
SPI.setClockDivider(SPI_CLOCK_DIV8);
myArray[0] = High_x;
myArray[1] = High_x;
myArray[2] = Low_x;
myArray[3] = High_y;
myArray[4] = Low_y;
myArray[5] = High_z;
myArray[6] = Low_z;
myArray[7] = 0;
Serial.print("wait for loop");
//Serial.println(X);
//Serial.println(Y);
//Serial.println(Z);
delay(2000);
}
void loop (void)
{
byte data;
// включаем выбор подчиненного
digitalWrite(SS, LOW); // SS — это контакт 10
// отправляем тестовую строку
for (byte * p = &myArray[0]; data = *p; p++){
SPI.transfer (data);
}
// отключаем выбор подчиненного
digitalWrite(SS, HIGH);
delay (1000); // задержка 1 секунда
} // конец цикла
НОВЫЙ РАБ
byte buf [100];
volatile byte pos;
// что делать с входящими данными
byte command = 0;
// начало транзакции, команды еще нет
void ss_falling ()
{
command = 0;
} // конец процедуры обработки прерывания (ISR) ss_falling
void setup (void)
{
Serial.begin(9600);
// нужно отправить на главный вход, *ведомый на выход*
pinMode(MISO, OUTPUT);
// включаем SPI в подчиненном режиме
SPCR |= _BV(SPE);
// включаем прерывания
SPCR |= _BV(SPIE);
// прерывание по заднему фронту SS
attachInterrupt (0, ss_falling, FALLING);
} // конец настройки
// процедура прерывания SPI
ISR (SPI_STC_vect)
{
byte data = SPDR;
// добавляем в буфер, если есть место
if (pos < (sizeof (buf) - 1))
buf [pos++] = data;
} // конец процедуры обслуживания прерывания (ISR) SPI_STC_vect
void loop (void)
{
for(int i = 0; i < 100; i++){
Serial.println (buf[i]);
pos = 0;
delay(1000);
}
} // конец цикла
Есть какие-нибудь предложения о том, почему это необходимо? Есть ли какая-то базовая особенность SPI, которую мне не хватает?
Всем спасибо
Росс
@Ross Hanna, 👍1
2 ответа
Лучший ответ:
У вас есть несколько проблем.
Прежде всего, пример Ника Гаммона — это передача строк. Строки обладают свойством, заключающимся в том, что конкретное значение '\0'
(null) обычно не может появиться в самой строке, и поэтому его можно использовать как «конечный флаг», чтобы отметить конец строки. нить. Вот почему цикл for
на главной стороне работает правильно.
Обратите внимание, что сам нуль НЕ передается по каналу SPI. Вместо этого Ник показывает пример использования другого символа ('\n'
— новая строка) для запуска обработки на подчиненной стороне.
Если вы хотите передать двоичные данные, которые могут легко содержать байты 0x00 (ноль), вам необходимо найти другой способ разграничить данные как на отправляющей, так и на принимающей стороне. Данные вашего примера (16-битные целые числа 10 000, 20 000 и 30 000) не содержат нулевых байтов, но в целом на это нельзя полагаться. Кроме того, поскольку значение null на самом деле не передается, вы не можете использовать его на принимающей стороне для запуска обработки.
Для отправки двоичных данных часто используется несколько подходов:
Отправьте количество байтов явно в начале транзакции и инициируйте обработку на принимающей стороне после получения этого количества байтов.
Используйте «специальное» значение (например, нулевое), чтобы отметить конец данных, но тогда вам придется каким-то образом «экранировать» это значение, если оно когда-либо появится в реальных данных.
Кроме всего этого, у вас есть ошибка в главном коде. Инициализация цикла for
, byte * p = myArray[0]
не устанавливает указатель p
на адрес myArray
как и предполагалось; вместо этого он устанавливает значение первого байта массива, равное 0x27 (High_x
). Вам нужно сказать либо byte * p = &myArray[0]
, либо просто byte * p = myArray
.
Я вижу в вашем коде две большие проблемы (их могло быть больше, но я не особо всматривался).
Во-первых, используется Serial.println в программе обработки прерываний. Это предотвращает получение более одного байта, поскольку выполнение Serial.println займет гораздо больше времени, чем получение 6 байтов (даже при медленной тактовой частоте SPI) по SPI. В большинстве случаев процедуры прерываний должны завершаться быстро.
Следующая проблема заключается в том, что вы проверяете наличие символа 0 («0»), который не равен байту 0, который вы отправляете в качестве конца данных, но имеет значение 48 в десятичном формате.
Изменить способ проверки конца данных:
Вы можете проверить окончание этих нескольких различных способов, установив фиксированное количество передач байтов, установив прерывание на выводе выбора подчиненного устройства или закодировав двоичные данные в base64 и используя управляющий символ. Я бы рекомендовал прерывание.
- Как заставить Arduino надежно работать в качестве ведомого устройства SPI?
- ADS1262 и 2,2 SPI TFT (два ведомых SPI) с Arduino
- Взаимная связь ESP32 SPI
- Библиотека AD7768-1 "Ардуино"
- nRF24L01 +pa +lna link не связывается
- Моя настройка двух ведомых SPI на arduino не работает правильно
- SPI Arduino slave не получает данные правильно
- Как использовать SPI на Arduino?
Если бы это был я, я бы, вероятно, использовал SS для явного разграничения данных. Ник показывает примеры этого с использованием опроса или прерываний., @Dave Tweed
Нет, он не собирает их на стороне приема в этом примере; он просто обрабатывает их напрямую в ISR и отправляет снова. Но нет причин, по которым вы не можете поместить данные в буфер, как в более ранних примерах на стороне подчиненного устройства., @Dave Tweed
Отредактируйте свой вопрос и замените код на последнюю версию. Обратите внимание, что я удалил имя Ника Гэммона из вашего кода. Сомневаюсь, что он хотел бы ассоциироваться с кодом, который он не писал., @Dave Tweed