Rejestry w C++

W tym miejscu zadajemy pytania na temat języka C++, dzielimy się swoją wiedzą, udzielamy wsparcia, rozwiązujemy problemy programistyczne.
Awatar użytkownika
Federerer
Posty: 9
Rejestracja: wtorek 15 gru 2015, 21:43

Rejestry w C++

Postautor: Federerer » niedziela 20 gru 2015, 12:04

Witam, tworzę sobie urządzenie, które będzie widziane "z zewnątrz" jako rejestry we/wy. Jako, że zwykle operacja zapisu bądź odczytu nie jest jedynie zapisem/odczytem wartości z ramu, tylko wymaga pewnego fragmentu kodu, wykombinowałem sobie taką koncepcję: Rejestr posiada dwie metody: "Get" i "Set" umożliwiające wstrzyknięcie odpowiedniej funkcjonalności odczytu i zapisu (np wywołanie funkcji ustawiającej timer w tryb PWM, lub cokolwiek innego, przy zapisie). Oprócz tego dodałęm pole data do przechowywania aktualnej wartości rejestru. Kod wygląda tak:
register.h

Kod: Zaznacz cały

#ifndef REGISTER_H_
#define REGISTER_H_

#include <stdint.h>

class Register
{
public:

   uint8_t Data;

   void Get(uint8_t (*Geter)(Register&));
   void Set(void (*Setter)(Register&, uint8_t));

   uint8_t Get(void);
   void Set(uint8_t);

private:

   uint8_t (*Geter)(Register&);
   void (*Setter)(Register&, uint8_t);

};

#endif /* REGISTER_H_ */

register.cpp

Kod: Zaznacz cały

#include "register.h"
#include "stddef.h"

uint8_t Register::Get(void)
{
   if(this->Geter != NULL)
   {
      return this->Geter(*this);
   }
   else
   {
      return Data;
   }
}

void Register::Set(uint8_t value)
{
   if(this->Setter != NULL)
      {
         this->Setter(*this, value);
      }
   else
   {
      Data = value;
   }
}

void Register::Get(uint8_t (*Geter)(Register&))
{
   this->Geter = Geter;
}

void Register::Set(void (*Setter) (Register&,uint8_t))
{
   this->Setter = Setter;
}

No i teraz tworzę sobie "mapę rejestrów":
register_map.h

Kod: Zaznacz cały

#ifndef REGISTER_MAP_H_
#define REGISTER_MAP_H_

#include "register.h"

class RegisterMap
{
public:
   RegisterMap();
   Register * const ByIndex[64] = {
         &IN, &OUTMH, &OUTML, itd...
   };
   Register IN;
   Register OUTMH;
   Register OUTML;
   itd...

};


#endif /* REGISTER_MAP_H_ */

Implementacja getterów i setterów:

Kod: Zaznacz cały

#include "register_map.h"

RegisterMap::RegisterMap()
{
   //IN
   IN.Get([](Register& reg) -> uint8_t
   {
      return reg.Data;
      //albo odczyt z rejestrów procesora/układów peryferyjnych/cokolwiek...
   });
   IN.Set([](Register& reg, uint8_t value) -> void
   {
      reg.Data = value;
      //tu może być wywołanie funkcji ustawiającej port jako wejście...
   });

   //OUTMH
    OUTMH.Get([](Register& reg) -> uint8_t
   {
      return reg.Data;
   });
   OUTMH.Set([](Register& reg, uint8_t value) -> void
   {
      reg.Data = value;
   });

   //OUTML
   OUTML.Get([](Register& reg) -> uint8_t
   {
      return reg.Data;
   });
   OUTML.Set([](Register& reg, uint8_t value) -> void
   {
      reg.Data = value;
   });
   
   itd...

}

Potem w mainie:

Kod: Zaznacz cały

   Registers.IN.Set(8);
   Registers.ByIndex[0]->Set(8);
   uint8_t a = Registers.IN.Get();
   uint8_t b = Registers.ByIndex[0]->Get();

To taka wstępna koncepcja i ma pewne wady:
- wskaźniki siedzą w ramie
- rejestry są tylko ośmiobitowe
Podejrzewam, że drugi problem rozwiąże zastosowanie szablonu? ale nie wiem jak zrobić, żeby wskaźniki były const. Zakładam oczywiście, że przypięte funkcje Get i Set nie będą zmieniane w trakcie pracy programu.

Awatar użytkownika
mokrowski
User
User
Posty: 190
Rejestracja: czwartek 08 paź 2015, 20:50
Lokalizacja: Tam gdzie Centymetro

Re: Rejestry w C++

Postautor: mokrowski » niedziela 20 gru 2015, 16:30

No i jak Ci tu odpisać żeby nie wyszedł elaborat...

Pytasz "jak szablonem..." więc tylko o tym napiszę bez dywagacji o kosztach i zaletach tego podejścia.

