2

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.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Kim
  • 23
  • 5
  • 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);` – Gerhardh Aug 31 '22 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 :) – Kim Aug 31 '22 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 – Gerhardh Aug 31 '22 at 11:09
  • You should take some tutorial on bitwise operators. Manimupating single or multiple bits should be handled there in depth. – Gerhardh Aug 31 '22 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? – Fe2O3 Aug 31 '22 at 11:16
  • @Gerhardh, thanks so much on your inputs, I am only learning the topic now so it's a bit tricky, appreciate your directions! And will look into the tutorials soon – Kim Aug 31 '22 at 11:26
  • @Fe2O3, thank you for your comments :) I think I don't understand the registers and the shifting operations hence confusions – Kim Aug 31 '22 at 11:27
  • Regarding *"How can I set multiple bits to 0 in C"*: Right there it should have been closed as a duplicate, not answered. Why is this answered in 2022? There must be plenty of duplicates. That it is setting hardware registers in a microcontroller shouldn't matter (presuming there aren't any side effects, like setting a bit to 1 would reset some other hardware registers or internal hardware state). – Peter Mortensen Sep 13 '22 at 09:09
  • More duplicates: *[How can I clear multiple bits at once in C?](https://stackoverflow.com/questions/63158929/)*, *[How can I set multiple bits in one line in C?](https://stackoverflow.com/questions/21786843/)* – Peter Mortensen Sep 13 '22 at 09:26
  • Related: *[What are bitwise shift (bit-shift) operators and how do they work?](https://stackoverflow.com/questions/141525/)* and *[How do I set, clear, and toggle a single bit?](https://stackoverflow.com/questions/47981/)*. – Peter Mortensen Sep 13 '22 at 09:33
  • More duplicates: *[How can I clear a certain number of bits in C?](https://stackoverflow.com/q/61948297)* (2020), *[Settings Multiple bits at Once in a Bitset](https://stackoverflow.com/q/54408089)* (2019), *[How to turn off some bits while ignoring others using only bitwise operators](https://stackoverflow.com/q/8965521)* (2012), *[How to replace bits in a bitfield without affecting other bits using C](https://stackoverflow.com/q/5925755)* (2011), and *[How do you set only certain bits of a byte in C without affecting the rest?](https://stackoverflow.com/q/4439078)* (2010) – Peter Mortensen Sep 13 '22 at 09:54

2 Answers2

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.

Lundin
  • 195,001
  • 40
  • 254
  • 396
  • 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 Aug 31 '22 at 11:54
  • 1
    @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. – Lundin Aug 31 '22 at 11:58
  • 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 Sep 02 '22 at 07:09
  • @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. – Lundin Sep 02 '22 at 07:26
  • As **generic** answer that paragraph is misleading. It all depends on hardware. – 0andriy Sep 02 '22 at 22:47
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...

Fe2O3
  • 6,077
  • 2
  • 4
  • 20
  • 1
    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 Aug 31 '22 at 11:00
  • @Kim Thanks for your response... Both answers are equivalent. When it comes to dealing with "bit clusters", my tired brain likes to see descriptive names that have been checked once and used often. Again, thanks for response `:-)` – Fe2O3 Aug 31 '22 at 11:04
  • @Kim Why hex? I'm well acquainted with it, and can 'fracture' a value into nibbles easier than working out what "<<21" actually means... Some say tomato and some say tomato `:-)` (Gerhardt's compact solution ALSO used the hex value '7'.... Tomato, tomato, tomato... `:-)` – Fe2O3 Aug 31 '22 at 11:06
  • 2
    @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! – pm101 Aug 31 '22 at 11:16
  • 1
    Fe2O3 and @pm101, thank you guys for help, it provided me with very useful insights, will dig down into it, cheers! – Kim Aug 31 '22 at 11:30
  • @pmg one might argue that due to the context of a 3bit field within a register, it would be even easier to use `(0x7<<3)` because with `0x38` you first need to extract the relevant 3 bits to get to their values. – Gerhardh Aug 31 '22 at 11:30
  • @Gerhardh You're right. Back when I did this stuff, though, the names (#define) were CAREFULLY composed with reference to doco, then became "Touch at the risk of your LIFE!" The code was more like COBOL, but there was less chance of "off by one" on a "bad day"... Whatever works for the person... `:-)` – Fe2O3 Aug 31 '22 at 11:35
  • 1
    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. – Lundin Aug 31 '22 at 11:42
  • @Lundin Readable, certainly... Especially if you have the manufacturer's doco to (again) check that 3 adjacent bits (not 4, not 2) in those positions... Yes, constants are evaluated at compile time... `#define BIT_03 (1u<<3)` etc., then `BIT_05 | BIT_04 | BIT_03`... It's all much of a muchness. Whatever floats one's boat... All good `:-)` – Fe2O3 Aug 31 '22 at 11:49
  • 4
    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. – Lundin Aug 31 '22 at 11:52