Arduino-тестер длинных линий интерфейса SPI

Duino A.R.
Offline
Зарегистрирован: 25.05.2015

Доброго времени суток!

В статье предлагается устройство для проверки прохождения данных в длинных линиях интерфейса SPI.  В статье приводятся структурная и принципиальная электрическая схемы устройства, текст программы. Подробно рассматривается постановка задачи. Даны необходимые объяснения.

Duino A.R.
Offline
Зарегистрирован: 25.05.2015

    Устройство позволяет:
    1. Опробовать кабель для линии передачи данных.
    2. Опробовать схемное решение кабельных драйверов.
    3. Оценить влияние фактических помех на передачу данных.
    4. Подобрать параметры вызова функций стандартной библиотеки SPI.

    Устройство обладает следующими особенностями:
    1. В качестве Master-устройства используется МК Arduino UNO (допускается другой МК того же семейства).
    2. Устройством считывания информации и ввода данных служит Ардуино-шилд с дисплеем LCD 1602 и 5-и кнопочной аналоговой клавиатурой.
    3. В качестве Slave-устройства в базовой версии используется ИМС 74HC165.
    4. Программирование Slave-устройства не требуется.
    5. Для реализации программной части обмена данными используется стандартная библиотека SPI с возможностью оперативного параметрирования вызова функций.
    6. Устройство может использоваться автономно (без компьютера).
    7. Устройство не требует наладки.

     Ниже идут следующие разделы:
    • О чем
    • Для кого
    • Для чего
    • ТЗ. Без него нельзя
    • Общий подход
    • Особенности программы
    • Структура устройства
    • Минимальная схемная реализация
    • Результаты экспериментов

Печатный вариант статьи в формате pdf можно скачать по ссылке: https://yadi.sk/i/n5xK7MvoE_PUpg

 

Duino A.R.
Offline
Зарегистрирован: 25.05.2015

О чём
Время от времени возникает необходимость обеспечить связь микроконтроллера Ардуино с удаленными датчиками, исполнительными устройствами, другими контроллерами Ардуино. Такие задачи возникают при проектировании «умного дома», игровых комплексов, теплиц и других проектов с распределённой структурой. Если проект объёмен, и делает его профессионал в области АСУ ТП или IT, то решение, скорее всего, он будет искать ближе к проверенным и хорошо им освоенным промышленным вариантам с использованием интерфейсов RS-485, CAN, Ethernet, I2C.

А что делать, если у калитки кодовый замок с клавиатурой из 16 клавиш, Ардуина в теплом доме (что совершенно оправдано), а между домом и калиткой метров эдак 15 по дорожке и 25 — 30 — по кабелю? Устраивать  из этого «кусочек АСУ ТП» как то «не оно», да, честно говоря, и некому.

Помочь тут может интерфейс SPI (Serial Peripheral Interface). Штука простая и при правильном применении весьма эффективная. Требует минимальных познаний в программировании и схемотехнике. Кто четко понимает, что такое «сдвиговый регистр», так вот это «оно и есть», только слегка приукрашенное.

Если «сдвиговый регистр» всплыл из памяти неуверенно, или его там раньше и вовсе не было, это не беда. Тогда сначала рекомендую прочесть:

«Множим выходы с помощью сдвигового регистра 74HC595» (для входов всё будет аналогично, только микросхема другая — 74HC165)
http://arduino.ru/Tutorial/registr_74HC595

Весьма полезно будет почитать в Википедии: «Serial Peripheral Interface»
https://ru.wikipedia.org/wiki/Serial_Peripheral_Interface

«Последовательный интерфейс SPI (3-wire)»
http://www.gaw.ru/html.cgi/txt/interface/spi/index.htm

Там всё коротко, по делу, без зауми. И по жизни при работе с Ардуино может не один раз пригодиться. Рекомендую освоить.

И обязательно нужно будет в итоге прочесть несколько страничек описания библиотеки SPI на сайте Arduino.ru, поскольку мы будем ориентироваться в дальнейшем на её использование.
http://arduino.ru/Reference/Library/SPI

Duino A.R.
Offline
Зарегистрирован: 25.05.2015

Для кого
Статья написана для тех, кто уже попробовал, кому понравилось, и кто хочет продолжать. В статье много букв. Это не модно. И это специально. Из статьи можно почерпнуть фрагменты кода или схем. Всё работает. Но главное - другое. Я показываю, как могу, как и почему я ставил задачу и как её решал. Можно согласиться или нет с моей постановкой задачи или с её решением, а, может быть, и с тем, и с другим. Главное, чтобы мой пример помог читателям правильно ставить собственные задачи и подбирать для них эффективные пути решения.

Для чего
Интерфейс  SPI прост, быстр и эффективен на… сверхкоротких и коротких дистанциях. Т.е. когда обменивающиеся по нему информацией устройства расположены на одной плате, на платах в «бутерброде» из Ардуино-шилдов, на одном столе.

Если речь идет о расстояниях от метров до десятков метров, то всё может оказаться не только ни радужно, а совсем даже плачевно. «Длинная линия» - это такое состояние проводов, когда их уже нельзя рассматривать, как на схеме, идеальным проводником с нулевым сопротивлением. Начинают самым неприятным образом сказываться их индуктивность, емкость, омическое (активное) сопротивление да и ещё и во взаимовлиянии отдельных жил. Последовательные интерфейсы — суть импульсные цепи, и импульсы эти могут в длинных линиях искажаться до неузнаваемости. На картинках потом сами увидите. Да, и на длинную линию можно еще и помех нацеплять, если она будет проходить рядом с силовым оборудованием, особенно, если в нем есть тиристорные регуляторы с фазо-импульсным управлением.

Можно ли эту неприятность как то преодолеть? Да, можно. При разумной постановке задачи и оправданном выборе:
    1. Кабеля.
    2. Кабельных драйверов (усилителей) и кабельных приемников.
    3. Схемы питания.
    4. Параметров интерфейса (частоты тактовых импульсов и сдвига фаз).

Описываемое устройство как раз и предназначено для облегчения указанного выбора в конкретных, вплоть до полевых, условий.

Используя конкретный кабель, проложенный и подключенный конкретным образом, Master-устройство посылает байт на Slave-устройство и принимает его же в ответ. Делает оно это много тысяч раз и подсчитывает, сколько байт вернулись без изменений, а сколько оказались сбойными. Человек это видит и принимает решение, устраивает его результат или нет. По-хорошему, сбойных байт не должно быть совсем. Но могут и допускаться. От задачи зависит.

Если человека результат не устроил, то он может:
    • Заменить кабель.
    • Изменить схему его включения («поиграть» с «землёй», например).
    • Изменить его трассу (чтобы обойти источники помех).
    • Изменить электронную схему, работающую непосредственно с кабелем.
    • Развязать между собой, а лучше и с кабелем, питание  Master- и  Slave-устройств.
    • Изменить частоту и/или фазу тактовых импульсов.

После проведенных изменений вновь запускается цикл измерений. По его результатам  оценивают, принесли ли сделанные изменения пользу. «Материальные» замены испытатель материально и делает, а изменять параметры интерфейса он может по меню, используя дисплей устройства. На нем же он видит и результаты измерений.

 

Duino A.R.
Offline
Зарегистрирован: 25.05.2015

ТЗ. Без него нельзя
Итак. Нам нужно устройство, которое позволит нам в конкретной, возможно полевой (дачно-приусадебной) обстановке проверять, насколько достоверно передаётся информация с использованием интерфейса SPI.

