TLC5940 czyli co najmniej 16 dodatkowych pinów PWM w Arduino

środa, styczeń 4, 2012 10:14 by sprae | Filled in arduino, dla początkujących, howto

Wygląda na to, że jesteś tutaj nowy(a) jeśli zapiszesz się do mojego RSS feed w prosty sposób otrzymasz informacje o nowych materiałach tutaj.

TLC5940 to układ scalony zawierający 16 wyjść PWM. Generator PWM układu ma rozdzielczość 12 bitów czyli 4096 stopni wypełnienia. Jedną z głównych zalet tego chipu jest możliwość podłączenia do jego wyjść bezpośrednio diod LED (bez rezystorów). Jego wyjścia mogą wytrzymać do 120 mA obciążenia.

Wyprowadzenia układu TLC5940

Read the rest of this entry »

Co to jest PWM?

poniedziałek, styczeń 2, 2012 21:41 by sprae | Filled in arduino, dla początkujących, howto

Wstęp

Wielu użytkowników Arduino zapewne zauważyło, że wśród pinów z grupy DIGITAL jest kilka oznaczonych jako “PWM” lub “~”. W tym artykule postaram się wyjaśnić co to znaczy i jak dokładnie działa. Napiszę też jak można to praktycznie wykorzystać.

PWM w teorii

PWM to skrót od angielskich słów “Pulse Width Modulation”, co oznacza po polsku “Modulacja Szerokości Impulsu”.

W życiu codziennym posługujesz się przełącznikami. One powodują, że włączasz jakieś urządzenie lub wyłączasz. Włączenie oznacza dostarczenie do urządzenia 100% energii elektrycznej, a wyłączenie zmniejsza tą ilość do 0%.

Jeśli masz w domu jakieś urządzenie z silnikiem to możesz zauważyć, że włączając i wyłączając je wiele razy w ciągu sekundy silnik nie zdąży się rozpędzić do maksymalnych obrotów. Wynika to z tego, że wolno się rozpędza. Zatem jeśli odetniesz mu prąd zanim osiągnie maksymalne obroty to będzie kręcił się wolniej i zwalniał do czasu, aż znowu go włączysz. W ten sposób można regulować jego prędkość.

Działanie PWM polega właśnie na tym, że im dłużej silnik jest włączony w ciągu sekundy, tym szybciej się kręci. A jeśli dłużej trwa czas wyłączenia tym wolniej. Czas włączenia to właśnie ten “Impuls” (“Pulse”) w nazwie, którego “Szerokość” (“Width”) regulujesz.

Fizycznie rzecz ujmując działanie PWM polega na dostarczeniu mniejszej ilości energii elektrycznej do urządzenia w przeciągu jakiegoś czasu. Czego skutkiem ubocznym są takie właśnie miłe efekty jak regulacja szybkości lub jasności.

Read the rest of this entry »

PCF8574 czyli jak łatwo zwiększyć liczbę pinów w Arduino

poniedziałek, listopad 28, 2011 0:19 by sprae | Filled in arduino, dla początkujących

Wstęp

Arduino z rodziny UNO posiada 14 pinów cyfrowych. Jest to wystarczająca ilość do większości prostych zastosowań z dziedziny automatyki. Zdarzają się jednak sytuacje, jak np. podłączenie wyświetlacza, które drastycznie tą liczbę mogą zmniejszyć.

Co wtedy robić? Można przesiąść się na 2 razy droższe Arduino MEGA i mieć ponad 50 dodatkowych pinów. Można też użyć rejestru przesuwnego 74HC595, który da dodatkowe 8 wyjść cyfrowych za cenę 2 zł i użycia 3 pinów cyfrowych. Można też użyć układu PCF8574 i mieć dodatkowe 8 pinów (lub nawet 128 łącząc więcej układów) wejścia/wyjścia o możliwościach przekraczających te w Arduino za cenę użycia 2 pinów analogowych.

Układ scalony PCF8574

  • Vcc – Pin zasilania, podłączany w Arduino do pinu 5V.

  • GND – Pin masy zasilania podłączany w Arduino do GND.

  • P0..P7 – Cyfrowe piny do własnego wykorzystania.

  • SDA – Sygnał danych magistrali I2C podłączany w Arduino do Analog In 4.

  • SCL – Sygnał zegara magistrali I2C podłączany w Arduino do Analog In 5.

  • A0, A1, A2 – Wybór adresu układu, jeśli używasz jednego układu, wszystkie można podłączyć do GND.

  • /INT – Zanegowany sygnał przerwania. Można go używać do wykrywania zmiany stanu na jednym z cyfrowych wejść.

Magistrala I2C

Układ PCF8574 komunikuje się z Arduino za pomocą magistrali I2C. Jest to synchroniczna magistrala szeregowa wykorzystywana powszechnie w sprzęcie RTV. Szeregowa oznacza, że bity są przesyłane jednym pinem po kolei – pinem SDA. Synchroniczna oznacza, że każdy wysłany bit jest zatwierdzany sygnałem na drugim pinie – SCL. Synchroniczność przyczynia się do zwiększenia prędkości komunikacji i eliminuje błędy transmisji.

Magistrala I2C ma też własny protokół komunikacji, dzięki któremu do jej sygnałów SDA/SCL można podłączyć więcej niż jeden układ scalony. Każdy układ w magistrali ma swój adres. Adres jest to liczba, która identyfikuje układ, wybierając tą liczbę masz pewność, że dane które wysyłasz trafią do właściwego układu. Układ PCF8574 ma dodatkowo piny A0, A1, A2 za pomocą których możesz konfigurować część adresu. Pozwala to na podłączenie do magistrali więcej takich samych układów scalonych, dzięki modyfikacji stanów logicznych na tych pinach. Trzy piny adresu oznaczają, że możesz podłączyć do magistrali 8 takich układów scalonych, co da 64 dodatkowe piny cyfrowe. Jeśli i to jest za mało to istnieje też wersja układu PCF8574A, która ma zmieniony adres, co sprawia, że oprócz tamtych 8 można podłączyć jeszcze 8 układów i mieć w sumie 128 pinów.

W Arduino magistrala I2C ze względu na problemy licencyjne nazywa się TWI (TwoWires [dwa przewody] – od liczby przewodów używanych przez nią). Mikrokontroler AVR zawarty na płytce zawiera sprzętową obsługę tej magistrali. Jej wyprowadzenia znajdują się na dwóch ostatnich pinach grupy “ANALOG IN”. Sygnał danych SDA znajduje się na pinie 4, a sygnał zegarowy SCL na pinie 5.

Adres układu PCF8574

