Показ дописів із міткою Atolic True Studio. Показати всі дописи
Показ дописів із міткою Atolic True Studio. Показати всі дописи

понеділок, 16 вересня 2019 р.

STM32: Бібліотека дисплею ILI9341 по інтерфейсу FSMC


Передмова

Бібліотека для роботи з дисплеєм 240х320 з чипом ILI9341 за допомоги інтерфейсу FSMC.

Колись придбав таку плату розробника з чипом STM32F407VET6 для дослідження 4хх серії. Плата якісна. Має на борту роз'єм для радіомодуля 2.4 ГГц NRF24L01, роз'єм для дисплею FSMC, модуль SD-CARD, тримач батарейки 3В для модуля RTC. Пам'ять FLASH. Два вбудованих світлодіода та дві користувацькі кнопки. Досить пристойний кандидат для серйозної розробки. Почнемо з дисплею.
STM32F407VET6 Black Board
До цієї плати продають сумісні по роз'єму дисплеї з резистивним тачскріном. Цей дисплей достатньо просто вставити "бутербродом" в роз'єм плати розробника.
3.2 inch TFT LCD Resistive Touch Screen 320 * 240 ILI9341 Display Module
Є варіант плати з дисплеєм в одним лотом на aliexpress.

Залізяччя

Схема підключення TFT до MCU

Розпіновка роз'ємів JTAG та TFT
Схема підключення TFT

CubeMX

Відкриваємо CubeMX або CubeIDE і налаштовуємо периферію:

  1. В розділі "Connectivity" обираємо "FSMC";
  2. В розділі "NOR Flash/PSRAM/SRAM/ROM/LCD 1" активуємо "NE1 Chip Select", обираємо "Memory type -> LCD Interface", "LCD Register Select -> A18" (відповідно до розпіновки роз'єму для дисплею, в мене сигнал RS дисплею під'єднано до PD13 мікроконтролеру. А ніжка PD13 активується для RS, коли обрати A18), "Data -> 16 bits";
  3. В розділі "Configurations -> NOR/PSRAM control" обираємо: "Memory type -> LCD Interface", "Bank -> Bank 1 NOR/PSRAM 1", "Write operation -> Enabled", "Extended mode -> Disabled". В розділі "NOR/PSRAM timing" обираємо: "Address setup time in HCLK clock cicles -> 1", "Data setup time in HCLK clock cycles -> 5", "Bus turn around time in HCLK clock cycles -> 0";
  4. Не забуваємо про підсвічування екрану. Відповідно до схеми це PB1. Встановимо режим GPIO_Output і дамо назву цій шпильці - "LCD_BL";
  5. Генеруємо проект. Даємо назву проекту, наприклад: "STM32F407VET6_Black_ILI9341". Вказуємо шлях до проекту та зберігаємо проект на диску.

Налаштування FSMC в CubeMX (для збільшення клацайте по світлині)

Підєднуємо бібліотеку до проекту

Запускаємо свій засіб розробки. Я використовую, або "CubeIDE for STM32", або "Atolic True Studio for STM32". Обидві IDE абсолютно безкоштовні. Відкриваємо проект згенерований CubeMX або CubeIDE і додаємо файли бібліотеки до проекту. Файли з розширенням *.h до теки INC проекту, а файли з розширенням *.c до теки SRC проекту. 
Бібліотека містить такі файли:
  • colors.h - визначення кольорів
  • fonts.h - структура шрифтів та перелік шрифтів
  • ili9341.h - визначення, макроси, прототипи функцій бібліотеки
  • image.h - структура для зображень, та перелік зображень
  • registers.h - визначення регістрів дисплею
  • STLogo.c - приклад картинки 240X240 для демонстрації
  • font12.c - шрифт 12 пікселів у висоту
  • font16.c - шрифт 16 пікселів у висоту
  • font20.c - шрифт 20 пікселів у висоту
  • font24.c - шрифт 24 пікселів у висоту
  • font8.c - шрифт 8 пікселів у висоту
  • ili9341.c - містить всі функції бібліотеки
  • example.c - містить демо-код, додати до файлу main.c свого проекту у відповідні секції коду.
Всі шрифти запозичив у ST, які знаходяться у репозиторії CubeMX за шляхом наприклад: "C:\Users\ваш_логін\STM32Cube\Repository\STM32Cube_FW_F4_V1.24.1\Utilities\Fonts".

Налаштування бібліотеки

Інтерфейс FSMC передбачає що, дисплей, який під'єднано до мікроконтролеру, буде "бачитись" як додаткова область пам'яті. І для надсилання команд і даних до дисплею достатньо ці команди і дані пересилати в певну ділянку додаткової пам'яті. Всі інші "ногодриги" шпильками зробить периферія FSMC мікроконтролеру. Залишилось тільки визначити за якими ж адресами надсилати команди і дані.
Звернемось до документації.
Розділ 36.4 External device address mapping:
Малюнок 435
Таблиці 216, 217

