Вывод данных с ардуино на компьютер в виде графика
Цифровые и аналоговые датчики, подключенные к Arduino, генерируют большие объёмы информации, которая требует обработки в реальном масштабе времени [1].
В настоящее время данные от Arduino распечатывают из командной строки или отображают в графическом интерфейсе с запаздыванием. Поэтому данные в режиме реального времени и не сохраняются, что делает невозможным их дальнейший анализ.
Данная публикация посвящена программному решению задачи хранения информации от датчиков, работающих с Arduino и её графическому представлению в реальном масштабе времени. В примерах используются широко известными датчиками, такими как потенциометр и датчик движения PIR.
Программируем «Hello, world!»
Для работы с ЖК дисплеями различных размеров и типов, в редакторе Arduino IDE имеется специальная библиотека LiquidCrystal. Чтобы подключить библиотеку, запишем первой строчкой нашей программы следующее выражение:
Далее, нам потребуется указать какие выводы Ардуино мы использовали для подключения дисплея. Эту информацию мы укажем при инициализации модуля:
Здесь первые два аргумента — это выводы RS и EN, а оставшиеся четыре — линии шины данных DB4-DB7.
Далее, укажем размер дисплея с помощью команды «begin»:
Напоминаю, в нашем дисплее имеется две строки, по 16 символов в каждой.
Наконец, для вывода текста нам понадобится простая функция «print». Вывод с помощью этой функции всем известной фразы будет выглядеть следующим образом:
Полностью программа будет выглядеть так:
Загружаем её на Ардуино Уно, и смотрим что творится на дисплее. Может быть три основных ситуации 🙂
1) На дисплее отобразится надпись «Hello, world!». Значит вы все правильно подключили, и контраст каким-то чудесным образом оказался изначально правильно настроен. Радуемся, и переходим к следующей главе.
2) На дисплее отобразится целый ряд черных прямоугольников — требуется настройка контраста! Именно для этого мы добавили в цепь потенциометр с ручкой. Крутим его от одного края, до другого, до момента пока на дисплее не появится четкая надпись.
3) Два ряда черных прямоугольников. Скорее всего, вы что-то напутали при подключении. Проверьте трижды все провода. Если не найдете ошибку — попросите кота проверить!
Объект Serial
Начнём знакомство с одним из самых полезных инструментов Arduino-разработчика – Serial, который идёт в комплекте со стандартными библиотеками. Serial позволяет как просто принимать и отправлять данные через последовательный порт, так и наследует из класса Stream кучу интересных возможностей и фишек, давайте сразу их все рассмотрим, а потом перейдём к конкретным примерам.
Запустить связь по Serial на скорости speed (измеряется в baud, бит в секунду). Скорость можно поставить любую, но есть несколько “стандартных” значений. Список скоростей для монитора порта Arduino IDE:
- 300
- 1200
- 2400
- 4800
- 9600 чаще всего используется, можно назвать стандартной
- 19200
- 38400
- 57600
- 115200 тоже часто встречается
- 230400
- 250000
- 500000
- 1000000
- 2000000 – максимальная скорость, не работает на некоторых китайских платах
Возвращает количество байт, которые можно записать в буфер последовательного порта, не блокируя при этом функцию записи.
Отправляет в порт val численное значение или строку, или отправляет количество len байт из буфера buf. Важно! Отправляет данные как байт (см. таблицу ASCII), то есть отправив 88 вы получите букву X: Serial.write(88); .
Отправляет в порт значение val – число или строку, фактически “печатает”. В отличие от write выводит именно текст, т.е. отправив 88, вы получите 88: Serial.print(88); . Отправляет любые стандартные типы данных: численные, символьные, строковые. Также методы print()/println() имеют несколько настроек для разных данных, что делает их очень удобным инструментом отладки:
format позволяет настраивать вывод данных: BIN, OCT, DEC, HEX выведут число в соответствующей системе счисления: двоичная, восьмеричная, десятичная (по умолчанию) и 16-ричная. Цифра после вывода float позволяет настраивать выводимое количество знаков после точки:
Полный аналог print() , но автоматически переводит строку после вывода. Позволяет также вызываться без аргументов (с пустыми скобками) просто для перевода курсора на новую строку.
Возвращает текущий байт с края буфера, не убирая его из буфера. При вызове Serial.read() будет считан тот же байт, но из буфера уже уберётся.
Устанавливает time (миллисекунды) таймаут ожидания приёма данных для следующих ниже функций. По умолчанию равен 1000 мс (1 секунда).
Читает данные из буфера и ищет набор символов target (тип char ), опционально можно указать длину length. Возвращает true , если находит указанные символы. Ожидает передачу по таймауту.
Читает данные из буфера и ищет набор символов target (тип char ) либо терминальную строку terminal. Ожидает окончания передачи по таймауту, либо завершает приём после чтения terminal.
Читает данные из порта и закидывает их в буфер buffer (массив char[] или byte[] ). Также указывается количество байт, который нужно записать – length (чтобы не переполнить буфер).
Читает данные из порта и закидывает их в буфер buffer (массив char[] или byte[] ), также указывается количество байт, который нужно записать – length (чтобы не переполнить буфер) и терминальный символ character. Окончание приёма в buffer происходит при достижении заданного количества length, при приёме терминального символа character (он в буфер не идёт) или по таймауту
Читает порт, формирует из данных строку String , и возвращает её (урок про стринги). Заканчивает работу по таймауту.
Читает порт, формирует из данных строку String , и возвращает её (урок про стринги). Заканчивает работу по таймауту или после приёма символа terminator (символ char ).
Читает целочисленное значение из порта и возвращает его (тип long ). Заканчивает работу по таймауту. Прерывает чтение на всех знаках, кроме знака – (минус). Можно также отдельно указать символ skipChar, который нужно пропустить, например кавычку-разделитель тысяч (10’325’685), чтобы принять такое число.
Видео
Как этим пользоваться? Да очень просто. Одна функция для инициализации “кастомных” символов, вторая – для вывода нужного элемента с настройками его размера и позиции на дисплее!
Полоса загрузки: fillBar(столбец, строка, ширина, значение)
- Столбец: отвечает за положение левой точки полосы, нумерация идёт слева направо с нуля
- Строка: отвечает за положение левой точки полосы, нумерация идёт сверху вниз с нуля
- Ширина: полная ширина полосы по горизонтали. Очевидно, что ширина + стартовая позиция по горизонтали (столбец) не должны превышать ширину дисплея в символах по горизонтали
- Значение: число от 0 до 100 – процент заполнения полосы. Любая ваша величина приводится к диапазону 0-100 при помощи ардуиновской функции map
- Особенность: если вы используете свои кастомные символы, то перед выводом полосок нужно обязательно вызвать initBar() для загрузки в память дисплея символов полосы! Полоски занимают разное количество мест в зависимости от типа, подробнее смотрите в самих примерах
График из массива: drawPlotArray(столбец, строка, ширина, высота, мин. значение, макс. значение, массив) – смотри пример!
График в реальном времени: drawPlot(столбец, строка, ширина, высота, мин. значение, макс. значение, величина)
Парсинг текста
Проще всего прочитать текст в String-строку (урок про них). Это максимально не оптимально, но зато довольно просто для восприятия:
Данный пример выводит любой текст, который был отправлен в монитор порта.
Плоттер
Помимо монитора последовательного порта, в Arduino IDE есть плоттер – построитель графиков в реальном времени по данным из последовательного порта. Достаточно отправлять значение при помощи команды Serial.println(значение) и открыть плоттер по последовательному соединению, например построим график значения с аналогового пина A0:
Плоттер поддерживает несколько линий графиков одновременно, для их отображения нужно соблюдать следующий протокол отправки данных: значения выводятся в одну строку, одно за другим по порядку, разделяются пробелом или запятой и в конце обязательно перенос строки.
значение1 пробел_или_запятая значение2 пробел_или_запятая значение3 пробел_или_запятая перенос_строки
Давайте выведем значения с аналоговых пинов A0, A1 и A2:
Получим вот такие графики:
Чтение из порта
Проблемы возникают при попытке принять данные в порт. Дело в том, что метод read() читает один символ, а если вы отправите длинное число или строку – программа получит его по одному символу. Чтение сложных данных называется парсинг. Его можно делать вручную, об этом мы поговорим в отдельном уроке из блока “Алгоритмы”. В рамках этого урока рассмотрим встроенные инструменты для парсинга Serial.
Чтобы не нагружать программу чтением пустого буфера, нужно использовать конструкцию
Таким образом чтение будет осуществляться только в том случае, если в буфере есть какие-то данные.
Использование CSV-файлов для хранения данных полученных от датчиков, работающих с Arduino
-
Для записи данных в CSVфайл можно использовать простой листинг:
Потенциометр подключён к аналоговому выводу 0, а датчик движения PIR к цифровому выводу 11, как показано на следующей схеме:
Для работы данной схемы, необходимо загрузить в Python модуль pyFirmata и эскиз StandardFirmata в плату Arduino.
В любом файле Python разместим следующий код, который запускает и останавливает запись данных от обеих датчиков в файл SensorDataStore.csv:
В результате работы листинга №1, получим запись данных в файл 'SensorDataStore.csv':
Рассмотрим код связанный с хранением данных датчиков. Первая строка записи в CSV файл– строка заголовка, которая объясняет содержимое столбцов: w.writerow([«Number», «Potentiometer», «Motion sensor»]).
Когда появляется динамика изменения данных, которую для приведенной схемы можно искусственно создать поворотом ручки потенциометра или движением руки возле датчика движения критичным становиться число записей в файл данных. Для восстановления формы сигнала частота записи данных в файл должна быть вдвое больше частоты изменения сигнала. Для регулировки частоты записей может использоваться шаг – n, а для регулировки времени измерения число циклов – m. Однако ограничения на n снизу накладывает быстродействие самих датчиков. Эту функцию в приведенной выше программе выполняет следующий фрагмент кода:
Приведенная программы может быть изменена в соответствии с проектными требованиями следующим образом:
- Можно изменить номера выводов Arduino и количество выводов, которые будут использоваться. Это можно сделать, добавив в код Python и эскиз StandardFirmata в Arduino строки дополнительных значений для новых датчиков.
- CSV-файл: имя файла и его местоположение можно изменить с SensorDataStore.csv на то, которое относится к вашему приложению.
- Частоту записей m в файл SensorDataStore.csv можно изменить, изменяя шаг, а длительность записи изменяя число циклов при постоянном шаге.
Парсинг цифр
Для чтения целых цифр используем Serial.parseInt() , для дробных – Serial.parseFloat() . Пример, который читает целое число и отправляет его обратно:
Если при парсинге у вас появляются лишние цифры – поставьте “Нет конца строки” в настройках монитора порта
Вы заметите, что после отправки проходит секунда, прежде чем плата ответит в порт. Эта секунда является таймаутом, о котором мы говорили чуть выше. Программа ждёт секунду после принятия последнего символа, чтобы все данные успели прийти. Секунда это очень много, ожидать, скажем, 50 миллисекунд. Это можно сделать при помощи метода setTimeout() .
Теперь после отправки цифры программа будет ждать всего 50 мс и ответит гораздо быстрее!
Отправка в порт
Рассмотрим самый классический пример для всех языков программирования: Hello World!
Отправка в порт позволяет узнать значение переменной в нужном месте программы, этот процесс называется отладка. Когда код работает не так, как нужно, начинаем смотреть, где какие переменные какие значения принимают. Или выводим текст из разных мест программы, чтобы наблюдать за порядком её работы. Во взрослых средах разработки и более серьёзных микроконтроллерах есть аппаратная отладка, которая позволяет наблюдать за ходом выполнения программы и значениями любых переменных без вывода в порт.
Давайте вспомним урок циклы и массивы и выведем в порт элементы массива:
Вывод: 0 50 68 85 15 214 63 254 – элементы массива, разделённые пробелами.
Использование CSV-файлов для хранения данных полученных от датчиков, работающих с Arduino
-
Для записи данных в CSVфайл можно использовать простой листинг:
Потенциометр подключён к аналоговому выводу 0, а датчик движения PIR к цифровому выводу 11, как показано на следующей схеме:
Для работы данной схемы, необходимо загрузить в Python модуль pyFirmata и эскиз StandardFirmata в плату Arduino.
В любом файле Python разместим следующий код, который запускает и останавливает запись данных от обеих датчиков в файл SensorDataStore.csv:
В результате работы листинга №1, получим запись данных в файл 'SensorDataStore.csv':
Рассмотрим код связанный с хранением данных датчиков. Первая строка записи в CSV файл– строка заголовка, которая объясняет содержимое столбцов: w.writerow([«Number», «Potentiometer», «Motion sensor»]).
Когда появляется динамика изменения данных, которую для приведенной схемы можно искусственно создать поворотом ручки потенциометра или движением руки возле датчика движения критичным становиться число записей в файл данных. Для восстановления формы сигнала частота записи данных в файл должна быть вдвое больше частоты изменения сигнала. Для регулировки частоты записей может использоваться шаг – n, а для регулировки времени измерения число циклов – m. Однако ограничения на n снизу накладывает быстродействие самих датчиков. Эту функцию в приведенной выше программе выполняет следующий фрагмент кода:
Приведенная программы может быть изменена в соответствии с проектными требованиями следующим образом:
- Можно изменить номера выводов Arduino и количество выводов, которые будут использоваться. Это можно сделать, добавив в код Python и эскиз StandardFirmata в Arduino строки дополнительных значений для новых датчиков.
- CSV-файл: имя файла и его местоположение можно изменить с SensorDataStore.csv на то, которое относится к вашему приложению.
- Частоту записей m в файл SensorDataStore.csv можно изменить, изменяя шаг, а длительность записи изменяя число циклов при постоянном шаге.
Выводы
В этой публикации представлены две основные парадигмы программирования Python: создание, чтение и запись файлов с использованием Python, а также хранение данных в этих файлах и построение значений датчиков и обновление графиков в реальном времени. Мы также изучили методы хранения и отображения данных датчика Arduino в реальном времени. Помимо помощи Вам в проектах с Arduino, эти методы также можно использовать в повседневных проектах Python.
Иногда требуется, не считывая детальную информацию с множества датчиков, просто оценить текущее состояние системы и динамику изменения ее состояния за какой-то период. Вот и мне захотелось сделать устройство, показывающее изменения данных с датчиков в виде красивых картинок небольших диаграмм, которые можно просмотреть в окне браузера мобильных устройств или компьютера, подключенных в локальную сеть. При этом определяющим фактором была минимальная стоимость, и простота реализации.
После перебора различных вариантов решения этой задачи обратил внимание на микроконтроллеры Arduino. Плюсом данных устройств является простота получения необходимого «железного» функционала путем простого соединения элементов. Например, для получения возможности соединения с локальной сетью достаточно на основную плату надеть сверху плату сетевого адаптера. Главное, чтобы при этом совпали соответствующие разъемы.
Аппаратная часть преобразователя состоит из двух частей: платы Arduino Uno, включающей процессор Atmega328, и Ethernet шилда. Были опробованы оба типа имеющихся в семействе Arduino Ethernet-шилдов на ИС ENC28j60 и на W5100.
Возможности вебсервера крайне ограничены размером доступной памяти платы Arduino и архитектурой сетевой платы, но удалось написать скетч, работающий с обеими Ethernet-шилдами.
Программное обеспечение (скетч) для arduino разработано для двух разных условий применения. В первом случае предполагалось, что клиенты, запрашивающие данные, имеют доступ в интернет, что позволяет использовать внешние библиотеки для визуализации. При использовании библиотеки для построения гистограмм от Google внешний вид странички, получаемой от Arduino, может быть таким:
Скетч для вывода данных в виде такой диаграммы c Ethernet шилдом на базе чипа W5100 представлен ниже. При желании использовать плату с чипом ENC28J60 необходимо просто раскомментировать 1 строку и закомментировать следующие 2 строки скетча. В программе применены методы оптимизации использования памяти процессора для того, чтобы максимально освободить оперативную память и добиться надежной работы программы на обоих типах сетевых адаптеров.
EthernetServer server(80);
const char str1[] PROGMEM ;
const char str2[] PROGMEM ";
const char* const string_table[] PROGMEM = ;
const char* const string_table2[] PROGMEM = ;
char myChar;
char buffer[80];
unsigned long previousMillis1 = 0;// посл момент времени
unsigned long previousMillis2 = 1;// посл момент времени
unsigned long previousMillis3 = 1;// посл момент времени
long OnTime2 = 60000; // минута
long OnTime3 = 1800000; // полчаса
int In_sec = 0; // отсчет за сек
int In_min = 0; // отсчет за мин
int In_half = 0; // отсчет за полчаса
long Sum_min = 0; // сумма за мин
long Sum_half = 0; // сумма за полчаса
float Sum_base_min = 0;
float Sum_base_half = 0;
int i,j,k =0;
void setup()
// start the Ethernet connection and the server:
Ethernet.begin(mac, ip);
server.begin();
>
//unsigned long begMillis = millis();// нач время в мс
void loop()
unsigned long currentMillis = millis();// тек время в мс
In_sec = analogRead(0);
Sum_min = Sum_min + (currentMillis — previousMillis1) * In_sec;
In_min = (Sum_min + Sum_base_min ) / (OnTime2 * i + currentMillis — previousMillis2);
Sum_half = Sum_half + (currentMillis — previousMillis1) * In_sec;
In_half = (Sum_half + Sum_base_half) / (OnTime3 * j + currentMillis — previousMillis3);
previousMillis1 = currentMillis; // запоминаем момент времени
if(currentMillis — previousMillis2 >= OnTime2)
< i=1;
Sum_base_min = Sum_min;
previousMillis2 = currentMillis; // запоминаем момент времени
Sum_min = 0;
>
if(currentMillis — previousMillis3 >= OnTime3)
< j=1;
Sum_base_half = Sum_half;
previousMillis3 = currentMillis; // запоминаем момент времени
Sum_half = 0;
>
EthernetClient client = server.available();
if (client) boolean currentLineIsBlank = true;
while (client.connected()) if (client.available()) char c = client.read();
if (c == '\n' && currentLineIsBlank) for (int i = 0; i < 7; i++)
strcpy_P(buffer, (char*)pgm_read_word(&(string_table[i])));
client.print(buffer);
delay( 500 );
>
client.print("['Ввод 1', ");
client.print(In_sec);
client.print(", ");
client.print(In_min);
client.print(", ");
client.print(In_half);
client.print( "],]);");
for (int i = 0; i < 5; i++)
strcpy_P(buffer, (char*)pgm_read_word(&(string_table2[i])));
client.print(buffer);
delay( 500 );
>
break;
>
if (c == '\n') >
else if (c != '\r')
При желании можно также выводить данные из arduino в виде линейных графиков:
Скетч для такого представления данных представлен ниже:
// Initialize the Ethernet server library
// with the IP address and port you want to use
// (port 80 is default for HTTP):
EthernetServer server(80);
const char str1[] PROGMEM ;
const char str2[] PROGMEM https://www.google.com/jsapi?autoload 'visualization','version':'1','packages':['corechart']>]>\">";
const char str4[] PROGMEM ";
const char* const string_table[] PROGMEM = ;
const char* const string_table2[] PROGMEM = ;
char myChar;
char buffer[80];
unsigned long previousMillis1 = 0;// посл момент времени
unsigned long previousMillis2 = 1;// посл момент времени
unsigned long previousMillis3 = 1;// посл момент времени
long OnTime2 = 60000; // минута
long OnTime3 = 600000; // полчаса
int In_sec = 0; // отсчет за сек
int In_min = 0; // отсчет за мин
int In_half = 0; // отсчет за полчаса
long Sum_min = 0; // сумма за мин
long Sum_half = 0; // сумма за полчаса
float Sum_base_min = 0;
float Sum_base_half = 0;
int i,j,k =0;
void setup()
// start the Ethernet connection and the server:
Ethernet.begin(mac, ip);
server.begin();
>
//unsigned long begMillis = millis();// тек время в мс
void loop()
unsigned long currentMillis = millis();// тек время в мс
In_sec = analogRead(0);
Sum_min = Sum_min + (currentMillis — previousMillis1) * In_sec;
In_min = (Sum_min + Sum_base_min ) / (OnTime2 * i + currentMillis — previousMillis2);
Sum_half = Sum_half + (currentMillis — previousMillis1) * In_sec;
In_half = (Sum_half + Sum_base_half) / (OnTime3 * j + currentMillis — previousMillis3);
previousMillis1 = currentMillis; // запоминаем момент времени
if(currentMillis — previousMillis2 >= OnTime2)
< i=1;
Sum_base_min = Sum_min;
previousMillis2 = currentMillis; // запоминаем момент времени
Sum_min = 0;
>
if(currentMillis — previousMillis3 >= OnTime3)
< j=1;
Sum_base_half = Sum_half;
previousMillis3 = currentMillis; // запоминаем момент времени
Sum_half = 0;
>
/*
In_sec = 990;
In_min = 500;
In_half = 90;
for (int i = 0; i < 7; i++)
strcpy_P(buffer, (char*)pgm_read_word(&(string_table[i]))); // Necessary casts and dereferencing, just copy.
client.print(buffer);
delay( 500 );
>
client.print("['Сейчас', ");
client.print(660);
client.print(", ");
client.print(1120);
client.print( "],]);");
for (int i = 0; i < 5; i++)
strcpy_P(buffer, (char*)pgm_read_word(&(string_table2[i]))); // Necessary casts and dereferencing, just copy.
client.print(buffer);
delay( 500 );
>
break;
>
if (c == '\n') // you're starting a new line
currentLineIsBlank = true;
>
else if (c != '\r') // you've gotten a character on the current line
currentLineIsBlank = false;
>
>
>
// give the web browser time to receive the data
delay(1);
// close the connection:
client.stop();
>
>
Кроме того, из чистого любопытства, в рамках исследования возможностей применения библиотеки google был написан скетч, позволяющий выводить информацию в виде аналогового измерительного прибора. Он позволяет быстро оценить текущее значение сигнала.
EthernetServer server(80);
const char str1[] PROGMEM ;
const char str2[] PROGMEM ";
const char* const string_table[] PROGMEM = ;
const char* const string_table2[] PROGMEM = ;
char myChar;
char buffer[80];
int In_sec = 0; // отсчет за сек
int i,j,k =0;
void setup()
// start the Ethernet connection and the server:
Ethernet.begin(mac, ip);
server.begin();
for (int i = 0; i < 7; i++)
strcpy_P(buffer, (char*)pgm_read_word(&(string_table[i]))); // Necessary casts and dereferencing, just copy.
client.print(buffer);
delay( 500 );
>
for (int i = 0; i < 5; i++)
strcpy_P(buffer, (char*)pgm_read_word(&(string_table2[i]))); // Necessary casts and dereferencing, just copy.
client.print(buffer);
delay( 500 );
>
break;
>
if (c == '\n') // you're starting a new line
currentLineIsBlank = true;
>
else if (c != '\r') // you've gotten a character on the current line
currentLineIsBlank = false;
>
>
>
// give the web browser time to receive the data
delay(1);
// close the connection:
client.stop();
>
>
В случае, если для чтения данных с датчиков нет возможности использовать устройства, связанные с интернетом, опробован другой способ построения диаграмм в Arduino, основанный на использовании Scalable Vector Graphic (SVG).
Т.к. требуется просто оценить динамику изменения параметров, то в результате выполнения нижеприведенного скетча при запросе данных с ардуино, на экране браузера увидим следующую диаграммку:
Один взгляд на диаграммку позволяет понять динамику изменения параметра за определенные ранее интервалы времени.
Скетч приведен ниже:
//вывод данных с помощью web-сервера в виде гистограммы
// автор А. Коновалов 2015 г.
EthernetServer server(80);
const char str1[] PROGMEM = "";
const char str2[] PROGMEM ;
const char str4[] PROGMEM = "";
char myChar;
int out;
unsigned long previousMillis1 = 0;// посл момент времени
unsigned long previousMillis2 = 1;// посл момент времени
unsigned long previousMillis3 = 1;// посл момент времени
long OnTime2 = 60000; // минута
long OnTime3 = 600000; // полчаса
int In_sec = 0; // отсчет за сек
int In_min = 0; // отсчет за мин
int In_half = 0; // отсчет за полчаса
long Sum_min = 0; // сумма за мин
long Sum_half = 0; // сумма за полчаса
float Sum_base_min = 0;
float Sum_base_half = 0;
int i,j,k =0;
Ethernet.begin(mac, ip);
server.begin();
>
//unsigned long begMillis = millis();// тек время в мс
void loop()
unsigned long currentMillis = millis();// тек время в мс
In_sec = analogRead(0);
Sum_min = Sum_min + (currentMillis — previousMillis1) * In_sec;
In_min = (Sum_min + Sum_base_min ) / (OnTime2 * i + currentMillis — previousMillis2);
Sum_half = Sum_half + (currentMillis — previousMillis1) * In_sec;
In_half = (Sum_half + Sum_base_half) / (OnTime3 * j + currentMillis — previousMillis3);
previousMillis1 = currentMillis; // запоминаем момент времени
if(currentMillis — previousMillis2 >= OnTime2)
< i=1;
Sum_base_min = Sum_min;
previousMillis2 = currentMillis; // запоминаем момент времени
Sum_min = 0;
>
if(currentMillis — previousMillis3 >= OnTime3)
< j=1;
Sum_base_half = Sum_half;
previousMillis3 = currentMillis; // запоминаем момент времени
Sum_half = 0;
>
for (k = 0; k < strlen(str1); k++)
myChar = pgm_read_byte_near(str1 + k);
client.print(myChar);
>
for (k = 0; k < strlen(str2); k++)
myChar = pgm_read_byte_near(str2 + k);
client.print(myChar);
>
out = 200 — In_sec/3;
client.print(out);
client.print(" 100,");
client.print(out);
client.print(" 250,");
out = 200 — In_min/3;
client.print(out);
client.print(" 300,");
out = 200 — In_half/3;
client.print(out);
for (k = 0; k < strlen(str3); k++)
myChar = pgm_read_byte_near(str3 + k);
client.print(myChar);
>
client.println();
client.print(In_sec);
client.println();
client.print(In_min);
client.println();
client.print(In_half);
for (k = 0; k < strlen(str4); k++)
myChar = pgm_read_byte_near(str4 + k);
client.print(myChar);
>
break;
>
if (c == '\n') // you're starting a new line
currentLineIsBlank = true;
>
else if (c != '\r') // you've gotten a character on the current line
currentLineIsBlank = false;
>
>
>
// give the web browser time to receive the data
delay(1);
// close the connection:
client.stop();
>
>
Приведенные выше скетчи показывают, как самыми простыми средствами, без использования дополнительного оборудования и программных средств, например, веб-сервера и серверных приложений обработки данных, получить приемлемый в условиях эксплуатации вид отображения информации с датчиков. Конечно, при данном способе передачи данных необходимо наличие локальной сети и для работы мобильных устройств, Wi-Fi доступа в эту сеть.
При написании скетчей большую помощь оказало руководство для разработчика, а также статья на Хабре «Знакомство с SVG-графикой».
Библиотека UIP не входит в стандартный набор, но может быть загружена здесь.
Надеюсь, опираясь на приведенные мной примеры скетчей и ссылки на обучающие материалы вам будет легко заставить Arduino выводить графики в том виде, который вам больше понравится.
P.S. Как оказалось, спойлер хабра безжалостно выгрыз куски html кода из скетчей. Поэтому, чтобы не разочаровывать читателей, откушенные фрагменты я добавил в виде картинок.
Все давно привыкли, что у каждого электронного устройства есть экран, с помощью которого оно дает человеку всякую полезную информацию. MP3-плеер показывает название играемого трека, пульт квадрокоптера отображает полетную телеметрию, даже стиральная машина выводит на дисплей время до конца стирки, а на смартфоне вообще размещается целый рабочий стол персонального компьютера!
Скорее всего, вашему очередному устройству тоже не помешает какой-нибудь небольшой дисплейчик 🙂 Попробуем сделать простые электронные часы! А в качестве табло используем распространенный и дешевый символьный жидкокристаллический дисплей 1602. Вот прямо такой, как на картинке:
Кроме 16х2, достаточно популярным считается символьный дисплей 20х4 (четыре строки по 20 символов), а также графический дисплей с разрешением 128х64 точек. Вот они на картинках:
Программируем часы
Теперь когда дисплей точно работает, попробуем превратить наше нехитрое устройство в настоящие электронные часы.
Внимание! Для вывода времени нам потребуется библиотека «Time». Если она еще не установлена, то можно скачать архив по ссылке. Подключим ее:
Затем установим текущие дату и время с помощью функции «setTime»:
Здесь все понятно: часы, минуты, секунды, месяц, число, год.
Для вывода даты используем кучу функции:
- year() — вернет нам год;
- month() — месяц;
- day() - день;
- hour() - час;
- minute() — вернет минуту;
- second() - секунду.
Теперь обратим внимание вот на какой факт. Если посчитать количество символов в типовой записи даты: «31.12.2015 23:59:59», получим 19. А у нас всего 16! Не влазит, однако, в одну строчку.
Решить проблему можно еще одной полезной функцией — «setCursor». Эта функция устанавливает курсор в нужную позицию. Например:
Установит курсор в начало второй строчки. Курсор — это место символа, с которого начнется вывод текста следующей командой «print». Воспользуемся этой функцией для вывода даты в первой строчке, а времени во второй.
С выводом даты и времени теперь все ясно. Остались рутинные вещи. Например, после каждого заполнения дисплея, мы будем его чистить функцией «clear()»:
А еще нам нет смысла выводить данные на дисплей чаще чем раз в секунду, поэтому между двумя итерациями сделаем паузу в 1000 миллисекунд.
Итак, сложив все вместе, получим такую программу:
Загружаем скетч на Ардуино Уно, и наблюдаем за ходом часиков! 🙂 Для того чтобы закрепить полученные знания, рекомендую прокачать наши часы до полноценного будильника. Всего-то на всего потребуется добавить пару кнопок и зуммер 🙂
Как мы с вами знаем из урока “Что умеет микроконтроллер“, у многих микроконтроллеров есть интерфейс UART, позволяющий передавать и принимать различные данные. У интерфейса есть два вывода на плате – пины TX и RX. На большинстве Arduino-плат к этим пинам подключен USB-UART преобразователь (расположен на плате), при помощи которого плата может определяться компьютером при подключении USB кабеля и обмениваться с ним информацией. На компьютере создаётся виртуальный COM порт (последовательный порт), к которому можно подключиться при помощи программ-терминалов и принимать-отправлять текстовые данные. Таким же образом кстати работают некоторые принтеры и большинство станков с ЧПУ.
В самой Arduino IDE есть встроенная “консоль” – монитор порта, кнопка с иконкой лупы в правом верхнем углу программы. Нажав на эту кнопку мы откроем сам монитор порта, в котором будут настройки:
Если с отправкой, автопрокруткой, отметками времени и кнопкой очистить вывод всё и так понятно, то конец строки и скорость мы рассмотрим подробнее:
- Конец строки: тут есть несколько вариантов на выбор, чуть позже вы поймёте, на что они влияют. Лучше поставить нет конца строки, так как это позволит избежать непонятных ошибок на первых этапах знакомства с платформой.
- Нет конца строки – никаких дополнительных символов в конце введённых символов после нажатия на кнопку отправка или клавишу Enter.
- NL – символ переноса строки в конце отправленных данных.
- CR – символ возврата каретки в конце отправленных данных.
- NL+CR – и то и то.
Подключение символьного ЖК дисплея 1602
У дисплея 1602 есть 16 выводов. Обычно они нумеруются слева-направо, если смотреть на него так как на картинке. Иногда выводы подписываются, типа: DB0, DB1, EN и т.п. А иногда просто указывают номер вывода. В любом случае, список выводов всегда одинаковый:
1 — «GND», земля (минус питания);
2 — «Vcc», питание +5В;
3 — «VEE», контраст;
4 — «RS», выбор регистра;
5 — «R/W», направление передачи данных (запись/чтение);
6 — «EN», синхронизация;
7-14 — «DB0», «DB1», . «DB7″- шина данных;
15 — анод подсветки (+5В);
16 — катод подсветки (земля).Линии VEE, RS и четыре линии данных DB4, DB5, DB6, DB7 подключаем к цифровым выводам контроллера. Линию «R/W» подключим к «земле» контроллера (так как нам потребуется только функция записи в память дисплея). Подсветку пока подключать не будем, с этим, я полагаю, вы сами легко разберетесь 🙂
Принципиальная схема подключения дисплея к Ардуино Уно
Внешний вид макета
На всякий случай еще и в виде таблички:
ЖК дисплей 1602 1 2 4 6 11 12 13 14 15 16 Ардуино Уно GND +5V 4 5 6 7 8 9 +5V GND Графический анализ данных из файла CSV
Используя файл SensorDataStore.csv создадим программу для поучений массивов данных от потенциометра и датчика движения и построения графиков по данным массивам:
В результате работы листинга №2, получим два графика на одной форме для отображения в реальном масштабе времени выходных данных потенциометра и датчика движения.
В этой программе мы создали два массива значений датчиков — pValues и mValues — путем чтения файла SensorDataStore.csv по строкам. Здесь pValues и mValues представляют данные датчика для потенциометра и датчика движения соответственно. С использованием методов matplotlib построим два графика на одной форме.
Созданный код после каждого цикла формирования массивов данных pValues и mValues полученным от датчиков Arduino обновляет интерфейс, что позволяет сделать вывод о получении данных в реальном масштабе времени.
Однако метод хранения и визуализации данных имеет следующие особенности – весь набор данных сначала записываются в файл SensorDataStore.csv (Листинг №1), а затем считываются из этого файла (Листинг №2). Графики должны перерисовываться каждый раз, когда поступают новые значения от Arduino. Поэтому нужно разработать такую программу, в которой планирование и обновление графиков происходит в реальном времени, а не строится весь набор значений датчиков, как в листингах №1,2.[2].
Пишем программу для потенциометра создавая динамику выходного сигнала путём изменения его активного сопротивления постоянному току.
В результате работы листинга №3, получим график.
Планирование в реальном времени в этом упражнении достигается с помощью комбинации функций pyplot ion (), draw (), set_xdata () и set_data (). Метод ion () инициализирует интерактивный режим pyplot. Интерактивный режим помогает динамически изменять значения x и y графиков на рисунке pyplot.ion ().
Когда интерактивный режим установлен в True, график будет вырисовываться только при вызове метода draw (). Инициализируем плату Arduino с помощью модуля pyFirmata и входных пинов для получения значений датчиков.
Как вы можете видеть в следующей строке кода, после настройки платы Arduino и интерактивного режима pyplot, мы инициализировали график с набором пустых данных, в нашем случае 0: pData = [0] * 25.
Этот массив для значений y, pData, затем используется для добавления значений из датчика в цикле while. Цикл while продолжает добавлять новые значения в этот массив данных и перерисовывает график с этими обновленными массивами для значений x и y.
В листинге №3 мы добавляем новые значения датчиков в конце массива, одновременно удаляя первый элемент массива, чтобы ограничить размер массива:
Методы set_xdata () и set_ydata () используются для обновления данных осей x и y из этих массивов. Эти обновленные значения наносятся с использованием метода draw () на каждой итерации цикла while:
Вы также заметите, что мы используем функцию xrange () для генерации диапазона значений в соответствии с предоставленной длиной, которая равна 25 в нашем случае. Фрагмент кода [i for i in xrange(25)] будет генерировать список из 25 целых чисел, которые начинаются постепенно с 0 и заканчиваются на 24.Интеграция графиков в окне Tkinter
Благодаря мощным возможностям интеграции Python, очень удобно связать графики, созданные библиотекой matplotlib, с графическим интерфейсом Tkinter. Напишем программу для соединения между Tkinter и matplotlib.
В результате работы листинга №4, получим встроенный в окно Tkinter. график с элементами кнопочного интерфейса – startButton, pauseButton и exitButton.
Кнопки «Start» и «Exit» предоставляют контрольные точки для операций matplotlib, та-кие как обновление графика и закрытие графика с помощью соответствующих функций onStartButtonPress () и onExitButtonPress (). Функция onStartButtonPress () также состоит из точки взаимодействия между библиотеками matplotlib и pyFirmata. Как вы можете видеть из следующего фрагмента кода, мы начнем обновлять график, используя метод draw () и окно Tkinter, используя метод update () для каждого наблюдения с аналогового вывода a0, который получается с помощью метода read ().
Функция onExitButtonPress () реализует функцию exit, как описано в самом имени. Она закрывает фигуру pyplot и окно Tkinter перед отключением платы Arduino от последовательного порта.
При подготовке материалов данной публикации принимал участие Мисов О. П.
Управляющие символы
Существуют так называемые управляющие символы, позволяющие форматировать вывод. Их около десятка, но вот самые полезные из них
- \n – новая строка
- \r – возврат каретки
- \v – вертикальная табуляция
- \t – горизонтальная табуляция
Также если в тексте вы захотите использовать одинарные кавычки ' , двойные кавычки " , обратный слэш \ и некоторые другие символы – их нужно экранировать при помощи обратного слэша, он просто ставится перед символом:
- \" – двойные кавычки
- \' – апостроф
- \\ – обратный слэш
- \0 – нулевой символ
- \? – знак вопроса
Выведем строку с кавычками:
Комбинация \r\n переведёт строку и вернёт курсор в левое положение:
Символы табуляции позволят удобно отправлять данные для последующей вставки в таблицу. Например выведем несколько степеней двойки в виде таблицы, используя символ табуляции \t :
Результат скопируем и вставим в excel Удобно!
Читайте также: