Универсальный асинхронный приёмопередатчик (Universal Asynchronous Receiver-Transmitter, UART) — один из самых популярных интерфейсов для передачи данных между электронными устройствами. Как правило все микроконтроллеры имеют встроенный узел связи UART. И микроконтроллер atmega328, на котором основана плата Ардуино, не является исключением.
Для чего используют UART? Вот несколько примеров:
- Arduino подключается к персональному компьютеру через UART и следующий за ним USB-UART мост;
- два контроллера часто соединяют между собой по UART;
- как правило, GPS модули имеют только UART интерфейс;
- bluetooth и другие радиомодули тоже часто используют UART.
Почти на каждом уроке мы практикуем передачу данных от Ардуино к персональному компьютеру для отладки программ и проверки различных датчиков. Да, мы используем при этом USB кабель, но фактически, Ардуино сначала передает данные в UART. И только потом, проходя через USB-UART мост, эти данные трансформируются в USB пакеты и улетают в компьютер.
Сегодня же мы соединим две платы Ардуино между собой непосредственно только с помощью UART интерфейса.
Список необходимых компонентов
Для выполнения всех экспериментов в этом уроке, потребуется два контроллера Arduino Uno или их любые аналоги, модуль фоторезистора, светодиод с токозадающим резистором и немного проводов.
Необходимые компоненты можно добавить в корзину прямо здесь, и затем оформить заказ в нашем интернет-магазине.
Подключение
Передача данных в интерфейсе UART осуществляется по двум проводам. При этом, обе стороны, участвующие в обмене данными, подключаются крест-накрест:
RX — от английского receiver — приёмник, а TX — tranceiver — передатчик.
Микроконтроллер atmega328 имеет один встроенный UART узел, который соединен с контактами D0 и D1. Контакт D0 соответствует линии RX, а D1 — TX. Чтобы связать две платы Ардуино между собой, соединим линии RX и TX крест-накрест, а также свяжем платы общей линией питания Gnd.
Теперь приступим к написанию программ для обоих контроллеров.
Ведущий и ведомый (master/slave)
Пусть одна плата будет отправлять каждые 500мс какие-то данные второй плате. Вторая плата будет их принимать и сигнализировать об этом изменением состояния штатного светодиода на контакте №13 (то есть мигать).
В этой задаче первая плата будет называться ведущей, или по-английски master. А вторая ведомой — то есть slave.
Программа ведущего
Для этого мы используем две функции встроенного объекта Serial:
Serial.begin(9600); // инициализирует UART со скоростью 9600 бод
Serial.write(128); // отправляет в UART один байт данных, например, число 128
Полностью программа ведущего будет выглядеть так.
void setup() {
Serial.begin(9600); // инициализируем UART со скоростью 9600 бод
}
void loop() {
Serial.write(1); // отправляем в UART число 1
delay(500); // ждём 500мс
}
Программа ведомого
Для чтения одного байта данных вызываем функцию read и присваиваем её результат переменной v:
byte v = Serial.read(); // считывает 1 байт из буфера UART
Также нам понадобится функция available(), которая возвращает количество байт принятые узлом UART. Таким образом, мы будем менять состояние светодиода только если в порт что-то пришло.
byte ledPin = 13;
byte ledState = 0;
byte v;
void setup() {
Serial.begin(9600);
pinMode( ledPin, OUTPUT );
}
void loop() {
if( Serial.available() ){
v = Serial.read(); // считываем один байт из UART
digitalWrite( ledPin, ledState ); // включаем/выключаем светодиод
ledState = !ledState; // меняем значение ledState
}
}
Загружаем программы на платы и подаем питание. Ведомая плата должна начать мигать светодиодом.
Кстати. В этом примере будет достаточно задействовать всего один из двух проводов UART интерфейса. Мы передаем данные только в одном направлении: от ведущего к ведомому, поэтому можем оставить провод, соединяющий RX ведомого и TX ведущего.
В следующем примере попробуем передать что-то более полезное, чем просто число 1.
Передача показаний датчика
Пусть теперь первая плата Ардуино считывает значение датчика освещенности на основе фоторезистора и передает эти показания в UART. Вторая плата будет принимать данные, и менять яркость светодиода согласно полученному значению.
Для этого примера к ведущей плате подключим датчик по схеме из нашего урока. А к ведомой плате — светодиод к любому контакту с ШИМ выходом (для Arduino Uno это контакты: 3,5,6,9,10,11). ШИМ нам понадобится чтобы плавно менять яркость светодиода функцией analogWrite.
Программа ведущего
byte photoPin = A0;
void setup() {
Serial.begin(9600);
pinMode(photoPin, INPUT);
}
void loop() {
int val = analogRead(photoPin);
val = val*255/1023.0;
Serial.write(val);
delay(100);
}
В этой программе мы считываем значение АЦП, к которому подключен фоторезистор, с помощью функции analogRead. Вот только проблема в том, что в UART мы можем передать только один байт, а analogWrite возвращает нам число от 0 до 1023. Чтобы обойти эту неприятность, просто нормируем большое число в диапазон от 0 до 255. Делается это формулой:
val = val*255/1023.0;
Программа ведомого
byte ledPin = 3;
void setup() {
Serial.begin(9600);
pinMode( ledPin, OUTPUT );
}
void loop() {
if( Serial.available() ){
byte v = Serial.read();
// передаем полученное из UART значение в функцию analogWrite
analogWrite( ledPin, v);
}
}
Загружаем программы на контроллеры и подаем питание. Для проверки закрываем фоторезистор рукой и наблюдаем за поведением светодиода, он должен менять яркость.
Как уже было замечено, в этой программе нам пришлось огрубить показания датчика, иначе они не влазили в UART. Эта проблема решается разложением числа на байты и последовательной передачей этих байт в UART (или любой другой последовательный интерфейс). Например, так:
int val = analogRead(photoPin);
Serial.write(val & 0xFF); // передаем младший байт числа val
Serial.write((val>>8) & 0xFF); // передаем старший байт числа val
В программе ведомого, соответственно, нужно будет поймать эти два байта и склеить, например, так:
if( Serial.available()>1 ){
byte v1 = Serial.read();
byte v2 = Serial.read();
int v = v1 | (v2<<8);
}
Но это не тема нашего урока. Лучше попробуем передавать данные в оба направления!
Передача в обоих направлениях
Загрузим на оба контроллера две одну и ту же программу, которая будет и отправлять данные и принимать их.
byte ledPin = 13;
byte ledState = 0;
byte v;
unsigned long tm, send_next;
unsigned int send_to = 500; // отправка каждые 500мс
void setup() {
Serial.begin(9600);
pinMode( ledPin, OUTPUT );
}
void loop() {
// проверка наличия данных в буфере UART
if( Serial.available() ){
v = Serial.read();
digitalWrite( ledPin, ledState );
ledState = !ledState;
}
// отправка данных по таймауту
tm = millis();
if( tm > send_next ){
send_next = tm + send_to;
Serial.write('A');
}
}
После подачи питания, оба контроллера начнут мигать светодиодами, что означает — данные идут в оба направления!
Ради эксперимента, можно на ходу отключить один из проводов UART интерфейса, соответствующий контроллер перестанет получать данные и мигать.
Вот и всё. Теперь можно соединять Ардуино между собой и необязательно проводами. Например, тот же Bluetooth модуль или мощные радиомодемы работают по UART интерфейсу, а значит мы способны передавать данные по радиосвязи на приличные расстояния!
Добрый день!
А как поступить с отрицательными числами?
Точно так же. Передается все равно байт, а уж -128..127 или 0..255 это вам решать на приемнике. Просто создаете переменную нужного типа и в нее записываете значение из UART.
Почитайте про отрицательные числа в двоичном коде у ВМ
Сразу станет понятно что как передавать
https://ru.wikipedia.org/wiki/%D0%94%D0%BE%D0%BF%D0%BE%D0%BB%D0%BD%D0%B8%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D1%8B%D0%B9_%D0%BA%D0%BE%D0%B4