0

I want to create a simple program, that responds to me sending a character over the Arduino IDE console. I am using an arduino UNO. Here is my program:

volatile bool MeasReceived=false;

ISR(PCINT2_vect){  
  PCICR = 0b00000000;    // turn off port d
  MeasReceived=true;
}

void setup() {    
  Serial.begin(9600);
  Serial.flush();

  Serial.print("Hello PC");
  delay(1000);
  cli();      
  PCICR |= 0b00000100;    // turn on port d
  PCMSK2 |= 0b0000001;    // turn on pins PD0 , PCINT16
  sei();


}

void loop() {

  if(MeasReceived==true){
    MeasReceived=false;
    inByte = Serial.read();
     
    if(inByte=='A'){   //A: PC requests measurement data from Arduino
       Serial.print('B');
    }
    else{
      Serial.print(inByte);
    }
  }
  PCICR = 0b00000001; // re-enable port d
}

What I do is that I set the RX pin as a pin change interrupt. After that, I send a character through the console, for example 'A'. The ISR kicks in, firstly disabling the interrupt, and after that, the flag is set true. That way, I enter the part of the code where I do the Serial.read() command, to find what character was sent to me via the console.

The problem is that, no matter the character I send, the result is always 255. That means that the value of inByte, after the Serial.read() is always 255. What goes wrong?

EDIT: Original question answered by me below. However, one interesting thing occured in the new code posted below. If I put the Arduino to sleep (powerDown Mode), and try to wake it up with the serial interrupt, if I send only one character, then the arduino does wake up, but does not enter the ISR! If I send two characters, then the ISR is accessed and the flags set, with the program working fine. Here is the code, to reproduce the issue:

volatile bool MeasReceived=false;
uint8_t inByte;



ISR(PCINT2_vect){
  PCMSK2 = 0b00000000;    // turn off port d
  MeasReceived=true;
}

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);

 
  Serial.begin(9600);
  Serial.flush();
  Serial.print("Hello PC");
  delay(1000);
      
  cli();
  PCICR = 0b00000100;    // turn on port d
  PCMSK2 = 0b00000001;    // turn on pins PD0 , PCINT16
  sei();
}

void loop() {

  if(MeasReceived==true){
    MeasReceived=false;
    delay(2000);
    inByte = Serial.read();        
       
    if(inByte=='A'){   //A: PC requests measurement data from Arduino
       Serial.print('B');
    }
    else{
      Serial.print(inByte);
    }
    PCMSK2 = 0b00000001;
    
  }
  
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);    //Set power down mode
  sleep_bod_disable();  //disable brown-out detection
  digitalWrite(LED_BUILTIN, LOW); 
  sleep_enable();
  sleep_cpu();
  sleep_disable();
  digitalWrite(LED_BUILTIN, HIGH); 
  delay(1000);
  //int i=Serial.available();
  //Serial.print(i);
  delay(1000);     
  
}

So, by sending one character, the Arduino wakes up, the LED shines, and after the two seconds, Arduino goes back to sleep, never entering the ISR and as a result not setting the flag. Furthermore, having sent one character, if I uncomment the Serial.available() printing, it prints "0"!

If I send two characters, the Arduino wakes up, and the program works fine.

4

2 Answers 2

2

Your diagnostic about the interrupt handled too fast is correct. When a byte arrives at the serial port, the start bit will trigger the interrupt, but the byte is only available for reading once the UART receives the stop bit, which is about one millisecond later.

to disable/re-enable the pin-change interrupt, the right way seems to be to use the PCMSK register and not the PCICR.

The canonical procedure for enabling any edge-triggered interrupt is to first clear the interrupt flag, then set the interrupt enable bit. Note that the interrupt flag is cleared by writing a logic one to it. For the PCINT2 interrupt that would be:

PCIFR =  _BV(PCIF2);  // clear interrupt flag
PCICR |= _BV(PCIE2);  // enable interrupt

This being said, there is no point in using a pin change interrupt in the first place. You can instead use the canonical idiom

if (Serial.available() > 0) {
    char inByte = Serial.read();
    // ...
}

