2022-10-07 11:14 AM
Hi
I am working on a drivers for the various peripherals on the STM32. Let me take the GPIO as an example. GPIO BSRR is a write only register that I want to read and I need to read back always 0x00000000. Why?
I made a bit-field struct on the whole GPIO block. That struct is accessed based on Register name and specific bit field.
The processor can not operate on individual bits so the compiler translates the Bit field instruction in to a register read followed by mask and shift operations ending with a Register write. It's the compiler that manipulates individual bits in a register in this way.
So the value of the write only register like a BSRR when it is read is important and needs to be 0x00000000 for a bit set operation.
Here is my question, is the read back of a write only register like the BSRR well-defined in the design of the STM32 chip or is that value undefined.
Question:
Can someone tell me if the read back of Write only registers is well-defined as 0x0000000 for the STM32 family of processors.
typedef struct {
struct {
uint32_t ch00 :4;
uint32_t ch01 :4;
uint32_t ch02 :4;
uint32_t ch03 :4;
uint32_t ch04 :4;
uint32_t ch05 :4;
uint32_t ch06 :4;
uint32_t ch07 :4;
} CRL; // 0x44444444, RW
struct {
uint32_t ch08 :4;
uint32_t ch09 :4;
uint32_t ch10 :4;
uint32_t ch11 :4;
uint32_t ch12 :4;
uint32_t ch13 :4;
uint32_t ch14 :4;
uint32_t ch15 :4;
} CRH; // 0x44444444, RW
struct {
uint32_t idr00 :1;
uint32_t idr01 :1;
uint32_t idr02 :1;
uint32_t idr03 :1;
uint32_t idr04 :1;
uint32_t idr05 :1;
uint32_t idr06 :1;
uint32_t idr07 :1;
uint32_t idr08 :1;
uint32_t idr09 :1;
uint32_t idr10 :1;
uint32_t idr11 :1;
uint32_t idr12 :1;
uint32_t idr13 :1;
uint32_t idr14 :1;
uint32_t idr15 :1;
uint32_t res :16;
} IDR; // 0x00000000, R
struct {
uint32_t odr00 :1;
uint32_t odr01 :1;
uint32_t odr02 :1;
uint32_t odr03 :1;
uint32_t odr04 :1;
uint32_t odr05 :1;
uint32_t odr06 :1;
uint32_t odr07 :1;
uint32_t odr08 :1;
uint32_t odr09 :1;
uint32_t odr10 :1;
uint32_t odr11 :1;
uint32_t odr12 :1;
uint32_t odr13 :1;
uint32_t odr14 :1;
uint32_t odr15 :1;
uint32_t res :16;
} ODR; // 0x00000000, RW
struct {
uint32_t bs00 :1;
uint32_t bs01 :1;
uint32_t bs02 :1;
uint32_t bs03 :1;
uint32_t bs04 :1;
uint32_t bs05 :1;
uint32_t bs06 :1;
uint32_t bs07 :1;
uint32_t bs08 :1;
uint32_t bs09 :1;
uint32_t bs10 :1;
uint32_t bs11 :1;
uint32_t bs12 :1;
uint32_t bs13 :1;
uint32_t bs14 :1;
uint32_t bs15 :1;
uint32_t br00 :1;
uint32_t br01 :1;
uint32_t br02 :1;
uint32_t br03 :1;
uint32_t br04 :1;
uint32_t br05 :1;
uint32_t br06 :1;
uint32_t br07 :1;
uint32_t br08 :1;
uint32_t br09 :1;
uint32_t br10 :1;
uint32_t br11 :1;
uint32_t br12 :1;
uint32_t br13 :1;
uint32_t br14 :1;
uint32_t br15 :1;
} BSRR; // 0x00000000, W
struct {
uint32_t br00 :1;
uint32_t br01 :1;
uint32_t br02 :1;
uint32_t br03 :1;
uint32_t br04 :1;
uint32_t br05 :1;
uint32_t br06 :1;
uint32_t br07 :1;
uint32_t br08 :1;
uint32_t br09 :1;
uint32_t br10 :1;
uint32_t br11 :1;
uint32_t br12 :1;
uint32_t br13 :1;
uint32_t br14 :1;
uint32_t br15 :1;
uint32_t res :16;
} BRR; // 0x00000000, W
struct {
uint32_t lck00 :1;
uint32_t lck01 :1;
uint32_t lck02 :1;
uint32_t lck03 :1;
uint32_t lck04 :1;
uint32_t lck05 :1;
uint32_t lck06 :1;
uint32_t lck07 :1;
uint32_t lck08 :1;
uint32_t lck09 :1;
uint32_t lck10 :1;
uint32_t lck11 :1;
uint32_t lck12 :1;
uint32_t lck13 :1;
uint32_t lck14 :1;
uint32_t lck15 :1;
uint32_t lck16 :1;
uint32_t res :15;
} LCKR; // 0x00000000, RW
} GPIO_RBLK;
GPIO_RBLK RegisterBlock;
GPIO_RBLK *GPIO=&RegisterBlock;
#define Reg32(R) (*(uint32_t*)&R)
#define Reg16(R) (*(uint16_t*)&R)
int main() {
printf("size:%u\n",sizeof(GPIO_RBLK));
GPIO->CRL.ch00=0x0u;
GPIO->CRL.ch01=0x1u;
GPIO->CRL.ch02=0x2u;
GPIO->CRL.ch03=0x3u;
Reg32(GPIO->CRL)=0xAABBCCDD;
printf("CRL:%08X\n",Reg32(GPIO->CRL));
HEX_MEM((uint8_t*)GPIO);
}
2022-10-07 02:52 PM
MPU or MCU ?
Don't expect Peripheral registers with logic behind them to act like regular memory cells.
Accessing the data registers for SPI and UART may touch completely unrelated logic.
BSRR maps to a latch/gate structure operating on the ODR, so you can modify in a single action rather than a RMW (Read-Modify-Write) one.
2022-10-07 09:23 PM
Yes I know about the RMW vs Atomic action. However I'm looking for the answer if the design of the chip is done in a way that a Read from a write only register gives 0x00000000. I did a small test with 10milj reads from this BSRR and yes it gave me back only 0x00000000 it seems that the design of the chip doesn't return and undefined values on this BSRR. I would however like to know if it was a design requirement.
2022-10-09 11:39 PM
Hi @WOGoos ,
I think in most case, a write only register will read back as 0 (i.e. by design, if there is no read path on APB bit, the value must be 0), however, due to variety of design/bus/IP providers, we cannot guarantee that over all STM32 IPs.
E.g. There could be bus errors returned on read, or read value not always at 0 because linked to some hidden internal state.
In general, bit structures on registers should be avoided as known to be not portable (behavior and padding depend on compiler).
Sometimes, when talking low level drivers, elegant coding may not be optimized. Brute force coding is usually more robust.
E.g. in your example the compiler will do RMW whereas it is useless on a bit set/reset write only register. Apart functionality behavior, this could impact performance as you will loose the advantage of having set/reset registers. A write could be posted on the processor and/or on busses (i.e. might be 0 wait state from processor view) whereas the additional read will create additional wait states (as it usually flushes all the pending writes and should wait for the read data to be completely done from peripheral to processor).
Regards.
2022-10-10 01:21 AM
Yes, all what Clive and PatrickF said above, and I also find it silly to RMW the BSRR whatever noble reasons are behind it. You did not care to tell us, but given the CRx registers you are using 'F1xx which is probably the only family which does not guarantee BSRR being read as 0 in the RM. Btw. as the 'F1 GPIO is different from all other STM32 families' GPIO, general portability in this particular case is not an issue.
And, speaking of noble reasons:
> The processor can not operate on individual bits
That's not true generally. There are processors which can operate on individual bits, e.g. 8051, or Cortex-M3/4 through bit-banding (that the latter is RMW in hardware is a different thing again). So compiler may very well use whatever method they deem appropriate to set individual bits of a bitfield, including using those hardware facilities.
> so the compiler translates the Bit field instruction in to a register read followed by mask and shift
> operations ending with a Register write. It's the compiler that manipulates individual bits in a register in this way.
It's a particular compiler which does this in this way, it's not a requirement on the compiler. Read, it's not a portable or in any way guaranteed behaviour.
The true issue here is, that this is a volatile access (not in the code you presented above, but that's in fact an error on your side). And, according to C99 6.7.3, it's implementation-defined what constitutes an access to volatile-qualified object. In other words, the compiler may - and if desired, should - be made in that what that for particular hardware registers it performs a particular operation.
This is not as shockingly unseen as it may sound, for example the avr-gcc compiler "knows" that 16-bit timer registers (i.e. dedicated register-pairs) have to be accessed in a certain order (again for hardware-implemented atomicity reasons).
JW
2022-10-10 01:58 AM
#include <stdint.h>
typedef union {
volatile struct {
uint32_t bs00 :1;
uint32_t bs01 :1;
uint32_t bs02 :1;
uint32_t bs03 :1;
uint32_t bs04 :1;
uint32_t bs05 :1;
uint32_t bs06 :1;
uint32_t bs07 :1;
uint32_t bs08 :1;
uint32_t bs09 :1;
uint32_t bs10 :1;
uint32_t bs11 :1;
uint32_t bs12 :1;
uint32_t bs13 :1;
uint32_t bs14 :1;
uint32_t bs15 :1;
uint32_t br00 :1;
uint32_t br01 :1;
uint32_t br02 :1;
uint32_t br03 :1;
uint32_t br04 :1;
uint32_t br05 :1;
uint32_t br06 :1;
uint32_t br07 :1;
uint32_t br08 :1;
uint32_t br09 :1;
uint32_t br10 :1;
uint32_t br11 :1;
uint32_t br12 :1;
uint32_t br13 :1;
uint32_t br14 :1;
uint32_t br15 :1;
};
uint32_t all;
} GPIO_BSRR;
typedef struct {
struct {
uint32_t ch00 :4;
uint32_t ch01 :4;
[...]
uint32_t odr15 :1;
uint32_t res :16;
} ODR; // 0x00000000, RW
GPIO_BSRR BSRR;
struct {
uint32_t br00 :1;
uint32_t br01 :1;
[...]
uint32_t lck16 :1;
uint32_t res :15;
} LCKR; // 0x00000000, RW
} GPIO_RBLK;
volatile GPIO_RBLK * const GPIOA = (volatile GPIO_RBLK *)0x40010800;
int main(void) {
GPIOA->BSRR.all = (GPIO_BSRR){.bs03 = 1}.all;
}
compiling by arm-none-eabi-gcc (GNU Tools for ARM Embedded Processors) 4.8.3 20131129 (release) [ARM/embedded-4_8-branch revision 205641] results in
08000204 <main>:
volatile GPIO_RBLK * const GPIOA = (volatile GPIO_RBLK *)0x40000000;
int main(void) {
GPIOA->BSRR.all = (GPIO_BSRR){.bs03 = 1}.all;
8000204: f04f 4380 mov.w r3, #1073741824 ; 0x40000000
8000208: 2208 movs r2, #8
800020a: 611a str r2, [r3, #16]
}
800020c: 2000 movs r0, #0
800020e: 4770 bx lr
08000210 <GPIOA>:
8000210: 0000 4000 ...@
JW
2022-10-10 09:17 AM
I like your message, but it seems that you know more C tricks than I do. I understand that you try to show the Assembly translation of your code, however I have problems to read it.
2022-10-10 09:22 AM
Hi Patrick thanks for helping me. I agree with the non portability problem. I did some further digging and it turned out that at page 46 of Chapter2 of the RM0008 manual it states write-only (w) Software can only write to this bit. Reading the bit returns the reset value.
I did a test of 10mil reads and it did return all zeros. It seems to be by design.
The RMW is a direct need when you work with bit fields in C. You probably know that already.
2022-10-10 09:29 AM
Thanks waclawek for helping me. I'm quite hard-headed, you probably have recognized that characteristic. I am aware of the pro's and cons of Bit field operations, however ones you can prove that the compiler executes the operations well in a certain architecture it works so nice :)
2022-10-10 09:33 AM
I found the answer it was right in front of me however I didn't see it:
I did some further digging, and it turned out that at page 46 of Chapter2 of the RM0008 manual it states write-only (w) Software can only write to this bit. Reading the bit returns the reset value. That value is 0x00000000
I did a test of 10milj reads and it did return all zeros. the Read of a Write only register returns 0x00 It seems to be by design.
The RMW is a direct need when you work with bit fields in C. You probably know that already. Thanks guys for helping me