В нас обрано Bank1 - NOR/PSRAM 1, А18, 16-біт. Відповідно до малюнку та таблиць - адреса для команд 0x60000000. Визначаємо адресу для даних: 1 << 18 = 0x40000 де 18 це A18. Плюс, так як в нас 16 бітна шина, то 0x40000 << 1 = 0x80000. Тепер складаємо ці дві адреси до купи: 0x60000000 + 0x80000 = 0x60080000 і отримуємо адресу для даних. Заносимо ці адреси до визначень у файл ili9341.h:
#define LCD_BASE0          ((uint32_t)0x60000000)
#define LCD_BASE1          ((uint32_t)0x60080000)
Це і всі налаштування.

Якщо ви в CubeMX не вказали назву шпильки для керування підсвіткою екрана, як було запропоновано "LCD_BL", то тоді потрібно тут поміняти:
#define LCD_BL_ON() HAL_GPIO_WritePin(LCD_BL_GPIO_Port, LCD_BL_Pin, GPIO_PIN_SET)
#define LCD_BL_OFF() HAL_GPIO_WritePin(LCD_BL_GPIO_Port, LCD_BL_Pin, GPIO_PIN_RESET)
На GPIOB замість LCD_BL_GPIO_Port та GPIO_PIN_1 замість LCD_BL_Pin. Порт і номер шпильки куди під'єднано підсвітку дисплея.

Демо-код

Перш за все вкладаємо до свого проекту директивою #include - бібліотеку ili9341:
/* USER CODE BEGIN Includes */
#include "ili9341.h"
/* USER CODE END Includes */
Оголошуємо власний дефайн функції "min" (потрібно для роботи демо):
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
#define min(a,b) (((a)<(b))?(a):(b))
/* USER CODE END PD */
Оголошуємо прототипи всіх демо-функцій:
/* USER CODE BEGIN PFP */
/* Private function prototypes -----------------------------------------------*/
void demoLCD(int i);
unsigned long testFillScreen();
unsigned long testText();
unsigned long testLines(uint16_t color);
unsigned long testFastLines(uint16_t color1, uint16_t color2);
unsigned long testRects(uint16_t color);
unsigned long testFilledRects(uint16_t color1, uint16_t color2);
unsigned long testFilledCircles(uint8_t radius, uint16_t color);
unsigned long testCircles(uint8_t radius, uint16_t color);
unsigned long testTriangles();
unsigned long testFilledTriangles();
unsigned long testRoundRects();
unsigned long testFilledRoundRects();
unsigned long testDrawImage();
/* USER CODE END PFP */
Викликаємо функцію ініціалізації дисплея і вмикаємо підсвічування дисплея. Оголошуємо допоміжну змінну "i":
/* USER CODE BEGIN 2 */
  LCD_BL_ON();
  lcdInit();
  int i = 0;
  /* USER CODE END 2 */
В безкінечному циклі викликаємо демо-функцію і збільшуємо змінну "i" на одиницю, щоб міняти орієнтування екрану за кожний прохід демо-функції:
 /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
   demoLCD(i);
   i++;
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */

  }
  /* USER CODE END 3 */
