ESP32 external interrupt latency

MiguelMagno
Posts: 2
Joined: Mon Aug 21, 2023 10:10 pm

ESP32 external interrupt latency

Postby MiguelMagno » Mon Aug 21, 2023 10:31 pm

Lately, I've been working on a project that consists of programming a Z80 with 8 address and data lines, the clock is done with ledc, it has two external interrupts on the Z80's WR and RD pins --> ESP32. The code is functional, but I can't work with frequencies (Z80 clock) higher than 10000Hz because the ISR somehow can't keep up with the pace of the Z80, my z80 can operate up to 20MHz. I'm sure the problem is not related to the ledc, as it can generate signals up to 40MHz. I would like a suggestion of change(s) in my code, I appreciate it :D

Code: Select all

#include <Arduino.h>
#include <esp_task_wdt.h>
#include "driver/ledc.h"

byte pinDados[8] = {13, 12, 14, 27, 26, 25, 33, 32};
byte pinEndereco[8] = {35, 34, 39, 36, 15, 2, 4, 16};
byte pinIORQ = 22;
byte pinRST = 21;
byte pinWR = 19;
byte pinRD = 18;
byte pinMREQ = 5;
byte pinZ80 = 23;

byte memory[0x100];

byte program[] = {
    0x21, 0x0a, 0x00, // ld hl, ??
    0x0e, 0x00,       // ld c, 0
    0x06, 0x26,       // ld b, 38
    0xed, 0xb3,       // otir
    0x76,             // halt
    '\x1B', '[', '3', '8', ';', '2', ';', '0', '7', '0', ';', '2', '0', '0', ';', '1', '8', '0', 'm',
    'O', 'i', ' ', 'Z', '8', '0', '!', '\n',
    'T', 'c', 'h', 'a', 'u', '!', '\n',
    '\x1B', '[', '0', 'm'};

const int timerPin = 17;

String portaZ80_buf;

#define RESETAR(estado) estado ? digitalWrite(pinRST, 0) : digitalWrite(pinRST, 1);

bool running = false;

void IRAM_ATTR intWriteCPU();
void IRAM_ATTR intReadCPU();
void IRAM_ATTR interrupcoes();
uint16_t readAddress();
void writeData(byte data);
byte readData();

void setDataDir(int dir)
{
  for (int i = 0; i < sizeof(pinDados); i++)
  {
    pinMode(pinDados[i], dir);
  }
}

uint16_t readAddress()
{
  uint16_t address = 0;
  for (int i = 0; i < sizeof(pinEndereco); i++)
  {
    address |= (digitalRead(pinEndereco[i]) & 1) << i;
  }

  return address;
}

void writeData(byte data)
{
  setDataDir(OUTPUT);
  for (int i = 0; i < sizeof(pinDados); i++)
  {
    digitalWrite(pinDados[i], (data >> i) & 1);
  }
}

byte readData()
{
  byte data = 0;
  setDataDir(INPUT);
  for (int i = 0; i < sizeof(pinDados); i++)
  {
    data |= (digitalRead(pinDados[i]) & 1) << i;
  }

  return data;
}

uint32_t oscilador = 10000; // Frequencia inicial do oscilador - 12543 Hz
uint32_t mDuty = 0;         // Valor calculado do ciclo de trabalho
uint32_t resolucao = 0;     // Valor calculado da resolucao

