port and toggle question T4

Status
Not open for further replies.

hanz

Member
Dear readers,

Just started with Teensy 4.
Trying to find out how I can toggle a pin like in 3.6 with
PORTC_PCR4


and how the mapping : PORT to pin?
Like in T3.6
PORTC_PCR4


Thanks,

hanz
 
Quick answer/example for T_3.6 after : pinMode(LED_BUILTIN, OUTPUT);

Either of these will toggle the current state of LED Pin #13:

#define qBlink() {GPIOC_PTOR=32;}

#define qBlink() (digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN) ))
 
Quick answer/example for T_3.6 after : pinMode(LED_BUILTIN, OUTPUT);

Either of these will toggle the current state of LED Pin #13:

#define qBlink() {GPIOC_PTOR=32;}

#define qBlink() (digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN) ))


Hello defragster,
Sorry I mean how to do it in T4:
GPIOC_PTOR=0x10;


hanz
 
The hardware register you seek is documented on page 1037 of the IMXRT1060 reference manual (rev 1, 12/2018).

https://www.pjrc.com/teensy/IMXRT1060RM_rev1.pdf

For the mapping, first check out the schematic to get the pad name.

https://www.pjrc.com/teensy/schematic.html

For example, if you choose pin 5, the pad name is EMC_08. Then to figure out which GPIO unit controls it, look up that pin in the IOMUX chapter. For the case of EMC_08, it's on page 434.

gpio3.png
(click for full size)

Unfortunately GPIO4_IO08 is not the end of the story. Each pin can be controlled by either of 2 different GPIO units. GPIO 1 to 5 are accessed through a (slow) peripheral bridge. GPIO 6 to 9 are accessed by the fast AHB bus. By default (due to the startup code), we use the fast ones on Teensy 4.0.

This detail is buried in the reference manual, on pages 371-372.

gpio2.png

GPIO9 is actually used.

So you could toggle the pin with this code:

Code:
void setup() {
  pinMode(5, OUTPUT);
}

void loop() {
  while (1) {
    GPIO9_DR_TOGGLE = 1 << 8;
    delayMicroseconds(1);
  }
}

I ran it here on a Teensy 4.0. Here's the waveform on pin 5:

file.png

However, if you want to toggle the pin at extreme speed, there is only more detail you need. By default, pinMode() configures the pin for slew rate limiting and moderate bandwidth. Normally that's a very helpful feature to reduce noise. If you want maximum speed, you need to write to IOMUXC_SW_PAD_CTL_PAD_GPIO_EMC_08, which is documented on page 569-570.

For example:

Code:
void setup() {
  pinMode(5, OUTPUT);
  IOMUXC_SW_PAD_CTL_PAD_GPIO_EMC_08 = 0xFF;
  noInterrupts();
}

void loop() {
  while (1) {
    GPIO9_DR_TOGGLE = 1 << 8;
  }
}

This will produce a 150 MHz waveform.

Sadly, my oscilloscope has only 200 MHz bandwidth (and passive probes rated for only 700 MHz), so when I try to measure this I get pretty much just the 150 MHz fundamental. Here's how it looks:

file2.png

In another thread, Greg at Stanford University used a 1.5 GHz bandwidth scope and active probe to measure these waveforms. Even with that amazing gear, good measurements are tough due to the length of a ground wire.

I should also point out, you can get this exact same 150 MHz waveform using digitalWriteFast().

Code:
void setup() {
  pinMode(5, OUTPUT);
  IOMUXC_SW_PAD_CTL_PAD_GPIO_EMC_08 = 0xFF;
  noInterrupts();
}

void loop() {
  while (1) {
    digitalWriteFast(5, HIGH);
    digitalWriteFast(5, LOW);
  }
}


But if you *really* want to mess with direct register access to the toggle register, there's all the info you'll need.
 
Hello Paul,
I got the 150Mhz output, but got one more question.
I'm using the Pit lifetime timer for counting, in the document I see snibbets for setting the pit. I see in one example PIT at 50mhz and an other one PIT is 100mhz, how can I set this?
I'm using a state machine and this counter to generate on different pins different duty cycle outputs, but they have to start all at the same time.

