Урок 49. Обмен данными между платами Ардуино через интерфейс UART.

Обмен данными между платами Ардуино

Создадим из 2 плат Ардуино простую распределенную систему сбора и отображения информации. Разработаем центральный контроллер и свяжем его с локальным контроллером из предыдущего урока.

Предыдущий урок     Список уроков     Следующий урок

В предыдущем уроке мы разработали локальный контроллер на базе платы Ардуино. Мы подключили его к персональному компьютеру и полностью проверили его работу. Теперь, создадим систему без применения компьютера. В качестве устройства отображения данных, принятых от локального контроллера будем использовать центральный контроллер, также собранный на базе платы Ардуино.

 

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

 

Разработка схемы центрального контроллера.

Что должен делать центральный контроллер?

  • Обеспечивать обмен данными с локальным контроллером.
  • Отображать данные, полученные от локального контроллера:
    • температуру;
    • напряжение;
    • состояние кнопки.
  • Передавать данные на локальный контроллер, в нашем случае управлять светодиодом локального контроллера.
  • Контролировать состояние обмена. Мы проверяем работу сети и программы, поэтому необходимо фиксировать каждую ошибку обмена.

Для осуществления этих функций достаточно подключить к плате Ардуино LCD дисплей и кнопку. На дисплее будут отображаться все параметры системы, а с помощью кнопки можно управлять светодиодом локального контроллера. Учитывая, что этот центральный контроллер мы будем использовать в последующих уроках в системах с несколькими локальными контроллерами, я решил подключить 3 кнопки.

Схема центрального контроллера выглядит так.

Схема подключения LCD дисплея и кнопок

В качестве ЖК дисплея я использовал WH2004A. У индикатора 4 строки по 20 символов.  Подключил я его в 4 битном режиме. Схема аналогична схеме из урока 23. В этом же уроке можете посмотреть информацию о LCD индикаторах, подключении их к микроконтроллерам и о программировании дисплеев такого типа.

Собранная система из двух контроллеров у меня выглядит так.

Система обмена данными между платами Ардуино

Центральный контроллер я собрал на базе платы Arduino UNO R3. Запитал его стандартным кабелем от USB порта компьютера. Для питания  локального контроллера использовал сигнал 5V платы Arduino UNO R3. В рабочем варианте можно отключить плату от компьютера, подав питание каким-либо другим способом.

 

Разработка резидентной программы центрального контроллера.

Опять обязательное условие – управление обменом данными с локальным контроллером должно происходить параллельным процессом.

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

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

Определим интерфейс между основной программой и модулем обмена данными по сети. Достаточно трех компонентов:

byte dataFromLC[10]; // буфер данных для приема из локального контроллера
byte dataToLC; // данные для передачи в локальный контроллер (светодиод)
byte stateCommun; // состояние обмена
// (0 - успешно закончено, 1 - пуск, 2 - продолжается, 3 - ошибка)

Последовательность работы следующая:

  • В переменную dataToLC мы кладем данное для передачи на локальный контроллер. У нас имеет значение только младший бит – состояние светодиода локального контроллера.
  • В переменную stateCommun загружаем число 1, что инициирует процесс обмена данными.
  • Ждем когда stateCommun примет значения 0 или 3. 0 означает, что обмен данными закончился успешно, 3 - сообщает об ошибке обмена.
  • Используем полученные из локального контроллера данные, которые оказываются в массиве dataFromLC[].

Я сразу приведу скетч программы управления обменом через интерфейс UART.

#include <MsTimer2.h>

#define TIME_OUT 25 // время тайм-аута ответа (мс)

byte dataFromLC[10]; // буфер данных для приема из локального контроллера
byte dataToLC; // данные для передачи в локальный контроллер (светодиод)
byte stateCommun; // состояние обмена
// (0 - успешно закончено, 1 - пуск, 2 - продолжается, 3 - ошибка)

byte timeOutCount; // счетчик времени тайм-аута обмена

void setup() {
MsTimer2::set(1, timerInterrupt); // прерывание по таймеру 1 мс
MsTimer2::start(); // разрешаем прерывание
Serial.begin(9600); // 9600 бод
}

