Программный сброс ESP8266 при использовании расширителя GPIO
В моем проекте я пытаюсь использовать 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 секунды для выполнения полного обновления дисплея).
На данный момент любое предложение приветствуется.
@Luca, 👍2
Обсуждение1 ответ
Лучший ответ:
В конце концов, ответом на проблему является частота обновления контактов, которые управляют дисплеем электронной бумаги:
Исходная библиотека 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 отправляло данные порциями, включая и отключая вывод выбора микросхемы только в начале и конце "транзакции".
Я планирую опубликовать обновленную библиотеку в какой-то момент, как только все будет сглажено, если это может кого-то заинтересовать, оставьте сообщение :)
- Проблемы с подключением I2C на ESP8266 — 12F, какие контакты использовать?
- esp8266-01 и Arduino UNO обмен данными через i2c
- ADS1115: измерение напряжения 5 В и питание от 3,3 В
- Варианты протокола для обмена данными между Arduino и ESP8266
- Использование SCL и SDA ESP8266-01 с GY-30 BH1750FVI для измерения люксов
- I2C Arduino ESP8266 Плохое чтение
- Отправка значений из arduino uno в wemos d1 r1
- HD44780 отображает неправильно черный на синем вместо белого на синем
Вы уверены, что декодируете трассировку стека с помощью правильного двоичного файла?, @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