Показ дописів із міткою esp8266. Показати всі дописи
Показ дописів із міткою esp8266. Показати всі дописи

субота, 14 квітня 2018 р.

STM32: Бібліотека OLED Display SSD1306 по I2C з DMA


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" робимо такі налаштування шпильок:
  1. RCC - Crystal/Ceramic resonator
  2. SYS - Serial Wire
  3. обираємо порт 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 і налаштуємо бібліотеку для правильної роботи з дисплеєм:

  • //#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
  1. Відкриваємо вкладку з налаштуваннями шини I2C
  2. Режим I2C
  3. Вкладка DMA Setting
  4. Кнопка ADD
  5. Обираємо I2C2_TX (по шині I2C ми тільки відправляємо байти TX, на прийом RX не вмикаємо режим DMA)
Обов'язково вмикаємо переривання "I2C2 event interrput", без цього працювати не буде:
Вмикаємо переривання
Генеруємо проект. В своїй IDE оновлюємо проект та розкоментуємо рядок "#define USE_DMA". Все готово. Можна компілювати та заливати в МК проект і милуватись демкою на своєму дисплеї.

Image2CPP

Для формування дампу даних зображення перейдіть за ланкою.

Архів з бібліотекою і демо-кодом

Бібліотека для роботи з дисплеєм OLED Display 0.96'' SSD1306
Ланка на драйвер на GitHub

Відео приклад роботи демо-коду

Посилання на джерела натхнення

понеділок, 22 січня 2018 р.

STM32: Сканер пристроїв на I2C шині (оновлено 24.12.2020)

update 24.12.2020: статтю оновлено до сучасних реалій. Додано відео-посібник.

Передмова

Розбирався я з шиною I2C, точніше хотів під'єднати сенсор освітлення TSL2561, який працює по шині I2C. І щоб зрозуміти, як тим сенсором скористуватись, довелось розбиратись з шиною I2C. Поки розбирався, вивчав, як воно все то працює і як наслідок написав невеличку програмку для сканування адрес пристроїв які "висять" на шині I2C. Зручно перевіряти адреси пристроїв, бо не завжди впевнений в правильності адреси, або переконатись чи взагалі, той, чи інший пристрій, відгукується на запит. Бо був випадок. Ніяк не міг запустити на іншому проекті пов'язаному з ESP8266 сенсор si7021. Як виявилось, він взагалі не реагує на запити. Більш за все бракований, або зіпсовався, коли припаював дроти до нього.

Створюємо проект в STM32CubeIDE

Створюємо новий проект в STM32CubeIDE. Обираємо свій MCU. Позначаємо потрібні піни. Я використовую MCU STM32F103C8T6, тож приклад буде для нього, але це не принципово. У вас може бути будь який інший MCU STM32.

Налаштування проекту
  1. Оберемо шину: I2C1 (або I2C2);
  2. Назначимо, що тактуватись будемо від зовнішнього кварцу: Crystal/Ceramic Resonator (не обов'язково, можна залишити як є, тактуватись буде, в цьому випадку, від внутрішнього генератора мікроконтролера);
  3. Оберемо пристрій Debug по якому заливаємо прошивку та налагоджуємо програму: Serial Wire;
  4. Оберемо послідовну шину: USART2 (можна інший, який зручно вам).
З налаштуваннями це все. Можна генерувати проект "Ctrl+S".

Пишемо програму

Відкриваємо в дереві проекту файл "main.c". В визначених, для користувача/розробника, ділянках додаємо такий код.

Додаємо до проекту бібліотеку "stdio.h", вона нам згодиться для друку повідомлень в Serial Monitor за допомоги функції "printf()":
/* USER CODE BEGIN Includes */
#include <stdio.h>
/* USER CODE END Includes */

Робимо редирект потоку до UART за допомоги переназначення системної функції "write":
/* USER CODE BEGIN 0 */
int _write(int file, char *ptr, int len)
{
	HAL_UART_Transmit(&huart2, (uint8_t *) ptr, len, HAL_MAX_DELAY);
	return len;
}
/* USER CODE END 0 */

Тепер сама програма сканеру шини I2C:
/* USER CODE BEGIN WHILE */
  while (1)
  {
	  printf("\r\nStart scanner I2C bus\r\n");

	  for (int i = 0; i < 128; ++i)
	  {
		uint16_t address = i << 1;

		if (HAL_I2C_IsDeviceReady(&hi2c1, address, 1, HAL_MAX_DELAY) == HAL_OK)
		{
			printf("Address: 0x%X\r\n", address >> 1);
		}
	  }

	  printf("End test I2C bus\r\n");
	  HAL_Delay(5000);
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */

Компілюємо, заливаємо. Підключаємо до шини I2C MCU ваші пристрої I2C, та конвертер USART2USB до зазначеного USART по такій схемі (шпильки MCU мають відповідати назначеним шпилькам в STM32CubeIDE):
Схема підключення

В будь якій термінальній програмі слухаємо COM порт до якого підключений USART2USB конвертер.  Подаємо живлення на MCU і в термінальній програмі бачимо результат сканування:

Результат роботи програми

Я використовую вбудований в STM32CubeIDE термінал. "З коробки" Serial Monitor не встановлено, потрібно встановити додатково з репозиторію. Як це зробити, описано в статті.

Відео-посібник