Программный сброс ESP8266 при использовании расширителя GPIO

esp8266 i2c io-expander

В моем проекте я пытаюсь использовать PCF8574 для управления некоторыми периферийными устройствами, поскольку ESP8266 не предлагает всех необходимых мне выводов GPIO, однако, похоже, у меня возникают проблемы на самом базовом уровне управления выводами дисплея электронной бумаги.

Дисплей электронной бумаги подключен к MOSI, MISO и SCL к "родным" выводам GPIO12, 13 и 14, и я хотел полагаться на PCF для управления остальными четырьмя выводами (CS, DC, RST, BUSY).

Я изменил исходную библиотеку так, чтобы она могла принимать произвольную абстракцию контакта, которая выглядит следующим образом:

class Pin {
public:
  Pin(void){};
  ~Pin(void){};

  virtual uint8_t read() = 0;
  virtual void write(uint8_t value) = 0;
};

и сначала реализовал его как ArduinoPin, который все, что он делает, это настраивает режим pin и вызывает digitalRead или digitalWrite, когда это необходимо.

При подключении EPD к реализации ArduinoPin с использованием D0, D1, D2, D4 в качестве выводов дисплей электронной бумаги работает корректно, поэтому я приступил к созданию другой реализации Pin с помощью расширителя GPIO.

После реализации расширителя GPIO и относительных выводов

gpio-расширитель.h

class GpioExpander {
public:
  GpioExpander(uint16_t address);
  GpioExpander();
  uint8_t read(uint8_t pin);
  void write(uint8_t pin, uint8_t value);

private:
  uint8_t buffer[1] = {0xFF};
  uint16_t address;
};

class GpioExpanderPin : public Pin {
public:
  GpioExpanderPin(GpioExpander *expander, uint8_t pinNumber);
  ~GpioExpanderPin(void);
  virtual uint8_t read();
  virtual void write(uint8_t value);

private:
  uint8_t myPin;
  GpioExpander *expander;
};

gpio-expander.cpp

GpioExpander::GpioExpander(uint16_t address) {
  this->address = address;
  buffer[0] = 0xFF;
  Wire.beginTransmission(address);
  Wire.write(buffer[0]);
  Wire.endTransmission();
}

GpioExpander::GpioExpander() : GpioExpander(0x20) {}

uint8_t GpioExpander::read(uint8_t pin) {
  write(pin, 1);
  Wire.requestFrom(address, 1);
  buffer[0] = Wire.read();
  return (buffer[0] >> pin) & 1;
}

void GpioExpander::write(uint8_t pin, uint8_t value) {
  if (value == 1)
    buffer[0] |= (1 << pin);
  else
    buffer[0] &= ~(1 << pin);
  Wire.beginTransmission(address);
  Wire.write(buffer[0]);
  Wire.endTransmission();
}

gpio-expander-pin.cpp

GpioExpanderPin::GpioExpanderPin(GpioExpander *expander, uint8_t pinNumber) {
  this->expander = expander;
  myPin = pinNumber;
}

GpioExpanderPin::~GpioExpanderPin(void) {}

uint8_t GpioExpanderPin::read() { return this->expander->read(myPin); }

void GpioExpanderPin::write(uint8_t value) {
  this->expander->write(myPin, value);
}

Эти реализации работают нормально, я протестировал все с помощью мультиметра, и я могу произвольно записать 1 и 0 на всех 8 контактах PCF, но когда я пытаюсь управлять четырьмя контактами электронной бумаги, ESP8266 сбрасывается, и я понятия не имею, почему. Я попробовал использовать декодер исключений, и появляется следующая трассировка стека:

Exception Cause: Not found