void loop() {
}

//-------------------------------------- обработчик прерывания 1 мс
void timerInterrupt() {

//-------------------- обмен данными
timeOutCount++; // счетчик тайм-аута ответа

if ((stateCommun == 0) || (stateCommun == 3)) {
// обмен закончен без ошибкой или с ошибкой
// ожидание следующего пуска
timeOutCount= 0;
}

else if (stateCommun == 1) {
// пуск обмена, посылка команды
Serial.write(0x10); // код операции
Serial.write(dataToLC); // данные (светодиод)
Serial.write(0x10 ^ dataToLC ^ 0xe5); // контрольный код
stateCommun= 2; // переход на ожидание ответа
}

else if (stateCommun == 2) {
// ожидание ответа от локального контроллера
if (Serial.available() == 12) {
// принято 12 байтов
// чтение данных в буфер, проверка контрольной суммы
byte buf[10];
unsigned int sum= 0; // контрольная сумма
for (int i=0; i<10; i++) {
buf[i]= Serial.read();
sum += buf[i];
}
sum ^= 0xa1e3;
if ( ((* ((byte *)(& sum))) == Serial.read()) && ((* (((byte *)(& sum)) + 1)) == Serial.read())) {
// контрольная сумма правильная
// перегрузка принятых данных в dataFromLC
for (int i=0; i<10; i++) {
dataFromLC[i] = buf[i];
}
stateCommun= 0; // операция успешно закончена
}
else {
// контрольная сумма неправильная
// очистка буфера порта
while (true) { if (Serial.read() == 0xffff) break;}
stateCommun= 3; // окончание операции с ошибкой
}
}
else {
// проверка тайм-аута
if (timeOutCount > TIME_OUT) {
// ошибка тайм-аута (ответ не пришел)
// очистка буфера порта
while (true) { if (Serial.read() == 0xffff) break;}
stateCommun= 3; // окончание операции с ошибкой
}
}
}
else {
// сбой программы
stateCommun= 3;
}
}

Скетч можно загрузить по ссылке:

Зарегистрируйтесь и оплатитеВсего 60 руб. в месяц за доступ ко всем ресурсам сайта!

Эта программа ничего не делает. В ней реализован только модуль управления обменом данными. Цикл loop() пустой, основной программы нет.

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

  • Модуль управления обменом данными вызывается в обработчике прерывания по таймеру с периодом 1 мс.
  • В нем проверяется состояние переменной stateCommun. Если его значение равно 1, то формируется команда обмена.
  • Дальше ожидается приход 12 байтов ответа.
  • Если в течение времени тайм-аута, заданном в константе TIME_OUT, ответ не приходит, то порт сбрасывается и в переменную stateCommun загружается число 3, признак ошибки.
  • Если все 12 байтов пришли, то проверяется контрольная сумма и данные перегружаются в массив dataFromLC[].

 

Основная программа контроллера.

Я посчитал, что основная программа центрального контроллера должна отображать:

  • текущее число циклов обмена;
  • число ошибок обмена;
  • значение температуры;
  • значение напряжения;
  • состояние кнопки локального контроллера.

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

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

Отображение параметров на дисплее

Я выбрал период цикла основной программы 250 мс. Т.е. 4 раза в секунду основная программа обменивается данными с локальным контроллером и выводит считанные параметры на экран дисплея.

Временные задержки основной программы я специально реализовал с помощью функции delay(). Функция подвешивает, останавливает работу программы. Тем не менее, в параллельном процессе происходит обмен данными по сети.

Основная программа работает в цикле loop().