hanz
 
Hello Paul,
I got the 150Mhz output, but got one more question.
I'm using the Pit lifetime timer for counting, in the document I see snibbets for setting the pit. I see in one example PIT at 50mhz and an other one PIT is 100mhz, how can I set this?
I'm using a state machine and this counter to generate on different pins different duty cycle outputs, but they have to start all at the same time.

hanz

In startup.c T4 configures PIT and GPT to run at 24 mhz. To run PIT and GPT at 150mhz, in your setup() do
CCM_CSCMR1 &= ~CCM_CSCMR1_PERCLK_CLK_SEL;
(this will break IntervalTimer which expects 24mhz)
 
The hardware register you seek is documented on page 1037 of the IMXRT1060 reference manual (rev 1, 12/2018).

https://www.pjrc.com/teensy/IMXRT1060RM_rev1.pdf

For the mapping, first check out the schematic to get the pad name.

https://www.pjrc.com/teensy/schematic.html

For example, if you choose pin 5, the pad name is EMC_08. Then to figure out which GPIO unit controls it, look up that pin in the IOMUX chapter. For the case of EMC_08, it's on page 434.


...

Great Post Paul! This would make a great BlogPost on interpreting the RefMan. Maybe the "Teensy on Arduino IS Bare Metal" series!

Given that digitalReadFast and digitalWriteFast do it as well is nice - but if the BlogPost extended to Group/Parallel Read/Write like two recent threads. For instance your simple SDIO POGO test 6 pin running frequency

Teensy-4-0-First-Beta-Test :: Create 6 different frequencies on the SD card pins
Parallel-IO-is-it-possible
Teensy-3-5-or-Teensy-4-0-Parallel-Synchronous-DMA-output
 
Thank you all for the answers it helps me a lot.
If I may, I try to find in the T4 environment the isr routines for the PIT. In 3.6 I had this running but do not get it how to do it in 4.

Thanks hanz
 
Unfortunately GPIO4_IO08 is not the end of the story. Each pin can be controlled by either of 2 different GPIO units. GPIO 1 to 5 are accessed through a (slow) peripheral bridge. GPIO 6 to 9 are accessed by the fast AHB bus. By default (due to the startup code), we use the fast ones on Teensy 4.0.

This detail is buried in the reference manual, on pages 371-372.

View attachment 17697

GPIO9 is actually used.

...

But if you *really* want to mess with direct register access to the toggle register, there's all the info you'll need.

@Paul or other... I noticed the register you mentioned that changed using GPIO1->GPIO6

But What I have not noticed is anything in manual that describes the differences between the two. Or really for example describes GPIO6-9?
I am probably missing it, but ...

As for Toggle, At times I wish we would actually define: digitalToggleFast()... That uses the toggle register!
 
@manitou - Thanks, I remember reading posts as they went through, but did not remember the details on the speed...

Yep that document has lots more details. Maybe @PaulStoffregen may want to put link up in the documents section...

But I still wonder about some of the trade offs, and if in some cases we might want to make it more easily configurable.

If I understand some of this correctly (maybe big assumption). Now when have all possible IO pins routed to GPIO6-9, there is only one one IRQ (157) that is assigned to all 4 of these ports, or all 128 possible GPIO pins. So when a sketch does something like attachInterrupt(0, &my_isr, RISING);

All of these will funnel through one internal interrupt handler.
Code:
FASTRUN static inline __attribute__((always_inline))
inline void irq_anyport(volatile uint32_t *gpio, voidFuncPtr *table)
{
	uint32_t status = gpio[ISR] & gpio[IMR];
	if (status) {
		gpio[ISR] = status;
		while (status) {
			uint32_t index = __builtin_ctz(status);
			table[index]();
			status = status & ~(1 << index);
			//status = status & (status - 1);
		}
	}
}