Układy z rodziny PCF8574 mają 7 bitowy adres. Pierwsze 3 bity adresu nadaje mu użytkownik, przez ustawienie pinów A0, A1, A2. Kolejne 4 są nadane fabrycznie na stałe. Układ PCF8574 ma je ustawione na 0100, a układ PCF8574A ustawione na 0111.

Binarny adres układu PCF8574

0

1

0

0

A2

A1

A0

Co oznacza, że minimalny adres (dla wszystkich A = LOW) to dziesiętnie 32, szesnastkowo 0×20. Maksymalny adres (dla wszystkich A = HIGH) to dziesiętnie 39, a szesnastkowo 0×27.

Binarny adres układu PCF8574A

0

1

1

1

A2

A1

A0

Co oznacza, że minimalny adres (dla wszystkich A = LOW) to dziesiętnie 56, szesnastkowo 0×38. Maksymalny adres (dla wszystkich A = HIGH) to dziesiętnie 63, a szesnastkowo 0x3F.

Podłączenie do Arduino

Podłączenie nie sprawia trudności i wykorzystuje tylko 4 przewody. Vcc układu łącze z pinem 5V Arduino, GND układu łącze z pinem GND Arduino w sekcji zasilania. Sygnały SDA łącze z pinem 4, a SCL z pinem 5 sekcji “ANALOG IN” Arduino.

Ponieważ używam w tej konfiguracji tylko jednego układu, podłączyłem wszystkie linie adresowe w stan niski (LOW) łącząc je z GND. Jeśli w twojej konfiguracji jest więcej takich samych układów, powinieneś do linii adresowych stosować różne kombinacje stanów (LOW – GND, HIGH – 5V) innych dla każdego układu. Pozostałe piny P0 do P7 możesz wykorzystywać dowolnie jako piny cyfrowe (DIGITAL), jak to robisz w Arduino.

Programowanie układu PCF8574

Przygotowanie

#include <PCF8574.h>
#include <Wire.h>

PCF8574 expander;

void setup()
{
  expander.begin(0x20);
}

void loop()
{
}

Jak zawsze społeczność Arduino nie zawiodła i jeden z miłośników platformy przygotował odpowiednią bibliotekę. Aby rozpocząć komunikacje z układem PCF8574 musisz najpierw podłączyć 2 biblioteki – PCF8574.h (dostarczającą funkcje obsługi układu) i Wire.h (do obsługi magistrali TWI).

Następnie trzeba zadeklarować obiekt klasy “PCF8574”, który nazwałem “expander”. Deklaruje się to jak inne zmienne czyli “PCF8574 expander;”.

Potem w funkcji “setup” należy za pomocą metody “expander.begin” ustawić adres układu, który ma reprezentować zadeklarowany obiekt. W tym przypadku ustawiłem wartość szesnastkową 0×20 odpowiadającą układowi z liniami adresowymi w stanie “LOW” – jak na schemacie.

Read the rest of this entry »

Sygnalizator revisited czyli przyciski i debouncing

wtorek, wrzesień 13, 2011 7:02 by netmaniac | Filled in arduino, dla początkujących

Dawno temu pisałem o tym jak można korzystać z przycisków (tactile switch, pushbutton) na przykładzie kartonowego sygnalizatora. Wróćmy do tematu, tym razem na poważniej zajmując się przyciskami.

Jeśli spojrzycie na kod tamtego sygnalizatora, możecie dostrzec następującą pętlę loop:

void loop()
{
  val = digitalRead(buttonPin);
  if (val == HIGH && prev == LOW) {
    next_status();
  }
  prev = val;
  display_status();
  delay(50);

}

Wykrywanie naciśnięcia odbywa się przez porównanie bieżącej wartości wejścia cyfrowego z poprzednią. Jeżeli aktualna wartość to HIGH a poprzednia to LOW, to wykonujemy akcję next_status(), która zmienia stan sygnalizatora. I wszystko działa. Ale nie ma problemów, tylko dzięki ostatniej linii kodu w loop: delay(50);.

Przycisk jest urządzeniem mechanicznym i włączenie lub wyłączenie nie jest jednoznaczne, jeżeli będziemy stan przycisku badać dostatecznie często. W momencie dociskania/zwalniania przycisku jest taki moment, w którym styk już łapie/puszcza przez co włączenie wyłączenie nie jest czystym przełączeniem między LOW a HIGH tylko migotaniem. Angielska Wikipedia przy haśle contact bounce (bo tak się zjawisko to nazywa) ma ten piękny obrazek ilustrujący ten problem:

Ilustracja migotania przełącznika (CC Wikipedia)

Ilustracja migotania przełącznika (CC Wikipedia)

Opóźnienie w loop załatwia nam ten problem, bowiem jeżeli zauważymy zmianę stanu klawisza odczekanie 50 ms przed następnym odczytem zapewnia margines wykluczający błędny odczyt. Gdyby nie ono, wówczas w następnym przebiegach moglibyśmy odczytać migotanie jako kolejne przyciśnięcia. Rezultat? Z punktu widzenia użytkownika – on nacisnął przycisk jeden raz, a sygnalizator przeskoczył o 1 lub więcej stanów za jednym razem.

Rozwiązanie z użyciem delay nie zawsze jest dopuszczalne, bo może nasze Arduino musi robić coś więcej niż tylko czekać na naciśnięcie klawisza.

Jak się zabezpieczyć przed złymi odczytami?

Read the rest of this entry »

Rezystor – co o nim trzeba wiedzieć

poniedziałek, wrzesień 5, 2011 22:39 by netmaniac | Filled in dla początkujących, howto

Rezystory to jeden z podstawowych elementów wszystkich układów elektronicznych. Jako, że jest to element naprawdę podstawowy i powszechny, spróbujemy się o nim dowiedzieć kilku rzeczy.

Teoria związana z przepływem prądów w układach RLC (R – rezystory, L – cewki, C – kondensatory) jest cała najeżona wzorami, których nie chcecie widzieć :) (a jeżeli ktoś musi się przekonać to np zobaczcie to i wrócicie zaraz tutaj :) ).

Teraz podam kilka uproszczeń, które wystarczą do zmierzenia się z użyciem rezystorów w kontekście Arduino.

Ważną cechą układów elektronicznych jest to, że jeżeli mamy jakieś napięcie w układzie (coś je wytwarza), to jeżeli obwód jest zamknięty (nie ma żadnej przerwy), napięcie to się odłoży na wszystkich elementach w taki sposób, że sumując napięcia na każdym elemencie z osobna otrzymamy napięcie z naszego źródła. Jest to zgodne z intuicyjnym rozumieniem – napięcie nie bierze się znikąd i nie może nigdzie znikać.

Read the rest of this entry »

Gameduino – czyli zróbmy fajną grę

