2015-09-25 20 views
0

我想有效地映射帶有寄存器的結構到內存。 其實我已經工作這樣的代碼:通過引用指向結構作爲模板參數

結構寄存器的外圍:

struct Periph { 
    volatile uint32_t REG1; 
    volatile uint32_t REG2; 
}; 

在設備是位於內存中的兩個不同的地址這個周2次,所以確定這些地址:

static constexpr size_t PERIPH1_BASE = 0x40000000; 
static constexpr size_t PERIPH2_BASE = 0x40001000; 

然後我具有可以使用任何這些寄存器的駕駛員:

template<size_t Base> struct Driver { 
    inline Periph &r() { 
     return *reinterpret_cast<Periph *>(base); 
    } 
    void setFoo(uint32_t x) { 
     r().REG1 = x; 
    } 
    uint32_t getBar() { 
     return r().REG2; 
    } 
}; 

要使用這個驅動程序很簡單,只需要一些外圍的設置地址模板:

Driver<PERIPH1_BASE> drv; 
uint32_t x = drv.getBar(); 
drv.setFoo(x); 
... 

如果編譯器優化合並後的所有內聯函數,則此方法寄存器和沒有任何開銷工作非常有效。

但是這不是很安全,因爲我可以設置Driver來自不同外設的任何地址。

我的想法是改善這一點是把參考結構作爲模板參數,但沒有成功。

首先,我定義爲寄存器引用:

static Periph &PERIPH1 = *reinterpret_cast<Periph *>(PERIPH1_BASE); 
static Periph &PERIPH2 = *reinterpret_cast<Periph *>(PERIPH2_BASE); 

這是工作,我可以直接訪問這些寄存器,如:

PERIPH2.REG1 = 123; 

但我不知道怎麼打發這些引用模板參數,我的嘗試如下:

template<Periph &r> struct Driver { 
    void setFoo(uint32_t x) { 
     r.REG1 = x; 
    } 
    uint32_t getBar() { 
     return r.REG2; 
    } 
}; 

Driver<PERIPH2> drv; 
drv.setFoo(x); 

由此我得到以下錯誤:

`error: the value of 'PERIPH2' is not usable in a constant expression` 

如果我定義爲PERIPH2然後constexpr我得到另一個錯誤:

`error: reinterpret_cast from integer to pointer` 

...所以如何把引用一個對象作爲模板參數? 或者一個想法或建議,使其更好。

此外,這裏還存在很多其他解決方案(比如將引用放到Driver構造函數中......),但是這會降低對寄存器的訪問速度。

感謝您的幫助。

+0

你嘗試過'boost :: ref'或'std :: ref'嗎? –

+0

沒有提升,沒有標準,它嵌入在缺乏內存的小型MCU中 – vlk

+0

坦率地說,我認爲你的第一種方法很好 – Les

回答

1

But I have no idea how to pass these references to template argument

因爲它們是不同的東西。據我瞭解,這可能會幫助你:使用你的外設結構作爲帶有封裝基地址的單例。

template<std::size_t Base> 
struct periph { 

    static constexpr periph volatile& instance() { 
     return *reinterpret_cast<periph volatile*>(Base); 
    } 

    template<std::size_t N> 
    static constexpr std::uint32_t volatile& reg() { 
     return periph::instance().reg_[N]; 
    } 

    // prohibit instance constructing 
    periph() = delete; 
    periph(periph const&) = delete; 
    periph(periph&&) = delete; 

private: 

    uint32_t reg_[2]; 
}; 

您可以通過instance()reg<>()方法來訪問寄存器。例如:

periph<0x00001000>::reg<0>() = 0; 

現在,讓你的Driver的模板參數類型採取不值:

template<typename Periph> 
struct driver { 
    using periph_type = Periph; 

    // for example 
    void foo() { 
     periph_type::reg<0>() = 1234; 
    } 
}; 

看,Periph::instance()是你想傳遞的參考。如您所見,driver::foo()使用Periph-定義的靜態方法Periph::reg<>()來訪問外設實例,而不是顯式地址。看起來更安全。

你也可以放棄默認模板執行和實施專業化:

using periph1 = periph<0x40000000>; 
using periph2 = periph<0x40001000>; 

template<typename Periph> 
struct driver; 

template<> 
struct driver<periph1> { 
    // specialization for periph1 only 
}; 

template<> 
struct driver<periph2> { 
    // specialization for periph2 only 
}; 

或者

template<std::size_t Base> 
struct driver< periph<Base> > { 
    // use periph<Base> here 
}; 

對於另一個(不一樣periph)周邊應實現另一個類型(例如,i2c<>)。可能與實現periph<>的方式相同(使用封裝地址作爲模板參數)。但是,如果您處理多個相同類型的外設(例如,多個CAN總線),您應該/可能會使用與Base不同的相同類型。

UPDATE:

此外,你可能想看看在這個實施:

template<std::size_t Base> 
struct periph { 
private: 
    struct context { 
     std::uint32_t reg[2]; 
    }; 

    static constexpr context volatile& ctx() { 
     return *reinterpret_cast<context volatile*>(Base); 
    } 

public: 

    static volatile std::uint32_t & REG1; 
    static volatile std::uint32_t & REG2; 
}; 

template<std::size_t Base> 
volatile std::uint32_t & periph<Base>::REG1 = ctx().reg[0]; 

template<std::size_t Base> 
volatile std::uint32_t & periph<Base>::REG2 = ctx().reg[1]; 

在這種情況下地址(Base)仍然incapsulated到struct periph但寄存器可以作爲靜態成員訪問:

periph<0> p; 
p.REG1 = 1; 

periph<0>::REG1 = 0; 
+0

你的所有建議都非常有趣。我主要喜歡第一個使用singleton併發送到模板類型而不是引用的想法,第二個也很好,但需要在'.cpp'中重複使用寄存器定義,而我更喜歡只有頭文件。謝謝。 – vlk

相關問題