Программный сброс 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
- Отправка значений из arduino uno в wemos d1 r1
- Соединение i2c для MCP4725 (Dac) с Esp8266 wemos d1 mini
- HD44780 отображает неправильно черный на синем вместо белого на синем
- ESP8266 не работает с MPU 6050 по проводной библиотеке и I2C
Вы уверены, что декодируете трассировку стека с помощью правильного двоичного файла?, @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