update 13.04.2019 - виправлено функцію виводу зображення на екран, та додано до демонстрації вивід зображення. Додав можливість роботи в режимі DMA (читай в статті як це зробити).
Передмова
![]() |
OLED Display 0.96'' SSD1306 |
Один із поширених дисплеїв для поробок на мікроконтролерах є OLED Display 0.96'' з контролером SSD1306. Невеличкий розмір, проста схема підключення, великий контраст зображення, доступна ціна, роблять цей дисплей бажаним в своїх невеличких вбудованих проектах на мікроконтролерах. З успіхом цей дисплей використовую разом з чипом ESP8266, для якого є вибір з декількох бібліотек. А ось для мікроконтролеру STM32 повноцінної бібліотеки не знайшов. Тому вирішено портувати бібліотеку для OLED SSD1306 з Arduino подібних засобів писаних на C++, до наших, STM32-ушних потреб, використовуючи мову Сі та HAL бібліотеки. По суті, вся робота, це переробка функцій бібліотек з C++ на Сі, а те що стосується апаратної частини це два рядка коду - виклик HAL-овських функцій для роботи з I2C протоколом.
CubeMX for STM32
Запускаємо CubeMX, створюємо новий проект, обираємо свій чип STM32. У вкладці "Pinout" робимо такі налаштування шпильок:
- RCC - Crystal/Ceramic resonator
- SYS - Serial Wire
- обираємо порт I2C до якого буде підключено дисплей, я обрав I2C1
![]() |
Налаштування в шпильок |
Перейдемо до вкладки "Clock Configuration" та налаштуємо тактування мікроконтролеру як зазначено червоним на малюнку:
![]() |
Наалаштування тактування |
У вкладці "Configuration" можна налаштувати порт I2C. На чипові STM32F1xx все працює за замовчуванням на частоті 400кГц. А от на чипові STM32F0xx є більше можливостей в налаштуваннях I2C і вдалось запустити дисплей на частоті шини I2C аж 1000кГц:
![]() |
Налаштування I2C для STM32F030F4 |
Але ця бібліотека "заважка" для чипу STM32F030F4, бо займає майже всю пам'ять мікроконтролера. Хоча можна видалити з бібліотеки непотрібні функції і цілком комфортно використовувати її і на таких молодших серіях MCU.
З CubeMX це все. Зберігаємо проект, надаємо ім'я проекту, генеруємо код для свого засобу розробки, та запускаємо свій засіб розробки. В мене це Atolic TrueStudio for STM32 - професійний засіб розробки і абсолютно безкоштовний.
Підключення бібліотеки до проекту та демо-код
Бібліотека складається з таких файлів:
- fonts.h - заголовний файл для трьох шрифтів
- ssd1306_defines.h - налаштування для роботи дисплею
- ssd1306.h - заголовний файл бібліотеки
- font.c - три шрифта розміром 7х10, 11х18, 16х26
- ssd1306.c - сирцевий файл бібліотеки
- image.h - заголовний файл зображення для демки
Ці файли потрібно вкласти до вашого проекту. Файли з розширенням ".h" до теки "inc", а файли з розширенням ".c" до теки "src". Або всю теку з бібліотекою, але на деяких засобах розробки потрібно в налаштуваннях проекту прописати шлях до теки з бібліотекою.
Сподіваюсь з підключенням до проекту бібліотеки у вас все вийшло.
Відкриємо в засобі розробки файл ssd1306_defines.h і налаштуємо бібліотеку для правильної роботи з дисплеєм:
Тепер напишемо демо програмку, щоб оцінити роботу дисплею. Відкриваємо файл "main.c" в засобі розробки і в визначених програмою CubeMX місцях для користувача, напишемо такий код:
Відкриємо в засобі розробки файл ssd1306_defines.h і налаштуємо бібліотеку для правильної роботи з дисплеєм:
- //#define USE_DMA - коли I2C шина для передачі використовує DMA потрібно розкоментувати рядок
- #define STM32_I2C_PORT hi2c1 - вказати порт I2C до якого під'єднано дисплей
- #define SSD1306_ADDRESS 0x3C - адреса дисплею на шині I2C
- #define SSD1306_128X64 - тип дисплею 128Х64 або 128Х32
Тепер напишемо демо програмку, щоб оцінити роботу дисплею. Відкриваємо файл "main.c" в засобі розробки і в визначених програмою CubeMX місцях для користувача, напишемо такий код:
- Вкладемо заголовний файл з бібліотекою до нашого коду та файл з зображенням:
/* USER CODE BEGIN Includes */ #include "ssd1306.h"
#include "image.h"
/* USER CODE END Includes */
- Напишемо декілька функцій для демонстрації можливостей бібліотеки
/* USER CODE BEGIN 0 */ // Adapted from Adafruit_SSD1306 void drawLines() { for (int16_t i = 0; i < ssd1306_GetWidth(); i += 4) { ssd1306_DrawLine(0, 0, i, ssd1306_GetHeight() - 1); ssd1306_UpdateScreen(); HAL_Delay(10); } for (int16_t i = 0; i < ssd1306_GetHeight(); i += 4) { ssd1306_DrawLine(0, 0, ssd1306_GetWidth() - 1, i); ssd1306_UpdateScreen(); HAL_Delay(10); } HAL_Delay(250); ssd1306_Clear(); for (int16_t i = 0; i < ssd1306_GetWidth(); i += 4) { ssd1306_DrawLine(0, ssd1306_GetHeight() - 1, i, 0); ssd1306_UpdateScreen(); HAL_Delay(10); } for (int16_t i = ssd1306_GetHeight() - 1; i >= 0; i -= 4) { ssd1306_DrawLine(0, ssd1306_GetHeight() - 1, ssd1306_GetWidth() - 1, i); ssd1306_UpdateScreen(); HAL_Delay(10); } HAL_Delay(250); ssd1306_Clear(); for (int16_t i = ssd1306_GetWidth() - 1; i >= 0; i -= 4) { ssd1306_DrawLine(ssd1306_GetWidth() - 1, ssd1306_GetHeight() - 1, i, 0); ssd1306_UpdateScreen(); HAL_Delay(10); } for (int16_t i = ssd1306_GetHeight() - 1; i >= 0; i -= 4) { ssd1306_DrawLine(ssd1306_GetWidth() - 1, ssd1306_GetHeight() - 1, 0, i); ssd1306_UpdateScreen(); HAL_Delay(10); } HAL_Delay(250); ssd1306_Clear(); for (int16_t i = 0; i < ssd1306_GetHeight(); i += 4) { ssd1306_DrawLine(ssd1306_GetWidth() - 1, 0, 0, i); ssd1306_UpdateScreen(); HAL_Delay(10); } for (int16_t i = 0; i < ssd1306_GetWidth(); i += 4) { ssd1306_DrawLine(ssd1306_GetWidth() - 1, 0, i, ssd1306_GetHeight() - 1); ssd1306_UpdateScreen(); HAL_Delay(10); } HAL_Delay(250); } // Adapted from Adafruit_SSD1306 void drawRect(void) { for (int16_t i = 0; i < ssd1306_GetHeight() / 2; i += 2) { ssd1306_DrawRect(i, i, ssd1306_GetWidth() - 2 * i, ssd1306_GetHeight() - 2 * i); ssd1306_UpdateScreen(); HAL_Delay(10); } } // Adapted from Adafruit_SSD1306 void fillRect(void) { uint8_t color = 1; for (int16_t i = 0; i < ssd1306_GetHeight() / 2; i += 3) { ssd1306_SetColor((color % 2 == 0) ? Black : White); // alternate colors ssd1306_FillRect(i, i, ssd1306_GetWidth() - i * 2, ssd1306_GetHeight() - i * 2); ssd1306_UpdateScreen(); HAL_Delay(10); color++; } // Reset back to WHITE ssd1306_SetColor(White); } // Adapted from Adafruit_SSD1306 void drawCircle(void) { for (int16_t i = 0; i < ssd1306_GetHeight(); i += 2) { ssd1306_DrawCircle(ssd1306_GetWidth() / 2, ssd1306_GetHeight() / 2, i); ssd1306_UpdateScreen(); HAL_Delay(10); } HAL_Delay(1000); ssd1306_Clear(); // This will draw the part of the circel in quadrant 1 // Quadrants are numberd like this: // 0010 | 0001 // ------|----- // 0100 | 1000 // ssd1306_DrawCircleQuads(ssd1306_GetWidth() / 2, ssd1306_GetHeight() / 2, ssd1306_GetHeight() / 4, 0b00000001); ssd1306_UpdateScreen(); HAL_Delay(200); ssd1306_DrawCircleQuads(ssd1306_GetWidth() / 2, ssd1306_GetHeight() / 2, ssd1306_GetHeight() / 4, 0b00000011); ssd1306_UpdateScreen(); HAL_Delay(200); ssd1306_DrawCircleQuads(ssd1306_GetWidth() / 2, ssd1306_GetHeight() / 2, ssd1306_GetHeight() / 4, 0b00000111); ssd1306_UpdateScreen(); HAL_Delay(200); ssd1306_DrawCircleQuads(ssd1306_GetWidth() / 2, ssd1306_GetHeight() / 2, ssd1306_GetHeight() / 4, 0b00001111); ssd1306_UpdateScreen(); } void drawProgressBarDemo(int counter) { char str[128]; // draw the progress bar ssd1306_DrawProgressBar(0, 32, 120, 10, counter); // draw the percentage as String ssd1306_SetCursor(64, 15); sprintf(str, "%i%%", counter); ssd1306_WriteString(str, Font_7x10); ssd1306_UpdateScreen(); } /* USER CODE END 0 */
- Зробимо ініціалізацію дисплею, встановимо орієнтацію дисплею, очистимо і встановимо білий колір:
/* USER CODE BEGIN 2 */ ssd1306_Init(); ssd1306_FlipScreenVertically(); ssd1306_Clear(); ssd1306_SetColor(White); /* USER CODE END 2 */
- В циклі викликаємо по черзі функції для демонстрації:
/* USER CODE BEGIN WHILE */ while (1) { drawLines(); HAL_Delay(1000); ssd1306_Clear(); drawRect(); HAL_Delay(1000); ssd1306_Clear(); fillRect(); HAL_Delay(1000); ssd1306_Clear(); drawCircle(); HAL_Delay(1000); ssd1306_Clear(); for(int i = 0; i < 100; i++) { drawProgressBarDemo(i); HAL_Delay(25); ssd1306_Clear(); } ssd1306_DrawRect(0, 0, ssd1306_GetWidth(), ssd1306_GetHeight()); ssd1306_SetCursor(8, 20); ssd1306_WriteString("SSD1306", Font_16x26); ssd1306_UpdateScreen(); HAL_Delay(2000); ssd1306_Clear(); ssd1306_DrawBitmap(0, 0, 128, 64, stm32fan); ssd1306_UpdateScreen(); HAL_Delay(2000); ssd1306_InvertDisplay(); HAL_Delay(2000); ssd1306_NormalDisplay(); ssd1306_Clear(); /* USER CODE END WHILE */
- Тепер можна компілювати, заливати до мікроконтролера та милуватись роботою дисплею
Список функцій бібліотеки
uint16_t ssd1306_GetWidth(void); // повертає ширину екрану в пікселях uint16_t ssd1306_GetHeight(void); // повертає висоту екрану в пікселях SSD1306_COLOR ssd1306_GetColor(void); // повертає поточний колір void ssd1306_SetColor(SSD1306_COLOR color); // задаємо поточний колір uint8_t ssd1306_Init(void); // початкова ініціалізація дисплею void ssd1306_Fill(); // заповнення екрану поточним кольором, аналог функції clear void ssd1306_UpdateScreen(void); // оновлення зображення екрану void ssd1306_DrawPixel(uint8_t x, uint8_t y); // малювання пікселю в заданих координатах void ssd1306_DrawLine(int16_t x0, int16_t y0, int16_t x1, int16_t y1); // малювання лінії по заданих координатах void ssd1306_DrawVerticalLine(int16_t x, int16_t y, int16_t length); // малювання вертикальної лінії по заданих координатах void ssd1306_DrawHorizontalLine(int16_t x, int16_t y, int16_t length); // малювання горизонтальної лінії по заданих координатах void ssd1306_DrawRect(int16_t x, int16_t y, int16_t width, int16_t height); // малювання прямокутника по заданих координатах void ssd1306_DrawTriangle(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t x3, uint16_t y3); // малювання трьохкутника по заданим координатах void ssd1306_FillRect(int16_t xMove, int16_t yMove, int16_t width, int16_t height); // малювання зафарбованого прямокутника по заданим координатам void ssd1306_DrawCircle(int16_t x0, int16_t y0, int16_t radius); // малювання кола по заданим координатам void ssd1306_FillCircle(int16_t x0, int16_t y0, int16_t radius); // малювання зафарбованого кола по заданим координатам void ssd1306_DrawCircleQuads(int16_t x0, int16_t y0, int16_t radius, uint8_t quads); // малювання сектора кола по заданим координатам void ssd1306_DrawProgressBar(uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint8_t progress); // малювання прогресбару по заданим координатам і наскільки заповнений у відсотках void ssd1306_DrawBitmap(uint8_t X, uint8_t Y, uint8_t W, uint8_t H, const uint8_t* pBMP); // вивід зображення char ssd1306_WriteChar(char ch, FontDef Font); // друк окремого символу char ssd1306_WriteString(char* str, FontDef Font); // друк текстового рядку void ssd1306_SetCursor(uint8_t x, uint8_t y); // встановлення координат курсору в пікселях void ssd1306_DisplayOn(void); // увімкнути дисплей void ssd1306_DisplayOff(void); // вимкнути дисплей void ssd1306_InvertDisplay(void); // інверсія дисплею void ssd1306_NormalDisplay(void); // нормалізація дисплею void ssd1306_ResetOrientation(void); // скидання оріентації дисплею void ssd1306_FlipScreenVertically(void); // перегорнути зображення дисплею по вертикалі void ssd1306_MirrorScreen(void); // дзеркалювання зображення дисплею void ssd1306_Clear(void); // очистка буферу дисплея
Робота бібліотеки з DMA
Якщо потрібно, для надсилання даних по шині I2C з DMA, то спочатку потрібно увімкнути і налаштувати режим DMA в програмі CubeMX:
![]() |
Увімкнення режиму DMA |
- Відкриваємо вкладку з налаштуваннями шини I2C
- Режим I2C
- Вкладка DMA Setting
- Кнопка ADD
- Обираємо I2C2_TX (по шині I2C ми тільки відправляємо байти TX, на прийом RX не вмикаємо режим DMA)
Обов'язково вмикаємо переривання "I2C2 event interrput", без цього працювати не буде:
![]() |
Вмикаємо переривання |
Генеруємо проект. В своїй IDE оновлюємо проект та розкоментуємо рядок "#define USE_DMA". Все готово. Можна компілювати та заливати в МК проект і милуватись демкою на своєму дисплеї.
Image2CPP
Для формування дампу даних зображення перейдіть за ланкою.
Архів з бібліотекою і демо-кодом
Бібліотека для роботи з дисплеєм OLED Display 0.96'' SSD1306
Ланка на драйвер на GitHub
Ланка на драйвер на GitHub
Здравствуйте, не могу запустить функцию void ssd1306_Image(uint8_t *img, uint8_t frame, uint8_t x, uint8_t y); // вивід зображення, остальные вроде работают.
ВідповістиВидалитиСоздал массив uint8_t test [] = {данные}; с картинкой при помощи LCDAssistant, вставил в функцию, но ничего не отображается. И не понимаю что за переменная "frame". Пробовал менять переменную на const unsigned char test [] = {};, все равно ничего не выходит.
На жаль я не перевіряв цю функцію. Закінчу з драйвером для ST7735, все перевірю і гляну на цю проблему.
ВидалитиМожет она и работает, я просто не пойму как использовать, на экране даже мусора нет. И в интернете примеров тоже не нашел. В Main.c функцию по разному объявлял, для проверки что не зависает контроллер использовал рабочие функции. Отладчик функцию читает, но я не умею с ним работать, только начал заниматься баловством с МК. Буфер в таком виде uint8_t test [] = {0x00, 0x1C, 0x36, 0xC3.....0x36, 0xC3};.
ВидалитиФункция в таком void ssd1306_Image(test, 128, 0, 0); Про Frame выше писал, но значение ставил по количеству данных в массиве. После функции ssd1306_UpdateScreen();, но все тихо. Буду очень благодарен за помощь.
Можливо цей допис допоможе розібратись?
Видалитиhttps://forum.arduino.cc/index.php?topic=434348.0
Там зазначено що біти для растрового зображення мають бути навпаки: наприклад 1000101 має бути 1010001
А frame номер кадру в масиві кадрів, для анімації, як кадр один, то напевно має бути там 0. Ось приклад з frame:
http://forum.arduino.cc/index.php?topic=375985.0
Не получилось с родной функцией вывести изображение, нашел рабочую из другого примера, она с начала мусор выводила и оказалось пиксели по вертикали выстраиваются.
Видалитиhttps://github.com/LonelyWolf/stm32/tree/master/SSD1306 LCD_DrawBitmap();
Почему родная функция не выводит изображение не понял.
Добре. Я трішки пізніше розберусь що до чого і оновлю бібліотеку. Можливо додам режим DMA.
ВидалитиСпасибо за помощь!
ВідповістиВидалитиШановний Віталій.
ВидалитиОновив статтю і драйвер.
Будь ласка до ознайомлення.
Большое спасибо! Попробую в ближайшее время!
ВидалитиСпасибо за помощь.Хотел бы скромно отблагодарить за труд,скиньте номер карточки при возможности.
ВідповістиВидалитиБудь ласка. РаДію, що допоміг вам. Вгорі є кнопка "Підтримати проект". Там буде форма від "Monobank", або 5168757319219764, Гончаренко Андрій.
ВидалитиСоздание Веб-сервера разложите нам по полочкам,пожалуйста!
ВідповістиВидалитиДякую за таку довіру. Але я ще не досвіду з мережевими технологіями на STM32. Хоча ця тема мені цікава.
ВидалитиОтличная и довольно детально обдуманная работа.
ВідповістиВидалитиНо исходник, библиотеки, который вы, выложили на droupbox, никоим образом скачать нельзя, ибо защищен от копирования!
Скористайтесь ланкою на GitHub https://github.com/taburyak/STM32_OLED_SSD1306_HAL_DMA
ВидалитиЩодо DropBox скарг не було. По можливості перевірю.
Будьласка хелп. помилка undefined reference to `Font_7x10'
ВідповістиВидалитиМало інформації. Ваш код допоміг би. В свій код додали #include "ssd1306.h"?
ВідповістиВидалитиЗдравствуйте!
ВідповістиВидалитиSPL библиотеку надо подключать?
Добрый день!А что за резисторы возле дисплея на фото?
ВідповістиВидалитиДоброго дня! Все працює, поки не додаю DMA. DMA на TX додав, переривання увімкнув, рядок розкоментував. На єкрані - нічого. Без DMA все працює. Може я щось забув?
ВідповістиВидалитиP.S. Я прибрав додаткові функції типу drawCircle і демо скоротив тільки до ініціалізації і малювання бітмапи - без DMA малює.
Дякую!
P.P.S
#elif SSD1306_128X32
прийшлося замінити на
#elif defined(SSD1306_128X32)
інакше компілятор сперечався)
Пройшовся дебаггером.
ВидалитиПершу команду ініціалізації дисплея (ssd1306_WriteCommand(DISPLAYOFF)) проходить норм, навіть повертає HAL_OK. Але після виходу з цієї функції одразу випадає у void HardFault_Handler(void)...
Вітаю!
ВідповістиВидалитиНе можу під'єднати 1.3" OLED Waveshare SH1106 до STM32F4DISCOVERY по I2C. Один раз вдалося запустити тест, потім втратив проект більше нічого не виходить. Перепробував все. Наштовхніть на думку. Я початківець, використовую Куб.
Дякую. Валерій. м.Кропивницький.
чи може бути таке, що на STM32F103C8T6 існує глюк з шиною ?
ВідповістиВидалитипробував і2с1 - взагалі мовчить. Включив і2с2 - шина запрацювала, але подивився в логічний аналізатор - там постійно тільки 0х78 посилає (це адресса, яка написана на моєму ssd1306). Почитав, є певна еррата з цього приводу. Але з нею в мене чіп зависає.
(HAL + CubeIDE 1.5.0 under ubuntu)
Спробував цей проект без DMA на STM32F411 (чорна таблетка). Придбав на arduino.ua https://arduino.ua/prod3985-otladochnaya-plata-stm32f411-neraspayannaya
ВідповістиВидалитиВикористав CubeIDE без інших засобів розробки. Все працює! Щиро дякую!
Дякую за відгук.
Видалити