If you plan to sleep, you will likely want to do it with the serial port enabled, in order for it not to miss the start bit. This means you will use the IDLE sleep mode. Then, when the serial port receives a full byte, it will wake up the CPU with its own interrupt, so you do not have to take care of this yourself.


Edit 1: The simplest way to put the CPU to sleep until the next interrupt is received is to call sleep_mode() at the end of loop(), e.g.:

// Sleep until the next character is received.
set_sleep_mode(SLEEP_MODE_IDLE);
if (!Serial.available()) {
    sleep_bod_disable();
    sleep_mode();
}

There is, however, a race condition here: if the interrupt fires after you test Serial.available() but before going to sleep, you will sleep instead of handling the received character. The solution to this problem is to do the test with interrupts disabled and then enable them right before issuing the sleep instruction, as explained in the documentation of the avr-libc:

set_sleep_mode(SLEEP_MODE_IDLE);
cli();
if (!Serial.available()) {
    sleep_enable();
    sleep_bod_disable();
    sei();
    sleep_cpu();
    sleep_disable();
}
sei();

Edit 2: answering the questions in the comments.

  1. Pin change interrupt allows you to sleep deeper than IDLE, but if you do so, the UART will be stopped and it will miss the start bit, which could throw off its timing. I would rather stick with IDLE and use the power_*_disable() functions to turn off the unneeded peripherals.

  2. I have a theory, though I don't know if it's valid. Look at the schematic of the PCINT detection. If the clock is stopped, a change in the pin level will make the XOR gate switch to 1, which presumably wakes up the MCU, but that will not be recorded by the downstream flip-flops, which rely on the clock. If the pin returns to its original level before the clock restarts (which takes some time when coming out of PWR_DOWN), then the flip-flops never see the pin change condition, and the interrupt flag does not rise.

Edit 3: I made a test that confirms that, when waking up from PWR_DOWN by a pulse-triggered PCINT event, the ISR does not run if the pulse is too short. The setup involves two Arduinos:

  • the “tester” Arduino:
    • sends pulses of various lengths through digital 8 = PB0
    • forwards on TX the data received on RX
  • the “DUT” Arduino:
    • sleeps in PWR_DOWN mode
    • wakes up by pin change interrupt on digital 8 = PB0 = PCINT0
    • reports whether the ISR ran on the serial port

The connections are:

DUT   Tester    PC
───────────────────
        USB --- USB
GND --- GND
+5V --- +5V
  8 --- 8
 TX --- RX

The “tester” program:

const uint32_t PULSE_PERIOD = 500;  // pulses every 500 ms

void setup() {
    DDRB |= _BV(PB0);  // digital 8 = PB0 as output
    Serial.begin(9600);
    Serial.println("Tester: ready");
}

void loop() {
    // Forward the serial communication.
    while (Serial.available())
        Serial.write(Serial.read());

    // Periodically send a pulse.
    static uint32_t last_pulse_time;
    if (millis() - last_pulse_time >= PULSE_PERIOD) {
        last_pulse_time += PULSE_PERIOD;

        // Send a pulse of 2^n microseconds.
        static uint8_t n = 0;
        uint16_t length = 1U << n;
        Serial.print("Tester: sending pulse of ");
        Serial.print(length);
        Serial.println(" us");
        Serial.flush();
        uint32_t now = millis();
        while (millis() == now) ;  // wait for the tick
        PORTB |= _BV(PB0);
        delayMicroseconds(length);
        PORTB &= ~_BV(PB0);

        // Next pulse size.
        if (++n >= 16) n = 0;
    }
}

The “DUT” program:

#include <avr/sleep.h>

// The PCINT ISR only sets a flag.
volatile bool isr_ran;
ISR(PCINT0_vect) { isr_ran = true; }

void setup() {
    // Setup interrupt.
    PCMSK0 = _BV(PCINT0);  // sense PCINT0 = PB0
    PCIFR |= _BV(PCIF0);   // clear interrupt flag
    PCICR  = _BV(PCIE0);   // enable PCINT0 interrupt

    // Avoid other interrupts.
    TIMSK0 = 0;         // avoid timer interrupt
    PORTD |= _BV(PD0);  // pull RX high to prevent USART_RX interrupt
    set_sleep_mode(SLEEP_MODE_PWR_DOWN);

    Serial.begin(9600);
    Serial.println("DUT: ready");
}

