понеділок, 9 січня 2017 р.

Дисплей Nokia 5110 на контролері PCD8544 - бібліотека для STM32

За мотивами попереднього поста, написав бібліотечку для роботи із ним за допомогою STM32. Бібліотека ще зовсім сира, але нею вже можна користуватися. В подальшому її код знаходитиметься на Github, але поки прикріпляю архівом. Опис нижче орієнтується на System Workbench for STM32 та STM32F3Discovery, хоча ні те ні те не є принциповим.

Увага! Так як бібліотека мала б розвиватися, дана стаття може доволі швидко застаріти. Але гарантій не дам. :-)

Огляд 

В списку нижче, цифрами вказано номери виносок, розшифрованих в технічних подробицях. Отож, бібліотека:
  • Дозволяє під'єднувати довільну кількість дисплеїв (1).
  • Підтримує вивід тексту: окремих літер, С-стрічок,  чисел, та стрічок, форматованих у стилі printf() (2).
  • Підтримує вивід довільних прямих. (5)
  • Вміє виводити прямокутники, зокрема -- заповнені. (5)
  • Вивід може відбуватися як "білими", так і чорними пікселями.
  • Надає доступ до всіх режимів дисплею та безпосереднього керування його параметрами.
  • На кожному дисплеї може використовуватися декілька незалежних вікон. Із кожним вікном пов'язаний свій "графічний" курсор, який задає лівий верхній кут виводу наступної літери (3).
  • Для кожного дисплея підтримує свій "відео"-буфер, котрий при оновленнях передається на пристрій для відображення.
  • Шрифт вкомільовується в код, моноширинний, але розмір символу не фіксується бібліотекою (4). 
  • У шрифті по замовчуванню підтримується кирилиця -- українська та російська. Кодування -- ASCII + CP1251.
  • На жаль, автоматично генерованої документації в коді ще немає. Планується. Поки дивіться код та користуйтеся цим постом. 


Для більшої гнучкості код складається із трьох частин, кожна із яких представлена парою файлів, .h i .c, плюс файл із шрифтом:
  1. lcd5110_ll.h/lcd5110_ll.c -- низькорівневий код (ll -- low-level), який безпосередньо взаємодіє із дисплеєм, вміє його ініціалізувати, передати низькорівневі команди та дані. 
  2. lcd5110_hal.h/lcd5110_hal.c -- власне, Hardware Abstraction Level. "Абстрагує" організацію RAM дисплея. Реалізовує функції виведення пікселів та растрових зображень (bitmap). Маніпулює лише буфером -- з апаратурою безпосередньо не взаємодіє. (Тому назва може не дуже вдала, але Buffer Abstraction Level звучатиме ще гірше  :-).
  3. lcd5110.h/lcd5110.c -- високорівневий код. Виведення тексту, ліній, прямокутників, реалізація вікон та курсорів -- тут. 
  4. font6x8.h -- шрифт по замовчуванню.
Імена функцій, правда, потребують певного впорядкування...
 В проект слід додати всі сім файлів, але користувачу достатньо включати "lcd5110.h".

Технічні подробиці:
  • Конфігурування дисплею -- динамічне, в стилі HAL. Тобто, створюється структура, котра містить вказівник на дескриптор апаратного SPI, номери пінів та портів RST, CS, DC і передається всім функціям. 
  • Такий підхід повільніший і генерує трохи більший код, ніж визначення пінів, портів, SPI за допомогою #define, але багато гнучкіший. Планую, хоч і без гарантій, в майбутньому написати її реалізацію (можливо, неповну -- лише для тестів) із використанням статичного підходу (#define) та на С++, із використанням шаблонів -- щоб порівняти як зручність різних підходів, так і їх ефективність (з точки зору розміру коду та його швидкодії).
  • (1) Обмеження поточної реалізації:  на даний момент використовує апаратний SPI, тому кількість одночасно керованих дисплеїв обмежується кількістю апаратних SPI. Одночасно підключених дисплеїв може бути значно більше -- завдяки CS, кілька пристроїв можуть знаходитися на одній шині. Планую додати механізм вибору реалізації послідовного інтерфейсу. 
  • Поточна реалізація використовує STM32 HAL. Попередньо виглядає, що спричинені ним накладні витрати тут не є серйозним обмеженням, але в майбутньому планую дослідити це питання ретельніше.
  • Обмеження поточної реалізації: поки пам'ять дисплею оновлюється повністю. Планується можливість часткових оновлень.
  • Обмеження поточної реалізації: ніяких спроб оптимізувати вивід бітмапів поки не зроблено, хоча інфраструктура для того є. 
  • Обмеження поточної реалізації: DMA поки не використовується. Для передачі "відео"-даних у ньому може бути сенс.
  • (2) Якщо користуватиметеся виводом floating-point чисел, не забудьте його підключити (розділ "Додаток: SemiHosting i libc для лінивих", після слів "По замовчуванню, SW4STM32 використовує Newlib-nano без підтримки floating-point чисел при вводі-виводі").
  • (3) Обмеження поточної реалізації: Якийсь Z-порядок вікон поки не передбачений. Котре вікно останнім перемалювалося, те і зверху. При чому, вікна прозорі. Літери, правда, ні. Планується можливість керувати і тим і тим.
  • (4) Вивід літер іде підряд, тому шрифт повинен передбачати порожню вертикальну лінію, щоб символи не зливалися. 
  • Недолік поточної реалізації: CS постійно активний -- дисплей споживає зайве, та й вразливіший до завад на пінах.
  • Взагалі, поки бібліотека надміру конкретна. Потім узагальнюватиму для інших споріднених дисплеїв.
  • Недолік поточної реалізації: майже гарантовано -- доволі багато багів. Всі знайдені виправив, але місця для них -- багато.
  • (5) Обмеження поточної реалізації: лінії та прямокутники поки товщиною лише 1 піксель. Звичайно, можна емулювати товстіші, малюючи декілька ліній поруч. 
  • Обмеження поточної реалізації: не реалізовано прокрутку (scrolling).

Використання

Підключення до проекту та апаратна конфігурація 

Всі сім файлів бібліотеки слід додати до проекту. Файл шрифту можна заміняти на свій.
Так як бібліотека (поки) використовує HAL, виходжу  із того, що проект коду для мікроконтролера створювався в STM32CubeMX -- нижче трохи скріншотів з нього.

Потребує одного SPI (можна однонаправленого -- від Master до Slave) та трьох пінів керування -- RST, CS, DC. Тобто, всього 5. Взагалі, всілякими хитрощами цю кількість можна скорочувати, тут відповідні фокуси не розглядатимуться.

