Dumbo
Dumbo

Reputation: 14112

How to read/write into specific bits of a unsigned char

I want to read and write from/to an unsigned char according to the table below: enter image description here

for example I have following variables:

unsigned char hsi_div = 0x01; /* HSI/2 */
unsigned char cpu_div = 0x05; /* Fmaster/32 */

I want to write hsi_div to bits 4,3 and cpu_div to bits 2,1,0 (imagine the whole char is named CLK_DIVR):

CLK_DIVR |= hsi_div << 4; //not correct!
CLK_DIVR |= cpu_div << 2; //not correct!

And lets say I want to read the register back to make sure I did it correct:

if( ((CLK_DIVR << 4) - 1) & hsi_div) ) { /* SET OK */ }
if( ((CLK_DIVR << 2) - 1) & cpu_div) ) { /* SET OK */ }

Is there something wrong with my bitwise operations!? I do not get correct behaviour.

Upvotes: 1

Views: 496

Answers (3)

too honest for this site
too honest for this site

Reputation: 12263

I assume CLK_DIVR is a hardware peripheral register which should be qualified volatile. Such registers should be set up with as few writes as possible. You change all write-able bits, so just

 CLK_DIVR = (uint8_t)((hsi_div << 3) | (cpu_div << 0));

Note using fixed width type. That makes mentioniong it is an 8 bit register unnecessary. According to the excerpt, the upper bits are read-only, so they are not changed when writing. The cast keeps the compiler from issuing a truncation warning which is one of the recommended warnings to always enable (included in -Wconversion for gcc).

The shift count is actually the bit the field starts (the LSbit). A shift count of 0 means "no shifting", so the shift-operator is not required. I still use it to clarify I meant the field starts at bit 0. Just let the compiler optimize, concentrate on writing maintainable code.


Note: Your code bit-or's whatever already is in the register. Bit-or can only set bits, but not clear them. Addiionally the shift counts were wrong.


Not sure, but if the excerpt is for an ARM Cortex-M CPU (STM32Fxxxx?), reducing external bus-cycles becomes more relevant, as the ARM can take quite some cycles for an access.

Upvotes: 2

ABu
ABu

Reputation: 12259

Just

CLK_DIVR |= hsi_div << 3;
CLK_DIVR |= cpu_div << 0;

Since hsi_div is a 2-digit binary, you have to move it three positions to skip the CPUDIV field. And the cpu_div is already at the end of the field.

Upvotes: 0

Thomas Matthews
Thomas Matthews

Reputation: 57688

For the HSIDIV bit fields you want:

hw_register = (hw_register & 0x18) | (hsi_value & 0x03) << 0x03;

This will mask the value to 2 bits wide then shift to bit position 3 and 4.

The CPUDIV fields are:

hw_register = (hw_register & 0x7) | (cpu_value & 7);

Reading the register:

hsi_value = (hw_register & 0x18) >> 3;
cpu_value = hw_register & 0x07;

Upvotes: 1

Related Questions