poniedziałek, sierpień 1, 2011 23:50 by netmaniac | Filled in arduino, projekty

W zasadzie od momentu kiedy w ofercie Nettigo pojawił się pierwszy czujnik przyspieszenia miałem ochotę zrobić pewien hack i wykorzystać go jako główny czujnik do jakiegoś urządzenia sterującego. Myślałem o grze sportowej i podłączeniu czujnika do komputera. Ale, czasu ciągle brakowało, a przynajmniej odnosiłem wrażenie, że hack będzie długi i skomplikowany. Odczyt z czujnika przetworzyć na ciąg wciśnięć klawiszy w komputerze. Arduino, serial, jakaś biblioteka odczytująca serial, potem emulacja wciśnięcia klawisza. Może to wszystko okaże się prostsze niż wygląda, ale sprawiało wrażenie dużej liczby ruchomych części, a co za tym idzie większą szansę niepowodzenia. No cóż, po prostu nie chciało mi się :)

Gdy pojawiło się Arduino UNO, przez chwilę pojawiła się chęć przetestowania klawiaturowego firmware dla ATmega8u2 w ramach tego właśnie hacku, ale projekt nie wystartował. Co UNO zmieniało? Otóż z nowym firmware, podłączone do komputera widziane jest jako klawiatura więc odpada cała komplikacja z odczytem seriala i wysyłania komunikatów do gry. Po prostu Arduino może ‘wciskać klawisze’ przez port USB i całość wydaje się znacznie prostsza.

Ale gdy tylko zobaczyłem Gameduino wiedziałem że to jest to czego potrzebowałem. Ostatnie dni deszczowe były, tak więc – do dzieła!

Read the rest of this entry »

Czytnik RFID 125 kHz

środa, czerwiec 15, 2011 12:47 by netmaniac | Filled in arduino, dla początkujących, howto

Od dłuższego czasu w ofercie Nettigo jest czytnik kart RFID z interfejsem UART. Co to znaczy?

System RFID w uproszczeniu składa się z dwóch elementów – czytnika i tokenów. Token to fizyczna rzecz, mająca w sobie trochę elektroniki. Każdy token ma swój unikalny numer. Gdy token zostanie zbliżony do drugiego elementu – czytnika, ten będzie w stanie odczytać ten numer.

Jakie ma to zalety? Po pierwsze – token nie musi mieć swojego zasilania (wbudowanej baterii). Może być mały, lekki i odporny na wodę. Po drugie – nie jest potrzebny fizyczny kontakt tokena i czytnika – wystarczy token zbliżyć na kilka, kilkanaście centymetrów aby go odczytać.

Na rynku dostępnych jest kilka standardów, jeden z pierwszych to standard działający na częstotliwości 125 kHz. Urządzenia i tokeny do niego są proste a co za tym idzie niedrogie.

Jak skorzystać z RFID?

Pokażę teraz jak wygląda odczytywanie tokenów. Zestaw RFID sprzedawany przez Nettigo składa się z czytnika oraz 5 tokenów – 2 kart oraz trzech breloczków. Przyjrzyjmy się jednemu.

Token RFID

Token RFID

Jest na nim numer. Ale to nie numer, który zostanie odczytany przez czytnik. Dlaczego? Gdyby numer nadrukowany na tokenie oraz ten ‘w środku’ były takie same, łatwo byłoby sklonować token, a tak aby poznać jego numer trzeba go odczytać – nie jest to jakiś duży problem, ale zawsze utrudnienie.

Teraz – jak go odczytać. Przyjrzyjmy się czytnikowi. Jak napisałem ma interfejs UART, czyli można go podłączyć do Arduino korzystając z portu szeregowego, lub do komputera za pomocą konwertera USB/Serial.

Czytnik ma trzy złącza: 2, 3, i 5 pinów. Złącze dwu-pinowe jest przeznaczone na antenę dołączoną do zestawu. Nie ma znaczenia jak ją podłączymy. Pozostałe dwa złącza pozwoli opisać to zdjęcie:

Wyprowadzenia czytnika RFID

Wyprowadzenia czytnika RFID

Zaznaczyłem na czerwono piny nr 1 i tak złącze trzy-pinowe:

  1. LED – podłączenie dla diody sygnalizującą pracę czytnika (świeci ciągle gaśnie na chwilę w momencie odczytania tokena)
  2. +5V – wyprowadzone napięcie zasilania
  3. GND – masa

Jak widać rola jego jest informacyjna – można podłączyć diodę, która będzie ludziom sygnalizować że został odczytany token.

Złącze 5-cio pinowe to jest to co nam jest potrzebne aby uzyskać informację od czytnika:

  1. TX – czyli to co odczytuje czytnik
  2. RX – dane wysyłane do czytnika (ten model nie jest konfigurowalny, więc nie ma pożytku z tego)
  3. NC – nie używany
  4. GND – masa
  5. +5V – zasilanie

Zademonstruję podłączenie czytnika do komputera z wykorzystaniem konwertera USB/Serial – FTDI Friend. Podłączamy TX (pin 1) czytnika do RX na FTDI Friend, masę i zasilanie 5V – i gotowe:

Podłączenie czytnika RFID do FTDI Friend

Podłączenie czytnika RFID do FTDI Friend

Po wsadzeniu kabla USB do komputera i podpięciu monitora portu szeregowego (Hyperterminal na Windows, gtkterm lub minicom na Linuxie, lub monitor z Arduino IDE, parametry portu 9600,8,N,1) dostaniemy np taki ciąg:

0041DEF392FE

Najpierw – bajt o wartości 0×02 – symbol startu numeru karty, potem 5 bajtów zapisanych w ASCI w kodowaniu szesnastkowym (tutaj to 00-41-DE-F3-92), dwa znaki sumy kontrolnej (XOR 5 bajtów, tutaj to FE), bajt o wartości 0×03 – znak końca transmisji.

W dużym skrócie – to tyle. Podłączyć do Arduino można ‘tak samo’. Czyli podłączamy zasilanie i masę, oraz RX na Arduino (D0) z TX na czytniku i korzystając z Serial.read() można odczytywać dane.

Cały układ podłączony via FTDI Friend wygląda tak:

Caly uklad

Caly uklad

 

 

 

Debouncing klawiszy w Nettigo Keypad (i nie tylko)

sobota, czerwiec 4, 2011 10:25 by netmaniac | Filled in arduino, dla początkujących

Biblioteka do Nettigo Keypada została uaktualniona o domyślne wsparcie dla debouncingu. O co chodzi? Jeżeli naciskasz klawisz, może pojawić się zjawisko migotania podczas włączania i wyłączania przycisku. Wynika to właściwości styku mikroprzełącznika – jest to mechaniczne zetknięcie/rozłączenie styków, więc może być tak że pojawi się szereg impulsów podczas jednego przełączenia.