Я посчитал, что такое устройство должно отвечать следующим требованиям:
    1. В качестве  Master-устройства использовать Arduino UNO.
    2. В качестве устройства ввода-вывода ЧМИ использовать Ардуино-шилд LCD 1602 c 5-и кнопочной аналоговой клавиатурой.
    3. Slave-устройство не должно требовать программирования.
    4. Устройство не должно требовать настройки или калибровки.
    5. Устройство должно допускать применение без использование компьютера.
    6. Устройство должно допускать батарейное питание.
    7. Программная реализация интерфейса SPI должна использовать стандартную библиотеку  SPI.
    8. Программирование задачи и загрузка программы в МК должны вестись средствами Arduino IDE без использования ассемблерных вставок.

Сделаем некоторые пояснения к пунктам Технического Задания (ТЗ).
Пункты 1, 2, 5 весьма тесно связаны. Задуманное устройство будет использоваться редко, но при использовании циклы измерений и ввод параметров будут весьма частыми. Изменять параметры интерфейса непосредственно в программе и потом каждый раз загружать её в МК через компьютер банально неудобно, особенно, если делать это приходится на выезде. Параметров у интерфейса не сильно много, но они имеют численные значения, да и результаты измерений имеют вид целых чисел. Их хорошо бы видеть.  Нужен алфавитно-цифровой дисплей для считывания результатов и для ввода параметров. К нему, соответственно, нужна клавиатура, пусть самая небольшая.

Ардуино-шилд, содержащий двух строчный алфавитно-цифровой дисплей, совмещенный с 5-и кнопочной аналоговой клавиатурой сейчас весьма распространен и не дорог. Его программная поддержка известна и хорошо работает. Этот модуль рассчитан на прямую стыковку с платой Arduino UNO или её клонами и аналогами, выполненными в том же конструктиве. Прямая стыковка  обеспечивает максимальную простоту монтажа и достаточную для макета конструктивную устойчивость. Если аккуратно, можно обойтись и без корпуса. Нам, по большому счету, даже и не прибор нужен, а прилично собранный макет. Замеры сделать и разобрать. Дает нам этот шилд такую возможность? Да. И он оставляет нетронутыми важные для нашего проекта пины на МК. Кроме линии SS (Slave Select) на пине D10. В шилде этот пин используется для управления подсветкой экрана в ШИМ-режиме. У нас одно специфичное Slave-устройство, сигнал SS не используется. Библиотеки SPI и LiquidCrystal из-за этого пина тоже не конфликтовали. Всё сошлось.

Для воспроизведения SPI -интерфейса нам нужно Slave-устройство. Оно должно быть самым простым. Чтобы не морочиться, исключить написание, отладку, заливку двух программ вместо одной. Тем более, что библиотека SPI поддерживает только режим «Мастер». Мы не проверим всех режимов  SPI? Да и пусть. Мы не интерфейс проверяем, а кабель, электронные драйверы, уровень помех.

Батарейное питание. Вы пришли, например, на площадку квеста, где Вам кинули кабели. Какие надо или, что не редко, какие были. А Вы хотите проверить, оно хоть как то будет работать? А стройка продолжается. Батарейное питание будет весьма кстати.

Зачем нам библиотека  SPI? Одна из самых важных причин, почему мы выбрали интерфейс  SPI – простая программная реализация. Я бы даже сказал — самая простая из возможных для конкретного случая. Могли бы мы написать драйвер  SPI сами? Для самодельных устройств - категорически да! Но… Мы с вами — любители. Успели освоить некоторую часть С++ из среды Arduino IDE. И мы вполне можем сами написать драйвер, даже разными способами. Только скорость его работы будет во много раз ниже, чем при использовании  библиотеки SPI.  Ассемблера МК мы с вами не знаем, структуру МК подробно — тоже нет. Лучше туда пока не лезть. Так что попользоваться на данном этапе готовой, входящей в «джентельменский набор»  Arduino IDE библиотекой  — прекрасный выход.

Общий подход
Наша задача состоит в обеспечении одно- или двунаправленной передачи данных по интерфесу SPI на расстояние от нескольких до нескольких десятков метров. Из некоторых исходных предположений выбираем кабель и схемное решение электронной части, к которой подключается непосредственно кабель. В идеальном случае прокладываем кабель так, как должен проходить при эксплуатации системы. Если это невозможно, то раскладываем кабель «примерно» с обязательным учетом его необходимой длины. К одной стороне кабеля через кабельные усилители подключается Master-устройство на базе МК Ардуино, к другой стороне кабеля также через кабельные усилители подключается Slave-устройство, выполненное на базе микросхемы сдвигового регистра, в данном случае — 74HC165. Структурная схема максимальной версии устройства представлена на рис. 1.

Рис. 1.  Arduino-тестер длинных линий  интерфейса SPI. Максимальная версия.  Схема структурная.

Рис. 1.  Arduino-тестер длинных линий  интерфейса SPI. Максимальная версия.
Схема структурная.

 

Duino A.R.
Offline
Зарегистрирован: 25.05.2015

Через экранное меню на Master-устройстве задаются параметры SPI интерфейса:
    1. Количество пересылаемых и принимаемых байт в одном цикле измерения (от 100 до 30000).
    2. Значение делителя опорной частоты процессора для задания частоты тактовых импульсов Clock Divider (2, 4, 8, 16, 32, 64, 128).
    3. Режим фазы синхронизации Date Mode (0, 1, 2, 3).

После запуска цикла измерения программа формирует значение тестового байта и из Master-устройства за 8 тактов пересылает его в  Slave-устройство. Сразу после этого  Master-устройство также за 8 тактов считывает переданный байт с выхода сдвигового регистра  Slave-устройства. Если переданный байт совпал с принятым, то на единицу увеличивается счетчик «годных» байт. Если переданный и принятый байт не совпали, то на единицу увеличивается счетчик сбойных байт. Процедура передачи и приёма байта автоматически повторяется то количество раз, которое было указано при задании параметров.

После окончания цикла измерения на экран выводятся количество переданных байт, количество правильно принятых байт и количество сбойных байт.

Изменяя параметры Clock Divider добиваются, чтобы при минимальном возможном значении параметра, что соответствует максимальной скорости передачи данных, сбойные байты отсутствовали. Отмечу, что для Arduino UNO Rev.3 все указанные значения параметра Clock Divider – рабочие.

Если добиться желаемого только подбором Clock Divider не удалось, значит необходимо менять аппаратную часть устройства и/или кабель и повторять измерения. Весь процесс продолжают до получения необходимого результата.

Значение Date Mode зависит от схемы кабельной части устройства. Имеет смысл подобрать его для выбранной схемы, заменив кабель короткими проводниками. При сохранении схемы значение Date Mode можно не менять.

 

Duino A.R.
Offline
Зарегистрирован: 25.05.2015
[code]

/*
  Программа для тестирования линий передачи данных
  по интерфейсу SPI Arduino
  04.01.2020 - 30.01.2020
*/
/*
  ---[ Подключение ЖКИ]---------------------

  >>> ЖКИ <<<          >>> Arduino <<<

   RW pin           = GND
   LCD RS pin       = digital pin 8
   LCD Enable pin   = digital pin 9
   LCD D4 pin       = digital pin 4
   LCD D5 pin       = digital pin 5
   LCD D6 pin       = digital pin 6
   LCD D7 pin       = digital pin 7
*/
// Подключение библиотек
#include <LiquidCrystal.h>  // Для работы с ЖКИ
#include <SPI.h>            // Для работы с SPI

// Задание пинов для работы с ЖКИ-панелью
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);

