Individální projekty MPOA

Mikroprocesory s architekturou ARM

Uživatelské nástroje

Nástroje pro tento web


2014:thermo-web

Internetový teploměr s historií

Vojtěch Dluhý, 19.1.2015, 06:50

Zadání

Ke FRDM-K64F připojte teplotní čidlo (např. LM75A) a zprovozněte Ethernet. Údaje z čidla zobrazujte na webové stránce formou grafu s nastavitelným měřítkem časové osy. Pokuste se obrázek s grafem generovat přímo v mikrokontroléru, např. pomocí knihovny LodePNG.


Úvod

Cílem projektu je vytvořit teploměr s připojením k internetu. Na webové stránce bude zobrazena aktuální teplota a graf vývoje teploty.
Základním prvkem je vývojový kit FRDM-K64F s rozšiřující destičkou s obvodem LM75A připojeným pomocí sběrnice I2C. Data jsou ukládána na microSD kartu. A přístup k datům je zajištěn pomocí webového rozhraní.

Blokové schéma


Použité prvky

slot microSD

Vložená SD karta obsahuje data načtená teploměrem a uložená do textového souboru a zdrojové soubory webové stránky, která je generována samotným programem.

Ethernet

Ve vývojovém kitu je spuštěn HTTP server, který zpřístupňuje obsah SD karty, čímž se lze dostat k datům uloženým na SD kartě. IP adresu získává z DNS serveru sítě.

DPS s teploměrem LM75A

Pro připojení teploměru k vývojovému kitu je využita sběrnice I2C sběrnice. A napájení +3,3 V.
Schéma desky DIP přepínače slouží k nastavení 3 bitů adresy obvodu LM75A, což umožní připojit na sběrnici až 8 zařízení. motiv DPS


Stručný popis zdrojového kódu

Při tvorbě zdrojového kódu jsem využil databáze komunity mbed.com.

HTTP server

Základním kamenem se stal program pro HTTP server s použitím SD karty. Server zpřístupňuje obsah SD karty. Stránka je aktualizována a uložena vždy při načtení, kdy je do ní vložena aktuální teplota. Přístupem k IP adrese serveru je otevřena stránka s obsahem SD karty. Kam je také ukládán soubor index.html s hlavní stránkou.
Kód je pro svou obsáhlost uložen v příloze.

Ukládání hodnot

Pro zajištění pravidelnosti odečítání hodnot z teploměru slouží druhé vlákno programu. Kde dochází v pětivteřinovém intervalu k ukládání hodnot teploty z obvodu LM75A do souboru temp.txt ve formátu vhodném k vložení do JavaScriptového souboru.

void ukladani_teploty(void const *args)
{
    wait(10.0);
    while(1)
    {
    poradi++;
       fp2 = fopen("/sd/temp.txt", "a");
       if (fp2 == NULL)
       {
          printf("Unable to write the file \n");
       }
       else
       {
           fprintf(fp2, ",\r\n[%d, %7.3f]\0", poradi, (float)tmp); 
           fclose(fp2);
       }   
       wait(5.0); 
    }   
}

Tvorba grafu

Poslední částí měla být tvorba grafu vývoje teploty. Knihovna určená v zadání by byla pro tento případ složitá, tak jsem se rozhodl využít appletu Google Charts, přičemž jsem narazil na problém spojování několika souborů do jednoho, který se mi nepodařilo překonat. Jediným řešením by tedy bylo ruční skládání souboru JavaScriptu pro tvorbu grafu. Soubor JavaScriptu graf.js je taktéž uložen na SD kartě a volán ze souboru index.html. Jistou nevýhodou je očíslování naměřených hodnot, místo čehož by bylo lepší uložit teplotu s časem a datem měření, čehož jsem nemohl dosáhnout díky neexistenci SMTP klienta pro daný vývojový kit.


Výsledky

Zařízení měří teplotu pomocí obvodu LM75A připojeným přes I2C sběrnici, zobrazuje aktuální teplotu na webové stránce a ukládá každých 5 vteřin hodnotu do souboru. Bohužel se mi nepodařilo zprovoznit automatické vykreslování grafu. Na následujícím obrázku lze vidět provedení webové stránky i s grafem, který jsem generoval ručně. Hodnota na ose x je pořadím načtené hodnoty (každých 5 vteřin). Nevyřešeným problémem tedy je neschopnost automaticky generovat graf.


