1

I have been digging into some embedded C++ firmware used by DaveJone's (eevblog) uSupply project

https://gitlab.com/eevblog/usupply-firmware.

There is common pattern of code that I just can't quite wrap my head around what is happening.

For example: In the file "RegistersRCC.hpp" there is a template struct:

template <std::size_t A>
struct CR : public General::u32_reg<A>
{
    using base_t = General::u32_reg<A>;
    using base_t::base_t;

    //PLL register bits
    auto PLLRDY () { return base_t::template Actual<RCC_CR_PLLRDY>(); }
    auto PLLON  () { return base_t::template Actual<RCC_CR_PLLON>(); }

    //PLL Management functions
    void EnablePLL() noexcept
    {
        if ( not PLLON().Get() )
        {
            PLLON() = true;
            while ( not PLLRDY().Get() );
        }
    }
    void DisablePLL() noexcept
    {
        if ( PLLON().Get() )
        {
            PLLON() = false;
            while ( PLLRDY().Get() );
        }
    }

    //Enable clock security
    auto CSSON  () { return base_t::template Actual<RCC_CR_CSSON>(); }

    //High speed external oscillator bits
    auto HSEBYP () { return base_t::template Actual<RCC_CR_HSEBYP>(); }
    auto HSERDY () { return base_t::template Actual<RCC_CR_HSERDY>(); }
    auto HSEON  () { return base_t::template Actual<RCC_CR_HSEON>(); }

    //HSE Management functions
    void EnableHSE()
    {
        if ( not HSEON().Get() )
        {
            HSEON() = true;                 //Enable the clock
            while( not HSERDY().Get() );    //Wait for it to stable
        }
    }
    void DisableHSE()
    {
        if ( HSEON().Get() )
        {
            HSEON() = false;            //Disable the clock
            while( HSERDY().Get() );    //Wait for it to disable
        }
    }
    void ConnectHSE()
    {
        HSEBYP() = false;   //Connect it to system
    }
    void BypassHSE()
    {
        HSEBYP() = true;    //Disconnect it to system
    }

    //High speed internal oscillator bits
    auto HSICAL () { return base_t::template Actual<RCC_CR_HSICAL>(); }
    auto HSITRIM() { return base_t::template Actual<RCC_CR_HSITRIM>(); }
    auto HSIRDY () { return base_t::template Actual<RCC_CR_HSIRDY>(); }
    auto HSION  () { return base_t::template Actual<RCC_CR_HSION>(); }

    //HSI Management functions, No calibration provided
    // these chips are factory calibrated
    void EnableHSI()
    {
        if (not HSION().Get())
        {
            HSION() = true;
            while (!HSIRDY());
        }
    }
    void DisableHSI()
    {
        if ( HSION().Get() )
        {
            HSION() = false;
            while (HSIRDY());
        }
    }
};

This struct exists in the namespace:

namespace Peripherals::RCCGeneral
{

}

Within the same namespace/header file there is this "Default"

CR() -> CR<RCC_BASE + offsetof(RCC_TypeDef, CR)>;

I think this is where my gap in understanding lies. What is happening here? Specifically with the lvalue and arrow operator, and why this is located within the header.

Within the files that utilize the RCCRegisters you see usages like:

CR{}.DisablePLL();
Bryan
  • 13
  • 2

1 Answers1

2

This is called class template argument deduction(CTAD) which allows writing deduction guides to the compiler about how to deduce the template arguments from constructor calls.

It is a handy C++17 addition that saves on typing:

std::vector x{1.,2.,3.} //deduces std::vector<double>

C++14 and older requires to explicitly write std::vector<double> which gets tedious and too verbose for some more complex examples.

In this case, the guide

CR() -> CR<RCC_BASE + offsetof(RCC_TypeDef, CR)>;

specifies that the default constructor should deduce A template parameter to RCC_BASE + offsetof(RCC_TypeDef, CR).

The same could have been achieved by simply using a default template argument:

template <std::size_t A = default_value>
struct CR : public General::u32_reg<A>{ ... };

But here comes the catch, offsetof(RCC_TypeDef, CR) is not valid here because at this line, CR doesn't exist yet.

So my assumption is this a fix around this limitation to allow making the default value depend on the class definition itself, quite clever I think.

Quimby
  • 17,735
  • 4
  • 35
  • 55
  • 1
    Cool that makes a lot of sense when you write it the alternative way you show. offsetof(RCC_TypeDef, CR) I believe is valid because RCC_TypeDef is a struct defined in another header with a member named CR. – Bryan Oct 11 '22 at 20:44
  • @Bryan Oops, I totally exchanged `offsetof`'s arguments... in that case I am not sure why they did not use default template argument. – Quimby Oct 11 '22 at 20:46
  • 1
    Not sure, might just be for readability in the sense that all the MCU specific memory address are located in one block of code. – Bryan Oct 11 '22 at 21:01