Moim zdaniem przy odbsłudze rejestrów w C++, problem leży w tym że należy zdefiniować dobrą i nie tworzącą narzutu otoczkę obiektową dla pojedynczego portu I/O. Ta otoczka jest potrzebna aby można było port przekazać sensownie do klasy jako argument szablonu. Jeśli (jak to jest w ARM) jest to struktura, to masz ułatwienie bo ona może być argumentem szablonu. Jeśli nie (a tak jest np. w AVR), to trzeba tenże
zdefiniowany port "rozebrać makrem".

Z definicji, port jest tak naprawdę wskaźnikiem na pamięć. Powinien więc "udawać" dostęp do normalnego wskaźnika. Ma także swoje rodzaje co do ilości bajtów oraz kierunku (zapis, odczyt, zapis i odczyt).

Jestem teraz w pociągu i nie mam dostępu do zestawu uruchomieniowego. Napiszę pseudokod (choć będę się starał aby był to C++)
i jedynie przedstawię to ideowo.

Jeśli port można przekazać adresem (czyli uint*_t) to:

Kod: Zaznacz cały

template<uint32_t Reg_t, typename Value_t>
struct Port {
    static volatile * Value_t ptr() { return &Reg_t; }
    Port& operator=(const Value_t& value) { *ptr() = value; }
    Value_t operator()(const Value_t& value) { return *ptr(); }
};

// Dany port zdefiniujesz np. tak.
// Oczywiście jak zamias tych "magic number" masz symbole z makr, to lepiej nazwy.
// Ja to pokazuję dla zrozumienia...
typedef Port<0x14343244, uint16_t> FooPort;
typedef Port<0x6783443A, uint32_t> BarPort;

// Dostęp do portu to
// Zapis:

FooPort out_foo;
*out_foo::ptr() = 0x1423;

// Odczyt:

BarPort in_port;
auto val = *in_port::ptr();


Ja wiem że to wygląda na pierwszy rzut oka dość koszmarnie, ale to poziom "drutów" i za chwilę będą porty wysokiego poziomu.

Aha.. jeszcze porty wyłącznie do zapisu.. po prostu nie implementujesz odpowiednich metod lub.. rozważ dodanie operatora wyłuskania (operator*).

Jeszcze będziesz potrzebował abstrakcji typu "bit na porcie". Tu już łatwiej:

Kod: Zaznacz cały

template<typename Port_t, uint8_t BitNumber>
struct BitOnPort {
    static void clear() {
        *Port_t::ptr() &= ~(1 << BitNumber);
    }
    static void set() {
        *Port_t::ptr() |= (1 << BitNumber);
    }
    static void toggle() {
        *Port_t::ptr() ^= (1 << BitNumber);
    }
    static bool value() const {
        return *Port_t::ptr() & (1 << BitNumber) ? true : false;
    }
};

// No i definicje tych istotnych dla Ciebie bitów tak..
typedef BitOnPort<FooPort, 1> SecondOnFooPort;
typedef BitOnPort<BarPort, 0> FirstOnBarPort;


Jak chcesz innym ułatwić pracę z tą klasą, to dodaj jeszcze metody on(), off(), get() które będą wołały już zdefiniowane. Ot niektórzy myślą set() a niektórzy on() :-)

Ok. Jak wiadomo _rzeczywisty_port_, posiada także rejesty kontrolne. Peryferia także je posiadają.
Ja tu jakieś peryferium wymyślę.. :-)

Kod: Zaznacz cały

template<typename Port_t, typename UpDownBit_t, bool Direction>
struct LaunchRocketPort {
    svoid initRocket() {
        // Opreracje na Port_t, UpDownBit_t - czyli np. podciąganie i na ustawianiu jekiegoś kierunku I/O..
    }
    void countDown(uint8_t counter) {
        // No odliczanie...
    }
    bool prepareToLaunch() {
        // Spawdzi czy wszystko gotowe
    }
    void start(uint8_t counter) {
        if(prepareToLaunch()) {
            countDown(counter);
            ignition();
            goToMoon();
        } else {
            // I sruu... nie poleciała :-)
        }
    }
};


Jak wiesz że to będzie jedna rakieta i chcesz "spłaszczać" typem jak widziałeś wcześniej dla Port, to możesz zamienić na static i dodać typedef :-)

Ja w swojej implementacji jeszcze mam warstwę InputPort i OuputPort oraz InputOutputPort która łączy
elementy portu z podciąganiem, ustawianiem "na prędkość" (tj. stromość zbocza). Niemniej samą ideę mam
nadzieję przekazałem :-)

Daj znać czy to jest dla Ciebie zrozumiałe.
,,Myślenie nie jest łatwe, ale można się do niego przyzwyczaić" - Alan Alexander Milne: Kubuś Puchatek

Awatar użytkownika
Federerer
Posty: 9
Rejestracja: wtorek 15 gru 2015, 21:43

Re: Rejestry w C++

Postautor: Federerer » niedziela 20 gru 2015, 19:18

Mnie
mokrowski pisze:Pytasz "jak szablonem..." więc tylko o tym napiszę bez dywagacji o kosztach i zaletach tego podejścia.

