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

середа, 23 жовтня 2019 р.

STM32: Бібліотека touchscreen XPT2046 по шині SPI


Передмова

В попередній статті "Бібліотека дисплею ILI9341 по інтерфейсу FSMC" розібрались як виводити зображення на екран дисплею. А в цій статті обговоримо, як зчитувати координати сенсорної панелі при натисканні на неї.
Цей дисплей оснащено резистивним touchscreen який обслуговує контролер XPT2046 або ADS7843. Цей контролер є 12 бітним дискретизатором аналогово-цифрового перетворювача, при натисканні на панель, зчитує напругу по координатам X, Y. Значення напруги перетворює за допомоги АЦП в дискретні значення, та виставляє сигнал готовності даних. По синхронному послідовному інтерфейсу (SPI) можемо запросити дискретні значення координат X та Y зі швидкістю 1.5 - 2.5 Mbits/sec.

CubeMX

За основу візьмемо проект з попередньої статті. Згідно схеми, сенсорну панель підключено до SPI2 мікроконтролеру, де T_SCK - PB13, T_MISO - PB14, T_MOSI - PB15, сигнал чип-селект T_CS - PB12, а сигнал готовності даних при натисканні T_IRQ (T_PEN) - PC5 (виділено червоним):
Схема підключення touchscreen
Завантажимо до CubeMX проект з попередньої статті. В мене це "STM32F407VE_Black_ILI9341.ioc", робимо потрібні налаштування та зберігаємо з іншим ім'ям, як окремий проект.

Налаштування для роботи з touchscreen

Налаштування SPI:

Налаштування SPI
  1. В розділі "Connectivity" обираємо SPI2;
  2. Mode -> Full-Duplex Master;
  3. Prescaller -> 32 (можна спробувати 16), щоб Baud Rate було 1.3125 Mbits/s (2.625 Mbits/s);
  4. Перевірте щоб SPI2 був на шпильках як зазначено на малюнку.

Налаштування GPIO для Chip Select:

Налаштування T_CS
  1. В розділі "System Core" обираємо GPIO;
  2. Тиснемо на PB12;
  3. Налаштовуємо як на малюнку GPIO output level -> High, GPIO mode -> Output Push Pull, GPIO Pull-up, Maximum output speed -> High, User Label -> T_CS;
  4. Перевіряємо щоб назва PB12 була T_CS.

Налаштування GPIO для T_IRQ:

Налаштування T_IRQ
  1. В розділі "System Core" обираємо GPIO;
  2. Тиснемо на PC5;
  3. Налаштовуємо як на малюнку. GPIO mode -> External Interrupt Mode with Rising/Falling edge trigger detection, GPIO Pull-up, User Label -> T_IRQ;
  4. Перевіряємо щоб назва PC5 була T_IRQ.

Налаштування переривання від шпильки T_IRQ:

Налаштування переривань

  1. В розділі "System Core" обираємо NVIC;
  2. Позначаємо "галочкою" (checkbox) "EXTI line [9:5] interrupts".
Налаштування FSMC залишаємо як є з попередньої статті. Або, як проект створили новий, то налаштовуємо, як в статті "STM32: Бібліотека дисплею ILI9341 по інтерфейсу FSMC".

Зберігаємо проект, та генеруємо код проекту. Відкриваємо чи експортуємо проект до свого засобу розробки.

Бібліотека touch screen

Взяв за основу бібліотеку у користувача github . Виправив одну помилку, а так залишив як є. Оригінал бібліотеки за ланкою. Ланка на мою бібліотеку touchscreen. Бібліотека складається з двох файлів XPT2046_touch.h та XPT2046_touch.c 

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

#define XPT2046_SPI_PORT hspi2
extern SPI_HandleTypeDef XPT2046_TOUCH_SPI_PORT;

#define XPT2046_IRQ_Pin       T_IRQ_Pin
#define XPT2046_IRQ_GPIO_Port T_IRQ_GPIO_Port
#define XPT2046_CS_Pin        T_CS_Pin
#define XPT2046_CS_GPIO_Port  T_CS_GPIO_Port

