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.