if (stm32f103xb::usb->istr.any(Usb::Istr::RESET)) {Here is an example from usb_dev.cxx as most of these probably will be. We know what this does. It tests the interrupt status register (istr) in the usb controller and tests if the RESET bit is set. We know this by context and "squinting" at this code rather than understanding it.
We can follow this back to a definition in stm32f103xb.h that looks like this. Just as a note, this struct is inside of struct Usb. You might guess this from the syntax "Usb::Istr::RESET" above.
struct Istr { using pos_t = PosWow! Now there is a lot of tricky C++ code. Let's discuss some of it. "Pos" is a template class defined in regbits.h. You might guess this from the angle brackets. I won't (for now) dive into the way that is set up in regbits.h, but the template facility is used like a fancy sort of macro substitution. It isn't hard to see what is doing on. The bit positions in the istr register are being made into pos_t objects, then these are used to generate definitions like "RESET"; static constexpr pos_t EP_ID_POS = pos_t( 0), DIR_POS = pos_t( 4), ESOF_POS = pos_t( 8), SOF_POS = pos_t( 9), RESET_POS = pos_t(10), SUSP_POS = pos_t(11), WKUP_POS = pos_t(12), ERR_POS = pos_t(13), PMAOVR_POS = pos_t(14), CTR_POS = pos_t(15); using bits_t = Bits ; static constexpr bits_t DIR = bits_t(1, DIR_POS), DIR_OUT = bits_t(1, DIR_POS), ESOF = bits_t(1, ESOF_POS), SOF = bits_t(1, SOF_POS), RESET = bits_t(1, RESET_POS), SUSP = bits_t(1, SUSP_POS), WKUP = bits_t(1, WKUP_POS), ERR = bits_t(1, ERR_POS), PMAOVR = bits_t(1, PMAOVR_POS), CTR = bits_t(1, CTR_POS); static const uint32_t EP_ID_MASK = 0xF; using mskd_t = Mskd ; using shft_t = Shft ; REGBITS_MSKD_RANGE("istr::EpId", EP_ID, ep_id, EP_ID_MASK, EP_ID_POS, EP_ID_MASK); }; // struct Istr using istr_t = Reg ; istr_t istr;
The "using" statements are converting the template names into something
shorter and handier (pos_t instead of Pos
The method "any()" tests for any bits being set in a word, in this case in
the word "stm32f103xb::usb->istr". This is defined in stm32f103xb.h
with code like:
What about this code in the ctr() method?
I add a printf and discover a shift value of 15.
This shift would give us the CTR bit.
It turns out this is entirely bogus, unexplainable, and to be ignored.
The answer lies in overloading of the ">>" operator in regbits.h -- the operator actually does this:
So I feel angry. I have been lied to.
I'll forgive most sins, but I won't forgive being lied to and tricked like this.
Any normal human being with knowledge of the C language
seeing the shift operator alongside a constant (or what looks like a constant) containing
"SHFT" in the name would hardly expect masking and no shifting at all.
The decision to overload the ">>" operator was a very bad one. Why not just introduced
a method called "extract" that would actually help a person to understand the code.
This is a case where advanced language features tempt a person to be far too cute.
This entirely ends any interest I might have had in regbits and even papoon.
This kind of unreadable coding and developing of dialects with ugly surprises is
the worst possible thing imaginable. Badly named variables are one thing, but deliberate
deception, trickery, and twisting of the language is inexcusable.
static const uint32_t PERIPH_BASE = 0x40000000U;
static const uint32_t APB1PERIPH_BASE = PERIPH_BASE;
static const uint32_t USB_BASE = APB1PERIPH_BASE + 0x00005C00U;
STM32F103XB_PERIPH( Usb, usb, USB_BASE );
The final macro in the above expands to:
static volatile Usb* const usb = reinterpret_cast
So, "usb" is a handy pointer to a "struct Usb", which happens to contain
"Istr", our interrupt status register.
Extracting the endpoint number
Usb::istr_t istr = usb->istr;
uint8_t eprn_ndx = istr >> Usb::Istr::EP_ID_SHFT;
What can I say -- this just looks totally wrong. The endpoint ID is in the last 4 bits
of "istr", so it doesn't need to be shifted, it needs to get masked out.
On top of that, I don't see that EP_ID_SHFT gets defined anywhere.
Somehow the code compiles (and seems to work).
Adding more printf, I get:
istr = 00008012
eprn_ndx = 2
EP_ID_SHFT = 15
Getting 2 out of the lower 4 bits of 0x8012 is certainly correct, but how we get it by shifting
15 bits is far far beyond my understanding.
return (_word & shft._mask) >> shft._pos._pos;
Holy cow. This sort of thing hardly makes for readable code (unless you are an expert in the
regbits dialect). Knowing there is magic here and in the REGBITS_MSKD_RANGE sort of explains
this. The mystery is how EP_ID_SHFT belched out the irrelevant and misleading value "15".
Endpoint registers
After all of that, I am in a bad mood and ready to pour out merciless criticism of
the regbits scheme used in papoon. Here is an example of testing a bit in an endpoint
register:
if (usb->EPRN<0>().any(Usb::Epr::CTR_RX)) {
What is odd (to me anyway) is that he uses the C++ template mechanism rather that a simple
array to refer to endpoint registers. An odd choice, but certainly something we can learn
how to read. And we are using his "any()" method to test whether a single bit is set.
I can certainly read this now, but I would simply use an array and write code like:
if ( usb->epreg[0] & EP_CTR_RX ) {
I am skeptical that the C++ source would produce equally good assembly code but I could be wrong.
(Actually I did peek and the code doesn't look all that bad).
In any case, good generated code doesn't excuse using an unreadable dialect.
Feedback? Questions?
Drop me a line!