// Переменные для задания параметров цикла измерения
int Param[]={              // Массив значений параметров цикла измерения 
  30000,                   // Количество байт для передачи-приёма в цикле измерения
  0,                       // Значение номера DataMetod
  3                        // Значение номера ClockDivider
};
int Param_Min[]={          // Массив минимальных разрешенных значений параметров цикла измерения 
  10,                      // Минимальное количество байт для передачи-приёма в цикле измерения
  0,                       // Минимальное значение номера DataMetod
  0                        // Минимальное значение номера ClockDivider
};
int Param_Max[]={          // Массив максимальных разрешенных значений параметров цикла измерения 
  30000,                   // Максимальное количество байт для передачи-приёма в цикле измерения
  3,                       // Максимальное значение номера DataMetod
  6                        // Максимальное значение номера ClockDivider
};
int Param_Step[]={         // Массив размеров приращений значений параметров цикла измерения при автоповторе кнопок
  50,                      // Размер приращения при задании количества байт в цикле измерения
  1,                       // Размер приращения номера DataMetod 
  1                        // Размер приращения номера ClockDivider 
};
char* ClockDivText[]={     // Текстовые значения делителя частоты для вывода на экран
  "2",
  "4",
  "8",
  "16",
  "32",
  "64",
  "128" 
};

// Переменные для организации цикла измерения
int CCNDT=0;                // Количество актов передачи-приёма в цикле измерения  CycleCompletedNumberDataTransmissions
int CNVB=0;                 // Количество правильно переданных байтов в цикле измерения  CycleNumberValidBytes
int CNBB=0;                 // Количество сбойных байтов в цикле измерения  CycleNumberBadBytes
byte StartON=0;             // Флаг запуска цикла измерения. =1 - измерение запущено

// Строки-заготовки для вывода на дисплей
char* MenuText_1[] = {      // Массив надписей 1-й строки
  "Number of bytes",        // Задайте количество актов передачи-приёма в цикле измерения
  "Data Metod",             // Задайте режим логики работы интерфейса SPI
  "Clock Divider",          // Задайте значение делителя тактовой частоты
  "*SELECT*->START",        // Для запуска цикла измерения нажмите кнопку *SELECT*
  "All  Valid Bad "         // Заставка в цикле измерения:"Всего  Годных  Сбойных"
};
char* MenuText_2[] = {      // Массив надписей 2-й строки
  "-<      >+",             // Поле для ввода занчения параметра
  "-<      >+",             // 
  "-<      >+",             // 
  "                ",       // Очистка строки  
  "                "        // Очистка строки
};

// Переменные меню
int SNmax = 4;               // Максимальный номер экрана  ScreenNumberMax
int SN = 0;                  // Текущий номер экрана  ScreenNumber

// Переменные для работы обработчика кнопок
byte KeyMaxIndex = 4;               // Максимальный индекс кнопки. Количество кнопок минус 1.
byte Pressed = 1;                   // Задает, какой сигнал считать нажатой кнопкой - "0" или "1".
unsigned long KeyTimeLong = 500;    // Время нажатия в мс, которое считается долгим ("long").
//
byte KeyCurrent[5];                 // Массив текущего состояния кнопок.
byte KeyPrevious[5];                // Массив предыдущего состояния кнопок.
byte KeyPressed[5];                 // Массив флагов "кнопку нажали".
byte KeyReleased[5];                // Массив флагов "кнопку отпустили".
byte KeyPressedLong[5];             // Массив флагов "кнопку нажали долго".
unsigned long KeyTimePressed[5];    // Время, в которое нажали каждую кнопку.
//
unsigned long KeyTimerClock = 150;  // Количество миллисекунд в "тике" общего таймера кнопок.
unsigned long KeyTimerMillis = 0;   // Текущее значение общего таймера кнопок в мс.
byte KeyTimerValue[5];              // Массив количества "тиков" в таймерах автоповтора кнопок.
byte KeyTimer[5];                   // Массив таймеров-счетчиков автоповтора кнопок.

// Задание пинов ввода-вывода
int pinMOSI = 11;                   // Задание пина на последовательную выдачу данных MOSI
int pinSCK = 13;                    // Задание пина тактовых импульсов SCK
int pinMISO = 12;                   // Задание пина на последовательный прием данных MISO
int pinSS = 10;                     // Задание пина выбора ведомого устройства Slave Select

// Переменные для передачи, приема и сравнения байт
byte Byte_TX = 0;                   // Байт на передачу в цикле измерения
byte Byte_RX = 0;                   // Байт, принятый в ходе измерения
byte Byte_CMP = 0;                  // Флаг равенства переданного и принятого байта. =1 - байты совпали


// *******Подпрограмма ввода состояния кнопок****************
void KeyInput () {
  for (byte i = 0; i <= KeyMaxIndex; i++) KeyCurrent[i] = 0; // Перед считыванием состояния очистить массив текущего состояния кнопок
  int key_in = analogRead(0);  // Считываем сигнал с клавиатуры                                           
  if (key_in > 1000) return; // Состояние "не нажато ни одной кнопки" встречается чаще всего, поэтому его проверяем первым
  if (key_in < 100)   {
    KeyCurrent[0] = 1;  // Пишем "1", если нажата кнопка btnRIGHT
    return;
  }
  if (key_in < 250)  {
    KeyCurrent[1] = 1;  // Пишем "1", если нажата кнопка btnUP
    return;
  }
  if (key_in < 450)  {
    KeyCurrent[2] = 1;  // Пишем "1", если нажата кнопка btnDOWN
    return;
  }
  if (key_in < 650)  {
    KeyCurrent[3] = 1;  // Пишем "1", если нажата кнопка btnLEFT
    return;
  }
  if (key_in < 850)  {
    KeyCurrent[4] = 1;  // Пишем "1", если нажата кнопка btnSELECT
    return;
  }
}
//*********************************************

// ******* Подпрограмма обработки состояния кнопок *************
void KeyProcessor() {
  unsigned long Time = millis();                                     // Запоминаем время входа в обработчик кнопок.
  for (byte i = 0; i <= KeyMaxIndex; i++) {
    if ((KeyCurrent[i] != Pressed) && (KeyPrevious[i] == Pressed))   // Если кнопку отпустили, то:
    {
      KeyReleased[i] = 1;                                            // взводим флаг "кнопку отпустили",
      KeyPressed[i] = 0;                                             // сбрасываем флаг "кнопку нажали",
      KeyPressedLong[i] = 0;                                         // сбрасываем флаг "кнопку нажали долго".
    }
    if ((KeyCurrent[i] == Pressed) && (KeyPrevious[i] != Pressed))   // Если кнопку нажали, то:
    {
      KeyReleased[i] = 0;                                            // сбрасываем флаг "кнопку отпустили",
      KeyPressed[i] = 1;                                             // взводим флаг "кнопку нажали",
      KeyPressedLong[i] = 0;                                         // сбрасываем флаг "кнопку нажали долго",
      KeyTimePressed[i] = Time;                                      // запоминаем время, когда кнопку нажали.
    }
    if ((KeyCurrent[i] == Pressed) && (KeyPrevious[i] == Pressed))   // Кнопка была и остается нажатой
    {
      if (KeyTimePressed[i] >= Time) {                               // Если системный таймер "перевернулся",
        KeyTimePressed[i] = Time;                                    // перепривяжем времия к новому таймеру.
      }
      if ((Time - KeyTimePressed[i]) >= KeyTimeLong) {               // Если кнопка нажата "долго" -
        KeyPressedLong[i] = 1;                                       // взодим флаг "кнопку нажали долго".
      }
    }
  }
  for (byte i = 0; i <= KeyMaxIndex; i++) {                          // Обработка закончена.
    KeyPrevious[i] = KeyCurrent[i];                                  // Заменяем прошлое состояние кнопок текущим.
  }
}
// ******************************************************

