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

Thanks Leray,

I had my doubts about applying the mask to GPIOB_PDOR. So the following should work?


GPIOB_PDOR = (GPIOB_PDIR & ~b_mask) | (send_value & b_mask); // Modify only 6 bits in Port B


I was thinking that if the syntax above will safely modify the value on the port, it wouldn't actually matter if the bits within the port are contiguous. With the correct mask I can access all the available bits within a given port and access them all simultaneously.

Although a more straight forward parallel interface would be great, if I can get away with manipulating the ports using this approach, it would be versatile enough for my needs.
 
Thanks Leray,

I had my doubts about applying the mask to GPIOB_PDOR. So the following should work?


GPIOB_PDOR = (GPIOB_PDIR & ~b_mask) | (send_value & b_mask); // Modify only 6 bits in Port B


I was thinking that if the syntax above will safely modify the value on the port, it wouldn't actually matter if the bits within the port are contiguous. With the correct mask I can access all the available bits within a given port and access them all simultaneously.

Although a more straight forward parallel interface would be great, if I can get away with manipulating the ports using this approach, it would be versatile enough for my needs.

You could be completely safe using GPIOB_GPIR (With 'I' as INPUT)for reading the state of the whole B register. And relaoad it after your boolean operations.
 
I could be wrong but I have no idea why you would be looking at the state of the GPIOB_PDIR register? This is the Input state (read only register), of those IO pins on that object...
You can readn GPIOB_PDOR - For the current output state of all IO pins on GPIOB... This register is Read/Write. (Page 2189 of my T3.6 PDF...)

So you can do things like:
Code:
GPIOB_PDOR = (GPIOB_PDOR & ~b_mask) | (send_value & b_mask);

I am not sure which N pins of the GPIOB you are using. But hypothetically you are using: Bits 0-5 for 6 bits, which quick look is something like pins:
(16, 17, 19, 18, 49, 50)

However I assume you realize that you run a risk with this code. For example suppose your code uses some form of interrupts, example an intervalTimer, and suppose your interval timer procedure does something like:

Code:
void myIntervalTimerProc() {
    digitalWriteFast(0, !digitalReadFast(0));
     (it's work)
}

And suppose you have the above code like: GPIOB_PDOR = (GPIOB_PDOR & ~0x3f) | (send_value & 0x3f);

Now suppose your code is running, and just after it reads the GPIOB_PDOR but before it updates it, your ISR runs and changes the state of D0 (which is B16)
When the ISR returns and your code completes the update of the GPIOB_PDOR you just throw away the updated state from the ISR...

And these are fun bugs to track down.. Been there!


Again there are ways to fix this, like:
Code:
	__disable_irq();
	GPIOB_PDOR = (GPIOB_PDOR & ~b_mask) | (send_value & b_mask);
	__enable_irq();

or if you can stand the timings and a pins changing in two steps... Maybe something like:
Code:
	GPIOB_PSOR = send_value;   // assumes send_value only has the N bits set or cleared in it if not mask it.
	GPIOB_PCOR = ~send_value & b_mask;
These operators only muck with the bits you are using and as such are interrupt safe...
 
That is a huge help. Your 2nd example that allows me to clear or set only the bits I'm using, without touching anything else, is absolutely perfect. Since I only send brief signals and already zero out the bits afterwards, it's actually more of a one step process for me to use GPIOx_PSOR to put a value on the port.

I followed your lead and looked at the data sheets at https://www.pjrc.com/teensy/datasheets.html

The "General-Purpose Input/Output (GPIO)" chapter in the T3.5 and T3.6 manuals was very concise and easy to follow. It's obvious once you know it, but that chapter put everything discussed in this thread into perfect context for me.
 
*where i = uint32_t

I've been doing some code optimization using GPIO ports on the T.36, for some reason I'm having troubles with GPIOB that I'm not seeing on other ports.

Why doesn't this second equation work equally to the first (which does work)?

example 1 works.
Code:
GPIOB_PSOR = ((i & 0x40)    << 12);
GPIOB_PSOR = ((i & 0x80)    << 12);

example 2 doesn't.
Code:
GPIOB_PSOR = ((i & 0xC0)    << 12);

its the same port and the same shift, so i should be able to combine the two. Works well on other ports, not GPIOB. Any thoughts?
 
It looks like you are setting bits 18 and 19 in GPIOB. In the second example, both bits are set from i at the same time. In the first example, the first line sets bit 18 from i with bit 19 set to 0, and the second line sets bit 19 from i with bit 18 set to 0. At the end, bit 18 will always be 0.

To make example 1 work like example 2, you would OR the previous value of GPIOB_PSOR:

Code:
GPIOB_PSOR = (GPIOB_PSOR & ~0x40000) | ((i & 0x40)    << 12);
GPIOB_PSOR = (GPIOB_PSOR & ~0x80000) | ((i & 0x80)    << 12);
That should make example 1 fail just like example 2 ;)
 
