Repository navigation
Chapter 15
STM32F439ZI의 RTC peripheral을 LSI clock으로 동작시키고, 현재 시간과 날짜를 UART로 출력한다.
이번 실습:
LSI
↓
RTC Clock
↓
Prescaler
↓
1 Hz Calendar Clock
↓
RTC Time / Date
↓
UART
↓
PC Terminal실제 board에서 다음까지 확인했다.
Time count : PASS
1-second tick : PASS
Date rollover : PASS
09-19
↓
09-20
Ch09 TIM2:
System / Peripheral Clock
↓
Timer Counter
↓
Periodic EventRTC:
Low-Speed Clock
↓
Backup Domain
↓
Calendar Hardware
↓
Second / Minute / Hour
Day / Month / YearRTC는 단순 counter가 아니라 시간과 날짜를 관리하는 calendar peripheral이다.
RTC는 일반 peripheral과 달리 Backup Domain에 존재한다.
따라서 바로 register를 수정할 수 없다.
먼저:
PWR Clock Enable
↓
DBP = 1
↓
Backup Domain Write Access순서가 필요하다.
핵심:
RCC->APB1ENR |= PWREN;
PWR->CR |= CR_DBP;RTC는 여러 clock source를 사용할 수 있다.
LSE
→ External 32.768 kHz crystal
LSI
→ Internal low-speed RC oscillator
HSE
→ External high-speed clock division
이번 실습에서는:
LSI를 사용한다.
장점:
External crystal 불필요
배선 불필요
간단한 실습에 적합단점:
LSE보다 정확도가 낮음즉 실제 정밀 RTC application에서는 보통 LSE가 더 적합하다.
이번 실습의 설정:
LSI ≈ 32 kHz
↓
Asynchronous Prescaler
↓
Synchronous Prescaler
↓
≈ 1 Hz
↓
Calendar사용값:
RTC_ASYNCH_PREDIV = 0x7F
= 127
→ divide by 128
RTC_SYNCH_PREDIV = 0x00F9
= 249
→ divide by 250
따라서:
128 × 250
32000
LSI가 약 32kHz라는 가정에 맞춘 설정이다.
| Register | 역할 |
|---|---|
| RTC->TR | Hour / Minute / Second |
| RTC->DR | Year / Month / Day / Weekday |
| RTC->CR | RTC control / format |
| RTC->ISR | Initialization / synchronization status |
| RTC->PRER | Async / Sync prescaler |
| RTC->WPR | Write protection |
| RCC->CSR | LSI enable / ready |
| RCC->BDCR | RTC clock select / RTC enable |
| PWR->CR | Backup domain write access |
RTC의 시간과 날짜는 일반 binary integer가 아니라 BCD 형태로 저장된다.
예:
Decimal 59일반 binary:
0x3BBCD:
0x59BCD에서는 각 decimal digit을 4bit씩 저장한다.
5 → 0101
9 → 1001
59
→ 0101 1001
→ 0x59
따라서 RTC register에:
0x59가 보이면 실제 값:
59로 읽을 수 있다.
원본 실습 코드:
uint8_t rtc_convert_bcd2dec(uint8_t value)
{
return (uint8_t)(((value >> 4U) * 10U)
+ (value & 0x0FU));
}예:
value = 0x59상위 nibble:
0x5 × 10
50
하위 nibble:
0x9
9
결과:
50 + 9
59
RTC 주요 register는 실수로 변경되는 것을 방지하기 위해 write protection이 걸려 있다.
Unlock sequence:
RTC->WPR = 0xCA;
RTC->WPR = 0x53;순서가 중요하다.
0xCA
↓
0x53
↓
RTC Write Protection Disabled설정이 끝난 후:
RTC->WPR = 0xFF;로 write protection을 다시 활성화한다.
현재 Notion에 전체가 명확히 남아 있는 핵심:
rtc_init()
rtc_convert_bcd2dec()
main()다음 helper는 호출 구조와 역할은 남아 있지만 전체 함수 본문은 현재 기록에 보존되어 있지 않다.
rtc_init_seq()
exit_init_seq()
rtc_date_config()
rtc_time_config()
rtc_set_asynch_prescaler()
rtc_set_synch_prescaler()
display_rtc_calendar()
따라서 없는 원본 코드를 임의로 만들어 넣지는 않는다.
#ifndef RTC_H__
#define RTC_H__
#include <stdint.h>
#include "stm32f4xx.h"
/*
- Initialize RTC using the LSI clock.
*/
void rtc_init(void);
/*
- Convert one BCD encoded value
- into normal decimal integer form.
-
- Example:
-
-
0x59 → 59
*/
uint8_t rtc_convert_bcd2dec(uint8_t value);
/*
- Read RTC time/date registers and
- print current calendar data through UART.
-
- Full archived implementation body
- is not currently available.
*/
void display_rtc_calendar(void);
#endif
#include "rtc.h"
/*
- ============================================================
- RCC / PWR Control Bits
- ============================================================
*/
/*
- APB1 PWR peripheral clock enable.
*/
#define PWREN (1U << 28)
/*
- PWR CR DBP:
-
- Enable write access to Backup Domain.
*/
#define CR_DBP (1U << 8)
/*
- RCC CSR LSION:
-
- Enable internal low-speed oscillator.
*/
#define CSR_LSION (1U << 0)
/*
- RCC CSR LSIRDY:
-
- Indicates LSI oscillator is stable.
*/
#define CSR_LSIRDY (1U << 1)
/*
- Backup Domain Reset.
*/
#define BDCR_BDRST (1U << 16)
/*
- RTC Enable.
*/
#define BDCR_RTCEN (1U << 15)
/*
- ============================================================
- RTC Write Protection
- ============================================================
*/
#define RTC_WRITE_PROTECTION_KEY_1 0xCAU
#define RTC_WRITE_PROTECTION_KEY_2 0x53U
/*
- ============================================================
- Prescaler
- ============================================================
*/
#define RTC_ASYNCH_PREDIV 0x7FU
#define RTC_SYNCH_PREDIV 0x00F9U
void rtc_init(void)
{
/*
* --------------------------------------------------------
* Step 1. Enable PWR Peripheral Clock
* --------------------------------------------------------
*
* RTC belongs to the Backup Domain.
*
* Backup-domain write permission is controlled
* through the PWR peripheral.
*/
RCC->APB1ENR |= PWREN;
/*
* --------------------------------------------------------
* Step 2. Enable Backup Domain Write Access
* --------------------------------------------------------
*
* DBP = Disable Backup-domain Protection.
*
* DBP = 1
*
* allows RTC / backup domain registers
* to be modified.
*/
PWR->CR |= CR_DBP;
/*
* --------------------------------------------------------
* Step 3. Enable LSI
* --------------------------------------------------------
*
* LSI:
*
* Low-Speed Internal RC oscillator
*
* Used as the RTC source in this project.
*/
RCC->CSR |= CSR_LSION;
/*
* --------------------------------------------------------
* Step 4. Wait Until LSI Is Stable
* --------------------------------------------------------
*
* LSIRDY = 1
*
* means the LSI oscillator is ready.
*
* RTC clock should not be configured until
* the selected source clock is stable.
*/
while ((RCC->CSR & CSR_LSIRDY) != CSR_LSIRDY)
{
}
/*
* --------------------------------------------------------
* Step 5. Reset Backup Domain
* --------------------------------------------------------
*
* BDRST = 1
*
* resets RTC / backup-domain configuration.
*/
RCC->BDCR |= BDCR_BDRST;
/*
* Release reset.
*/
RCC->BDCR &= ~BDCR_BDRST;
/*
* --------------------------------------------------------
* Step 6. Select LSI as RTC Clock
* --------------------------------------------------------
*
* RTCSEL field is configured to:
*
* 10b
*
* for LSI.
*
* Existing code performs this by:
*
* bit8 = 0
* bit9 = 1
*/
RCC->BDCR &= ~(1U << 8);
RCC->BDCR |= (1U << 9);
/*
* --------------------------------------------------------
* Step 7. Enable RTC
* --------------------------------------------------------
*/
RCC->BDCR |= BDCR_RTCEN;
/*
* --------------------------------------------------------
* Step 8. Disable RTC Write Protection
* --------------------------------------------------------
*
* Hardware-defined unlock sequence:
*
* 0xCA
* ↓
* 0x53
*/
RTC->WPR = RTC_WRITE_PROTECTION_KEY_1;
RTC->WPR = RTC_WRITE_PROTECTION_KEY_2;
/*
* --------------------------------------------------------
* Step 9. Enter RTC Initialization Mode
* --------------------------------------------------------
*
* RTC calendar registers must be configured
* while the RTC is in initialization mode.
*
* Full helper body is not preserved
* in the current archive.
*/
rtc_init_seq();
/*
* --------------------------------------------------------
* Step 10. Configure Date
* --------------------------------------------------------
*
* Existing practice value:
*
* Weekday : Friday
* Date : 19
* Month : 09
* Year : 26
*
* Values are BCD encoded.
*/
rtc_date_config(
WEEKDAY_FRIDAY,
0x19,
0x09,
0x26
);
/*
* --------------------------------------------------------
* Step 11. Configure Time
* --------------------------------------------------------
*
* Existing practice:
*
* 11:59:55 PM
*
* Starting close to midnight allows
* date rollover to be tested quickly.
*/
rtc_time_config(
TIME_FORMAT_PM,
0x11,
0x59,
0x55
);
/*
* --------------------------------------------------------
* Step 12. RTC Time Format
* --------------------------------------------------------
*
* IMPORTANT:
*
* The archived Notion source contains:
*
* RTC->CR |= CR_FMT;
*
* together with the comment "24-hour format".
*
* However the same source calls:
*
* TIME_FORMAT_PM
*
* so the archived notes are internally inconsistent
* regarding 12-hour / 24-hour format.
*
* Keep this as a re-test item and verify CR.FMT
* against the STM32F439 RTC Reference Manual
* before finalizing the driver.
*/
RTC->CR |= CR_FMT;
/*
* --------------------------------------------------------
* Step 13. Configure Asynchronous Prescaler
* --------------------------------------------------------
*
* 0x7F = 127
*
* Actual division:
*
* 127 + 1
* = 128
*/
rtc_set_asynch_prescaler(
RTC_ASYNCH_PREDIV
);
/*
* --------------------------------------------------------
* Step 14. Configure Synchronous Prescaler
* --------------------------------------------------------
*
* 0xF9 = 249
*
* Actual division:
*
* 249 + 1
* = 250
*/
rtc_set_synch_prescaler(
RTC_SYNCH_PREDIV
);
/*
* --------------------------------------------------------
* Step 15. Exit Initialization Mode
* --------------------------------------------------------
*
* Calendar counting starts/resumes.
*
* Helper also waits for register synchronization
* in the original experiment flow.
*/
exit_init_seq();
/*
* --------------------------------------------------------
* Step 16. Re-enable RTC Write Protection
* --------------------------------------------------------
*/
RTC->WPR = 0xFF;
}
이 값은 임의로 고른 것이 아니라 날짜 rollover를 빠르게 검증하기 위해 유리한 test condition이다.
11:59:55 PM
↓
11:59:56
↓
11:59:57
↓
11:59:58
↓
11:59:59
↓
12:00:00동시에 날짜:
09-19
↓
09-20이 되는지 확인할 수 있다.
즉 단순히 RTC가 증가하는 것뿐 아니라:
Second rollover
Minute rollover
Hour rollover
Date rollover를 한 번에 검증할 수 있다.
RTC는 계속 시간을 count하고 있기 때문에 calendar register를 동작 중에 아무 때나 수정하면 안 된다.
설정 과정:
Normal RTC Operation
↓
Enter Init Mode
↓
Calendar Counter Paused
↓
Configure
TR / DR / PRER / CR
↓
Exit Init Mode
↓
RTC Counting Starts이 과정을 helper:
rtc_init_seq()
exit_init_seq()가 담당한다.
Bare-metal peripheral initialization에서는:
Write configuration만 하고 바로 다음 단계로 넘어가면 안 되는 경우가 많다.
예:
RCC->CSR |= CSR_LSION;
while ((RCC->CSR & CSR_LSIRDY)
!= CSR_LSIRDY)
{
}
이는:
LSI Enable
↓
Oscillator starting...
↓
Not Ready
↓
Not Ready
↓
LSIRDY = 1
↓
Continue라는 hardware synchronization 과정이다.
이 패턴은 이후에도 반복된다.
Enable
↓
Wait Ready Flag
↓
Configure Next Stageuint8_t rtc_convert_bcd2dec(uint8_t value)
{
/*
* Upper nibble contains the tens digit.
*
* Example:
*
* 0x59 >> 4
* =
* 0x05
*
* 5 × 10
* =
* 50
*/
uint8_t tens =
(uint8_t)((value >> 4U) * 10U);
/*
* Lower nibble contains the ones digit.
*
* 0x59 & 0x0F
* =
* 9
*/
uint8_t ones =
(uint8_t)(value & 0x0FU);
/*
* 50 + 9 = 59
*/
return (uint8_t)(tens + ones);
}
원본의 한 줄 형태:
return (uint8_t)(((value >> 4U) * 10U)
+ (value & 0x0FU));과 동일한 동작이다.
#include "rtc.h"
#include "uart.h"
#include "systick.h"
int main(void)
{
/*
* --------------------------------------------------------
* Step 1. Initialize UART
* --------------------------------------------------------
*
* Current RTC date/time is printed
* through USART3.
*/
uart_init();
/*
* --------------------------------------------------------
* Step 2. Initialize RTC
* --------------------------------------------------------
*
* RTC setup:
*
* Clock Source → LSI
* Prescaler
* Date
* Time
*/
rtc_init();
while (1)
{
/*
* ----------------------------------------------------
* Step 3. Read and Display RTC Calendar
* ----------------------------------------------------
*
* Expected information:
*
* Hour
* Minute
* Second
* Month
* Day
* Year
*
* The complete archived implementation of
* display_rtc_calendar() is not currently
* preserved in the source notes.
*/
display_rtc_calendar();
/*
* ----------------------------------------------------
* Step 4. Print Approximately Once Per Second
* ----------------------------------------------------
*
* SysTick driver from Ch08 is reused.
*/
systick_msec_delay(1000);
}
}
rtc_init()
│
├── PWR Clock Enable
│
├── DBP = 1
│
├── LSI Enable
│
├── Wait LSIRDY
│
├── Backup Domain Reset
│
├── RTCSEL = LSI
│
├── RTC Enable
│
├── WPR Unlock
│
├── Enter Init Mode
│
├── Date Configure
│
├── Time Configure
│
├── Prescaler Configure
│
├── Exit Init Mode
│
└── WPR LockLSI
↓
RTC Prescaler
↓
1-second Calendar Tick
↓
TR / DR Update
↓
display_rtc_calendar()
↓
BCD → Decimal
↓
UART
↓
PC TerminalRTC time register stores:
Hours
Minutes
Seconds개념적으로:
TR
┌────────┬─────────┬─────────┐
│ Hour │ Minute │ Second │
└────────┴─────────┴─────────┘각 field는 BCD encoded value다.
예:
11:59:55이면 register field들을 사람이 hex로 봤을 때:
11
59
55처럼 읽기 쉽다.
RTC date register에는:
Year
Month
Date
Weekday정보가 저장된다.
이번 초기값:
Year = 0x26
Month = 0x09
Date = 0x19
Weekday = Friday자정이 지나면 hardware calendar가 자동으로:
09-19
↓
09-20으로 증가시킨다.
RTC register read는 내부 shadow register와 관련된 synchronization이 존재한다.
기존 실습에서 reset 직후 순간적으로:
01-01-2000처럼 초기값이 표시될 수 있다는 기록이 있었다.
그 후 RTC register synchronization이 끝나면 실제 설정한 calendar 값이 표시됐다.
따라서 reset 직후 한 번의 잘못된 화면만 보고:
RTC initialization failed라고 바로 판단하면 안 된다.
현재 보존된 실습 코드에는:
rtc_time_config(
TIME_FORMAT_PM,
0x11,
0x59,
0x55
);
RTC->CR |= CR_FMT;
가 있고, 기존 주석은:
24시간제라고 되어 있다.
하지만:
TIME_FORMAT_PM이라는 설정과 같은 코드 안에 존재하므로 12/24-hour format에 대한 기록이 내부적으로 일관되지 않는다.
따라서 재실습 시 반드시:
STM32F439 RTC CR.FMT definition을 Reference Manual에서 다시 확인하고 최종 코드의:
FMT
AM/PM
Hour encoding을 일치시킨다.
이 부분은 현재 Wiki에서 “검증 필요” 항목으로 남긴다.
기존 실습:
Build : PASS
Download : PASS
Run : PASSProgram size:
text = 2260 bytes
data = 0 bytes
bss = 2560 bytes경고 없이 build 완료.
실제 terminal에서:
Second
↓
Second
↓
Second가 약 1초씩 증가하는 것을 확인했다.
또한:
09-19
11:59:59 PM이후:
09-20
12:00:00으로 날짜가 넘어가는 것을 확인했다.
즉:
Second increment PASS
Minute rollover PASS
Hour rollover PASS
Date rollover PASS로 볼 수 있다.
make clean
makeExpected:
text = 2260
data = 0
bss = 2560Debugger:
RCC->CSR확인:
LSION = 1
LSIRDY = 1확인:
PWR->CRExpected:
DBP = 1initialization 중 backup domain access가 허용되었는지 확인한다.
확인:
RCC->BDCR확인할 항목:
RTCSEL
RTCENExpected:
RTC Clock Source = LSI
RTC Enable = 1확인:
RTC->PRERExpected:
Asynchronous = 0x7F
Synchronous = 0x00F9Watch:
RTC->TR초가 증가하는지 확인한다.
55
56
57
58
59
00자정 전:
RTC->DR
→ 09-19자정 후:
RTC->DR
→ 09-20인지 확인한다.
images/ch15_build.png
images/ch15_lsi.png
images/ch15_rtc_registers.png
images/ch15_time_before_midnight.png
images/ch15_time_after_midnight.png
images/ch15_rollover_terminal.png가장 중요한 evidence:
11:59:59 / 09-19
↓
12:00:00 / 09-20terminal 화면이다.
이 한 장이:
RTC counting
+
Time rollover
+
Calendar rollover를 동시에 보여준다.
LSI
↓
Backup Domain
↓
RTC
↓
Prescaler
↓
Calendar
↓
BCD Time / Date
↓
UARTRTC는 일반 timer와 달리 calendar hardware다.
RTC는 Backup Domain에 존재한다.
RTC register를 수정하려면
DBP로 backup domain write access를 허용해야 한다.이번 실습은 LSI를 RTC clock source로 사용한다.
LSI를 enable한 후
LSIRDY를 기다린다.RTC register write 전
0xCA → 0x53key sequence로 write protection을 해제한다.RTC 설정은 initialization mode에서 수행한다.
TR은 time,DR은 date를 저장한다.RTC 값은 BCD 형식으로 저장된다.
0x59BCD는 decimal59를 의미한다.Prescaler는
0x7F / 0x00F9설정을 사용했다.실제 board에서 1초 증가와 09-19 → 09-20 날짜 rollover를 검증 완료했다.
기존 기록의
CR.FMT와TIME_FORMAT_PM조합은 재실습 시 다시 확인해야 한다.