Skocz do zawartości

Jak pisać biblioteki na Arduino?


Pomocna odpowiedź

23 minuty temu, Matthew11 napisał:

Rozumiem, że nie chcesz uzależnić biblioteki od funkcji w kodzie głównym, tylko przekazać tą funkcje z kodu głównego do biblioteki. 

Nie nie...chodzi mi o przekazanie tylko "namiarow" na ta funkcje do biblioteki po to zeby podczas przerwania ISR w tej bibliotece wykonac wlasnie ta funkcje...

Czyli najprostszy przypadek...

 

static void (*fun)(void);
void biblioteka::begin(void(*funkcja)(void))
  {fun = funkcja;
   }
void biblioteka;:costam(void)
  {
  fun();
  }

(pisane na telefonie po piątym piwie więc mogłem nieco namieszać)

  • Pomogłeś! 1
6 minut temu, ethanak napisał:

W sumie można prościej... w begin ustawić że Twoja funkcja to ISR i wtedy nie jest potrzebna funkcja pośrednicząca...

Mysle i nie lapie jak by to mialo dzialac...ta funkcja posredniczaca sluzyc by miala do tego zeby w niej umiescic to co ma sie wykonac podczas przerwania...i o to wlasnie chodzi o elastycznosc zastosowania...a tak to musial bym wpisac do ISR na "sztywno" jeden kod, czego nie chce....przepraszam jezeli zle cos odbieram..

(edytowany)
47 minut temu, farmaceuta napisał:

Nie nie...chodzi mi o przekazanie tylko "namiarow" na ta funkcje do biblioteki po to zeby podczas przerwania ISR w tej bibliotece wykonac wlasnie ta funkcje...

Czyli wskaźnika na twoją funkcję czyli jej adresu. Można zrobić tak: 

moja_funkcja(){
  //coś tam
}


typedef void(*my_fun_callback_t)(); // tworzysz nowy typ który będzie wskaźnikiem na funkcje która nic nie przyjmuje i nic nie zwraca
my_fun_callback_t f;  //  za pomocą nowego typu danych tworzysz nową zmienną (tak jak np int w char s itd);
f = moja_funkcja; // a tutaj do nowej zmiennej przypisujesz wartość czyli adres twojej funkcji 

ISR....
{
  f(); // a tutaj wywołanie twojej funkcji zauważ że 'f' może mieć różne wartości tj może wskazywać na różne funkcje enjoy!
}

W ogóle jak "zatrybisz" wskaźniki na funkcje to zabawa później jest przednia, możesz je podmieniać w locie, tablicować, przesyłać jeszcze do innych funkcji a ilość ifów caseów i innych kombinacji drastycznie spada 🙂

 

 

Edytowano przez _LM_
  • Pomogłeś! 1
30 minut temu, _LM_ napisał:

Czyli wskaźnika na twoją funkcję czyli jej adresu.

Czyli do biblioteki musze tylko przekazac adres funkcji...cos w stylu "begin(&moja_funkcja)" czy bez znaku ampersand?

Panowie  @Gieneq @ethanak @_LM_ @Matthew11 dziekuje za ekspresowe odpowiedzi! Bardzo mi pomagacie..😉 jeszcze kwestia tego drugiego pytania..mianowicie...jak przekazac obiekt do biblioteki? Powiedzmy ze robie cos takiego..

SoftwareSerial ss(2, 3);

I jak przekazac ss? Zebym mogl nim normalnie operowac w tej bibliotece...wiem ze odwoluje sie strzalkami do metod i na tym w sumie koniec...

 

To może ja dokończę wątek o przekazywaniu wskaźników na funkcje a specjaliści od c++ pomogą Ci w obiektach.

Ogólnie jeśli budujesz bibliotekę to powinieneś na zewnątrz udostępnić ten dodatkowy typ danych. Robi się to w pliku .h twojej biblioteki:

typedef void(*my_fun_callback_t)();

Następnie w pliku .cpp biblioteki [Tu UWAGA Literalnie mylę deklarację z definicją więc w razie W proszę mnie poprawić] definiujesz zmienną typu my_fun_callback_t w taki sposób:

my_fun_callback_t f; // zamiast f nazwa dowolna

następnie w z powrotem w pliku .h należy udostępnić zmienna f

extern my_fun_callback_t f; // od tej pory zmienna jest widoczna tam gdzie dołączysz plik .h

teraz w programie głównym np w begin robisz 

f = moja_funkcja;

Oczywiście f możesz podmieniać, nie musi to być zawsze ta sama funkcja, byle parametry się zgadzały. Ważna Uwaga dotycząca już samego wywołania: warto wcześniej sprawdzić czy f posiada jakąś wartość gdyż wywołanie pustego lub przypisaną nietypową wartością callbacka wpuści program w maliny. Można to zrobić tak:

ISR....
{
  if(f)f(); //jeśli wskaźnik ma wartość to wykonaj;
  }

to wszystko chyba grubszych będów nie popełniłem 🙂 

 

  • Pomogłeś! 1
(edytowany)
Przed chwilą, farmaceuta napisał:

SoftwareSerial ss(2, 3);

I jak przekazac ss? Zebym mogl nim normalnie operowac w tej bibliotece...wiem ze odwoluje sie strzalkami do metod i na tym w sumie koniec...

Na dwa sposoby, wskaźnikiem lub referencją. Najbezpieczniej za pomocą referencji i w konstruktorze klasy np.:

class Test
{
  public:
  Test(SoftwareSerial& softwareSerial): m_softwareSerial{softwareSerial} {}
  
  private:
  SoftwareSerial& m_softwareSerial;
};

Albo rzeczonym wskaźnikiem:

class Test
{
  public:
  Test(SoftwareSerial* const softwareSerial): m_softwareSerial{softwareSerial} {}
  
  private:
  SoftwareSerial * const m_softwareSerial;
};

* const po to żeby nie zrobić sobie krzywdy np. w ten sposób:

m_softwareSerial++;

Zastosowanie:

SoftwareSerial ss;

Test t1{ss};  // jak referencja
Test t2{&ss}; // jak wskaźnik 

Jak w Test korzystasz z referencji to wtedy metody wywołujesz operatorem kropki m_softwareSerial.call(), a jak wskaźnik to operatorem strzałki m_softwareSerial->call().

Edytowano przez Matthew11
  • Lubię! 1
  • Pomogłeś! 1
7 godzin temu, ethanak napisał:

(pisane na telefonie po piątym piwie więc mogłem nieco namieszać)

tylko odrobinke...😉 napisales

static void (*fun)(void);

a powinno byc

 void (*fun)(void);

ze static wywalalo blad

In function `begin': 
undefined reference to `tester::fun'

mowie to tylko oczywiscie jako info zeby w przyszlosci jakis balwan jak ja nie mial z tym problemu...tak to dziala jak marzenie😁 (no chyba ze ja cos zle zadeklarowalem w h/cpp)

@Matthew11 dzieki wielkie za odpowiedz...no a jeszcze takie pytanie jak sprawa by wygladala gdybym z gory nie znal danej biblioteki? w sense moze to byc softwareserial ale rozniez neo czy alt czy tez serial sprzetowy..widzialem wlasnie taki przyklad (github) gdzie mozna bylo korzystac tylko z hardwareserial (bo ten zostal ustalony przy tworzeniu wskaznika z gory) i zastanawialo mnie jak by to wygladalo (co by sie zmienilo) gdy mialo obslugiwac rozne biblioteki...

5 godzin temu, _LM_ napisał:

teraz w programie głównym np w begin robisz 


f = moja_funkcja;

Oczywiście f możesz podmieniać, nie musi to być zawsze ta sama funkcja, byle parametry się zgadzały.

 

 

i tu mi rozwiales watpliwosci bo tego wczesniej nie moglem zalapac za chiny...😅 twoja wersje tez przestudiuje...wielkie dzieki Panowie bo bez was to naprawde bym zginal marnie...😅

(edytowany)
44 minuty temu, farmaceuta napisał:

zastanawialo mnie jak by to wygladalo (co by sie zmienilo) gdy mialo obslugiwac rozne biblioteki...

Jeśli jakieś biblioteki załóżmy, że jedna z nich to będzie SoftwareSerial i będzie jeszcze jakiś HardwareSerial i BetterSerial to jeżeli każda z nich dziedziczy po jakieś klasie strumienia np. arduinowy Stream to wtedy Ty w swojej bibliotece/programie, możesz bazować na interfejsie (tak się na to ładnie mówi) jaki daje klasa Stream. Wtedy:

class Test // BasedOnStream
{
  public:
  Test(Stream& stream): m_stream{stream} {}
  
  private:
  Stream& m_stream;
};

Jednym z ograniczeń jest to, że do dyspozycji masz tylko metody klasy Stream. I nie będziesz mógł korzystać z jakiś specjalnych metod klas pochodnych po Stream

I jeśli każda klasa jakiegoś seriala dziedziczyła po Stream:

class SoftwareSerial : public Stream
{ ... }

class HardwareSerial : public Stream
{ ... }

class BetterSerial : public Stream
{ ... }

To można robić tak:

SoftwareSerial ss;
HardwareSerial hs;
BetterSerial bs;

Test t1{ss};
Test t2{hs};
Test t3{bs};

I każdy każdy serial będzie działać z Twoją biblioteką. 

Natomiast w praktyce jest mała szansa, że klasy serial będą dziedziczyć po Stream (te pochodne od Arduino pewnie bedą). Wtedy rozwiązaniem jest stworzenie warstwy abstrakcji (interfejsu), która będzie pośredniczyć między Twoją biblioteką a dowolną klasą jakiegoś seriala. Robi się to w ten sposób, że definiujesz tzw. interfejs (jest to klasa czysto wirtualna z metodami i wirtualnym destruktorem):

class ISerialInterface
{
public:
    ~ISerialInterface() = default;

    virtual void setup(const uint32_t baudrate) = 0;
    virtual void send(char &c) = 0;
    virtual char read() = 0;
  
    // tutaj metod może być więcej w zależności od potrzeb
};

Z kolei Twoja biblioteka nie korzysta już z konkretnej biblioteki jakiegoś seriala czy interfejsu Stream - tylko z Twojego interfejsu (ISerialInterface).

class Test // BasedOnInterface
{
  public:
  Test(ISerialInterface& serialInterface): m_serialInterface{serialInterface} {}
  
  private:
  ISerialInterface& m_serialInterface;
};

I teraz zaczyna się najlepsze, bo jeśli chcesz korzystać z np. arduinowej klasy Serial to tworzysz tzw. konkretną implementację Twojego interfejsu ISerialInterface czyli w tym wypadku np. ArduinoSerial:

class ArduinoSerial : public ISerialInterface
{
public:
    ArduinoSerial(Serial &serial) : m_serial{serial} {}

    void setup(const uint32_t baudrate) override
    {
        m_serial.begin(baudrate);
    }

    void send(char &c) override
    {
        m_serial.send(c);
    }

    char read() override
    {
        return m_serial.read();
    }

private:
    Serial &m_serial;
};

Użycie jest takie samo jak wcześniej:

ArduinoSerial arduinoSerial(Serial);  // Serial to nazwa obiektu klasy Serial (sprzętowy UART), które standardowo daje framework 

Test t1{arduinoSerial};               // Test oczekuje klasy, która implementuje interfejs ISerialInterface - i ArduinoSerial to spełnia

I teraz jeśli najdzie Cię ochota (lub potrzeba) na zmianę biblioteki do seriala (np. BetterSerial - jeśli taka jest), to tworzysz tylko jedną nową klasę (niech się nazywa BetterSerialAdapter) podobną do ArduinoSerial, gdzie zamiast arduinowego Serial używasz BetterSerial:

class BetterSerialAdapter : public ISerialInterface
{
public:
    BetterSerialAdapter(BetterSerial &serial) : m_serial{serial} {}

    // ... reszta identycznie jak ArduinoSerial
};

Daje to taką możliwość, że jeśli nawet Serial Arduino czy BetterSerial mają różne metody, czy sposoby użycia to nie jest to problemem - używasz tych specyficznych metod i po sprawie. Na końcu robisz:

BetterSerialAdapter betterSerial(Serial);

Test t1{betterSerial};

I reszta programu pozostaje bez zmian. I to jest najważniejszy wniosek. 

* niekoniecznie przykłady muszą się kompilować

Edytowano przez Matthew11
  • Lubię! 1
  • Pomogłeś! 1
(edytowany)
2 godziny temu, ethanak napisał:

A kto Ci takich rzeczy naopowiadał, że to trzeba wepchać do klasy?

Wiedzialem ze to nie mozliwe zebys blad popelnil...😅 

O matko to gdzie ja mam upchnac ten static-fun?...aha bo moze sie zle rozumiemy...w "cpp" fun istnieje tylko jako fun() , czyli jako wywolanie funkcji...a w "h" tylko jako deklaracja wskaznika w private..

/// h
#ifndef tester_h
#define tester_h

#include "Arduino.h"

class tester
{
   public:
     void begin(void(*funkcja)(void));
     void costam(void);
  

   private:
   void (*fun)(void);

 
};



#endif

 

/// cpp

#include "tester.h"

void tester::begin(void(*funkcja)(void))
  { fun = funkcja;
   }

void tester::costam(void)
  {
  fun();
  }

 

Edytowano przez farmaceuta

W tester.h w ogóle nie trzeba o niej wspominać bo nikogo to nie interesuje.

W tester.cpp masz po prostu:
 

static void (*fun)(void);

void tester::begin(void (*funkcja)(void))
{
...

Taka zwykła zmienna statyczna...

  • Pomogłeś! 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...