Actually still confused. I thought a value of "0" had no effect on bits for PSOR? I've been clearing the bitmasks at the beginning of the loop with PCOR...

PSOR: Port Set Output Register: 0 has no effect. 1 sets the corresponding bit/pin to 1.

It looks like you are setting bits 18 and 19 in GPIOB. In the second example, both bits are set from i at the same time. In the first example, the first line sets bit 18 from i with bit 19 set to 0, and the second line sets bit 19 from i with bit 18 set to 0. At the end, bit 18 will always be 0.

To make example 1 work like example 2, you would OR the previous value of GPIOB_PSOR:

Code:
GPIOB_PSOR = (GPIOB_PSOR & ~0x40000) | ((i & 0x40)    << 12);
GPIOB_PSOR = (GPIOB_PSOR & ~0x80000) | ((i & 0x80)    << 12);
That should make example 1 fail just like example 2 ;)
 
Sorry, I've been using PDOR and didn't notice the one letter difference. It looks like it should work with PSOR, but I've never used those registers so don't have any experience with them. I'll give it a try this evening.
 
I was not able to duplicate your problem. Both ways always set both pins. I first set and cleared the bits individually, then set and cleared them together.

Untitled2.jpg

Here is my code:

Code:
pinMode (29, OUTPUT);
pinMode (30, OUTPUT);

uint32_t i = 0xFFFFFFFF;

while (1)
{
  GPIOB_PSOR = ((i & 0x40)    << 12);
  GPIOB_PSOR = ((i & 0x80)    << 12);

  delayMicroseconds (1);

  GPIOB_PCOR = ((i & 0x40)    << 12);
  GPIOB_PCOR = ((i & 0x80)    << 12);

  delayMicroseconds (1);

  GPIOB_PSOR = ((i & 0xC0)    << 12);

  delayMicroseconds (1);

  GPIOB_PCOR = ((i & 0xC0)    << 12);

  delay (1000);
}
 
I switched over to PDOR and haven’t experienced the same issues.

I will revisit this again, but given that I can write and clear bits in one operation with PDOR, it turns out to be more efficient vs PSOR + PCOR.

For most of my ports, the global setting of pins shouldn’t be an issue, but one port (GPIOB) has my ADC hooked up to it (B1)and I’m concerned it may erase that value.

Is there a way to ignore writing specific port bit when using PDOR?

Appreciate you taking the time to look into this.

Cheers.

I was not able to duplicate your problem. Both ways always set both pins. I first set and cleared the bits individually, then set and cleared them together.

View attachment 19923

Here is my code:

Code:
pinMode (29, OUTPUT);
pinMode (30, OUTPUT);

uint32_t i = 0xFFFFFFFF;

while (1)
{
  GPIOB_PSOR = ((i & 0x40)    << 12);
  GPIOB_PSOR = ((i & 0x80)    << 12);

  delayMicroseconds (1);

  GPIOB_PCOR = ((i & 0x40)    << 12);
  GPIOB_PCOR = ((i & 0x80)    << 12);

  delayMicroseconds (1);

  GPIOB_PSOR = ((i & 0xC0)    << 12);

  delayMicroseconds (1);

  GPIOB_PCOR = ((i & 0xC0)    << 12);

  delay (1000);
}
 
Think my issue could be related to setting optimize to "Fastest with LTO" in arduino IDE. When i switch it to "Faster with LTO", the corruption goes away...

