14. STM32. Програмування STM32F103. RTC
Годинник реального часу (RTC)
STM32 мають вбудований годинник реального часу. Він може працювати незалежно від основного живлення мікроконтролера. Для роботи вбудованого годинника до спеціального виводу треба підключити живлення напругою 3В. Наприклад, батарейку CR2032. Такі батарейки використовують у годинниках, у комп`ютерах на материнських платах, та в інших приладах. Годинник споживає дуже мало енергії, тому батарейки вистачає на тривалий час. Також годинник може працювати як будильник - формувати сигнал на одному з виходів або виводити мікроконтролер з режиму енергозбереження. Спочатку ми запустимо годинник і налаштуємо таким чином, щоб він рахував секунди і продовжував працювати після відключення основного живлення мікроконтролера. Будильник розглянемо пізніше.Перш ніж перейти до використання годинника, слід знати, що у STM32 є так званий Battery backup domain. Це частина мікроконтролера, яка може живитися від батарейки. До нього належать наш годинник та регістри налаштування, LSE генератор, тобто схема обслуговування зовнішнього низькочастотного (32768 Гц) кварцу, виводи PC13..PC15. Крім того, ця зона у STM32F103C8 містить 42 16-бітних регістри, які зберігають свої значення, доки на вхід, до якого підключається батарейка, подається напруга. Їх можна використовувати для своїх задач. Використання цих регістрів теж розглянемо пізніше.
Отже, нам треба запустити RTC. Робити це потрібно тільки за умови, якщо RTC ще не ввімкнутий. Після того, як ми налаштуємо RTC, його налаштування будуть зберігатися у регістрах, поки не відключимо батарейку. Копирсатися у налаштуваннях, коли годинник вже "на ходу" не слід. Тому функція ініціалізації годинника має такий вигляд:
unsigned char RTC_Init(void)
{
// Дозволити тактування модулів управління живленням і управлінням резервної областю
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);
// Дозволити доступ до області резервних даних
PWR_BackupAccessCmd(ENABLE);
// Якщо годинник вимкнений - ініціалізувати
if ((RCC->BDCR & RCC_BDCR_RTCEN) != RCC_BDCR_RTCEN)
{
// Виконати скидання області резервних даних
RCC_BackupResetCmd(ENABLE);
RCC_BackupResetCmd(DISABLE);
// Вибрати джерелом тактових імпульсів зовнішній кварц 32768 і подати тактування
RCC_LSEConfig(RCC_LSE_ON);
while ((RCC->BDCR & RCC_BDCR_LSERDY) != RCC_BDCR_LSERDY) {}
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);
RTC_SetPrescaler(0x7FFF); // Встановити поділювач, щоб годинник рахував секунди
// Вмикаємо годинник
RCC_RTCCLKCmd(ENABLE);
// Чекаємо на синхронізацію
RTC_WaitForSynchro();
return 1;
}
return 0;
}
Запишемо у мікроконтролер програму, текст якої наведено у кінці статті. Підключимо батарейку. Вмикаємо живлення мікроконтролера. Після першого включення мікроконтролер запустить функцію ініціалізації та ввімкне RTC. Під час наступних запусків, функція буде вмикати лише тактування потрібних модулів і вмикати доступ до області резервних даних (PWR_BackupAccessCmd(ENABLE);). Це нам потрібно, щоб отримати доступ до лічильника RTC. Перша ініціалізація RTC - досить тривалий процес. Тому, хай Вас не лякає затримка при старті мікроконтролера, яка може тривати близько секунди. У подальшому мікроконтролер стартує без затримок. Якщо відключити живлення мікроконтролера і живлення від батарейки, налаштування RTC будуть скинуті.
Лічильник RTC зчитується функцією RTC_GetCounter(). Ми отримаємо час у секундах. Лишається тільки перевести секунди у звичайний формат часу. Встановити лічильник на конкретну дату можна за допомогою функції RTC_SetCounter(). Функції слід передати час у секундах.
Я дуже засмутився через те, що у цьому контролері такий куций RTC. У інших серіях контролерів ініціалізація RTC відбувається через структуру, де можна задати дату, день тижня та час у звичному форматі. Та зчитувати дату та час у структури. Тобто, у тих контролерах реальний годинник, який сам враховує високосні роки, кількість днів у місяці та таке інше. У нашому випадку він хіба що тягне на назву "таймер, який може працювати від батарейки". Але це не біда. Треба добавити трохи коду. Ось Вам структура і дві функції. Перша функція переводить секунди у звичайну дату і час. Друга функція вираховує секунди по заданій у структурі даті та часу. З методикою розрахунків можна ознайомитися тут.
Приклад програми, яка ініціалізує RTC та встановлює початковий час тільки під час першої ініціалізації. Дані про дату і час виводяться у порт USART.
#include "stm32f10x.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_rtc.h"
#include "stm32f10x_rcc.h"
#include "stm32f10x_pwr.h"
#include "stm32f10x_usart.h"
#include "stdio.h"
#include "misc.h"
#include "tim2_delay.h"
void SetSysClockToHSE(void)
{
ErrorStatus HSEStartUpStatus;
/* SYSCLK, HCLK, PCLK2 and PCLK1 configuration -----------------------------*/
/* RCC system reset(for debug purpose) */
RCC_DeInit();
/* Enable HSE */
RCC_HSEConfig( RCC_HSE_ON);
/* Wait till HSE is ready */
HSEStartUpStatus = RCC_WaitForHSEStartUp();
if (HSEStartUpStatus == SUCCESS)
{
/* Enable Prefetch Buffer */
//FLASH_PrefetchBufferCmd( FLASH_PrefetchBuffer_Enable);
/* Flash 0 wait state */
//FLASH_SetLatency( FLASH_Latency_0);
/* HCLK = SYSCLK */
RCC_HCLKConfig( RCC_SYSCLK_Div1);
/* PCLK2 = HCLK */
RCC_PCLK2Config( RCC_HCLK_Div1);
/* PCLK1 = HCLK */
RCC_PCLK1Config(RCC_HCLK_Div1);
/* Select HSE as system clock source */
RCC_SYSCLKConfig( RCC_SYSCLKSource_HSE);
/* Wait till PLL is used as system clock source */
while (RCC_GetSYSCLKSource() != 0x04)
{
}
}
else
{ /* If HSE fails to start-up, the application will have wrong clock configuration.
User can add here some code to deal with this error */
/* Go to infinite loop */
while (1)
{
}
}
}
void usart_init(void)
{
/* Enable USART1 and GPIOA clock */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);
/* Configure the GPIOs */
GPIO_InitTypeDef GPIO_InitStructure;
/* Configure USART1 Tx (PA.09) as alternate function push-pull */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/* Configure USART1 Rx (PA.10) as input floating */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/* Configure the USART1 */
USART_InitTypeDef USART_InitStructure;
/* USART1 configuration ------------------------------------------------------*/
/* USART1 configured as follow:
- BaudRate = 115200 baud
- Word Length = 8 Bits
- One Stop Bit
- No parity
- Hardware flow control disabled (RTS and CTS signals)
- Receive and transmit enabled
- USART Clock disabled
- USART CPOL: Clock is active low
- USART CPHA: Data is captured on the middle
- USART LastBit: The clock pulse of the last data bit is not output to
the SCLK pin
*/
USART_InitStructure.USART_BaudRate = 115200;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_Init(USART1, &USART_InitStructure);
/* Enable USART1 */
USART_Cmd(USART1, ENABLE);
}
void USARTSend(char *pucBuffer)
{
while (*pucBuffer)
{
USART_SendData(USART1, *pucBuffer++);
while(USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET)
{
}
}
}
//========================================================================================
unsigned char RTC_Init(void)
{
// Дозволити тактування модулів управління живленням і управлінням резервної областю
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);
// Дозволити доступ до області резервних даних
PWR_BackupAccessCmd(ENABLE);
// Якщо годинник вимкнений - ініціалізувати
if ((RCC->BDCR & RCC_BDCR_RTCEN) != RCC_BDCR_RTCEN)
{
// Виконати скидання області резервних даних
RCC_BackupResetCmd(ENABLE);
RCC_BackupResetCmd(DISABLE);
// Вибрати джерелом тактових імпульсів зовнішній кварц 32768 і подати тактування
RCC_LSEConfig(RCC_LSE_ON);
while ((RCC->BDCR & RCC_BDCR_LSERDY) != RCC_BDCR_LSERDY) {}
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);
RTC_SetPrescaler(0x7FFF); // Встановити поділювач, щоб годинник рахував секунди
// Вмикаємо годинник
RCC_RTCCLKCmd(ENABLE);
// Чекаємо на синхронізацію
RTC_WaitForSynchro();
return 1;
}
return 0;
}
/*
* Як конвертувати дату можна прочитати тут:
* https://ru.m.wikipedia.org/wiki/%D0%AE%D0%BB%D0%B8%D0%B0%D0%BD%D1%81%D0%BA%D0%B0%D1%8F_%D0%B4%D0%B0%D1%82%D0%B0
*/
// (UnixTime = 00:00:00 01.01.1970 = JD0 = 2440588)
#define JULIAN_DATE_BASE 2440588
typedef struct
{
uint8_t RTC_Hours;
uint8_t RTC_Minutes;
uint8_t RTC_Seconds;
uint8_t RTC_Date;
uint8_t RTC_Wday;
uint8_t RTC_Month;
uint16_t RTC_Year;
} RTC_DateTimeTypeDef;
// Get current date
void RTC_GetDateTime(uint32_t RTC_Counter, RTC_DateTimeTypeDef* RTC_DateTimeStruct) {
unsigned long time;
unsigned long t1, a, b, c, d, e, m;
int year = 0;
int mon = 0;
int wday = 0;
int mday = 0;
int hour = 0;
int min = 0;
int sec = 0;
uint64_t jd = 0;;
uint64_t jdn = 0;
jd = ((RTC_Counter+43200)/(86400>>1)) + (2440587<<1) + 1;
jdn = jd>>1;
time = RTC_Counter;
t1 = time/60;
sec = time - t1*60;
time = t1;
t1 = time/60;
min = time - t1*60;
time = t1;
t1 = time/24;
hour = time - t1*24;
wday = jdn%7;
a = jdn + 32044;
b = (4*a+3)/146097;
c = a - (146097*b)/4;
d = (4*c+3)/1461;
e = c - (1461*d)/4;
m = (5*e+2)/153;
mday = e - (153*m+2)/5 + 1;
mon = m + 3 - 12*(m/10);
year = 100*b + d - 4800 + (m/10);
RTC_DateTimeStruct->RTC_Year = year;
RTC_DateTimeStruct->RTC_Month = mon;
RTC_DateTimeStruct->RTC_Date = mday;
RTC_DateTimeStruct->RTC_Hours = hour;
RTC_DateTimeStruct->RTC_Minutes = min;
RTC_DateTimeStruct->RTC_Seconds = sec;
RTC_DateTimeStruct->RTC_Wday = wday;
}
// Convert Date to Counter
uint32_t RTC_GetRTC_Counter(RTC_DateTimeTypeDef* RTC_DateTimeStruct) {
uint8_t a;
uint16_t y;
uint8_t m;
uint32_t JDN;
a=(14-RTC_DateTimeStruct->RTC_Month)/12;
y=RTC_DateTimeStruct->RTC_Year+4800-a;
m=RTC_DateTimeStruct->RTC_Month+(12*a)-3;
JDN=RTC_DateTimeStruct->RTC_Date;
JDN+=(153*m+2)/5;
JDN+=365*y;
JDN+=y/4;
JDN+=-y/100;
JDN+=y/400;
JDN = JDN -32045;
JDN = JDN - JULIAN_DATE_BASE;
JDN*=86400;
JDN+=(RTC_DateTimeStruct->RTC_Hours*3600);
JDN+=(RTC_DateTimeStruct->RTC_Minutes*60);
JDN+=(RTC_DateTimeStruct->RTC_Seconds);
return JDN;
}
void RTC_GetMyFormat(RTC_DateTimeTypeDef* RTC_DateTimeStruct, char * buffer) {
const char WDAY0[] = "Monday";
const char WDAY1[] = "Tuesday";
const char WDAY2[] = "Wednesday";
const char WDAY3[] = "Thursday";
const char WDAY4[] = "Friday";
const char WDAY5[] = "Saturday";
const char WDAY6[] = "Sunday";
const char * WDAY[7]={WDAY0, WDAY1, WDAY2, WDAY3, WDAY4, WDAY5, WDAY6};
const char MONTH1[] = "January";
const char MONTH2[] = "February";
const char MONTH3[] = "March";
const char MONTH4[] = "April";
const char MONTH5[] = "May";
const char MONTH6[] = "June";
const char MONTH7[] = "July";
const char MONTH8[] = "August";
const char MONTH9[] = "September";
const char MONTH10[] = "October";
const char MONTH11[] = "November";
const char MONTH12[] = "December";
const char * MONTH[12]={MONTH1, MONTH2, MONTH3, MONTH4, MONTH5, MONTH6, MONTH7, MONTH8, MONTH9, MONTH10, MONTH11, MONTH12};
sprintf(buffer, "%s %d %s %04d",
WDAY[RTC_DateTimeStruct->RTC_Wday],
RTC_DateTimeStruct->RTC_Date,
MONTH[RTC_DateTimeStruct->RTC_Month -1],
RTC_DateTimeStruct->RTC_Year);
}
//========================================================================================
int main(void)
{
char buffer[80] = {`\0`};
uint32_t RTC_Counter = 0;
RTC_DateTimeTypeDef RTC_DateTime;
SetSysClockToHSE();
TIM2_init();
usart_init();
if (RTC_Init() == 1) {
// Якщо перша ініціалізація RTC Встановлюємо початкову дату, наприклад 22.09.2016 14:30:00
RTC_DateTime.RTC_Date = 22;
RTC_DateTime.RTC_Month = 9;
RTC_DateTime.RTC_Year = 2016;
RTC_DateTime.RTC_Hours = 14;
RTC_DateTime.RTC_Minutes = 30;
RTC_DateTime.RTC_Seconds = 00;
// Після ініціалізації потрібна затримка. Без неї час не встановлюється.
delay_ms(500);
RTC_SetCounter(RTC_GetRTC_Counter(&RTC_DateTime));
}
while(1)
{
RTC_Counter = RTC_GetCounter();
sprintf(buffer, "\r\n\r\nCOUNTER: %d\r\n", (int)RTC_Counter);
USARTSend(buffer);
RTC_GetDateTime(RTC_Counter, &RTC_DateTime);
sprintf(buffer, "%02d.%02d.%04d %02d:%02d:%02d\r\n",
RTC_DateTime.RTC_Date, RTC_DateTime.RTC_Month, RTC_DateTime.RTC_Year,
RTC_DateTime.RTC_Hours, RTC_DateTime.RTC_Minutes, RTC_DateTime.RTC_Seconds);
USARTSend(buffer);
// Функція генерує у буфері дату власного формату
RTC_GetMyFormat(&RTC_DateTime, buffer);
USARTSend(buffer);
/* delay */
while (RTC_Counter == RTC_GetCounter()) {
}
}
}
Бажаю успіхів!
Дивись також:
- 1. STM32. Програмування STM32F103. Тестова плата. Прошивка через UART та через ST-Link
- 2. STM32. Програмування. IDE для STM32
- 3. STM32. Програмування STM32F103. GPIO
- 4. STM32. Програмування STM32F103. Тактування
- 5. STM32. Програмування STM32F103. USART
- 6. STM32. Програмування STM32F103. NVIC
- 7. STM32. Програмування STM32F103. ADC
- 8. STM32. Програмування STM32F103. DMA
- 9. STM32. Програмування STM32F103. TIMER
- 10. STM32. Програмування STM32F103. TIMER. Захоплення сигналу
- 11. STM32. Програмування STM32F103. TIMER. Encoder
- 12. STM32. Програмування STM32F103. TIMER. PWM
- 13. STM32. Програмування STM32F103. EXTI
- 14. STM32. Програмування STM32F103. RTC
- 15. STM32. Програмування STM32F103. BKP
- 16. STM32. Програмування STM32F103. Flash
- 17. STM32. Програмування STM32F103. Watchdog
- 18. STM32. Програмування STM32F103. Remap
- 19. STM32. Програмування STM32F103. I2C Master
- 20. STM32. Програмування STM32F103. I2C Slave
- 21. STM32. Програмування STM32F103. USB
- 22. STM32. Програмування STM32F103. PWR
- 23. STM32. Програмування STM32F103. Option bytes
- 24. STM32. Програмування STM32F103. Bootloader
- STM32. Скачати приклади
- System Workbench for STM32 Інсталяція на Ubuntu
- Keil uVision5 – IDE для STM32
- IAR Workbench – IDE для STM32
- Керування безколекторним двигуном постійного струму (BLDC) за допомогою STM32
- Керування PMSM за допомогою STM32
Привіт славному місту Кременчук! Андрію, дуже вдячний вам за цикл по програмуванню STM32. Але - чому не HAL? На HAL є готові функції для роботи з RTC. Питання по темі. Не можу допетрити як отримати дату з назвою дня неділі та назвою місяця (наприклад: Середа 21 Вересня 2016) . У файлі stm32f1xx_hal_rtc.h є дефайни днів та місяців, співставлення до номеру дня та місяця. А як можна це використати навпаки? Вельми вдячний за увагу. P.S. І все ж таки - чи плануєте перехід на HAL?
Доброго дня. HAL і FreeRTOS планується розглянути пізніше. Ще буде з десяток статей з використанням SPL. Що до дати у текстовому форматі, я дороблю приклад де буде видно як це можна зробити.
Дякую. Чекаю з нетерпінням. Можлива у вас є якісь проекти де використовуються кнопки для корекції часу і дати. Або де почитати. Контролер STM32F103C8.
Трохи доробив приклад. Зверніть увагу на функцію RTC_GetMyFormat. Вона формує час у форматі типу Thursday 22 October 2016.
Дякую за увагу. Буду розбиратись. Чекаю HAL.
пан Микола, ось тут http://stm32withoutfear.blogspot.com/2016/12/RTC-stm32f1xx.html перегляньте варіант RTC за допомоги HAL драйверу
#include "tim2_delay.h" Поясніть, будь ласка, де взяти цей файлик і куди покласти...
Прямуєте на сторінку «STM32. Скачати приклади» http://www.avislab.com/blog/stm32-examples/ Знайдіть потрібний приклад, у даному випадку https://github.com/avislab/STM32F103/tree/master/Example_RTC І там є усі файли проекту. Також можна скачати всі приклади одним ZIP-архівом: https://github.com/avislab/STM32F103/archive/master.zip
Там проект для CooCox. А як для Keil ? Тут же для Keil чи ні ?
Немає різниці Keil, CooCox, IAR. Кругом "C" і однакові бібліотеки. Просто додайте *.h та *.c файли у свій проект.
Доброго дня! Підкажіть, будь ласка, в чому може бути проблема. Підключаю батарейку до відповідного входу, RTC працює при виключеному живлені, проте напруга батерйки з'являється на живленні.
Недавні записи
- Text to speech. Українська мова
- LCD Display ST7567S (IIC)
- Розпізнавання мови (Speech recognition)
- Selenium
- Комп'ютерний зір (Computer Vision)
- Деякі думки про точність вимірювань в електроприводі
- Датчики Холла 120/60 градусів
- Модуль драйверів напівмосту IGBT транзисторів
- Драйвер IGBT транзисторів на A316J
- AS5600. Варіант встановлення на BLDC мотор
Tags
docker sensors rtc led timer wifi remap sms css websocket mongodb bme280 rfid bluetooth esp8266 nodemcu bkp piezo bmp280 i2c mpu-6050 encoder examples avr brushless ngnix nvic displays bldc java-script pmsm barometer pwm lcd ethernet stm32 raspberry-pi capture usart gpio exti atmega meteo solar smd dma adc eeprom eb-500 flask python ssd1306 uart options flash mpx4115a html gps st-link 3d-printer servo dc-dc foc git books battery hih-4000 watchdog dht11 web tim programmator ssd1331 mpu-9250 usb rs-232 motor max1674 soldering
Архіви