Skocz do zawartości

Program do roweru stacjonarnego


Pomocna odpowiedź

Napisano

Hej.

W zeszłym roku (2024) rozpocząłem budowę komputera do roweru stacjonarnego. Rower złapałem na tzw. wystawce gabarytów. Nie było komputera, zasilacza. Był mechanizm od ustawiania obciążenia (obciążenie magnetyczne).

Obecnie chcę zrobić wersję trochę rozbudowaną tego komputera.

  • Płytka wykonana w JLCPCB;
  • ESP-WROOM-32 (LOLIN-32);
  • L9110 - sterowanie silnikiem od obciążenia (fabrycznie 16 poziomów obciążenia) + feedback z potencjometru (fabryczne rozwiązanie)
  • Kontaktron - obroty
  • LCD 4" ILI9486 SPI 320x480 color (biblioteka TFT_eSPI - bo z nią wyświetlacz działa bez problemu) + touch XPT2046 controller (dotyk rezystancyjny) + microSD;
  • Enkoder - do ustawień;
  • Opaska monitorująca pracę serca POLAR H7 BLE;
  • Buzzer;

Zasilanie całości 9V/2A z zasilacza wtyczkowego (tak było oryginalnie).
Rower to nieprodukowany już Kettler Golf C4. Poniżej na zdjęciu oryginalny komputer z tego roweru.
microSD na tę chwilę bez pomysłu.
Fajnie byłoby użyć obu rdzeni z ESP - pytanie czy jest sens?

Obawiam się, że polegnę, ale nie chcę się poddać.

Moje pomysły:

  • monitorowanie pracy serca + obliczanie spalonych kalorii + strefy tętna;
  • kilka programów "gotowców" ze zmiennym obciążeniem + ustawienie czasu treningu i w tym czasie rozkład obciążenia;
  • prędkość, czas jazdy (pedałowania), może dystans;
  • ręczne ustawienie obciążenia na sztywno;
  • Wifi na potrzeby ustawienia RTC z NTP;
  • użytkownicy, np. 4 i jeden "guest".

Chciałbym ten program napisać w sposób "porządny" czyli rozbicie na moduły, czyli wszystko co związane z monitorowaniem serca + kalorie w jednym pliku *.h + *.cpp; sterowanie silnikiem obciążenia + feedback drugi plik, itd. Sprawiało mi to problemy spore, gdy jakiś czas temu próbowałem - wierzę, że z Waszą pomocą dam radę.

W tym wątku będę pytał oraz w miarę możliwości wrzucał postępy..

Mile widziane pomysły już teraz.

 

image.thumb.png.670f1c2c8cfed011bee423695b9a53d0.pngimage.thumb.png.c9029fa171a366b1570e82300ace6ffa.pngimage.thumb.png.3357a2891fc8cc15656cdcf5662c2d0f.pngimage.thumb.png.efaf8a83f9a459378381ba221a92f619.pngimage.thumb.png.95de3d1e249cc287099231b28a83a19a.pngimage.thumb.png.88f39c62a64a1fab14b00d1e08dc2ee2.png

  • Lubię! 1
(edytowany)

Wstępnie utworzyłem GUI.

Obszary z czerwoną ramką (ramki nie będzie w docelowym urządzeniu) są aktywne na dotyk - będzie możliwość zmiany parametru na potrzeby treningu - np. ustawić docelową ilość spalonych kalorii.

Dane są generowane na razie funkcją rand() na potrzeby testów.

Niestety Polar H7 ma uszkodzony pasek, więc na tę chwilę odpada jego użycie.

W tym tygodniu powinienem otrzymać czujnik (moduł) MAX30102 do monitorowania tętna oraz spO2.

Jeszcze sporo pracy, ale nie ukrywam - frajda jest! 🙂

image.thumb.png.2d6aaf2b9f250727e015f87bef7f3a1f.png

 

Edytowano przez roz
  • Lubię! 1

Potrzebna pomoc.

W plikach hmi.h oraz hmi.cpp mam obsługę ekranu i dotyku na ekranie.

Potrzebuję z hmi.cpp po wywołaniu funkcji wysłać "coś" lub uruchomić funkcję w main.cpp, która spowoduje pauzę w programie.

O ile z main.cpp wiem jak wysłać, to w drugą stronę nie mam pojęcia.

Pomożecie?

  • Lubię! 1

- zwykle wstawia się w programach statusy i je obsługuje w innym programie.

- statusy mogą być globalne a nie lokalne.

bool czekaj = false;
...
  
czekaj = true;