I did a test, recompiling both ways. Either it's problematic with GPIO port commands, or just the way I'm coding it has to be phrased a differnet way to play nice...

I was not able to duplicate your problem. Both ways always set both pins. I first set and cleared the bits individually, then set and cleared them together.

View attachment 19923

Here is my code:

Code:
pinMode (29, OUTPUT);
pinMode (30, OUTPUT);

uint32_t i = 0xFFFFFFFF;

while (1)
{
  GPIOB_PSOR = ((i & 0x40)    << 12);
  GPIOB_PSOR = ((i & 0x80)    << 12);

  delayMicroseconds (1);

  GPIOB_PCOR = ((i & 0x40)    << 12);
  GPIOB_PCOR = ((i & 0x80)    << 12);

  delayMicroseconds (1);

  GPIOB_PSOR = ((i & 0xC0)    << 12);

  delayMicroseconds (1);

  GPIOB_PCOR = ((i & 0xC0)    << 12);

  delay (1000);
}
 
Think my issue could be related to setting optimize to "Fastest with LTO" in arduino IDE. When i switch it to "Faster with LTO", the corruption goes away...

I tried my example above compiled with "Fastest with LTO" at speeds up to 240 MHz and it worked every time. It may be a subtle timing issue with your setup - what defines 'not working'.

For most of my ports, the global setting of pins shouldn’t be an issue, but one port (GPIOB) has my ADC hooked up to it (B1)and I’m concerned it may erase that value.

If a pin is not assigned to GPIO via the MUX (by calling pinMode (x, OUTPUT)), then the value in GPIO_PDOR does not affect the pin.
 
I suspect you’re right. I think maybe the code is “too fast” in some situations for the memory I’m controlling.

I’m going to try and program in some delay (without using the delay()) and see if that helps. The memory wants specific timing once an address line is written before it’s ready to read the data, maybe 70ns.

Since delayMicroseconds() is too slow (1us min), I was just going to wing it using a while or for loop and experiment with the count max. Unless someone has built a delayNanoseconds() libray...

I tried my example above compiled with "Fastest with LTO" at speeds up to 240 MHz and it worked every time. It may be a subtle timing issue with your setup - what defines 'not working'.



If a pin is not assigned to GPIO via the MUX (by calling pinMode (x, OUTPUT)), then the value in GPIO_PDOR does not affect the pin.
 
Teensy 4 has a delayNanoseconds(), but not Teensy 3.

This line has about 40 nanosecond overhead plus 40 nanosecond delay per loop at 240 MHz (80 nanoseconds at 120 MHz):

Code:
for (int volatile index = 0; index < 1; index++);

So 40 nanoseconds for 0, 80 for 1, 120 for 2, 160 for 3, etc.
 
Last edited:
Bullseye!

I plugged that in before i read the datalines and BINGO. It adds about 0.8% more CPU overhead (vs without delay), but allows me to compile using Fastest With LTO.

Is this more efficient than using a timer?

Thanks for your help, 3 days ive been struggling with this issue.

Teensy 4 has a delayNanoseconds(), but not Teensy 3.

This line has about 40 nanosecond overhead plus 40 nanosecond delay per loop at 240 MHz (80 nanoseconds at 120 MHz):

Code:
for (int volatile index = 0; index < 1; index++);

So 80 nanosecond for 1, 120 for 2, 160 for 3, etc.
 
I am a new to this forum.
Looked for over an hour for Teensy 4.x port map.
I found the 4.x diagram at: https://www.pjrc.com/teensy/schematic.html but there are no Port names in that diagram similar to the 3.x. I was expecting to find PTAxx (replace A with port letter; B, C, or D and xx with bit number) but see only EMC_xx or Bx_xx (replace x with a number - ex: B1_08). So much much for consistency. Does the Teensy 4.x not have Port names?
 
Teensy 4 does have port names - ports 6/7/8/9. See this forum entry for more details. In the diagrams, the GPIO column is the port and bit number, but you need to add 5 to the port number. For example, pin 1 is GPIO 1.2, which means it is port 6 (1+5), bit 2.
 
