Teensy register inconsistencies

BitSeeker

Member
I have been doing some experiments reading Teensy registers on the Teensy 4.1 and I am a bit puzzled by the results. I a setting 8 pins simmultaneously as an 8-bit bus. If I set the pins outputs and set a value and then read back the DR register I get the correct result. LEDs attached to the pins light up in the correct pattern. However, if I try to read back with the Arduino digitalRead() function I get all zeros.

Similarly if I set all pins to input_pullup with 22k resistor value (also confirmed by LEDs lighting up dimly) I get all zeros when reading back both ways. A bit of googling suggests to use the PSR register for reading back when in input mode, but that seems to give the same result.

I have attached my code. What am I missing?

Code:
/***** NOTE: Teensy 4.1 board *****/
#define DIO1_PIN  23  /* GPIB 1  : AD_B1_09 : GPIO1_25 */
#define DIO2_PIN  22  /* GPIB 2  : AD_B1_08 : GPIO1_24 */
#define DIO3_PIN  21  /* GPIB 3  : AD_B1_11 : GPIO1_27 */
#define DIO4_PIN  20  /* GPIB 4  : AD_B1_10 : GPIO1_26 */
#define DIO5_PIN  19  /* GPIB 13 : AD_B1_00 : GPIO1_16 */
#define DIO6_PIN  18  /* GPIB 14 : AD_B1_01 : GPIO1_17 */
#define DIO7_PIN  17  /* GPIB 15 : AD_B1_06 : GPIO1_22 */
#define DIO8_PIN  16  /* GPIB 16 : AD_B1_07 : GPIO1_23 */

#define IFC_PIN   15  /* GPIB 9  : AD_B1_03 : GPIO1_19 */
#define NDAC_PIN  14  /* GPIB 8  : AD_B1_02 : GPIO1_18 */
#define NRFD_PIN  41  /* GPIB 7  : AD_B1_05 : GPIO1_21 */
#define DAV_PIN   40  /* GPIB 6  : AD_B1_04 : GPIO1_20 */
#define EOI_PIN   39  /* GPIB 5  : AD_B1_13 : GPIO1_29 */

#define SRQ_PIN   38  /* GPIB 10 : AD_B1_12 : GPIO1_28 */
#define REN_PIN   26  /* GPIB 17 : AD_B1_14 : GPIO1_30 */
#define ATN_PIN   27  /* GPIB 11 : AD_B1_15 : GPIO1_31 */

#define PIN_PULLUP_ENABLE   IOMUXC_PAD_PUS(1) | IOMUXC_PAD_PUE | IOMUXC_PAD_PKE | IOMUXC_PAD_DSE(6)
#define PIN_PULLUP_DISABLE  ~IOMUXC_PAD_PKE



/***** PIN to GPIO1 map*****/
uint8_t gpioDbBits[8] = {25, 24, 27, 26, 16, 17, 24, 25};
uint8_t gpioCtrlBits[8] = {19, 18, 21, 20, 29, 28, 30, 31};


/***** GPIB data and control buses ******/
const uint8_t databus[2][8] = {
                { DIO1_PIN, DIO2_PIN, DIO3_PIN, DIO4_PIN, DIO5_PIN, DIO6_PIN, DIO7_PIN, DIO8_PIN },
                { 25, 24, 27, 26, 16, 17, 22, 23 }
};

const uint8_t ctrlreg[2][8] = {
                { IFC_PIN, NDAC_PIN, NRFD_PIN, DAV_PIN, EOI_PIN, REN_PIN, SRQ_PIN, ATN_PIN },
                { 19, 18, 21, 20, 29, 28, 30, 31 }
};


/***** GPIO6 register value *****/
uint32_t gpioDbReg = 0;
uint32_t gpioCtrlReg = 0;