// change depending on screen orientation
#define XPT2046_SCALE_X 240
#define XPT2046_SCALE_Y 320

// to calibrate uncomment UART_Printf line in ili9341_touch.c
#define XPT2046_MIN_RAW_X 3400
#define XPT2046_MAX_RAW_X 29000
#define XPT2046_MIN_RAW_Y 3300
#define XPT2046_MAX_RAW_Y 30000
  1. Назначаємо на якому SPI "сидить" touch панель;
  2. Назначаємо порт і номер шпильки де під'єднано сигнали chip select та сигнал переривання про готовність даних (T_CS, T_IRQ);
  3. Задаємо розподільну здатність екрану по X та Y;
  4. Калібровочні значення touch панелі.
Якщо ви створили проект за допомоги CubeMX, як зазначено вище, то налаштування бібліотеки залишаються як є і додаткових налаштувань не потребує.

Додайте файли бібліотеки до свого проекту. Відкрийте файл "main.c" та додайте рядок:
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "XPT2046_touch.h"
/* USER CODE END Includes */
Спробуйте зкомпілювати проект. Як немає помилок, спробуємо щось намалювати за допомоги touch screen.

Малюємо за допомоги touch screen

Щоб можна було малювати на дисплеї ILI9341 додайте до проекту файли бібліотеки дисплею ILI9341, як зазначено в попередній статті. Це тека inc та src.

Додайте до коду в файл "main.c" проекту рядок з вкладенням файлу бібліотеки дисплея:
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "ili9341.h"
#include "XPT2046_touch.h"
/* USER CODE END Includes */
Спробуйте зкомпілювати проект. Як немає помилок, йдемо далі.

Додайте до коду в файл "main.c" в користувацьку секцію 2 такий код:
/* USER CODE BEGIN 2 */
  lcdBacklightOn();
  lcdInit();
  lcdSetOrientation(LCD_ORIENTATION_PORTRAIT);
  lcdFillRGB(COLOR_BLACK);
  /* USER CODE END 2 */
Вмикаємо підсвітку екрану, ініціалізуємо дисплей, задаємо орієнтацію дисплею, очищаємо дисплей чорним кольором.

В користувацьку секцію 4 файлу "main.c"додайте callback функцію:
void HAL_GPIO_EXTI_Callback (uint16_t GPIO_Pin)
{
 if (GPIO_Pin == T_IRQ_Pin)
 {
  if(XPT2046_TouchPressed())
  {
   uint16_t x = 0, y = 0;
   if(XPT2046_TouchGetCoordinates(&x, &y))
   {
    lcdFillCircle((lcdGetWidth() - x), y, 2, COLOR_GREENYELLOW);
   }
  }

 }
}
До цієї функції мікроконтролер буде потрапляти кожного разу коли ви будете натискати (малювати) на touch панелі. Тут буде перевірятись чи натиснуто на панель, і якщо так, то будуть братись координати натискання, та по цим координатам намалюємо маленьке зафарбоване коло жовто-зеленого кольору.

Компілюйте, заливайте і малюйте.

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

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

Відео



понеділок, 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 додати позначення і порядковий номер в масиві - нового символу.

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

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

четвер, 10 травня 2018 р.

STM32: Годинник реального часу на stm32f1xx. Проблеми, рішення (частина друга).


update 07.10.2018: знайшов помилку в бібліотеці у функції задавання часу. Виправив бібліотеку і трішки змінив саму бібліотеку. Всі функції залишились на своєму місці. Коли викликається функція взяття часу, то крім часу визначається і дата. Коли викликається дата, то просто повертається дата, яку вже вирахували під час взяття часу. Якщо, до взяття дати,  функція взяття часу, ніколи не викликалась, то в функції взяття дати, виклик функції взяття часу, станеться автоматично. Так само автоматично оновиться дата при її зміні. 

Передмова

