Skip to content

Bare metal C Ch15 RTC

Hongjayeong-02 edited this page Sep 21, 2026 · 2 revisions

Ch15. Real-Time Clock (RTC)

한 줄 요약: LSI를 RTC clock source로 사용해 시간/날짜를 설정하고 BCD 값을 읽어 1초마다 UART로 출력한다.

1. 핵심 이론

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

Write protection key

0xCA
  ↓
0x53

순서로 RTC->WPR에 기록해 write protection을 해제한다.

BCD

RTC의 Time/Date field는 BCD 형식을 사용한다.

예:

0x59 → decimal 59

변환 코드는:

((value >> 4U) * 10U) + (value & 0x0FU)

핵심 register

Register | 역할 -- | -- RTC->TR | Time RTC->DR | Date RTC->ISR | init/synchronization flags RTC->PRER | prescaler RTC->WPR | write protection RCC->BDCR | backup-domain / RTC clock PWR->CR | backup domain write access

2. 동작 흐름

LSI enable
   │
   ▼
Backup Domain 접근 허용
   │
   ▼
RTC source = LSI
   │
   ▼
WPR unlock
   │
   ▼
INIT mode
   │
   ├─ Date
   ├─ Time
   └─ Prescaler
   │
   ▼
INIT exit + sync
   │
   ▼
TR / DR read
   │
   ▼
BCD → Decimal
   │
   ▼
UART output

3. 실제 프로젝트 구조


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를 전부 기록한다.

4. 전체 코드

Inc/rtc.h

#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

Src/rtc.c

#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-&gt;CSR |= CSR_LSION;
while ((RCC-&gt;CSR &amp; CSR_LSIRDY) != CSR_LSIRDY) {}

RCC-&gt;BDCR |= BDCR_BDRST;
RCC-&gt;BDCR &amp;= ~BDCR_BDRST;

RCC-&gt;BDCR &amp;= ~(1U &lt;&lt; 8);
RCC-&gt;BDCR |= (1U &lt;&lt; 9);

RCC-&gt;BDCR |= BDCR_RTCEN;

RTC-&gt;WPR = RTC_WRITE_PROTECTION_KEY_1;
RTC-&gt;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-&gt;CR |= CR_FMT;

rtc_set_asynch_prescaler(RTC_ASYNCH_PREDIV);
rtc_set_synch_prescaler(RTC_SYNCH_PREDIV);

exit_init_seq();

RTC-&gt;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); }

Inc/uart.h

#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

Src/uart.c

#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-&gt;MODER &amp;= ~(0x3UL &lt;&lt; (8 * 2));
GPIOD-&gt;MODER |=  (0x2UL &lt;&lt; (8 * 2));
GPIOD-&gt;MODER &amp;= ~(0x3UL &lt;&lt; (9 * 2));
GPIOD-&gt;MODER |=  (0x2UL &lt;&lt; (9 * 2));

GPIOD-&gt;AFR[1] &amp;= ~(0xFUL &lt;&lt; 0);
GPIOD-&gt;AFR[1] |=  (0x7UL &lt;&lt; 0);   /* PD8 -&gt; AF7 (USART3_TX) */
GPIOD-&gt;AFR[1] &amp;= ~(0xFUL &lt;&lt; 4);
GPIOD-&gt;AFR[1] |=  (0x7UL &lt;&lt; 4);   /* PD9 -&gt; AF7 (USART3_RX) */

RCC-&gt;APB1ENR |= USART3EN;

uart_set_baudrate(APB1_CLK, DBG_UART_BAUDRATE);

USART3-&gt;CR1 = CR1_TE;
USART3-&gt;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); }

Inc/systick.h

#ifndef SYSTICK_H_
#define SYSTICK_H_

#include <stdint.h> #include "stm32f4xx.h"

void systick_msec_delay(uint32_t delay);

#endif /* SYSTICK_H_ */

Src/systick.c

#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 &lt; delay; i++)
{
    while ((SysTick-&gt;CTRL &amp; CTRL_COUNTFLAG) == 0UL) { }
}

SysTick-&gt;CTRL = 0UL;

}

Src/main.c

#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);
}

}

5. 실습 결과

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 동기화 특성으로 기록해두었다.

6. 핵심 정리

  • RTC는 backup domain 접근과 write-protection 해제 절차가 필요하다.

  • Time/Date는 BCD field로 저장된다.

  • 초기화 모드와 synchronization 절차를 거쳐야 한다.

Clone this wiki locally