FASTRUN
void irq_gpio6789(void)
{
	irq_anyport(&GPIO6_DR, isr_table_gpio1);
	irq_anyport(&GPIO7_DR, isr_table_gpio2);
	irq_anyport(&GPIO8_DR, isr_table_gpio3);
	irq_anyport(&GPIO9_DR, isr_table_gpio4);
}
Where when we are not using this Fast GPIO mode, there are several more Interrupts defined, especially on GPIO1
Where ISRs 72-79 Go directly with GPIO1 Pins 0-7 which in our case I think we only have Pin 0 (1.3) and Pin1(1.2)
But in addition:
80 - GPIO 1.0-15
81 - GPIO 1.16-31
82 - GPIO 2.1-5
...
89 - GPIO 4.16-31

So lots less pins to scan for changes. Also Some of these ranges actually only have one valid T4 pin on them
Example on 89 there is only 4.31 on pin 29... So again could be real fast.

Some only have 2: 83 (2.17, 2.16) on pins 7, 8
...

So again if I need/want fast ISRs, some of these pins may work great to simply set attachInterruptVector and bypass most of the overhead.

But again I am probably missing something obvious.

If not, wonder if we should allow some easy way to configure this...
 
What a great thread - only this week was I poring through the documentation to work out how to map the physical pins to the controlling registers, particularly with respect being able to handle fast interrupts on pin changes!
This has at least given me a way in even though my programming skills are somewhat basic.
I look forward to any follow-ups on this from Paul - if he has time!!
Cheers
Ed
 
@all... I thought I would sort of answer my self in wondering about ISR Speeds... So I mucked up a test program to get a general idea of differences in overhead...

So did a simple test program to see differences in ISR overhead. What it sort of shows to me, that one can still muck their way through and setup direct ISR if they want, but not sure worth the hassle. Setup where I did one pin as normal attachInterrupt() and had a jumper to another pin that I toggle N times... In the ISR I read pin state and echo it out to another pin. I then look to see delta time between the two pins changing state with Logic Analyzer. I also did with attachInterruptVector to different IO pin (0), swtiched that pin back to use GPIO1, and then tried as well..

Code sort of primitive, but shows some of the delta time differences. Would be better to hook up to external fast ISR generator like an encoder...
Code:
#define IRQ_PIN 0
#define ECHO_PIN 1
#define TRIGGER_PIN 2

#define IRQ2_PIN 3
#define ECHO2_PIN 4
#define TRIGGER2_PIN 5

#define CORE_PIN0_PINREG_SLOW  GPIO1_PSR
#define readFastIRQPin() ((CORE_PIN0_PINREG_SLOW & CORE_PIN0_BITMASK) ? 1 : 0)
uint32_t cycles_per_second = 100;  //
void setup() {
  while (!Serial && millis() < 5000) ;
  Serial.begin(115200);
  pinMode(IRQ_PIN, INPUT);
  pinMode(ECHO_PIN, OUTPUT);
  pinMode(TRIGGER_PIN, OUTPUT);
  digitalWrite(TRIGGER_PIN, LOW);
  Serial.printf("Test IRQ timing:\n    Pins IRQ:%d ECHO:%d TRIGGER: %d\n", IRQ_PIN, ECHO_PIN, TRIGGER_PIN);
  pinMode(IRQ2_PIN, INPUT);
  pinMode(ECHO2_PIN, OUTPUT);
  pinMode(TRIGGER2_PIN, OUTPUT);
  digitalWrite(TRIGGER2_PIN, LOW);
  Serial.printf("    Normal pins: IRQ:%d ECHO:%d TRIGGER: %d\n", IRQ2_PIN, ECHO2_PIN, TRIGGER2_PIN);
  delay(500);

  //---------------------------------------------
  // First lets setup pin 0 to slow mode and direct ISR...
  CCM_CCGR1 |= CCM_CCGR1_GPIO1(CCM_CCGR_ON);
  attachInterruptVector(IRQ_GPIO1_0_15, &pin_isr);
  NVIC_ENABLE_IRQ(IRQ_GPIO1_0_15);
  Serial.println("After Attach"); Serial.flush();
  // I think this will have GPIO1 handle its pin 3
  IOMUXC_GPR_GPR26 = 0xFFFFFFFF;
  Serial.println("After set IOMUXC"); Serial.flush();
  GPIO1_ICR1 = 0x00; // set to 0
  Serial.println("After set ICR1"); Serial.flush();
  GPIO1_GDIR &= ~0x08;  // Make sure set as input in GPIO1
  GPIO1_EDGE_SEL = 0x08; // set to 0
  GPIO1_ISR = 0xffff;
  Serial.println("After set ISR1"); Serial.flush();
  GPIO1_IMR = 0x08;
  Serial.println("After set GPIO"); Serial.flush();

  //---------------------------------------------
  // Next setup pin 3 to use attach interrupt in normal mode
  attachInterrupt(IRQ2_PIN, &pin2_isr, CHANGE);
}

