AnsweredAssumed Answered

Macro for accessing part of registers

Question asked by rbtx99 on Jun 16, 2015
Latest reply on Jun 16, 2015 by rbtx99
This is not a question, just something I developed and I wanted to share with the community.

I am fairly new to ARM and STM32 and perhaps there is some things I haven't come across yet. I realise that many people will be using STM32Cube libraries but I have always been more comfortable with the idea of accessing the hardware registers myself. This brought me to this predicament:

I noticed that in core_cm4.h both the register position and its mask are define which is handy. To read a part of a register we can do:

(SysTick->CALIB & SysTick_CALIB_SKEW_Msk) >> SysTick_CALIB_SKEW_Pos

However, for the peripheral access (in my case stm32f446xx.h) only the mask is defined with no indication of the position of the bits. So say you want to set PPLN you have to find in the datasheet where the bits are so you can shift by the appropriate number: So here is my little contribution which I hope you will find useful. The macro CTZ32 (Count Trailing Zeros) evaluates at compile time so it adds no workload to the processor. It takes as input the mask and returns the position of the bits in the register. So if for example you want to access PLLN all you have to do is:

uint16_t plln= (RCC->PLLCFGR & RCC_PLLCFGR_PLLN) >> CTZ32(RCC_PLLCFGR_PLLN);

Even better, we can define and use higher level read and write macros:

SETREG(RCC->PLLCFGR, RCC_PLLCFGR_PLLN, 150);
uint16_t plln= GETREG(RCC->PLLCFGR, RCC_PLLCFGR_PLLN);





Here is the code for the macros:

//=============================================================================================
// Macro to Count Trailing Zeros (CTZ) of a 32bit binary
// Developed by Angelos Gonias. Based on a known CTZ algorithm.
// Do not use this macro with variables as it produces very large code each time
// it is used. For variables use the ctz32() function instead. With this macro if v
// is a constant the compiler should resolve all this to a single integer during
// compilation thus adding zero workload to the processor at runtime. Useful for
// calculating how many shifts are needed to access a part of a register using 
// its corresponding CMSIS bit mask.
//=============================================================================================
#define CTZ32(v) ({   32                                               \
                   - !!((v)&(((v)^0xFFFFFFFF)+1)) * 1                  \
                   - !!(((v)&(((v)^0xFFFFFFFF)+1)) & 0x0000FFFF) * 16  \
                   - !!(((v)&(((v)^0xFFFFFFFF)+1)) & 0x00FF00FF) * 8   \
                   - !!(((v)&(((v)^0xFFFFFFFF)+1)) & 0x0F0F0F0F) * 4   \
                   - !!(((v)&(((v)^0xFFFFFFFF)+1)) & 0x33333333) * 2   \
                   - !!(((v)&(((v)^0xFFFFFFFF)+1)) & 0x55555555) * 1;  \
})
 
//=============================================================================================
// Macro to set part of register
//=============================================================================================
#define SETREG(reg, clearmask, value) ({     \
    uint32_t v= (reg);                       \
    v&= ~clearmask;                          \
    v|= (value) << CTZ32(clearmask);         \
    (reg)= v;                                \
})
 
//=============================================================================================
// Macro to read part of register
//=============================================================================================
#define GETREG(reg, bitmask) ( ((reg)&(bitmask)) >> CTZ32(bitmask) )

If you want to count trailing zeros of a variable it is best to use the function below instead. The code below will also help you understand the algorithm behind the macro.

//=============================================================================================
// Function to Count Trailing Zeros (CTZ) of a 32bit binary
// http://graphics.stanford.edu/~seander/bithacks.html#ZerosOnRightParallel
// trailing-on-the-right-in-parallel-an-explanat
//
// Notes:
// ((v^0xFFFFFFFF)+1) is equivalent to (v)*(-v) and it clears all but
// the lowest set bit. For example 0b1110 becomes 0b0010
//=============================================================================================
uint8_t ctz32(uint32_t v)
{
  v &= (v ^ 0xFFFFFFFF) + 1;    // Alternative: v &= -(int32_t)v;
  uint8_t c = 32;
  if (v) c--;
  if (v & 0x0000FFFF) c -= 16;
  if (v & 0x00FF00FF) c -= 8;
  if (v & 0x0F0F0F0F) c -= 4;
  if (v & 0x33333333) c -= 2;
  if (v & 0x55555555) c -= 1;
  return c;
}




Outcomes