Урок 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;
}
}

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

Эта программа ничего не делает. В ней реализован только модуль управления обменом данными. Цикл 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);
}

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

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

 

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

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

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

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

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

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

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

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

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

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

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

 

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

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

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

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