0

How can I set multiple bits to 0 in C when working with CR registers with SPI?

I know that for setting individual bits I do:

SPI1->CR1 |= (1<<2); // To set bit 2 SPI1->CR1 &= ~(1<<7); // To reset bit 7 

Context: I am setting a prescaler for the baud rate. In my case the prescaler is 2 and the binary value assigned to it according to the datasheet is 000. How can I assign bits 3-5 to 000?

Will it be

SPI1->CR1 &= ~(1<<3); 

?

In the video I am following the guys does: SPI1->CR1 |=(3<<3), but he uses 011. He has a different microcontroller, thus the difference.

11
  • 1
    Welcome to SO. If you want to deal with 3 bits at a time, you must combine the bits in your mask: SPI1->CR1 &= ~(1<<5 | 1<<4 | 1<<3); or shorter SPI1->CR1 &= ~(7<<3); Commented Aug 31, 2022 at 10:47
  • @Gerhardh, okay, thank you! I would like to gain some clarification on the shorter version, how does the bit assignement work in this case? I see we have a binary 7 = 111 and we are performing the negation, to obtain 000. If I had to encode 4 using 4 bits (say bits 3-6), I would do SPI1->CR1 |= (4<<3); but another alternative could be: SPI1->CR1 &= ~(11<<3); as I am negating 1011 to obtain 0100; just trying to understand the concept :) Commented Aug 31, 2022 at 10:56
  • 1
    If you want to set multiple bits to different values, you must combine AND and OR operation. uint16_t val = SPI1->CR1; val &= ~(15<3); val |= (4<<3); SPI1->CR1 = val; With your attempt (&= ~(11<<3)), you do not touch all bits and cannot be sure what the state of the missing bit is afterwards Commented Aug 31, 2022 at 11:09
  • You should take some tutorial on bitwise operators. Manimupating single or multiple bits should be handled there in depth. Commented Aug 31, 2022 at 11:10
  • @kim "we have a binary 7 = 111 and we are performing the negation," Not really... The three bits have been left shifted (filling 0's on the right) so the "one's complement" is performed on 0b00....0111000... Left shift 3 bits, remember? Commented Aug 31, 2022 at 11:16

2 Answers 2

2

Some hardware peripheral register basics for embedded systems beginners:

As a rule of thumb, only read from peripheral registers at one single place and only write to them at one single place. Otherwise you risk subtle real-time issues. Also, doing multiple reads/writes in a row makes the code needlessly slow for absolutely nothing gained, as seen in various Arduino "tutorials" etc written by quacks.

In this case (I'm assuming 32 bit registers):

uint32_t cr1 = SPI1->CR1; // read ONCE // Now do any bit manipulations you fancy here, without concerns for performance: cr1 |= 1<<2; cr1 &= ~(1<<7); SPI1->CR1 = cr1; // write ONCE 

Example:

(In this case I just used dummy volatile variables that ended up on the stack to simulate registers)

volatile uint32_t SPI1_CR1; uint32_t cr1 = SPI1_CR1; cr1 |= 1<<2; cr1 &= ~(1<<7); SPI1_CR1 = cr1; 

Disassembling this on gcc/ARM-none-eabi -O3 gives something like:

 ldr r3, [sp, #4] bic r3, r3, #128 orr r3, r3, #4 str r3, [sp, #4] 

My cr1 variable ended up in a register. Everything is done in 4 instructions. Had I however written directly to the volatile-qualified register, then I'd get extra overhead:

 volatile uint32_t SPI1_CR1; SPI1_CR1 |= 1<<2; SPI1_CR1 &= ~(1<<7); ldr r3, [sp, #4] orr r3, r3, #4 str r3, [sp, #4] ldr r3, [sp, #4] bic r3, r3, #128 str r3, [sp, #4] 

Now regarding naming, readability and ruggedness:

  • We shouldn't write magic numbers such as 1<<2 to a register. If you force the reader of your code to sit with their nose in the user manual watching the register descriptions at all times, then your code is bad.
  • We can do all bit manipulations to the same register on a single line, which might improve readability and performance slightly.
  • We should never write 1<<... because 1 has type int which is signed and we should never do bitwise arithmetic on signed types. Write 1u<<... instead.

For more details check out How to access a hardware register from firmware? Now as it happens it even used a generic SPI peripheral as example. After following all advise in that post, the proper code should look something like:

#define SPICR_SPIE (1u << 7) #define SPICR_CPOL (1u << 4) #define SPICR_CPHA (1u << 3) ... SPICR = SPICR_SPIE | SPICR_CPHA; 

Or in case you prefer the alternative style:

#define SPICR_SPIE(val) ((val) << 7) #define SPICR_CPOL(val) ((val) << 4) #define SPICR_CPHA(val) ((val) << 3) ... SPICR = SPICR_SPIE(1) | SPICR_CPOL(0) | SPICR_CPHA(1) ; 

In the latter form we aren't forced to use a single bit either, so it could be used for setting multiple bits like in a baudrate prescaler. However, it is then also custom to mask. Lets say there are 4 baudrate prescaler bits found from bit 3 to 6 in the register:

#define SPICR_BAUD(val) ((val & 0xF) << 3) 

4 bits = the mask 1111 = 0xF. Then shift afterwards, for readability. Something like (val << 3) & 0xE8u would be equivalent but needlessly hard to read.

Sign up to request clarification or add additional context in comments.

5 Comments

Rich post! Well described! Regarding closing section, my experience has been 'preset' values of 'val' with 16 #define tokens each naming the speed that they represent... Your thoughts, please :-)
@Fe2O3 Yes using constants corresponding to fixed baudrates would be ideal. Especially in other scenarios like UART or CAN where there is a set of industry standard baudrates that are the only ones one should use. SPI is more flexible there since it is (alas) so poorly standardized.
Your first paragraph (after the ambitious beggining phrase) is not (fully) correct. Write and Read data from the register according to the datasheet. Hardware registers are not limited to RO, WO and RW, but also RC, W1C, W0C, latches in the bits (makes you to go for very specific order of writings / readings) and might be more.
@0andriy Sure, and in the context of SPI you often clear flags by first reading the status register then reading the data register. But it's a better answer if it is kept generic and applicable to all (common) registers, instead of getting into various details of a certain hardware peripheral.
As generic answer that paragraph is misleading. It all depends on hardware.
1

You can define your own (multi-bit) bit patterns without needing to shift:

 #define BITS_543 0x38 // == 0b0...111000 

(May as well express the set bits in left-to-right order in the name)

To clear those bits you ask about:

 SPI1->CR1 &= ~BITS_543; 

The name you chose can even be more functional; eg "BAUD_RATE_TRIO"

Give that a try...

10 Comments

Gerhardh answer was what I was looking for, with your answer, I have a small follow-up question, why do you use the hex representation for those bits in the define statement?
@Kim We use hex because if you can remember the 16 hex values in binary (which isn't very difficult), 38(hex) is a lot easier to translate into 00111000 (0011 being the '3' nibble and 1000 being the '8' nibble) in your head, than 56(dec) where you'd need to do your divide by two get the remainder repeatedly to work out the binary in your head. and if you're a programmer you should just know hex!
Fe2O3 and @pm101, thank you guys for help, it provided me with very useful insights, will dig down into it, cheers!
In the real world the bits of some SPI control register will have individual names and so something like SPI1->CR1 &= ~BITS_543; is quite similar to using "magic numbers". And "without needing to shift" is not a concern since these shifts are integer constant expressions evaluated at compile-time. You should shift since (1u<<5) | (1u<<4) | (1u<<3) is some of the most readable code you could produce. This clearly sets bits 5, 4 and 3. Sure, 0x38 is considered reasonably readable too, but neither is readable in the context of initializing a SPI peripheral.
This really isn't subjective... after reading endless amounts of crappy embedded systems code (much of it handed to you by incompetent silicon vendors), you eventually realise that there are lots of bad ways and there are also industry best practices. I posted an answer demonstrating how to do it proper in the case of SPI.
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.