Gdy Arduino często sprawdza stan klawisza takie migotanie może zostać odczytane jako wielokrotne naciśnięcia i zwolnienia danego przycisku, mimo że został naciśnięty tylko raz.

Jak temu zapobiegać?

Potrzebny jest tak zwany debouncing. Można po odczycie stanu klawisza poczekać chwilę używając delay. Proste i skuteczne.

Pseudokod realizujący to wygląda tak:

obecny_stan_klawisza = odczytaj_klawisz();
if (obecny_stan_klawisza != poprzedni_stan) {
  poprzedni_stan = obecny_stan_klawisza;
  zrob_cos_po_zmianie_stanu();
  delay(50);
}

Taki kod po wykryciu zmiany stanu klawisza zapisuje jego nową wartość , wykonuje co trzeba po naciśnięciu/zwolnieniu klawisza i czeka 50 milisekund, zanim podejmie dalsze wykonywanie programu. Jeżeli więcej nic specjalnie szkic nie ma do roboty to zapobiegnie to odczytaniu migotania w następnym przebiegu loop.

Takie rozwiązanie, choć proste ma jednak jedną niezaprzeczalną wadę – Arduino nic nie może innego robić w czasie gdy czeka korzystając z funkcji delay. Nie zawsze jest to dopuszczalne. Jeżeli nasz szkic robi coś co wymaga szybkiej pracy, lub ścisłych zależności czasowych (generowanie obrazu, programowe implementacje różnych protokołów) takie rozwiązanie jest wykluczone.

Co zostaje? Trzeba zignorować wszystkie zmiany stanu klawisza w krótkim czasie po zaobserwowaniu jego wciśnięcia. Zapamiętujemy czas wciśnięcia klawisza i jeżeli zmiany stanu następują w krótkim czasie po tym – są ignorowane. Znowu – pseudokod:

obecny_stan_klawisza = odczytaj_klawisz();
if (obecny_stan_klawisza != poprzedni_stan && millis() - czas_ostatniego_wcisniecia > timeout) {
  poprzedni_stan = obecny_stan_klawisza;
  czas_ostatniego_wcisniecia = millis()
  zrob_cos_po_zmianie_stanu();
}

Oczywiście, na starcie szkicu musimy ustawić wartość czas_ostatniego_wcisniecia i timeout – zostawione bez nadanej wartość mogą stwarzać problemy. Czas jaki trzeba czekać ignorując zmiany stanu zależy od przełącznika. W wypadku mikroprzełączników 50 ms wystarcza w zupełności.

Dla wejść cyfrowych są gotowe biblioteki do obsługi debouncingu np Bounce, jednak dla naszego keypada ona się nie nadaje.

Dlatego biblioteka do Nettigo Keypada ma od wersji 0.3 domyślnie debouncowane klawisze. Domyślnie timeout jest ustawiony na 50 ms. Można go odczytać/zmienić przez funkcje getDebounceDelay i setDebounceDelay.

P4A – PHP dla Arduino cz. 2

czwartek, maj 19, 2011 10:20 by netmaniac | Filled in arduino, dla początkujących

W poprzednim wpisie skończyliśmy w miejscu w którym Webduino mogło nam już serwować dowolne pliki z karty SD. Teraz musimy wybrane pliki przepuścić przez nasze PHP :) i rezultat przesłać do klienta.

Dla uproszczenia całego procesu, zakładamy, że każdy plik który ma zostać poddany obróbce jest nam znany. Tzn rejestrujemy każdy taki plik (URL) za pomocą addCommand.

Następnie jak to ma działać? Idea jest taka, że mamy swoje funkcje w kodzie szkicu i których wynik działania ma zostać wklejony w wybrane miejsca kodu HTML. Czyli chcemy mieć plik HTML z takim kawałkiem kodu:

<p>
Wynik odczytu czujnika 1: MAGIA1<br/>
Wynik odczytu czujnika 2: MAGIA2<br/>
</p>

Na skutek działania naszego parsera chcemy MAGIA1 i MAGIA2 mieć zastąpione przez wynik działania funkcji w szkicu.

Najlepiej zacząć od magii!

Czyli jak zapisać w HTMLu że ma nasz parser wsadzić w to miejsce inny tekst. Dla uproszczenia przyjmujemy następującą formułę: #{X} zostanie zastąpione przez wywołanie odpowiedniej funkcji. X to jest jednoliterowy mnemonik określający którą funkcję chcemy wywołać.

Funkcje muszą mieć określoną definicję i nie mogą przyjmować argumentów. Dlaczego? Przyjmowanie argumentów komplikuje parser i na tym etapie nie jest chyba potrzebne.

Wybraliśmy sposób zapisu w HTML. Teraz, jak nasza funkcja ma przekazać wynik działania? Otóż zakładamy że przykładowa funkcja ma mieć następującą definicję:

void timeReport(char *buf) { itoa(millis()/1000, buf, 10); };

Funkcja ma nie zwracać danych (void) a przyjmować wskaźnik na bufor tekstowy. W tym buforze ma umieścić wynik swojego działania, który następnie zostanie wstawiony w odpowiednie miejsce HTML. Funkcja sama musi pilnować, żeby nie przepełnić tego bufora. Jego rozmiar jest definiowany przez P4A_MAX_FUNCTION_RESPONSE_LENGTH.

Jak widać powyższa funkcja zwraca liczbę pełnych sekund które minęły od uruchomienia lub resetu Arduino.

Jak parsować plik? Dzięki prostemu znacznikowi naszej magii jest to względnie proste. Czytamy plik znak po znaku. Jeżeli natkniemy się na # wówczas czytamy następny znak i sprawdzamy czy jest to klamra { tworzące w sumie sekwencję otwierającą naszej magii. Póki nie natkniemy się na # znaki nas nieinteresujące wysyłamy do bufora, który zostanie w końcu wysłany do przeglądarki.

Jeżeli następny znak po # nie jest klamrą wysyłamy do przeglądarki oba znaki # i następny – w tym miejscu nie mamy nic do robienia, czekamy na następny #.

Gdy jednak drugi znak to była klamra, wówczas czytamy kolejny znak – jest to nasz mnemonik! Wywołujemy odpowiednią funkcję w zależności od mnemonika, wysyłamy wynik do przeglądarki.

Następnie czytamy plik aż do zamykającej klamry i skanujemy dalej plik szukając kolejnego #.

Pozostaje nam kwestia przypisania funkcji do mnemoników. Posłuży nam do tego

Tablica wskaźników do funkcji

