SpenceKonde / ATTinyCore

Arduino core for ATtiny 1634, 828, x313, x4, x41, x5, x61, x7 and x8
Other
1.53k stars 301 forks source link

Pin Change Interrupt not triggering when using PB2 (PCINT10) on Attiny167 (and Digispark PRO) #796

Closed Rimbaldo closed 10 months ago

Rimbaldo commented 10 months ago

Hi! I'm using Attinycore 1.5.2

I'm trying to trigger a Pin Change Interrupt on my Attiny167. When I use physical pin 9, which is PB1 (PCINT9), it works, I can trigger a Pin Change interrupt. It also works when using PB0, PCINT8.

But when I try to use pin 10, PB2 (PCINT10) with the same code, the Pin Change Interrupt never triggers

I tried using both a bare attiny167 and a Digispark PRO. It happens with both. (in the Digispark I use PB0, instead of PB1, because PB1 is the led.)

I tried also, with the Digispark PRO, using it's own core, Digistump. Things got worse. Neither PCINT9 nor PCINT10 triggered an Pin Change interrupt.

I can read the status of the pin 10 (PB2) using a digitalRead(). It reads normally and detects highs and lows.

But when changing from high to low or the opposite, the interrupt doesn't get triggered.

Am I doing something wrong (or so stupid I can't see...) or Is there anything (in the core) blocking PB2 of being used as a PIn Change Interrupt?

This is the code below:

void setup() {
  DDRB &= ~(1 << DDB1);  // define PB1 as input
  DDRB &= ~(1 << DDB2);  // define PB2 as input
  PORTB |= 1 << PB1;     // enable pull up resistor at PB1
  PORTB |= 1 << PB2;     // enable pull up resistor at PB2  
  initInterrupt();
}

void initInterrupt() {
  PCICR |= 1 << PCIE1;           // enable PCINT[8:15] pin change interrupt
 //PCMSK1 |= 1 << PCINT9;    // configure interrupt at PCINT9 PIN9 
 PCMSK1 |= 1 << PCINT10;  // configure interrupt at PCINT10 PIN 10
  sei();                                   // globaly enable interrupts
}

ISR(PCINT1_vect) {
// do something
}
SpenceKonde commented 10 months ago

I agree that that should work, especially if it works for other pins. I can't think of anything on PB2 that could cause a problem

I'm not sure what's up with the digispark core - I don't recommend touching that one, and don't particularly care how busted it is. But that they have a more widespread problem is strange. If it was PB3 or PB6 - I'd be like "You using a digispark pro? What do you expect those are the USB pins!" but you're not using PB2. And the result would be tons of unexpected triggers.

You're doing that in a way that leaves very little room for the core to have impacted it. I'd be interested to know how many pins you see this on.

You can verify 100% that the core is not at fault, by overriding main to skip the init code (there will be no millis, PWM, or ADC, of course - nothing will work) all you can really do to confirm the ISR fired is toggle an LED or something (writing 1 to the PINx register bit acts as a strobe bit to toggle the pin if you didn't know) - you could put what you have in setup now into main(), thus overriding the stock main. When you do this, the sei() which right now does nothing because interrupts are already enabled by init(), will be essential.

(note: for context, stock main is essentially

This code below expects a LED to be on some pin on PORTA.

It should give a 1 second flash at startup (assuming it's set to default speed).

#include <util/delay.h>
int main() {
  if (MCUSR == 0 && GPIOR0 == 0) {
     /* If this triggers and you get this fast blink until reset when you trigger the interrupt, the wrong interrupt 
       * is being triggered- namely one you haven't defined */ 
    /* Some versions of the bootloader stash MCUSR in GPIOR0 so checking both*/
     DDRA |= 1 << (bit on PORTA connected to an LED);
     while(1) {
       PINA |= 1<< LEDBIT;
      _delay_ms(100);
      PINA |= 1<< LEDBIT;
      _delay_ms(100);
    }
  } else {
    MCUSR = 0; 
  }
  PORTB |= 1 << PB1;     // enable pull up resistor at PB1
  PORTB |= 1 << PB2;     // enable pull up resistor at PB2  
  initInterrupt();
  // Note that pins have a default state: DDRx = 0, PORTx = 0. So if you're running first thing, you just need to set 
  // the pins high to get input pullup on classic AVR. They're already set as inputs. 
  // set the LED pin output, and do 1 sec on 1 sec off on startup. 
  DDRA |= 1 << (bit on PORTA connected to an LED);
  PINA |= 1<< LEDBIT;
  _delay_ms(1000);
  PINA |= 1<< LEDBIT;
  _delay_ms(1000);
  while(1); // no equivalent to loop
  return 0; //needed to keep the compiler happy - Blame the C standard group not me. 
}
void initInterrupt() {
  PCICR |= 1 << PCIE1;           // enable PCINT[8:15] pin change interrupt
 PCMSK1 |= 1 << PCINT10;  // configure interrupt at PCINT10 PIN 10
  sei();                                    // now this actually does something
}

ISR(PCINT1_vect) {
  PINA |= 1 << (bit on PORTA with an LED used above); 
}

I'd be interested to hear two things: 1: Is it a digispark pro board or a vanilla board? 2. What is the symptom? Does it fail to do anything? Reboot unexpectedly? The results of this test:

  1. Starting from sketch above, go through through all the available pins on PORTB: PB0, PB1, PB2, PB3, PB4, PB5 PB6.
  2. For PB3 and PB6, while running the sketch, if it's a digispark pro anything that does VUSB on those pins, be sure to either plug it into a "non-smart" (no fast charge 500mA) USB charger or use a charging only cable, as neither of these should molest pins connected to what is normally the data lines.
  3. For PB4 and PB5, if you are using an external crystal, skip these! The chip will hang when you try to trigger the interrupt because you just took away the clock source ;-)
  4. Move the led over to a pin on port B, repeat test across the 8 pins of port A (none of which require special treatment).
  5. Post a list of which ones work, which ones dont.

The code above has a "dirty reset detector" which will put it into a state where it continually flashes the LED much faster if a dirty reset is seen so that you can catch that case too. <util/delay.h> uses cyclecounting loops and hence works without init() (as long as your running at the default speed of the selected clock source, without prescaling not set by fuses, that is, the 2MHz and 4 MHz internal options that may be offered on the old release and are now offered on the new on and it also blinks the light 1 sec on 1 sec off on startup so you can see that the LED works and see any reboots that are somehow not dirty.

(A dirty reset is when program execution arrives at the reset vector without a hardware reset having occurred. This can happen from three things not involving overclocking or undervoltaging. It is the THIRD case that is relevant to you, and is BOLDED so you don't need to read all this shit

  1. from an idiot jumping to 0x0000 (never do that)
  2. if you smash the stack and overwrite the function return pointer with garbage; when you return from the function, you will land at some random place on the flash and execution will continue from there, bouncing around the code like a ball in a pinball machine, each return instruction sending it off somewhere else wacky, but likely eventually it would either land on 0 (a common value to write, so more likely than most numbers to be what you smash the stack with), or after the end of the written area of the flash; in that case, it either runs the bootloader (but most likely misses all entry conditions, at least if you're doing dirty reset detection and hence runs the sketch immediately), or if not using a bootloader, skids along the 0xFFFF's of empty flash (treated as sbrs r31, 7), leaving a 50:50 chance of skipping the reset vector or not. If it skips it, it will instead run the next vector, which you don't have define, so see below), either way bootloader if any runs, you miss the entry conditions and end up jumping to application. 3.When an interrupt routine fires and is enabled but thee is no handler, the generic BADISR handler is called. Guess what it does? Jumps to 0x0000, which jumps to either a bootloader that will miss it's entry condititions and run the app or directly to the app).

All this happens without a reset occurring but all initialization code in the core assumes that the chip is in it's poweron reset state when the sketch starts. This is the mechanism by which array overruns lead to both erratic behavior and bootloops/hangs, and I'm convinced that it accounts for the vast majority (on the modern AVR cores, where the consequences are even worse in the interrupt case (the interrupt system is a little different, in mostly good ways, but it has a sideffect that that BADISR jump from interrupt context to reset makes it impossible to execute any other interrupts until hardware reset because the CPU thinks it's still in one), I implement dirty reset trap that forces reset if it sees one in the core and have it on and non-obvious how to disable (intentionally, because most people shouldn't be disabling it)).)

Rimbaldo commented 10 months ago

Hi! Thanks for all info!

I tested last night your sketch in a bare attiny167 and it worked perfectly in all ports. With a negative wire I triggered each PCINT pin and all of them (in each setting) could turn the led on and off.

I was puzzled but then when checking my sketch, I had removed a library that deals with blink patterns (and this library declare the led's pins internally as outputs) and I forgot to declare myself the led pins as outputs..

But even then, I had managed to make the PCINT10 flash a led, but it was strange... Like the trigger, the interrupt, kept being constantly fired, non-stop

I was using analogWrite(12, 50) - red led, and analogWrite(13,50) - green led, to give me the output if the cable was connected or disconnected. That's the reason for my PCINT interrupt. To check a cable status.

But the led, instead of a faint glow, was turning on and off really quickly. And in my previous versions of my sketch, when I used analogWrite, even to a value of 1, the leds didn't blink (to the human eye perception). They had a faint glow.

Now they were "blinking". So I thought the interrupt had a problem..

The thing is I had been running the Attiny at 1Mhz before... For these tests you asked me to run I let the Attiny run at 4Mhz...

I saw written somewhere here in the ISSUES someone asking about the PWM frequency... and you wrote something about that PWM frequencies would be different at each speed.

Then, testing the different speeds, I came to realize that, in 1Mhz, 2Mhz and 8Mhz, pwm on the led works normally, with no flicker to the human eye. But only in 4Mhz speed the pwm frequency must be so low that we can actually see the led blink between on and off.

So I changed to 1Mhz and compiled my code and now it works correctly. The led now has a constant faint glow (PWM in higher frequency). The interrupts are firing correctly. I was mistaken thinking it was firing non-stop. And it was probably not triggering sometimes because I had not declared the leds as outputs when I removed that blink library... (I guess)

I'm so sorry to have bothered you with this ... and thanks for all your help.. Without it I couldn't have found out my mistakes..

Best Regards, Rodrigo

SpenceKonde commented 10 months ago

Hmmmm, I ought to take a look at that. It should never be that slow by default.

SpenceKonde commented 10 months ago

OH! Durrrr...

My sketch overrides main, and to make sure it is able to start at all voltages, has to default to prescale by 8. So the PWM frequency was 1/4th what it should have been. The x7 is actually better able to keep within 500-1khz than most parts.

SpenceKonde commented 10 months ago

2.0.0 now has more accurate FPWM (target is 1k, we can always stay within a factor of 2 of that, and the part specific documentation for all parts now lists what speeds are used.