Tutorial on digital I/O, ATMega PIN/PORT/DDR D/B registers vs. ARM GPIO_PDIR / _PDOR

immortalSpirit

Active member
Tutorial on digital I/O, ATMega PIN/PORT/DDR D/B registers vs. ARM GPIO_PDIR / _PDOR A/B/C/D/E registers!

OR, "How to get a fast 8-bit-wide digital read/write!"

I spent the evening figuring this out so I'd like to pass it on to you if u r interested.

My mission: read/write 8 digital I/O pins simultaneously and as fast as possible. I don't care WHICH 8 pins, I just need 8 pins. And I need *fast*.

On the ATMega based Arduino, I used PIND and PORTD, which are each 8 bit registers which read and write pins 0,1,2,3,4,5,6 and 7 packed into a single byte.

On TEENSY-3, the emulation code for PIND and PORTD is very inefficient. Indeed, it is basically 8 digital reads. The code (in avr_emulation.h) basically looks like this:

int ret = 0;
if (digitalReadFast(0)) ret |= (1<<0);
if (digitalReadFast(1)) ret |= (1<<1);
if (digitalReadFast(2)) ret |= (1<<2);
if (digitalReadFast(3)) ret |= (1<<3);
if (digitalReadFast(4)) ret |= (1<<4);
if (digitalReadFast(5)) ret |= (1<<5);
if (digitalReadFast(6)) ret |= (1<<6);
if (digitalReadFast(7)) ret |= (1<<7);
return ret;

That is 8 digitalReadFast's, plus 8 IFs and ORs.

The reason for this is that the actual bits corresponding to the TEENSY-3 pins are scrambled in their order. And therein lies a tale!

After a bunch of reading the "K20 Sub-Family Reference Manual" and studying the TEENSY-3 schematics, it somehow makes sense!

The ARM core has 5 registers much like the ATMel PIN/PORT registers. They are GPIOA, GPIOB, GPIOC, GPIOD, and GPIOE. Each of these supports up to 32 pins. They are *extremely* programmable, and can be mapped in whole or in part to various functions and various pins on the ARM chip.

The TEENSY 3.0 software apparently sets them up as follows:

(where: X = TEENSY-3 external pin number, Y = which GPIO port that reads and writes it, and Z = which bit of that port reads and writes it)

X Y Z
---------------
3 A 12
4 A 13
24 A 5
33 A 4
0 B 16
1 B 17
16 B 0
17 B 1
18 B 3
19 B 2
25 B 19
32 B 18
9 C 3
10 C 4
11 C 6
12 C 7
13 C 5
15 C 0
22 C 1
23 C 2
27 C 9
28 C 8
29 C 10
30 C 11
2 D 0
5 D 7
6 D 4
7 D 2
8 D 3
14 D 1
20 D 5
21 D 6
26 E 1
31 E 0

That's confusing. I'll explain the first four lines:

3 A 12
4 A 13
24 A 5
33 A 4

These say that:
GPIOA bit 12 is connected to pin 3 on the TEENSY-3 circuit board,
GPIOA bit 13 is connected to pin 4,
GPIOA bit 5 is connected to pin 24 (which is on the backside of the board), and
GPIOA bit 4 is connected to pin 33 (also on the backside of the board).

And the 5th line,
0 B 16

says that GPIOB bit 16 connects to pin 0 on the TEENSY-3.

In other words, we are using only 4 of the possible 32 bits of GPIOA, and they are connected to 4 pins of the TEENSY-3, and in scrambled order! In similar manner, GPIOB has 6 of its bits (16,17,0,1,3,2,19 and 18) connected to 6 of the TEENSY-3 external pins (0,1,16,17,18,19, and 25).

If you sort the above table in order by first column (external pin number) instead of second column you can see just how scrambled the pins are:

Teensy-3 Pin, GPIO Port, GPIO Bit #
---------------
0 B 16
1 B 17
2 D 0
3 A 12
4 A 13
5 D 7
6 D 4
7 D 2
8 D 3
9 C 3
10 C 4
11 C 6
12 C 7
13 C 5
14 D 1
15 C 0
16 B 0
17 B 1
18 B 3
19 B 2
20 D 5
21 D 6
22 C 1
23 C 2
24 A 5
25 B 19
26 E 1
27 C 9
28 C 8

How did I determine this table? Two answers: first, look at the TEENSY-3 circuit diagram: http://www.pjrc.com/teensy/schematic.html