Але не забуваємо, що нам потрібні ще функції, що входять до складу "demoLCD", прототипи яких ми же раніше оголосили. Її ставимо в кінці файла "main.c" в блок користувача 4:
/* USER CODE BEGIN 4 */
void demoLCD(int i)
{
 lcdSetOrientation(i % 4);

 uint32_t t = testFillScreen();
 lcdSetTextFont(&Font16);
 lcdSetCursor(0, lcdGetHeight() - lcdGetTextFont()->Height - 1);
 lcdSetTextColor(COLOR_WHITE, COLOR_BLACK);
 lcdPrintf("Time: %4lu ms", t);
 HAL_Delay(2000);

 t = HAL_GetTick();
 lcdTest();
 lcdSetTextFont(&Font16);
 lcdSetCursor(0, lcdGetHeight() - lcdGetTextFont()->Height - 1);
 lcdSetTextColor(COLOR_WHITE, COLOR_BLACK);
 lcdPrintf("Time: %4lu ms", HAL_GetTick() - t);
 HAL_Delay(2000);

 t = testText();
 lcdSetTextFont(&Font16);
 lcdSetCursor(0, lcdGetHeight() - lcdGetTextFont()->Height - 1);
 lcdSetTextColor(COLOR_WHITE, COLOR_BLACK);
 lcdPrintf("Time: %4lu ms", t);
 HAL_Delay(2000);

 lcdSetTextFont(&Font16);
 lcdSetCursor(0, lcdGetHeight() - lcdGetTextFont()->Height - 1);
 lcdSetTextColor(COLOR_WHITE, COLOR_BLACK);
 lcdPrintf("Time: %4lu ms", testLines(COLOR_CYAN));
 HAL_Delay(2000);

 lcdSetTextFont(&Font16);
 lcdSetCursor(0, lcdGetHeight() - lcdGetTextFont()->Height - 1);
 lcdSetTextColor(COLOR_WHITE, COLOR_BLACK);
 lcdPrintf("Time: %4lu ms", testFastLines(COLOR_RED, COLOR_BLUE));
 HAL_Delay(2000);

 lcdSetTextFont(&Font16);
 lcdSetCursor(0, lcdGetHeight() - lcdGetTextFont()->Height - 1);
 lcdSetTextColor(COLOR_WHITE, COLOR_BLACK);
 lcdPrintf("Time: %4lu ms", testRects(COLOR_GREEN));
 HAL_Delay(2000);

 lcdSetTextFont(&Font16);
 lcdSetCursor(0, lcdGetHeight() - lcdGetTextFont()->Height - 1);
 lcdSetTextColor(COLOR_WHITE, COLOR_BLACK);
 lcdPrintf("Time: %4lu ms", testFilledRects(COLOR_YELLOW, COLOR_MAGENTA));
 HAL_Delay(2000);

 lcdSetTextFont(&Font16);
 lcdSetCursor(0, lcdGetHeight() - lcdGetTextFont()->Height - 1);
 lcdSetTextColor(COLOR_WHITE, COLOR_BLACK);
 lcdPrintf("Time: %4lu ms", testFilledCircles(10, COLOR_MAGENTA));
 HAL_Delay(2000);

 lcdSetTextFont(&Font16);
 lcdSetCursor(0, lcdGetHeight() - lcdGetTextFont()->Height - 1);
 lcdSetTextColor(COLOR_WHITE, COLOR_BLACK);
 lcdPrintf("Time: %4lu ms", testCircles(10, COLOR_WHITE));
 HAL_Delay(2000);

 lcdSetTextFont(&Font16);
 lcdSetCursor(0, lcdGetHeight() - lcdGetTextFont()->Height - 1);
 lcdSetTextColor(COLOR_WHITE, COLOR_BLACK);
 lcdPrintf("Time: %4lu ms", testTriangles());
 HAL_Delay(2000);

 lcdSetTextFont(&Font16);
 lcdSetCursor(0, lcdGetHeight() - lcdGetTextFont()->Height - 1);
 lcdSetTextColor(COLOR_WHITE, COLOR_BLACK);
 lcdPrintf("Time: %4lu ms", testFilledTriangles());
 HAL_Delay(2000);

 lcdSetTextFont(&Font16);
 lcdSetCursor(0, lcdGetHeight() - lcdGetTextFont()->Height - 1);
 lcdSetTextColor(COLOR_WHITE, COLOR_BLACK);
 lcdPrintf("Time: %4lu ms", testRoundRects());
 HAL_Delay(2000);

 lcdSetTextFont(&Font16);
 lcdSetCursor(0, lcdGetHeight() - lcdGetTextFont()->Height - 1);
 lcdSetTextColor(COLOR_WHITE, COLOR_BLACK);
 lcdPrintf("Time: %4lu ms", testFilledRoundRects());
 HAL_Delay(2000);

 lcdSetTextFont(&Font16);
 lcdSetCursor(0, lcdGetHeight() - lcdGetTextFont()->Height - 1);
 lcdSetTextColor(COLOR_WHITE, COLOR_BLACK);
 lcdPrintf("Time: %4lu ms", testDrawImage());
 HAL_Delay(2000);
}

unsigned long testFillScreen()
{
 unsigned long start = HAL_GetTick(), t = 0;
 lcdFillRGB(COLOR_BLACK);
 t += HAL_GetTick() - start;
 lcdSetCursor(0, 0);
 lcdSetTextFont(&Font24);
 lcdSetTextColor(COLOR_WHITE, COLOR_BLACK);
 lcdPrintf("BLACK");
 HAL_Delay(1000);
 start = HAL_GetTick();
 lcdFillRGB(COLOR_RED);
 t += HAL_GetTick() - start;
 lcdSetCursor(0, 0);
 lcdSetTextFont(&Font24);
 lcdSetTextColor(COLOR_WHITE, COLOR_BLACK);
 lcdPrintf("RED");
 HAL_Delay(1000);
 start = HAL_GetTick();
 lcdFillRGB(COLOR_GREEN);
 t += HAL_GetTick() - start;
 lcdSetCursor(0, 0);
 lcdSetTextFont(&Font24);
 lcdSetTextColor(COLOR_WHITE, COLOR_BLACK);
 lcdPrintf("GREEN");
 HAL_Delay(1000);
 start = HAL_GetTick();
 lcdFillRGB(COLOR_BLUE);
 t += HAL_GetTick() - start;
 lcdSetCursor(0, 0);
 lcdSetTextFont(&Font24);
 lcdSetTextColor(COLOR_WHITE, COLOR_BLACK);
 lcdPrintf("BLUE");
 HAL_Delay(1000);
 start = HAL_GetTick();
 lcdFillRGB(COLOR_BLACK);
 return t += HAL_GetTick() - start;
}

unsigned long testText()
{
 lcdFillRGB(COLOR_BLACK);
 unsigned long start = HAL_GetTick();
 lcdSetCursor(0, 0);
 lcdSetTextColor(COLOR_WHITE, COLOR_BLACK);
 lcdSetTextFont(&Font8);
 lcdPrintf("Hello World!\r\n");
 lcdSetTextColor(COLOR_YELLOW, COLOR_BLACK);
 lcdSetTextFont(&Font12);
 lcdPrintf("%i\r\n", 1234567890);
 lcdSetTextColor(COLOR_RED, COLOR_BLACK);
 lcdSetTextFont(&Font16);
 lcdPrintf("%#X\r\n", 0xDEADBEEF);
 lcdPrintf("\r\n");
 lcdSetTextColor(COLOR_GREEN, COLOR_BLACK);
 lcdSetTextFont(&Font20);
 lcdPrintf("Groop\r\n");
 lcdSetTextFont(&Font12);
 lcdPrintf("I implore thee,\r\n");
 lcdSetTextFont(&Font12);
 lcdPrintf("my foonting turlingdromes.\r\n");
 lcdPrintf("And hooptiously drangle me\r\n");
 lcdPrintf("with crinkly bindlewurdles,\r\n");
 lcdPrintf("Or I will rend thee\r\n");
 lcdPrintf("in the gobberwarts\r\n");
 lcdPrintf("with my blurglecruncheon,\r\n");
 lcdPrintf("see if I don't!\r\n");
 return HAL_GetTick() - start;
}

