Программируем Arduino. Основы работы со скетчами - Монк Саймон
На рис. 10.4 изображена схема соединения плат.
Рис. 10.4. Две платы Ardiono Uno взаимодействуют через последовательный порт
Контакт Tx на одной плате Arduino должен быть подключен к контакту Rx на другой и наоборот. В этом примере обе платы используют библиотеку SoftwareSerial, контакты 8 служат линией Rx, а контакты 9 — линией Tx.
Контакты GND обеих плат должны быть соединены. Чтобы плата-отправитель могла служить источником питания для платы-получателя, необходимо также соединить их контакты 5V. К плате-отправителю подключено переменное сопротивление, соединяющее гнезда A0 и A2. Настроив контакты A0 и A2 на работу в режиме цифровых выходов и установив на выходе A2 уровень HIGH, можно изменять уровень напряжения на контакте A1 в диапазоне от 0 до 5 В, вращая шток резистора, и тем самым управлять частотой мигания светодиода на другой плате Arduino.
Далее приводится скетч для платы-отправителя:
// sketch_10_02_Adruino_Sender
#include "SoftwareSerial.h"
const int readingPin = A1;
const int plusPin = A2;
const int gndPin = A0;
SoftwareSerial sender(8, 9); // RX, TX
void setup()
{
pinMode(gndPin, OUTPUT);
pinMode(plusPin, OUTPUT);
digitalWrite(plusPin, HIGH);
sender.begin(9600);
}
void loop()
{
int reading = analogRead(readingPin);
byte h = highByte(reading);
byte l = lowByte(reading);
sender.write(h);
sender.write(l);
delay(1000);
}
Перед отправкой прочитанное 16-битное значение (int) разбивается на старший и младший байты, затем оба байта посылаются в последовательный интерфейс командой write. Команды print и println преобразуют свой аргумент в строку, но команда write посылает байт в двоичном виде.
Далее приводится скетч для платы-получателя:
// sketch_10_03_Adruino_Receiver
#include "SoftwareSerial.h"
const int ledPin = 13;
int reading = 0;
SoftwareSerial receiver(8, 9); // RX, TX
void setup()
{
pinMode(ledPin, OUTPUT);
receiver.begin(9600);
}
void loop()
{
if (receiver.available() > 1)
{
byte h = receiver.read();
byte l = receiver.read();
reading = (h << 8) + l;
}
flash(reading);
}
void flash(int rate)
{
// 0 — редко, 1023 — очень часто
int period = (50 + (1023 — rate) / 4);
digitalWrite(ledPin, HIGH);
delay(period);
digitalWrite(ledPin, LOW);
delay(period);
}
Принимающий скетч ждет, пока будет получено не менее 2 байт, и затем восстанавливает значение типа int, сдвигая старший байт на 8 бит влево и прибавляя к результату младший байт.
Для передачи данных с более сложной структурой можно использовать библиотеку EasyTransfer: www.billporter.info/2011/05/30/easytransfer-arduino-library/.
Несмотря на то что в этом примере контакт Tx одной платы Arduino связан проводом с контактом Rx другой, для взаимодействий точно так же можно использовать беспроводные соединения. Многие модули беспроводной связи действуют прозрачно, то есть как если бы связь осуществлялась по проводам.
Модуль GPS
В заключительном примере демонстрируется использование последовательного интерфейса ТТЛ для чтения географических координат (широты и долготы) из модуля глобальной навигационной системы (Global Positioning System, GPS), которые затем форматируются и выводятся в монитор последовательного порта (рис. 10.5).
Рис. 10.5. Чтение данных из модуля GPS в плату Arduino
Связь с модулем GPS возможна только в одну сторону, поэтому достаточно соединить вывод Tx модуля с выводом Rx на плате Arduino. В примере используется модуль GPS, выпускаемый компанией Sparkfun Venus (www.sparkfun.com/products/11058). Подобно большинству других модулей GPS, он имеет последовательный интерфейс ТТЛ и раз в секунду посылает сообщения на скорости 9600 бод.
Формат сообщений соответствует стандарту национальной ассоциации морской электроники (National Marine Electronics Association, NMEA). Сообщение — это текстовая строка, завершающаяся символом перевода строки, с полями, разделенными запятыми. Далее показано, как выглядит типичное сообщение:
$GPRMC,081019.548,A,5342.6316,N,00239.8728,W,000.0,079.7,110613,,,A*76
Поля в данном примере имеют следующие значения:
• $GPRMC — тип сообщения;
• 081019.548 — время (очень точное) в 24-часовом формате, 8:10:19.548;
• 5342.6316, N — широта, умноженная на 100, то есть 53,426316 градуса северной широты;
• 00239.8728,W — долгота, умноженная на 100, то есть 0,2398728 градуса западной долготы;
• 000.0 — скорость;
• 079.7 — курс 79,7 градуса;
• 110613 — дата, 11 июня 2013.
Остальные поля для данного примера не имеют значения.
ПРИМЕЧАНИЕ
Полный список сообщений NMEA GPS можно найти по адресу http://aprs.gids.nl/nmea/.
Далее приводится скетч для этого примера:
#include <SoftwareSerial.h>
SoftwareSerial gpsSerial(10, 11); // RX, TX (TX не используется)
const int sentenceSize = 80;
char sentence[sentenceSize];
void setup()
{
Serial.begin(9600);
gpsSerial.begin(9600);
}
void loop()
{
static int i = 0;
if (gpsSerial.available())
{
char ch = gpsSerial.read();
if (ch != 'n' && i < sentenceSize)
{
sentence[i] = ch;
i++;
}
else
{
sentence[i] = ' ';
i = 0;
//Serial.println(sentence);
displayGPS();
}
}
}
void displayGPS()
{
char field[20];
getField(field, 0);
if (strcmp(field, "$GPRMC") == 0)
{
Serial.print("Lat: ");
getField(field, 3); // число
Serial.print(field);
getField(field, 4); // широта N/S
Serial.print(field);
Serial.print(" Long: ");
getField(field, 5); // число
Serial.print(field);
getField(field, 6); // долгота E/W
Serial.println(field);
}
}
void getField(char* buffer, int index)
{
int sentencePos = 0;
int fieldPos = 0;
int commaCount = 0;
while (sentencePos < sentenceSize)
{
if (sentence[sentencePos] == ',')
{
commaCount ++;
sentencePos ++;
}
if (commaCount == index)
{
buffer[fieldPos] = sentence[sentencePos];
fieldPos ++;
}
sentencePos ++;
}
buffer[fieldPos] = ' ';
}
Сообщения, посылаемые модулем GPS, имеют разную длину, но не более 80 символов, поэтому в скетче используется буфер емкостью 80 символов, который заполняется данными, пока не заполнится или не будет получен признак конца строки.
После того как полное сообщение будет прочитано, в конец буфера вставляется нулевой символ, завершающий строки в языке C. Это необходимо, только если вы пожелаете «напечатать» сообщение в его исходном виде.
Остальная часть скетча реализует извлечение полей и форматирование выходной строки для записи в монитор последовательного порта. Функция getField извлекает текст из поля с указанным индексом.
Функция displayGPS игнорирует любые сообщения, тип которых отличается от "$GPRMC", и извлекает широту и долготу, а также односимвольные названия полушарий для отображения.
В заключение