Це продовження статті "STM32: Годинник реального часу на stm32f1xx. Проблеми, рішення (частина перша)". В першій частині описано проблеми RTC на STM32F1xx серії та невдалі спроби подолати їх за допомоги HAL драйверу. Проблема полягає в тому, що при використанні HAL_RTC - лічильник RTC, який рахує секунди, функціями HAL драйверу, "обрізається" в межах доби. Максимально 86400 секунд. А дата зберігається окремо просто в RAM пам'яті мікроконтролера. Тому при вимкнені мікроконтролеру чи перехід в сон, втрачається дата RTC. Можна записати дату в backup регістри, але це не врятує, якщо мікроконтролер буде без живлення при переході на нову дату. Тому на серії STM32F1xx єдиний вихід правильної роботи RTC це враховувати в лічильнику RTC секунди, як часу так і дати - разом, а за допомоги розрахунків видобувати вже поточну час і дату. Ну і якщо вже використовуємо HAL драйвера та не створювати дублікат бібліотеки HAL_RTC, я просто створив невеличку бібліотеку зі зміненими деякими функціями:
HAL_StatusTypeDef mRTC_GetTime(RTC_HandleTypeDef* hrtc, RTC_TimeTypeDef* sTime, uint32_t Format);
HAL_StatusTypeDef  mRTC_GetDate(RTC_HandleTypeDef* hrtc, RTC_DateTypeDef* sDate, uint32_t Format);
HAL_StatusTypeDef  mRTC_SetTime(RTC_HandleTypeDef *hrtc, RTC_TimeTypeDef *sTime, uint32_t Format);
HAL_StatusTypeDef  mRTC_SetDate(RTC_HandleTypeDef *hrtc, RTC_DateTypeDef *sDate, uint32_t Format);
HAL_StatusTypeDef mRTC_SetAlarm(RTC_HandleTypeDef *hrtc, RTC_AlarmTypeDef *sAlarm, uint32_t Format);
HAL_StatusTypeDef mRTC_SetAlarm_IT(RTC_HandleTypeDef *hrtc, RTC_AlarmTypeDef *sAlarm, uint32_t Format);
Це функції встановлення/взяття часу, та встановелння/взяття дати, а також встановлення будильнику.

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

Налаштування і запуск RTC в CubeMX

Всі налаштування RTC в CubeMX залишаються без змін і не мають ніяких особливостей. Як налаштувати роботу RTC можна переглянути в попередній статті, все абсолютно так само в однойменному розділі "Налаштування і запуск RTC в CubeMX"
Та не забудьте увімкнути переривання від RTC раз на секунду:
Увімкнути переривання від RTC раз на секунду
Це потрібно для демонстрації роботи RTC.

Додаткова бібліотека RTC з модифікованими функціями

Додаткова бібліотека mRTC з модіфікованими функціями містить два файли:
В цій бібліотеці є змінені функції аналогічні функціям HAL драйверу, так і додаткові для зручності використання.

Список всіх функцій додаткової бібліотеки mRTC:
#define TIME_COUNTER(hrtc) mRTC_ReadCounter(hrtc) // повертає значення лічильнику часу
#define ALARM_COUNTER(hrtc) mRTC_ReadAlarmCounter(hrtc) // повертає значення лічильнику будильника
#define SECOND(hrtc, format) mRTC_GetSecond(hrtc, format) // повертає секунди
#define MINUTE(hrtc, format) mRTC_GetMinute(hrtc, format) // повертає хвилини
#define HOUR(hrtc, format) mRTC_GetHour(hrtc, format) // повертає години
#define WEEKDAY(hrtc) mRTC_GetWeekDay(hrtc) // повертає день тижня
#define DAY(hrtc, format) mRTC_GetDay(hrtc, format) // повертає день
#define MONTH(hrtc, format) mRTC_GetMonth(hrtc, format) // повертає місяць
#define YEAR(hrtc, format) mRTC_GetYear(hrtc, format) // повертає рік

