Подключение 3 светодиодов OLED 0,91 дюйма (с использованием 8-канального мультиплексора TCA9548A) и 1 прозрачного OLED-дисплея SPI диагональю 1,51 дюйма
Как следует из заголовка, у меня есть Arduino Nano, подключенный к мультиплексору (который подключен к 3 x 0,91 дюймовым OLED-дисплеям) и 1,51-дюймовый OLED-дисплей, подключенный через SPI.
Проблема в том, что они просто отказываются работать одновременно — оба работают, я тестировал их независимо. Однако при написании кода для обоих вторая часть, требующая инициализации, всегда даёт сбой. Если я сначала инициализирую OLED 1,51, он работает, а 3-кратный мультиплекс 0,91 — нет, и наоборот. Вот мой код:
#include <Wire.h>
#include <SPI.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define SCREEN_WIDTH_T 128
#define SCREEN_HEIGHT_T 64
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 32
#define OLED_RESET -1
#define OLED_ADDR 0x3C
#define TCA9548A_ADDR 0x70
#define OLED_MOSI 11
#define OLED_CLK 13
#define OLED_DC 8
#define OLED_CS 10
#define OLED_RESET_SPI 9
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
Adafruit_SSD1306 displayO(SCREEN_WIDTH_T, SCREEN_HEIGHT_T, OLED_MOSI, OLED_CLK, OLED_DC, OLED_RESET_SPI, OLED_CS);
void tcaSelect(uint8_t i) {
if (i > 7) return;
Wire.beginTransmission(TCA9548A_ADDR);
Wire.write(1 << i);
Wire.endTransmission();
}
void setup() {
Wire.begin();
Serial.begin(9600);
delay(100);
if (!displayO.begin(SSD1306_SWITCHCAPVCC, 0x3D)) {
Serial.println(F("SSD1306 allocation failed for SPI OLED"));
while (1);
}
displayO.display();
delay(200);
displayO.clearDisplay();
displayO.drawPixel(10, 10, SSD1306_WHITE);
displayO.display();
delay(200);
// Initialize I2C displays on the multiplexer
for (uint8_t i = 0; i < 3; i++) {
tcaSelect(i);
delay(100);
if (!display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDR)) {
Serial.print(F("SSD1306 allocation failed for display on channel "));
Serial.println(i + 1);
} else {
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 0);
display.print(F("Hello World\nScreen "));
display.print(i + 1);
display.print(F("/3"));
display.display();
delay(1000);
}
}
// Initialize SPI OLED display
}
void loop() {
displayO.clearDisplay();
displayO.setTextSize(1);
displayO.setTextColor(SSD1306_WHITE);
displayO.setCursor(0, 0);
displayO.println(F("Hello, world!"));
displayO.display();
delay(2000);
}
В этом примере OLED-дисплей 1.51 (display0) инициализируется нормально, однако остальные 3 дисплея дают сбой. Если я поменяю местами часть кода (поместив код display0 под кодом display), то 3 OLED-дисплея будут работать, а display0 — нет.
Я пытаюсь решить эту проблему уже много лет и понятия не имею, в чем причина сбоя. Пожалуйста, спасите меня.
@anton koluh, 👍3
Обсуждение1 ответ
Итак, вы подтвердили ошибку инициализации в комментарии, в котором говорилось:
Да, я получаю сообщения «Ошибка выделения» при попытке инициализации
Когда я создаю ваш код, используя то, что у меня есть для среды:
Wire 1.0 /home/user/.arduino15/packages/arduino/hardware/avr/1.8.7/libraries/Wire
SPI 1.0 /home/user/.arduino15/packages/arduino/hardware/avr/1.8.7/libraries/SPI
Adafruit GFX Library 1.11.9 /home/user/Arduino/libraries/Adafruit_GFX_Library
Adafruit BusIO 1.15.0 /home/user/Arduino/libraries/Adafruit_BusIO
Adafruit SSD1306 2.5.9 /home/user/Arduino/libraries/Adafruit_SSD1306
там написано:
Global variables use 587 bytes (28%) of dynamic memory, leaving 1461 bytes for local variables. Maximum is 2048 bytes.
Под «динамической памятью» не подразумевается то, что обычно подразумевается под «динамической памятью» (динамически выделяемой памятью). Они имеют в виду SRAM (изменяемую, поэтому «динамическую» память), которая включает в себя как глобальные переменные, так и обычные строковые литералы на nano и другие вещи.
Компилятор знает, что 587 байт абсолютно необходимы, независимо от того, что происходит во время выполнения. У вас осталось 1461 байт, и вы пытаетесь использовать один экземпляр Adafruit_SSD1306 для дисплея с разрешением 128x64 и второй экземпляр для нескольких дисплеев с разрешением 128x32. Использование одного экземпляра для управления тремя дисплеями, вероятно, не соответствует ожиданиям Adafruit, но я не слишком удивлён, что эта часть, по крайней мере, работает.
Проблема в том, что у вас просто заканчивается память, как и следует из полученного сообщения. Это дисплей с глубиной цвета 1 бит на пиксель. Поэтому вам одновременно нужны разрешения 128x32 и 128x64 бита. Вы можете увидеть этот расчёт выполняется в библиотеке Adafruit. Это 128 * 4 и 128 * 8 байт соответственно. Или 512 байт и 1024 байта. Или 1536 байт в общей сложности (только для буфера отображения) Это уже не помещается в 1461 байт, доступный вам на старте выполнения, не говоря уже о стеке, локальных переменных и других данных, выделяемых во время выполнения (например, последовательных буферах), которые будут находиться в SRAM. Таким образом, вы просто не сможете использовать оба экземпляра Adafruit_SSD1306 для разрешений 128x32 и 128x64 одновременно, при этом у вас останется достаточно памяти для работы на ATmege328P. Реалистично, вам, вероятно, стоит приобрести чип большего объёма.
Но вы можете попробовать написать код так, чтобы в любой момент времени создавался только один экземпляр любого из них. Вероятно, самый простой способ сделать это — использовать функции, отображающие данные на дисплеях x32 и x64, и сделать объект Adafruit_SSD1306 локальной переменной в этой функции, чтобы при выходе из функции после отрисовки кадра используемая динамическая память освобождалась. Недостаток заключается в том, что вам придётся заново инициализировать дисплей в каждом кадре при повторном создании объекта Adafruit_SSD1306, но это может и не быть проблемой. Он может мерцать или что-то в этом роде. Вам нужно будет протестировать.
Таким образом, ваши функции будут иметь вид, подобный следующему:
void display_on_spi_x64() {
Adafruit_SSD1306 display(SCREEN_WIDTH_T, SCREEN_HEIGHT_T, OLED_MOSI, OLED_CLK, OLED_DC, OLED_RESET_SPI, OLED_CS);
if (!display.begin(SSD1306_SWITCHCAPVCC)) {
Serial.println(F("SSD1306 allocation failed for SPI OLED"));
while (1);
}
display.clearDisplay();
// various display.drawWhatever(); calls.
display.display();
}
void display_on_i2c_x32(int display_i2c_mux_channel) {
tcaSelect(display_i2c_mux_channel);
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
if (!display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDR)) {
Serial.print(F("SSD1306 allocation failed for I2C display on channel "));
Serial.println(display_i2c_mux_channel + 1);
while (1);
}
display.clearDisplay();
// various display.drawWhatever(); calls.
display.display();
}
Я не проверял вышеперечисленное. Это всего лишь руководство.
Таким образом, экземпляры Adafruit_SSD1306 существуют только во время входа в функцию и умирают по её завершении. При этом оперативная память для буфера дисплея освобождается. Дисплею больше ничего не требуется, поэтому он должен сохранить последнее переданное ему изображение даже после того, как объект Adafruit_SSD1306 умирает при выходе из функции.
Теоретически библиотеку Adafruit_SSD1306 можно модифицировать так, чтобы вы предоставляли собственный буфер как минимум минимально необходимого размера во время begin(), а не позволяли begin() выделять его внутри, как это сейчас делает библиотека через malloc(). Если бы вы использовали собственный буфер, вы бы разделяли его между четырьмя экземплярами Adafruit_SSD1306, но при этом вам придётся перерисовывать весь кадр при переключении между устройствами. Хотя вы уже подвергаетесь этому ограничению из-за того, что используете один экземпляр Adafruit_SSD1306 для управления тремя дисплеями I2C.
Другая библиотека (например, u8g2) может уже поддерживать это. Если это невозможно, приобретите Arduino с большим объёмом ОЗУ. При использовании дисплея 128x64 вам будет доступно менее 2048-1024-587 или 437 байт SRAM для стека/локальных переменных, последовательных буферов и так далее. Неудивительно, что проекту с четырьмя дисплеями потребуется более 437 байт для всех задач, не связанных с отображением. Полагаю, u8g2 может использовать 512 байт ОЗУ при работе с дисплеем SSD1306 128x64 в течение нескольких проходов на этапе отрисовки. Поэтому вы можете использовать u8g2 просто для освобождения части SRAM, даже если в итоге вам придётся использовать ту же стратегию, что и выше, создавая и уничтожая экземпляры объектов дисплеев u8g2. Или, опять же, приобретите Arduino большего объёма.
- Хорошие способы подключения нескольких шин I2C?
- OVF в последовательном мониторе вместо данных
- Несколько датчиков I2C с одинаковым адресом
- Путаница между SPI и I2C для SSD1306 OLED
- I2C и SPI одновременно?
- Как подключить MPU9250 к NodeMCU с помощью SPI или I2C Slave?
- Последовательная связь между несколькими устройствами (или ардуино)
- MPU-9250 IMU на SPI, внешнем датчике или магнитометре с использованием мастера I2C
Возможно, где-то возникают конфликты контактов. Убедитесь, что нет конфликтов между библиотеками SPI и I2C., @tepalia
Я действительно не имею ни малейшего понятия. Мультиплекс подключен к контактам A5 и A4, а SPI — к контактам D9–D13. Теоретически это не должно быть проблемой (хотя я не знаю достаточно, чтобы быть в этом уверен). Все проведенные мной проверки (сканирование портов I2C и т. п.) не давали никаких ошибок. В конце концов я сдался и остановился на adafruit для Multiplex OLEDS и SSD1306Ascii для SPI. Это некрасиво, но работает, пусть и не оптимально., @anton koluh
Вы не получаете сообщения о том, что распределение не удалось?, @timemage
Да, я получаю сообщения «Ошибка выделения» при попытке инициализации., @anton koluh