4

An ATMEGA328-P chip is used with a toggle switch attached to pin 8 and a momentary switch attached to pin 9. My objective is to send the following information over serial:

  1. The status of pin 8, every second.
  2. Count the number of presses of pin 9, sending the value as the counter increments

I understand that to achieve point 2 I will need to utilise interrupts, but I have become stuck in the way I am trying to use them.

I am familiar with the following code's simple style:

  1. Within the loop...
  2. Poll the status of the input
  3. Send status over serial

Code A:

const int SWITCH_1 = 8;
const int READ_INTERVAL = 1000;

int val_s1 = 0;

void setup() {
    Serial.begin(9600);
    pinMode(LED_BUILTIN, OUTPUT);

    pinMode(SWITCH_1, INPUT);
    digitalWrite(S1, HIGH);
}

void loop() {
    val_s1 = digitalRead(SWITCH_1);
    Serial.print("S1:");
    Serial.print(val_s1);
    Serial.print("\n");

    digitalWrite(LED_BUILTIN, HIGH);
    delay(10);
    digitalWrite(LED_BUILTIN, LOW);
    delay(READ_INTERVAL);
}

When introducing the concept of interrupts, I have tried to retain the simplistic style of code A for testing purposes. The next snipped attempts to achieve the following:

  1. Attach an interrupt to pin 9 when the voltage rises
  2. No need for loop function (?)
  3. Within interrupt callback function...
  4. Increment the counter
  5. Send the counter value over serial

Code B:

void c1_rise();

const int COUNTER_1 = 9;
const int READ_INTERVAL = 1000;

volatile int val_c1 = 0;

void setup() {
    Serial.begin(9600);
    pinMode(LED_BUILTIN, OUTPUT);

    pinMode(COUNTER_1, INPUT_PULLUP);
    attachInterrupt(COUNTER_1, c1_rise, RISING);
}

void loop() {
}

void c1_rise() {
    val_c1 ++;
    Serial.print("C1:");
    Serial.print(val_c1);
    Serial.print("\n");
}

I've tried to learn where I'm going wrong, but I have become stuck and have decided to ask my questions here.

  • Observation: I expect to see some data sent over serial when the button is pressed, but actually nothing observable happens.
  • Problem 1: This might be because the interrupt isn't being attached at all.
  • Problem 2: This might be because I misunderstand the limitations of interrupt callback functions.

While trying to solve problem 1, Arduino documentation implies that to use the attachInterrupt function, parameter 1 must be an interrupt, from digitalPinToInterrupt, but if I use that function I receive the following error message: error: ‘digitalPinToInterrupt’ was not declared in this scope.

From reading up about how interrupts behave, I found problem 2: it seems that even though I have declared the integer as volatile, using Serial from within a callback function isn't allowed. This is the limit of my knowledge currently, I hope that someone can help me out.

2 Answers 2

8

You cannot use Serial inside an interrupt. Transmitting Serial relies on interrupts being available, and from inside an interrupt they aren't.

All Serial communication must be done from loop().

So you need to just count the switch toggles and check to see if that value has changed in your loop.

volatile uint32_t toggles = 0;
uint32_t old_toggles = 0;
uint32_t ts = 0;

void setup() {
    pinMode(2, INPUT_PULLUP);
    pinMode(3, INPUT_PULLUP);
    attachInterrupt(0, toggle, RISING);
}

void loop() {
    if (toggles != old_toggles) {
        old_toggles = toggles;
        Serial.print("C1: ");
        Serial.println(toggles);
    }

    if (millis() - ts >= 1000) {
        ts = millis();
        Serial.print("S1: ");
        Serial.println(digitalRead(3));
    }
}

void toggle() {
    toggles++;
}
3
  • May I ask what parameter 0 of attachInterrupt should be set to? Have you set to 0 arbitrarily? I am using 9 as that is the pin the switch is attached to, but using this method still isn't triggering the callback.
    – Greg
    Commented Nov 14, 2017 at 18:21
  • 1
    I have coded that for the Uno. Pin 2 is interrupt 0. Pin 3 is interrupt 1. Select the right pins for your chosen board.
    – Majenko
    Commented Nov 14, 2017 at 18:21
  • Your answer is straight to the point, thanks for making it simple for me to understand.
    – Greg
    Commented Nov 14, 2017 at 18:36
1

Problem

In essence, attachInterrupt uses a macro called ISR (stands for Interrupt Service Routine) to register a user provided function to handle an external interrupt event.

The issue you are encountering is that ISR also wraps the call to your function with special instructions to clear the global interrupt flag of your microcontroller, and set it again after your function returns. This is a good idea, and almost always what you want, because it prevents the possibility of infinitely recursive interrupts.

However, this behaviour will prevent Serial from working correctly, since it relies on interrupts being enabled to do its thing.

Solution

Although this is the default behaviour, there is nothing stopping you from enabling interrupts again inside your c1_rise function, as in:

void c1_rise() {
    val_c1 ++;
    interrupts();
    Serial.print("C1:");
    Serial.print(val_c1);
    Serial.print("\n");
}

Alternatively, but perhaps more involved, you can use the ISR macro directly by including avr/interrupt.h, and use ISR(vect, NO_BLOCK) to override the default behaviour.

Of course, you should be careful when overriding the default behaviour --- its there for a reason!

For more details about working with interrupts, I recommend reading this documentation.

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.