Teensy 3.2 difference vs Uno: pinmode() detaches attachInterrupt()

Status
Not open for further replies.

Dave X

Well-known member
I copied some tachometer code from https://www.homemade-circuits.com/tachometer-using-arduino/ which had an attachInterrupt() before setting the pinMode() and discovered that on the Teensy 3.2, different from the Uno, setting the pinMode after the attachInterrupt seems to detach the interrupt.

After spending too long debugging my way to the difference and into some much improved different tachometer code, I found some discussion at https://github.com/arduino/ArduinoCore-sam/issues/26 and tried this code:

Code:
// Interrupt comparison between Uno & Teensy 3.2
// Based on https://github.com/arduino/ArduinoCore-sam/issues/26 code
// Expected output (obtained on Uno)
// Pin high, count=0
// Pin low, count=1
// Pin high, count=1
// Pin low, count=2
// BUT...
// On due (Teensy 3.2), count remains 0

int pin = 3;    // INT 1 on UNO
volatile int i = 0;

void test() { i++; }

void setup() {            
  Serial.begin(9600);   
  attachInterrupt(digitalPinToInterrupt(pin), test, FALLING);    // Uno: Counts up, Teensy: does not count
  pinMode(pin, OUTPUT);
  //attachInterrupt(digitalPinToInterrupt(pin), test, FALLING);    // Both Uno and Teensy count
}

void loop() {
  digitalWrite(pin, HIGH);
  Serial.print("Pin high, count="); Serial.println(i);
  delay(500);
  digitalWrite(pin, LOW);
  Serial.print("Pin low, count="); Serial.println(i);
  delay(500);
}


The github thread indicated a use-case for keeping interrupts while changing pinmode, but that wasn't what I needed. I would expect the Teensy behavior to be the same as the Uno, and to not have to pay attention to the order of calls on different architectures.

My kluged working tachometer code is below. It worked well enough to test the 5-650RPM range of speeds on a DIY wood lathe with a 12" plywood pulley driven by a treadmill motor.

Code:
// Tachometer from https://www.homemade-circuits.com/tachometer-using-arduino/
// But its interrupts were odd.
// This measures dT between pulses, debounced
// and reports when RPM changes

const byte millis_not_micros = 0;
const byte debug = 1; // should optimize out stuff
const int debounce = (2 * millis_not_micros?1:1000); // ms for settling. 
const int sensorPin = 14; // reed sensor
const int sensor_ground = 15 ; // Ground for pulling sensor low  
volatile int countsD = 0;

const float alpha = 0.5; // EWMA smoother
float rpm; 
unsigned long tnow = 0UL;
unsigned long prev = 0UL;
volatile unsigned long dt = 1;

void count_function()
{ /*The ISR function
    Called on Interrupt */
    tnow=millis_not_micros? millis() : micros();
    if ((tnow - prev) >= debounce){ 
       dt = tnow - prev;
       prev = tnow;
       countsD++;
    }
}
  
void setup() 
{
  Serial.begin(9600); 
  pinMode(sensorPin, INPUT_PULLUP); //Sets sensor as input
  pinMode(sensor_ground, OUTPUT); //Sets sensor as input
  attachInterrupt(digitalPinToInterrupt(sensorPin), count_function, FALLING); //Interrupts are called on Rise of Input
  digitalWrite(sensor_ground,0);
  rpm = 0;  
  count_function(); // initialize
}

void loop()
{
  static int lastcount;
  
  if (countsD != lastcount ) {
    lastcount = countsD;
    rpm = (1-alpha)* rpm+ alpha * 60.0 * (millis_not_micros?1000.0:1e6) /(dt);
  
    Serial.print("RPM=");
    Serial.print(rpm); //Calculated values are displayed
    if(debug){
      Serial.print(" Sensor: ");Serial.print(digitalRead(sensorPin));
      Serial.print(" tnow: ");Serial.print(tnow);
      Serial.print(" prev: ");Serial.print(prev);
    }
    Serial.print(" countsD: ");Serial.print(countsD);
    Serial.print(" dt: ");Serial.print(dt);;
    Serial.println();
  }
}
 
