1

TL;DR When Serial is invoked on a rev3 Uno, the UART uses the Tx and Rx pins on PORTD regardless of what you try to do to PORTD I/O directly. The warnings on arduino.cc about avoid those bits appears to be wrong. Readbacks from PORTD on those pins reflects the last user input, not the state of those pins. /TL;DR

I have extensive assembler (other platforms) and python experience, but no C and am still on the lowers lopes of the Arduino learning curve. I am using the Arduino/C IDE at the moment for simplicity, though I have a feeling that once I bite the bullet (not for this project if possible), I am going to find the direct AVR route suits my style more.

I'm developing a charliplexed LED driver application on Uno hardware which uses most of the I/O pins, so am having to use portD. I'm using direct writes to DDRD and PORTD rather than digitalWrite(), for a number of reasons not all related to speed.

I am aware of the use of pins 0 and 1 for serial comms, so am using the recommended DDRD |= Bxxxxxx00 format when setting to 1, and DDRD &= Bxxxxxx11 when setting to 0.

I hadn't intended reading back the port, though I'm knee-deep in debug readbacks, so did it eventually. When I read back DDRD, it's Bxxxxxx00 every time. But my readbacks are working, so serial communication using the Serial.println(xx) format and the monitor in the IDE, continues to work.

I would have expected the Tx direction bit to be 1. Is it the case that the Serial library grabs the port when it needs to transmit, and then reverts it? So is it actually 0 when I read it, or am I reading wrongly, or I can't read it? As I expect DDRD &= Bxxx to work, I should be able to read it.

I'm comfortable working with the bare metal, where I have total control, and with python where it's all magic, but get a bit uneasy when I'm not sure whether I expect magic to be happening under the hood or not.

Here's the code

const byte portD_1_mask = B11111100;
const byte portD_0_mask = B00000011;

#define DEBUG_LEN 20
char debug_str[DEBUG_LEN+1];  
#include "debug_formats.h"

byte counter;

void setup() {
  // Open serial communications and wait for port to open:
  Serial.begin(9600);
  while (!Serial) {
    ; // wait for serial port to connect. Needed for native USB port only
  }

  // send an intro:
  Serial.println("\n\ndebug portD demo:");
  Serial.println();
}

void loop()
  {
    counter += 3;
    Serial.println(counter);
    drive_LEDs(counter);    
    delay(100);
  }

void drive_LEDs(int a)
{  
    // create port programming variables, initialised to 0
    byte D_dir = 0;
    byte D_data = 0;

    // prepare bytes to be written to the ports   
    // the anode gets driven by ORing into both data and direction
    D_dir |= 4;  // constant for this demo
    D_data |= 4;
    // the cathode(s) get driven by ORing into the direction only
    D_dir |= a;   // wave these around

    Serial.println("generated programming bytes");   
    Serial.print("direction - data - ");
    debug_format(D_dir, D_data);
    Serial.println(debug_str);      

    // finally write all pins to their new values
    // D port needs to pussyfoot around not changing the TxRx pins

    // reset D port 7-2 to all 0s
    PORTD &= portD_0_mask;
    // and then set the ones we've got in the 7-2 positions
    D_data &= portD_1_mask;
    PORTD |= D_data;

    // and the same for D direction
    // switch D 7-2 to inputs first
    DDRD &= portD_0_mask;
    // then set the outputs
    D_dir &= portD_1_mask;
    DDRD |= D_dir;

    // and see what we have written to port
    Serial.println("what the hardware has been set to");   
    Serial.print("direction - data - ");
    D_dir = DDRD; 
    D_data = PORTD;
    debug_format(D_dir, D_data);
    Serial.println(debug_str);      
}

and the results it produces on the monitor

debug portD demo:

