Skocz do zawartości

Własna Class'a i problemy z przerwaniami PCINT (zielony)


MarCiu

Pomocna odpowiedź

Witam 

Chciałem trochę posprzątać w obecnym projekcie i zrobić własne klasy w oddzielnych plikach i ogółem zaprowadzić trochę porządku.

I o ile wszystko miałem w zwykłych procedurach to działało, to tu jestem zielony i kompletnie sobie nie radzę (Jest to moje pierwsze zetknięcie i klasami i oddzielnymi bibliotekami). Niestety do tego słaby angielski nie pomaga i trochę błądzę i nie potrafię dojść w jakim kierunku podążać.

Klasę ze zwykłym miganiem diodą (przekazanie pinu i stanu), zrobiłem bez problemu. Ale z czymś takim jak to siedzę od wieczora..

Ale do rzeczy. Chciałem przenieść obsługę silników i enkoderów do oddzielnych plików/klas.

klasa wygląda tak:

plik encoder.h:

#ifndef encoder_h
#define encoder_h

#include "Arduino.h"
#include <pcint.h>

class Encoder {
  public:   
    byte _encoder_pin_A;
    byte _encoder_pin_B;
    void ISR_encoder_A();
    void ISR_encoder_B();
    
	Encoder(int pin_A, int pin_B);
    void begin();
    int get_cnt();
    int _enc_cnt = 0;
};

#endif

 plik encoder.cpp:

#include "Arduino.h"
#include "encoder.h"
#include <pcint.h>

Encoder::Encoder(int pin_A, int pin_B) {
  _encoder_pin_A = pin_A;
  _encoder_pin_B = pin_B;
}

void Encoder::ISR_encoder_A() {
  if ( digitalRead(_encoder_pin_A)  != digitalRead(_encoder_pin_B) ) _enc_cnt++;
  else _enc_cnt--;
}
void Encoder::ISR_encoder_B() {
  if ( digitalRead(_encoder_pin_B)  == digitalRead(_encoder_pin_A) ) _enc_cnt++;
  else _enc_cnt--;
}

int Encoder::get_cnt(){
  return _enc_cnt;
}

void begin(){
  pinMode(_encoder_pin_A, INPUT_PULLUP);
  pinMode(_encoder_pin_B, INPUT_PULLUP);
  PCattachInterrupt<_encoder_pin_A>(Encoder::ISR_encoder_A, CHANGE);
  PCattachInterrupt<_encoder_pin_B>(Encoder::ISR_encoder_B, CHANGE);
}

Arduino sypie mi takimi błędami:

C:\Arduino\Nano\SMARS\encoder.cpp: In function 'void begin()':
C:\Arduino\Nano\SMARS\encoder.cpp:26:33: error: invalid use of non-static member function 'void Encoder::ISR_encoder_A()'
   PCattachInterrupt<4>(Encoder::ISR_encoder_A, CHANGE);
                                 ^~~~~~~~~~~~~
C:\Arduino\Nano\SMARS\encoder.cpp:27:33: error: invalid use of non-static member function 'void Encoder::ISR_encoder_B()'
   PCattachInterrupt<5>(Encoder::ISR_encoder_B, CHANGE);
                                 ^~~~~~~~~~~~~

exit status 1

Compilation error: invalid use of non-static member function 'void Encoder::ISR_encoder_A()'

Coś robię nie tak. Możecie pomóc?

 

  • Lubię! 1
Link do komentarza
Share on other sites

Metoda na przykład Encoder::ISR_encoder_A nie przynależy do klasy, a do konkretnego obiektu, który jest instancją tej klasy. Każdy obiekt ma swoją metodę. Ona gdzieś tam w końcu uruchamia kod, który jest tu napisany, ale, jeśli metoda jest przeciążona albo wirtualna, to ten kod może być inny.

 

Z syntaktycznego punktu widzenia: w tej linii

PCattachInterrupt<_encoder_pin_A>(Encoder::ISR_encoder_A, CHANGE);

Odwołujesz się do "metody klasy", a taki byt nie istnieje. Nie istnieje (w tym wypadku) też żaden obiekt, który by był tej klasy.

Masz dwa wyjścia:

  • albo mieć obiekt typu Encoder, który to obiekt będzie mieć metodę ISR_encoder_A, i dla tego konkretnego obiektu  wydobędziesz adres jego metody, i ten adres przekażesz do PCattachInterrupt.
    • taka zmienna powinna być gdzieś powołana do życia, albo podobnie jak jak zmienne proste
    • Encoder encoder;
      
      // podobnie jak
      
      int value;

      albo przez new()

    • Encoder *encoder = new Encoder{constructor_parameters};
      // BTW: kiedyś się pisało tak
      Encoder *encoder = new Encoder(constructor_parameters);

      I wtedy z takiego obiektu wydobywasz adres

    • popatrz na kod na przykład pierwszy z brzegu: post numer #9 w https://forum.arduino.cc/t/use-attachinterrupt-with-a-class-method-function/301108/8

  • albo powiesz, że istnieje jedna, konkretna metoda klasowa (nie obiektowa), a ona istnieje bez potrzeby mania obiektu, i wtedy to się nazywa metoda statyczna, i trzeba jej dać przedrostek "static". Kompilator mówi, że on by właśnie chciał taką statyczną metodę. Ten zapis z operatorem czterokropek to właśnie jest referencja do metody właściwej dla klasy, a nie dla obiektu. Ma to niedogodności wielkie: metoda nic nie wie o obiektach, więc nie ma dostępu do atrybutów, nawet nie ma jak cnt zmienić. Można oczywiście mieć też atrybuty statyczne (też z przedrostkiem "static"), i wtedy są to atrybuty wspólne dla całej klasy. I wszystko co się do nich odnosi musi być statyczne... I wtedy tan kod nie przypomina już nawet C++

