четвер, 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

2 коментарі:

  1. Good day and thanks for this post.
    Can you tell me please, why do yo say that the Alarm functions don't work?, because I'm using them and actually do work.

    ВідповістиВидалити
    Відповіді
    1. Thank you for your feedback. This is a great job. I did not work out. There was an error in the date calculations. Therefore, the alarm clock and date did not match correctly. After the fix I did not try to alarm. This is a good news. Thanks again for the comment.

      Видалити