Going down the right side of the big chip, we see:
PT C2 45 23/A9
PT C1 44 22/A8
PT D6 63 21/A7
and so on. This corresponds to the lines in my table that read:
23 C 2
22 C 1
21 D 6
(The 45,44, and 63 are irrelevant in this discussion, but are the pin numbers of the physical ARM chip. Just to further confuse you!)

So, a few bits from each of the 5 ports are being used, some more than others, and all in scrambled order!

The other way to determine this table, and corroborating my observations, is the #defines inside the core_pins.h file. Actually, that is where I started from, then noticed the schematic had the same data.

Armed with this understanding, the solution becomes apparent. Let's look just at the lines from the above table which use GPIO "D", and sort them by GPIOD bit number:

2 D 0
14 D 1
7 D 2
8 D 3
6 D 4
20 D 5
21 D 6
5 D 7

In other words, the lower 8 bits of GPIOD map to 8 pins, just like ATMega's PIND/PORTD registers, except that where PIND/PORTD map to pins 7,6,5,4,3,2,1,0 in that order, GPIOD maps to pins 5,21,20,6,8,7,14,2, in that order!

Solution:

1. connect my 8 wires to the TEENSY-3 circuit board pins labeled digital 5,21,20,6,8,7,14,2, IN THAT ORDER. The other ends of these 8 wires goes my own connector labeled 7,6,5,4,3,2,1,0.

2. Execute this statement in my code:

byte allEight = GPIOD_PDIR & 0xFF;

QED! allEight bits 0 through 7 correspond to the data on my wires 0 through 7!

Thus, by choosing this particular set of 8 pins, WE HAVE THE EXACT EQUIVALENT of what the ATMel processors call "PIND".

Note also that I could have chosen port C and gotten TWELVE consecutive pins! Executing this statement:

byte anotherEight = GPIOC_PDIR & 0xFF;

then gives us what ATMel processors call "PINB", scrambled to pins 15,22,23,9,10,13,11 and 12 in that order!

GPIOx_PDIR is the input register for port x, and GPIOx_PDOR is the output register for port x. I won't go into how one can set the direction, it is a bit more complex. Since my application only needs the speed for the read/write, I can use the normal "pinMode" calls to set the direction.

============================
I ran and tested the following code:

// input test:
byte pinTable[] = {2,14,7,8,6,20,21,5};

void setup() {
Serial.begin(0);
for (int i=0; i<8; i++) { pinMode(pinTable,INPUT_PULLUP); }
}

void loop() {
byte eight,prev_eight;
do {
eight = GPIOD_PDIR & 0xFF;

if (eight != prev_eight)
{
prev_eight = eight;
Serial.println(eight,HEX);
}
} while (1==1);
}

With nothing touching the above program prints "FF", all ones. By connecting a jumper from ground and alternately touching it to any of the pins in the "pinTable" above, it printed a new value with the given port bit 0.

And the following program tests output. I took an LED+resistor to ground and touched the other end alternately to one of the specified pins, and within 8 seconds, when the program came around to printing that pin's number, the LED lit!

// output test:
byte pinTable[] = {2,14,7,8,6,20,21,5};

void setup() {
Serial.begin(0);
for (int i=0; i<8; i++) { pinMode(pinTable,OUTPUT); }
}

void loop() {
do {
for (int i=0; i<=7; i++)
{
byte b = 1<<i;
GPIOD_PDOR = b;
Serial.println(pinTable);
delay(1000);
}
} while (1==1);
}

=======================
Thus, in summary:

One can use "GPIOD_PDIR" and "GPIOD_PDOR" as almost exact replacements for "PIND" and "PORTD", if you are willing to use a funny set of pins instead of a pins 0 through 7.

=======================
End of tutorial. Thanks for listening...
 
Last edited:
thank you, thank you, THANK YOU !!!!

You have given the most complete detailed and understandable explanation I have found so far...
Maybe you've seen my topic about a TFT-touchscreen I've been trying to connect, and this has been my main issue, except unlike you, I have been trying and testing for a few weeks now...

With your explanation I finally gotten connect the 8 control lines from the TFT to 'PORTD' now als known as 'GPIOD_PDOR' :p

I owe you a HUGE thank you (and probably a lot more), not only for fixing my problem, but mainly for sharing your knowledge in such a clear way!
 
P.S. I am pretty new to the subject, but I managed to update a library after reading your tutorial/guide only once
Again my compliments on how detailed it is while still being clear!
 
You are most welcome! It is gratifying to help someone! Please make all checks out to the "Steven Swift Car Repair Fund"... :)
 
Yes, thank you! I have been searching for this info for a few days, and your explanation is very clear. I made the attached image for myself as a reference, which might be helpful to others.

Teensy3Ports.JPG
 