Зверніть увагу на Transmit Only Master. Ніякої шкоди із Full Duplex Master не було б -- крім марної витрати одного піна.
CLK i DATA керуються апаратурою SPI, решта пінів -- GPIO-Output.
  • Пінами DC, RST, CS (іноді також називають CE) керуватиметься програмно -- їх використання визначається контролером дисплея,  тому вони налаштовуються як GPIO-Output. (В мікроконтролері не передбачено апаратних драйверів для PCD8544 і подібних).
  • Тактування шиною SPI задається головним пристроєм, Master. Тому SPIx-SCK мікроконтролера слід під'єднати до відповідного піна дисплея -- CLK.
  • SPI-MOSI -- "Master output -  slave input" є лінією, по якій мікроконтролер передаватиме дані, тому вона під'єднується до DIN (Data-Input) дисплея. 
  • SPI-MISO -- "Master input - slave output" не використовується. Якщо обрати "Transmit Only Master" -- під нього навіть не буде виділено фізичного піна.
SPI -- 8-бітний, MSB first, режим 0 (CPOL==0, CPHA==0), швидкість 4Мбіт/с, налаштовується подільником (Prescaler), який зараз рівний 8:
Зауважте, що швидкість обміну обирається вибором подільника (prescaler) -- на нього буде ділитися тактова частота шини.
 

Hello, world!

Почнемо із класики (код ініціалізації та секцію включень опущено, звичайно, зверху є рядок #include "lcd5110.h"): 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
/* USER CODE BEGIN 0 */
LCD5110_display lcd1;
/* USER CODE END 0 */

int main(void)
{

  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration----------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* Configure the system clock */
  SystemClock_Config();

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_SPI2_Init();

  /* USER CODE BEGIN 2 */
  lcd1.hw_conf.spi_handle = &hspi2;
  lcd1.hw_conf.spi_cs_pin =  LCD1_CS_Pin;
  lcd1.hw_conf.spi_cs_port = LCD1_CS_GPIO_Port;
  lcd1.hw_conf.rst_pin =  LCD1_RST_Pin;
  lcd1.hw_conf.rst_port = LCD1_RST_GPIO_Port;
  lcd1.hw_conf.dc_pin =  LCD1_DC_Pin;
  lcd1.hw_conf.dc_port = LCD1_DC_GPIO_Port;
  lcd1.def_scr = lcd5110_def_scr;
  LCD5110_init(&lcd1.hw_conf, LCD5110_NORMAL_MODE, 0x40, 2, 3);

  LCD5110_print("Hello world!\n", BLACK, &lcd1);

  /* USER CODE END 2 */

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

  /* USER CODE BEGIN 3 */

  }
  /* USER CODE END 3 */

}

Отримаємо:
 
На жаль, поки краще сфотографувати не маю можливості, тому фото в цьому пості -- по мінімуму, не знущатимуся над читачами. Додам кращі і більше в майбутньому. (Взагалі, це певне перше фото мого авторства у цьому блозі. :-)

Аналізуємо код.
  • В рядку номер 2 створюємо дескриптор, що описує конкретний дисплей. Його тип --  LCD5110_display.
  • В рядках 25-31 заповнюємо поле структури дескриптора hw_conf, котре описує апаратну конфігурацію -- які піни та апаратний SPI використано. 
  • Рядок 32 задає вікно по замовчуванню -- яке використовуватиметься, коли конкретне вікно не вказано. Займає весь екран.
  • LCD5110_init() ініціалізує дисплей, задавши, крім апаратної конфігурації також режим відображення, напругу на LCD, контраст та температурний коефіцієнт . Це низькорівнева функція, із lcd5110_ll.
  • В рядку 35 виводиться текст, функцією LCD5110_print(), яка приймає стрічку, колір та, власне, дескриптор дисплея. 
Ось і все.

Однак, є важливий нюанс -- вивід відбувається в буфер. Щоб він потрапив на екран -- слід подати відповідну команду. По замовчуванню вона подається автоматично у відповідь на символ нового рядка, '\n'. Якщо є потреба відправити буфер на дисплей раніше, слід користуватися функцією LCD5110_refresh(). Зокрема, будьте уважні під час відладки -- до її виклику на екрані не буде оновлень! З іншого боку, можна заборонити оновлення у відповідь на '\n'.

Для прикладу виведемо трохи геометричних фігур. Щоб не треба було кожного разу наново прошивати мікроконтролер, скористаємося простою функцією, яка зупиняє роботу, поки не натиснули користувацьку кнопку нашої демоплати STM32F3Discovery:

1
2
3
4
5
6
7
void wait_for_button1_pressed()
{
 while( HAL_GPIO_ReadPin(B1_GPIO_Port, B1_Pin) == GPIO_PIN_RESET ){}
 HAL_Delay(50);
 while( HAL_GPIO_ReadPin(B1_GPIO_Port, B1_Pin) == GPIO_PIN_SET ){}
 HAL_Delay(50);
}

Тоді код може виглядати якось так:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
  wait_for_button1_pressed();
  LCD5110_clear_scr(&lcd1);

  LCD5110_line(1, 1, 30, 30, BLACK, &lcd1);
  LCD5110_line(1, 1, 3, 30, BLACK, &lcd1);
  LCD5110_line(1, 1, 30, 3, BLACK, &lcd1);

  LCD5110_line(35, 35, 20, 20, BLACK, &lcd1);
  LCD5110_line(35, 35, 2, 20, BLACK, &lcd1);
  LCD5110_line(35, 35, 20, 2, BLACK, &lcd1);

  LCD5110_line(83, 47, 20, 20, BLACK, &lcd1);
  LCD5110_line(83, 1,  20, 20, BLACK, &lcd1);
  LCD5110_line(1,  47, 20, 20, BLACK, &lcd1);

  LCD5110_refresh(&lcd1);

  wait_for_button1_pressed();
  LCD5110_clear_scr(&lcd1);
  rect_t rect={5,5, 22, 33};
  LCD5110_rect(&rect, 1, &lcd1);
  LCD5110_refresh(&lcd1);

  wait_for_button1_pressed();
  LCD5110_clear_scr(&lcd1.hw_conf);
  LCD5110_rect_fill(&rect, 1, &lcd1);
  LCD5110_refresh(&lcd1);

Найважливіша із нових функцій --  LCD5110_refresh(), яка пересилає сформоване в буфері зображення на екран.

Для очищення екрану використано функцію LCD5110_clear_scr().

LCD5110_line() малює лінію. Їй передаються координати початку та кінця, колір і дескриптор дисплею.. На жаль, поки товщина лінії -- завжди один піксель.

