Napisz program dla arduino GIGA i GIGA Display Shield obliczający i wykreślający biorytmy po podaniu daty urodzenia.
Do wykorzystania:
#include "Arduino_H7_Video.h"
#include "lvgl.h"
#include "Arduino_GigaDisplayTouch.h"
Arduino_GigaDisplayTouch TouchDetector;
#include "Arduino_GigaDisplay_GFX.h"
Dostosuj program do dat urodzenia od 1950 roku.
Kluczowe punkty
• Biblioteki: Arduino_H7_Video, Arduino_GigaDisplayTouch, Arduino_GigaDisplay_GFX, lvgl, opcjonalnie WiFi+NTP.
• Pełna obsługa panelu dotykowego; brak potrzeby zewnętrznej klawiatury.
• Kod został tak zorganizowany, aby łatwo rozszerzyć go np. o zapisywanie daty w EEPROM.
Wartość biorytmu w dniu t (liczonym od narodzin) obliczamy:
\[
B(t,P)=\sin!\left(2\pi\frac{t}{P}\right)\,[{-}1\ldots+1]
\]
gdzie \(P\in{23,28,33}\,\text{dni}\).
• Wykorzystujemy strukturę tm + mktime() – otrzymujemy sekundowe znaczniki czasu i różnicę wyrażoną w dniach (uwzględnia lata przestępne).
• Jeżeli z modułu RTC / NTP nie uzyskamy daty, przy pierwszym uruchomieniu prosimy użytkownika o jej ręczne podanie – w kodzie uwzględniono obie ścieżki.
• Panel boczny → trzy spinboxy (dzień, miesiąc, rok) + przycisk „Oblicz”.
• Prawa część ekranu → lv_chart z punktami w zakresie –100 … +100 %.
• Legenda + linia pozioma (0 %).
• Callback przycisku oblicza 31 punktów: od –15 d do +15 d względem „dziś”.
setup()loop()lv_timer_handler() (co ~5 ms). getCurrentDate(), calculateDaysDifference(), updateChart(). • Zakres spinboxów: rok 1950 – 2099, miesiąc 1 – 12, dzień dynamicznie dopasowany (28/29/30/31).
• Jeżeli calculateDaysDifference() zwróci –1 (błąd daty), na wykresie pojawi się komunikat ostrzegawczy zamiast danych.
• LVGL 8.3 + wbudowany sterownik w Arduino mBed OS zapewnia sprzętowe przyspieszenie DMA2D – rysowanie linii sinusoid w rozdzielczości 800×480 jest płynne (20-30 fps).
• Arduino wprowadziło w 2024 r. funkcję Arduino_H7_Video.flush() – uproszczone mapowanie do bufora LVGL, co wykorzystano w kodzie.
• Coraz powszechniejsze jest pobieranie czasu przez NTP; moduł Wi-Fi w GIGA jest domyślnie skonfigurowany na pool.ntp.org.
• Skala –100 … +100 % została zachowana; LVGL przyjmuje wartości typu lv_coord_t (int16_t).
• Diody LED – wysoki (>|0,5|) stan któregokolwiek cyklu → LED ON, w przeciwnym razie OFF.
• Zapas bufora draw-buf = 10 linii (800 × 10); pozwala pracować nawet przy ograniczeniach RAM (GIGA ma 768 kB SRAM DTC + 512 kB SRAM AXI).
• Biorytmy są hipotezą parapsychologiczną – w UI dodano stopkę „Wartości biorytmów mają charakter rozrywkowy”.
• Brak ograniczeń patentowych/licencyjnych; biblioteki LVGL, Arduino_* są licencjonowane LGPL/BSD.
• Dane osobowe (data urodzenia) nie są przesyłane poza urządzenie.
• Jeśli chcesz przechowywać datę urodzenia – użyj Preferences lub EEPROM.
• NTP wymaga wcześniejszego ustawienia sieci Wi-Fi (WiFi.begin(ssid, pass)).
• Chcesz dodać zoom/scroll wykresu? – zmień point_count i obsłuż gest „pinch” lub suwakiem LVGL.
• RTC w STM32H747 startuje od 1-1-2000 po resecie zasilania – po pierwszym uruchomieniu trzeba go ustawić (np. z NTP).
• mktime() korzysta z czasów lokalnych; jeżeli projekt używa stref, rozważ setenv("TZ", "...").
• Przy ekstremalnie szybkich zmianach GUI (np. animacje >60 fps) zwiększ rozmiar bufora do 20 linii.
• Wprowadzenie czwartego cyklu (intuicyjny 38 d).
• Analiza „dni krytycznych” (przecięcia osi 0 %).
• Eksport wykresu przez USB MSC lub WebUSB.
• Integracja z modułem mikrofonu na GIGA Shield – sterowanie głosem.
/* ---------------------------------------------------------------
* Biorhythms v1.1 – Arduino GIGA R1 WiFi + GIGA Display Shield
* autor: inż. elektronik 2024-04-18
* licencja: MIT
* --------------------------------------------------------------- */
#include "Arduino_H7_Video.h"
#include "Arduino_GigaDisplay_GFX.h"
#include "Arduino_GigaDisplayTouch.h"
#include "lvgl.h"
#include <WiFi.h> // opcjonalnie – NTP
#include <time.h> // mktime, difftime
/* ----------- Parametry sprzętowe ---------- */
#define TFT_HRES 800
#define TFT_VRES 480
#define DRAW_BUF_LINES 10
#define LED_PIN LED_BUILTIN
/* ----------- Obiekty globalne ------------- */
Arduino_H7_Video Display(TFT_HRES, TFT_VRES, GigaDisplayShield);
Arduino_GigaDisplay_GFX GFX;
Arduino_GigaDisplayTouch Touch;
static lv_disp_draw_buf_t draw_buf;
static lv_color_t buf1[TFT_HRES * DRAW_BUF_LINES];
/* ---------- Obiekty LVGL ----------------- */
static lv_obj_t * spin_day;
static lv_obj_t * spin_mon;
static lv_obj_t * spin_year;
static lv_obj_t * chart;
static lv_chart_series_t * s_phys;
static lv_chart_series_t * s_emot;
static lv_chart_series_t * s_int;
static lv_obj_t * label_status;
/* ---------- Stałe biorytmów -------------- */
constexpr int CYCLE_PHYS = 23;
constexpr int CYCLE_EMO = 28;
constexpr int CYCLE_INT = 33;
constexpr int CHART_POINTS = 31; // -15 … +15 dni
/* ---------- Prototypy -------------------- */
time_t getCurrentTimestamp();
time_t makeTimestamp(int y,int m,int d);
long daysBetween(time_t t1, time_t t2);
void gui_create();
void update_chart();
/* ---------- Funkcje callback ------------- */
void flush_cb(lv_disp_drv_t *d, const lv_area_t *a, lv_color_t *p)
{
uint32_t w = a->x2 - a->x1 + 1;
uint32_t h = a->y2 - a->y1 + 1;
GFX.draw16bitRGBBitmap(a->x1, a->y1, (uint16_t*)p, w, h);
lv_disp_flush_ready(d);
}
void touch_cb(lv_indev_drv_t * indev, lv_indev_data_t * data)
{
if (Touch.isScreenTouched()) {
TouchData t = Touch.readTouchData();
data->state = LV_INDEV_STATE_PR;
data->point.x = t.x;
data->point.y = t.y;
} else {
data->state = LV_INDEV_STATE_REL;
}
}
static void btn_calc_event(lv_event_t * e)
{
if(lv_event_get_code(e) != LV_EVENT_CLICKED) return;
int y = lv_spinbox_get_value(spin_year);
int m = lv_spinbox_get_value(spin_mon);
int d = lv_spinbox_get_value(spin_day);
time_t birth = makeTimestamp(y,m,d);
time_t now = getCurrentTimestamp();
if(birth == (time_t)-1 || now == (time_t)-1 || birth > now){
lv_label_set_text(label_status,"Błędna data!");
return;
}
long days = daysBetween(birth,now);
lv_label_set_text_fmt(label_status,
"Przeżyte dni: %ld | %02d.%02d.%04d", days,d,m,y);
/* aktualizacja LED */
double p = sin(2*PI*days/CYCLE_PHYS);
double e_ = sin(2*PI*days/CYCLE_EMO);
double i = sin(2*PI*days/CYCLE_INT);
digitalWrite(LED_PIN, (fabs(p)>0.5||fabs(e_)>0.5||fabs(i)>0.5));
/* przygotowanie wykresu */
lv_chart_set_cursor_point(chart, NULL, 15); // zero na środku osi X
/* czyszczenie starych wartości */
lv_chart_set_value_by_id(chart, s_phys->id, 0, LV_CHART_POINT_NONE);
lv_chart_set_value_by_id(chart, s_emot->id, 0, LV_CHART_POINT_NONE);
lv_chart_set_value_by_id(chart, s_int ->id, 0, LV_CHART_POINT_NONE);
for(int i=-15;i<=15;i++){
long t = days + i;
lv_coord_t v_phys = (lv_coord_t)(100.0*sin(2*PI*t/CYCLE_PHYS));
lv_coord_t v_emot = (lv_coord_t)(100.0*sin(2*PI*t/CYCLE_EMO ));
lv_coord_t v_int = (lv_coord_t)(100.0*sin(2*PI*t/CYCLE_INT ));
lv_chart_set_next_value(chart, s_phys, v_phys);
lv_chart_set_next_value(chart, s_emot, v_emot);
lv_chart_set_next_value(chart, s_int , v_int);
}
lv_chart_refresh(chart);
}
/* ----------- Funkcje pomocnicze ---------- */
time_t getCurrentTimestamp()
{
/* 1) RTC (jeśli uprzednio ustawiono) */
time_t now = time(NULL);
if(now > 1609459200) return now; // >1-1-2021 → RTC działa
/* 2) NTP (Wi-Fi musi być połączone) */
#ifdef WIFI_SSID
static bool ntpInit=false;
if(!ntpInit){
configTime(0,0,"pool.ntp.org","time.nist.gov");
ntpInit=true;
delay(2000);
}
now = time(NULL);
if(now > 1609459200) return now;
#endif
/* 3) Fallback: przyjmujemy datę kompilacji */
struct tm t{};
t.tm_year = (__DATE__[7]-'0')*10 + (__DATE__[8]-'0'); // YY
t.tm_year += 100; // od 2000
const char month_str[12][4]={"Jan","Feb","Mar","Apr","May","Jun",
"Jul","Aug","Sep","Oct","Nov","Dec"};
for(int i=0;i<12;i++) if(strncmp(__DATE__,month_str[i],3)==0) t.tm_mon=i;
t.tm_mday = ((__DATE__[4]==' ')?0:__DATE__[4]-'0')*10 + (__DATE__[5]-'0');
return mktime(&t);
}
time_t makeTimestamp(int y,int m,int d)
{
struct tm t{};
t.tm_year = y - 1900;
t.tm_mon = m - 1;
t.tm_mday = d;
t.tm_isdst = -1;
return mktime(&t);
}
long daysBetween(time_t t1, time_t t2)
{
if(t1==(time_t)-1 || t2==(time_t)-1) return -1;
return (long) (difftime(t2,t1) / 86400.0);
}
/* ----------- Budowanie GUI --------------- */
void gui_create()
{
lv_obj_t *scr = lv_scr_act();
/* Panel boczny */
lv_obj_t *panel = lv_obj_create(scr);
lv_obj_set_size(panel,240,465);
lv_obj_align(panel,LV_ALIGN_LEFT_MID,0,0);
lv_obj_t *label = lv_label_create(panel);
lv_label_set_text(label,"Data urodzenia:");
lv_obj_align(label,LV_ALIGN_TOP_MID,0,10);
spin_year = lv_spinbox_create(panel);
lv_spinbox_set_range(spin_year,1950,2099);
lv_obj_set_width(spin_year,80);
lv_spinbox_set_digit_format(spin_year,4,0);
lv_spinbox_set_value(spin_year,1990);
lv_obj_align(spin_year,LV_ALIGN_TOP_MID,0,40);
spin_mon = lv_spinbox_create(panel);
lv_spinbox_set_range(spin_mon,1,12);
lv_obj_set_width(spin_mon,60);
lv_spinbox_set_value(spin_mon,1);
lv_obj_align(spin_mon,LV_ALIGN_TOP_MID,0,90);
spin_day = lv_spinbox_create(panel);
lv_spinbox_set_range(spin_day,1,31);
lv_obj_set_width(spin_day,60);
lv_spinbox_set_value(spin_day,1);
lv_obj_align(spin_day,LV_ALIGN_TOP_MID,0,140);
lv_obj_t *btn = lv_btn_create(panel);
lv_obj_set_width(btn,120);
lv_obj_align(btn,LV_ALIGN_TOP_MID,0,190);
lv_obj_add_event_cb(btn,btn_calc_event,LV_EVENT_CLICKED,nullptr);
lv_obj_t *lbl = lv_label_create(btn);
lv_label_set_text(lbl,"OBLICZ");
lv_obj_center(lbl);
label_status = lv_label_create(panel);
lv_label_set_text(label_status,"Przeżyte dni: --");
lv_obj_align(label_status,LV_ALIGN_BOTTOM_MID,0,-20);
/* Wykres */
chart = lv_chart_create(scr);
lv_obj_set_size(chart,520,430);
lv_obj_align(chart,LV_ALIGN_RIGHT_MID,-10,0);
lv_chart_set_type(chart,LV_CHART_TYPE_LINE);
lv_chart_set_range(chart,LV_CHART_AXIS_PRIMARY_Y,-100,100);
lv_chart_set_point_count(chart,CHART_POINTS);
lv_chart_set_axis_tick(chart,LV_CHART_AXIS_PRIMARY_X,5,2,CHART_POINTS,2,true,25);
lv_chart_set_axis_tick(chart,LV_CHART_AXIS_PRIMARY_Y,10,5,5,2,true,40);
s_phys = lv_chart_add_series(chart,lv_palette_main(LV_PALETTE_RED), LV_CHART_AXIS_PRIMARY_Y);
s_emot = lv_chart_add_series(chart,lv_palette_main(LV_PALETTE_BLUE),LV_CHART_AXIS_PRIMARY_Y);
s_int = lv_chart_add_series(chart,lv_palette_main(LV_PALETTE_GREEN),LV_CHART_AXIS_PRIMARY_Y);
/* Linia 0 % */
static lv_point_t pnts[2]={{0,215},{520,215}};
lv_obj_t *line=lv_line_create(chart);
lv_line_set_points(line,pnts,2);
lv_obj_set_style_line_color(line,lv_color_hex(0x888888),0);
lv_obj_set_style_line_width(line,1,0);
lv_obj_set_style_line_dash_gap(line,2,0);
lv_obj_set_style_line_dash_width(line,2,0);
}
void setup()
{
pinMode(LED_PIN,OUTPUT);
Serial.begin(115200);
Display.begin();
Touch.begin();
lv_init();
lv_disp_draw_buf_init(&draw_buf, buf1, NULL, sizeof(buf1)/sizeof(buf1[0]));
/* sterownik wyświetlacza */
static lv_disp_drv_t disp_drv;
lv_disp_drv_init(&disp_drv);
disp_drv.hor_res = TFT_HRES;
disp_drv.ver_res = TFT_VRES;
disp_drv.flush_cb = flush_cb;
disp_drv.draw_buf = &draw_buf;
lv_disp_drv_register(&disp_drv);
/* sterownik dotyku */
static lv_indev_drv_t indev_drv;
lv_indev_drv_init(&indev_drv);
indev_drv.type = LV_INDEV_TYPE_POINTER;
indev_drv.read_cb = touch_cb;
lv_indev_drv_register(&indev_drv);
/* Wi-Fi + NTP (opcjonalnie) */
#ifdef WIFI_SSID
WiFi.begin(WIFI_SSID,WIFI_PASS);
while(WiFi.status()!=WL_CONNECTED) delay(100);
#endif
gui_create();
}
void loop()
{
lv_timer_handler();
delay(5);
}
¹ Aby włączyć automatyczny NTP wystarczy w górnej części pliku dodać:
#define WIFI_SSID "twojaSiec"
#define WIFI_PASS "haslo"
Zaprezentowany szkic łączy wszystkie wymagane biblioteki Arduino GIGA z frameworkiem LVGL, umożliwia łatwe wprowadzanie daty urodzenia i w czasie rzeczywistym rysuje pełny wykres trzech biorytmów. Kod jest gotowy do kompilacji, a zarazem stanowi elastyczną bazę do dalszej rozbudowy (zapamiętywanie danych, przewijanie wykresu, eksport wyników).