//****************Подпрограмма вывода текущего экрана*********
void ScreenOutput ()                  // Вывод на ЖКИ текущего экрана
{
   lcd.clear();                       // Очистка экрана
      lcd.print(MenuText_1[SN]);      // Выводим текст верхней строки соответствующего экрана
      lcd.setCursor(0, 1);            // Ставим курсор в начало второй строки
      lcd.print(MenuText_2[SN]);      // Выводим заготовку поля ввода числового значения параметра
      if (SN < SNmax - 1)             // Если это экран ввода параметров, то
      {
      lcd.setCursor(3, 1);            // Ставим курсор внутрь поля ввода параметра
      if (SN==2)                      // Если это экран ввода значения делителя частоты, то
      {
        lcd.print(ClockDivText[Param[SN]]);  // Выводим значение делителя частоты
        return;                              // и выходим из пп
      }
      else
      {
      lcd.print(Param[SN]);           // Выводим действующее значение параметра
      return;                         // и выходим из пп
      }
      };
      if (SN == SNmax - 1) return;    // Если это экран запуска измерения, то просто уходим
      {
       lcd.setCursor(0, 1);           // Для экрана измерения выводим строку данных
       lcd.print(CCNDT);              // ВСЕГО
       lcd.setCursor(5, 1);           // Для экрана измерения выводим строку данных
       lcd.print(CNVB);               // ГОДЕН
       lcd.setCursor(11, 1);          // Для экрана измерения выводим строку данных
       lcd.print(CNBB);               // СБОЙНЫХ
      }
}
// *******************************************************************

// *********** Подпрограмма экранного меню ***************************
void ScreenMenu ()
{ 
  if (KeyPressed[1] == 0 && KeyPressed[2] == 0) return;   // Если не нажали ни кнопку UP, ни кнопку DOWN, уходим
  if (KeyPressed[1] == 1)               // Если нажали кнопку UP, 
  {
    KeyPressed[1] = 0;                  // Сбрасываем флаг нажатия кнопки UP
    SN++;                               // Увеличиваем номер текущего экрана на 1
    if (SN > SNmax) SN = 0;             // С последнего экрана перескакиваем на первый. Движемся по кольцу
  };
    if (KeyPressed[2] == 1)             // Если нажали кнопку DOWN,
    {
      KeyPressed[2] = 0;                // Сбрасываем флаг нажатия кнопки DOWN
      SN--;                             // Уменьшаем номер текущего экрана на 1
      if (SN < 0) SN = SNmax;           // С первого экрана перескакиваем на последний. Движемся по кольцу
    }
     ScreenOutput ();                   // Выод на ЖКИ текущего экрана
}
// **********************************************************

// ******* Подпрограмма ввода параметров цикла измерения ************
void ParamInput ()
{
      if (SN == SNmax-1) {                          // Если это экран запуска измерения, тогда,
        if (KeyPressed[4] == 0) return;             // если SELECT не нажат, то уходим из пп
        {                                           // Если SELECT нажат, то
          KeyPressed[4] = 0;                        // Сбрасываем флаг нажатой кнопки
          StartON = 1;                              // Выставляем флаг "измерение запущено"
          return;                                   // и выходим из пп
        }
      };
      if (SN > SNmax-1) return;                     // На этом экране нет ввода параметров. Выходим из пп
      if (KeyPressed[0] == 1)                       // Нажата кнопка RIGHT                     
      {
       Param[SN]++;                                  // Инкрементируем значение соответствующего экрану параметра
       Param[SN] = constrain(Param[SN], Param_Min[SN], Param_Max[SN]); // Не даем значению параметра выйти за установленные границы
       KeyPressed[0] = 0;                            // Сбрасываем флаг нажатия кнопки RIGHT
       ScreenOutput ();                              // Выод на ЖКИ текущего экрана
      }
    if (KeyPressed[3] == 1)                          // Нажата кнопка LEFT   
    {
     Param[SN]--;                                    // Декрементируем значение соответствующего экрану параметра
     Param[SN] = constrain(Param[SN], Param_Min[SN], Param_Max[SN]); // Не даем значению параметра выйти за установленные границы
     KeyPressed[3] = 0;                              // Сбрасываем флаг нажатия кнопки LEFT
     ScreenOutput ();                                // Выод на ЖКИ текущего экрана
    }
// Реализация автоповтора кнопок ввода значения параметров 
 unsigned long Time = millis();                      // Запоминаем текщее время в мс.
  if (KeyTimerMillis > Time) KeyTimerMillis = Time;  // Парируем возможный "переворот" системного таймера.
  if ((Time - KeyTimerMillis) >= KeyTimerClock) {    // Если прошёл "тик" общего таймера кнопок, то
       KeyTimerMillis = Time;                        // перезагружаем таймер и
       // Обработка кнопки "LEFT (-)"
      if (KeyPressedLong[3] == 0) {                   // Если кнопка "LEFT" не нажата "долго",
        KeyTimer[3] = KeyTimerValue[3];               // загружаем таймер кнопки и уходим.
      }
      else {                                          // Если кнопка "LEFT" нажата "долго",
        if (KeyTimer[3] <= 0) {                       // и таймер кнопки исчерпался, то
          KeyTimer[3] = KeyTimerValue[3];             // загружаем таймер кнопки,
          if (Param[SN]<=Param_Step[SN]) Param[SN]=0; // Не даем параметру стать "отрицательным"
          else Param[SN] = Param[SN] - Param_Step[SN];     // Уменьшаем на шаг значение соответствующего экрану параметра
          Param[SN] = constrain(Param[SN], Param_Min[SN], Param_Max[SN]); // Не даем значению параметра выйти за установленные границы
          ScreenOutput ();                            // Вывод на ЖКИ текущего экрана
        }
        KeyTimer[3]--;                                // Декрементируем таймер кнопки "LEFT".
      }
      // Обработка кнопки "RIGHT (+)"
      if (KeyPressedLong[0] == 0) {                   // Если кнопка "RIGHT" не нажата "долго",
        KeyTimer[0] = KeyTimerValue[0];               // загружаем таймер кнопки и уходим.
      }
      else {                                          // Если кнопка "RIGHT" нажата "долго",
        if (KeyTimer[0] <= 0) {                       // и таймер кнопки исчерпался, то
          KeyTimer[0] = KeyTimerValue[0];             // загружаем таймер кнопки,
          Param[SN] = Param[SN] + Param_Step[SN];     // Увеличиваем на шаг значение соответствующего экрану параметра
          Param[SN] = constrain(Param[SN], Param_Min[SN], Param_Max[SN]); // Не даем значению параметра выйти за установленные границы
          ScreenOutput ();                            // Вывод на ЖКИ текущего экрана
        }
        KeyTimer[0]--;                                // Декрементируем таймер кнопки "RIGHT".
      } 
    }
} 
// ***************************************************************