BTW. Kod ze wspominanego przykładu jest daleki od poprawności, ale ma szanse zadziałać.

Ten wygląda lepiej: https://community.particle.io/t/cpp-attachinterrupt-to-class-function-help-solved/5147/2

W tym przykładzie, bardzo sensownym - attachInterrupt jest użyte w konstruktorze obiektu. Konkretnego obiektu. Ten konkretny obiekt wie, gdzie leży w pamięci, w szczególności wie, gdzie jest jego metoda do obsługi przerwań, potrafi wskazać jej adres i potrafi ją podwiesić pod przerwanie.

Czyli tworzymy obiekt i temu obiektowi mówimy: weź się konkretny obiekcie podłącz pod przerwanie (którego numer przekazujemy jako parametr do konstruktora)

----

A. I użycie podkreślenia na początku identyfikatora jest w C++ zarezerwowane dla twórców języka, żeby nie było konfliktu nazw (oni używają wyłącznie podkreślenia na początku, my nie używamy nigdy, nie wejdziemy sobie w drogę)

----

A. A te ify Ci się kompilują? "if ( digitalRead( ... " bo tam jest średnik na końcu i "else" chyba w powietrzu wisi.

----

A. I atrybut "_enc_cnt", który jest modyfikowany w przerwaniu wypadałoby opatrzyć przedrostkiem "volatile". Żeby się kompilator nie rozpędzał z optymalizacją

----

A. I większość rzeczy w klasie raczej może być prywatna. Konstruktor i get_cnt - publiczne, begin zniknie, reszta private

 

Edytowano przez jbanaszczyk
  • Lubię! 2
Link do komentarza
Share on other sites

Bardzo dziękuję za tak szczegółowe rozwinięcie problemu i chęć wyjaśnienia przedszkolakowi jak działa prom kosmiczny 🙂

Jutro podrapie się w głowę i postaram się w to wgryźć z myśleniem.

Odnośnie końcowych punktów A 🙂

Tak wiem o volatile, private i public itd. To już było "rzeźbienie" do potęgi entej, po godzinach siedzenia i kombinowania a może tu a może tam, a może to przeszkadza... 

Jeszcze raz bardzo dziękuję 🙂

  • Lubię! 1
Link do komentarza
Share on other sites

14 godzin temu, MarCiu napisał:

Bardzo dziękuję za tak szczegółowe rozwinięcie problemu i chęć wyjaśnienia przedszkolakowi jak działa prom kosmiczny 🙂

Każdy był kiedyś przedszkolakiem

W ogóle to łączenie przerwań z obiektami w C++ jest dość trudne i ryzykowne. Klasy i całe to programowanie mają za zadanie wprowadzenie pewnej reużywalności kodu, ale co to za reużywalność, jak przerwanie jest jedno? No chyba, że encoderów masz mieć wiele, to wtedy reużywalność ma sens.

Problem jest tu: procedura w C++ powinna działać na rzecz konkretnego obiektu, żeby konkretnemu obiektowi zmieniać "cnt", powinna działać w pewnym kontekście. A procedura obsługi przerwania jest blisko sprzętu, żeby być szybka to powinna być prymitywna do bólu, i o żadnym ce-plus-plusowym kontekście słyszeć nie chce, a próby dostarczenia jej tego kontekstu powodują ból głowy.

Ze dwa razy wpuściłem się w "klasy i przerwania", bo mogę i ładnie by było mieć, powstały bardzo rozbudowane twory, nawet działały, ale ... to nie było to, końcowy efekt nie powalał na kolana. Szkoda wysiłku. Dla porządku - wywal te procedury do oddzielnego pliku, zostaw je po staremu, w dobrze napisanym C. Oszczędzisz sobie "rzeźbienia" i stressu

Edytowano przez jbanaszczyk
  • Lubię! 1
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

Prawdę mówiąc przez "nocne dumanie", doszedłem do podobnych wniosków. Trochę za dużo rzeźbienia i sztuki dla samej sztuki. 

Tworzenie "sztucznych" procedur i ukrywanie w nich tego co powinno być normalnie dostępne jest trochę mi niepotrzebne i mija się celem. Poukładam w plikach i też będzie dobrze.