void setup()
{
  Serial.begin(115200);
  while (!Serial)
    ;
  memset(memory, 0, sizeof(memory));
  memcpy(memory, program, sizeof(program));
  for (int i = 0; i < sizeof(pinEndereco); i++)
  {
    pinMode(pinEndereco[i], INPUT);
  }
  writeData(0x00);

  pinMode(timerPin, OUTPUT);
  pinMode(pinRST, OUTPUT);
  pinMode(pinZ80, OUTPUT);
  pinMode(pinWR, INPUT_PULLUP);
  pinMode(pinRD, INPUT_PULLUP);
  pinMode(pinMREQ, INPUT_PULLUP);
  pinMode(pinIORQ, INPUT_PULLUP);
  digitalWrite(pinZ80, LOW);
  /*esp_timer_create_args_t timerConfig = {
      .callback = onTimer,
      .arg = NULL,
      .name = "clock_timer"};

  esp_timer_handle_t timer;
  esp_timer_create(&timerConfig, &timer);

  esp_timer_start_periodic(timer, 90);*/

  resolucao = (log(80000000 / oscilador) / log(2)) / 2; // Calculo da resolucao para o oscilador
  if (resolucao < 1)
    resolucao = 1; // Resoluçao mínima
  // Serial.println(resolucao);                                           // Print
  mDuty = (pow(2, resolucao)) / 2; // Calculo do ciclo de trabalho 50% do pulso
  // Serial.println(mDuty);                                               // Print
  ledc_timer_config_t ledc_timer = {};                      // Instancia a configuracao do timer do LEDC
  ledc_timer.duty_resolution = ledc_timer_bit_t(resolucao); // Configura resolucao
  ledc_timer.freq_hz = oscilador;                           // Configura a frequencia do oscilador
  ledc_timer.speed_mode = LEDC_HIGH_SPEED_MODE;             // Modo de operacao em alta velocidade
  ledc_timer.timer_num = LEDC_TIMER_0;                      // Usar timer0 do LEDC
  ledc_timer_config(&ledc_timer);                           // Configura o timer do LEDC
  ledc_channel_config_t ledc_channel = {};                  // Instancia a configuracao canal do LEDC
  ledc_channel.channel = LEDC_CHANNEL_0;                    // Configura canal 0
  ledc_channel.duty = mDuty;                                // Configura o ciclo de trabalho
  ledc_channel.gpio_num = timerPin;                         // Configura GPIO da saida do LEDC - oscilador
  ledc_channel.intr_type = LEDC_INTR_DISABLE;               // Desabilita interrupção do LEDC
  ledc_channel.speed_mode = LEDC_HIGH_SPEED_MODE;           // Modo de operacao do canal em alta velocidade
  ledc_channel.timer_sel = LEDC_TIMER_0;                    // Seleciona timer 0 do LEDC
  ledc_channel_config(&ledc_channel);                       // Configura o canal do LEDC

  interrupcoes();

  printf("\x1B[32mBem-vindo ao Subsistema Z80!\x1B[0m\n");

  printf("Pressione a tecla Enter para ligar o Z80...\n");
}

void loop()
{
  if (Serial.available())
  {
    char keyPressed = Serial.read();
    switch (keyPressed)
    {
    case 0x1B:
      if (running)
      {
        running = false;
        printf("Resetando Z80...\n");
        RESETAR(1);
        delay(1);
        RESETAR(0);
        running = true;
      }
      break;

    case 0x0D:
      if (!running)
      {
        running = true;
        printf("Ligando Z80...\n");
        digitalWrite(pinZ80, HIGH);
        RESETAR(1);
        delay(1);
        RESETAR(0);
      }
      break;

    case 0x70:
      printf("Imprimindo dados da porta...\n");
      printf(portaZ80_buf.c_str());
      portaZ80_buf = "";
      break;
    default:
      break;
    }
  }
}

void IRAM_ATTR interrupcoes()
{
  attachInterrupt(pinWR, intWriteCPU, FALLING);
  attachInterrupt(pinRD, intReadCPU, FALLING);
}

void IRAM_ATTR intWriteCPU()
{
  int MREQ = !gpio_get_level((gpio_num_t)pinMREQ);
  int IORQ = !gpio_get_level((gpio_num_t)pinIORQ);

  if (MREQ && running)
  {
    memory[readAddress()] = readData();
  }

  else if (IORQ && running)
  {
    portaZ80_buf += (char)readData();
  }
}

void IRAM_ATTR intReadCPU()
{
  int MREQ = !gpio_get_level((gpio_num_t)pinMREQ);
  // int IORQ = !gpio_get_level((gpio_num_t)pinIORQ);

  if (MREQ && running)
  {
    // ets_printf("%d\n", memory[readAddress()]);
    writeData(memory[readAddress()]);
  }
}

ESP_Sprite
Posts: 9730
Joined: Thu Nov 26, 2015 4:08 am

Re: ESP32 external interrupt latency

Postby ESP_Sprite » Tue Aug 22, 2023 1:23 am

Interrupt latency on the ESP32 is in the order of microseconds, unfortunately; there's a fair amount of prologue going on. One way to get around this is to write a high-level interrupt in assembly, but that is non-trivial and I don't know if the Arduino environment supports it.

MiguelMagno
Posts: 2
Joined: Mon Aug 21, 2023 10:10 pm

Re: ESP32 external interrupt latency

Postby MiguelMagno » Tue Aug 22, 2023 7:41 pm

Thanks for your answer! I would like an example of handling external interrupts in assembly :)

MicroController
Posts: 1708
Joined: Mon Oct 17, 2022 7:38 pm
Location: Europe, Germany

Re: ESP32 external interrupt latency

Postby MicroController » Wed Aug 23, 2023 9:17 am

Does the Xtensa gcc support __attribute__((interrupt))? If it does, that may allow to write bare interrupt handlers in C.

Who is online

Users browsing this forum: No registered users and 118 guests