To poproszę jeszcze o dywagację. Jeśli dobrze zrozumiałem to korzystniejsze w moim przypadku jest dążenie do polimorfizmu statycznego, żeby uniknąć trzymania w ramie tych niepotrzebnych wskaźników. Tak kombinuję i myślę czy może nie lepiej zrobić sobie klasę bazową rejestru modbusowego z metodami wirtualnymi get/set??

Awatar użytkownika
mokrowski
User
User
Posty: 190
Rejestracja: czwartek 08 paź 2015, 20:50
Lokalizacja: Tam gdzie Centymetro

Re: Rejestry w C++

Postautor: mokrowski » niedziela 20 gru 2015, 21:18

No to dywagacje są takie...

1. Na ile "dynamiczny" ma być ten Twój modbus?
2. Ile ma być tych modbus'ów?
2. Czy potrzebujesz zmieniać działanie modbus'a w trakcie działania aplikacji?

W programowaniu na MCU, często mamy do czynienia z sytuacją: "robię klasę, z niej obiekt i żyje on do końca działania aplikacji" czyli.. jak nie wyłączę urządzenia to... do końca świata.
Stąd (najczęściej powiadam) nie pojawia się konieczność robienia new (czyli dynamicznego alokowania obiektu) a tylko jego 1 krotna inicjalizacja i ... używanie.
Jeśli tak jest, to sensownym podejściem jest stosowanie szablonów bo nie trzymasz wskaźników które _nigdy_nie_będą_zmieniane_ oraz nie tworzysz metod coś jak register*(), unregister*(), alloc*() .... bo ich ... nie użyjesz :-)

Jeśli jednak masz np. timer programowy który ma działać jakoś tak: "za 5 sekund uruchom mi tą funkcję jednokrotnie", to wiadomo że wskaźnik bo po 1 krotnym uruchomieniu funkcję wywoływaną należy "wyrejestrować" (mniej mądrze ustawić jej adres wskaźnika na nullptr i sprawdzać czy nim nie jest żeby nie było próby uruchomienia).

A jak mamy timer typu: "uruchamiaj mi tę funkcję co 1 sekundę" to także wskaźnik? A po co ? :-) to będzie timer którego _nigdy_ nie przestawimy na np. 7 sekund i/lub nie zmienimy funkcji uruchamianej co te 5 sekund. :-) No to lepiej podać te 5 sekund jako stałą w szablonie a nie robić metodę set_timeout(unsigned seconds). Nie uważasz?

No i tu dochodzimy do sedna. W MCU najczęściej masz do czynienia z polimorfizmem statycznym i takim "składaniem" struktur z szablonów. Rzadziej z polimorfizmem dynamicznym z new, delete oraz metodami virtual.

Tak na marginesie. Zastanów się czy chcesz virtual. Wywołanie metody virtual jest o ok 10 x wolniejsze niż normalnej (nie virtual). Powód? Przy każdej virtual jest tablica vtable która przechowuje wskaźniki do funkcji które należy uruchomić. Przeglądanie tej tablicy i uruchomienie funkcji kosztuje. Dostajesz "po łbie" jeśli chodzi o pamięć (tablicę trzeba przechować) i działanie (tablicę trzeba przejrzeć). Na słabej platformie bym się w to nie pchał (jeszcze raz... chyba że bym musiał).

Ja tu napisałem o ZASADACH ale jak ktoś wie co i jak, to może je łamać... ludzie zawsze łamali zasady i to także ok jeśli się wie .... jakie są konsekwencje zasad i ... czym za to płacimy.. :-/

No i znów się wymądrzałem...

Teraz zrozumiałe? :-)
,,Myślenie nie jest łatwe, ale można się do niego przyzwyczaić" - Alan Alexander Milne: Kubuś Puchatek

Awatar użytkownika
Federerer
Posty: 9
Rejestracja: wtorek 15 gru 2015, 21:43

Re: Rejestry w C++

Postautor: Federerer » wtorek 22 gru 2015, 21:41

mokrowski pisze:1. Na ile "dynamiczny" ma być ten Twój modbus?
2. Ile ma być tych modbus'ów?
2. Czy potrzebujesz zmieniać działanie modbus'a w trakcie działania aplikacji?


Ad1. Statyczny, raz definiuje i jest.
Ad2. Jeden.
Ad2. Nie, wszystko jest już ustalone na etapie kompilacji.

Czyli w tym wypadku rozsądniej szablonem?

Awatar użytkownika
mokrowski
User
User
Posty: 190
Rejestracja: czwartek 08 paź 2015, 20:50
Lokalizacja: Tam gdzie Centymetro

Re: Rejestry w C++

Postautor: mokrowski » wtorek 22 gru 2015, 23:04

Chyba szablon będzie lepszy. Chociaż powinieneś sobie odpowiedzieć na pytanie: Czy chcesz walczyć z szablonami?
Lepiej przecież zrobić jakkolwiek niż "rozgrzebać i się zniechęcić" :-)
,,Myślenie nie jest łatwe, ale można się do niego przyzwyczaić" - Alan Alexander Milne: Kubuś Puchatek


Wróć do „Pisanie programów w C++”

Kto jest online

Użytkownicy przeglądający to forum: Obecnie na forum nie ma żadnego zarejestrowanego użytkownika i 5 gości