Zápisník experimentátora
Hierarchy: Sviečkové grafy
V dnešnom pokračovaní seriálu si naprogramujeme triedu, ktorá bude vykresľovať sviečkové grafy na displeji Nokia 5110. Bude vykresľovať údaje v reálnom čase a ako zdroj údajov nám poslúži modifikovaný kód z prvého blogu seriálu. Triedu si naprogramujeme pomocou šablón a bude sa skladať z jednej triedy, ktorá sa bude starať o zber údajov a tri triedy sa budú starať o vykresľovanie rôznymi spôsobmi.
Čaká nás síce zložitejší kód, v ktorom budeme používať C++ šablóny, ale výsledkom budú nádherné grafy, ktoré sa zobrazia na displeji Nokia 5110. Šablóny sa používajú najmä preto, aby mohol kompilátor optimalizovať kód pre konkrétny displej a nemusel zbytočne do programu zahrnúť riadky, ktoré by sa vo výsledku nepoužili.
Z priložených obrázkov je zrejmé, ako bude výsledok vyzerať. Budeme používať tri rôzne spôsoby zobrazenia:
Schéma zapojenia sa v ničom neodlišuje od predchádzajúcich zapojení s displejom Nokia 5110, kde sme nepotrebovali pripojiť žiadne ďalšie súčiastky. Iba Arduino, level shifter a displej Nokia 5110.
Budeme programovať dva zodrojové kódy:
Vysvetlenie hlavného programu.
#include <Adafruit_GFX.h>
#include <Adafruit_PCD8544.h>
#include "candlestick.h"
Tieto hlavičkové súbory potrebujeme na to, aby nám program fungoval. Dve knižnice od Adafruit, ktoré si nainštalujete v správcovi knižníc. Tretí súbor je náš kód, ktorý sa nachádza v tom istom adresári.
Adafruit_PCD8544 display = Adafruit_PCD8544(7, 6, 5, 4, 3);
// 12 bars, 10 sec/bar, bar renderer
OHLCChart<int, 12, 10000, OHLCNokia5110BarRender<int> > ohlc;
int value=24;
void setup() {
Serial.begin(9600);
// connect renderer to Nokia 5110
ohlc.getRender().setDisplay(&display);
// Nokia 5110 init
display.begin();
display.setContrast(60);
display.clearDisplay();
display.display();
}
Definícia objektu displeja. Definícia šablónového objektu ohlc, ktorý je typu OHLCChart. Tie záhadné konštrukcie v zátvorkách predstavujú parametre šablóny. Jednotlivé parametre majú nasledovný význam. Pozor na posledný parameter, tá medzera tam naozaj musí byť.
Posledný zaujímavý riadok je ohlc.getRender().setDisplay(&display);. Pomocou neho sa napojí renderer na premennú display, aby vykresľoval údaje priamo na displej Nokia 5110.
void loop() {
// update random data
int valuemove=random(3)-1;
value+=valuemove;
value=constrain(value,0,47);
//Serial.println(value);
ohlc.addValue(value);
// render chart
display.clearDisplay();
// draw candlesticks
ohlc.draw();
// draw scale
for(int j=0;j<=84;j+=2)
display.drawLine(j,0,j,(j%10==0 ? 2 : 1),BLACK);
for(int j=0;j<=38;j+=2)
display.drawLine(0,47-j,(j%10==0 ? 2 : 1),47-j,BLACK);
// draw last value
display.fillRect(5,5,15,11,BLACK);
display.fillRect(6,6,13,9,WHITE);
display.setCursor(7,7);
display.print(value);
// draw indicator
display.drawLine(82,47-value,83,47-value,BLACK);
display.drawLine(83,47-value+1,83,47-value-1,BLACK);
display.display();
delay(200); // 5 valus/second
}
Funkcia loop je dobre komentovaná v zdrojových textoch. V prvej časti generuje náhodné údaje a v druhej časti vykreslí všetky potrebné objekty na displej. Pre nás je podstatný riadok ohlc.draw(), ktorý žiada našu šablónu o vykreslenie grafu. Ostatné riadky okolo iba dopĺňajú údaje na displej, ktoré samotný sviečkový graf nevie vykresľovať.
V tomto hlavičkovom súbore je toho na vysvetlenia viac.
///
/// One OHLC bar data
///
template<typename ohlcvalue>
struct OHLCData {
ohlcvalue o;
ohlcvalue h;
ohlcvalue l;
ohlcvalue c;
};
Prvá šablóna nám definuje štruktúru OHLCData. V predchádzajúcom texte sme si vysvetlili, že ako parameter šablóny bude int. Šablóna sa teda pri kompilácii programu zmení tak, že všetky slová ohlcvalue sa zmenia na slová int.
///
/// Serial port renderer
///
template<typename ohlcvalue>
class OHLCSerialRender {
public:
void drawHeader() {
Serial.println("###");
}
void drawBar(int pos, OHLCData<ohlcvalue> *value) {
Serial.print(pos);
Serial.print(",");
Serial.print(value->o);
Serial.print(",");
Serial.print(value->h);
Serial.print(",");
Serial.print(value->l);
Serial.print(",");
Serial.print(value->c);
Serial.println("");
}
void drawFooter() {}
};
Toto je prvá trieda, ktorá vykresľuje údaje na sériová port. Aby ju bolo možné použiť, musí mať tri funkcie drawHeader, drawBar a drawFooter. Nie všetky funkcie musia mať aj vyplnené telo, podstatné je len to, aby tam boli a kompilátor ich mohol bez problémov využiť. Samotné využitie nájdete v tomto istom súbore vo funkcii OHLCChart::draw.
///
/// Nokia 5110 Line Candlestick renderer
///
template<typename ohlcvalue>
class OHLCNokia5110LineRender {
Adafruit_PCD8544 *display;
public:
OHLCNokia5110LineRender() : display(NULL) {}
void drawHeader() {
}
void drawBar(int pos, OHLCData<ohlcvalue> *bar) {
if(display==NULL)
return;
int START = 5 + pos * 6;
const int BW = 2;
const int HEIGHT = 47;
// open
display->drawLine(START,HEIGHT-bar->o,START+1*BW,HEIGHT-bar->o,BLACK);
// high - low
display->drawLine(START+1*BW,HEIGHT-bar->h,START+1*BW,HEIGHT-bar->l,BLACK);
// close
display->drawLine(START+1*BW,HEIGHT-bar->c,START+2*BW,HEIGHT-bar->c,BLACK);
}
void drawFooter() {}
void setDisplay(Adafruit_PCD8544 *d) {display=d;}
};
Toto je druhá trieda, ktorá vykresľuje údaje v podobe čiar. Je skoro kompletne prevzatá z predchádzajúceho blogu, iba sa v nej mierne zmenili názvy parametrov. Opäť má tri funkcie, ktoré robia niečo iné, než predchádzajúca trieda. Kreslí pomocou pointra display, ktorý sme jej nastavili v hlavnom programe volaním funkcie setDisplay.
///
/// Nokia 5110 Bar Candlestick renderer
///
template<typename ohlcvalue>
class OHLCNokia5110BarRender {
Adafruit_PCD8544 *display;
public:
OHLCNokia5110BarRender() : display(NULL) {}
void drawHeader() {
}
void drawBar(int pos, OHLCData<ohlcvalue> *bar) {
if(display==NULL)
return;
int START = 5 + pos * 6;
const int BW = 2;
const int HEIGHT = 47;
int top;
int bottom;
if(bar->o < bar->c) {
top = bar->c;
bottom = bar->o;
} else {
top = bar->o;
bottom = bar->c;
}
// high
display->drawLine(START+1*BW,HEIGHT-bar->h,START+1*BW,HEIGHT-top,BLACK);
// low
display->drawLine(START+1*BW,HEIGHT-bar->l,START+1*BW,HEIGHT-bottom,BLACK);
// bar
if(bar->o < bar->c)
display->drawRect(START,HEIGHT-bottom,2*BW+1,bottom-top+1,BLACK);
else
display->fillRect(START,HEIGHT-bottom,2*BW+1,bottom-top+1,BLACK);
}
void drawFooter() {}
void setDisplay(Adafruit_PCD8544 *d) {display=d;}
};
Tretia trieda sa líši od predchádzajúcej iba grafikou.
///
/// OHLC recorder and renderer
/// ohlcvalue - int or float
/// len - number of bars
/// stp - time window (10000 = 10 seconds)
/// renderclass - which renderer we want to use
///
template<typename ohlcvalue, const int len, const int stp, typename renderclass>
class OHLCChart {
OHLCData<ohlcvalue> data[len];
const int ohlc_step;
int ohlc_first;
OHLCData<ohlcvalue> *last;
unsigned long ohlc_next_step;
renderclass render;
public:
OHLCChart() : ohlc_step(stp), ohlc_first(len), ohlc_next_step(0) {
last = data + (len - 1);
}
renderclass& getRender() {
return render;
}
boolean addValue(ohlcvalue v) {
unsigned long m=millis();
if(m>ohlc_next_step) {
if(ohlc_first>0)
ohlc_first--;
for(int i=0;i<(len-1);i++)
data[i] = data[i+1];
last->o = v;
last->h = v;
last->l = v;
last->c = v;
ohlc_next_step+=ohlc_step;
return true;
} else {
if(last->h<v)
last->h=v;
if(last->l>v)
last->l=v;
last->c=v;
}
return false;
}
void draw() {
render.drawHeader();
for(int i=ohlc_first;i<len;i++)
render.drawBar(i,data+i);
render.drawFooter();
}
};
Posledná trieda OHLCChart je čerešnićkou na torte, ktorá pod sebou zapúzdruje všetky predchádzajúce triedy. Ako sa to robí, je vidno hneď na začiatku triedy. Parametre šablóny kompilátor upraví tak, ako sme mu to predpísali v hlavnom programe. V konštruktore nastavíme niekoľko hodnôt do interných premenných. Vo funkcii addValue sa staráme o spracovanie prichádzajúceho prúdu hodnôt. Každých 10 sekund ich posunieme do ďalšieho OHLCData v poli data a do posledného si zbierame aktuálne hodnoty. Posledná funkcia draw sa postará o zavolanie renderera pre každú jednu hodnotu OHLCData. Používame premennú ohlc_first, ktorá sa stará o sledovanie počiatku validných údajov a po niekoľkých iteráciách sa posunie na hodnotu 0, čo predstavuje prvý prvok v poli data.
Zdrojový kód sa nachádza na GitHub.
Video obshuje doplňujúce vysvetlenie ku sviečkovým grafom a príklady v texte spomínaných grafov.
Nabudúce budeme zobrazovať pomocnú mierku a pohráme sa trochu s dedičnosťou v šablónovom metaprogramovaní.
07.08.2016