1

I'm coming from this problem where I had the issue that one GPIO write would influence the level of another pin. (GPIO19 would influence GPIO13).

When changing the set/clear operation from |= to = the behaviour is suddenly correct:

// clear GPIO19
*(GPCLEAR0) |= (1u << 19); // does not work
*(GPCLEAR0) = (1u << 19); // works

I'm aware that there are registers that should only be written to (like clearing interrupt flag).

However, the datasheet of BCM2835 does not mention to not read GPCLEAR0.

In fact, the datasheet claims that this register is read+write.

So why is it working with = and not with the more "logical" |=?

Marco
  • 957
  • 1
  • 9
  • 22
  • 1
    Why would you do a read/modify/write on a register that is provided to make bit clearing atomic? I gather there’s a corresponding set register which operates similarly. If you were accessing the port register, then the r/m/w would be the choice but consider if you have multiple tasks accessing the same port register - you would encounter atomicity issues. With the set and clear registers, only one indivisible write is involved. – Kartman Oct 30 '22 at 18:50
  • @Kartman Ah.. gotcha. I only have worked with port (AVR) registers so far, so that's why I used r/m/w. Thanks! – Marco Oct 30 '22 at 19:06
  • It does not mention you should not read from it. However, when you read it, what do you expect to read back from a write-only register? There is no mention what you get back if you read it. If zeroes, your code should work. if ones, then your code should fail. – Justme Oct 30 '22 at 19:26
  • 1
    @marco-a AVR has I/o bit operations which the compiler translates the apparent r/m/w to one of these. In comparison many PIC and 8051 compilers used a non standard bit syntax to implicitly do the same thing. With the ARMs, the i/o is not tightly coupled to the processor like the PIC, AVR,8051 etc as the gpio is a peripheral just like the uarts etc, so many manufacturers add the set and clear registers to give the same flexibility in setting/clearing bits quickly and atomically. Doing actual r/m/w operations are slow and need special attention in multitasking environments. – Kartman Oct 30 '22 at 23:12

1 Answers1

1

This behavior is rather typical for set-and-clear register pairs. When you read it, you get the actual state of the register back (or an undefined value if the register doesn't support reading). When you write to the clear register, any 1 bits in the written value will cause the corresponding register bit to be cleared.

Let's assume that GPIO13 is currently 1. You want to clear GPIO19.

In the |= case, your code first reads GPCLEAR0. This returns the actual register state, so bit 13 is 1.

Then you OR bit 19 onto that. Now bit 13 and bit 19 are both 1.

Then you finally write that value back into GPCLEAR0. Since bit 13 and bit 19 are both set, this will clear these two bits, clearing GPIO13 accidentally (in addition to GPIO19). Oops!

The correct way to clear a bit is to write only that bit to the GPCLEAR register. All other bits have to be 0. The same goes for GPSET. That's why you have to use =, not |=.

Jonathan S.
  • 18,288
  • 34
  • 56
  • Thanks for the quick and elaborate answer! – Marco Oct 30 '22 at 19:04
  • How do you know what value is read back when reading a write-only register? Why would it read back current state of IO pin? – Justme Oct 30 '22 at 19:28
  • @Justme You're right about that - I just used the behavior that set/clear register pairs generally have on ARM MCUs. It doesn't invalidate the reasoning in the answer, though. – Jonathan S. Oct 30 '22 at 19:34
  • I guess it is possible to read back what has previously been written to it, but the information is contradictory. Table 6-1 says write only, tables 6-8 and 6-9 say read/write. But yes, the registers should be written with the bit you want to change, because it specifically exists for having atomic access to GPIO bits so there is no need to read-modify-write any registers. – Justme Oct 30 '22 at 19:51
  • The thing is, most ARM MCUs do return the underlying register's contents on read, not just what's been previously written to the atomic set/clear registers. The fact that there doesn't seem to be an extra read-only register to get the GPIO state also makes me think that this broadcom chip works the same way, even if the datasheet is a little weird and contradictory. – Jonathan S. Oct 30 '22 at 19:53
  • @JonathanS. I still don't get why atomic setting register would return anything useful back related to pin state when read. And there is a read-only register to read back actual pin state, called GPLEV. – Justme Oct 30 '22 at 19:59
  • @Justme If you have two write-only registers and one read-only one, you could also just combine them: Writing the register has one function, reading has a different function. Most ARM microcontrollers do this for their atomic set/clear registers, saving a register. If you write to them, you get the atomic set/clear function, and if you read one of them, you get the actual state back. – Jonathan S. Oct 30 '22 at 20:06
  • Intresting and may make sense. But none of the ARM MCUs I have used worked like that. Can you give an example of one of these ARM MCUs that do? – Justme Oct 30 '22 at 20:14
  • The PORT registers of the ATSAMD10D14A work like this, for example. – Jonathan S. Oct 30 '22 at 20:22
  • @JonathanS. I checked the ATSAM10D14A register map and it does not seem to function like you claim it does. – Justme Oct 31 '22 at 07:22
  • I recreated my setup and printed the values for GPCLEAR0 and GPSET0 for both using = and |=. In both cases, I get 0x6770696F. I can confirm, that these values do not change over the course of execution (I always get 0x6770696F). – Marco Oct 31 '22 at 23:49
  • Examining 0x6770696F we can see that the bit for GPIO13 is set: (0x6770696F & (1 << 13)) > 0 is true. This means when I use |= I'm actually setting GPIO13 (and some other pins) implicitly because the register always reads 0x6770696F. I have not expected this behaviour. I expected that the register will always read zero, or at least, the last value I wrote to it. – Marco Oct 31 '22 at 23:52
  • Another test confirms this: 0x6770696F also includes setting/clearing GPIO pin 22. I set GPIO22 as output, run my code, and I can see the exact same behaviour I had with pin GPIO13. – Marco Oct 31 '22 at 23:55
  • The only thing I can conclude from this is to not read GPCLEARx/GPSETx but the datasheet was not clear on this. It explicitly states R/W. I don't expect a random (or garbage) value from a register that I'm allowed to read. I mean, what's the point in reading garbage values? – Marco Oct 31 '22 at 23:57
  • @marco-a The datasheet appears to be inconsistent and wrong in places. In the detailed register description, it mentions that these two registers are write-only. About the ATSAMD10D14A: The detailed register description of the PORT.OUTCLR / PORT.OUTSET registers mentions that they can be read too and will return the value of the PORT.OUT register. – Jonathan S. Nov 01 '22 at 00:06