Skip to main content
WOGoos
Associate III
October 7, 2022
Question

That is the readback value of a write only register.

  • October 7, 2022
  • 8 replies
  • 5544 views

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);
}

This topic has been closed for replies.

8 replies

Tesla DeLorean
Guru
October 7, 2022

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.

Tips, Buy me a coffee, or three.. PayPal VenmoUp vote any posts that you find helpful, it shows what's working..
WOGoos
WOGoosAuthor
Associate III
October 8, 2022

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.

PatrickF
Technical Moderator
October 10, 2022

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.

In order to give better visibility on the answered topics, please click on 'Accept as Solution' on the reply which solved your issue or answered your question.NEW ! Sidekick STM32 AI agent, see here
WOGoos
WOGoosAuthor
Associate III
October 10, 2022

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.

waclawek.jan
Super User
October 10, 2022

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

WOGoos
WOGoosAuthor
Associate III
October 10, 2022

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 :)

waclawek.jan
Super User
October 10, 2022
#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

WOGoos
WOGoosAuthor
Associate III
October 10, 2022

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.

Tesla DeLorean
Guru
October 10, 2022

For being this deep into the weeds knowing some machine code and assembler, and machine operation, would seem to be a prerequisite.

The bit-banding mode creates a faux bit level model, but at it's heart is a RMW action that the MCU, and surrounding logic, implement. There are *known* hazards with this, and it's use with the TIM->SR, among others.

Tips, Buy me a coffee, or three.. PayPal VenmoUp vote any posts that you find helpful, it shows what's working..
WOGoos
WOGoosAuthor
Associate III
October 10, 2022

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

waclawek.jan
Super User
October 10, 2022

> 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've missed that information, too. It then indicates, that all write-only registers should reliably read 0, if that's given as their reset value.

@PatrickF​ , can you please comment on this?

JW

PatrickF
Technical Moderator
October 11, 2022

Hi,

I guess what is written in RefMan introduction is true but slightly overoptimistic, but for most registers, it will be true. There is certainly very few exceptions.

Anyway, as process for writing low level drivers involve some validation checks, this will certainly catch any specific behavior.

There is no 'random' or 'floating' read data (high-z buses inside a chip was present in 20th century designs), by default the logic will drive 0 on unused bits (including write only) unless noted in the spec or if there is something hidden in the spec (i.e. test bits not described).

Regards.

In order to give better visibility on the answered topics, please click on 'Accept as Solution' on the reply which solved your issue or answered your question.NEW ! Sidekick STM32 AI agent, see here
waclawek.jan
Super User
October 12, 2022

Hi Patrick,

Thanks for the explanation.

JW

@PatrickF​