Port manipulation confusion

I've got the following piece of code I'm trying to understand:

inline void setAddress(int row, int col)
{
  PORTB = row & 0x3f;
  PORTC = (PORTC & 0xf0) | ((row >> 6) & 0x0f);

  PORTB = col & 0x3f;
  PORTC = (PORTC & 0xf0) | ((col >> 6) & 0x0f);

}

I understand the basic of port manipulation, so in the above:
PORTB = row & 0x3f;

Row is AND with 0x3F and the bits on PORTB are then set/cleared on the result of the AND

I can't get my head around the PORTC statement. :o

Row is shifted 6 bits to the right and then AND with 0x0F.
PORTC is AND with 0xF0 and result is OR with above?
Or is PORTC being read, AND with 0xF0, and then OR with ((col >> 6) & 0x0f) ?

Try this by an example and work with a binary representation of the values, breaking it down:

PORTC = (PORTC & 0xf0) | ((row >> 6) & 0x0f);

Let’s say, for example:
PORTC is B10111011 and that row is B11010011

(PORTC & 0xf0) is B10111011 & B11110000
Which reduces to B10110000
In other words, the 4 lower order bits of PORTC are masked out.

(row >> 6) is B11010011 shifted right 6 places, that is B00000011
so
((row >> 6) & 0x0f) is B00000011 & B00001111
Which reduces to B00000011

so
(PORTC & 0xf0) | ((row >> 6) & 0x0f)
Is B10110000 | B00000011
Which reduces to B10110011

So PORTC finally has the value B10110011

To add to the previous response, row and column are both 16-bit integers, so ((row >> 6) & 0x0f) will give you the upper 4 bits of what presumably is a 10-bit address, and then outputs that on the lower 4 bits of PORTC.

Thanks for the breakdown. Think I've got it now :slight_smile:

The overall effect is here:

PORTC bits before PORTC = (PORTC & 0xf0) | ((row >> 6) & 0x0f);
7 6 5 4 3 2 1 0
A B C D E F G H

row bits (int16_t)
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
                  W X Y Z


PORTC bits after PORTC = (PORTC & 0xf0) | ((row >> 6) & 0x0f);
7 6 5 4 3 2 1 0
A B C D W X Y Z

6v6gt:
So PORTC finally has the value B10110011

Is this value on the physical pin of the output port (PORTC) or on the internal latch of PORTC?

GolamMostafa:
Is this value on the physical pin of the output port (PORTC) or on the internal latch of PORTC?

All that is exposed to the programmer in the case of port c are the three registers PORTC, PINC and DDRC.
DDRC is set with pinMode() and we can assume, in the case, that the OP has set all the pins as OUTPUT.
Writing to PORTC, when it is declares as an output, will cause a corresponding change in the value of PINC.
Generally, you read the value of a pin by reading PINC and set the value of pin by writing to PORTC

The ATMEGA328P datasheet, chapter 14 I/O Ports gives a comprehensive description.

EDIT

Incidentally, I generated an assembler dump, hoping that the execution order would be visible, for the OP.
Unfortunately, the compiler did everything inline and highly optimised so it is not much use as an explanation.

#include <Arduino.h>
#undef main

void setAddress(int row, int col)
{
  PORTB = row & 0x3f;
  PORTC = (PORTC & 0xf0) | ((row >> 6) & 0x0f);
}

int main()
{
  
  setAddress( 0x3DEF , 6 ) ;
  setAddress( 2 , 3 ) ;

  while(1)
 {
   ;
 }
 return 0;
}

Dump of the invocation of setAddress( 0x3DEF , 6 ) ;

void setAddress(int row, int col)
{
  PORTB = row & 0x3f;
  80:   8f e2           ldi     r24, 0x2F       ; 47
  82:   85 b9           out     0x05, r24       ; 5
  PORTC = (PORTC & 0xf0) | ((row >> 6) & 0x0f);
  84:   88 b1           in      r24, 0x08       ; 8
  86:   80 7f           andi    r24, 0xF0       ; 240
  88:   87 60           ori     r24, 0x07       ; 7
  8a:   88 b9           out     0x08, r24       ; 8

6v6gt:
Generally, you read the value of a pin by reading PINC and set the value of pin by writing to PORTC

The quoted line is the textual description of what the following diagram (Fig-1) depicts at conceptual level for Bit-2 of Port-C Register of ATmega328P MCU.
PortcBit2.png
Figure-1:

Remarks:
1. When Bit-2 is configured to work as output, the pin-signal (could be even LOW if driving a low impedance load though HIGH was written on PORTC2) is available to the user via PINC2 (bool n = PINC2;).

2. When Bit-2 is configured to work as input, DDRC2 is OFF. the pin-signal (HIGH or LOW determined by the external device) is available to the user via PINC2 (bool n = PINC2;).

3. To know exactly what was written onto PORTC2 a while ago, we need to execute this code: "bool n = PORTC2;" and not this code: "bool n = PINC2;". This is how we toggle the logic level of the physical pin when the pin is configured to work as output.

PortcBit2.png

GolamMostafa:
. . .
3. To know exactly what was written onto PORTC2 a while ago, we need to execute this code: "bool n = PORTC2;" and not this code: "bool n = PINC2;". This is how we toggle the logic level of the physical pin when the pin is configured to work as output.

Of course we are talking specifically about the AVR architecture here, but toggling a pin by writing '1' to its PIN register works irrespective of whether the pin is defined as an input or output pin. That is, this operation does not respect the DDR register.
In your example, PINC2 = (bool) n ; // toggle

from ATmega328P data sheet

14.2.2 Toggling the Pin
Writing a logic one to PINxn toggles the value of PORTxn, independent on the value of DDRxn. Note that the
SBI instruction can be used to toggle one single bit in a port.

An operation like this is non-atomic and should be protected as a critical section:

PORTC = (PORTC & 0xf0) | ((row >> 6) & 0x0f);

Otherwise, an interrupt can occur between the PORTC read and write. If that interrupt changes PORTC, then the non-ISR code will corrupt that change when it regains control after the interrupt.

Use:

  noInterrupts();
  PORTC = (PORTC & 0xf0) | ((row >> 6) & 0x0f);
  interrupts();

gfvalvo:
An operation like this is non-atomic and should be protected as a critical section:

PORTC = (PORTC & 0xf0) | ((row >> 6) & 0x0f);

Otherwise, an interrupt can occur between the PORTC read and write. If that interrupt changes PORTC, then the non-ISR code will corrupt that change when it regains control after the interrupt.

Indeed. That is also clear from the assembler listing earlier in the thread. PORTC's value is loaded into register r24, which is used as a working register. At the end of the calculation, over several machine instructions, the current value of in r24 is written back to PORTC. Any changes to PORTC in the meantime, by some asynchronous task, will have been lost.