Система координат наступна:

LCD5110_rect() і LCD5110_rect_fill() малюють прямокутник та заповнений прямокутник, відповідно. Їм передається структура типу rect_t, яка містить координати лівого верхнього краю, ширину та висоту прямокутника:

1
2
3
4
typedef struct
{
 int16_t x0, y0, width, height;
} rect_t;

Декілька дисплеїв

Насправді, тут і коментувати немає особливо що:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
LCD5110_display lcd1;
LCD5110_display lcd2;
//...........................

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_SPI2_Init();
  MX_SPI3_Init();

  /* USER CODE BEGIN 2 */
  lcd1.hw_conf.spi_handle = &hspi2;
  lcd1.hw_conf.spi_cs_pin =  LCD1_CS_Pin;
  lcd1.hw_conf.spi_cs_port = LCD1_CS_GPIO_Port;
  lcd1.hw_conf.rst_pin =  LCD1_RST_Pin;
  lcd1.hw_conf.rst_port = LCD1_RST_GPIO_Port;
  lcd1.hw_conf.dc_pin =  LCD1_DC_Pin;
  lcd1.hw_conf.dc_port = LCD1_DC_GPIO_Port;
  lcd1.def_scr = lcd5110_def_scr;
  LCD5110_init(&lcd1.hw_conf, LCD5110_NORMAL_MODE, 0x40, 2, 3);

  LCD5110_print("Hello world!\n", BLACK, &lcd1);

  lcd2.hw_conf.spi_handle = &hspi3;
  lcd2.hw_conf.spi_cs_pin =  LCD2_CS_Pin;
  lcd2.hw_conf.spi_cs_port = LCD2_CS_GPIO_Port;
  lcd2.hw_conf.rst_pin =  LCD2_RST_Pin;
  lcd2.hw_conf.rst_port = LCD2_RST_GPIO_Port;
  lcd2.hw_conf.dc_pin =  LCD2_DC_Pin;
  lcd2.hw_conf.dc_port = LCD2_DC_GPIO_Port;
  lcd2.def_scr = lcd5110_def_scr;
  LCD5110_init(&lcd2.hw_conf, LCD5110_INVERTED_MODE, 0x40, 2, 3);

  LCD5110_set_cursor(20, 20, &lcd2);
  LCD5110_print("Hello world!\n", BLACK, &lcd2);

Нова функція -- LCD5110_set_cursor(). Курсор -- графічний, він задає лівий верхній край літери, що буде виведено наступною.


Зауважте, зображення на другому дисплеї -- інвертоване. Також видно, що відбувається автоматичне перенесення тексту.

Форматований вивід



1
2
3
4
  char str[] = "Text";
  LCD5110_printf(&lcd1, BLACK, "This is %s!\n", str);
  LCD5110_printf(&lcd1, BLACK, "X = %i, y = %i\n", 5, 17);
  LCD5110_printf(&lcd1, BLACK, "a = %f,\ny = %5.2e\n", 3.14, 56.17e5);

Насправді, в реалізації використано повноцінну функцію із сімейства printf() -- vsnprintf(), тому доступні всі її можливості:


Пам'ятайте додати: "-u _printf_float" чи "-u _printf_float -u _scanf_float" до Properties (проекту) -> C/C++ Build -> Settings -> MCU GCC Linker -> Miscellaneous -> Linker flags. Інакше виведення double просто нічого не виводитиме.

Зображення

Для виведення зображення повинно бути присутнім у пам'яті. (Як його прочитати, скажімо, із SD-карти, поговоримо в майбутньому). Якщо воно буде у тому форматі, який розуміє дисплей -- по біту на піксель, рядок байт представляє 8 рядків зображення, вивід відбуватиметься тривіально.

Так як над високорівневим представленням зображень ще йде робота, тут ми скористаємося низькорівневою функцією, LCD5110_drawBitmap(), якій слід передавати апаратний дескриптор дисплею.

Для перетворення зображення в послідовність байт, можна пошукати програмки в Інтернеті, хоча, всі знайдені мною на цей момент "якісь не такі" -- малофункціональні або/і незручні або/і глючні. Краща, яка поки трапилася: "GLCD Utils" (без проблем скомпілювалася Qt 5.4+GCC+Win32-64). Також вона видається хорошою в ролі генератора шрифтів, але про це -- нижче.

Отож, наступну картинку, розміром 40 на 40 пікселів:
можна представити в коді так:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
const unsigned char smile[] = 
{ 0x00, 0x00, 0x00, 0x00, 0x80, 0xc0, 0xe0, 0xf0,
  0xf8, 0x7c, 0x7c, 0x3c, 0x1e, 0x1e, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
  0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x1f, 0x1e, 0x1e, 0x3c, 0x7c, 0x7c, 0xf8,
  0xf0, 0xe0, 0xc0, 0x80, 0x00, 0x00, 0x00, 0x00, 0xc0, 0xf0, 0xfe, 0xff,
  0xff, 0x0f, 0x07, 0x01, 0x00, 0x00, 0x00, 0xf0, 0xfc, 0xfe, 0xfe, 0xfe,
  0xf8, 0xf0, 0x00, 0x00, 0x00, 0x00, 0xf0, 0xfc, 0xfe, 0xfe, 0xfe, 0xfc,
  0xf0, 0x00, 0x00, 0x02, 0x03, 0x07, 0x0f, 0xff, 0xff, 0xfc, 0xf0, 0x00,
  0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x40, 0xc0, 0xc0, 0xc0,
  0xc3, 0xc7, 0xc7, 0xc3, 0xc3, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc3,
  0xc7, 0xc7, 0xc7, 0xc3, 0xc0, 0xc0, 0xc0, 0xc0, 0x00, 0x00, 0x00, 0x00,
  0xff, 0xff, 0xff, 0xff, 0x03, 0x07, 0x3f, 0x7f, 0xff, 0xf8, 0xf0, 0xc0,
  0x80, 0x03, 0x07, 0x0f, 0x1f, 0x1f, 0x3f, 0x7f, 0x7f, 0x7f, 0xff, 0xff,
  0xff, 0xff, 0x7f, 0x7f, 0x3f, 0x3f, 0x1f, 0x1f, 0x0f, 0x07, 0x01, 0x80,
  0xe0, 0xf0, 0xf8, 0xff, 0x7f, 0x1f, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x03, 0x03, 0x07, 0x0f, 0x1f, 0x1e, 0x3e, 0x3c, 0x3c, 0x78, 0x78,
  0x78, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0x78, 0x78, 0x78, 0x3c, 0x3c,
  0x3e, 0x1e, 0x1f, 0x0f, 0x07, 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00 //
  };