Pomówimy teraz o trochę bardziej zaawansowanym temacie, czyli o wskaźnikach do funkcji. Otóż jak się dobrze zastanowić, funkcja po skompilowaniu jest adresem pod którym znajduje się kod ją realizujący oraz pewien kontrakt określający jak przekazywane są dane do funkcji i jak z niej są zwracane.

Jeżeli kontrakt jest taki sam w wypadku wielu funkcji (czyli lista typów argumentów oraz zwracana wartość), funkcje takie można zapisać w postaci samego wskaźnika i trzymać w tablicy. Wówczas możemy wywołać taką funkcję używając zapisanego wskaźnika, nie musimy znać jej nazwy w kodzie.

To właśnie nam posłuży jako mechanizm tłumaczący mnemoniki na wywoływane funkcje. Teraz wiemy czemu wszystkie nasze funkcje muszą mieć taki sam interfejs/kontrakt (jak ustaliliśmy będzie do void jeżeli chodzi o zwracane dane oraz char * jako argument) – dzięki temu możemy je trzymać w jednej tablicy, której indeks będzie literą. Ale po kolei:

void (*_fcts['z'-'a'])(char *);

Definiujemy tablicę wskaźników na funkcje. void z przodu określa typ zwracany przez funkcję, to co w nawiasie na końcu oznacza jakich argumentów spodziewa się funkcja. W środku znajduje się deklaracja tablicy. Jej rozmiar 'z'-'a' może wydawać się dziwny, ale w takim kontekście znaki przez kompilator traktowane są jako bajty. Czyli od kodu ‘z’ odejmujemy kod ‘a’ – różnica to liczba liter. Dzięki temu mamy tablicę mogącą pomieścić tyle wskaźników na funkcje ile jest małych liter w alfabecie łacińskim (lub raczej w standardzie ASCII).

Jeśli będziemy mieli jakiś kod litery wystarczy od niej odjąć kod litery a a dostaniemy indeks z tablicy. Zresztą zobaczmy:

				if (_fcts[c[0]-'a'] == NULL) {
					bufferedSend(server,"n/a");
					continue;
				} else {
					//Call function from table
					_fcts[ c[0]-'a'](buf);
					//Write response to client
					bufferedSend( server, buf );
				}

c[0] zawiera znak naszego mnemonika. Jeżeli tablica nie ma wartości (tzn ma wartość NULL) pod indeksem c[0] - 'a' wówczas w HTML jest wstawiane n/a – nasz sposób na sygnalizowanie złych mnemoników.

'a' w ASCII ma kod 97. Jeżeli nasz mnemonik to też 'a' 97-97 = 0, czyli pierwszy element tablicy. Jeżeli mnemonik to 'b' to 'b'-'a' = 98 – 97 = 1 czyli drugi element tablicy, itd. Przydałoby się sprawdzać czy mnemonik jest we właściwym zakresie, bo inaczej możemy próbować wywołać funkcję z losowym adresem (jeżeli indeks tablicy jest poza zakresem, wówczas pamięć RAM spoza tablicy zostanie odczytana i procesor spróbuje zinterpretować wartość spod tego adresu jako adres funkcji do wywołania – gwarantowane zawieszenie się programu).

W powyższym kodzie widać też, jak należy wywoływać funkcje w tablicy:

_fcts[ c[0]-'a'](buf);

Do analizy pliku wykorzystujemy zmienną status, dzięki której wiem w jakim stanie nasz parser się znajduje. Może to być:

  • P4A_PARSE_REGULAR – stan w którym szukamy znaku #
  • P4A_PARSE_HASH – stan w którym czekamy na klamrę otwierającą
  • P4A_PARSE_COMMAND – stan w którym szukamy mnemonika

Dzięki tym stanom łatwiej kontrolować co robi nasz parser. W zależności od bieżącego stanu oraz kolejnego znaku można podejmować decyzje co dalej. Wysyłać dane do przeglądarki czy wywoływać funkcję na podstawie mnemonika.

Dla zainteresowanych całość funkcji parseP4A:

//Reads HTML file, parses looking for our macro and sends back to client
int parseP4A( char * filename, WebServer &server ) {
	//simple status
	short int STATUS = P4A_PARSE_REGULAR;
	char c[2];
	c[1] = 0;

	//buffer to hold response from functions - there is no boundary checking, so
	//function has to not overwrite data
  char buf[P4A_MAX_FUNCTION_RESPONSE_LENGTH];

  if (! file.open(&root, filename, O_READ)) {
		return -1;
  }
  while ((file.read(c,1) ) > 0) {
		if (STATUS == P4A_PARSE_REGULAR && c[0] == '#')
		{
			//hash was found we need to inspect next character
			STATUS = P4A_PARSE_HASH;
			continue;
		}

		if (STATUS == P4A_PARSE_HASH) {
			if (c[0] == '{') {
				//go to command mode
				STATUS = P4A_PARSE_COMMAND;
				continue;
			} else {
				//fallback to regular mode, but first send pending hash
				STATUS = P4A_PARSE_REGULAR;
				bufferedSend(server, "#");
			}

		}

		if (STATUS == P4A_PARSE_COMMAND) {
			if(c[0] == '}') {
				STATUS = P4A_PARSE_REGULAR;
				continue;
			};
		  if (c[0] >= 'a' && c[0] <='z') {
				//Command found
				if (_fcts[c[0]-'a'] == NULL) {
					bufferedSend(server,"n/a");
					continue;
				} else {
					//Call function from table
					_fcts[ c[0]-'a'](buf);
					//Write response to client
					bufferedSend( server, buf );
				}
			}

		}

		if (STATUS == P4A_PARSE_REGULAR)
			bufferedSend(server, c);
  }

  //force buffer flushing
  flushBuffer(server);
	file.close();
  return 0;
}

P4A czyli PHP for Arduino w akcji

Załóżmy, że chcemy zrobić ładny wirtualny barometr, ale pokazujący prawdziwe ciśnienie. Skorzystamy z BMP085 – jest to poręczny adapter (breakout board) czujnika ciśnienia i temperatury  produkowany przez SparkFun. Rezultat ma być następujący:

Wygląd wirtualnego barometru

Wygląd wirtualnego barometru

Wskazówka ma pokazywać wartość odczytaną z czujnika, a symbol prognozowanej pogody ma się zmieniać w zależności od wartości ciśnienia.

Jak sobie poradzimy z tym, że jak pisaliśmy w poprzednim odcinku, serwer WWW na Arduino, nie jest najlepszym rozwiązaniem, gdy trzeba serwować wiele plików jednocześnie (obrazki!)? Ano, skoro całość i tak ma być dostępna z sieci, czemu nie udostępnić statycznych zasobów z serwera sieci? Na swoje potrzeby mam taki serwer i wszystkie potrzebne elementy graficzne są na nim umieszczone. Czyli tarcza barometru, obraz wskazówki i symbole pogody.