unsigned long testLines(uint16_t color)
{
  unsigned long start, t;
  int           x1, y1, x2, y2,
                w = lcdGetWidth(),
                h = lcdGetHeight();

  lcdFillRGB(COLOR_BLACK);

  x1 = y1 = 0;
  y2    = h - 1;
  start = HAL_GetTick();
  for(x2 = 0; x2 < w; x2 += 6) lcdDrawLine(x1, y1, x2, y2, color);
  x2 = w - 1;
  for(y2 = 0; y2 < h; y2 += 6) lcdDrawLine(x1, y1, x2, y2, color);
  t = HAL_GetTick() - start; // fillScreen doesn't count against timing

  HAL_Delay(1000);
  lcdFillRGB(COLOR_BLACK);

  x1 = w - 1;
  y1 = 0;
  y2 = h - 1;

  start = HAL_GetTick();

  for(x2 = 0; x2 < w; x2 += 6) lcdDrawLine(x1, y1, x2, y2, color);
  x2 = 0;
  for(y2 = 0; y2 < h; y2 += 6) lcdDrawLine(x1, y1, x2, y2, color);
  t += HAL_GetTick() - start;

  HAL_Delay(1000);
  lcdFillRGB(COLOR_BLACK);

  x1 = 0;
  y1 = h - 1;
  y2 = 0;
  start = HAL_GetTick();

  for(x2 = 0; x2 < w; x2 += 6) lcdDrawLine(x1, y1, x2, y2, color);
  x2 = w - 1;
  for(y2 = 0; y2 < h; y2 += 6) lcdDrawLine(x1, y1, x2, y2, color);
  t += HAL_GetTick() - start;

  HAL_Delay(1000);
  lcdFillRGB(COLOR_BLACK);

  x1 = w - 1;
  y1 = h - 1;
  y2 = 0;

  start = HAL_GetTick();

  for(x2 = 0; x2 < w; x2 += 6) lcdDrawLine(x1, y1, x2, y2, color);
  x2 = 0;
  for(y2 = 0; y2 < h; y2 += 6) lcdDrawLine(x1, y1, x2, y2, color);

  return t += HAL_GetTick() - start;
}

unsigned long testFastLines(uint16_t color1, uint16_t color2)
{
  unsigned long start;
  int x, y, w = lcdGetWidth(), h = lcdGetHeight();

  lcdFillRGB(COLOR_BLACK);
  start = HAL_GetTick();
  for(y = 0; y < h; y += 5) lcdDrawHLine(0, w, y, color1);
  for(x = 0; x < w; x += 5) lcdDrawVLine(x, 0, h, color2);

  return HAL_GetTick() - start;
}

unsigned long testRects(uint16_t color)
{
  unsigned long start;
  int n, i, i2,
      cx = lcdGetWidth()  / 2,
      cy = lcdGetHeight() / 2;

  lcdFillRGB(COLOR_BLACK);
  n = min(lcdGetWidth(), lcdGetHeight());
  start = HAL_GetTick();
  for(i = 2; i < n; i += 6)
  {
    i2 = i / 2;
    lcdDrawRect(cx - i2, cy - i2, i, i, color);
  }

  return HAL_GetTick() - start;
}

unsigned long testFilledRects(uint16_t color1, uint16_t color2)
{
  unsigned long start, t = 0;
  int n, i, i2,
      cx = lcdGetWidth() / 2 - 1,
      cy = lcdGetHeight() / 2 - 1;

  lcdFillRGB(COLOR_BLACK);
  n = min(lcdGetWidth(), lcdGetHeight());

  for(i = n; i > 0; i -= 6)
  {
    i2 = i / 2;
    start = HAL_GetTick();
    lcdFillRect(cx-i2, cy-i2, i, i, color1);
    t    += HAL_GetTick() - start;
    // Outlines are not included in timing results
    lcdDrawRect(cx-i2, cy-i2, i, i, color1);
  }

  return t;
}

unsigned long testFilledCircles(uint8_t radius, uint16_t color)
{
  unsigned long start;
  int x, y, w = lcdGetWidth(), h = lcdGetHeight(), r2 = radius * 2;

  lcdFillRGB(COLOR_BLACK);
  start = HAL_GetTick();
  for(x = radius; x < w; x += r2)
  {
    for(y = radius; y < h; y += r2)
    {
      lcdFillCircle(x, y, radius, color);
    }
  }

  return HAL_GetTick() - start;
}