3
generated programming bytes
direction - data - 00000111..00000100..
what the hardware has been set to
direction - data - 00000100..00000100..
6
generated programming bytes
direction - data - 00000110..00000100..
what the hardware has been set to
direction - data - 00000100..00000100..
9
generated programming bytes
direction - data - 00001101..00000100..
what the hardware has been set to
direction - data - 00001100..00000100..
12
generated programming bytes
direction - data - 00001100..00000100..
what the hardware has been set to
direction - data - 00001100..00000100..

The hardware is doing what I'd expect, LEDs are flashing.

Now I'm not concerned that the 'generated programming bytes' have LSBs set and they don't appear in the readback, as I'm doing a data &= portD_1_mask which clears the LSBs before ORing into the port.

What I am questioning is the fact that the two LSBs on DDRD are always read back as 0. If the Serial library grabbed and kept those pins continuously, then I would expect the Tx pin to be an output, whenever I read it. But it's not reading back that way. So does that mean Serial only makes the Tx an output when it's transmitting, and then puts it back to input when not? Or is my read of the DDRD Tx bit not working for some reason, though it seems to be working for the other bits?

OK, so I've got the 'scope out, put a resistor network on the Tx pin so I can see whether it's high or low Z, and it stays low Z even when not transmitting, so the UART does 'retain' the port. This means the readback of the DDRD contents is a lie. Section 19.6 of the AVR doc 8161 does say the port pins are overridden by the UART, which this seems to confirm.

So if they are overridden, all this warning on the arduino.cc site tutorials about not stomping on the pins is incorrect. The following code bashes the bejaabers out of PORTD while using it to receive and send.

  int count;

// the setup function runs once when you press reset or power the board
void setup() {
  // initialize digital pin LED_BUILTIN as an output.
  pinMode(LED_BUILTIN, OUTPUT);
  // Open serial communications and wait for port to open:
  Serial.begin(9600);
  while (!Serial) {
    ; // wait for serial port to connect. Needed for native USB port only
  }
  // send an intro:
  Serial.println("\n\ndebug portD demo:");
  Serial.println();
}

// the loop function runs over and over again forever
void loop() {
  count += 1;
  if ((count&1)==0) DDRD = 0; else DDRD = 0xff;
  if ((count&2)==0) PORTD = 0; else PORTD = 0xff;

  PORTB = 0xff;
  byte dd = DDRD;
  byte pp = PORTD;
  Serial.println();
  Serial.println(count);
  Serial.println(dd);
  Serial.println(pp);  
  PORTB = 0;
  delay(2000);     

  if (Serial.available() > 0)
  {
    byte inByte = Serial.read();
    Serial.print("got >");
    Serial.println(inByte);
  }
}

and this fragment of output captured from the monitor shows it keeps working. I used a sufficiently long delay so I'd be able to hit all phases of the write to data and direction on PORTD.

9
255
0
got >49

10
0
255
got >114

11
255
255
got >99

12
0
0
got >115

13
255
0
got >115

Lessons

a) When Serial is in use, the UART hardware does override the Tx Rx pins, at least on a rev3 Uno.

b) It does hang on to the hardware between active transmissions

c) The warnings on arduino.cc tutorials about writes to PORTD and DDRD don't seem to be valid, it appears the hardware just ignores them

d) Reads back from PORTD and DDRD show what you last wrote in pins 0 and 1 when the UART is in use, not what's on the pins.

8
  • so am using the recommended DDRD |= Bxxxxxx00 format when setting to 1 - how does that set anything to 1? Please post code that proves what you are saying. All this description stuff doesn't prove anything. Please post a Minimal, Complete, and Verifiable example
    – Nick Gammon
    Commented Nov 19, 2016 at 7:59
  • If your (unposted) code turns on Serial communication, the serial hardware takes over the functions of digital pins 0 and 1.
    – Nick Gammon
    Commented Nov 19, 2016 at 8:00
  • @NickGammon code posted.
    – Neil_UK
    Commented Nov 19, 2016 at 9:50
  • 1
    The "Serial library" does nothing with the pins. The "Serial library" controls the UART peripheral which then controls the pins. When using the UART peripheral you have no control over the UART pins directly, and any use of DDR, PORT or DIN on those pins is essentially meaningless.
    – Majenko
    Commented Nov 19, 2016 at 10:49
  • @Majenko I understood from the examples on the arduino.cc tutorials that it was possible to interfere with the UART's use of those pins, which is why I'm following the DDRD |= xxxxxx00 method of setting the high 6 pins' direction. Are you saying that that's not the case? I would have expected the first use of the Serial library to result in the 'capture' of those pins for the UART, which is why I'm questioning always reading both pins as input. Ultimately I have a method for using the D port and serial communication, I'm just trying to get my mental model to match documentation and HW.
    – Neil_UK
    Commented Nov 19, 2016 at 14:32