Na Arduino znajduje się sam plik HTML, na karcie SD. W szkicu umieszczamy funkcję wywołującą parser dla tego pliku:

void index(WebServer &server, WebServer::ConnectionType type, char *, bool){
  server.httpSuccess();
  if (!parseP4A("BARO.HTM", server)) {
		Serial.println("P4A: -1");
	}
};

parseP4A to funkcja, która parsuje podany plik i wysyła rezultat korzystając z obiektu serwera Webduino. Pozostaje zarejestrować naszą funkcję jako domyślną komendę:

webserver.setDefaultCommand(&index);

Sam HTML wykorzystuję JaveScript i obiekt canvas do narysowania samej tarczy. Robi to funkcja draw, która jako argument przyjmuje ciśnienie w hektopaskalach. Gdy strona jest gotowa do wyświetlenia (tzn załadowała się) przez atrybut onload wywołujemy funkcję draw, ciśnienie jest wstawiane przez nasze P4A:

<body onload="draw(#{p});"

Mnemonik p trzeba skojarzyć z właściwą funkcją. W szkicu, w setup ustawiamy funkcję dla p:

  _fcts['p'-'a'] = pressureReport;

A samo pressureReport ma wygląd:

void pressureReport(char *buf) {
	bmp085_read_temperature_and_pressure (&temperature, &pressure);
	itoa(pressure/100.0, buf, 10);
	Serial.print ("temp & press: ");
	Serial.print (temperature/10.0);
	Serial.print(" ");
	Serial.println (pressure/100.0);

};

Serial jest używany do sprawdzania czy wartości są jak się spodziewamy i nie ma wpływu na działanie barometru. bmp085_read_temperature_and_pressure to funkcja z kodu obsługi BMP085 zaczerpnięta z fińskiego bloga.

Całość kodu do ściągnięcia tutaj. Jest to szkic napędzający nasz serwer, plus plik HTML i grafiki. Tarcza barometru, grafika i kod HTML/JS autorstwa Sprae.

Instalacja

Ściągnąć, rozpakować w sketchbook. Zawartość podkatalogu html wrzucić w główny katalog na karcie SD, wsadzić ją w shielda. Szkic poprawić podając właściwy MAC i IP adres. Po otwarciu strony głównej powinniśmy zobaczyć barometr, o ile posiadamy BMP085 ;)

Kilka uwag na koniec

Kod jest wersją beta :) tzn – działa na tyle ile moje testy to potwierdzają, może być (i na pewno jest) kilka błędów o których nie mam pojęcia :)

Kod należałoby uporządkować – np funkcje związane z buforowanym wyjściem powinny zostać przeniesione do kodu Wbeduino. Planuję to zrobić i wszystkie zmiany jakie w Webduino zostały wprowadzone wysłać do developerów Webduino – może coś z tego znajdzie się bezpośrednio w bibliotece.

Jestem ciekaw waszych opinii. Czy coś takiego jak P4A ma w ogóle sens i może być przydatne?

P4A – PHP dla Arduino cz. 1

czwartek, maj 5, 2011 0:45 by netmaniac | Filled in arduino, dla początkujących, howto

Gdy poznamy już trochę Arduino w głowie każdego prędzej czy później pojawi się pomysł na projekt, który wymaga aby Arduino mogło połączyć się z siecią. Czy to będzie automatyka domowa dostępna przez sieć, czy zestaw czujników raportujący odczyty do bazy danych – trzeba jakoś połączyć Arduino do Internetu. I tu z pomocą przychodzi nam Ethernet Shield.

Najpierw trochę historii. Ethernet Shield był początkowo kompatybilny z małym Arduino. Dlaczego nie z Mega? Otóż do komunikacji z układem W5100 będącym sercem shielda wykorzystywany jest protokół SPI – na cyfrowych wejściach nr 10, 11, 12 i 13. W Arduino Mega SPI jest na innych wejściach. Można to obejść wyginając nóżki shielda i podłączając je do właściwych wejść cyfrowych kabelkiem.

Ale to dotyczy starszych shieldów. Obecnie sprzedawane przez Nettigo Ethernet Shieldy są kompatybilne zarówno z małymi Arduino (UNO, Duemilanove) jaki i Mega (te z procesorami ATmega1280 i ATmega2560) . Po czym poznać takiego shielda? Jeżeli jest na nim gniazdo kart microSD – znaczy to, że to jest nowsza wersja.

Ethernet Shield z gniazdem kart microSD

Ethernet Shield z gniazdem kart microSD

 

 

Wspomniany już został W5100 – układ scalony koreańskiej firmy WIZnet, napędzający Ethernet Shielda. Różni się tym od wielu innych kontrolerów Ethernet, że stos TCP/IP jest zaimplementowany bezpośrednio w układzie scalonym.  Co to znaczy dla użytkownika? Że biblioteka, którą musisz wykorzystać aby komunikować się ze światem potrzebuje mniej pamięci RAM i zajmuje mniej pamięci flash w porównaniu z układami nie mającymi na sobie stosu TCP/IP.

Jak korzystać z Ethernet Shielda?

Jest wiele przykładów w sieci jak tworzyć strony WWW wyświetlające dane z Arduino. Jednak, jeżeli masz już większe  doświadczenie z programowaniem Arduino to pewnie wiesz, że wszystkie łańcuchy znakowe, nawet te zdefiniowane w kodzie zajmują RAM, którego w ATmedze zawsze mało.

Weźmy oficjalny przykład z Arduino Tutoriala. Kod:

 client.println("HTTP/1.1 200 OK");
 client.println("Content-Type: text/html");
 client.println();

Zajmie nam 40 bajtów RAMu (15 znaków w HTTP… + kończące zero i 23 w Conte… + kończące zero). Łatwo sobie wyobrazić co to znaczy gdy mamy 2kB do dyspozycji w ogóle. Strona HTML nie może być zbyt rozbudowana.

Istnieje pewne rozwiązanie, które może nam pomóc – czyli przechowywanie stringów w pamięci Flash. Pozwala to zmniejszyć użycie pamięci RAM, ale często kosztem dodatkowego kodu. Dostęp do stringów tak definiowanych wymaga użycia specjalnego makra i kompilator nie pozwoli nam korzystać z tego makra w wywołaniu funkcji oczekującej char * jako argumentu. Na dodatek – każda zmiana w kodzie HTML, który chcemy wysłać oznacza, że trzeba zmodyfikować szkic i wgrać go w Arduino.

Zaraz, przecież Ethernet Shield od dwóch wydań ma na sobie gniazda na karty microSD – nie można jakoś wykorzystać przestrzeni jaką dają karty SD? Można, ale trzeba się trochę postarać.