unsigned long testCircles(uint8_t radius, uint16_t color)
{
  unsigned long start;
  int x, y, r2 = radius * 2,
      w = lcdGetWidth()  + radius,
      h = lcdGetHeight() + radius;

  // Screen is not cleared for this one -- this is
  // intentional and does not affect the reported time.
  start = HAL_GetTick();
  for(x = 0; x < w; x += r2)
  {
    for(y = 0; y < h; y += r2)
    {
      lcdDrawCircle(x, y, radius, color);
    }
  }

  return HAL_GetTick() - start;
}

unsigned long testTriangles()
{
  unsigned long start;
  int n, i, cx = lcdGetWidth() / 2 - 1,
            cy = lcdGetHeight() / 2 - 1;

  lcdFillRGB(COLOR_BLACK);
  n = min(cx, cy);
  start = HAL_GetTick();
  for(i = 0; i < n; i += 5)
  {
    lcdDrawTriangle(
      cx    , cy - i, // peak
      cx - i, cy + i, // bottom left
      cx + i, cy + i, // bottom right
      lcdColor565(i, i, i));
  }

  return HAL_GetTick() - start;
}

unsigned long testFilledTriangles()
{
  unsigned long start, t = 0;
  int i, cx = lcdGetWidth() / 2 - 1,
         cy = lcdGetHeight() / 2 - 1;

  lcdFillRGB(COLOR_BLACK);
  for(i = min(cx, cy); i > 10; i -= 5)
  {
    start = HAL_GetTick();
    lcdFillTriangle(cx, cy - i, cx - i, cy + i, cx + i, cy + i, lcdColor565(0, i*10, i*10));
    t += HAL_GetTick() - start;
    lcdFillTriangle(cx, cy - i, cx - i, cy + i, cx + i, cy + i, lcdColor565(i*10, i*10, 0));
  }

  return t;
}

unsigned long testRoundRects()
{
  unsigned long start;
  int w, i, i2,
      cx = lcdGetWidth() / 2 - 1,
      cy = lcdGetHeight() / 2 - 1;

  lcdFillRGB(COLOR_BLACK);
  w = lcdGetWidth(), lcdGetHeight();
  start = HAL_GetTick();
  for(i = 0; i < w; i += 6)
  {
    i2 = i / 2;
    lcdDrawRoundRect(cx-i2, cy-i2, i, i, i/8, lcdColor565(i, 0, 0));
  }

  return HAL_GetTick() - start;
}

unsigned long testFilledRoundRects()
{
  unsigned long start;
  int i, i2,
      cx = lcdGetWidth()  / 2 - 1,
      cy = lcdGetHeight() / 2 - 1;

  lcdFillRGB(COLOR_BLACK);
  start = HAL_GetTick();
  for(i = min(lcdGetWidth(), lcdGetHeight()); i > 20; i -=6 )
  {
    i2 = i / 2;
    lcdFillRoundRect(cx - i2, cy - i2, i, i, i / 8, lcdColor565(0, i, 0));
  }

  return HAL_GetTick() - start;
}

unsigned long testDrawImage()
{
 unsigned long start;

 lcdFillRGB(COLOR_BLACK);
 start = HAL_GetTick();
 if (lcdGetOrientation() == LCD_ORIENTATION_LANDSCAPE || lcdGetOrientation() == LCD_ORIENTATION_LANDSCAPE_MIRROR)
 {
  lcdDrawImage((lcdGetWidth() - bmSTLogo.xSize) / 2, 0, &bmSTLogo);
 }
 else
 {
  lcdDrawImage(0, (lcdGetHeight() - bmSTLogo.ySize) / 2, &bmSTLogo);
 }
 return HAL_GetTick() - start;
}
/* USER CODE END 4 */
Компілюємо проект. Заливаємо до пам'яті мікроконтролера і спостерігаємо за демонстрацією.

Файли

Відео-демострація




вівторок, 4 червня 2019 р.

STM32: Бібліотека для 7-сегментного 8 розрядного дисплею на MAX7219 по SPI шині


Передмова

Мабуть самий з найпоширеніших дисплеїв в радіоаматорскій практиці є семисегментний світлодіодний дисплей. Це й простота і дешевизна. Але й є недоліки. Це інформаційне обмеження щодо варіантів символів, які можна зобразити і велика кількість ніжок на мікроконтролері (7 або 8 ніжок для сегментів плюс на кожен розряд по 1 ніжці мікоконтролера).

За допомоги мікросхеми MAX7219 можна усунути недолік з великою кількістю ніжок. За допомоги однієї мікросхеми MAX7219 можна керувати 8 розрядним 7-ми сегментним дисплеєм, плюс режим декодування цифр, плюс регулювання яскравості свічення дисплею, плюс не потрібно турбуватись про динамічну індикацію. А данні до мікросхеми надсилати по шині SPI, яка обмежена швидкістю в 10 Мбіт/сек.

В цій статті розглянемо невеличку бібліотеку для зручного друку інформації на семисегментний дисплей з мікросхемою MAX7219:
  • Очищення дисплею
  • Встановлення рівню яскравості
  • Надсилання даних на MAX7219
  • Вмикання/вимикання дисплею
  • Встановлення/скидання режиму декодування
  • Друк цифр (символів) в заданому розряді
  • Друк чисел типу INT та FLOAT

Залізяччя

Для прикладу буду використовувати такі матеріали:

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

