For a few months now I have been working with an ARM CPU. To be specific an ARM Cortex M3 from STM (STM32107VC). Complete example
So far I am using the StdPeriphal library, and will continue to do so but I came across some oddities which led me to write the following class to represent register in more C++ way rather then calling library functions or querying registers directly. This bit shifting or masking i just do not like it.
So I came up with the idea of bitfields. Bitfields are already part of C++/C so that you can specify how many bits of a certain integer data type shall be used. (see)
But this did not satisfy my needs because at compile time I lose the information of how many bits certain parts of a struct will have and they do not map directly onto ONE memory location.
Unions on the hand do exactly that. Every data member of a union is mapped onto one location in memory which has the size of the biggest possible data member so that you can look at the underlying memory with different byte interpretations (a.k.a. the different datatypes of the unions member).
The first requirement was to define a new way for Bitfields which do not lose the information about how many bits they are describing and where they start within a memory location. This leads automatically to a template design:
template <typename T, size_t Index, size_t Bits = 1, T Mask = ((1u << Bits) - 1u)>
class BitField {
private:
T mValue;
public:
BitField &operator=(T value) {
mValue = (mValue & ~(Mask >> Index)) | ((mValue & Mask) << Index);
return *this;
}
size_t Start() { return Index; }
size_t End() { return Index + Bits; }
operator T() const { return (mValue >> Index) & Mask; }
explicit operator bool() const { return mValue & (Mask << Index); }
};
With this I have new way of describing bitfields within an byte/word or what ever I want to use as bit 'range'. The next step was to actually describe a Register. For this example I will hold that register totally generic.
union Register
{
struct Part1 {
typedef BitField<uint32_t, 0, 8> Bits;
};
struct Part2 {
typedef BitField<uint32_t, 8,8> Bits;
};
struct Part3 {
typedef BitField<uint32_t, 16, 8> Bits;
};
struct Part4 {
typedef BitField<uint32_t, 24, 8> Bits;
};
union Bit
{
Register::Part1::Bits Part1;
Register::Part2::Bits Part2;
Register::Part3::Bits Part3;
Register::Part4::Bits Part4;
} field;
uint32_t rawValue;
Register(uint32_t value) : rawValue{ value } {}
};
This union first describes the different parts for a certain theoretical register. Each struct within the union just holds an typedef for certain Bitfield with in our RawValue. The name of those structs should later of course correspond with the specification of an actual register.
The inner union then holds actual "instances" of the different typedefs which were previously defined. Through this we are capable to just ask for certain part of our rawValue.
int main(int , char**) {
uint32_t DATA = 0x1234'5678;
Register* Register = reinterpret_cast<::Register*>(&DATA);
std::cout << std::hex << "Raw Value: \t-> 0x" << Register->rawValue << std::dec << "\n";
std::cout << "Part1 " << Register->field.Part1.Start() << "-" << Register->field.Part1.End() << " \t-> 0x" << std::hex << Register->field.Part1 << std::dec << "\n";
std::cout << "Part2 " << Register->field.Part2.Start() << "-" << Register->field.Part2.End() << " \t-> 0x" << std::hex << Register->field.Part2 << std::dec << "\n";
std::cout << "Part3 " << Register->field.Part3.Start() << "-" << Register->field.Part3.End() << " \t-> 0x" << std::hex << Register->field.Part3 << std::dec << "\n";
std::cout << "Part4 " << Register->field.Part4.Start() << "-" << Register->field.Part4.End() << " \t-> 0x" << std::hex << Register->field.Part4 << std::dec << "\n";
return 0;
}
I hope you like that code snippet. Any criticism is welcome :D
struct { enum { ... } which_type; union { ... } data; };
. That problem is solved by C++17's variant, fortunately. \$\endgroup\$