Najpierw – Ethernet Shield musi zostać skonfigurowany do pracy w sieci – oznacza to ustawienie adresu MAC oraz IP. Można to zrobić tak:

byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
byte ip[] = { 192,168,1, 177 };

Ethernet.begin(mac, ip);

Adres MAC najlepiej, zamiast wymyślać samemu odczytać z naklejki jaka znajduje się na spodzie shielda. Adres IP zależy od konfiguracji sieci. Powyższa sekwencja Ethernet.begin będzie działać tylko w sieci lokalnej – tzn gdy wszystkie adresy IP znajdują się w jednej podsieci. Jeżeli shield ma się łączyć z hostami w innych sieciach (zarówno jako klient lub gdy ma służyć jako serwer) musicie podać jeszcze jeden argument – tablice z 4 liczbami – adres IP domyślnej bramki (default gateway). Więcej w opisie biblioteki Ethernet.

Większość klocków mamy prawie gotowych. Biblioteka Ethernet, wchodząca w skład Arduino IDE, nie jest tym co nam najbardziej będzie pasować. Ułatwia ona tworzenie m.in. serwera TCP, ale do serwera HTTP jeszcze trzeba kawałek. Dlatego przyda się nam Webduino – na bazie biblioteki Ethernet, ktoś za nas wykonał spory kawałek pracy budując serwer HTTP.

Normalnie ściągnęlibyśmy ze strony Downloads bibliotekę i rozpakowalibyśmy w sketchbook/libraries. Na nasze potrzeby będziemy modyfikować trochę Webduino,  będzie do ściągnięcia na końcu tego mini-tutoriala, więc na razie nic nie trzeba instalować.

Webduino – jak zaczać?

Na początek musimy wiedzieć, że za pomocą

void addCommand(const char *verb, Command *cmd);

możemy zarejestrować dowolną funkcję wywoływaną gdy URL będzie pasował do verb. Przykład:

webserver.addCommand("blob.htm", &blob);

Jeżeli URL będzie się zaczynał (tzn porcja po adresie Arduino) od blob.htm, wówczas wywołana zostanie funkcja blob, która musi przyjmować argumenty zgodnie ze zdefiniowanym typem Command:

typedef void Command(WebServer &server, ConnectionType type,

                       char *url_tail, bool tail_complete);

server to obiekt WebServer, dla którego nastąpiło wywołanie metody, type to rodzaj połaczenia (INVALID, GET, HEAD, POST), url_tail to jest to co zostało w URLu po dopasowanym blob.htm. Jeżeli URL został obcięty ze względu na niewielki bufor używany przez Webduino (ehh ta pamięć), to ostatni parametr tail_complete bedzie miał wartość false.

OK, ale to mało wygodne musieć rejestrować każdą funkcję, zwłaszcza, że chcemy serwować dane z karty SD, której zawartości nie znamy. Przyda się nam teraz setFailureCommand, która pozwoli zarejestrować funkcję w naszym kodzie wywoływaną, gdy nie nastąpiło żadne inne dopasowanie do zarejestrowanych funkcji przez addCommand.

Czyli jeżeli URL nie pasuje do żadnej zgłoszonej wcześniej funkcji, wówczas zostanie wywołana funkcja podana do setFailureCommand.

Teraz wystarczy w niej sprawdzić czy url_tail jest nazwą pliku na karcie SD (ponieważ nie nastąpiło żadne dopasowanie, url_tail zawiera pełny URL włącznie z pierwszym znakiem / za adresem Arduino). Gdy pliku nie ma – wyświetlamy HTTP 404, jeżeli jest – wystarczy go wysłać do klienta.

Jak odczytać plik z karty SD?

Podobnie jak z serwerem HTTP nie musimy robić wszystkiego sami. Dobrą pomocą do stworzenia tego szkicu był tutorial przygotowany przez Limor Fried czyli – LadyAda. Korzysta on z biblioteki SdFatLib, która ma wsparcie dla systemów plików FAT16 i FAT32 (czyli tego co zwykle na kartach SD i SDHC jest).

Kod inicjalizacji i obsługi plików w zasadzie został wzięty z tego tutoriala. Omówię tutaj funkcję fetchSD, która została zarejestrowana przez setFailureCommand. Jej zadaniem jest znaleźć plik na karcie i wysłać go do przeglądarki:

P(CT_PNG) = "image/png\n";
P(CT_JPG) = "image/jpeg\n";
P(CT_HTML) = "text/html\n";
P(CT_CSS) = "text/css\n";
P(CT_PLAIN) = "text/plain\n";
P(CT) = "Content-type: ";
P(OK) = "HTTP/1.0 200 OK\n";
void fetchSD(WebServer &server, WebServer::ConnectionType type, char *urltail, bool){
	char buf[32];
	int16_t  readed;

	++urltail;
	char *dot_index; //Where dot is located
	if (! file.open(&root, urltail, O_READ)) {
		//Real 404
		webserver.httpNotFound();
  } else {
	if (dot_index = strstr(urltail, ".")) {
		++dot_index;
		server.printP(OK);
		server.printP(CT);
		if (!strcmp(dot_index, "htm")) {
				server.printP(CT_HTML);

		} else if (!strcmp(dot_index, "css")) {
				server.printP(CT_CSS);

		} else if (!strcmp(dot_index, "jpg")) {
				server.printP(CT_JPG);

		} else {
				server.printP(CT_PLAIN);
		}
		server.print(CRLF);
	}
	readed = file.read(buf,30);
	while( readed > 0) {
		buf[readed] = 0;
		bufferedSend(server,buf,readed);
		readed = file.read(buf,30);
	}
	flushBuffer(server);
	file.close();
	}
}

Na samym początku mamy zarejestrowanych kilka stałych znakowych przechowywanych w pamięci flash.

P(CT_PNG) = "image/png\n";

Makro P jest częścią Webduino, które służy do zapisywania stringów na pamięci flash a nie w RAM. A stałe te to są nazwy różnych formatów danych, tzw MIME Type, jakie chcemy obsługiwać. O co chodzi? Przeglądarka nie wie jakie dane zostaną wysłane przez serwer. Czy to będzie HTML czy obrazek dowiaduje się ona właśnie z nagłówka Content-Type, o którym za chwilę.