void loop() {
    loop_until_bit_is_clear(PINB, PB0);  // wait for LOW input
    Serial.println("DUT: going to sleep");
    Serial.flush();
    isr_ran = false;
    sleep_mode();
    Serial.print("DUT: woke up, ISR ran: ");
    Serial.println(isr_ran ? "YES" : "NO");
}

And the results:

DUT: going to sleep
Tester: sending pulse of 1 us
DUT: woke up, ISR ran: NO
DUT: going to sleep
Tester: sending pulse of 2 us
...
Tester: sending pulse of 1024 us
DUT: woke up, ISR ran: NO
DUT: going to sleep
Tester: sending pulse of 2048 us
DUT: woke up, ISR ran: YES
DUT: going to sleep
Tester: sending pulse of 4096 us
DUT: woke up, ISR ran: YES
...

Conclusion: a pulse of 1024 µs or shorter wakes up the Arduino but the ISR does not run. If the pulse is 2048 µs or longer, the ISR does run.

4
  • Thank you for your answer! Two questions: 1) I was using serial.available, but pin change interrupt allows me to sleep deeper. Isn't that right? 2) I incorporated the correct way of enabling pin change interrupts, but the same problem persists. Sending one character wakes the uC, but the ISR is not accessed. Why is that?
    – NickG
    Commented Jun 20, 2020 at 13:15
  • To be clear, I do not understand how it is possible to wake up from sleep, and at the same time to not run the ISR code. That is what happens when I send one character from the Arduino IDE console.
    – NickG
    Commented Jun 20, 2020 at 13:39
  • @NickG: See updated answer. Commented Jun 20, 2020 at 14:25
  • Your explanation sounds good. I tested the code using the 'SLEEP_MODE_STANDBY' which only leaves the oscillator running, and it worked as expected. I will leave the question opened for a few days, in case somebody else would like to add or check what we discussed
    – NickG
    Commented Jun 20, 2020 at 14:57
0

After more testing, it seems that the problem was that when I sent the character over the console, the interval between the time t1 that the change was noticed and the ISR was fired, and the time t2 that the Serial.read() happened, was very short, almost immediate. As a result, the byte (character) that I had sent over the serial console was not yet ready to be read, and the Serial.read() command returned plain '1's.

Furthermore, to disable/re-enable the pin-change interrupt, the right way seems to be to use the PCMSK register and not the PCICR. The initially posted code, had the extra problem that, when PCICR was re enabled (set to 0x01), the ISR was immediately fired again, reading an extra character (obviously all '1's). That was solved by disabling/ re-enabling the pin change interrupt using PCMSK register. Here is the corrected code, which seems to work fine:

volatile bool MeasReceived=false;
uint8_t inByte;    

ISR(PCINT2_vect){   //ISR for pin-change interrupts occuring at PD0-PD7
  
  PCMSK2 = 0b00000000;    // turn off PD0 interrupt
  MeasReceived=true;      //Set char received Flag
  
}

void setup() {

  Serial.begin(9600);
  Serial.flush();
  Serial.print("Hello PC");
  delay(1000);
  
  cli();      
  PCICR = 0b00000100;    // turn on port d pin change interrupts
  PCMSK2 = 0b0000001;    // turn on pin PD0 , PCINT16
  sei();
}

void loop() {

  if(MeasReceived==true){  //If ISR fired
    MeasReceived=false;    //Reset the flag
    delay(1000);           //Wait a while for the character to be set. 1000ms may be too much
    inByte = Serial.read();  // Read the character that was sent
                        
    if(inByte=='A'){   //If the character was A, write B to console
       Serial.print('B');
    }
    else{   //else, write the ASCII code of the character received
      Serial.print(inByte);
    }
    PCMSK2 = 0b00000001;   //re-enable PD0 interrupt
  }
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.