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 to 1
SPI1->CR1 &= ~(1<<7); // to set bit 7 to 0
(For the 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.
Thanks for your help!
CodePudding user response:
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<<...
because1
has typeint
which is signed and we should never do bitwise arithmetic on signed types. Write1u<<...
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.
CodePudding user response:
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...