Następnie ‘pozbywamy’ się wiodącego ukośnika: ++urltail;, potem próbujemy otworzyć plik na karcie SD – jeżeli nie udaje się – to wyświetlamy błąd HTTP 404 (Not Found):

	if (! file.open(&root, urltail, O_READ)) {
		//Real 404
		webserver.httpNotFound();
  } else {

jeżeli się udało otworzyć plik, to w else spróbujemy go odczytać i wysłać do klienta.

Teraz kilka uwag. Po pierwsze – SdFatLib obsługuje tylko krótkie nazwy w formacie 8.3. Jeżeli spróbujesz użyć dłuższych nazw (które FAT32 dopuszcza) to pamiętaj, że nazwa widziana przez SdFatLib będzie inna od tej, którą zobaczysz po podmonotwaniu karty w twoim komputerze. I jeżeli zrobisz plik index.html (cztery znaki w rozszerzeniu), wówczas nazwa będzie dla SdFatLib ind~1.htm. Cóż, nawet jeżeli teraz w komputerze zmienisz nazwę na index.htm, wpis w katalog będzie w rozszerzonej formie. Musisz skasować plik i utworzyć go na nowo z nazwą w formacie 8.3.

Druga uwaga jest taka – z oczywistych względów (FISI :) ) nie będziemy się przejmować katalogami i zakładamy że wszystkie pliki są w głównym katalogu. Może w późniejszych wersjach kodu dodam obsługę trochę bardziej skomplikowanych struktur.

OK, wracamy do kodu fetchSD. Skoro udało się nam otworzyć plik, to szukamy kropki w nazwie pliku i jeżeli ją znajdziemy to sprawdzamy czy pozostała część (w domyśle – rozszerzenie) będzie pasować do znanych nam rozszerzeń. Bo nie wystarczy nam wysłać danych do klienta HTTP – musimy wysłać nagłówek z informacją o właściwym Content-Type (mówiliśmy o tym już wcześniej), inaczej dane mogą zostać błędnie zinterpretowane przez przeglądarkę.

Słowo o tym jak wygląda odpowiedź serwera WWW. Podzielona ona jest na dwie części. Pierwsza to tak zwane nagłówki. Przeglądarka jako nagłówek traktuje wszystko to co na początku, aż natrafi na pustą linię tekstu (linie są oddzielane znakami CR LF). Reszta to właściwa odpowiedź. Jak co ona zostanie zinterpretowana, będzie zależało od nagłówków. Serwer może pomóc przeglądarce przez ustawienie nagłówka określającego typ danych:

Content-type: text/html

Do pierwszego dwukropka jest nazwa nagłówka (tutaj Content-Type) potem wartość nagłówka. Tutaj używane są tak zwane typy MIME. I tak może to być np image/png dla obrazka PNG, image/jpg dla JPG czy text/html dla pliku HTML.

Obowiązkowym nagłówkiem jest status – czyli czy żądanie klienta zostało obsłużone, czy wystąpił błąd a może przekierowanie. HTTP/1.0 200 OK znaczy – jest dobrze, będzie żądna treść. Najpierw jest protokół i jego wersja (HTTP w wersji 1.0) a następnie sam kod 200 – w świecie HTTP znaczy to że jest dobrze. Inne częste kody to 404 – nie znaleziono zasobu (sławne Not Found), 301 i 302 – przekierowania.

Wiedząc to, staramy się rozpoznać rozszerzenie pliku i wysłać odpowiedni nagłówek:

	if (dot_index = strstr(urltail, ".")) {
		++dot_index;
		server.printP(OK);
		server.printP(CT);
		if (!strcmp(dot_index, "htm")) {
				server.printP(CT_HTML);

		} else if (!strcmp(dot_index, "css")) {
				server.printP(CT_CSS);

		} else if (!strcmp(dot_index, "jpg")) {
				server.printP(CT_JPG);

		} else {
				server.printP(CT_PLAIN);
		}
		server.print(CRLF);

Funkcje z dużym P na końcu oczekują nie char * ale const prog_uchar *.

Mamy już wysłany nagłówek HTTP (zakończony pustą linią server.print(CRLF)), więc wyślemy same dane:

	readed = file.read(buf,30);
	while( readed > 0) {
		buf[readed] = 0;
		bufferedSend(server,buf,readed);
		readed = file.read(buf,30);
	}
	flushBuffer(server);
	file.close();

Czytamy po 30 bajtów, wysyłamy do klienta przez funkcję buforującą wysyłane dane. Po co? Otóż jeżeli użyjemy najprostszego rozwiązania i będziemy wysyłać dane znak po znaku, wówczas każdy znak będzie w odrębnym pakiecie TCP. Bardzo (naprawdę, uwierz, naprawdę) nieefektywne rozwiązanie. Po prostu server.write wysyła od razu dane.

Dlatego napisałem funkcję bufferedSend, która jako argumenty bierze obiekt serwera WWW, wskaźnik na bufor z danymi i rozmiar bufora. Czemu nie korzystamy z funkcji określających rozmiar bufora znakowego takich jak strlen? Bo działać to może tylko gdy dane są tekstowe. Jeżeli dane są binarne (obrazki) to znacznik końca łańcucha może pojawić się w legalnym strumieniu danych.

W C i C++ znakiem końca łańcucha jest znak 0 (nie cyfra, tylko bajt o wartości 0). Jeżeli w naszym strumieniu danych mogą pojawić się zera, wszystkie funkcje związane z łańcuchami znakowymi, a oferowane przez standardową bibliotekę nie przydadzą się nam

Z tego powodu musimy wprost określić ilość danych wysyłanych do bufora.

I w zasadzie to tyle. Mamy na Arduino serwer WWW wysyłający dane z karty SD.

Czy to ma sens?

Wystarczy kilka testów z bardziej złożoną stroną WWW (nie jeden plik HTML ale do tego jakieś CSS i obrazki), żeby przekonać się, że rozwiązanie to ma swoje ograniczenia. Arduino jest jednowątkowe, więc każdy element z naszego serwera WWW jest ściągany po kolei. Oznacza to, że z punktu widzenia użytkownika strona się wolno ładuje.

Więc po co to? Arduino może prezentować dane zbierane z czujników w przyjaźniejszej formie jeżeli nie będzie ograniczeniem ilość pamięci RAM potrzebnej bardziej rozbudowanej stronie WWW. Trzymając kod HTML na karcie SD pozbywamy się tego ograniczenia. Ale jak w HTMLu trzymanym na karcie SD umieścić dane zebrane z czujników przez Arduino?

Potrzebujemy czegoś, co pozwoli nam wstrzyknąć dane do HTMLa pomiędzy ‘odczytem’ a ‘wysłaniem’. Czyli coś jak:

PHP dla Arduino

OK, to jest na wyrost :) potrzebujemy czegoś co bardziej przypomina szablony niż pełne PHP, ale na początku PHP też nie powalało funkcjonalnością :)

O tym jak zrobić taki parser (i pełny kod szkicu) – w następnym odcinku. Stay tuned.