// ******** Подпрограмма цикла измерения *************************
void MeasurementCycle ()                // Подпрограмма цикла измерения
{
  if (StartON == 0) return;             // Если цикл измерения не запущен, выходим из пп
//
  SN=4;                                 // Задать номер экрана цикла измерения
// Задаем начальные значения цикла измерения
  CCNDT=0;                                  // Количество актов передачи-приёма в цикле измерения
  CNVB=0;                                   // Количество правильно переданных байтов в цикле измерения
  CNBB=0;                                   // Количество сбойных байтов в цикле измерения
  ScreenOutput ();                          // Вывести экран цикла измерения
//
// Задаем параметры для работы библиотеки SPI
  SPI.setBitOrder(MSBFIRST);                // Первым выводить старший разряд, т.к. у нас на приеме просто сдвиговый регистр
// Задаем режим работы Data Mode
  switch (Param[1]){
    case 0:
    SPI.setDataMode(SPI_MODE0);
    break;
    case 1:
    SPI.setDataMode(SPI_MODE1);
    break;
    case 2:
    SPI.setDataMode(SPI_MODE2);
    break;
    case 3:
    SPI.setDataMode(SPI_MODE3);
    break;
  }
// Задаем значение делителя частоты
  switch (Param[2]){
    case 0:
    SPI.setClockDivider(SPI_CLOCK_DIV2);
    break; 
    case 1:
    SPI.setClockDivider(SPI_CLOCK_DIV4);
    break;
    case 2:
    SPI.setClockDivider(SPI_CLOCK_DIV8);
    break; 
    case 3:
    SPI.setClockDivider(SPI_CLOCK_DIV16);
    break; 
    case 4:
    SPI.setClockDivider(SPI_CLOCK_DIV32);
    break; 
    case 5:
    SPI.setClockDivider(SPI_CLOCK_DIV64);
    break; 
    case 6:
    SPI.setClockDivider(SPI_CLOCK_DIV128);
    break; 
  }

    SPI.begin();                 // Запустить библиотеку SPI
    
  for (CCNDT=1; CCNDT<=Param[0]; CCNDT++)
  {
  BytePreparation ();                       // Подготовить значение байта для передачи
  ByteTRC ();                               // Передать байт, принят байт и сравнить переданный и принятый байты
  if (Byte_CMP == 1) CNVB++;                // Если байт принят правильно, инкрементировать счетчик правильно переданных байтов
  else CNBB++;                              // Если байт принят неправильно, инкрементировать счетчик сбойных байтов
  lcd.setCursor(0, 1);           // Для экрана измерения выводим строку данных
  lcd.print(CCNDT);              // "Всего"
  lcd.setCursor(5, 1);           // Для экрана измерения выводим строку данных
  lcd.print(CNVB);               // "Годных"
  lcd.setCursor(11, 1);          // Для экрана измерения выводим строку данных
  lcd.print(CNBB);               // "Сбойных"
  }
  StartON=0;                     // Флаг запуска цикла измерения. =0 - измерение завершено
  CCNDT=Param[0];                // Выравниваем значение для правильной индикации после присвоений в цикле
  SPI.end();                     // Останавливаем библиотеку SPI
}
// ***********************************************************

// ******** Подпрограмма подготовки содержимого байта на передачу **************
void BytePreparation ()           // Подпрограмма подготовки содержимого байта на передачу 
{
  Byte_TX = B01010101;            // Чтобы было поровну нулей и единиц. Вот так захотел
  
}
// ******************************************************

// ********* Подпрограмма передачи, приема и сравнения байт ********************
void ByteTRC ()                   // Подпрограмма передачи, приема и сравнения байт 
{
  SPI.transfer(Byte_TX);                   //   Передаем один байт
  Byte_RX=SPI.transfer(Byte_TX);           // Принимаем один байт
//
  if (Byte_RX == Byte_TX) Byte_CMP = 1;    // Если переданный и принятый байты совпадают, взвести флаг
  else Byte_CMP = 0;                       // если не совпадают, то флаг сбросить
}
// **************************************************************************



// *********** ОСНОВНАЯ ПРОГРАММА *******************************************

void setup()
{
  lcd.begin(16, 2);              // Запуск библиотеки ЖКИ
  analogWrite(10, 150);          // яркость подсветки экрана

// Задание режимов пинов ввода-вывода
pinMode(pinMOSI, OUTPUT);  
pinMode(pinSCK, OUTPUT); 
pinMode(pinMISO, INPUT); 
pinMode(pinSS, OUTPUT); 
pinMode(pinMISO, INPUT_PULLUP);          // Включим встроенный подтягивающий резистор на входной линии

//  Задание начальных зачений таймеров автоповторов кнопок
  for (byte i = 0; i <= KeyMaxIndex; i++) { // Задаем начальные значения и значения
    KeyTimer[i] = 2;                        // таймеров автоповтора кнопок
    KeyTimerValue[i] = 2;                   // в мс*KeyTimerClock.
  }
// Вывести экран приветствия
  lcd.clear();            
  lcd.print("   SPI Lines"    );
  lcd.setCursor(0, 1);
  lcd.print(" Arduino-Tester ");
  delay(3000);

  ScreenOutput (); 
};



void loop()
{
 
  KeyInput ();           // Читаем кнопки
  KeyProcessor ();       // Формируем признаки нажатия, отпускания и долгого нажатия кнопок
  ScreenMenu ();         // Выводим экранные меню
  ParamInput ();         // Вводим значение параметров
  MeasurementCycle ();   // Цикл измерения

 }
[/code]

Особенности программы
Интерфейс данной версии программы англоязычен. Это решение вынужденное, и тому есть  несколько причин:
    • использование наиболее распространенного и наиболее дешевого дисплея без кириллических символов (Делать ему  «кривоколенную» русификацию я посчитал в данном случае излишним);
    • параметры библиотеки SPI определены в англоязычном варианте. Делать их перевод — только запутывать пользователя;
    • меню очень простое и короткое.

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

Основная программа
Основная программа, крутящаяся в цикле LOOP представляет собой всего несколько строк. Это вызовы необходимых подпрограмм.

void loop()
{
  KeyInput ();                                // Читаем кнопки
  KeyProcessor ();                        // Формируем признаки нажатия, отпускания и долгого нажатия кнопок
  ScreenMenu ();                          // Выводим экранные меню
  ParamInput ();                            // Вводим значение параметров
  MeasurementCycle ();               // Цикл измерения   
 }

Программа разбита на четкие функциональные задачи. Каждая задача решается своей отдельной подпрограммой.

Обмен данными между подпрограммами осуществляется через глобальные переменные, объявленные в начале текста программы.

Много глобальных переменных не всегда хорошо, но в данном случае их не «вагон», запас памяти для данной задачи в МК большой. В начале программирования общая структура программы ещё недостаточна ясна. Глобальные переменные позволяют в этом случае писать и отлаживать отдельные фрагменты кода, не заботясь об области действия переменных. Когда будет получен полностью функциональный код, его можно будет начать «прилизывать» и убрать «местные», локальные переменные внутрь использующих их функций.  Тут нужно быть весьма внимательным. Может оказаться, что после такого переноса нужные переменные станут в каких-то случаях недоступны, а при тестировании вы эту ситуацию пропустите, «ведь раньше всё работало». Если нет прямых показаний на такие действия в виде нехватки памяти или улучшения читаемости программы, то оставьте, как есть. Я поступил именно так. Также рекомендую всегда проводить явное присваивание начальных значений всем объявленным переменным.

Разделение подпрограмм по задачам достаточно очевиден и чёток.

KeyInput ()
Драйвер клавиатуры. Он однозначно связан с аппаратной частью. Его задача — опросить клавиатуру, определить, какая клавиша нажата, заполнить массив признаков нажатия клавиш и… всё. Возьмёте другую «клаву» (шилд) или в этой кнопки перепаяете — замените подпрограмму драйвер. Всё остальное можно не трогать.