Nie mniej dzięki raz jeszcze nad pochyleniem się nad tematem.

pozdrawiam

Marcin

  • Lubię! 2
Link do komentarza
Share on other sites

Dnia 28.07.2022 o 23:37, jbanaszczyk napisał:

albo mieć obiekt typu Encoder, który to obiekt będzie mieć metodę ISR_encoder_A, i dla tego konkretnego obiektu  wydobędziesz adres jego metody, i ten adres przekażesz do PCattachInterrupt.

Nie ma adresu metody konkretnego obiektu. Jest jedna i ta sama funkcja, której "pod maską" podczas wywołania jest przekazywany adres obiektu jako argument. Brzydki sposób na prezentację:

#include <cstdio>

class Foo {
	public:
		Foo(int i) : i(i) {}
		void bar() { printf("%d\n", i); }
	private:
		int i;
};

int main()
{
	Foo foo1(10), foo2(20);
	auto ptr = (void (*)(Foo *foo))&Foo::bar;
	ptr(&foo1);
	ptr(&foo2);
}

 

  • Lubię! 1
Link do komentarza
Share on other sites

22 godziny temu, trainee napisał:

Nie ma adresu metody konkretnego obiektu. Jest jedna i ta sama funkcja, której "pod maską" podczas wywołania jest przekazywany adres obiektu jako argument.

Tak, gdzieś trzeba było postawić granicę uproszczenia. W drugim poście trochę to uszczegółowiłem, bardziej poprawnie

 

Link do komentarza
Share on other sites

Ja w takim przypadku robiłem coś w stylu, że po stworzeniu obiektu już w programie głównym, to robiłem funkcje dla przerwań w których wywoływałem funkcje z konkretnych obiektów. Nie jest pewnie do końca to co chciałeś osiągnąć ale działa 🙂 

Ja robiłem to przy okazji sterowników do silników. Po prostu musiałem pamiętać, żeby dorobić funkcje dla przerwań 😛

 

Encoder encoder;

void ISR_encoder_A()
{
  encoder.ISR_encoder_A();
}

PCattachInterrupt<_encoder_pin_A>(ISR_encoder_A, CHANGE);

do przerwania przekazuje funkcje, która wywołuje metode obiektu. 

Edytowano przez Dantey
Link do komentarza
Share on other sites

Ja coś podobnego przerabiałem tworząc task FreeRTOS-a. Trzeba tylko pamiętać, aby w razie potrzeby zniszczenia obiektu wywalić najpierw przerwanie/task, bo inaczej program może pójść w buraki!

Link do komentarza
Share on other sites

Jeśli wydaje mi się, że dobrze odgadłem, jaką bibliotekę, @MarCiu, wybrałeś do obsługi przerwań i jest to PCINT autorstwa Rui Azevedo, to ma ona interfejs odpowiedni do szanujących się bibliotek z callbackami, czyli pozwala poza adresem funkcji podać dodatkowy wskaźnik, który będzie tej funkcji przekazany podczas wywołania, aka "user data". Jedynie nie jest to tam udokumentowane. Z jego pomocą można też połączyć wszystko tak, by uniknąć wspominanego wyżej problemu. Przykład:

#include <pcint.h>

class LightSwitch {
  public:
    LightSwitch(int buttonPin, int lightPin);
    ~LightSwitch();
  private:
    int buttonPin;
    int lightPin;
    int lit = 0;
    static void interruptHandler(void *userData);
};

LightSwitch::LightSwitch(int buttonPin, int lightPin)
  : buttonPin(buttonPin), lightPin(lightPin) {
  pinMode(lightPin, OUTPUT);
  PCattachInterrupt(buttonPin, mixHandler(&interruptHandler, this), FALLING);
}

LightSwitch::~LightSwitch() {
  PCdetachInterrupt(buttonPin);
  digitalWrite(lightPin, LOW);
}

void LightSwitch::interruptHandler(void *userData) {
  auto self = static_cast<LightSwitch *>(userData);
    
  self->lit = !self->lit;
  digitalWrite(self->lightPin, self->lit);
}

void setup() {
  Serial.begin(9600);
}

void loop() {
  LightSwitch ls1(2, 8);
  auto ls2 = new LightSwitch(3, 9);
  
  Serial.println("Send something to destroy one switch.");

  for (;;) {
    auto incoming = Serial.readString();
    if (incoming.length() > 0)
      break;
  }

  delete ls2;
  Serial.println("One destroyed, one still kicking.");
  
  for (;;)
    /* do nothing */;
}

PS celowo nie użyłem pokracznej moim zdaniem składni PCAttachInterrupt<buttonPin>(...), bo takie wykorzystanie szablonów przez autora wydaje mi się bezsensowne, tworzy jakąś dziwną formę niepasującą do języka, a i tak generuje ostatecznie wersję z numerem pinu jako argument. Przy czym ja nie jestem wyznawcą C++, więc może ktoś inny uważa ten pomysł za wyśmienity.

Edytowano przez trainee
  • Lubię! 2
Link do komentarza
Share on other sites

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

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.