... 
if(czekaj)  
{
(cos zrób )
}  
  

 

@99teki

Tak. Gdy wszystko leży w main.cpp.

A teraz jest tak.

hmi.cpp zawiera funkcję, która po dotknięciu ekranu sprawdza, w którym miejscu ekran był dotknięty i ta funkcja ma wywołać funkcję w main.cpp.

Chociaż tak sobie myślę, że łatwiej chyba będzie napisać funkcję getPauseStatus() i sprawdzać w loop().

czyli: (najjaśniej jak potrafię)

hmi.cpp:
  
void checkTouch()
{
  if (coś tam)
  {
    pauza(); //funkcja w main.cpp
  }
}

main.cpp:
  
void setup()
{}

void loop()
{}

void pauza() //ciało funkcji w main.cpp
{
}

 

Unikaj takich relacji w 2 strony. MAIN niech tworzy HMI i ewentualnie wywołuje jakieś jego metody ale HMI niech wprost nie odwołuje się do MAIN. Spróbuj
poczytać o callbackach i zrobić coś w stylu:

w main

main() {
    setHmiCallback([](const std::string& message) {
        if (message == "pauza") {
            std::cout << "Zatrzymano proces.\n";
            // Zrób coś
        }
    });
}


w hmi

std::function<void(const std::string&)> callback;

void setHmiCallback(std::function<void(const std::string&)> c) {
    callback = c;
}

if (coś) {
  callback("pauza");
}


To oczywiście zakłada, że to działa w 1 wątku. Tak naprawdę nie wiem skąd biorą się zdarzenia w HMI.

 

  • Lubię! 1
  • Pomogłeś! 1
(edytowany)

Czy jestem w stanie jeszcze bardziej ustabilizować odczyt analogRead na ESP32?

Dodałem kondensator 100uF pomiędzy suwak potencjometru a GND.

Potencjometr 5kR; 3,3V na potencjometrze.

Czy już tylko średnia z tego?

[EDIT]: nie wiem czy muszę to stabilizować bardziej - to jest odczyt położenia potencjometru, który jest feedbackiem od silnika - regulacja hamulca. Jednak potrzebne do ustawienia obciążenia.

19:38:44:267 -> analogRead 1623
19:38:45:240 -> analogReadMilliVolts 1482
19:38:45:267 -> analogRead 1621
19:38:46:240 -> analogReadMilliVolts 1482
19:38:46:267 -> analogRead 1626
19:38:47:241 -> analogReadMilliVolts 1474
19:38:47:268 -> analogRead 1621
19:38:48:241 -> analogReadMilliVolts 1482
19:38:48:269 -> analogRead 1622
19:38:49:243 -> analogReadMilliVolts 1475
19:38:49:271 -> analogRead 1621
19:38:50:244 -> analogReadMilliVolts 1483
19:38:50:271 -> analogRead 1620
19:38:51:244 -> analogReadMilliVolts 1487
19:38:51:272 -> analogRead 1622
19:38:52:245 -> analogReadMilliVolts 1477
19:38:52:273 -> analogRead 1622
19:38:53:245 -> analogReadMilliVolts 1481
19:38:53:282 -> analogRead 1622
19:38:54:246 -> analogReadMilliVolts 1482
19:38:54:274 -> analogRead 1623
19:38:55:248 -> analogReadMilliVolts 1484
19:38:55:275 -> analogRead 1622
19:38:56:249 -> analogReadMilliVolts 1482
19:38:56:277 -> analogRead 1622

 

Edytowano przez roz

Zgodnie z zaleceniami:

Kondensator 100nF a nie 100uF

Potencjometr wyposażyć w rezystory tak, aby minimalne napięcie przy skręceniu w lewo było przynajmniej 0.3V, a maksymalne ok. 2.5V

No i średnia z iluś tam pomiarów (według przykładów w dokumentacji 64, ja stosuję średnią ucinaną z 32 pomiarów).

Poza tym lepiej stosować funkcje z idf (dostępne w szkicach Arduino), ale tylko trochę lepiej.

Niestety ADC w ESP32 nie słynie z dokładności.

(edytowany)

Dlaczego kompilator czepia się, że funkcja countPulse w przerwaniu ma być static? Gdy przeniosę wszystko do sekcji "public" w klasie to nie czepia się.

void SpeedDistance::init()
{
    pinMode(REED_SWITCH, INPUT_PULLUP);
    attachInterrupt(digitalPinToInterrupt(REED_SWITCH), countPulse, FALLING);
}

Fragment klasy:

class SpeedDistance
{
private:
    volatile unsigned long _pulseCount =0;   /// Number of pulses from reed switch
    unsigned long debounceTime=50;  // Czas eliminacji drgań styków w milisekundach
    unsigned long lastPulseTime=0; /// Time of last pulse
    // unsigned int timeDiff = 0;
    // static unsigned long counterPrev;
    // static unsigned long counterCurr;
    // float _czas_s;
    float _cadence = 0.0;                  /// Cadence in RPM
    float _maxCadence = 0.0;               /// Max cadence in RPM
    float _speed = 0.0;                    /// Speed in km/h
    float _maxSpeed = 0.0;                 /// Max speed in km/h
    float _distance = 0.0;                 /// Distance in km
    bool _paused = false;                  /// Status of counting pulses
    const float wheelCircumference = 74.0; /// Obwód koła w cm
    void IRAM_ATTR countPulse();    /// Counting pulses from reed switch

I funkcja:

void IRAM_ATTR SpeedDistance::countPulse()
{
    unsigned long currentTime = millis();
    if (currentTime - lastPulseTime > debounceTime)
    {
        _pulseCount++;
        lastPulseTime = currentTime;
    }
}

EDIT: myślałem, że z tego powodu nie działa mi przerwanie, że funkcja/zmienne są statyczne, ale nie, powód jest inny. Z forum esp32.com

gpio36 i gpio39 są "ułomne" - nie da się zrobić programowo pullup i pulldown...

image.thumb.png.dfc4167af547039b5ee73b3cfe69ea4f.png

Edytowano przez roz
39 minut temu, roz napisał:

Dlaczego kompilator czepia się, że funkcja countPulse w przerwaniu ma być static?

Procedura obsługi przerwania musi być jedna dla całej klasy (czyli static), bo skąd ISR miałby wiedzieć, dane którego obiektu (instancji) ma w swoim działaniu używać.

W twoim programie przerwanie modyfikuje zmienną  _pulseCount, no ale którą jeśli mamy zadeklarowanych np. pięć liczników? 

Ciąg dalszy, wystarczy leniuchowania.

W jaki sposób zbieralibyście dane o treningu? Mam taką strukturę w głowie na tę chwilę, ale mam wątpliwości co do niej.

struct UserData {
  int id; // Identyfikator użytkownika
  char date[11]; // Data w formacie "YYYY-MM-DD"
  char startTime[6]; // Godzina startu w formacie "HH:MM"
  char endTime[6]; // Godzina końca w formacie "HH:MM"
  float maxSpeed; // Maksymalna prędkość w km/h
  float avgSpeed; // Średnia prędkość w km/h
  float distance; // Dystans w km
  float avgCadence; // Średnia kadencja w RPM
  float maxCadence; // Maksymalna kadencja w RPM
  float avgHeartRate; // Średnie tętno w BPM
  float maxHeartRate; // Maksymalne tętno w BPM
  float caloriesBurned; // Ilość spalonych kalorii
  unsigned int trainingTime; // Czas treningu w minutach
};

Powyższe nadaje się (moim zdaniem) jako podsumowanie treningu po zakończeniu.

Natomiast chodzi o ciągłe rejestrowanie całego treningu. Utworzenie tablicy np. 100 elementów (jeden wiersz co minutę?) i po jej zapełnieniu zapis do pliku?

Podzielcie się pomysłami.

Dziękuję.

Ps. chcę, aby było maksymalnie 4 użytkowników - wybór profilu przed treningiem, oraz jeden typu "gość".

Zmieniłbym przede wszystkim daty/godziny na time_t - krótsze, wyrównane i prostsze później w interpretacji. A przede wszystkim odporne na jakieś zmiany typu CET/CEST.

No i double a nie float albo jakiś int.

  • Lubię! 2
  • 1 miesiąc później...

Przegrałem na tę chwilę nierówną walkę z esp32, który (ten mój egzemplarz) jest zabytkiem. Patrząc na forum i wątek SOYER'a jak to robił sobie panel HMI - zamówiłem takie cudo z esp32-s3 na pokładzie. Prace nad rowerem (sterownikiem) zostaną wznowione, gdy otrzymam moduł.

  • Lubię! 1

Bądź aktywny - zaloguj się lub utwórz konto!

Tylko zarejestrowani użytkownicy mogą komentować zawartość tej strony

Utwórz konto w ~20 sekund!

Zarejestruj nowe konto, to proste!

Zarejestruj się »

Zaloguj się

Posiadasz własne konto? Użyj go!

Zaloguj się »
×
×
  • Utwórz nowe...