Reading MCP23017 from the task

Hello.
Hardware: esp32 + mcp23017
I have a problem to understand reading from the MCP23017 INCAP register from inside the task. I am trying to read this register to clear the interrupt so it is avaliable again.

My setup is 3LEDs and a button
Button is triggering the interrupt, than IRC notifies the task to ublock it. After unblocking the task it supposed to read from the register to clear the interrupt, than change state of LEDs and wait for notification again.
When i start up the program, i can use the button only once. Yet I know the read was done as I have log from Serial.print() of this register with the correct value.
When i read same register in the loop, the interrupt is reset and i am able to use button again, but still once between every loop cycle.
Code for reading the INTCAP register is same both in loop as in task.

I do not understand why do read from inside the task do not clear the interrupt.

Code:

#include <Arduino.h>
#include <Wire.h>

#define MCP_ADDR 0x20
#define INT_PIN 34
#define IODIRA 0x00
#define GPINTENA 0x04
#define DEFVALA 0x06
#define INTCONA 0x08
#define GPPUA 0x0C
#define GPIOA 0x12
#define INTCAPA 0x10
TaskHandle_t task;
enum State
{
    STATE_GREEN = 0,
    STATE_YELLOW = 1,
    STATE_RED = 2,
};
volatile State current_state = STATE_GREEN;

void writeRegister(uint8_t reg, uint8_t value);
uint8_t readRegister(uint8_t reg);
void interrupt_handler();
uint8_t state_to_map(State state)
{
    switch (state)
    {
    case STATE_GREEN:
        return 0b10000000;
    case STATE_YELLOW:
        return 0b11000000;
    case STATE_RED:
        return 0b11100000;
    default:
        return 0b10000000;
    }
}
void task1(void *param)
{
    while (1)
    {
        readRegister(INTCAPA);
        writeRegister(GPIOA, state_to_map(current_state));
        Serial.printf("Written state %d\n", current_state);
        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
        Serial.printf("reading from task INTCAPA: %d\n", readRegister(INTCAPA));
        Serial.println("Button pressed");
        current_state = (State)((current_state + 1) % 3);
    }
}

void setup()
{
    Serial.begin(9600);
    Wire.begin();
    writeRegister(IODIRA, 0b00000001);
    writeRegister(GPPUA, 0b00000001);
    writeRegister(GPINTENA, 0b00000001);
    writeRegister(DEFVALA, 0b00000001);
    writeRegister(INTCONA, 0b00000001);
    pinMode(INT_PIN, INPUT_PULLUP);

    attachInterrupt(digitalPinToInterrupt(INT_PIN), interrupt_handler, FALLING);
    xTaskCreate(task1, "LED State Machine", 2048, NULL, 1, &task);
}
void loop()
{
    Serial.printf("from loop: %d\n", readRegister(INTCAPA), BIN);
    vTaskDelay(1000 / portTICK_PERIOD_MS);
}

void IRAM_ATTR interrupt_handler()
{
    BaseType_t pxHigherPriorityTaskWoken = pdFALSE;
    vTaskNotifyGiveFromISR(task, &pxHigherPriorityTaskWoken);
    if (pxHigherPriorityTaskWoken > 0)
    {
        portYIELD_FROM_ISR();
    }
}

void writeRegister(uint8_t reg, uint8_t value)
{
    Wire.beginTransmission(MCP_ADDR);
    Wire.write(reg);
    Wire.write(value);
    Wire.endTransmission();
}
uint8_t readRegister(uint8_t reg)
{
    Wire.beginTransmission(MCP_ADDR);
    Wire.write(reg);
    Wire.endTransmission();
    Wire.requestFrom(MCP_ADDR, 1);
    return Wire.read();
}

Are you sure the interrupt is being triggered?
The first thing I'd do is get rid of this statement in loop() as it makes no sense to read in both the task and in loop:
Serial.printf("from loop: %d\n", readRegister(INTCAPA), BIN);

Then, put a Serial Print statement after the point where the task is supposed to become unblocked to see if it really does.

Thank you for response.
Yes, the print statment is there in the task as well:

void task1(void *param)
{
    while (1)
    {
        readRegister(INTCAPA);
        writeRegister(GPIOA, state_to_map(current_state));
        Serial.printf("Written state %d\n", current_state);
        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
        Serial.printf("reading from task INTCAPA: %d\n", readRegister(INTCAPA));
        Serial.println("Button pressed");
        current_state = (State)((current_state + 1) % 3);
    }
}

So IRS is triggered and task is unblocked, because i have "reding from task INCAPA: 128" log on serial monitor(so the read was also succesfull because i can see the number). Than i see "Written state" and after it the task it blocked again. But i cannot trigger the interrupt again. When i press button nothing happens, until read from loop happens, than i can casue interrupt with button again. Reading from the loop is not intended, its only for debugging and to show that interrupt is cleared when read called from loop, but not from task.

I don't know much about the MCP23017, but your task code is oddly different than the loop code. In particular, you're reading the INTCAPA register twice in the task ... once before it blocks waiting for the notification and once after it wakes up. Are you somehow disabling the MCP23017's interrupt function after one interrupt?

Anyway, the problem appears to have nothing to do with using a task. Rather, it appears to be with properly generating interrupts from the MCP23017.

Thats my mistake in copy-pasting the code. Second read was added when i tried to figure out what is wrong. Sorry. Originaly code was with only one read - the first one. Behavior is exactly the same.

void task1(void *param)
{
    while (1)
    {
        readRegister(INTCAPA);
        writeRegister(GPIOA, state_to_map(current_state));
        Serial.printf("Written state %d\n", current_state);
        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
        Serial.println("Button pressed");
        current_state = (State)((current_state + 1) % 3);
    }
}

MCP23017 says that INT pin keeps low (active) until the INTCAP is read. than INT pin goes HIGH(no active) and doing the interrupt is possible again. And this clearly work when i call read from the loop scope. I am sure this is caused by my code, just cannot find what it is.
Thank you anyway for replay, ill study more about interrupts in MCP and freeRTOS

Except that the loop() code does not depend on interrupts. It blindly reads the INTCAPA register whether there's data available or not and whether the MCP23017's interrupt output is active or not.

Okay, i think i found the problem. The read happens to early, and I am still pressing the button at this time, so interrupt is still saved there. Debouncing the button should solve it.
I added half a second wait before reading the INTCAP and than it works.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.