а вивести її:

1
2
  LCD5110_drawBitmap(5, 5, smile, 40, 40, 0, &lcd2.hw_conf);
  LCD5110_refresh(&lcd2);

Можна навіть організувати анімацію:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
  int i = 0;
  while (1)
  {
  /* USER CODE END WHILE */
   HAL_Delay(70);
   LCD5110_clear_scr(&lcd2);
   LCD5110_drawBitmap(5+i, 5, smile, 40, 40, 0, &lcd2.hw_conf);
   LCD5110_refresh(&lcd2);
   ++i;
   if(i>84+40)
    i=-40;

  /* USER CODE BEGIN 3 */

  }


Вікна

При виводі у вікно, воно відсікає все, що виходить за його межі. Кожне вікно має свій курсор. Координати об'єктів, що виводяться у вікні, відраховуються від його лівого верхнього краю.

Для кожної функції, що працює із цілим екраном, існує аналог, з іменем виду LCD5110_wXXXX(), який працює з вказаним вікном. Наприклад:

1
2
void LCD5110_print (const char* str, int color,                        LCD5110_display* lcd_conf);
void LCD5110_wprint(const char* str, int color, LCD5110_canvas_t* win, LCD5110_display* lcd_conf);


Увага! Реалізація вікон поки не дуже зручна, ймовірно -- малоефективна і мала б містити баги.

Іншими словами, якось так:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
  LCD5110_canvas_t win1={ {1, 8, 30, 20}, {2, 2}, 1};
  LCD5110_canvas_t win2={ {35, 5, 30, 20}, {2, 2}, 1};
  LCD5110_canvas_t win3={ {10, 10, 70, 30}, {2, 2}, 1};


  LCD5110_win_draw_frame(1, &win1, &lcd1);
  LCD5110_win_draw_frame(1, &win2, &lcd1);
  LCD5110_refresh(&lcd1);

  rect_t r1={3, 8, 50, 33};
  rect_t r1s={10, 10, 2, 2};
  LCD5110_wrect(&r1, 1, &win1, &lcd1);
  LCD5110_wrect(&r1s, 1, &win1, &lcd1);
  LCD5110_wline(5,5, 10, 20, 1, &win2, &lcd1);

  LCD5110_refresh(&lcd1);
  wait_for_button1_pressed();

  LCD5110_wrect_fill(&r1, 1, &win1, &lcd1);
  LCD5110_refresh(&lcd1);
  wait_for_button1_pressed();

  LCD5110_wrect_fill(&r1, 0, &win1, &lcd1);
  LCD5110_refresh(&lcd1);
  wait_for_button1_pressed();

  LCD5110_wprintf(&lcd1, BLACK, &win1, "This is window 1\n");
  LCD5110_refresh(&lcd1);
  wait_for_button1_pressed();

  LCD5110_wprintf(&lcd1, BLACK, &win2, "Window 2 here.\n");
  LCD5110_refresh(&lcd1);
  wait_for_button1_pressed();
  LCD5110_win_draw_frame(1, &win3, &lcd1);
  LCD5110_wprintf(&lcd1, BLACK, &win3, "Owerlapped_windows - window 3 was last.\n");
  LCD5110_refresh(&lcd1);

Початковий курсор задаємо у вигляді {2, 2} -- інакше він затиратиме рамку вікна. Саму рамку малює спеціалізована функція LCD5110_win_draw_frame().

Використано ряд "w"-indowed варіантів розглянутих раніше функцій.


Подробиці реалізації

Загальні зауваження:
  • Дрібні функції реалізовую як inline -- активно не люблю макроси в такій ролі, С++-ник, все ж,але і робити такі дрібні функції повноцінними -- соромно неефективно.
  • Бібліотека вважає, що всі піни та SPI-модуль вже є правильно ініціалізованими. Можливо, в майбутньому додам функцію-callback у ініціалізацію, яка, за потреби, займатиметься відповідною ініціалізацією, на манер XXX_MspInit() функцій HAL.
Що приємно -- PVS-studio не знайшла підозрілих місць в коді бібліотеки! Було лише одне попередження про схожі вирази у створенні вікон -- де такий код зумисне присутній. 

Низькорівневий код -- lcd5110_ll

Оголошує розміри дисплею, константи для обох кольорів та структуру-дескриптор LCD5110_conf. Буфер знаходиться якраз в цій структурі.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#define BLACK 1
#define WHITE 0

#define LCD_WIDTH 84
#define LCD_HEIGHT 48
#define LCD5110_BUFFER_SIZE (LCD_WIDTH * LCD_HEIGHT/8)

//...............................

typedef struct LCD5110_conf
{
 SPI_HandleTypeDef* spi_handle;

 uint16_t    spi_cs_pin;
 GPIO_TypeDef *spi_cs_port;

 uint16_t    rst_pin;
 GPIO_TypeDef *rst_port;

 uint16_t    dc_pin;
 GPIO_TypeDef *dc_port;

 unsigned char video_buffer[LCD5110_BUFFER_SIZE]; // LCD buffer

} LCD5110_conf;

Найважливішою є функція ініціалізації:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
int LCD5110_init(LCD5110_conf*   lcd_conf,
     LCD5110_modes   dmode,
     uint8_t         voltage,
     uint8_t      temp_coeff,
     uint8_t      bias
){
 SPI_enable(lcd_conf);
 LCD5110_CE_off(lcd_conf);

 //LCD5110_VCC_on();
 LCD5110_RST_off(lcd_conf); // Minimum 100 ns, maximum not limited (tbl. 12 AC CHARACTERISTICS, pic. 16)
 volatile int i = 100; // HAL_Delay() too slow, do not want to depend on some delay_us here.
 while (--i){}
 LCD5110_RST_on(lcd_conf);

 LCD5110_DC_off(lcd_conf); // Commands mode on
 //! Extended commands (bit H==1), horizontal addressing
 LCD5110_set_function(LCD5110_FN_SET_H_MASK, lcd_conf);
 //! Set display voltage
 LCD5110_set_voltage_ext(voltage, lcd_conf);
 //! Set temperature coefficient
 LCD5110_set_temp_coef_ext(temp_coeff, lcd_conf);
 //! Set bias
 LCD5110_set_bias_ext(bias, lcd_conf);


 //! Basic commands (bit H==0), horizontal addressing
 LCD5110_set_function(0, lcd_conf);
 //! Set display mode
 LCD5110_set_mode_base(dmode, lcd_conf);

 return LCD5110_OK;
 //!TODO: Check for transmission end and turn CE off (set it to HIGH).
}