volatile uint32_t * iomuxcRegB1[] = {
  &IOMUXC_SW_PAD_CTL_PAD_GPIO_B1_00,
  &IOMUXC_SW_PAD_CTL_PAD_GPIO_B1_01,
  &IOMUXC_SW_PAD_CTL_PAD_GPIO_B1_02,
  &IOMUXC_SW_PAD_CTL_PAD_GPIO_B1_03,
  &IOMUXC_SW_PAD_CTL_PAD_GPIO_B1_04,
  &IOMUXC_SW_PAD_CTL_PAD_GPIO_B1_05,
  &IOMUXC_SW_PAD_CTL_PAD_GPIO_B1_06,
  &IOMUXC_SW_PAD_CTL_PAD_GPIO_B1_07,
  &IOMUXC_SW_PAD_CTL_PAD_GPIO_B1_08,
  &IOMUXC_SW_PAD_CTL_PAD_GPIO_B1_09,
  &IOMUXC_SW_PAD_CTL_PAD_GPIO_B1_10,
  &IOMUXC_SW_PAD_CTL_PAD_GPIO_B1_11,
  &IOMUXC_SW_PAD_CTL_PAD_GPIO_B1_12,
  &IOMUXC_SW_PAD_CTL_PAD_GPIO_B1_13,
  &IOMUXC_SW_PAD_CTL_PAD_GPIO_B1_14,
  &IOMUXC_SW_PAD_CTL_PAD_GPIO_B1_15
};


/***** Generate GPIO mask from assigned pin map *****/
uint64_t genGpioMask(const uint8_t buspins[][8], uint8_t bitmask) {
  uint64_t gpioreg = 0;
  for (uint8_t i=0; i<8; i++) {
    if (bitmask & (1 << i)) {
      gpioreg |= ( 1ULL << buspins[1][i] );
    }
  }
  return gpioreg;
}


void setPullupsMasked(const uint8_t bus[][8], uint8_t mask, bool enable){
  volatile uint32_t * regptr = 0;
  if (enable) {
    for (uint8_t i=0; i<8; i++){
      if ( mask & (1<<i) ){
        regptr = iomuxcRegB1[ (bus[1][i] - 16) ];
        *regptr = PIN_PULLUP_ENABLE;
      }
    }
    return;
  }
  for (uint8_t i=0; i<8; i++){
    if ( mask & (1<<1) ){
      regptr = iomuxcRegB1[ (bus[1][i] - 16) ];
      *regptr = PIN_PULLUP_DISABLE;
    }
  }
}


uint8_t readGpioVal(uint32_t reg){
  uint32_t regval = reg;
  uint8_t result = 0;
  for (uint8_t i=0; i<8; i++){
    if ( regval & (1UL<<databus[1][i]) ) result |= (1<<i);
  }
  return result;
}


uint8_t arduGpioRead(){
  uint8_t result = 0;
  for (uint8_t i=0; i<8; i++){
    Serial.print(digitalRead(databus[0][i]));
    if (digitalRead(databus[0][i])) result |= (1U<<i);
  }
  Serial.println();
  return result;
}



/***** Init GPIO registers *****/
void initTsyGpioMasks(){
  gpioDbReg = genGpioMask(databus, 0xFF);
}


void outputTest(){

  uint32_t gpioVal = genGpioMask(databus, 0xAA);
  uint8_t result = 0;
 
  Serial.println("\nOutput test:");

  // Prepare register bits
  GPIO6_GDIR |= gpioDbReg;    // Set pins as output
  GPIO6_DR &= ~gpioDbReg;     // Clear pins to zero

  // Set output value
  GPIO6_DR |= gpioVal;        // Set value 0xAA
  delay(200);

  // Read back output value
  result = readGpioVal(GPIO6_DR);
  Serial.print("Teensy Result:");
  Serial.println(result, HEX);
  result = arduGpioRead();
  Serial.print("Arduino Result:");
  Serial.println(result, HEX);

}


void inputTest() {

  uint8_t result = 0;

  Serial.println("\nInput test:");

  GPIO6_DR &= ~gpioDbReg;     // Clear pins to zero
  GPIO6_GDIR &= ~gpioDbReg;   // Clear = set as input
  setPullupsMasked(databus, 0xFF, true);

  // Read back output value
  result = readGpioVal(GPIO6_DR);
  Serial.print("Teensy Result DR:");
  Serial.println(result, HEX);
  result = readGpioVal(GPIO6_PSR);
  Serial.print("Teensy Result PSR:");
  Serial.println(result, HEX);
  result = arduGpioRead();
  Serial.print("Arduino Result:");
  Serial.println(result, HEX);
}