Обираємо свій мікроконтролер або дошку. Та налаштовуємо проект.
Налаштування проекту в Cube
  1. Тиснемо RCC -> High Speed Clock (HSE) -> Crystal/Ceramic Resonator
  2. SYS -> Debug -> Serial Wire
  3. Тиснемо SPI1
  4. Обираємо шпильку Chip Select -> наприклад PB0, обираємо режим GPIO_OUTPUT, та називаємо її CS_MAX7219
  5. Обираємо режим для SPI1 -> Transmit only master
  6. Слідкуємо щоб Baud Rate не перевищував 10 MBits/sec, якщо перевищує збільшіть Prescaler (for Baud Rate)
Генеруємо проект та переходимо до підключення дисплейного модуля до мікроконтролера, завантаження бібліотеки та написання демо-коду.

Підключення дисплейного модуля до мікроконтролера

STM32F103C8T6 Дисплейний модуль
Vcc 3.3V
Vcc
GND
GND
PA7 SPI1_MOSI
DIN
PB0 CS_MAX7219
CS
PA5 SPI1_SCK
CLK

Бібліотека MAX7219

Завантажити бібліотеку можна з GitHub. Бібліотека складається з двох файлів: max7219.h та max7219.c та має такі функції:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
void max7219_Init(uint8_t intensivity);
void max7219_SetIntensivity(uint8_t intensivity);
void max7219_Clean(void);
void max7219_SendData(uint8_t addr, uint8_t data);
void max7219_Turn_On(void);
void max7219_Turn_Off(void);
void max7219_Decode_On(void);
void max7219_Decode_Off(void);
void max7219_PrintDigit(MAX7219_Digits position, MAX7219_Numeric numeric, bool point);
MAX7219_Digits max7219_PrintItos(MAX7219_Digits position, int value);
MAX7219_Digits max7219_PrintNtos(MAX7219_Digits position, uint32_t value, uint8_t n);
MAX7219_Digits max7219_PrintFtos(MAX7219_Digits position, float value, uint8_t n);
  1. Ініціалізація дисплею. На вхід "рівень яскравості" (0x0 - 0xF);
  2. Встановлення рівня яскравості. На вхід "рівень яскравості" (0x0 - 0xF);
  3. Очищення дисплею;
  4. Надсилання в шину SPI до MAX7219 даних. На вхід: адреса, дані;
  5. Увімкнення дисплею;
  6. Вимкнення дисплею;
  7. Увімкнути режим декодування;
  8. Вимкнути режим декодування;
  9. Друк цифри (символу). На вхід: позиція (1 - 8), цифра (0 - 9), точка (true/false);
  10. Друк числа типу INT. На вхід: стартова позиція числа на дисплеї, число INT. На виході: поточна позиція курсору;
  11. Друк числа типу INT з фіксованою кількістю розрядів. На вхід: стартова позиція числа на дисплеї, число INT, кількість розрядів. На виході: поточна позиція курсору;
  12. Друк числа типу FLOAT. На вхід: стартова позиція числа на дисплеї, число FLOAT, кількість знаків після коми (не більше 4-х). На виході: поточна позиція курсору;

Демо-код

Відкриваємо свій засіб розробки та експортуємо, згенерований CubeMX або CubeIDE, код. 
Копіюємо файли бібліотеки до проекту. Файли з розширенням *.h до теки INC, файли з розширенням *.c до теки SRC.
Відкриваємо файл max7219.h та налаштовуємо бібліотеку:
  • Кількість розрядів #define NUMBER_OF_DIGITS 8
  • Порт SPI до якого приєднано дисплейний модуль #define SPI_PORT hspi1
Відкриваємо файл main.c і в секції USER CODE Includes вкладаємо нашу бібліотеку:
/* USER CODE BEGIN Includes */
#include "max7219.h"
/* USER CODE END Includes */

В користувацьку секцію 2 додаємо ініціалізацію дисплею з встановленим рівнем яскравості 7 і вмикаємо режим декодування цифр:
/* USER CODE BEGIN 2 */
  max7219_Init(7);
  max7219_Decode_On();
  /* USER CODE END 2 */