1 Answer 1

1

See the datasheet: 14.3.3 Alternate Functions of Port D

  • TXD/PCINT17 – Port D, Bit 1 TXD, Transmit Data (Data output pin for the USART). When the USART Transmitter is enabled, this pin is configured as an output regardless of the value of DDD1.
    PCINT17: Pin Change Interrupt source 17. The PD1 pin can serve as an external interrupt source.

  • RXD/PCINT16 – Port D, Bit 0 RXD, Receive Data (Data input pin for the USART). When the USART Receiver is enabled this pin is configured as an input regardless of the value of DDD0. When the USART forces this pin to be an input, the pull-up can still be controlled by the PORTD0 bit.
    PCINT16: Pin Change Interrupt source 16. The PD0 pin can serve as an external interrupt source.

So basically if you turn on the USART the pins are configured as input/output regardless of DDRD.


but get a bit uneasy when I'm not sure whether I expect magic to be happening under the hood or not.

It's not the IDE - it's the underlying hardware.


This means the readback of the DDRD contents is a lie.

No it isn't. The hardware is ignoring DDRD in those bits. See this example:

void setup() 
{

DDRD = 0;
byte a = DDRD;
DDRD = 0xFF;
byte b = DDRD;

Serial.begin (115200);

DDRD = 0;
byte c = DDRD;
DDRD = 0xFF;
byte d = DDRD;
Serial.end ();

Serial.begin (115200);
Serial.print ("a = ");
Serial.println (a);
Serial.print ("b = ");
Serial.println (b);
Serial.print ("c = ");
Serial.println (c);
Serial.print ("d = ");
Serial.println (d);
}

void loop() 
{
}

Output:

a = 0
b = 255
c = 0
d = 255

The code is setting DDRD and reading it back as set. However the hardware is ignoring those bits because of the alternative use of those ports.

3
  • Yes, thanks. My issues were 'what does 'turn on the USART' mean?' so is it 'on' only when transmitting, or is it 'on' from the time Serial.begin() is called, and it looks like the latter. The second was the warning claptrap in the tutorials on arduino.cc that tell you how to not to mess with those pins. It turns out the hardware 'magic under the hood' is quite strong enough to avoid the need to tell noobs anything about the shared use of those pins, and let them write regardless, as they won't upset Serial, just won't be able to use those pins. I'll find my way round the docs soon. Thanks.
    – Neil_UK
    Commented Nov 20, 2016 at 7:12
  • The stuff on the Arduino reference is addressed at beginners. If you read the datasheets you can do what you want. Once the USART is enabled it will continue to take over those pins until it is disabled. (ie. Serial.end()).
    – Nick Gammon
    Commented Nov 20, 2016 at 8:10
  • In the tutorial, I would have thought that 'port D bits 0 and 1 are don't care, you can do what you like with them but they don't connect to the hardware I/O after a Serial.bgin()' was more accurate, and more noob friendly, than 'dont drive them, because if you do you'll trash your serial comms, and here's a fiddly |= way to avoid that', which is unnecessary, alarmist and wrong. Of course the total noob will stay with digitalWrite(pin_no). I wonder if the tutorials can be corrected, Majenko doesn't appear to like them!
    – Neil_UK
    Commented Nov 20, 2016 at 10:15

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.