Atomic bit operations on GPIO output (PODR) register
Background
Pins can be configured as general I/O port pins, peripheral pins, interrupt pins, etc. If a pin is configured as a general I/O port pin and output direction, the PODR register is used to output high or low level for the pin. As shown in the figure below, each bit in the PODR register represents the value of each pin, so care should be taken to ensure that they do not affect each other when writing to the register.
Users often mistakenly believe that one line of source code generates one assembly instruction, but this is not often true. So while reading and writing the PODR register, if interrupt handler reads and writes the same PODR register, the values read from the previous PODR register may be reflected incorrectly. This will result in incorrect GPIO output.
This article explains when and how atomic operations become available so that PODR register can be changed safely and exclusively. Atomic operation refers to a type of CPU instruction that runs without interruption.
Notes on PODR register access and its assembly output
PODR register writes are often coded like below. Depending on whether constants or variables are used as input values, the final assembly code generated by the compiler may use bit operation (BSET, BCLR) or/and with commonly used "read-modify-write" operation. As long as users don't modify the PODR register of the same I/O port in multiple contexts, users generally have nothing to worry about. If that’s not the case, users should ensure exclusive access to the same register access. This is not limited to PODR register, but is a general practice to follow when accessing shared resources (registers, variables, etc.), and tools such as mutexe/semaphore are sometimes used for this purpose. More details on why “read-modify-write” race condition is a problem can be found on Google.
Example) Three different ways to set P05 output to high.
1) PORT0.PODR.BIT.B5 = 1; //bitfield operation with constant
2) PORT0.PODR.BIT.B5 = user_podr0_b5; //bitfield operation with variable
3) PORT0.PODR.BYTE = (PORT0.PODR.BYTE | (1<<5)); //bit write operation with constant
Let's take a look at how the assembly code is generated in each case and where you might find problems.
1) PORT0.PODR.BIT.B5 = 1; //bitfield operation with constant
There are no potential problems since bit operations (BSET, BCLR) are performed directly on the PODR address.
2) PORT0.PODR.BIT.B5 = user_podr0_b5; //bitfield operation with variable
A potential problem can occur if the same PODR register is modified by another context, such as an interrupt handler, during a "read-modify-write" operation.
3) PORT0.PODR.BYTE = (PORT0.PODR.BYTE | (1<<5)); //bit write operation with constant
Even if BSET is used, the operand used is the R15 register, not the PODR address. Thus, the same potential problem can occur if the same PODR register is modified by another context, such as an interrupt handler, during a "read-modify-write" operation.
Example of atomic bit operations on PODR register
If GPIO output operations are needed on the same I/O port in multiple contexts, the following instructions and constants should be used, as in the following examples.
Example: Set P05 output to high/low.
PORT0.PODR.BIT.B5 = 1;
PORT0.PODR.BIT.B5 = 0;
or
__bset((unsigned char*)&PORT0.PODR.BYTE, 5);
__bclr((unsigned char*)&PORT0.PODR.BYTE, 5);
References
For more information about the RX family's instruction sets, such as BSET and BCLR, see the documentation linked below.
RX Family RXv1 Instruction Set Architecture
RX Family RXv2 Instruction Set Architecture
RX Family RXv3 Instruction Set Architecture