Skocz do zawartości

Jak pisać biblioteki na Arduino?


macizet

Pomocna odpowiedź

Ale z czym dokładnie masz problem?

Ogólnie wskaźnik na funkcję wygląda tak:

typ_wyniku (* nazwa)(typy_argumentów)

Jeśli funkcja ma być wywoływana przez inną funkcję, nad którą nie masz kontroli - powinieneś zaimplementować jakiś mechanizm pozwalający na zapamiętanie przez bibliotekę tego wskaźnika. Najprostsze (chociaż niekoniecznie najlepsze) rozwiązanie to stworzenie jakiejś funkcji w bibliotece typy "init" czy "begin", której jednym z argumentów będzie właśnie Twoja funkcja, a wskaźnik będzie zapamiętany w jakiejś zmiennej statycznej.

 

  • Pomogłeś! 1
Link do komentarza
Share on other sites

19 minut temu, farmaceuta napisał:

chcialbym sie odwolywac do jakiejs funkcji z poziomu biblioteki ktora znajduje sie w glownym kodzie

Rozumiem, że nie chcesz uzależnić biblioteki od funkcji w kodzie głównym, tylko przekazać tą funkcje z kodu głównego do biblioteki. Jeśli tak to rozwiązań jest w sumie dwa - pierwsze z użyciem wskaźników na funkcje (C style), drugie z wykorzystaniem std::function (modern C++ style). 

#include <functional>
#include <iostream>

class Test
{
public:
    Test(std::function<void(void)> callbackOne, void (*callbackTwo)()) : m_callbackOne{callbackOne}, m_callbackTwo{callbackTwo}
    {
    }

    void call()
    {
        m_callbackOne();
        m_callbackTwo();
    }

private:
    std::function<void(void)> m_callbackOne;
    void (*m_callbackTwo)();
};

auto functionOne = []()
{ std::cout << "Function One called!\n"; };

void functionTwo()
{
    std::cout << "Function Two called!\n";
}

int main()
{
    Test t{functionOne, functionTwo};

    t.call();
}

Do std::function można podać lambdę (to jest ten zapis [](){}), ale można też podać adres dowolnej funkcji, która pasuje pod względem sygnatury w tym wypadku funkcja która nic nie zwraca i nic nie przyjmuje. Więc mógłbym też zrobić:

Test t{functionTwo, functionTwo};
t.call();

Oczywiście jak chcesz, żeby te funkcje coś zwracały albo przyjmowały jakieś argumenty to zamiast void(*function)(void) możesz wstawić właśnie to co chcesz, i tak samo z lambdami (tylko trzeba się w nich orientować) ale dają więcej możliwości (i mogą generować więcej problemów).

  • Pomogłeś! 1
Link do komentarza
Share on other sites

18 minut temu, ethanak napisał:

Ale z czym dokładnie masz problem?

Najczesciej??...to z glowa..😁haha a tak na serio..

 

19 minut temu, ethanak napisał:

 Najprostsze (chociaż niekoniecznie najlepsze) rozwiązanie to stworzenie jakiejś funkcji w bibliotece typy "init" czy "begin", której jednym z argumentów będzie właśnie Twoja funkcja, a wskaźnik będzie zapamiętany w jakiejś zmiennej statycznej.

 

Dokladnie o to mi chodzi, cos w stylu..

void funkcja() {
  /// wykonaj podczas przerwania
  }
void setup() {
  biblioteka.begin(funkcja);
  }
void loop() {
  }

 No a w bibliotece ma sie znajdowac obsluga przerwania ISR i to z tego miejsca mam "przeskoczyc" do funkcja() czyli..

 // cpp
ISR (vect_) {
  funkcja();
  
}

No jakos tak...

Link do komentarza
Share on other sites

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...

Link do komentarza
Share on other sites

Zarejestruj się lub zaloguj, aby ukryć tę reklamę.
Zarejestruj się lub zaloguj, aby ukryć tę reklamę.

jlcpcb.jpg

jlcpcb.jpg

Produkcja i montaż PCB - wybierz sprawdzone PCBWay!
   • Darmowe płytki dla studentów i projektów non-profit
   • Tylko 5$ za 10 prototypów PCB w 24 godziny
   • Usługa projektowania PCB na zlecenie
   • Montaż PCB od 30$ + bezpłatna dostawa i szablony
   • Darmowe narzędzie do podglądu plików Gerber
Zobacz również » Film z fabryki PCBWay

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
Link do komentarza
Share on other sites

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..

Link do komentarza
Share on other sites

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
Link do komentarza
Share on other sites

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...

 

Link do komentarza
Share on other sites

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
Link do komentarza
Share on other sites

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
Link do komentarza
Share on other sites

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...😅

Link do komentarza
Share on other sites

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
Link do komentarza
Share on other sites

Dołącz do dyskusji, napisz odpowiedź!

Jeśli masz już konto to zaloguj się teraz, aby opublikować wiadomość jako Ty. Możesz też napisać teraz i zarejestrować się później.
Uwaga: wgrywanie zdjęć i załączników dostępne jest po zalogowaniu!

Anonim
Dołącz do dyskusji! Kliknij i zacznij pisać...

×   Wklejony jako tekst z formatowaniem.   Przywróć formatowanie

  Dozwolonych jest tylko 75 emoji.

×   Twój link będzie automatycznie osadzony.   Wyświetlać jako link

×   Twoja poprzednia zawartość została przywrócona.   Wyczyść edytor

×   Nie możesz wkleić zdjęć bezpośrednio. Prześlij lub wstaw obrazy z adresu URL.

×
×
  • Utwórz nowe...

Ważne informacje

Ta strona używa ciasteczek (cookies), dzięki którym może działać lepiej. Więcej na ten temat znajdziesz w Polityce Prywatności.