void setup() {
  Serial.begin(115200);
  while (!Serial && millis()<5000){};
  Serial.println("Teensy Hello :-)\n");

  initTsyGpioMasks();

//  Serial.print("Databus: ");
//  Serial.println(gpioDbReg, BIN);

//  Serial.print("Ctrlbus: ");
//  Serial.println(gpioCtrlReg, BIN);

  outputTest();

  inputTest();

  Serial.flush();
}


void loop() {

}



```
 
digitalRead() is intended to read the value of an input, not of an output. Have you tried digitalReadFast()? It's simpler and might work.
 
Thank you. Tried it. Same result unfortunately.

You have a point about input and I may need to review the premise of what I am trying to do. On Arduino AVR a digitalRead() will return a '1' for an output that is set, a pull-up being set as well as a high input - anything that makes the pin appear with a logic 'high'. However these are, in fact, all different things. I have a function that simply shows high or low for each pin of the bus and works for AVR, but I may need to re-think this and show the information differently.

In the meantime, I still need to figure out how to show whether pull-ups are on (reading the pull-up register?), and if the input is high or low so am curious how to do this on a Teensy.
 
I’m not at all expert on the I/O registers. Someone will answer this soon, I think. Have you searched with google? I usually have more look that way than with the forum search tool.
 
No worries. I am grateful for your reply. In answer to your question, yes I have searched with Google which I usually do before asking on a forum. I also found the list of registers and their descriptions in the 1062 manual which has provided some insight.

Unfortunately Google nowadays seems to use AI to respond. The reliability of the responses is often questionable so it can be more of a hindrance than a help sometimes. Some of the information it came back with was clearly incorrect as it referenced other platforms. It might sometimes help if there was a way to see the source reference it drew on but the little link icons at the end of the sentences seem to be a bit random and even unrelated? I have found at least some useful information that way though and will keep digging, but often I find at a certain point that forums like this are a good way to cut to the chase and get a more reliable and definitive answer.
 
I ran your program. This is the result I can see in the Arduino Serial Monitor.

Code:
Teensy Hello :-)


Output test:
Teensy Result:AA
00000000
Arduino Result:0

Input test:
Teensy Result DR:0
Teensy Result PSR:0
00000000
Arduino Result:0

I looked over your code briefly, but I didn't put in enough time to really dig into the details. I'm also not 100% clear on what the problem really is. I have a limited amount of time to invest in this sort of question, and to be a perhaps a bit blunt, this program is on the long and overly complex side.

But I do have a couple quick pieces of feedback. Not as good as a deep dive into your program, but maybe this can help anyway?

1: I don't see any calls to pinMode() in your code. I'd recommend composing another program which calls pinMode() to configure the hardware. Then try your comparison between digitalWrite / digitalRead and direct register access.

2: I also don't see any use of the MUX registers. Looks like only the PAD registers are used. Pay close attention to the SION bit, as the problem you've described "get all zeros when reading back both ways" might be related to needing the not-enabled-by-default input path.

If this isn't enough, please consider composing a much shorter and simpler test program. Use of 2D arrays and defines and lots of functions probably makes sense in a scope of a large program. But for a small example, it makes the time and effort to dive into the code just too much (at least for me... but since this thread hasn't received the sort of conversation we normally get when code is posted, I'm guessing others too).


sion_bit.png
 
To comment a bit further on the SION bit, because NXP's documentation only focuses on "what" but doesn't explain "why", my best guess is the NXP engineers put this feature into the chip for the sake of (perhaps insignificant) reduction in power consumption.

In static (not analog or analog-like stuff) CMOS circuit design, ignoring tiny leakage currents through transistors which are "off", all power dissipation comes from switching each wire from a 0 to 1, or 1 to 0. The wires and the transistor gates and transistor drain / source contacts all have tiny capacitance. Changing the digital value takes current to charge or discharge that capacitance. If the digital value doesn't change, then zero power is used.

NXP probably made the default to disconnect the input circuitry when you're using the hardware in modes that ought to not need it, like when the pin is configured as an output. That way, when you change the output pin with digitalWrite or with your own code which controls the DR or 3 special registers which modify DR, the input buffer and all the circuitry inside the chip doesn't have to also change digital value. All the input stuff stays in the state it has when the pin is digital low. It can't consume extra power, no matter how much activity you put onto the pin.

They made SION configurable, because clearly there are cases where you want to be able to get the input even though you're configured to some non-input mode.

In practice this power savings from disabling the input circuitry is probably insignificant. Especially in this sort of chip where the CPU clocks at 600 MHz and lots of things like USB consume plenty of power, clamping input stuff inside the chip to zero is unlikely to make much difference. But the reality of modern chip design is each peripheral gets a lot of work independent of the rest of the chip. They also tend to design for reuse in lots of other chips, so perhaps they will copypasta this GPIO pin design into other chips where very low power matters.

Of course this is all just guesswork and speculation on my part. I don't have any inside information from NXP.
 
For even more guesswork, NXP's IOMUX might have input synchronization circuitry to prevent metastability problems. If so, disabling input might do more than just turning off an input buffer or a logic gate to force it to zero. They might be automatically gating the clock to that synchronization circuitry, and setting SION keeps that circuitry clocked.

Maybe.

Again, just my speculation about what might actually be inside the chip. I'm sure NXP considers the actual design a trade secret. But that doesn't mean we can't wonder based on general knowledge of electronics...
 
I looked over your code briefly, but I didn't put in enough time to really dig into the details. I'm also not 100% clear on what the problem really is. I have a limited amount of time to invest in this sort of question, and to be a perhaps a bit blunt, this program is on the long and overly complex side.
Thank you for having a look anyway.

The two dimensional arrays was my way of trying to make sense of the Teensy pin to GPIO bits mapping. The below concentrates on just 3 pins which I set 1) output high, 2) output low and 3) input_pullup to illustrate.

If I run this on an AVR (using standard Arduino functions to set the pins) the result is:

Code:
P7: 1
P8: 0
P9: 1

This is with nothing connected to any pin. The digitalRead() function is therefore reporting a '1' because P9 is pulled up.

If I run this on the Teensy 4.1 I get:

Code:
P23: 1
P22: 0
P21: 0
PU: 1
IN: 0

So there is a difference in behavior and this happens regardless of whether I use Arduino functions or Teensy direct register manipulation to set the pins. On the Teensy I added an extra two lines to show the state of the pull-up and the DR register. The pull-up is evidently enabled, but digitalRead() reports 0. Adding a 3.3k resistor between GND and P2 yields the same result as expected. However, if I move the resistor from GND to 3V is returns:

Code:
P23: 1
P22: 0
P21: 0
PU: 1
IN: 1

So evidently GPIO6_DR reflects the actual input state, but on the Teensy the digitalRead() function does not report this and so is inconsistent with the AVR in this regard.

Its not a problem as such, just a matter of understanding the reason for these differences in behaviour. I observed something similar on the ESP32 when directly writing and reading registers. However, at least when set with pinMode(), digitalRead() returned a result that was consistent with the AVR architecture. On the other hand, when set directly with registers, the result was different. It turned out that pinMode() was setting the pin to a simultaneous "input-output" mode, but my code was setting just "output" mode. By setting setting the same mode using register access I was able to replicate the behaviour of digitalRead().

Its simply that the ESPs registers have a different arrangement to the classic Atmel AVR ICs. It is after all a different MCU architecture. I expect its the same with the Teensy. Whether digitalRead() provides the "correct" interpretation is debatable and probably an issue for Arduino.

However, it does make a difference to project code that depends on being able to correctly read the status of the pin and take an appropriate action or, at least, accurately show the status of the pin. It seems to me that on AVR digitalRead() is effectively OR'ing pullup with input state, whereas on the Teensy, pull-up and input states are treated as two separate things. It would appear that the sensible thing to do is not to mix direct register access with Arduino functions on non-AVR MCUs.

Code:
#ifdef __AVR__
#define GPIO_PIN1 7
#define GPIO_PIN2 8
#define GPIO_PIN3 9
#endif


#ifdef __IMXRT1062__
#define GPIO_PIN1 23
#define GPIO_PIN2 22
#define GPIO_PIN3 21
#endif


#define PIN_PULLUP_ENABLE   IOMUXC_PAD_PUS(1) | IOMUXC_PAD_PUE | IOMUXC_PAD_PKE | IOMUXC_PAD_DSE(6)
#define PIN_PULLUP_DISABLE  ~IOMUXC_PAD_PKE


void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);
  delay(200);

#ifdef __AVR__
  pinMode(GPIO_PIN1, OUTPUT);
  pinMode(GPIO_PIN2, OUTPUT);
  pinMode(GPIO_PIN3, INPUT_PULLUP);
  digitalWrite(GPIO_PIN1, HIGH);
  digitalWrite(GPIO_PIN2, LOW);
#endif

#ifdef __IMXRT1062__
  GPIO6_GDIR |= 1UL<<25;      // GPIO_PIN1 = output
  GPIO6_GDIR |= 1UL<<24;      // GPIO_PIN2 = output
  GPIO6_GDIR &= ~(1UL<<27);   // GPIO_PIN3 = input
  IOMUXC_SW_PAD_CTL_PAD_GPIO_B1_02 = PIN_PULLUP_ENABLE;
#endif


  Serial.print("P7: ");
  Serial.println(digitalRead(7));
  Serial.print("P8: ");
  Serial.println(digitalRead(8));
  Serial.print("P9: ");
  Serial.println(digitalRead(9));

#ifdef __IMXRT1062__
  Serial.print("PU: ");
  Serial.println( (IOMUXC_SW_PAD_CTL_PAD_GPIO_B1_02 & IOMUXC_PAD_PKE) ? 1 : 0 );
#endif

  Serial.flush();

}

void loop() {
  // put your main code here, to run repeatedly:
}

I have read, but not yet fully absorbed your two following posts so haven't experimented with SION yet, but in the meantime, hopefully this sketch makes it easier to understand what I was trying to convey.
 
Last edited:
If I run this on the Teensy 4.1 I get:

Code:
P23: 1
P22: 0
P21: 0
PU: 1
IN: 0

Maybe you ran a program different than the code you shared here?

The program you gave prints 4 lines, not 5. Those 4 lines begin "P7:", "P8:", "P9:", "PU:". I see some defines to use pins 21-23 rather than 7-9, but no code that could possibly print that specific output.

Look, I'd like to help, but let's talk about 1 test program. Why use different pins on different boards? Is simply using pins 7, 8, 9 on both boards ok just to keep this simple?
 
I tried writing a simple program using only the Arduino functions pinMode(), digitalWrite() and digitalRead().

When I run this on Arduino Uno R3, this is the result:

Code:
pin 7: 1
pin 8: 0
pin 9: 1

When I run it on Teensy 4.1, I see exactly the same result:

Code:
pin 7: 1
pin 8: 0
pin 9: 1

Here is the code I ran:

Code:
void setup() {
  Serial.begin(115200);
  delay(200);
 
  pinMode(7, OUTPUT);
  pinMode(8, OUTPUT);
  pinMode(9, INPUT_PULLUP);
  digitalWrite(7, HIGH);
  digitalWrite(8, LOW);
  delay(1);

  Serial.print("pin 7: ");
  Serial.println(digitalRead(7));
  Serial.print("pin 8: ");
  Serial.println(digitalRead(8));
  Serial.print("pin 9: ");
  Serial.println(digitalRead(9));
}

void loop() {
}


About this statement:
So evidently GPIO6_DR reflects the actual input state, but on the Teensy the digitalRead() function does not report this and so is inconsistent with the AVR in this regard.

First, using only the Arduino API functions, I can't reproduce the "inconsistent with the AVR" behavior you're saying exists.

Second, regarding "evidently GPIO6_DR reflects the actual input state", please refer to the reference manual part 12.6.1.2.2 on page 959. You will see a list of 4 conditions. In 1 of those 4 conditions "reading DR[n] returns the corresponding input signal's value". Two of the other 3 conditions are documented as "reading DR[n] returns the contents of DR[n]" and 1 of them says "reading DR[n] always returns zero".

Third, the GPIO peripheral provides PSR (pad status register) meant specifically for reading the actual state of the pin. This is the register digitalRead() uses. It's documented on pages 961-962. To read the pins, you probably should be using GPIO6_PSR.
 
I tried writing a simple program using only the Arduino functions pinMode(), digitalWrite() and digitalRead().

When I run this on Arduino Uno R3, this is the result:

Code:
pin 7: 1
pin 8: 0
pin 9: 1

I ran that and got the same result. Changed the pins to 23, 22 and 21 and got the same result.
So now I am rather confused, however, can agree that the Arduino function seems to work OK.

With regards to why I used pins 23, 22 and 21 on the Teensy, its because I have learned only enough so far to work with GPIO6. I haven't yet explored the other GPIOs so wasn't confident with using those yet. It has been my intention to figure out wich GPIO controls AD_B0_xx, B0_xx and B1_xx at some point so I will try and figure out which GPIO and registers control pins 7, 8 and 9 and re-write the code for those pins.

In the meantime this was the updated code:

Code:
#ifdef __AVR__
#define GPIO_PIN1 7
#define GPIO_PIN2 8
#define GPIO_PIN3 9
#endif


#ifdef __IMXRT1062__
#define GPIO_PIN1 23
#define GPIO_PIN2 22
#define GPIO_PIN3 21
#endif


#define PIN_PULLUP_ENABLE   IOMUXC_PAD_PUS(1) | IOMUXC_PAD_PUE | IOMUXC_PAD_PKE | IOMUXC_PAD_DSE(6)
#define PIN_PULLUP_DISABLE  ~IOMUXC_PAD_PKE


void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);
  delay(200);

#ifdef __AVR__
  pinMode(GPIO_PIN1, OUTPUT);
  pinMode(GPIO_PIN2, OUTPUT);
  pinMode(GPIO_PIN3, INPUT_PULLUP);
  digitalWrite(GPIO_PIN1, HIGH);
  digitalWrite(GPIO_PIN2, LOW);
#endif

#ifdef __IMXRT1062__
  GPIO6_GDIR |= 1UL<<25;      // GPIO_PIN1 = output
  GPIO6_GDIR |= 1UL<<24;      // GPIO_PIN2 = output
  GPIO6_GDIR &= ~(1UL<<27);   // GPIO_PIN3 = input
  GPIO6_DR |= 1UL<<25;        // GPIO_PIN1 set to high
  IOMUXC_SW_PAD_CTL_PAD_GPIO_B1_02 = PIN_PULLUP_ENABLE;
#endif

  Serial.print("P");
  Serial.print(GPIO_PIN1);
  Serial.print(": ");
  Serial.println(digitalRead(GPIO_PIN1));
  Serial.print("P");
  Serial.print(GPIO_PIN2);
  Serial.print(": ");
  Serial.println(digitalRead(GPIO_PIN2));
  Serial.print("P");
  Serial.print(GPIO_PIN3);
  Serial.print(": ");
  Serial.println(digitalRead(GPIO_PIN3));

#ifdef __IMXRT1062__
  Serial.print("PU: ");
  Serial.println( (IOMUXC_SW_PAD_CTL_PAD_GPIO_B1_02 & IOMUXC_PAD_PKE) ? 1 : 0 );
  Serial.print("DR: ");
  Serial.println( GPIO6_DR & (1U<<27) ? 1 : 0 );
  Serial.print("PSR: ");
  Serial.println( GPIO6_PSR & (1U<<27) ? 1 : 0 );
#endif

  Serial.flush();

}

void loop() {
  // put your main code here, to run repeatedly:
}

UPDATE: My apologies. I realised that I had made a really stupid mistake in my sketch 😐. I was setting the pins 23, 22 and 21 in the #defines but still printing 7,8 and 9. I have changed this to print GPIO_PIN1, GPIO_PIN2 and GPIO_PIN3 which would make more sense. I also added a line to print PSR so I can see what it does. The Arduino function results now make sense.
 
Last edited:
Back
Top