Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

using an absolute pointer address as a template argument

I have a template class which takes as its first template argument a foo * pointer. I'd like to instantiate one of these with a foo located at an absolute address, like so:

class foo
{
    int baz;
};

template<foo *f> class bar
{
public:
    bar() {}
    void update() { /* ... */ }
};

// ....

#define FOO_ADDR ((foo *)0x80103400)
#define FOO_NULL ((foo *)0)

foo testFoo;

bar<FOO_ADDR> myFoo;        // fails with non-integral argument
bar<FOO_NULL> huh;          // compiles, I was surprised by this
bar<&testFoo> test;         // compiles as expected (but not useful)

Does anyone know if it's possible without resorting to the linker and getting FOO_ADDR to be defined with external linkage?

This is with the Keil ARM C/C++ Compiler version V5.06 update 1 (build 61), I've tried switching on C++11 mode but (apart from throwing a load of new errors in the system headers) it didn't change the behaviour.

Update: here's the proposed solution (with the real code this time) using int casts

template<uint32 PORT, uint32 BIT, uint32 RATE> class LedToggle
{
    uint32 mTicks;
    uint32 mSetReset;

    public:

    LedToggle()
    {
        mTicks = 0;
        mSetReset = 1 << BIT;
    }

    void Update()
    {
        uint32 mask = ((mTicks++ & RATE) - 1) >> 31;
        ((GPIO_TypeDef *)PORT)->BSRR = mSetReset & mask;
        mSetReset ^= ((1 << BIT) | (1 << (BIT + 16))) & mask;
    }
};

LedToggle<(uint32)GPIOC, 13, 1023> led;

It's pretty ugly, but it does work. I'd be interested to hear if anyone can improve on it?

like image 971
Charlie Skilbeck Avatar asked May 18 '16 15:05

Charlie Skilbeck


1 Answers

Casting to/from ints works, but as pointed out, it's dangerous. Another solution similar to JimmyB's is to use enum classes instead of function pointers. The enum class member values are set to the device addresses as specified inthe vendor-supplied header. For instance, for the STM32 series, ST provides a header with the following defined:

// Vendor-supplied device header file (example)

#define GPIOA_BASE = 0x40001000
#define GPIOB_BASE = 0x40002000
//    etc...

In your code, create an enum class:

#include <vendor-supplied-device-header.h>

enum class GPIO : uint32_t {
    A = GPIOA_BASE, 
    B = GPIOB_BASE, 
    C = GPIOC_BASE, 
    D = GPIOD_BASE, 
    E = GPIOE_BASE,
    F = GPIOF_BASE,
    G = GPIOG_BASE,
    #ifdef GPIOH_BASE   //optional: wrap each member in an #ifdef to improve portability
    H = GPIOH_BASE,
    #endif
    //.. etc
};

To avoid multiple messy casts, just do it once in the class using a private method. For example then your LedToggle class would be written like this:

template<GPIOPORT PORT, uint8_t PIN, uint32_t RATE> class LedToggle
{
    static_assert(PIN < 15, "Only pin numbers 0 - 15 are valid");

    volatile auto GPIOPort(GPIOPORT PORT) {
        return reinterpret_cast<GPIO_TypeDef *>(port_);
    }

    uint32_t mTicks;
    uint32_t mSetReset;

    public:

    LedToggle()
    {
        mTicks = 0;
        mSetReset = 1 << PIN;
    }

    void Update()
    {
        uint32 mask = ((mTicks++ & RATE) - 1) >> 31;
        GPIOPort(PORT)->BSRR = mSetReset & mask;
        mSetReset ^= ((1 << PIN) | (1 << (PIN + 16))) & mask;
    }
};

LedToggle<GPIO::C, 13, 1023> led;

The benefit of this method is that the class users are forced to use only members of the GPIO enum class, therefore invalid addresses are prohibited.

You can use enum classes for any of the template parameters, for instance you could replace the PIN parameter with an enum class whose members are set to the vendor's specified GPIO_PIN_1, GPIO_PIN_2, etc. Then you'd write:

LedToggle<GPIO::C, Pin::_13, 1023> 
like image 174
Dan Green Avatar answered Sep 20 '22 03:09

Dan Green