Hi Im new here so sorry if im in the wrong place, please guide me to the right one.
Im trying to port code made for teensy 3.5 onto teensy 4.1, ive changed pins in the arduino software for the basics and it uses DMA, im entirely unsure about this, but the things that are causing issues have names
GPIOC_PCOR
GPIOD_PDOR
arduino asked me if i was talking about
GPIO9_PSR
GPIO9_DR
respectively
so i changed them to what arduino suggested and it compiled but im sure i did something wrong, can someone lead me on the right path,
Thanks
 
Maybe post your updated code so we know precisely what its doing?
ive mentioned in comments where changes have been done specifically with the words gpio in the dma file
 

Attachments

  • HP45-standalone-V4-main_mod.zip
    48.3 KB · Views: 7
ive mentioned in comments where changes have been done specifically with the words gpio in the dma file
Code:
 // configure the 8 port C output pins
  GPIO9_PSR = 0xFF; //changed GPIOC_PCOR maybe incorrect
  pinMode(15, OUTPUT); //C0
  pinMode(22, OUTPUT);  //C1
  pinMode(35, OUTPUT); //C2 //was 23
  pinMode(9, OUTPUT); //C3
  pinMode(10, OUTPUT); //C4
  pinMode(13, OUTPUT);  //C5
  pinMode(11, OUTPUT);  //C6
  pinMode(12, OUTPUT); //C7

  // configure the 8 port D output pins
  GPIO9_DR = 0xFF; //changed GPIOD_PCOR maybe incorrect
  pinMode(2, OUTPUT); //D0
  pinMode(14, OUTPUT);  //D1
  pinMode(7, OUTPUT); //D2
  pinMode(8, OUTPUT); //D3
  pinMode(6, OUTPUT); //D4
  pinMode(20, OUTPUT);  //D5
  pinMode(28, OUTPUT);  //D6 //was 21
  pinMode(5, OUTPUT); //D7
Note: the change from T3.5 to 4.1 will not be that easy as to just change the register. The problem is what pins are on
which IO port is different between the two boards.

Extract from my excel document on T4.1
1732805584644.png


So for example your first section: Note: my port numbers are shown 1-5 which is the ports in normal mode, 6-9 are mappings
to ports 1-4 to high speed. That is Port1 -> Port 6, Port 2 -> Port 7...

Been a while since I tried doing DMA to GPIO pins. Don't remember if DMA works in the high speed or not.
So your pin maping:
pin 15 -> Port 1 (Port 6) pin 19
22 -> Port 1 pin 24
35 -> Port 2 pin pin 28
9 -> 2:11
10 -> 2: 0
...

So they are not all on the same port, nor in consecutive order.
I have another page in the Excel document, which shows the pins in GPIO order.
PinNameGPIO
1AD_B0_021.02
0AD_B0_031.03
24/A10AD_B0_121.12
25/A11AD_B0_131.13
19/A5AD_B1_001.16
18/A4AD_B1_011.17
14/A0AD_B1_021.18
15/A1AD_B1_031.19
40/A16AD_B1_041.20
41/A17AD_B1_051.21
17/A3AD_B1_061.22
16/A2AD_B1_071.23
22/A8AD_B1_081.24
23/A9AD_B1_091.25
20/A6AD_B1_101.26
21/A7AD_B1_111.27
38/A14AD_B1_121.28
39/A5AD_B1_131.29
26/A12AD_B1_141.30
27/A13AD_B1_151.31
10B0_002.00
12B0_012.01
11B0_022.02
13B0_032.03
6B0_102.10
9B0_112.11
32B0_122.12
8B1_002.16
7B1_012.17
36B1_022.18
37B1_032.19
35B1_122.28
34B1_132.29
45SD_B0_003.12
44SD_B0_013.13
43SD_B0_023.14
42SD_B0_033.15
47SD_B0_043.16
46SD_B0_053.17
28EMC_323.18
31EMC_363.22
30EMC_373.23
2EMC_044.04
3EMC_054.05
4EMC_064.06
33EMC_074.07
5EMC_084.08
51EMC_224.22
48EMC_244.24
53EMC_254.25
52EMC_264.26
49EMC_274.27
50EMC_284.28
54EMC_294.29
29EMC_314.31

Hope that helps
 
Back
Top