void mRTC_Begin(RTC_HandleTypeDef* hrtc); // оновлює дату в буфері, викликати раз після увімкнення MCU
uint8_t mRTC_GetSecond(RTC_HandleTypeDef* hrtc, uint32_t Format); // повертає секунди
uint8_t mRTC_GetMinute(RTC_HandleTypeDef* hrtc, uint32_t Format); // повертає хвилини
uint8_t mRTC_GetHour(RTC_HandleTypeDef* hrtc, uint32_t Format); // повертає години
uint8_t mRTC_GetWeekDay(RTC_HandleTypeDef* hrtc); // повертає день тижня
uint8_t mRTC_GetDay(RTC_HandleTypeDef* hrtc, uint32_t Format); // повертає день
uint8_t mRTC_GetMonth(RTC_HandleTypeDef* hrtc, uint32_t Format); // повертає місяць
uint16_t mRTC_GetYear(RTC_HandleTypeDef* hrtc, uint32_t Format); // повертає рік

HAL_StatusTypeDef mRTC_GetTime(RTC_HandleTypeDef* hrtc, RTC_TimeTypeDef* sTime, uint32_t Format); // повертає час в структуру часу
HAL_StatusTypeDef mRTC_GetDate(RTC_HandleTypeDef* hrtc, RTC_DateTypeDef* sDate, uint32_t Format); // повертає дату в структуру дати
HAL_StatusTypeDef mRTC_SetTime(RTC_HandleTypeDef *hrtc, RTC_TimeTypeDef *sTime, uint32_t Format); // задає час з структури часу
HAL_StatusTypeDef mRTC_SetDate(RTC_HandleTypeDef *hrtc, RTC_DateTypeDef *sDate, uint32_t Format); // задає дату з структури дати

HAL_StatusTypeDef mRTC_SetAlarm(RTC_HandleTypeDef *hrtc, RTC_AlarmTypeDef *sAlarm, uint32_t Format); // задає час будильника в межах доби
HAL_StatusTypeDef mRTC_SetAlarm_IT(RTC_HandleTypeDef *hrtc, RTC_AlarmTypeDef *sAlarm, uint32_t Format); // задає час будильника в межах доби з перериваннями
На початку програми, після увімкнення MCU потрібно один раз викликати функцію mRTC_Begin для оновлення дати в буфері. Щоб не вираховувати кожного разу день тижня, день, місяць і рік у функціях: mRTC_GetWeekDay, mRTC_GetDay, mRTC_GetMonth, mRTC_GetYear, та mRTC_GetDate - значення беруться просто з буферу. Кожного разу коли викликається функція mRTC_GetTime, автоматично оновлюється дата в буфері. Якщо користуватись тільки функцією RTC_GetDate про зміну дня не потрібно турбуватись, функція mRTC_GetTime, при зміні дня, викличеться автоматично. Якщо користуватись функціями взяття окремо годин (mRTC_GetHour), хвилин (mRTC_GetMinute), секунд (mRTC_GetSecond), дня (mRTC_GetDay), місяця (mRTC_GetMonth), року (mRTC_GetYear) і дня тижня (mRTC_GetWeekDay), то теж потрібно самому контролювати вчасне оновлення дати в буфері викликом функції mRTC_Begin, або mRTC_GetTime.

Демонстраційна програма

  • За допомоги CubeMX створіть проект з налаштуваннями RTC, як зазначено вище у розділі "Налаштування і запуск RTC в CubeMX". Дайте йому ім'я, та згенеруйте код ініціалізації для свого засобу розробки.
  • Відкрийте чи імпортуйте проект створений програмою CubeMX у своєму засобі розробки. В мене це Atolic TrueStudio, професійний засіб розробки абсолютно безкоштовний.
  • Додайте файли бібліотеки до свого проекту. Файли з розширенням ".h" до теки "inc", а файли з розширенням ".c" до теки "src"
  • Відрийте файл "main.c" і в зазначені для користувача ділянки коду додаємо демонстраційний код.
Приєднуємо до програми бібліотеку "mRTC":
/* USER CODE BEGIN Includes */
#include "m_rtc.h"
/* USER CODE END Includes */

Оголошуємо глобальні змінні та структури часу і дати:
/* USER CODE BEGIN PV */
/* Private variables ---------------------------------------------------------*/
uint8_t aShowTime[12] = {0};
uint8_t aShowDate[12] = {0};

