this is my first post here. I hope I chose the right place to post this.
I am currently working on my masterthesis in Embedded Systems Engineering and came upon a problem I wasn't able to solve by myself.
Board: ESP32 DevKit C V4
IDE: Arduino IDE
OS: Windows
on ESP: FreeRTOS
my Question:
Is there a way to use the I2C-interface inside a interrupt handler, without triggering the interrupt wdt when the slave-device takes too long to answer? Or at least be able to catch such errors without the esp crashing?
Background:
I'm developing a control unit, that consists of two PCBs (One Master and up to 16 Slaves).
Those PCBs comunicate via CANBUS.
The Master gets sensor values from all the slaves and regulates 8 seperate outputs accordingly.
As I didn't have enough pins free to control the outputs directly I chose to use a additional DAC (DAC5578SPW) which is controlled via I2C from my ESP32. This was an oversight because I did not think thoroughly enough about all the desired output signals. The hardware layout can not be changed in this iteration, as its already ordered and will arrive soon.
I need the capability to create a PWM Signal on at least 2 outputs. But as all the outputs are controlled via the I2C I cannot use the integrated PWM peripheral(bound to GPIO pins) and have to create the PWM somehow in software. I tried one approach using a task to create it. But here my dutycycle resolution is bound to my tick rate in FreeRTOS(I read that its not wise to reduce the tick rate below 1ms). If I want to have a resolution of 1% my PWM-frequency cannot be higher than:
100% * 1ms = 100ms --> 1/0.1 = 10Hz
This is way too far away from my goal, which is around 1kHz.
My Problem:
So i tried to use the hardware timers to create the PWM. This works greate until I want to change the output states using the I2C interface. As I don't have the hardware yet, the DAC does not exist of right now. So the wire.begin waits so long for a response that the interrupt wdt gets triggered. But even if the DAC exists I think I should not accept the possibility of any delay in communication to crash my ESP entirely. I tried to change the Interrupt wdt time by changing CONFIG_ESP_INT_WDT_TIMEOUT_MS without any effect on the outcome(the wdt gets triggered almost instantly {propably 300ms default} even if I change the timeout to 10000ms). Even if I try to disable the wdt (I know, not advised.) with CONFIG_ESP_INT_WDT nothing changes. Does someone know why nothing is changing? Something to do with the Arduino IDE?
The interrupt handler:
Code: Select all
//ISR handler of ESP-Timer for channel 0
void IRAM_ATTR channel0_timer_callback(void* args)
{
BaseType_t _woken_higher_prio_task = pdFALSE;
uint64_t _temp_interval = 0;
//Cast args to info-struct and safe it in internal struct
_struct_timer_info *info = (_struct_timer_info*) args;
//Enter cricital Section (disable othe rinterrupts)
// timer_spinlock_take(info->group); //Deprecated!
//Get interrupt status
uint32_t _intr_status = TIMERG0.int_st_timers.val;
//Trigger a counter value update to read out correct value
TIMERG0.hw_timer[info->timer].update = 1;
//Get counter value <-- not needed righ now
// uint64_t _counter_value = ((uint64_t) TIMERG0.hw_timer[info->timer].cnt_high) << 32 | TIMERG0.hw_timer[info->timer].cnt_low;
//Get semaphore for pwm-array
xSemaphoreTakeFromISR(_sema_output, &_woken_higher_prio_task);
//Check whether output is PWM
if(_output[0].state == PWM)
{
//Check the last state, skip when dc is 100%
if(_output[0].last_state_high && _output[0].dutycycle < 255)
{
//Switch output
mySerial0.print("pulling output low: ");
update_DAC_Channel(0, 0x00, true);
mySerial0.println(esp_timer_get_time());
//Update flag for status
_output[0].last_state_high = false;
//Calculate new interval (must be in 0.1us --> *10)
_temp_interval = (_output[0].t_period - _output[0].dc_time) * 10;
//Set new interval/alarm value
timer_group_set_alarm_value_in_isr(info->group, info->timer, _temp_interval);
}
else if (_output[0].dutycycle > 0)
{
//Switch output
mySerial0.print("pulling output high: ");
update_DAC_Channel(0, (uint8_t) _output[0].output_value, true);
mySerial0.println(esp_timer_get_time());
//Update flag for status
_output[0].last_state_high = true;
//Calculate new interval (must be in 0.1us --> *10)
_temp_interval = (_output[0].dc_time) * 10;
//Set new interval/alarm value
timer_group_set_alarm_value_in_isr(info->group, info->timer, _temp_interval);
}
}
//Channel has constant output
else if (_output[0].state == CONSTANT)
{
// update_DAC_Channel(0, (uint8_t) _output[0].output_value, true);
timer_pause(info->group, info->timer);
mySerial0.print("Channel0 is constant\n");
}
//Channel is off
else if (_output[0].state == OFF)
{
// update_DAC_Channel(0, 0x00, true);
timer_pause(info->group, info->timer);
mySerial0.print("Channel0 is off\n");
}
xSemaphoreGiveFromISR(_sema_output, &_woken_higher_prio_task);
//Reset interrupt flag
if(_intr_status & BIT(info->timer))
{
TIMERG0.int_clr_timers.t0 = 1;
//Setting new alarm value (only when autoload is disabled)
// timer_counter_value += (uint64_t) (TIMER_INTERVAL0_SEC * TIMER_SCALE);
// TIMERG0.hw_timer[timer_idx].alarm_high = (uint32_t) (timer_counter_value >> 32);
// TIMERG0.hw_timer[timer_idx].alarm_low = (uint32_t) timer_counter_value;
}
else
{
//Interrupt not triggered because of value reached...
}
//Reenable alarm
TIMERG0.hw_timer[info->timer].config.alarm_en = TIMER_ALARM_EN;
// timer_spinlock_give(info->group); //Deprecated!
}
Code: Select all
//Sends register update to DAC on specific channel, direct update of register optional.
//Value 0...255 (8bit), channels 0...7.
uint8_t update_DAC_Channel(uint8_t _channel, uint8_t _value, bool _update)
{
Wire.beginTransmission(DAC_ADR);
//Send command and channel for changing Channel output
//Update register directly
if (_update)
{Wire.write(_DAC_writeupdate_mask | _channel);}
//Don't update register directly
else
{Wire.write(_DAC_write_mask | _channel);}
//Send value for channel register
Wire.write(_value);
//Return errorcode
return Wire.endTransmission();
}
Code: Select all
pulling output high: Guru Meditation Error: Core 1 panic'ed (Interrupt wdt timeout on CPU1).
Core 1 register dump:
PC : 0x4008cca6 PS : 0x00060235 A0 : 0x8008b5de A1 : 0x3ffbf13c
A2 : 0x3ffb29b8 A3 : 0x3ffb81a4 A4 : 0x00000004 A5 : 0x00060223
A6 : 0x00060223 A7 : 0x00000001 A8 : 0x3ffb81a4 A9 : 0x00000018
A10 : 0x3ffb81a4 A11 : 0x00000018 A12 : 0x3ffc2f4c A13 : 0x00060223
A14 : 0x007bf3a8 A15 : 0x003fffff SAR : 0x0000000c EXCCAUSE: 0x00000006
EXCVADDR: 0x00000000 LBEG : 0x40086a78 LEND : 0x40086a8e LCOUNT : 0x00000000
Core 1 was running in ISR context:
EPC1 : 0x400e04fb EPC2 : 0x00000000 EPC3 : 0x00000000 EPC4 : 0x00000000
Backtrace: 0x4008cca3:0x3ffbf13c |<-CORRUPTED
Core 0 register dump:
PC : 0x4008ce3b PS : 0x00060035 A0 : 0x8008b207 A1 : 0x3ffbeacc
A2 : 0x3ffbf3a8 A3 : 0xb33fffff A4 : 0x0000abab A5 : 0x00060023
A6 : 0x00060021 A7 : 0x0000cdcd A8 : 0x0000abab A9 : 0xffffffff
A10 : 0x3ffc2d68 A11 : 0x00000000 A12 : 0x3ffc2d64 A13 : 0x00000007
A14 : 0x007bf3a8 A15 : 0x003fffff SAR : 0x0000001d EXCCAUSE: 0x00000006
EXCVADDR: 0x00000000 LBEG : 0x00000000 LEND : 0x00000000 LCOUNT : 0x00000000
Backtrace: 0x4008ce38:0x3ffbeacc |<-CORRUPTED
Is there any way to communicate via I2C inside a interrupt handler?
Or is there another more elegant way to produce the desired PWM signal on my DAC?
How would you advice me to solve this problem?
I'm pretty lost right now(even google can't help me right now) and would appreciate any advice from more experienced engineeres...
Thanks for your time in advance!
PS: If I accidently violated some etiquette in creating forum posts please let me know! I'm a newbie...