volatile uint32_t irq_count = 0;

void pin_isr(void) {
  digitalWriteFast(ECHO_PIN, digitalReadFast(IRQ_PIN));
  irq_count++;
  GPIO1_ISR = 0x08; // clear the IRQ
  asm("dsb");
}

void pin2_isr(void) {
  digitalWriteFast(ECHO2_PIN, digitalReadFast(IRQ2_PIN));
  irq_count++;
}

void loop() {
  // put your main code here, to run repeatedly:
  Serial.printf("Enter cycles per second default(%d):", cycles_per_second);
  while (!Serial.available()) ;
  uint32_t cps = 0;
  int ch;
  while ((ch = Serial.read()) != -1) {
    if ((ch >= '0') && (ch <= '9')) cps = cps * 10 + ch - '0';
  }
  if (cps) {
    cycles_per_second = cps;
  }
  uint32_t delay_per_cycle = 1000000 / (cycles_per_second * 2);

  irq_count = 0;
  elapsedMicros em = 0;
  for (uint32_t i = 0; i < cycles_per_second; i++) {
    digitalWriteFast(TRIGGER_PIN, HIGH);
    delayMicroseconds(delay_per_cycle);
    digitalWriteFast(TRIGGER_PIN, LOW);
    delayMicroseconds(delay_per_cycle);
  }

  uint32_t delta_time = em;
  Serial.printf("\nDirect IRQs processed:  %u dt: %d calc:%d\n", irq_count,
                delta_time, delay_per_cycle * 2 * cycles_per_second);

  // Now do normal way

  irq_count = 0;
  em = 0;
  for (uint32_t i = 0; i < cycles_per_second; i++) {
    digitalWriteFast(TRIGGER2_PIN, HIGH);
    delayMicroseconds(delay_per_cycle);
    digitalWriteFast(TRIGGER2_PIN, LOW);
    delayMicroseconds(delay_per_cycle);
  }

  delta_time = em;
  Serial.printf("Normal IRQs processed:  %u dt: %d calc:%d\n", irq_count,
                delta_time, delay_per_cycle * 2 * cycles_per_second);

}

Note: The differences in elapsed micros times between direct and Normal, also gives an indication of how much more time was taken processing the ISR, as the normal loop is not running when the ISR code is running...
Code:
Test IRQ timing:
    Pins IRQ:0 ECHO:1 TRIGGER: 2
    Normal pins: IRQ:3 ECHO:4 TRIGGER: 5
After Attach
After set IOMUXC
After set ICR1
After set ISR1
After set GPIO
Enter cycles per second default(100):
Direct IRQs processed:  200 dt: 1000004 calc:1000000
Normal IRQs processed:  200 dt: 1000005 calc:1000000
Enter cycles per second default(100):
Direct IRQs processed:  200 dt: 1000004 calc:1000000
Normal IRQs processed:  200 dt: 1000005 calc:1000000
Enter cycles per second default(100):
Direct IRQs processed:  2000 dt: 1000040 calc:1000000
Normal IRQs processed:  2000 dt: 1000043 calc:1000000
Enter cycles per second default(1000):

Could show some Logic Analyzer output, to show some differences, but see differences like:
My direct way the delta time between the IO pin changing and my echo was something like: 55-66ns (looking at 500mhz so ...)
The way using attachInterrupt something like: 180-190ns

So again the simple answer is yes you can process pin change interrupts faster, but probably in majority of cases it aint worth it! As this is a really fast processor!
 
Status
Not open for further replies.
Back
Top