В безкінечному циклі друкуємо від'ємне число Pi. Та слово HELP. Чекаємо 1.5 секунди, очищаємо дисплей та друкуємо число 765 на 4-ри розряди (додається незначащий нуль), та ціле число 321. Знову чекаємо 1.5 секунди і все з початку.
/* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
   max7219_Clean();
   max7219_PrintFtos(DIGIT_8, -3.14, 2);
   max7219_PrintDigit(DIGIT_4, LETTER_H, false);
   max7219_PrintDigit(DIGIT_3, LETTER_E, false);
   max7219_PrintDigit(DIGIT_2, LETTER_L, false);
   max7219_PrintDigit(DIGIT_1, LETTER_P, false);
   HAL_Delay(1500);
   max7219_Clean();
   max7219_PrintNtos(DIGIT_8, 765, 4);
   max7219_PrintItos(DIGIT_3, 321);
   HAL_Delay(1500);
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
Щоб друкувати довільні символи, то треба вимкнути режим декодування цифр і в масив static uint8_t SYMBOLS[] файлу max7219.c додати ці символи. А в нумерований список MAX7219_Numeric в файлі max7219.h додати позначення і порядковий номер в масиві - нового символу.

Приклад роботи

Приклад роботи

понеділок, 25 березня 2019 р.

STM32: Бібліотека для роботи з LCD дисплеєм з контролером ST7735

update 10.03.2021: Замінив в файлі бібліотеки st7735.c дефайни з маніпулюванням регістрами GPIO на HAL функції. Для сумісності з будь яким чипом. Додав відео-підтримку до статті.

Передмова

Бібліотека призначена для роботи з LCD дисплеєм з контролером ST7735(S) по SPI шині. Для розмірів 128X160, 128X128 та 160X80. Дисплей можна придбати деінде, наприклад: ebay, aliexpress, тощо.

Я в проекті використовую:

Схема з'єднання

Схема з'єднання
Клацайте по зображенню, щоб збільшити. На деяких дисплеях, як на моєму, шпильки MOSI та SCK позначені як SDA та SCL. Та помилково на платі надруковано роздільна здатність як 128Х120, насправді роздільна здатність дисплею 128X160 точок.
Приклад дисплею ST7735S

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

Запускаємо CubeMX, обираємо свій мікроконтролер, або дошку. Та налаштовуємо проект:
Налаштування периферії
  1. Тиснемо RCC -> High Speed Clock (HSE) -> Crystal/Ceramic Resonator, та SYS -> Debug -> Serial Wire.
  2. Тиснемо SPI1.
  3. Обираємо Mode -> Transmit Only Master, та Hardware NSS Signal -> Disable, все інше без змін.
  4. Обираємо шпильки для сигналів BL (підсвітка), CS (вибір чипу), DC (дані/команда), RES (скидання). Шпильки називаємо так як на малюнку: ST7735_BL, ST7735_CS, ST7735_DC, ST7735_RES.
  5. Генеруємо проект: вказуємо шлях, даємо назву проекту "STM32F103C8T6_ST7735_SPI", обираємо свій засіб розробки. В мене це "Atolic Truestudio". Тиснемо "OK".

Демонстраційний код

Відкриваємо свій засіб розробки та експортуємо згенерований, CubeMX, проект. Файли st7735.h, st7735_cfg.h, fonts.h, testimg.h копіюємо до теки "inc" проекту, а файли st7735.c та fonts.c до теки "src" проекту.
Відкриваємо файл st7735_cfg.h та налаштовуємо бібліотеку.
Вказуємо до якого саме SPI під'єднано дисплей. В прикладі до SPI1, та якщо використовуєте DMA для передачі даних по SPI, то розкоментуйте рядок з "USE_SPI_DMA". Але хочу зауважити, що в режимі SPI з DMA екран дисплею промальовується повільніше ніж без DMA.
#define ST7735_SPI_PORT hspi1 //hspi1, hspi2, hspi3...
//#define USE_SPI_DMA   //if used DMA for SPI bus
Вказуємо тип дисплею. В прикладі це ST7735S.
//#define ST7735_1_8_DEFAULT_ORIENTATION // AliExpress/eBay 1.8" display, default orientation
#define ST7735S_1_8_DEFAULT_ORIENTATION  // WaveShare ST7735S-based 1.8" display, default orientation
//#define ST7735_1_44_DEFAULT_ORIENTATION  // 1.44" display, default orientation
//#define ST7735_MINI_DEFAULT_ORIENTATION  // mini 160x80 display (it's unlikely you want the default orientation)
Та якщо ви не дали назви шпилькам в CubeMX, то потрібно самому назначити порт і шпильку до відповідних назв:
//Port and pin connected signal 'RES' (reset) ST7735 display
#ifndef ST7735_RES_Pin
#define ST7735_RES_Pin   GPIO_PIN_12
#endif
#ifndef ST7735_RES_GPIO_Port
#define ST7735_RES_GPIO_Port  GPIOB
#endif
//Port and pin connected signal 'DC' (data or command) ST7735 display
#ifndef ST7735_DC_Pin
#define ST7735_DC_Pin   GPIO_PIN_13
#endif
#ifndef ST7735_DC_GPIO_Port
#define ST7735_DC_GPIO_Port  GPIOB
#endif
//Port and pin connected signal 'CS' (chip select) ST7735 display
#ifndef ST7735_CS_Pin
#define ST7735_CS_Pin   GPIO_PIN_14
#endif
#ifndef ST7735_CS_GPIO_Port
#define ST7735_CS_GPIO_Port  GPIOB
#endif
//Port and pin connected signal 'BL' (back light) ST7735 display
#ifndef ST7735_BL_Pin
#define ST7735_BL_Pin  GPIO_PIN_15
#endif
#ifndef ST7735_BL_GPIO_Port
#define ST7735_BL_GPIO_Port  GPIOB
#endif
Якщо ви вже дали назви шпилькам в програмі CubeMX, то про це вже не потрібно турбуватись. З налаштуванням бібліотеки це все.

Залишилось додати демонстраційний код до файлу main.c. Відкриваємо його і додаємо такі рядки в секцію "USER CODE BEGIN Includes":
/* USER CODE BEGIN Includes */
#include "st7735.h"
#include "fonts.h"
#include "testimg.h"
/* USER CODE END Includes */
В секцію "USER CODE BEGIN PV" оголошуємо глобальну змінну:
/* USER CODE BEGIN PV */
uint8_t r = 0;
/* USER CODE END PV */
В секцію "USER CODE BEGIN 0" додаємо функцію демонстрації можливостей бібліотеки:
/* USER CODE BEGIN 0 */
void demoTFT(void)
{
 ST7735_SetRotation(r);

 ST7735_FillScreen(ST7735_BLACK);

 for(int x = 0; x < ST7735_GetWidth(); x++)
 {
   ST7735_DrawPixel(x, 0, ST7735_WHITE);
   ST7735_DrawPixel(x, ST7735_GetHeight() - 1, ST7735_WHITE);
 }

 for(int y = 0; y < ST7735_GetHeight(); y++)
 {
   ST7735_DrawPixel(0, y, ST7735_WHITE);
   ST7735_DrawPixel(ST7735_GetWidth() - 1, y, ST7735_WHITE);
 }

 ST7735_DrawLine(0, 0, ST7735_GetWidth(), ST7735_GetHeight(), ST7735_WHITE);
 ST7735_DrawLine(ST7735_GetWidth(), 0, 0, ST7735_GetHeight(), ST7735_WHITE);

 HAL_Delay(2000);

 ST7735_FillScreen(ST7735_BLACK);

 for (int i = 0; i < ST7735_GetHeight(); i += 4)
 {
  ST7735_DrawFastHLine(0, i, ST7735_GetWidth() - 1, ST7735_WHITE);
 }

 for (int i = 0; i < ST7735_GetWidth(); i += 4)
 {
  ST7735_DrawFastVLine(i, 0, ST7735_GetHeight() - 1, ST7735_WHITE);
 }

 HAL_Delay(2000);

 // Check fonts
 ST7735_FillScreen(ST7735_BLACK);
 ST7735_DrawString(0, 0, "Font_7x10, red on black, lorem ipsum dolor sit amet", Font_7x10, ST7735_RED, ST7735_BLACK);
 ST7735_DrawString(0, 3*10, "Font_11x18, green, lorem ipsum", Font_11x18, ST7735_GREEN, ST7735_BLACK);
 ST7735_DrawString(0, 3*10+3*18, "Font_16x26", Font_16x26, ST7735_BLUE, ST7735_BLACK);
 HAL_Delay(2000);

 // Check colors
 ST7735_FillScreen(ST7735_BLACK);
 ST7735_DrawString(0, 0, "BLACK", Font_11x18, ST7735_WHITE, ST7735_BLACK);
 HAL_Delay(500);

 ST7735_FillScreen(ST7735_BLUE);
 ST7735_DrawString(0, 0, "BLUE", Font_11x18, ST7735_BLACK, ST7735_BLUE);
 HAL_Delay(500);

 ST7735_FillScreen(ST7735_RED);
 ST7735_DrawString(0, 0, "RED", Font_11x18, ST7735_BLACK, ST7735_RED);
 HAL_Delay(500);

 ST7735_FillScreen(ST7735_GREEN);
 ST7735_DrawString(0, 0, "GREEN", Font_11x18, ST7735_BLACK, ST7735_GREEN);
 HAL_Delay(500);

 ST7735_FillScreen(ST7735_CYAN);
 ST7735_DrawString(0, 0, "CYAN", Font_11x18, ST7735_BLACK, ST7735_CYAN);
 HAL_Delay(500);

 ST7735_FillScreen(ST7735_MAGENTA);
 ST7735_DrawString(0, 0, "MAGENTA", Font_11x18, ST7735_BLACK, ST7735_MAGENTA);
 HAL_Delay(500);

 ST7735_FillScreen(ST7735_YELLOW);
 ST7735_DrawString(0, 0, "YELLOW", Font_11x18, ST7735_BLACK, ST7735_YELLOW);
 HAL_Delay(500);

 ST7735_FillScreen(ST7735_WHITE);
 ST7735_DrawString(0, 0, "WHITE", Font_11x18, ST7735_BLACK, ST7735_WHITE);
 HAL_Delay(500);

 // Draw circles
 ST7735_FillScreen(ST7735_BLACK);
 for (int i = 0; i < ST7735_GetHeight() / 2; i += 2)
 {
  ST7735_DrawCircle(ST7735_GetWidth() / 2, ST7735_GetHeight() / 2, i, ST7735_YELLOW);
 }
 HAL_Delay(1000);

 ST7735_FillScreen(ST7735_BLACK);
 ST7735_FillTriangle(0, 0, ST7735_GetWidth() / 2, ST7735_GetHeight(), ST7735_GetWidth(), 0, ST7735_RED);
 HAL_Delay(1000);

 ST7735_FillScreen(ST7735_BLACK);
 ST7735_DrawImage(0, 0, 128, 128, (uint16_t*) test_img_128x128);
 HAL_Delay(3000);

 r++;
}
/* USER CODE END 0 */
Та додаємо код що ініціалізує дисплей, вмикає підсвітку, та по колу запускає демонстрацію:
/* USER CODE BEGIN 2 */
  ST7735_Init();
  ST7735_Backlight_On();
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
   demoTFT();
    /* USER CODE END WHILE */

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

Файли для завантаження

Відео інструкція


Відео-демонстрація