В конкретном случае есть некоторые ньюансы. В используемом экранном модуле клавиатура собрана по схеме рис. 2.


Рис. 2.  Клавиатура экранного модуля Arduino. Схема электрическая принципиальная.

Особенность схемы в том, что определяется нажатие только одной кнопки. Каждая верхняя по схеме кнопка «глушит» нижние. Для нашей задачи этого достаточно. Пять кнопок позволят нам двигаться по меню, вводить значения параметров и осуществлять выбор режима работы. Для ввода в Ардуино значения напряжения, соответствующего нажатой кнопке,  используется жестко заданный пин аналогового ввода А0.

Основа текста драйвера была заимствована мною лет пять назад на просторах Интернета: LCD KeyPad Shield For Arduino SKU: DFR0009 https://www.dfrobot.com/wiki/index.php?title=LCD_KeyPad_Shield_For_Ardui...

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

На практике оказалось, что нажатие кнопки RIGHT частенько определялось как срабатывание кнопки UP. Я пробовал бороться с этим троекратным считыванием с аналогового входа и вычислением среднего. Мало помогло. Ситуация не исправилась полностью, но заметно улучшилась при более тщательном подборе порога определения клавиши  RIGHT. На этом и остановился.

Для пробы взял экранный модуль другого производителя. Там подглючивала другая кнопка и по-другому. Модульность общей программы позволяет побороться с этими неприятностями программным способом на уровне драйвера, не затрагивая других частей общей программы. Так и придётся сделать, если уровень неприятностей будет мешать работать с устройством. Но это отдельная история.

KeyProcessor ()
Драйвер клавиатуры выдаёт наружу только текущее состояние кнопок. Нам же нужно определить факт нажатия, долгого нажатия и отпускания кнопки. Факт нажатия (передний фронт) кнопки нужен для движения по меню и однократного изменения значения вводимого параметра. Долгое нажатие нужно для перехода в режим автоувеличения или автоуменьшения значения параметра, а отпускание — бонусом. Бывает полезно.

KeyProcessor (), сравнивая текущее и предыдущее состояния кнопки, как раз и выдаёт нам по каждой кнопке признаки: кнопку нажали, кнопку нажали долго (более заданного времени, здесь — 0,5 с), кнопку отпустили. Обработчик был рассчитан на работу с дискретной клавиатурой, где клавиши можно было нажимать независимо и в любых сочетаниях. Здесь такой код избыточен, но он хорошо показал себя в предыдущих решениях, поэтому и был оставлен без изменений.

ScreenMenu ()
Эта подпрограмма выводит кранное меню. Точнее, текстовые заготовки содержимого экранов для ввода параметров, запуска измерения и результатов измерения. Тексты заранее заготовлены для 1-й и 2-й строк отдельно в виде двух раздельных строковых массивов.

Нажатие на кнопки UP и DOWN вызывает смену экранов в одном или другом направлении. Смена экранов идёт по кругу. Ввиду малости количества экранов долгое нажатие на кнопки  UP и DOWN не отрабатывается.

ParamInput ()
Подпрограмма непосредственно позволяет вводить численные значения параметров процесса измерения. В поле ввода индицируется текущее значение вводимого параметра. Это значение можно увеличивать или уменьшать на единицу коротким нажатием кнопок  RIGHT или LEFT соответственно. При нажатии на эти кнопки более 0,5 с начинается процесс автоувеличения или автоуменьшения вводимого параметра. Он продолжается до момента отпускания кнопки или до достижения параметром предельно допустимых значений. Поскольку диапазон значений количества измерений в цикле многократно превышает диапазоны изменения других параметров, а механизм ввода значений у них один и тот же, то для сокращения времени ввода значения количества измерений при его автоувеличения или автоуменьшения принимается шаг изменения, равный 50. Значения шага задается в программе явным образом и может быть легко изменено для каждого из параметров.

Значения параметра Clock Divider меняются не непрерывно, а в соответствии с множеством его возможных значений согласно описания библиотеки SPI: 2, 4, 8, 16, 32, 64, 128.

MeasurementCycle ()
Подпрограмма MeasurementCycle () реализует цикл измерений согласно заданных ранее параметров. Первоначально значения библиотечных параметров переводятся из условной численной формы в вид, определённый в библиотеке SPI, и инициализируются.

Параметр SPI.setBitOrder() всегда имеет значение  MSBFIRST, поскольку наше Slave-устройство заранее известно и неизменно для всех измерений. И это просто сдвиговый регистр. По-другому тут никак.

После инициализации параметров библиотека запускается. Проводится указанное количество передачи и приема байт, подсчитывается количество правильно принятых и ошибочно принятых байт. Всё это выводится на экран.

Внутри цикла измерений вызывается подпрограмма BytePreparation (). Она задаёт значение тому байту, который будет передаваться в данный момент. У меня это единственное присвоение байту значения, состоящего из чередующихся нулей и единиц. И стоило из-за одного оператора огород городить? Если будет нужда или желание устроить более серьезные испытания, применив «бегущий ноль», «бегущую единицу» и ещё более хитроумные алгоритмы формирования данных для передачи, то это можно будет сделать внутри подпрограммы BytePreparation () никак ни затронув остальные части программы.

Во время цикла измерений опрос клавиатуры не производится, чтобы не тратить на это  время. Поэтому, если появилось желание прервать процесс измерений, то нужно нажать кнопку RESET и перезагрузить МК.

Если цикл измерений завершился штатным образом, клавиатура вновь оживает, экраны меню можно пролистывать, вводить новые параметры. При этом результаты прошедшего измерения не пропадают и их можно увидеть, долистав до соответствующего экрана.

Полученные измерения бесследно исчезнут только в случае выключения/перезагрузки МК или запуска нового цикла измерений, который происходит после нажатия кнопки SELECT на экране запуска измерений.

 

Duino A.R.
Offline
Зарегистрирован: 25.05.2015

Структура устройства
На рис. 1 приведена структурная схема устройства в максимальной версии. Эта максимальная версия весьма желательна как раз для тех случаев, когда линия передачи данных «длинная». Т.е. малопонятный кабель значительной (до десятков метров) длины, у которого явным образом сказываются индуктивность, емкость и омическое сопротивление, мешающие прохождению наших импульсных сигналов.

Что в этом варианте относится к максимуму, а что к необходимому минимуму? Минимум — это Master-устройство  и Slave-устройство. Максимум получится, если добавить довесок из кабельных усилителей и кабельных приемников с обеих сторон линии связи. Для чего они нужны?
Первая задача — гальванически изолировать кабель и от  Master-устройства,  и от Slave-устройства. Вокруг нас огромное количество источников электромагнитных излучений. Буквально все электрические, электромеханические и электронные устройства. Все они излучают помехи. Прикоснитесь пальцем ко входу усилителя низкой частоты. В колонках будет стоять рёв. И это самый безобидный случай. Чем больше мощность устройства, тем больше от него помех. Особенно, если оно импульсное. Длинный кабель — прекрасная антенна. Как только подсоединить такую антенну к высокоомному сигнальному входу МК, о сигнале можно забыть. Даже если далеко от МК вывести только общий провод и то можно нахватать помех. Помеховая обстановка вещь очень специфическая. Борьба с помехами обычно ведется непосредственно по месту методом «Профессора Тыкова». Описываемое в статье устройство один из приборов, помогающих оценить влияние помех и найти эффективный способ борьбы с ними. Гальваническая изоляция передатчика и приемника от кабеля линии передачи данных это как мытьё рук перед едой. 100% гарантий не даёт, а профилактически очень хорошо действует. Правда, в отличие от мытья рук, требует существенно больших усилий по реализации.

