ESP32C3: hardware I2C conflict with timer

MCU: ESP32C3
Board: ESP32C3 Dev Module
Arduion: 2.3.2

Using an I2C DAC MCP4725 with timer, can't begin the timer. Seperately using the DAC or timer is OK. Maybe the timer0 is conflicted with I2C. But I can't specify the timer with the new timer API.

Only one parameter in the new timerbegin API:

timerBegin
This function is used to configure the timer. After successful setup the timer will automatically start.
hw_timer_t * timerBegin(uint32_t frequency);

  • frequency select timer frequency in Hz. Sets how quickly the timer counter is “ticking”.
    This function will return timer structure if configuration is successful. If NULL is returned, error occurs and the timer was not configured.

The code

#include "Wire.h"
#include "MCP4725.h"

#define I2C_CLOCK 800000
#define SDA_PIN 4
#define SCL_PIN 5
#define SERIAL_BAUD 115200
#define TIMER_INTERVAL_US 5000
#define PEAK_VALUE 4095
#define FREQUENCY 200.0

MCP4725 MCP(0x60);

bool connected = false;
 
hw_timer_t *Timer0_Cfg = NULL;

#define TIMTER_Basic_Freq 80*1000000 // change to 1000000 also not work 
#define TIMER_INTERVAL_US 50000

 
void IRAM_ATTR Timer0_ISR()
{
  if (connected) {

    MCP.setValue(2047);
  }
}
void setup()
{
  Serial.begin(SERIAL_BAUD);
  Serial.println(__FILE__);
  Serial.print("MCP4725_VERSION: ");
  Serial.println(MCP4725_VERSION);

  Wire.begin(SDA_PIN, SCL_PIN);
  Wire.setClock(I2C_CLOCK);

  if (MCP.begin() == false) {
    Serial.println("Could not find MCP4725");
    connected = false;
  } else {
    Serial.println("Successfully connected to MCP4725!");
    connected = true;
  }

  /* init timer */
  if ((Timer0_Cfg = timerBegin(TIMTER_Basic_Freq)) == NULL)
  {  
    Serial.println("timerBegin Failed!!");
  }
  timerAttachInterrupt(Timer0_Cfg, &Timer0_ISR);
  timerAlarm(Timer0_Cfg, TIMER_INTERVAL_US, true,0);
}
void loop()
{
    // Do Nothing!
}

I moved your topic to a more appropriate forum category @franklinhahaha6666.

The Nano ESP32 category you chose is only used for discussions directly related to the Arduino Nano ESP32 board.

In the future, please take the time to pick the forum category that best suits the subject of your question. There is an "About the _____ category" topic at the top of each category that explains its purpose.

Thanks in advance for your cooperation.

Do you need timer interrupts? Can't you just use a millis() based approach in loop()?

What are the symptoms? Does it not compile? Unexpected run time behaviour?

In case it does not compile, which version of the board package are you using?

Note:
No experience with ESP32 based boards but the above might be relevant to others that want to try to help.

Hi
I need timer driving interrupt for more precise timing control
The code can be compiled correctly, but the timer can't be initialized after the init of I2C

First, I'm going to guess that the problem isn't a hardware conflict but attempting to do I2C I/O inside an ISR.

Second, your timer interval is 50,000 microseconds. There are several ways you can achieve that period on an EPS32 without using timer interrupts.

Third, have you established that you can performed the desired MCP4725 operations at a rate of 20/second (given the speed that you're running the I2C Bus, the size of the data being transferred over the bus, and the MCP4725's conversion speed / latency)?

1 Like

Thank you very much for you great reply.

The key issue is we should not put any bus/time related operations in the timer interrupt attached functions.

revised code works

#include "Wire.h"
#include "MCP4725.h"

#define I2C_CLOCK 400000
#define SDA_PIN D4
#define SCL_PIN D5
#define SERIAL_BAUD 460800
#define PEAK_VALUE 4095
#define FREQUENCY 200.0
#define TIMTER_Basic_Freq 1000000 // change to 1000000 also not work 
#define TIMER_INTERVAL_US 100

#define outPIN D5

MCP4725 MCP(0x60);

bool connected = false;
bool timerTrigger = false;
 
hw_timer_t *Timer1_Cfg = NULL;



unsigned int value = 0;

 
void IRAM_ATTR Timer1_ISR()
{
  if (connected) {
    //float t = timerCount * TIMER_INTERVAL_US / 1e6;
    //int value = generateSineWave(t);
    timerTrigger = true;  
  }
}
void setup()
{
  Serial.begin(SERIAL_BAUD);
  delay(5000);
  Serial.println(__FILE__);
  Serial.print("MCP4725_VERSION: ");
  Serial.println(MCP4725_VERSION);

  //Wire.begin(SDA_PIN, SCL_PIN);
  Wire.begin();
  Wire.setClock(I2C_CLOCK);

  if (MCP.begin() == false) {
    Serial.println("Could not find MCP4725");
    connected = false;
  } else {
    Serial.println("Successfully connected to MCP4725!");
    connected = true;
  }

  pinMode(outPIN, OUTPUT);
  digitalWrite(outPIN, timerTrigger);

  /* init timer */
  if ((Timer1_Cfg = timerBegin(TIMTER_Basic_Freq)) == NULL)
  {  
    Serial.println("timerBegin Failed!!");
  }
  timerAttachInterrupt(Timer1_Cfg, &Timer1_ISR);
  timerAlarm(Timer1_Cfg, TIMER_INTERVAL_US, true,0);
}
void loop()
{
  if(timerTrigger)
  {
    MCP.setValue(value);
    Serial.println(value);
    digitalWrite(outPIN, timerTrigger);
    value++;
    if (value > PEAK_VALUE)
    {
      value = 0;
    }
    timerTrigger = false;
  }
}

Variables access by both ISR and non-ISR code must be declare volatile.

You should reset timerTrigger to false as the very first thing inside the if() statement.

1 Like

Hi, how about this? (just show the related code)
thank you very much

hw_timer_t *Timer1_Cfg = NULL;
portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED;
bool connected = false;
volatile bool timerTrigger = false;

void IRAM_ATTR Timer1_ISR()
{
  if (connected) {
    portENTER_CRITICAL_ISR(&timerMux);
    timerTrigger = true;  
    portEXIT_CRITICAL_ISR(&timerMux);    
  }
}
void setup()
{
  /* init timer */
  if ((Timer1_Cfg = timerBegin(TIMTER_Basic_Freq)) == NULL)
  {  
    Serial.println("timerBegin Failed!!");
  }
  timerAttachInterrupt(Timer1_Cfg, &Timer1_ISR);
  timerAlarm(Timer1_Cfg, TIMER_INTERVAL_US, true,0);
}
void loop()
{
  if(timerTrigger)
  {
    timerTrigger = false;
    MCP.setValue(value);
    digitalWrite(outPIN, timerTrigger);
    value++;
    if (value > PEAK_VALUE)
    {
      value = 0;
    }
  }
}

Should abort() after this or you will dereference a NULL pointer. Not sure if timerAttachInterrupt() and timerAlarm() do null-pointer checks

1 Like