Přílohy

Zdrojový kód main.c

#include "mbed.h"
#include "rtos.h"
#include "EthernetInterface.h"
#include "SDFileSystem.h"
#include <stdio.h>
#include <string.h>
#include "LM75B.h"
#include "time.h"
 
#define HTTPD_SERVER_PORT   80
#define HTTPD_MAX_REQ_LENGTH   1023
#define HTTPD_MAX_HDR_LENGTH   255
#define HTTPD_MAX_FNAME_LENGTH   127
#define HTTPD_MAX_DNAME_LENGTH   127
 
SDFileSystem sd(PTE3, PTE1, PTE2, PTE4, "sd"); // K64F
 
EthernetInterface eth;
TCPSocketServer server;
TCPSocketConnection client;
 
char buffer[HTTPD_MAX_REQ_LENGTH+1];
char httpHeader[HTTPD_MAX_HDR_LENGTH+1];
char fileName[HTTPD_MAX_FNAME_LENGTH+1];
char dirName[HTTPD_MAX_DNAME_LENGTH+1];
char *uristr;
char *eou;
char *qrystr;
 
int poradi=0;
char ch;
 
FILE *fp, *fp2, *fp3;
int rdCnt;
 
LM75B tmp(PTE25,PTE24);
 
// Generování obsahu SD karty pro web
void get_file(char* uri)
{
    printf("get_file %s\r\n", uri);
    char *lstchr = strrchr(uri, NULL) -1;
    if ('/' == *lstchr) {
        printf("Open directory /sd%s\r\n", uri);
        *lstchr = 0;
        sprintf(fileName, "/sd%s", uri);
        DIR *d = opendir(fileName);
        if (d != NULL) {
            sprintf(httpHeader,"HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nConnection: Close\r\n\r\n");
            client.send(httpHeader,strlen(httpHeader));
            sprintf(httpHeader,"<html><head><title>Directory Listing</title></head><body><h1>%s Directory Listing</h1><ul>", uri);
            client.send(httpHeader,strlen(httpHeader));
            struct dirent *p;
            while((p = readdir(d)) != NULL) {
                sprintf(dirName, "%s/%s", fileName, p->d_name);
                printf("%s\r\n", dirName);
                DIR *subDir = opendir(dirName);
                if (subDir != NULL) {
                    sprintf(httpHeader,"<li><a href=\"./%s/\">%s/</a></li>", p->d_name, p->d_name);
                } else {
                    sprintf(httpHeader,"<li><a href=\"./%s\">%s</a></li>", p->d_name, p->d_name);
                }
                client.send(httpHeader,strlen(httpHeader));
            }
        }
        closedir(d);
        printf("Directory closed\r\n");
        sprintf(httpHeader,"</ul></body></html>");
        client.send(httpHeader,strlen(httpHeader));
    } else {
        sprintf(fileName, "/sd%s", uri);
        fp = fopen(fileName, "r");
        if (fp == NULL) {
            sprintf(httpHeader,"HTTP/1.1 404 Not Found \r\nContent-Type: text\r\nConnection: Close\r\n\r\n");
            client.send(httpHeader,strlen(httpHeader));
            client.send(uri,strlen(uri));
        } else {
            sprintf(httpHeader,"HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nConnection: Close\r\n\r\n");
            client.send(httpHeader,strlen(httpHeader));
            while ((rdCnt = fread(buffer, sizeof( char ), 1024, fp)) == 1024) {
                client.send(buffer, rdCnt);
            }
            client.send(buffer, rdCnt);
            fclose(fp);
        }
    }
}
 
// Vlákno ukládání teploty do souboru
void ukladani_teploty(void const *args)
{
    wait(10.0);
    while(1)
    {
    poradi++;
       fp2 = fopen("/sd/temp.txt", "a");
       if (fp2 == NULL) {
          printf("Unable to write the file \n");
       } else {
          fprintf(fp2, ",\r\n[%d, %7.3f]\0", poradi, (float)tmp); 
          fclose(fp2);
       }   
       wait(5.0); 
    }   
}
 