void loop() {

// инициализация обмена с локальным контроллером
// состояние кнопки
if (button1.flagPress == true) dataToLC= 1;
else dataToLC= 0;
stateCommun= 1; // инициализация обмена
cyclCount++; // счетчик циклов

delay(50); // время на завершения обмена

if (stateCommun == 0) {
// данные успешно получены
// вывод данных на дисплей
disp.clear(); // очистка экрана
disp.print("C=");
disp.print(cyclCount);
disp.print(" E=");
disp.print(errorCount);
disp.setCursor(0, 1);
disp.print("T=");
disp.print(* ((float *)dataFromLC),1);
disp.print(" C U=");
disp.print(* ( ((float *)dataFromLC) +1 ),1);
if ( (dataFromLC[8] & 1) == 0) disp.print(" V B=F");
else disp.print(" V B=P");
}
else if (stateCommun == 3) {
// обмен завершен с ошибкой
errorCount++; // счетчик ошибок

disp.clear(); // очистка экрана
disp.print("C=");
disp.print(cyclCount);
disp.print(" E=");
disp.print(errorCount);
disp.setCursor(0, 1);
disp.print("ERROR");
}

delay(200);
}

Полностью скетч программы можно загрузить по этой ссылке:

Зарегистрируйтесь и оплатитеВсего 60 руб. в месяц за доступ ко всем ресурсам сайта!

Если данные приняты с ошибкой, то на дисплей выводится сообщение ”ERROR”. Это не совсем правильно. В рабочих программах необходимо при ошибочных данных подождать следующие, оставив на дисплее последние правильные данные. И только после нескольких в подряд ошибках обмена выводить сообщение об ошибке.

 

Проверка работы системы.

При загрузке программы в центральный контроллер из Arduino IDE необходимо отключать сигнал RX. Это связано с тем, что локальный контроллер удерживает сигнал в высоком уровне и блокирует сигнал от преобразователя интерфейсов USB-UART. Компьютер не в состоянии передать данные на микроконтроллер.

Другой способ – в момент загрузки программы в центральный контроллер удерживать кнопку ”RESET” локального контроллера в нажатом состоянии. Все выводы микроконтроллера локального контроллера перейдут в состояние входов, и сигнал RX не будет блокироваться.

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

Отображение параметров на дисплее

У меня не появилось ни единой ошибки обмена.

Дальше я нажал кнопку ”RESET” локального контроллера и убедился, что на дисплее отображается ошибка.

Индикация ошибки обмена

Отпустил кнопку “RESET” – ошибка обмена исчезла, обмен возобновился.

Индикация параметров системы

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

 

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

 

Предыдущий урок     Список уроков     Следующий урок

0

Автор публикации

не в сети 3 дня

Эдуард

279
Комментарии: 1936Публикации: 197Регистрация: 13-12-2015

