Repository navigation
Bare metal C Ch15 RTC
한 줄 요약: LSI를 RTC clock source로 사용해 시간/날짜를 설정하고 BCD 값을 읽어 1초마다 UART로 출력한다.
RTC는 일반-purpose timer와 달리 backup domain에 속하는 peripheral이다. Notion에서는 main system과 분리된 영역에서 시간을 유지하는 장치로 정리했다.
RTC register는 일반 peripheral보다 보호 절차가 많다.
PWR clock enable
↓
DBP = backup domain write enable
↓
LSI enable + ready wait
↓
RTC clock source select
↓
RTC enable
↓
WPR unlock sequence
↓
Initialization mode
↓
Time / Date / Prescaler 설정
↓
Initialization mode exit
↓
Synchronization wait
↓
WPR lock
0xCA
↓
0x53
순서로 RTC->WPR에 기록해 write protection을 해제한다.
RTC의 Time/Date field는 BCD 형식을 사용한다.
예:
0x59 → decimal 59
변환 코드는:
((value >> 4U) * 10U) + (value & 0x0FU)
LSI enable
│
▼
Backup Domain 접근 허용
│
▼
RTC source = LSI
│
▼
WPR unlock
│
▼
INIT mode
│
├─ Date
├─ Time
└─ Prescaler
│
▼
INIT exit + sync
│
▼
TR / DR read
│
▼
BCD → Decimal
│
▼
UART output
Ch15_rtc/
├── Inc/rtc.h
├── Src/rtc.c
├── Inc/uart.h
├── Src/uart.c
├── Inc/systick.h
├── Src/systick.c
├── Src/main.c
syscalls.c,sysmem.c, startup file, linker script, CubeIDE metadata/build output은 Ch04에서 다룬 공통 기반이므로 이 페이지에서는 반복하지 않는다. 아래에는 이 chapter 기능 구현에 직접 사용한 source/header를 전부 기록한다.
#ifndef RTC_H__
#define RTC_H__
#include <stdint.h>
#include "stm32f4xx.h"
void rtc_init(void);
uint8_t rtc_convert_bcd2dec(uint8_t value);
uint32_t rtc_date_get_day(void);
uint32_t rtc_date_get_month(void);
uint32_t rtc_date_get_year(void);
uint32_t rtc_time_get_hour(void);
uint32_t rtc_time_get_minute(void);
uint32_t rtc_time_get_second(void);
#endif
#include "rtc.h"
#define PWREN (1U << 28)
#define CR_DBP (1U << 8)
#define CSR_LSION (1U << 0)
#define CSR_LSIRDY (1U << 1)
#define BDCR_BDRST (1U << 16)
#define BDCR_RTCEN (1U << 15)
#define RTC_WRITE_PROTECTION_KEY_1 ((uint8_t)0xCAU)
#define RTC_WRITE_PROTECTION_KEY_2 ((uint8_t)0x53U)
#define RTC_INIT_MASK 0xFFFFFFFFU
#define ISR_INITF (1U << 6)
#define ISR_RSF (1U << 5)
#define WEEKDAY_FRIDAY ((uint8_t)0x05U)
#define TIME_FORMAT_PM (1U << 22)
#define CR_FMT (1U << 6)
#define RTC_ASYNCH_PREDIV ((uint32_t)0x7F)
#define RTC_SYNCH_PREDIV ((uint32_t)0x00F9)
static void _rtc_enable_init_mode(void) { RTC->ISR = RTC_INIT_MASK; }
static void _rtc_disable_init_mode(void) { RTC->ISR = (uint32_t)(~RTC_INIT_MASK); }
static uint8_t _rtc_isActiveflag_init(void) { return (uint8_t)((RTC->ISR & ISR_INITF) == ISR_INITF); }
static uint8_t _rtc_isActiveflag_rs(void) { return (uint8_t)((RTC->ISR & ISR_RSF) == ISR_RSF); }
static uint8_t rtc_init_seq(void)
{
_rtc_enable_init_mode();
while (_rtc_isActiveflag_init() != 1U) {}
return 1U;
}
static uint8_t wait_for_synchro(void)
{
RTC->ISR &= ~ISR_RSF;
while (_rtc_isActiveflag_rs() != 1U) {}
return 1U;
}
static uint8_t exit_init_seq(void)
{
_rtc_disable_init_mode();
return wait_for_synchro();
}
static void rtc_date_config(uint32_t WeekDay, uint32_t Day, uint32_t Month, uint32_t Year)
{
uint32_t temp;
temp = (WeekDay << RTC_DR_WDU_Pos) |
(((Year & 0xF0U) << (RTC_DR_YT_Pos - 4U)) | ((Year & 0x0FU) << RTC_DR_YU_Pos)) |
(((Month & 0xF0U) << (RTC_DR_MT_Pos - 4U)) | ((Month & 0x0FU) << RTC_DR_MU_Pos)) |
(((Day & 0xF0U) << (RTC_DR_DT_Pos - 4U)) | ((Day & 0x0FU) << RTC_DR_DU_Pos));
MODIFY_REG(RTC->DR,
(RTC_DR_WDU | RTC_DR_MT | RTC_DR_MU | RTC_DR_DT | RTC_DR_DU | RTC_DR_YT | RTC_DR_YU),
temp);
}
static void rtc_time_config(uint32_t Format12_24, uint32_t Hours, uint32_t Minutes, uint32_t Seconds)
{
uint32_t temp;
temp = Format12_24 |
(((Hours & 0xF0U) << (RTC_TR_HT_Pos - 4U)) | ((Hours & 0x0FU) << RTC_TR_HU_Pos)) |
(((Minutes & 0xF0U) << (RTC_TR_MNT_Pos - 4U)) | ((Minutes & 0x0FU) << RTC_TR_MNU_Pos)) |
(((Seconds & 0xF0U) << (RTC_TR_ST_Pos - 4U)) | ((Seconds & 0x0FU) << RTC_TR_SU_Pos));
MODIFY_REG(RTC->TR,
(RTC_TR_PM | RTC_TR_HT | RTC_TR_HU | RTC_TR_MNT | RTC_TR_MNU | RTC_TR_ST | RTC_TR_SU),
temp);
}
static void rtc_set_asynch_prescaler(uint32_t AsynchPrescaler)
{
MODIFY_REG(RTC->PRER, RTC_PRER_PREDIV_A, AsynchPrescaler << RTC_PRER_PREDIV_A_Pos);
}
static void rtc_set_synch_prescaler(uint32_t SynchPrescaler)
{
MODIFY_REG(RTC->PRER, RTC_PRER_PREDIV_S, SynchPrescaler);
}
void rtc_init(void)
{
RCC->APB1ENR |= PWREN;
PWR->CR |= CR_DBP;
RCC->CSR |= CSR_LSION;
while ((RCC->CSR & CSR_LSIRDY) != CSR_LSIRDY) {}
RCC->BDCR |= BDCR_BDRST;
RCC->BDCR &= ~BDCR_BDRST;
RCC->BDCR &= ~(1U << 8);
RCC->BDCR |= (1U << 9);
RCC->BDCR |= BDCR_RTCEN;
RTC->WPR = RTC_WRITE_PROTECTION_KEY_1;
RTC->WPR = RTC_WRITE_PROTECTION_KEY_2;
if (rtc_init_seq() != 1U) { /* 실패 처리 생략 */ }
/* 원하는 초기 날짜/시간으로 바꿔도 됨 (BCD로 지정) */
rtc_date_config(WEEKDAY_FRIDAY, 0x19, 0x09, 0x26);
rtc_time_config(TIME_FORMAT_PM, 0x11, 0x59, 0x55);
RTC->CR |= CR_FMT;
rtc_set_asynch_prescaler(RTC_ASYNCH_PREDIV);
rtc_set_synch_prescaler(RTC_SYNCH_PREDIV);
exit_init_seq();
RTC->WPR = 0xFF;
}
uint8_t rtc_convert_bcd2dec(uint8_t value)
{
return (uint8_t)(((value >> 4U) * 10U) + (value & 0x0FU));
}
uint32_t rtc_time_get_hour(void) { return ((RTC->TR & (RTC_TR_HT | RTC_TR_HU)) >> RTC_TR_HU_Pos); }
uint32_t rtc_time_get_minute(void) { return ((RTC->TR & (RTC_TR_MNT | RTC_TR_MNU)) >> RTC_TR_MNU_Pos); }
uint32_t rtc_time_get_second(void) { return ((RTC->TR & (RTC_TR_ST | RTC_TR_SU)) >> RTC_TR_SU_Pos); }
uint32_t rtc_date_get_day(void) { return ((RTC->DR & (RTC_DR_DT | RTC_DR_DU)) >> RTC_DR_DU_Pos); }
uint32_t rtc_date_get_month(void) { return ((RTC->DR & (RTC_DR_MT | RTC_DR_MU)) >> RTC_DR_MU_Pos); }
uint32_t rtc_date_get_year(void) { return ((RTC->DR & (RTC_DR_YT | RTC_DR_YU)) >> RTC_DR_YU_Pos); }
#ifndef UART_H
#define UART_H
#include "stm32f4xx.h"
void uart_init(void);
void uart_write_string(const char *s);
int __io_putchar(int ch);
#endif
#include <stdint.h>
#include "uart.h"
#define GPIODEN (1U << 3)
#define USART3EN (1U << 18)
#define DBG_UART_BAUDRATE 115200U
#define SYS_FREQ 16000000U
#define APB1_CLK SYS_FREQ
#define CR1_TE (1U << 3)
#define CR1_UE (1U << 13)
#define SR_TXE (1U << 7)
static void uart_write(int ch);
static uint16_t compute_uart_bd(uint32_t periph_clk, uint32_t baudrate);
static void uart_set_baudrate(uint32_t periph_clk, uint32_t baudrate);
int __io_putchar(int ch)
{
uart_write(ch);
return ch;
}
void uart_init(void)
{
RCC->AHB1ENR |= GPIODEN;
GPIOD->MODER &= ~(0x3UL << (8 * 2));
GPIOD->MODER |= (0x2UL << (8 * 2));
GPIOD->MODER &= ~(0x3UL << (9 * 2));
GPIOD->MODER |= (0x2UL << (9 * 2));
GPIOD->AFR[1] &= ~(0xFUL << 0);
GPIOD->AFR[1] |= (0x7UL << 0); /* PD8 -> AF7 (USART3_TX) */
GPIOD->AFR[1] &= ~(0xFUL << 4);
GPIOD->AFR[1] |= (0x7UL << 4); /* PD9 -> AF7 (USART3_RX) */
RCC->APB1ENR |= USART3EN;
uart_set_baudrate(APB1_CLK, DBG_UART_BAUDRATE);
USART3->CR1 = CR1_TE;
USART3->CR1 |= CR1_UE;
}
static void uart_write(int ch)
{
while (!(USART3->SR & SR_TXE)) { }
USART3->DR = (ch & 0xFF);
}
void uart_write_string(const char *s)
{
while (*s) uart_write((int)*s++);
}
static uint16_t compute_uart_bd(uint32_t periph_clk, uint32_t baudrate)
{
return (uint16_t)((periph_clk + (baudrate / 2U)) / baudrate);
}
static void uart_set_baudrate(uint32_t periph_clk, uint32_t baudrate)
{
USART3->BRR = compute_uart_bd(periph_clk, baudrate);
}
#ifndef SYSTICK_H_
#define SYSTICK_H_
#include <stdint.h>
#include "stm32f4xx.h"
void systick_msec_delay(uint32_t delay);
#endif /* SYSTICK_H_ */
#include "systick.h"
#define CTRL_ENABLE (1UL << 0)
#define CTRL_CLKSRC (1UL << 2)
#define CTRL_COUNTFLAG (1UL << 16)
/* 코어클럭 16MHz(기본 HSI) 기준 1ms당 클럭 사이클 수 */
#define ONE_MSEC_LOAD 16000UL
void systick_msec_delay(uint32_t delay)
{
SysTick->LOAD = ONE_MSEC_LOAD - 1UL;
SysTick->VAL = 0UL;
SysTick->CTRL = CTRL_CLKSRC;
SysTick->CTRL |= CTRL_ENABLE;
for (uint32_t i = 0; i < delay; i++)
{
while ((SysTick->CTRL & CTRL_COUNTFLAG) == 0UL) { }
}
SysTick->CTRL = 0UL;
}
#include <stdio.h>
#include "rtc.h"
extern void uart_init(void);
void systick_msec_delay(uint32_t delay);
static void display_rtc_calendar(void)
{
uint8_t hh = rtc_convert_bcd2dec((uint8_t)rtc_time_get_hour());
uint8_t mm = rtc_convert_bcd2dec((uint8_t)rtc_time_get_minute());
uint8_t ss = rtc_convert_bcd2dec((uint8_t)rtc_time_get_second());
uint8_t mon = rtc_convert_bcd2dec((uint8_t)rtc_date_get_month());
uint8_t day = rtc_convert_bcd2dec((uint8_t)rtc_date_get_day());
uint8_t yy = rtc_convert_bcd2dec((uint8_t)rtc_date_get_year());
printf("Time : %02d:%02d:%02d Date : %02d-%02d-20%02d\r\n",
hh, mm, ss, mon, day, yy);
}
int main(void)
{
uart_init();
rtc_init();
while (1)
{
display_rtc_calendar();
systick_msec_delay(1000);
}
}
Notion 기록 기준 실제 보드 검증 완료.
초/분/시 증가 확인
날짜 rollover
09-19 → 09-20확인UART로 1초마다 calendar 출력 확인
text = 2260 bytes
data = 0 bytes
bss = 2560 bytes
warning 없음
Notion에는 초기화 직후 잠깐 01-01-2000이 출력될 수 있는 현상을 RTC shadow register 동기화 특성으로 기록해두었다.
RTC는 backup domain 접근과 write-protection 해제 절차가 필요하다.
Time/Date는 BCD field로 저장된다.
초기화 모드와 synchronization 절차를 거쳐야 한다.