If u look at the circuit diagram here: http://www.pjrc.com/teensy/schematic.html (scroll down to the 2.0 version)
you will see identifiers around the edges. For example, the top right hand corner says "ADC0 PF0 | 41 ----- F0". This means that port F bit 0 is connected to internal pin 41 (which is the ADC0), external pin (on the board) F0. (You don't care about the internal pin number, 41, ignore it). If u look at the board, you'll see "F0" painted along the edge. Indeed, from the upper left of the physical board, you'll see: "GND, B0, B1, B2, B3, B7, D0, D1,...". Those are the port and bit number, right there! Hope that helps...
 
Perfect Thanks, it seems so simple now.

If u look at the circuit diagram here: http://www.pjrc.com/teensy/schematic.html (scroll down to the 2.0 version)
you will see identifiers around the edges. For example, the top right hand corner says "ADC0 PF0 | 41 ----- F0". This means that port F bit 0 is connected to internal pin 41 (which is the ADC0), external pin (on the board) F0. (You don't care about the internal pin number, 41, ignore it). If u look at the board, you'll see "F0" painted along the edge. Indeed, from the upper left of the physical board, you'll see: "GND, B0, B1, B2, B3, B7, D0, D1,...". Those are the port and bit number, right there! Hope that helps...
 
Thanks for the outstanding tutorial! you made the whole port manipulation thing a considerable step easier to comprehend!

I have one quick question and one possibly more difficult one. This is my first time using Port Manipulation and normally the pins are set high or low using 8-bits (B11110000) but Teensy 3.1 uses 32 bit right? does that mean you should use something like B00000000111100001111000011110000 ?


My more difficult question is about triggering multiple port registers at the same time:

I am using a 16 pin Multiplexer and I recently found out that for my project I need to trigger the 4 control pins of the multiplexer to their HIGH or LOW values at exactly the same time.

This would be fine if I had all 4 pins in the same port register (A, B, C, or D) but I don't. Mostly I need to trigger two port registers at the same time. I would rewire, but I have already made a PCB for my project, so I am wondering if there is a way to trigger multiple port registers at the same time:

e.g. PORTD = B00001010 & PORTB = B10010000

Any help with this is greatly appreciated!
 
If I read u right, u r connecting 4 Teensy pins to your mux and want to change them simultaneously.

Do they need to be consecutive pins on the Teensy? If not, then the solution is to wire your pins non-consecutively as follows. You will have to cut the traces on your pcb and add jumper wires, can u do that?

Observe this table from my first post:

X Y Z
---------------
3 A 12
4 A 13
24 A 5
33 A 4
0 B 16
1 B 17
16 B 0
17 B 1
18 B 3
19 B 2
25 B 19
32 B 18
9 C 3
10 C 4
11 C 6
12 C 7
13 C 5
15 C 0
22 C 1
23 C 2
27 C 9
28 C 8
29 C 10
30 C 11
2 D 0
5 D 7
6 D 4
7 D 2
8 D 3
14 D 1
20 D 5
21 D 6
26 E 1
31 E 0

(Three fields are teensy pin #, port letter, port bit number, eg: teensy pin 14 is port D bit 1)

If you take these in a different order, ie, sort by columns 2 and 3, you'll see that each port A,B,C,D has many pins, more than 4 each, that u could use. For example, Port D bit 0 drives teensy pin 2, port D bit 1 drives teensy pin 14, port D bit 2 drives teensy pin 7 and port d bit 3 drives teensy pin 8. Thus, if you connect pins 2,14,7, and 8 in that order to your mux, you can change them all simultaneously using GPIOD_PDOR to set/clear port D bits 0,1,2,and 3 simultaneously setting pins 2,14,7 and 8 simultaneously. The port registers are:

PDDR: Port Data Direction Register: 0 means this bit/pin is a input, 1 means output.
PDIR: Port Data Input Register: 0/1 according to the pin's state.
(If any pin is an output it can still be read by PDIR and will simply read back the same value last output on that pin).
PDOR: Port Data Output Register: 0 sets the bit/pin to 0, 1 to 1.

The following three sub-registers are logically redundant, but save an instruction or two in some cases.

PSOR: Port Set Output Register: 0 has no effect. 1 sets the corresponding bit/pin to 1.
PCOR: Port Clear Output Register: 0 has no effect. 1 sets the corresponding bit/pin to 0.
PTOR: Port Toggle Output Register: 0 has no effect. 1 toggles the corresponding bit/pin.

Thus
GPIOD_PDOR = 0xF
sets pins 2,14,7,and 8 to 1's.
GPIOD_PDOR = 0xA // 1010 binary
sets pins 2 and 7 to 1 and 14 and 8 to 0
Etc.

Is that what u r looking for?
 
Regarding:
e.g. PORTD = B00001010 & PORTB = B10010000

No, no way to do that. But can do two instructions back-to-back. On Teensy what u called PORTD/B is GPIOD/B_PDIR/PDOR. At 48 or 72 MHz these instructions are way less than a microsecond apart. Does your mux really need to be *that* simultaneous?
 
Last edited:
Thank you!!! I'm searching for this informantion! Now I'm praying the Teensy ++ 3 (if Paul release it someday) comes with the GPIO ordered like the beautiful Teensy ++ 2!
 
Hi immortalSpirit,

I'm newbie to tweensy and K20 chip. This post was very useful. I trying to learn more. Maybe I'm completely wrong here, but I disagree with one of the statements. The assertion that the K20 function has a feature to do IO port to pin mapping.

> The ARM core has 5 registers much like the ATMel PIN/PORT registers. They
>are GPIOA, GPIOB, GPIOC, GPIOD, and GPIOE. Each of these supports up to 32
>pins. They are *extremely* programmable, and can be mapped in whole or in
>part to various functions and various pins on the ARM chip.

I don't want to sound like I am nitpicking because I just want to learn the teensy 3.1 board.

So I read skimmed the K20 manual to see how PORTs can be mapped to pins, but it looks like they are fixed. Each pin can have alternate functions like ADC, but no IO port to pin mapping. So for example I count only 10 pins on GPIOA, and Teensy 3.1 board has less. I assume the pin brought out to the headers was purely for the alternate analog functions.
 
Well, OK, u could be right! Seems like that's what I determined "back then" when I answered the first post here. I too merely "skimmed" the K20 manual. I'm not engaged enuf now to look at it more, but if u can find a more definitive answer, please do! And post it! Even assuming u r right, there is still then the question of why Paul S. did the PCB the way he did, since whatever the K20 pins turn out to be doesn't limit the PCB board to swizzling them into consecutive holes/pins around the edge of the board. For me, I am happy to re-swizzle them externally by connecting my 8 bit cable to what appears on to be 8 "random" pins on the tennsy, ie, not consecutive, which makes for a messy connector, but simple software. Here is a case where the cliche "There is a method to my madness" is appropriate...
 
there is still then the question of why Paul S. did the PCB the way he did

A lot of planning went into maximizing Arduino compatibility. A lesson learned from earlier versions of Teensy was how entrenched the Arduino Uno pinout is, even years after Arduino has released other boards with signals on different pins. For example, a LOT of tutorials on many websites, particularly Adafruit and Sparkfun, are written for the SPI signals to be pins 10, 11, 12 and 13. A tremendous effort went into studying nearly all libraries and examples that existed 2 years ago (when Teensy 3.0 was designed) and crafting a pinout that would maximize compatibility.

Even with a 4 layer PCB, routing all those pins was incredibly difficult. In the end, I believe it was worthwhile. For most advanced users, the pin arrangement doesn't matter much. But for a lot of novices, having the signals on pins that correspond to many tutorials published on other websites really makes things simpler.
 
Came across the PORT Table - and wondered how it applied on Teensy units 3.1 and LC and got reply below::

A lot of planning went into maximizing Arduino compatibility. ... A tremendous effort went into studying nearly all libraries and examples that existed 2 years ago (when Teensy 3.0 was designed) and crafting a pinout that would maximize compatibility.

FYI:

Looks like it's still only for Teensy 3.0 and 3.1.

Teensy-LC differs on pins 3-4 and 24-26. Of course, LC doesn't have 27-33. All the other pins have identical mapping between Teensy LC and 3.1.
 
@defragster, do you live here in this forum ? ;)

Its very useful to study the schematics. Perhaps you can complete the Port Table ?
 
Its very useful to study the schematics. Perhaps you can complete the Port Table ?

From what Paul says those ports are right for the 3.0 and 3.1 and also the LC where the pins exist. I suppose the missing pins are the ones the 3.1 added?

@defragster, do you live here in this forum ? ;)

Too much (the taxes are lower ;) ) - [17 years cold-turkey after leaving work] enjoying coding again and accumulating Teensy info in case I ever start my project . . . hitting the 1.6.0 beta is the reason I unpacked my 3.1's but the distractions through 1.6.3 release fed my enormous propensity to procrastinate. Yesterday I got 10 sets M/F Machine 40 pin headers - so I should be out of excuses again.

I will repower an ILI9341 and put your code to the test with the FFT code I need to get back to.
 
Last edited:
Back
Top