0x40202984: Twi::read_bit() at ??:?
0x402029db: Twi::write_byte(unsigned char) at ??:?
0x40202b94: Twi::writeTo(unsigned char, unsigned char*, unsigned int, unsigned char) at ??:?
0x40202bb9: Twi::writeTo(unsigned char, unsigned char*, unsigned int, unsigned char) at ??:?
0x40202cc0: twi_writeTo at ??:?
0x40201720: TwoWire::endTransmission(unsigned char) at ??:?
0x40105cfe: os_printf_plus at ??:?
0x40201748: TwoWire::endTransmission() at ??:?
0x402017f5: GpioExpander::write(unsigned char, unsigned char) at ??:?
0x40201794: GpioExpanderPin::write(unsigned char) at ??:?
0x402011f9: Epd::spiTransfer(unsigned char) at ??:?
0x40201238: Epd::sendData(unsigned char) at ??:?
0x40201385: Epd::clear() at ??:?
0x402010b6: setup at ??:?
0x4020583c: std::_Function_handler<bool (), settimeofday::{lambda()#1}>::_M_manager(std::_Any_data&, std::_Function_handler<bool (), settimeofday::{lambda()#1}> const&, std::_Manager_operation) at time.cpp:?
0x4020583c: std::_Function_handler<bool (), settimeofday::{lambda()#1}>::_M_manager(std::_Any_data&, std::_Function_handler<bool (), settimeofday::{lambda()#1}> const&, std::_Manager_operation) at time.cpp:?
0x4020583c: std::_Function_handler<bool (), settimeofday::{lambda()#1}>::_M_manager(std::_Any_data&, std::_Function_handler<bool (), settimeofday::{lambda()#1}> const&, std::_Manager_operation) at time.cpp:?
0x4020583c: std::_Function_handler<bool (), settimeofday::{lambda()#1}>::_M_manager(std::_Any_data&, std::_Function_handler<bool (), settimeofday::{lambda()#1}> const&, std::_Manager_operation) at time.cpp:?
0x40202158: loop_wrapper() at core_esp8266_main.cpp:?
0x40100e85: cont_wrapper at ??:?

(Не знаю, почему я не могу отобразить исходные строки, любое предложение в этом отношении приветствуется.)

В любом случае, по-видимому, проблема возникает при завершении передачи I2C на минимально возможном уровне в Twi::read_bit, который я включаю ради всех вас, пытающихся мне помочь

core_esp8266_si2c.cpp

bool Twi::read_bit(void)
{
    SCL_LOW(twi_scl);
    SDA_HIGH(twi_sda);
    busywait(twi_dcount + 2);
    SCL_HIGH(twi_scl);
    WAIT_CLOCK_STRETCH();
    bool bit = SDA_READ(twi_sda);
    busywait(twi_dcount);
    return bit;
}

Самое неприятное, что эта же настройка, когда она запрограммирована в Micropython, работает просто отлично (хотя я не могу использовать Micropython, потому что мне нужна вся оперативная память, которую я могу получить от ESP, чтобы рисовать картинки, и меня очень интересует общая производительность, которую я могу получить с помощью C ++).

У кого-нибудь была такая же проблема? Есть какие-нибудь советы о том, как исправить это поведение?

Обновление

С помощью некоторых экспериментов мне удалось воспроизвести проблему в меньшем масштабе. Следующий код также сбрасывает ESP8266:

void setup() {
  Wire.begin();
  GpioExpander expander;
  GpioExpanderPin pin(&expander, 0);
  while(true) {
    pin.write(0);
    pin.write(1);
  }
}

Похоже, что быстрые циклы включения-выключения приводят к сбою системы (именно это делает библиотека EPD при отправке данных, относительно быстром включении и выключении контактов для переключения между режимами и т.д.). Не уверен, как решить эту проблему, но я полагаю, что это может быть связано с библиотеками Twi на данный момент.

ПРАВКА 2

Добавление yield() к функции записи класса gpio expander устраняет проблему, но время выполнения простой очистки дисплея увеличивается до неприемлемых уровней (~ 3 секунды для выполнения полного обновления дисплея).

На данный момент любое предложение приветствуется.

, 👍2

Обсуждение

Вы уверены, что декодируете трассировку стека с помощью правильного двоичного файла?, @Sim Son

сборка, загрузка, запуск, сбой, копирование дампа стека, декодирование за один проход, чтобы декодер мог использовать файл elf, соответствующий загруженной ячейке, @Juraj

@SimSon да, я верю, что это так. Я создаю все с помощью platformio, который выводит конечный двоичный файл в .pio /build/ nodemcuv2 /firmware.elf, и это цель, которую я предоставляю декодеру исключений, который, кстати, является jar, найденным здесь: https://github.com/littleyoda/EspStackTraceDecoder, @Luca

Вы не опубликовали свой код о том, как вы создаете экземпляр GpioExpander. Но судя по тому, что ваша конструкция GpioExpander выполняет проводную транзакцию, это будет зависеть от того, где вы создаете экземпляр GpioExpander, если он создан до функции setup (), то Wire.begin() еще не установлен. Лучше создать метод GpioExpander:: begin () и переместить большую часть кода в конструкции в метод begin ()., @hcheung

@hcheung к сожалению, это ничего не меняет. Как я уже упоминал, расширитель gpio работает просто отлично, я могу произвольно включать и выключать контакты. Проблема возникает только при использовании его с дисплеем epaper., @Luca

Я отредактировал вопрос, потому что нашел способ воспроизвести проблему. Похоже, быстрые рабочие циклы приводят к сбою системы., @Luca

Добавление помощи yield(), чтобы «покормить собаку»... Возможно, вам следует подумать об использовании другого MCU, ESP8266 не имеет аппаратного i2c., @hcheung


1 ответ


Лучший ответ:

2

В конце концов, ответом на проблему является частота обновления контактов, которые управляют дисплеем электронной бумаги:

Исходная библиотека WaveShare (ссылка на которую указана в посте) выполняет следующие действия для отправки данных в EPD:

  • включить DC (чтобы сообщить EPD, что он получает ДАННЫЕ)
  • включить CS (чтобы указать EPD прослушивать входящие данные SPI)
  • отправить данные SPI
  • отключить CS (чтобы сообщить EPD, что все, что получено после этого, не входит в его компетенцию)

Обычно с помощью digitalWrite в arduino это происходит в течение ~10 мкс, и даже если мы повторим этот процесс 15 тысяч раз (именно столько байт поддерживает 4,2-дюймовый дисплей), мы потратим 0,15 секунды на обновление < b>половина содержимого дисплея (поскольку EPD имеет 2 банка памяти, которые необходимо записать).

При использовании расширителя GPIO среднее время отправки данных через I2C составляет около 50 мкс, а это означает, что для трехкратного включения/отключения контактов мы десятикратно увеличиваем время, которое требуется для обычной цифровой записи (~ 150 мкс). что соответствует 1,5 секундам для обновления половины экрана и примерно 3 секундам для полного обновления.

Во время этого цикла сторожевой таймер считает, что программа зависает, и сбрасывает чип. Вот почему при использовании yield обновление выполняется корректно.

Чтобы решить эту проблему, я работал над библиотекой EPD и сократил использование цифровой записи, создав "сеанс" своего рода: всякий раз, когда я начинаю записывать данные в цикле, я включаю вывод CS и отключаю его только в конце цикла, так что 100 мкс расходуются только на цикл передачи данных.

На самом деле именно это произошло в драйвере Micropython для дисплея электронной бумаги: соединение SPI отправляло данные порциями, включая и отключая вывод выбора микросхемы только в начале и конце "транзакции".

Я планирую опубликовать обновленную библиотеку в какой-то момент, как только все будет сглажено, если это может кого-то заинтересовать, оставьте сообщение :)

,