Вторая задача — обеспечить необходимую мощность сигнала в кабеле. Чем выше мощность сигнала, тем сложнее помехе его перешибить. Плюс мощность нужна, чтобы быстро перезаряжать паразитные для нас емкости кабеля и продавливать током паразитные же для нас кабельные индуктивности. У обычных цифровых микросхем мощности на это не хватает. Поэтому приходится использовать дополнительные усилители. Конкретных схем таких усилителей огромное множество от самых простых любительских до вполне навороченных профессиональных. Какая из них подойдет в конкретном случае можно определить только практикой.

Всегда ли нужны такие «страсти»? Нет. Вполне может оказаться, что на относительно малых расстояниях, отсутствии мощных источников помех и невысоких требованиях к скорости передачи данных можно обойтись минимальными средствами. Рассмотрим конкретный пример.

Минимальная схемная реализация
Для первых практических опытов будем использовать схему устройства в минимальном варианте, представленном на рис. 3

Рис. 3.  Arduino-тестер длинных линий  интерфейса SPI. Схема электрическая принципиальная. Минимальный вариант.

Здесь нет никаких кабельных усилителей и приемников. Выводы Master-устройства (МК) и Slave-устройства (74HC165) соединены напрямую. Только сигнальные входы подтянуты к плюсу питания через резисторы сопротивлением 1 кОм. Резисторы помогут избежать неприятностей, когда мы будем подключать и отключать кабели.

Чтобы лучше понимать физику процесса, подключим вход осциллографа к тактовому входу сдвигового регистра  74HC165  Slave-устройства. Тактовые импульсы самые короткие, и неприятностей, в первую очередь, следует ожидать именно с ними.

Slave-устройство небольшое, простое. Для удобства соберем его на макетной плате без пайки.

По имеющимся возможностям подберем для экспериментов разные кабели. Первый вариант -это просто короткие провода с наконечниками. Их часто называют DUPON DIY. Второй и третий варианты это обычные Ethernet витые пары 5-й категории разной длины. В данном эксперименте из витых пар используются только по одному проводу. Второй провод витой пары остаётся неподключенным. Для эксперимента выбрали худший вариант, как двойная телефонная «лапша».

 

Duino A.R.
Offline
Зарегистрирован: 25.05.2015

Результаты экспериментов
Результаты экспериментов для нескольких сочетаний тип кабеля/длина кабеля/значение Clock Divider приведены на рис. 4, рис. 5, рис.6.

Рис. 4. Осциллограммы напряжения на входе CLK (пин 2) сдвигового регистра Slave-устройства. Провода DUPON DIY 0,2 м.

Рис. 5. Осциллограммы напряжения на входе CLK (пин 2) сдвигового регистра Slave-устройства. Провода - витая пара категории 5, 1,0 м.

Рис. 6. Осциллограммы напряжения на входе CLK (пин 2) сдвигового регистра Slave-устройства. Провода - витая пара категории 5, 12 м.

В данных экспериментах никаких, даже простых способов улучшения ситуации не проводилось. Это делалось сознательно. Важно было понять, на что можно надеяться, просто подключив «пин к пину». В известном смысле, такой подход лежит в русле общей идеи Ардуино.

Также хотелось оценить эффективность разработанного устройства. Помогает ли оно на практике решить поставленные задачи по выбору параметров устойчивой работы интерфейса SPI? На мой взгляд, полученные результаты достаточно красноречивы. «Сработала, машинка то...»

Максимальный вариант устройства требует уже существенно больших усилий в реализации. Применить с приемлемым результатом детали, просто взятые наугад из коробки, может не получиться. Прежде всего, это касается оптронов. Например, горячо любимые в Ардуино-проектах за распространенность и дешевизну оптроны PC817 даже по паспортному быстродействию «очень не очень». Максимальное время нарастания и спада импульса может доходить до 18 мкс. В моих опытах время спада оказалось и того хуже. Даже, если предположить типовые значения времен нарастания и спада по 4 мкс и 3 мкс соответственно и совсем ничего не оставить на «полку» тактового импульса, то мы не укладываемся даже в самую низкую частоту тактовых импульсов, которые можно получить в библиотеке SPI.

Что, максимальный вариант оказался только «в принципе»? Нет. Его тоже вполне можно реализовать. Самый простой способ — понизить частоту тактовых импульсов до того уровня, когда «тормознутость» оптронов широкого применения перестанет сказываться. Здесь библиотекой SPI пользоваться уже нельзя. Драйвер придется писать самим. Утешает то, что драйвер несложный, и что писать его можно с применением привычных конструкций языка «С++ от Ардуино»: digitalWrite() и digitalRead(), поскольку о быстродействии речи не идет. Ещё и задержки придется ставить.

Второй способ реализации максимальной версии устройства, без категорической потери быстродействия — применить быстродействующие оптроны и проверенное схемотехническое решение. Этот путь явно сложнее и воспроизводимость его в любительских условиях может оказаться не легче, чем использование, например, связки RS-485/Modbus RTU.

А есть ли еще варианты кроме максимального и минимального? Да, есть. Начнем с того, что при использовании витой пары второй провод в паре лучше подключить к общему проводу устройства, часто ошибочно называемого «землей». Причем результат может оказаться разным при подключении к общему проводу Master-устройства, Slave-устройства или обоих одновременно. Если уровень импульсных помех по трассе кабеля ожидается терпимым, то можно попробовать обойтись без оптронов, подгрузив сигнальные линии с двух сторон кабеля резисторами, поставив транзисторные ключи в качестве кабельных усилителей, а входы Master- и Slave-устройств отделить от кабельной линии триггерами Шмитта в интегральном исполнении.

И снова пробовать по месту. Универсальных решений нет. Ардуино-тестер в помощь!
 

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

Duino A.R. пишет:

В статье предлагается устройство для проверки прохождения данных в длинных линиях интерфейса SPI.

Duino A.R. пишет:

    3. В качестве Slave-устройства в базовой версии используется ИМС 74HC165.

А Вы уверены, что 74HC165 - это SPI?

Нет, протокол похожий, но, насколько я помню, 74HC165 не способен отпускать шину, а потому его использование параллельно с другими устройствами вызывает проблемы. И эти проблемы имеют непосредственное отношение к функционированию "длинных линий" (кстати, не уверен в корректности используемой терминологии). Мне кажется, для объективности следовало бы использовать:

- не менее двух устройств. Использование шины, предназначенное для подключение множества устройств, для единственного устройства вряд ли может быть показательным.

- устройства, которые реализуют SPI достаточно строго. По меньшей мере, используют оба сигнала MOSI и MISO, а также могут параллельно работать на одной шине.

Могу порекомендовать в качестве устройств для примера SD карту и SPI дисплей.

NikShel
Offline
Зарегистрирован: 21.01.2018

Подписываюсь

Duino A.R.
Offline
Зарегистрирован: 25.05.2015

andriano пишет:

А Вы уверены, что 74HC165 - это SPI?

Это частный случай простейшей аппаратной эмуляции Slave-устройства, служащей одной единственной цели - проверить прохождение сигнала в конкретном кабеле с конкретными кабельными драйверами, если таковые есть. Библиотека SPI (наш рабочий эталон) работает с 74HC165 штатным образом. Для решения указанной задачи такое использование корректно.

andriano пишет:
Нет, протокол похожий, но, насколько я помню, 74HC165 не способен отпускать шину, а потому его использование параллельно с другими устройствами вызывает проблемы.