17 комментариев на «Урок 49. Обмен данными между платами Ардуино через интерфейс UART.»

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

    0
    • Здравствуйте!
      Используйте другой протокол, в котором число данных в пакете может меняться, например ModBus.

      0
  2. Очень интересные и познавательные уроки, почерпнул из них много нового и полезного. Никак не разберусь с указателями, работой с буфером и переменными float. Прошу помочь. Ситуация такая: Протокол передачи у меня выглядит так:
    0 — byte — признак данных (0xAA — данные с внутренних датчиков, 0x0F — данные с внешних датчиков)
    1-4 — float — данные 1-го датчика
    5-8 — float — данные 2-го датчика
    9-12 — float — данные 3-го датчика
    13-16 — float — данные 4-го датчика
    17,18 — контрольная сумма
    вопрос в следующем: как из этого массива данных получить значение переменных float?
    так будет правильно?:
    temperature1 = *((float*)dataRecvBuf)+1;
    humidity = *((float*)dataRecvBuf)+5;
    temperature2 = *((float*)dataRecvBuf)+9;
    pressure = *((float*)dataRecvBuf)+13;

    0
    • Здравствуйте! Спасибо.
      dataRecvBuf это указатель на данные типа byte. Если вы отсчитываете данные в байтах, значит смещение должны прибавлять к нему.
      dataRecvBuf + 1 — указатель на начало данных temperature1.

      temperature1 = * ((float *)(dataRecvBuf + 1));
      humidity = * ((float *)(dataRecvBuf + 5));
      temperature2 = * ((float *)(dataRecvBuf + 9));
      pressure = * ((float *)(dataRecvBuf + 13));

      0
  3. Добрый день. При передачи массива 22 байтов принимающий pro mini не может поймать начало и сбиваются данные.
    Также происходит при отправке обратно 3 байтов.
    Скетч мастера. Я так думаю, что начало теряется. Пробовал
    по разному , вариантов 7 перебрал. Не выходит. Если подскажете , буду очень благодарен.
    if (Serial.available()) {

    code_beg=Serial.read();
    Resiv_Array[1]= Serial.read();
    Resiv_Array[2]= Serial.read();
    }

    if (delay_send==0 ) delay_send= millis(); // пауза

    if ((millis() > (delay_send+500)) && (code_beg==0)) {

    Serial.write(Send_Array,22);

    delay_send=0; // пауза 0

    }
    скетч slave

    if ((millis() > (delay_send+1000)) && code_beg==1) {

    Send_Array[0]=code_beg;
    Serial.write(Send_Array,3);
    code_beg=0; //код о завершении передачи
    }

    if (Serial.available()>22 && code_beg==0) {

    for (int i=0; i<=21; i++) {
    Resiv_Array[i]= Serial.read(); // массив байт
    } }
    code_beg==1; //код о завершении приёма

    0
    • Здравствуйте!
      В первых строчках некорректно принимаются входные данные.
      if (Serial.available()) {
      code_beg=Serial.read();
      Resiv_Array[1]= Serial.read();
      Resiv_Array[2]= Serial.read();
      }
      Вы проверяете, что в буфере последовательного порта есть данные и считываете 3 байта. А может там только одно данное.
      Посмотрите, как сделано в моей программе.
      Еще можете использовать библиотеку для протокола ModBus в последующих уроках. Там все просто.

      0
      • Проверил по разному. Передаю 24 байта массив. А приёмник , если не читать, Serial.available() показывает , что в буфере 6 байт. если отправить 20 байт , то приходит 5. Понять не могу , куда они пропадают.

        0
        • Чудес не бывает. Проверяйте блоками. Какие байты пропадают? Первые, последние, случайные? Сделайте на передатчике однократную передачу по кнопке и проверяйте прием. Передатчик проверьте Монитором последовательного порта на компьютере. Много чего еще можно придумать.

          0
          • А может быть причиной длина провода. У меня 1,2м.

            0
  4. Приветствую! На карантине особо заняться нечем, решил освоить Ардуинку… Можно считать — с нуля
    1. Спасибо за уроки. На данный момент освоил Т-датчики(библиотека), датчик АД(библиотека), датчик давления с АЦП по прерыванию
    2. в принципе, моделька протокола тоже работает
    не понял только это — while (true) { if (Serial.read() == 0xffff) break;}
    Если не затруднит — разжуйте , плиз…

    0
    • Здравствуйте!
      Насколько я помню, этот блок очищает буфер от принятых данных, если они там случайно появились.
      Заканчивается обмен пакетом данных. После передачи данных на ведущее устройство приемный буфер ведомого лучше очистить. Это производится чтением в цикле приемного буфера Serial, до тех пор пока не будет получен код 0xffff — признак пустого буфера.

      0
      • Спасибо.
        Смущают 2 момента…
        1. В библиотеке просто вписывается этот код при неактивном Rx
        2. Этот код не является уникальным или запрещенным. В одной из таблиц Ascii это просто «я».
        Соответственно, поиск этой комбинации в приемном буфере совсем необязательно означает окончание приема. Или я что-то недопонял?

        0
        • Здравствуйте!
          1. В буфере Serial данные хранятся бесконечно долго, до тех пор пока их не считали. Именно эти старые данные и удаляет блок.
          2. При пустом буфере, когда данных для чтения нет, функция Serial.read возвращает код 0xffff.

          0
  5. Здравствуйте Эдуард

    Какой скетч в какую Ардуино загружать?
    Или только скетч 49_2 загрузить в центральный контроллер UNO R3?

    0
    • Здравствуйте!
      49_2 это скетч центрального контроллера. А локальный контроллер был разработан в предыдущем уроке.

      0

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Нажимая кнопку "Отправить" Вы даёте свое согласие на обработку введенной персональной информации в соответствии с Федеральным Законом №152-ФЗ от 27.07.2006 "О персональных данных".