Perhaps this special case code for compatibility is causing trouble:

Code:
	if ((*config & 0x00000700) == 0) {
		// for compatibility with programs which depend
		// on AVR hardware default to input mode.
		pinMode(pin, INPUT);
	}

Not looked through/beyond that code - but assume the pin not yet set that defaults to a DISABLED state may trigger making the pin INPUT while attaching the interrupt - for AVR compatibility

Then changing the pin to OUTPUT and attaching the interrupt does a wholesale change to "portConfigRegister" losing the interrupt indication. Though in general it looked like that code was working around bits to some degree.

In any case it clearly indicates PJRC's great effort to suffer through 'compatibility' that either cannot always be done with the architectural differences - or just a case where it is better to do it as 'discovered' rather than adding yet another 'special case' for that AVR stuff that could end up just making yet another set of problems living forever like the above code where working around one issue causes another in some atypical case ... i.e. doing an attachInterrupt() on an OUTPUT ... which I've done before but just for an odd test case.
 
I saw that bit of code, but I think the difference is in the pinMode() code -- AVR's code twiddles 1-2 bits but ARM has more options and more bits to handle, and PJRC's code re-writes the register:

.../Java/hardware/arduino/avr/cores/arduino/wiring_digital.c
Code:
void pinMode(uint8_t pin, uint8_t mode)
{
        uint8_t bit = digitalPinToBitMask(pin);
        uint8_t port = digitalPinToPort(pin);
        volatile uint8_t *reg, *out;

        if (port == NOT_A_PIN) return;

        // JWS: can I let the optimizer do this?
        reg = portModeRegister(port);
        out = portOutputRegister(port);

        if (mode == INPUT) { 
                uint8_t oldSREG = SREG;
                cli();
                *reg &= ~bit;
                *out &= ~bit;
                SREG = oldSREG;
        } else if (mode == INPUT_PULLUP) {
                uint8_t oldSREG = SREG;
                cli();
                *reg &= ~bit;
                *out |= bit;
                SREG = oldSREG;
        } else {
                uint8_t oldSREG = SREG;
                cli();
                *reg |= bit;
                SREG = oldSREG;
        }
}




.../Java/hardware/teensy/avr/cores/teensy3/pins_teensy.c
Code:
void pinMode(uint8_t pin, uint8_t mode)
{
        volatile uint32_t *config;

        if (pin >= CORE_NUM_DIGITAL) return;
        config = portConfigRegister(pin);

        if (mode == OUTPUT || mode == OUTPUT_OPENDRAIN) {
#ifdef KINETISK
                *portModeRegister(pin) = 1;
#else
                *portModeRegister(pin) |= digitalPinToBitMask(pin); // TODO: atomic
#endif
                *config = PORT_PCR_SRE | PORT_PCR_DSE | PORT_PCR_MUX(1);
                if (mode == OUTPUT_OPENDRAIN) {
                    *config |= PORT_PCR_ODE;
                } else {
                    *config &= ~PORT_PCR_ODE;
                }
        } else {
#ifdef KINETISK
                *portModeRegister(pin) = 0;
#else
                *portModeRegister(pin) &= ~digitalPinToBitMask(pin);
#endif
                if (mode == INPUT) {
                        *config = PORT_PCR_MUX(1);
                } else if (mode == INPUT_PULLUP) {
                        *config = PORT_PCR_MUX(1) | PORT_PCR_PE | PORT_PCR_PS;
                } else if (mode == INPUT_PULLDOWN) {
                        *config = PORT_PCR_MUX(1) | PORT_PCR_PE;
                } else { // INPUT_DISABLE
                        *config = 0;
                }
        }
}
 
Status
Not open for further replies.
Back
Top