Здесь никаких других устройств нет. Чтобы проверить кабельную сеть, 74HC165 последовательно ставится на место каждого устройства и осуществляется проверка.

andriano пишет:
И эти проблемы имеют непосредственное отношение к функционированию "длинных линий" (кстати, не уверен в корректности используемой терминологии).

Я написал, что имею ввиду: "«Длинная линия» - это такое состояние проводов, когда их уже нельзя рассматривать, как на схеме, идеальным проводником с нулевым сопротивлением."
У меня линия связи - это кабель, который для "настоящего" SPI с его настоящими частотами и длинами не используют. Т.е. это случай относящийся к "начинающему самодельщику", которому надо вынести регистры-расширители портов МК на десяток другой метров от МК.

andriano пишет:
Мне кажется, для объективности следовало бы использовать:

- не менее двух устройств. Использование шины, предназначенное для подключение множества устройств, для единственного устройства вряд ли может быть показательным.

- устройства, которые реализуют SPI достаточно строго. По меньшей мере, используют оба сигнала MOSI и MISO, а также могут параллельно работать на одной шине.

Могу порекомендовать в качестве устройств для примера SD карту и SPI дисплей.

"Настоящие" SPI-устройства, использующие "настоящий" SPI-протокол, в частности, рекомендованные Вами SD-карта и SPI-дисплей, располагаются на шильде, буквально воткнутым в плату Ардуино. Тут проблемы если и возникают, то совсем другие. Я рассматриваю случай: "А что делать, если у калитки кодовый замок с клавиатурой из 16 клавиш, Ардуина в теплом доме (что совершенно оправдано), а между домом и калиткой метров эдак 15 по дорожке и 25 — 30 — по кабелю? Устраивать  из этого «кусочек АСУ ТП» как то «не оно», да, честно говоря, и некому."

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

Duino A.R. пишет:

Это частный случай...  Библиотека SPI (наш рабочий эталон) работает с 74HC165 штатным образом. Для решения указанной задачи такое использование корректно.

...

Я рассматриваю случай: "А что делать, если у калитки кодовый замок с клавиатурой из 16 клавиш, Ардуина в теплом доме (что совершенно оправдано), а между домом и калиткой метров эдак 15 по дорожке и 25 — 30 — по кабелю? Устраивать  из этого «кусочек АСУ ТП» как то «не оно», да, честно говоря, и некому."

Ну так если Вы рассматриваете лишь частный случай (причем, устройством, не поддерживающим протокол SPI), то и пишите о 74HC165, а не об SPI. А если хотите писать именно об SPI, то следует использовать устройство, работающее именно по этому протоколу.

Duino A.R.
Offline
Зарегистрирован: 25.05.2015

andriano пишет:
Ну так если Вы рассматриваете лишь частный случай (причем, устройством, не поддерживающим протокол SPI), то и пишите о 74HC165, а не об SPI.

1. В самом названии темы указано, что речь идет о тестировании длинных линий связи, а не устройств интерфейса SPI.

2. Во 2-м посту темы п. 3 я прямо указываю, что в качестве Slave-устройства использую 74HC165. А дальше подробно обосновываю этот выбор.

andriano пишет:
А если хотите писать именно об SPI, то следует использовать устройство, работающее именно по этому протоколу.

3. Библиотека SPI работает с 74HC165 без каких либо условностей, т.е. в данном случае воспринимает его как полноценное SPI-устройство.

4. Единственная "претензия", которую можно предъявить 74HC165 - неотпускание линии MISO по сигналу SS, что не дает строить из 74HC165 сеть устройств SPI по топологии "звезда" (в терминологии Википедии).

4.1. Для проверки линии связи нет необходимости формировать сеть, достаточно одного устройства. При единственном устройстве сигнал выбора устройства SS не нужен, отпускание линии MISO не нужно.

4.2. По топологии "кольцо" SPI-сеть на 74HC165 построить можно. Отпускать линию MISO там также нет необходимости. Причем это будет совершенно реальная сеть, т.к. по единственному сигналу SS во всех регистрах можно будет защелкивать в регистрах внешнюю информацию, а после выталкивать ее по последовательному каналу в мастер-устройство.

4.3. Библиотека SPI формирует один единственный сигнал SS. Даже если бы он в данном случае не был занят шилдом с дисплеем, то в случае сети сигналы выбора устройств пришлось бы формировать самостоятельно.

Предположу, что у Вас есть какие-то нерешенные технические вопросы по одновременному подключению к Ардуино нескольких "классических" SPI-устройств: дисплеев, карт памяти, ... Увидев в названии темы слово "SPI", но не найдя ответов на Ваши вопросы, расстроились. :))

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

Предполагать, конечно, Вы можете все, что угодно, но в данном случае Ваши предположения ошибочны сразу по нескольким пунктам:

- нерешенных технических вопросов, связанных с SPI, у меня в данный момент нет. Хотя, были, когда я подключал 165 регистры одновременно с другими SPI устройствами. Естественно, они уже давно разрешены, но способом, который может повлиять на функционирование "длинной линии", а именно - включением в линию MISO дополнительного резистора. Т.е., на мой взгляд, у сочетания 74HC165+"длинная линия" есть особенности, которые не позволяют использовать 74HC165 в качестве типового устройства для "длинной линии".

- естественно, каких-либо ответов в Вашей теме я искать не пытался. Более того, не стал искать в ней и другие ляпы. И-нете достаточно много [скажем так:] не слишком достоверной информации, поэтому, дабы не тратить свое время, я, исхожу из принципа "чтобы понять, что яйцо тухлое, не обязательно есть его целиком". Извините.

Duino A.R.
Offline
Зарегистрирован: 25.05.2015

andriano пишет:

- нерешенных технических вопросов, связанных с SPI, у меня в данный момент нет. Хотя, были, когда я подключал 165 регистры одновременно с другими SPI устройствами. Естественно, они уже давно разрешены, но способом, который может повлиять на функционирование "длинной линии", а именно - включением в линию MISO дополнительного резистора. Т.е., на мой взгляд, у сочетания 74HC165+"длинная линия" есть особенности, которые не позволяют использовать 74HC165 в качестве типового устройства для "длинной линии".

Сказали бы сразу: "Я придумал проверенный на практике способ, как с помощью одного резистора превратить "неправильный" 74HC165 в "правильный", на котором можно строить SPI-сеть с топологией "звезда"." Прекрасно. Можно было бы предметно обсудить "+" и "-" этого решения и его применимость. Прямого отношения к данной теме это не имеет просто в силу постановки задачи, но, вполне возможно, что кому-то из читателей было бы полезно. "Звезда" часто оказывается удобнее "кольца", доступного "из коробки".

andriano пишет:
- естественно, каких-либо ответов в Вашей теме я искать не пытался.

Эта тема не для профессионалов, а для начинающих, желающих продолжить, о чем сразу сказано в пункте "Для кого".

andriano пишет:
Более того, не стал искать в ней и другие ляпы.

Выявление ляпов и их предметный осознанный разбор помогает другим людям их избежать в будущем. Опять же, осознав в ходе разбора причинно-следственную связь. Но... На "нет" и суда нет.

andriano пишет:
И-нете достаточно много [скажем так:] не слишком достоверной информации, поэтому, дабы не тратить свое время, я, исхожу из принципа "чтобы понять, что яйцо тухлое, не обязательно есть его целиком". Извините.

Взрослый человек сам решает, что ему делать или не делать, на что тратить свое время, а на что - нет. Это - Ваш выбор.