RTC_TimeTypeDef sTime;
RTC_DateTypeDef sDate;
/* USER CODE END PV */

Оголошуємо прототипи функцій показу часу/дати та встановлення часу/дати:

/* USER CODE BEGIN PFP */
/* Private function prototypes -----------------------------------------------*/
void Show_RTC_Calendar(void);
void setDataTime(void);
/* USER CODE END PFP */

Викликаємо функції: увімкнути переривання RTC кожної секунди, оновити в буфері значення дати, показати дату і час.
  /* USER CODE BEGIN 2 */
  HAL_RTCEx_SetSecond_IT(&hrtc);
  // setDataTime(); // якщо потрібно встановити початковий час і дату, то розкоментуйте рядок
  mRTC_Begin(&hrtc);
  Show_RTC_Calendar();
  /* USER CODE END 2 */

Та не забуваємо написати самі функції в користувацькому блоці 4:
/* USER CODE BEGIN 4 */
void HAL_RTCEx_RTCEventCallback (RTC_HandleTypeDef* hrtc)
{
 Show_RTC_Calendar();
}

void Show_RTC_Calendar(void)
{
 /* Note: need to convert in decimal value in using __LL_RTC_CONVERT_BCD2BIN helper macro */
 /* Display time Format : hh:mm:ss */
 mRTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BIN);
 sprintf((char*)aShowTime,"%.2d:%.2d:%.2d", sTime.Hours, sTime.Minutes, sTime.Seconds);

 /* Display date Format : mm-dd-yy */
 mRTC_GetDate(&hrtc, &sDate, RTC_FORMAT_BIN);
 sprintf((char*)aShowDate,"%.2d-%.2d-%.2d-%.2d", sDate.Date, sDate.Month, sDate.Year, sDate.WeekDay);
}

void setDataTime(void)
{
 sTime.Hours = 21;
 sTime.Minutes = 37;
   sTime.Seconds = 00;

   if(mRTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BIN) != HAL_OK)
   {
  Error_Handler();
   }

   sDate.Date = 9;
   sDate.Month = 5;
   sDate.Year = 18;

   if(mRTC_SetDate(&hrtc, &sDate, RTC_FORMAT_BIN) != HAL_OK)
   {
    Error_Handler();
   }
}
/* USER CODE END 4 */

В такому варіанті кожної секунди буде викликатись функція Show_RTC_Calendar і в масивах aShowTime та aShowDate будуть знаходитись текстові рядки з актуальним часом і датою. А вже цей текст з датою і часом виводьте - чи то на дисплей, чи то в послідовний порт. На ваш розсуд.

RTC Alarm

Також можна встановити будильник за допомоги функцій mRTC_SetAlarm або mRTC_SetAlarm_IT значення лічильника RTC_Alarm встановлюється в межах доби, якщо поточний час більший за значення часу будильника і якщо значення будильника менше значення поточного часу, то будильник встановиться на наступну добу.

Значення лічильника RTC_Alarm встановлюється коректним і по досягненню лічильника RTC значення RTC_Alarm будильника скидається. Але чомусь самого переривання по Alarm не відбувається. Хоча все робив як в прикладі, що йде з HAL драйверами. Запустити будильник мені так і не вдалось: як з рідними HAL драйвером RTC, як за допомоги CMSIS,  так не виходило і за допомоги STD_Periph драйверів, ну і звісно і зараз не вийшло.
Можливо я просто не розумію як працює цей alarm і не правильно користуюсь ним. Як хто має позитивний досвід використання RTC_Alarm на STM32F1xx серії - поділіться цим зі мною.

RTC LL

Зараз в додачу до HAL драйверів з'явились LL драйвер (сучасний нащадок Std_Periph). Пробував запустити RTC на STM32F1xx серії за допомоги LL драйверу. То також повне фіаско. В лічильнику RTC додається одна секунда аж раз на 30 реальних секунд. В кого є позитивний досвід використання RTC за допомоги LL драйверу, теж, будь ласка, відгукніться.

Бібліотека mRTC