Bitte um Hilfe bei komplexen Templates



  • Hallo,

    ich versuche die Hardware-Timer eines AVR-Prozessors in C++ zu modellieren.
    Da die meisten Anweisungen Schreib- bzw. Leseoperationen von memorymapped Register sind, dachte ich an constexpr und Templates.
    Gleichzeitig wollte ich die Benutzerschnittstelle einheitlich haben, was zu div. enum-Definitionen führte.

    Dann stellte sich heraus, dass die wichtigste Funktion eine umfangreiche switch-Anweisung ist. Das in einer Funktion verbraucht sehr viel Speicher, wobei eigentlich zu Übersetzungszeit bereits bekannt ist, welche Fälle verwendet werden.

    Das führte mich zu weiteren Template-Definitionen. Über Spezialisierung der Templates wollte ich den Switch auflösen, sodass nur noch der tatsächlich benötigte Code auch übersetzt wird.

    Soviel zur Vorgeschichte. Jetzt bin ich an einem Punkt angelangt, wo der Compiler mir Fehlermeldungen auswirft, mit denen ich nix anfangen kann.
    Das übersetzbare Beispiel besteht leider immer noch aus einer Vielzahl von Dateien - einfach weil es portabel angelegt ist.

    Die betreffenden Dateien finden sich im Pfad src/_HAL_/cpu/avr
    HalHardwareTimer.h ist die Ausgangsdatei.

    enum class TimerNum {
         Timer0,
         Timer1,
         Timer2
         };
    
    template<typename RT, typename CT>
            class HardwareTimerBase
    {
    public:
      typedef RT    RegisterType;
      typedef CT    CounterType;
    
    protected:
      // gemeinsame Basisfunktionen
      };
    
    // leeres allgemeines Template
    template<TimerNum timerNum, typename RegisterType, typename CounterType>
            class HardwareTimer : public HardwareTimerBase<RegisterType, CounterType>
    {
      };
    
    // Spezialisierungen
    template<typename RegisterType, typename CounterType>
            class HardwareTimer<TimerNum::Timer0, RegisterType, CounterType> : public HardwareTimerBase<RegisterType, CounterType>
    {
      };
    
    template<typename RegisterType, typename CounterType>
            class HardwareTimer<TimerNum::Timer1, RegisterType, CounterType> : public HardwareTimerBase<RegisterType, CounterType>
    {
      };
    
    template<typename RegisterType, typename CounterType>
            class HardwareTimer<TimerNum::Timer2, RegisterType, CounterType> : public HardwareTimerBase<RegisterType, CounterType>
    {
      };
    

    Da ich meine verstanden zu haben, dass ein spezialisiertes Template nichts von dem allgemeinen Template verwendet kann, habe ich die ehemals fetten Funktionen in den Spezialisierungen abgelegt.

    template<typename RegisterType, typename CounterType>
            class HardwareTimer<TimerNum::Timer2, RegisterType, CounterType> : public HardwareTimerBase<RegisterType, CounterType>
    {
    public:
      template<TimerMode mode, PreScale ps>
              static void startTimer() {
        setMode<mode>();
        setPrescale<ps>();
        }
      template<TimerMode mode>
              static void setMode() {
        TimerModeSetter<CounterType, mode>::setMode(configA, configB);
        }
      };
    

    TimerModeSetter ist der Versuch, den switch über Templates aufzulösen. Die Templates habe ich in einer eigenen Datei (HalHWTimerMode.h) zusammengefasst.
    Da setMode im Timer ein Funktionstemplate ist, musste ich auch Funktionen die setMode verwenden (wie z.B. startTimer) zu Templates machen.
    Die allgemeine Form (fehlerhafte Anwendung) führt zum Anhalten des Timers.

    template<typename CounterType, TimerMode mode>
            class TimerModeSetter
    {
    public:
      inline static void setMode(std::uint8_t cfgA, std::uint8_t cfgB) {
        sys::reg::dynamicAccess<std::uint8_t, std::uint8_t>::regNot( cfgA, static_cast<const std::uint8_t>(TCCR1A::WGM10 | TCCR1A::WGM11));
        sys::reg::dynamicAccess<std::uint8_t, std::uint8_t>::regNot( cfgB, static_cast<const std::uint8_t>(TCCR1B::WGM12 | TCCR1B::WGM13));
        }
      };
    

    Die Spezialisierung erfolgt für alle Anwendungsfälle, nach diesem Schema:

    template<>
            class TimerModeSetter<std::uint8_t, TimerMode::PWM_PhaseCorrect>
    {
    public:
      inline static void setMode(std::uint8_t cfgA, std::uint8_t cfgB) {
        sys::reg::dynamicAccess<std::uint8_t, std::uint8_t>::regMask(cfgA,
                                                                     static_cast<const std::uint8_t>(TCCR1A::WGM10 | TCCR1A::WGM11),
                                                                     static_cast<const std::uint8_t>(TCCR1A::WGM10));
        sys::reg::dynamicAccess<std::uint8_t, std::uint8_t>::regNot( cfgB, static_cast<const std::uint8_t>(TCCR1B::WGM12 | TCCR1B::WGM13));
        }
      };
    

    Hm, irgendwie finde ich keinen Zugang zu Anhängen?
    Wie kann/soll/darf ich ein übersetzbares Beispiel zur Verfügung stellen?

    Ja, also die Fehlermeldungen sehen so aus:

    make/../src/_HAL_/cpu/avr/HalPWM.h: In member function 'void hal::pwm::PWM<TimerType, channelNum>::start(hal::timer::ChannelMode)':
    make/../src/_HAL_/cpu/avr/HalPWM.h:27:39: error: expected primary-expression before ')' token
         TimerType::startTimer<mode, ps> ( );
                                           ^
    make/../src/_HAL_/cpu/avr/HalPWM.h: In instantiation of 'void hal::pwm::PWM<TimerType, channelNum>::start(hal::timer::ChannelMode) [with hal::timer::TimerMode mode = (hal::timer::TimerMode)16u; hal::timer::PreScale ps = (hal::timer::PreScale)3u; TimerType = hal::timer::HardwareTimer<(hal::timer::TimerNum)2, unsigned char, unsigned char>; hal::timer::Channel channelNum = (hal::timer::Channel)4u]':
    make/../src/board/ArduinoNano.cpp:34:74:   required from here
    make/../src/_HAL_/cpu/avr/HalPWM.h:27:26: error: no match for 'operator<' (operand types are '<unresolved overloaded function type>' and 'hal::timer::TimerMode')
         TimerType::startTimer<mode, ps> ( );
    

    Wie man sehen kann, führt die gleiche Code-Zeile zu unterschiedlichen Fehlermeldungen.
    Ich weiß nicht mehr weiter und bin für jede Hilfestellung dankbar!

    Gruß Reinhard


  • Mod

    Den Code, der als fehlerhaft angezigt wird, hast du offenbar nicht geliefert.
    Die Fehlermeldung lässt vermuten, dass ein template-Schlüsselwort vergessenwurde:

    TimerType::template startTimer<mode, ps> ( );
    


  • django013 schrieb:

    Da die meisten Anweisungen Schreib- bzw. Leseoperationen von memorymapped Register sind, dachte ich an constexpr

    Timerregister zur Compilezeit lesen? Auf den Gedanken wäre ich nicht gekommen.

    django013 schrieb:

    Dann stellte sich heraus, dass die wichtigste Funktion eine umfangreiche switch-Anweisung ist. Das in einer Funktion verbraucht sehr viel Speicher,

    Eine switch-Anweisung verbraucht viel Speicher?



  • @manni66
    Ja, da habbich mich wohl unglücklich ausgedrückt.
    Viel "Speicher" braucht der switch, weil alle Anweisungen aller case übersetzt werden.

    @camper
    ich versuche mal nach zu liefern:
    Eine Verwendung sieht so aus:

    void hal::systemTick::init() {
      hal::timer::HardwareTimer1Type::startTimer<hal::timer::TimerMode::CTC_ICR,
                                                 hal::timer::PreScale::PS_1>(true);
      }
    

    Dann gibt es eine weitere Template-Klasse die Timer verwendet. Dazu wird ein typedef verwendet:

    typedef HardwareTimer<TimerNum::Timer2, uint8_t, uint8_t>  HardwareTimer2Type;
    

    bei dieser Templateklasse ist "TimerType" der obige typedef:

    template<typename TimerType, hal::timer::Channel channelNum>
            class PWM
    {
      enum ChannelNum {
           ChannelUsed = channelNum
           };
    public:
      template<hal::timer::TimerMode mode, hal::timer::PreScale ps>
              void start(hal::timer::ChannelMode cm) {
        TimerType::enableChannelOutput(static_cast<hal::timer::Channel>(ChannelUsed), cm);
        TimerType::startTimer<mode, ps> ( );
        }
      void stop() {
        TimerType::stopTimer();
        }
      void setDuty(const typename TimerType::CounterType duty) {
        TimerType::setCompareValue(static_cast<hal::timer::Channel>(ChannelUsed), duty);
        }
      typename TimerType::CounterType getDuty() const { return TimerType::getCompareValue(static_cast<hal::timer::Channel>(ChannelUsed)); }
      };
    

    die wie folgt verwendet wird:

    hal::pwm::PWM<HardwareTimer2Type, Channel::B> pwm2;
    
      pwm2.start<TimerMode::FastPWM_OCR, PreScale::PS_32>(ChannelMode::Toggle);
    

    Den Vorschlag mit dem eingefügten Schlüsselwort "template" habe ich schon ausprobiert und der hat die Sache nur noch schlimmer gemacht.


  • Mod

    django013 schrieb:

    @@camper
    ich versuche mal nach zu liefern:

    template<typename TimerType, hal::timer::Channel channelNum>
            class PWM
    {
    ...
      template<hal::timer::TimerMode mode, hal::timer::PreScale ps>
              void start(hal::timer::ChannelMode cm) {
        TimerType::enableChannelOutput(static_cast<hal::timer::Channel>(ChannelUsed), cm);
        TimerType::startTimer<mode, ps> ( );
        }
    

    Hast du auch meinen Lösungvorschlag ausprobiert?

    TimerType::startTimer ist ein (von Templateparametern) abhängiger Bezeichner. Solange der Compiler die konkreten Argumente nicht kennt, kann er nicht wissen, ob startTimer ein Template, ein Typ oder etwas Anderes ist. Und solange wir dies nicht explizit mitteilen, soll der Compiler unterstellen, dass es etwas Anderes ist. Dann kann das folgende < Token nur den kleiner-als Operator meinen, und dort steht dann ein Ausdruck mit Kommaoperator, nur dass die rechte Seite offenkundig kein gültiger Ausdruck ist. Und genau deshalb steiugt der Compiler mit der ersten Fehlermeldung aus. Wohlgemerkt: die bekommst du immer, ohne dass das Template überhaupt instantiert wird.
    Die zweite Meldung entsteht dann beim Instantiieren: hier wird der Fehler noch etwas früher angezeigt, weil der Compiler mit den konkreten Templateargumenten gar nicht erst einen passenden <-Operator findet.



  • Hast du auch meinen Lösungvorschlag ausprobiert?

    Ja, aber offensichtlich mit einem Tippfehler 😞
    die Fehlermeldungen wurden immer schlimmer.

    Gerade habe ich es nochmal ausprobiert und siehe da, per Zufall richtig geschrieben und jetzt wird alles fehlerfrei übersetzt.
    Herzlichen Dank!

    @manni66
    auch wenn ich es jetzt nicht belegen kann - mein Konzept ist aufgegangen.
    Ohne die Template-Hilfsklassen war die Funktion mit dem Switch die größte Funktion.
    Jetzt nach der Umstellung auf die Hilfs-Templates taucht die Funktion nicht mehr in der Top-10 der fettesten Funktionen auf 🙂


Anmelden zum Antworten