Pull to refresh

Данные с Arduino в виде диаграмм и графиков

Reading time 10 min
Views 63K
Иногда требуется, не считывая детальную информацию с множества датчиков, просто оценить текущее состояние системы и динамику изменения ее состояния за какой-то период. Вот и мне захотелось сделать устройство, показывающее изменения данных с датчиков в виде красивых картинок небольших диаграмм, которые можно просмотреть в окне браузера мобильных устройств или компьютера, подключенных в локальную сеть. При этом определяющим фактором была минимальная стоимость, и простота реализации.

После перебора различных вариантов решения этой задачи обратил внимание на микроконтроллеры Arduino. Плюсом данных устройств является простота получения необходимого «железного» функционала путем простого соединения элементов. Например, для получения возможности соединения с локальной сетью достаточно на основную плату надеть сверху плату сетевого адаптера. Главное, чтобы при этом совпали соответствующие разъемы.

image

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

Аппаратная часть преобразователя состоит из двух частей: платы Arduino Uno, включающей процессор Atmega328, и Ethernet шилда. Были опробованы оба типа имеющихся в семействе Arduino Ethernet-шилдов на ИС ENC28j60 и на W5100.

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

Программное обеспечение (скетч) для arduino разработано для двух разных условий применения. В первом случае предполагалось, что клиенты, запрашивающие данные, имеют доступ в интернет, что позволяет использовать внешние библиотеки для визуализации. При использовании библиотеки для построения гистограмм от Google внешний вид странички, получаемой от Arduino, может быть таким:

image

Скетч для вывода данных в виде такой диаграммы c Ethernet шилдом на базе чипа W5100 представлен ниже. При желании использовать плату с чипом ENC28J60 необходимо просто раскомментировать 1 строку и закомментировать следующие 2 строки скетча. В программе применены методы оптимизации использования памяти процессора для того, чтобы максимально освободить оперативную память и добиться надежной работы программы на обоих типах сетевых адаптеров.

скетч гистограммы
//вывод данных с помощью web-сервера в виде гистограммы
// автор А. Коновалов 2015 г.
//#include <UIPEthernet.h> //для работы с ENC28J60
#include <Ethernet.h>
#include <SPI.h
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
IPAddress ip(192,168,26,15);

EthernetServer server(80);
const char str1[] PROGMEM = "<!DOCTYPE html>";
const char str2[] PROGMEM = " »;
const char str12[] PROGMEM = " ";

const char* const string_table[] PROGMEM = {str1, str2, str3, str4, str5, str6, str7};
const char* const string_table2[] PROGMEM = {str8, str9, str10, str11, str12};
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') {

}
}
}
delay(1);
client.stop();
}
}




В данном скетче считываются данные с нулевого аналогового входа и производится их усреднение за получасовой и минутный интервал. При запросе с http клиента по адресу 192.168.26.15 эти данные выводятся в виде гистограммы. Разумеется, перед загрузкой данной программы необходимо ввести нужный адрес вашей локальной сети.

При желании можно также выводить данные из arduino в виде линейных графиков:

image

Скетч для такого представления данных представлен ниже:

скетч с линейным графиком
//вывод данных с помощью web-сервера в виде графиков
// автор А. Коновалов 2015 г.
//#include <UIPEthernet.h>
#include <Ethernet.h>
#include <SPI.h>
// Enter a MAC address and IP address for your controller below.
// The IP address will be dependent on your local network:
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
IPAddress ip(192,168,26,15);

// 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 = "<!DOCTYPE html><script src=";
const char str2[] PROGMEM = "\«www.google.com/jsapi?autoload={'modules':[{'name':»;
const char str3[] PROGMEM = "'visualization','version':'1','packages':['corechart']}]}\">";
const char str4[] PROGMEM = " »;
const char str12[] PROGMEM = " ";

const char* const string_table[] PROGMEM = {str1, str2, str3, str4, str5, str6, str7};
const char* const string_table2[] PROGMEM = {str8, str9, str10, str11, str12};
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;

*/
// listen for incoming clients
EthernetClient client = server.available();
if (client) {
// an http request ends with a blank line
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]))); // 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 был написан скетч, позволяющий выводить информацию в виде аналогового измерительного прибора. Он позволяет быстро оценить текущее значение сигнала.

image

скетч с прибором
//вывод данных с помощью web-сервера в виде прибора
// автор А. Коновалов 2015 г.
//#include <UIPEthernet.h>
#include <Ethernet.h>
#include <SPI.h>

byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
IPAddress ip(192,168,26,15);

EthernetServer server(80);
const char str1[] PROGMEM = "<!DOCTYPE html>";
const char str2[] PROGMEM = " »;
const char str12[] PROGMEM = " ";

const char* const string_table[] PROGMEM = {str1, str2, str3, str4, str5, str6, str7};
const char* const string_table2[] PROGMEM = {str8, str9, str10, str11, str12};
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();

}
//unsigned long begMillis = millis();// тек время в мс
void loop()
{
In_sec = analogRead(0);
EthernetClient client = server.available();
if (client) {
// an http request ends with a blank line
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]))); // Necessary casts and dereferencing, just copy.
client.print(buffer);
delay( 500 );
}

client.print(In_sec);
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();
}
}



В случае, если для чтения данных с датчиков нет возможности использовать устройства, связанные с интернетом, опробован другой способ построения диаграмм в Arduino, основанный на использовании Scalable Vector Graphic (SVG).

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

image

Один взгляд на диаграммку позволяет понять динамику изменения параметра за определенные ранее интервалы времени.

Скетч приведен ниже:

скетч SVG график
//вывод данных с помощью web-сервера в виде гистограммы
// автор А. Коновалов 2015 г.

//#include <UIPEthernet.h>
#include <Ethernet.h>
#include <SPI.h>

byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
IPAddress ip(192,168,26,15);

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;

void setup()
{

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) {
// an http request ends with a blank line
boolean currentLineIsBlank = true;
while (client.connected()) {
if (client.available()) {
char c = client.read();
if (c == '\n' && currentLineIsBlank) {

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 кода из скетчей. Поэтому, чтобы не разочаровывать читателей, откушенные фрагменты я добавил в виде картинок.
Самое вкусное
SVG

прибор

линии

гистограммы

Tags:
Hubs:
+18
Comments 14
Comments Comments 14

Articles