Як бачимо, вона виконує ту послідовність, яку було описано в попередньому пості. Для своєї роботи використовує ряд статичних -- внутрішніх, функцій цього файлу (їх видно по суфіксах _ext i _base).

Для всіх команд оголошено відповідні бітові маски (див. код!), для режимів дисплею -- перерахування LCD5110_modes, в файлі заголовків, решта -- в реалізації, так як користувацький код не мав би до них звертатися. Кожна така функція виклику команди бере вхідні дані та намагається із них сформувати правильну команду. Якщо це неможливо, повертає код помилки із перерахування LCD5110_errors.

Виглядають ці функції якось так:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
static inline void LCD5110_CE_off(LCD5110_conf* lcd_conf) {
 HAL_GPIO_WritePin(lcd_conf->spi_cs_port, lcd_conf->spi_cs_pin, GPIO_PIN_RESET);
}

//.........................

//! H==0
static int LCD5110_set_mode_base(LCD5110_modes mode_byte, LCD5110_conf* lcd_conf)
{
 if ( (mode_byte & (~LCD5110_INVERTED_MODE) ) != 0) //0b10x0y -- only possible values
 {
  //printf("Seq: err\n");
  return LCD5110_bad_dmode;
 }
 //printf("Seq: %i\n", mode_byte);
 send_byte_to_LCD5110(mode_byte, lcd_conf);
 return LCD5110_OK;
}

Звичайно, функцію ініціалізації можна реалізувати у вигляді передачі "стрічки ініціалізації", відповідної послідовності байт, але реалізація при цьому ускладниться, стане важчою для сприйняття, а виграш буде мізерним.

Другою важливою функцією є функція передачі даних:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
void LCD5110_refresh_ll(LCD5110_conf* lcd_conf)
{
 LCD5110_CE_off(lcd_conf);
 LCD5110_DC_off(lcd_conf);
 LCD5110_set_XY_base(0, 0, lcd_conf);
 LCD5110_DC_on(lcd_conf);

 send_data_to_LCD5110(lcd_conf->video_buffer, LCD_HEIGHT*LCD_WIDTH/8, lcd_conf);
//!TODO: Check for transmission end and turn CE off (set it to HIGH).
}

Фізичною передачею даних послідовним інтерфейсом займаються дві функції, які покладаються на HAL:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
static inline HAL_StatusTypeDef send_byte_to_LCD5110(uint8_t dat, LCD5110_conf* lcd_conf)
{
 //! HAL_SPI_Transmit takes care about waiting transmission to finish.
 //! Details: https://habrahabr.ru/post/276605/ -- do not turn command mode off
 //! before transmission finished. (Check BSY flag before DC_on/off, if directly
 //! manipulating SPIx_DR.
 return HAL_SPI_Transmit(lcd_conf->spi_handle, &dat, 1, 1000);
}

static inline HAL_StatusTypeDef send_data_to_LCD5110(uint8_t data[], uint16_t size, LCD5110_conf* lcd_conf)
{
 return HAL_SPI_Transmit(lcd_conf->spi_handle, data, size, 1000);
}


Думаю, є сенс передбачити можливість заміни інтерфейсу, аж по використання чисто програмної реалізації (bit banging). Корисним може бути і DMA. Детальніше про SPI взагалі та у реалізації STM32 --  зокрема, напишу в майбутньому.
Якщо коротко, HAL_SPI_Transmit() приймає дескриптор того апаратного SPI, з яким працюємо, адресу масиву даних -- байт чи символів (char в С/С++ -- синонім байта), їх кількість та таймаут в мілісекундах. Автоматично чекає, поки SPI звільниться, передає дані та чекає завершення передачі.

Останнє -- важливо, бо, навіть якщо SPI був не зайнятий, після запису в його регістр даних, DR, передача лише розпочинається. Якщо, не дочекавшись її завершення, зняти той же CS -- буде передано не всі біти, і контролер дисплея "дещо здивується": "правило, которая Алиса никогда не забывала -- что если выпить слишком много из пузырька с надписью "Яд", то почти наверняка, рано или поздно, почувствуешь недомогание". Детальніше  див., наприклад, "STM32: SPI: LCD — Вы всё делаете не так [восклицательный знак]".

Крім того, модуль надає три користувацькі функції керування режимами дисплею:


1
2
3
int LCD5110_set_mode(LCD5110_modes mode, LCD5110_conf* lcd_conf);
int LCD5110_set_temp_coef(uint8_t TC, LCD5110_conf* lcd_conf);
int LCD5110_set_bias(uint8_t bias, LCD5110_conf* lcd_conf);

З міркувань безпеки, явна функція керування напругою не передбачена. Їх реалізація, на відміну від внутрішніх функцій модуля, вибирає потрібний набір команд:

1
2
3
4
5
6
7
8
9
//! User mode function -- takes care about correct instruction set
int LCD5110_set_bias(uint8_t bias, LCD5110_conf* lcd_conf)
{
 LCD5110_set_function(LCD5110_FN_SET_H_MASK, lcd_conf); // H==1, basic instruction set
 LCD5110_DC_off(lcd_conf); // Commands mode on
 int ec = LCD5110_set_bias_ext(bias, lcd_conf);
 LCD5110_DC_on(lcd_conf); // Data mode on -- default
 return ec;
}


Низькорівневий графічний код -- lcd5110_hal


Цей модуль займається формуванням зображення та шрифтами.

Шрифт являє собою файл заголовків, font6x8.h  по замовчуванню, наступного формату:

1
2
3
4
5
6
7
8
static const unsigned int base_font_width = 6;
static const unsigned int base_font_height = 8;
static const unsigned char base_font[] = {
 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //00 0
 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //01 1
 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //02 2
 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //03 3
....................................

Містить всі символи кодування ASCII та CP1251. Напевне, варто, для економії пам'яті, перших 32 викинути -- зараз вони порожні. Константи base_font_width і base_font_height задають розмір однієї літери в пікселях. Правий стовпчик мав би бути завжди порожнім -- щоб літери не налазили одна на одну.

Шрифт створено генератором із "Arduino, модуль Nokia 5110 LCD и кириллица", на базі шрифту від його автора, із доведенням до ладу вручну. (Якщо кому треба, маю цей генератор, нашвидкоруч перенесений під Python 3).

Пізніше натрапив на інструмент, який видається значно серйознішим -- вміє імпортувати системні шрифти, змінювати їх параметри, і т.д.: "Making custom fonts for GLCD library", "glcd-utils". Систематично не випробовував, але виглядає дуже цікавим. От просто іконки воно генерує так собі...

Наявні в цьому модулі функції говорять самі за себе:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
void LCD5110_clrscr(LCD5110_conf* lcd_conf);
void LCD5110_fillscr(LCD5110_conf* lcd_conf);

void LCD5110_putpix(int x, int y, int mode, LCD5110_conf* lcd_conf);
void LCD5110_setpix(int x, int y, LCD5110_conf* lcd_conf);
void LCD5110_clrpix(int x, int y, LCD5110_conf* lcd_conf);
void LCD5110_invpix(int x, int y, LCD5110_conf* lcd_conf);
void LCD5110_fill_region(int x0, int y0, int w, int h, int color, LCD5110_conf* lcd_conf);

void LCD5110_drawBitmap(int x, int y, const unsigned char* bitmap, int cols, int rows, int invert, LCD5110_conf* lcd_conf);

На жаль, поки виведення зображень не оптимізовано та не реалізовано ніяких способі його взаємодії із фоном, крім заміщення.

За подробицями -- дивіться код.


Високорівневий графічний код -- lcd5110

Власне, єдиний код, по справжньому цікавий користувачу. Так як принципи його функціонування та способи використання описувалися вище, тут на ньому не зупинятимуся. Повний список функцій див. у lcd5110.h  -- вони достатньо очевидні. Більшість використовуються у прикладах вище.


Завершальне слово


Сподіваюся, дана бібліотека розвиватиметься, і ця стаття скоро застаріє. В майбутньому код буде на Github, а поки його можна скачати тут. Крім того, демонстраційний проект для SW4STM32 (на прикладі плати STM32F3 Discovery), який містить купу зайвого, зате зразу "готовий до вживання" -- тут.
 
Update 2020-11-18: оновив архіви -- виправлено проблему із inline, очищення екрану/вікна повертає курсор у лівий верхній кут, додано тест кирилиці.

На сьогодні -- все,

Дякую за увагу!

47 коментарів:

  1. Дякую! Дуже цікаво та корисно. Як завжди. Очикую наступних Ваших туторіалів!

    ВідповістиВидалити
  2. Я как - то просил растолковать считывания импульсов с датчика расхода. Вопрос - все еще актуален. Но появилась еще одна тема. Почти новогодняя. Помните, возможно, когда то была мода на цвето(свето) музыкальные приставки? Они все были аналоговые. И простые - были назойливыми, а сложные - содержали такие узлы как сложные фильтры и компрессоры.
    Время идет, и подобные аналоговые схемы, вероятно, проще заменить цифровыми?
    Вот здесь человек занимается чем-то подобным на PIC.
    http://catcatcat.d-lan.dp.ua/ch-svetomuzyika/uzlyi-svetomuzyiki/filtr-nizkih-chastot-ot-maxim/
    Я просто уверен, что на STM32 подобная задача реализуется проще и лучше - скорости другие, и частота дискретизации АЦП. И количествто АЦП. Возможно, Вам захочется создать что-то подбное в качестве урока? Тем более что об АЦП я ничего у Вас пока не нашел :)

    ВідповістиВидалити
    Відповіді
    1. Дякую за відгук! :-)

      АЦП, безперечно, плануються! Включаючи як внутрішні STM32, так зовнішні, наприклад AD7417 (схема вже на столі стоїть). Але гарантії щодо часу дати не можу, крім того, перед ними, певне, будуть BMP180/BMP280, DHT11/DHT22, керовані RGB-світлодіоди WS2812 (світломузику згадуючи), можливо ще щось. Ті ж апаратні SPI та I2C в STM32 давно вже просяться...

      Щодо приставок -- нічого не скажу, мало стикався, але хороші аналогові схеми залишаються актуальними -- DSP не така тривіальна задача, щоб його чого-будь застосовувати. Зокрема, згадані за Вашим посиланням MAX7400 і т.д. -- аналогові! Просто він за допомогою мікроконтролера керує смугою фільтрації.

      Щодо підрахунку імпульсів різними каналами одного таймера -- знайшлася в мене задачка під то, але коли до неї дійде час -- поняття не маю...

      Видалити
  3. Ну, я просто страничку дал неудачно - автор описывал фильтры для ЦМУ на переключаемых конденсаторах. Просто, как пример темы. В любом случае, спасибо. Очень жду продолжения Ваших уроков. Вероятно, скоро они смогут превратиться в очень любопытную книжку... Если посмотреть - ничего подобного пока нет :)

    ВідповістиВидалити
  4. Кстати, о книжке - хорошо бы все "причесать", перевести на бесплатный и общедоступный компилятор SW4STM32 с применением, как Вы делали SMSIS и HAL, параллельно.

    ВідповістиВидалити
    Відповіді
    1. В ідеалі - так і планується. :-) Але коли... STM32 може встигнути застаріти до того часу!

      Тільки, увага, SW4STM32 -- не компілятор! Це середовище розробки (IDE). А компілятор у всіх "контролерних" постах цього блогу був поки єдиний -- GCC. Хоча, компілятор від Keil теж мав би скоро бути -- планую випробувати, як їхнє середовище (разом із їх компілятором) дружить із С++.

      Видалити
  5. С Форума
    http://kazus.ru/forums/showthread.php?t=14588
    (с)
    Для алгоритма и расчёта цифровых фильтров пользую вот эту страничку. Очень просто: выбираете тип фильтра из "Баттерворт", "Бессель", "Чебышев" и ФНЧ, ФВЧ, полосовой или заградительный, далее порядок фильтра, макс. = 10, далее вводите частоту дискретизации сигнала и необходимые частоты среза фильтра. Нажимаете Submit и на следующей страничке имеете пример кода на С, значения коэффициентов, АЧХ полученного фильтра и графики реакции на ступеньку.

    http://www-users.cs.york.ac.uk/~fisher/mkfilter/trad.html

    ВідповістиВидалити
  6. Дякую. Допис цікавий. Колись теж дійду до цього дисплею і ваша праця згодиться. Але від розмитих світлин очі сльозяться і болять :)))

    ВідповістиВидалити
    Відповіді
    1. Дякую! :-)

      Зразу, користуючись нагодою -- у Вас дуже корисний пост про RTC в STM32F1! :-) На жаль, у Вас коментувати не можу -- з Google+ не дружу...

      Про світлини -- згоден. :-( Бачили б Ви, на що вони робилися і скільки це часу забрало -- я вже задумався, що простіше намалювати.:-) Тому абсолютний мінімум їх.

      Видалити
    2. Дякую за оцінку мого допису про RTC.
      Статтю оновив 22 січня, бо спочатку в коді не враховувались дні у "відключці", думав що враховується :)
      Тепер все працює як слід - перевірено.

      Видалити
    3. На рахунок google+ не зрозумів. А хіба ви свої дописи не в blogger.com створюєте як і я? Це ж одна й та сама контора - google :)))

      Видалити
    4. Контора, звичайно, та ж! Але тут, у мене, можна коментувати, залогінившись із gmail (теж проект Google, звичайно), livejournal, wordpress, і т.д., аж по -- анонімно (останнє -- до пори-до часу, звичайно).

      А у Вас від мене вимагало реєстрацію у google+ -- соцмережі від Google, де я поки реєструватися не хочу. :-(

      Видалити
  7. Уважаемый автор!
    Пытаюсь использовать эту библиотеку для stmf103c8t6, но не работает, бьюсь несколько дней, но экран никак не реагирует.
    Учитывая, ваш код основан на HAL, то по идее должен иметь 100% переносимость.
    Пины переконфигурировал под свой камень. Код собирается нормально.
    Пожалуйста, подскажи где может быть зарыта собака?

    ВідповістиВидалити
    Відповіді
    1. Прошу прощения, все работает, полагаю отходил контакт на макетке.
      Спасибо за проделанную работу и расжеванный материал!
      Добрых успехов в инжиниринге!

      Видалити
    2. Як то кажуть, 95% проблем із електронікою -- в контактах...

      І Вам дякую за відгук! :-)

      Переносимість справді мала б бути високою. Якщо б проблема не вирішилася, варто би було перевіряти частоту, контраст (його значення доволі сильно відрізняються для ніби однакових китайських модулів) і живлення -- є модулі, які хочуть 5В живлення, а є, що 3.3В (візуально майже однакові, знову ж таки).

      Видалити
    3. Частоту -- тактування мікроконтролера, звичайно.

      Видалити
    4. Не компилируется в кокосе, контроллер stmf103c8t6, не видит spi.h и gpio.h, куб их не генерирует, подскажите начинающему откуда они берутся?

      Видалити
    5. Частина відповіді -- внизу. А щодо того, що не генерує -- я традиційно ставлю галочку в: Project settings -> Code Generator -> Generate peripheral initialization as pair of '.c/.h' files per peripheral. Так в згенерованому коді легше колупатися.

      Якщо Ви без неї згенерували -- у Вас все це лежатиме в main.c (і може ще якихось файликах -- не дуже знайомий із таким режимом).

      Як би там не було, spi.h потрібен заради змінних hspi1 і т.д. та правильного вибору файлів HAL, змінити код, щоб він не був потрібен -- тривіально. Якщо самостійно Вам не вдасться -- напишіть, на днях (на жаль, традиційно, жорстко бракує часу) закину варіант без них. (Але тоді проблеми з іншого боку почнуть вилазити -- для кожного іншого контролера треба буде включати свій .h файл HAL).

      Видалити
    6. Автор видалив цей коментар.

      Видалити
    7. Щодо вибору .h-файлу, мається на увазі, інакше доведеться писати щось таке:

      #ifdef STM32F303xC
      #include ‹stm32f3xx.h›
      #elif defined STM32F407xx
      #include ‹stm32f4xx.h›
      #elif defined STM32F072xB
      #include ‹stm32f0xx.h›
      #elif defined STM32L053xx
      #include ‹stm32l0xx.h›
      #else
      #error Please, add your MPU CMSIS header
      #endif

      Видалити
  8. Не компілюється в кокосі,помилка вlcd5110_ll.h, пише нема spi.h i gpio.h

    ВідповістиВидалити
    Відповіді
    1. Так це файли (разом із відповідними *.c файлами), згенеровані STM32CubeMX, коли ви підключаєте SPI чи використовуєте GPIO. У них знаходиться код ініціалізації відповідної периферії і структури, що описують конкретні SPI та порти для викликів HAL, типу:
      SPI_HandleTypeDef hspi1;

      Звичайно, якщо периферію ініціалізуєте самостійно, легко можна модифікувати код, щоб йому ці файли не були потрібними.

      Видалити
    2. Ну і див. відповідь на комент трішки вище -- там детальніше написав. :-)

      Видалити
    3. Поставив галку, потрібні файли згенерувались, але тепер помилка collect2.exe: error: ld returned 1 exit status

      Видалити
    4. А весь текст помилки? :-) Ви тільки заголовок до нього дали, критичні подробиці -- наступними рядками.

      P.S. На жаль, робота з MCU не дуже допускає загальних рішень -- коду, який просто працює завжди, завжди доводиться в якихось таких нюансах колупатися.

      Видалити
    5. Помітив, що ця помилка виникає коли я в проект добавляю startup файл.

      Видалити
    6. Але я таки питаюся не про це. ;-) Воно (про startup) корисний натяк, але текст помилки на 95% зразу б прояснив.

      Видалити
    7. Starting link
      [cc] arm-none-eabi-gcc -mcpu=cortex-m3 -mthumb -g2 -Wl,-Map=fito.map -O0 -Wl,--gc-sections -LD:/Coocox/fito -Wl,-TD:/Coocox/fito/stm32f103c8tx_flash.ld -g -o fito.elf ..\obj\stm32f1xx_hal_dma.o ..\obj\lcd5110_ll.o ..\obj\stm32f1xx_hal_flash_ex.o ..\obj\stm32f1xx_hal_cortex.o ..\obj\stm32f1xx_hal_gpio.o ..\obj\stm32f1xx_hal_spi.o ..\obj\gpio.o ..\obj\lcd5110.o ..\obj\stm32f1xx_hal_tim_ex.o ..\obj\stm32f1xx_hal_rcc_ex.o ..\obj\stm32f1xx_hal_spi_ex.o ..\obj\stm32f1xx_hal.o ..\obj\stm32f1xx_hal_gpio_ex.o ..\obj\stm32f1xx_it.o ..\obj\system_stm32f1xx.o ..\obj\lcd5110_hal.o ..\obj\spi.o ..\obj\startup_stm32f103xb.o ..\obj\main.o ..\obj\stm32f1xx_hal_msp.o ..\obj\stm32f1xx_hal_flash.o ..\obj\stm32f1xx_hal_tim.o ..\obj\stm32f1xx_hal_pwr.o ..\obj\stm32f1xx_hal_rcc.o
      [cc] ..\obj\main.o: In function `main':
      [cc] D:\Coocox\fito\Src/main.c:106: undefined reference to `LCD5110_print'
      [cc] collect2.exe: error: ld returned 1 exit status
      Я так розумію помилка лінкера

      Видалити
    8. А от це дивно!

      Скаржиться воно, що не знайшло тіла функції LCD5110_print(). Але ця функція, як inline, оголошена в lcd5110.h -- її тіло і не мало б бути потрібним.

      Ймовірно, Ви цей хідер забули включити. Тоді, неявне оголошення функцій в С(*) все рівно дало скомпілюватися об'єктному файлу, а вже лінкер виявив, що такої функції немає (бо тіло інлайн-функції просто підставляється у місце виклику, без створення "фізичної" функції (**)).

      Додайте #include "lcd5110.h" там, де використовуєте ф-ції дисплея. (Можливо, IDE треба буде вказати, де він лежить, як це зробити -- див., наприклад, картинку після слів "5. Слід ще підказати середовищу, де шукати щойно додані файли заголовків:" тут: http://indrekis2.blogspot.com/2016/10/lsm303dlhc-hal.html, але воно сильно залежить від IDE).

      _______
      (*) Брррр, гидота! :-) -- воно вважає всі неоголошені функції такими, що приймають рівно передані аргументи і повертають int.
      (**) Так не завжди є, але не будемо зараз відволікатися на особливості inline функцій.

      Видалити
    9. нічого не виходить, не розуміє кокос LCD5110_print("Hello world!\n", BLACK, &lcd1); Якщо закоментувати цей рядок, то все компілюється.

      Видалити
    10. Ви точно все інклуднули?

      Як крайня міра -- в lcd5110.h заберіть inline перед оголошенням цієї ф-ції, а тіло її перенесіть в lcd5110.c.

      В сенсі, в .h замість:
      inline void LCD5110_print(const char* str, int color, LCD5110_display* lcd_conf){
      LCD5110_wprint(str, color, &lcd_conf->def_scr, lcd_conf);
      }
      має бути:
      void LCD5110_print(const char* str, int color, LCD5110_display* lcd_conf);

      А в .c:
      void LCD5110_print(const char* str, int color, LCD5110_display* lcd_conf){
      LCD5110_wprint(str, color, &lcd_conf->def_scr, lcd_conf);
      }

      -----------------------
      Але спершу акуратно перевірте, чи lcd5110.h всюди включений, де ця ф-ція викликається!

      Видалити
  9. Indrekis, использую библиотеку в среде FreeRTOS. Есть непонятное поведение при попытке вывести на экран переменную типа float. Если не указывать линкеру указанные в тексте ключи, то код, как и написано, хоть и работает, но на экран не выводится.
    Если же ключ указать, то программа так же хорошо компилируется, но виснет при выводе этого числа.

    ВідповістиВидалити
    Відповіді
    1. Хм! Дивно. Спробую подивитися, хоча зараз критично не маю часу, тому не знаю, коли вдасться...

      Взагалі, код тієї бібліотеки жахливий (все ніяк не пофікшу хоча б відомі проблеми), але додаткових проблем під FreeRTOS не мав би породжувати.

      А взагалі printf() із підтримкою floating point, який виводить хоч кудись (UART, semihosting, навіть sprintf() в пам'ять) у вас під FreeRTOS працює?

      Видалити
    2. Надо попробовать, еще пока не не проверял.
      А вообще, что это за этакий ход конем, указывать дополнительные ключи для линкера? Что именно происходит, когда мы подсовываем ему их?

      Видалити
    3. Це така оптимізація в Newlib -- ввід та вивід FP доволі дорогий, вимагає багато коду і помітну кількість RAM. Тому, по замовчуванню він просто ігнорується -- для економії. (Очевидно, стоїть виклик якихось weak-функцій, які просто нічого не роблять.) А ті ключі кажуть прилінкувати повноцінну підтримку.

      Суть, що бібліотека ніякого аналізу не робить -- вона вміє виводити лише текст, а текст для нетривіального форматування генерується vsnprintf() із NewLib.

      До речі, версія. А у вас буфер достатнього розміру? Бо без FP може в нього все вміщалося, а з -- перестало?

      Трішки детальніше ще тут: http://indrekis2.blogspot.com/2016/10/hc-sr04-gpiohal.html

      Видалити
    4. Как раз, пока Вы писали ответ, выяснил, что все уперлось в RAM, выделенную для задачи.
      Увеличил кучу и размер стека для этой задачи, все успешно заработало.
      Спасибо за помощь!

      Видалити
    5. Stew hiki можно ли где то увидеть ваши проекты с использованием FreeRTOS? Интересует логика построения программы, передача данных в задачи и т.д.

      Видалити
  10. скажить будласка звiдки в проектi взялися файли spi.h, hal.h i т.п.?
    я початкiвець, програмую под stm32 лише 2-3 днi..
    програмую под лiнуксом.
    начеб б то розiбрався iз stm32cubeMX (елементарне пiдклюення spi, i2c,uart i т.п.), створив IDE на основi qtcreator i вона навiть працюе)))
    бачу, що i у вас використовуется cubemx з його бiблiотеками (hal,ll), але мiй cubemx (останноi на данний час версii - 4.26.1, MCU 1.6.1 для stm32f1) не генеруе цiх файkiв. на томiсть генеруе stm32f1xx_hal_gpio.h, stm32f1xx_ll_gpio.h, тощо...
    якi ж пiдедyeднувати менi? мабудь менi придеться пiдiбрати потрiбнi, але хотiлося би знати напевно..

    ps: прошу вибачення за "кривопис" - немае деяких литер..

    ВідповістиВидалити
    Відповіді
    1. в настройках проекту потрiбно вибрати герерацiю проекту там створити h i c файли

      Видалити
  11. Користуюсь CubeMX та Keil, компілятор видає таке:

    LCD\LCD.axf: Error: L6218E: Undefined symbol LCD5110_init (referred from main.o).
    LCD\LCD.axf: Error: L6218E: Undefined symbol LCD5110_wprint (referred from main.o).
    LCD\LCD.axf: Error: L6218E: Undefined symbol lcd5110_def_scr (referred from main.o).

    Що можна зробити?

    ВідповістиВидалити
  12. А вот что делать с:
    Error: L6218E: Undefined symbol itoa (referred from lcd5110.o).
    По ходу надо писать свою реализацию этой функции :(

    ВідповістиВидалити
  13. Вітаю!
    Дуже був би Вам вдячний за виготовлення біліотеки з українським шрифтом для LCD 5110.
    В інтернеті такої немає.

    ВідповістиВидалити
    Відповіді
    1. На жаль, пропустив Ваше повідомлення, як і купу інших -- тут зламалася система нотифікації.

      Файл font6x8.h із цієї бібліотеки містить українські та російські літери, включаючи ІіЇїЄєҐґ та ЫыЭэЁёЪъ, в кодуванні CP1251

      Видалити
  14. не могли бы вы дать доступ к этой библиотеке?

    ВідповістиВидалити