// Vlákno generování grafu (nefunkční)
void gener_grafu(void const *args)
{
    while(1){
 
    fp = fopen("/sd/graf.js", "w");
    if (fp == NULL) {
        printf("Unable to write the file \n");
        } else {    
            fprintf(fp, "google.load('visualization', '1.0', {'packages':['corechart']});\r\ngoogle.setOnLoadCallback(drawChart);\r\nfunction drawChart() {\r\nvar options = {'title':'Teplota', 'width':800, 'height':600, 'legend': { position: 'none'}};\r\nvar data = new google.visualization.DataTable();\r\ndata.addColumn('number', 'pořadí');\r\ndata.addColumn('number', 'teplota');\r\ndata.addRows([\r\n");
            fp3 = fopen("/sd/temp.txt", "r");
            if (fp3 == NULL) {
                printf("Unable to read the file \n");
            } else {
                while( (ch = fgetc(fp3)) != '\0'){            
                    fprintf(fp, "%c", ch);
                }
            }
            fclose(fp3);
            fprintf(fp, "]);\r\nvar chart = new google.visualization.LineChart(document.getElementById('chart_div'));\r\nchart.draw(data, options);\r\n};S");
        }
        fclose(fp);  
        wait(20.0);
    }  
}
 
int main (void)
{
    Thread thread(ukladani_teploty);
//    Thread thread2(gener_grafu);                //vlákno vytíží procesor tak, že již nic jiného neudělá
 
    tmp.open();
 
 
    printf("Initializing\r\n");
 
// Kontrola systému souborů
    printf("Checking File System\r\n");
    DIR *d = opendir("/sd/");
    if (d != NULL) {
        printf("SD Card Present\r\n");
    } else {
        printf("SD Card Root Directory Not Found\r\n");
    }
 
// Inicializace EthernetInterface eth;
 
    printf("Initializing Ethernet\r\n");
    eth.init(); //Use DHCP
    printf("Connecting\r\n");
    eth.connect();
    printf("IP Address is %s\r\n", eth.getIPAddress());
 
// Spuštění TCPSocketServer serveru;
    server.bind(HTTPD_SERVER_PORT);
    server.listen();
    printf("Server Listening\n");
 
    while (true) {
        printf("\nWait for new connection...\r\n");
 
        server.accept(client);
        client.set_blocking(false, 1500); // Timeout za 1.5 s
 
// Tvorba index.html        
    fp = fopen("/sd/index.html", "w");
    if (fp == NULL) {
        printf("Unable to write the file \n");
    } else {
        fprintf(fp, "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">\r\n<HTML>\r\n <HEAD>\r\n   <TITLE>teploměr LM75A</TITLE>\r\n   <META http-equiv=\"Content-Type\" content=\"text/html\" charset=utf-8>\r\n </HEAD>\r\n");
        fprintf(fp, "<BODY style=\"overflow: scroll; overflow-y: scroll; overflow: -moz-scrollbars-vertical; text-align: center\">\r\n<h1> Teploměr LM75A </h1>\r\n<h3> s vývojovým kitem Freescale FRDM-K64F </h3>\r\nAktuální teplota: <b>%6.3f\r\n °C</b>\r\n</BODY>\r\n</HTML>", (float)tmp);
 
        fclose(fp);
        printf("File successfully written! \n");
    }        
 
        printf("Connection from: %s\r\n", client.get_address());
        while (true) {
            int n = client.receive(buffer, sizeof(buffer));
            if (n <= 0) break;
            printf("Recieved Data: %d\r\n\r\n%.*s\r\n",n,n,buffer);
            if (n >= 1024) {
                sprintf(httpHeader,"HTTP/1.1 413 Request Entity Too Large \r\nContent-Type: text\r\nConnection: Close\r\n\r\n");
                client.send(httpHeader,strlen(httpHeader));
                client.send(buffer,n);
                break;
            } else {
                buffer[n]=0;
            }
            if (!strncmp(buffer, "GET ", 4)) {
                uristr = buffer + 4;
                eou = strstr(uristr, " ");
                if (eou == NULL) {
                    sprintf(httpHeader,"HTTP/1.1 400 Bad Request \r\nContent-Type: text\r\nConnection: Close\r\n\r\n");
                    client.send(httpHeader,strlen(httpHeader));
                    client.send(buffer,n);
                } else {
                    *eou = 0;
                    get_file(uristr);
                }
            }
        }
 
        client.close();
    }
}
2014/thermo-web.txt · Poslední úprava: 2015/01/19 06:49 autor: Vojtěch Dluhý