STM32 RTC 구현

서론

진행한 프로젝트중 하나가 Raspberry Pi와 STM32를 함께 사용하는 구조로 되어 있습니다.

Raspberry Pi는 High-Level Application을 담당하여 HMI 처리와 인쇄 데이터 생성을 수행하고, STM32는 Low-Level Application을 담당하여 모터 구동, 온도 측정 및 PID 제어, 그리고 인쇄 동작을 수행합니다.

이 시스템에서는 인쇄 시 날짜와 시간을 함께 출력해야 하는 요구사항이 있습니다. 그러나 라즈베리 파이에는 기본적으로 실시간 시계(RTC, Real-Time Clock)가 내장되어 있지 않기 때문에, 정확한 시간을 유지하기 위해서는 별도의 RTC 모듈을 추가로 연결해야 합니다.

이러한 방식은 하드웨어 구성이 복잡해지고, 배선 및 관리 측면에서도 번거로움이 발생합니다.

따라서 본 프로젝트에서는 STM32 제어 보드에 RTC 기능을 구현하여 시스템 전체의 시간을 관리하도록 구성하였습니다.

본 글에서는 STM32 RTC 구현 방법과 함께, 개발 과정에서 겪었던 문제와 디버깅 팁을 정리해 보겠습니다.
특히 CubeMX 설정 변경 시 RTC 시간이 초기화되는 문제와 그 해결 방법도 함께 다룹니다.

본론

RTC 개요

RTC(Real-Time Clock)는 현재 시간을 유지하고 관리하는 기능으로, 날짜와 시간 정보를 지속적으로 카운트하는 하드웨어 모듈입니다. 일반적인 MCU의 타이머와는 달리, RTC는 전원이 차단되더라도 별도의 백업 전원을 통해 시간을 계속 유지할 수 있다는 특징을 가지고 있습니다.

STM32에서는 RTC가 Backup Domain에 포함되어 있으며, VBAT 핀을 통해 별도의 전원을 공급받으면 메인 전원이 꺼진 상태에서도 시간 정보를 유지할 수 있습니다. 이 기능은 로그 기록, 데이터 타임스탬프, 예약 동작 등 다양한 응용에서 매우 중요한 역할을 합니다.

RTC는 일반적으로 32.768kHz의 저주파 클럭을 사용하며, STM32에서는 다음과 같은 두 가지 클럭 소스를 선택할 수 있습니다.

  • LSE (Low-Speed External): 외부 크리스탈을 사용하는 방식으로 높은 정확도를 제공
  • LSI (Low-Speed Internal): 내부 RC 오실레이터를 사용하는 방식으로 별도의 부품이 필요 없지만 정확도가 상대적으로 낮음

또한 STM32의 RTC는 단순한 시간 카운터를 넘어서 Alarm, Wakeup Timer, Timestamp 기능 등을 제공하여 저전력 시스템에서도 다양한 시간 기반 제어가 가능하도록 설계되어 있습니다.

본 프로젝트에서는 인쇄 시점에 정확한 날짜와 시간을 출력하기 위해 RTC 기능이 필요하며, 시스템 전원이 꺼졌다가 다시 켜지는 상황에서도 시간 정보가 유지되어야 합니다. 이러한 요구사항을 만족하기 위해 STM32의 RTC를 활용하여 시스템의 기준 시간을 관리하도록 구성하였습니다.

STM32 RTC의 내부 구성

STM32의 RTC는 단순한 타이머와는 달리, 저전력 환경에서도 독립적으로 동작하도록 설계된 시간 관리 모듈입니다. RTC는 메인 시스템 클럭과 분리된 저속 클럭을 기반으로 동작합니다. Backup Domain에 포함되어 있기 때문에 별도의 백업 전원이 공급되는 경우 시스템 전원이 차단되더라도 시간 정보를 유지할 수 있습니다.

RTC의 클럭 소스로는 외부 32.768kHz 크리스탈을 사용하는 LSE(Low-Speed External)와 내부 RC 오실레이터를 사용하는 LSI(Low-Speed Internal)를 선택할 수 있으며, 일반적으로 시간 정확도가 중요한 경우에는 LSE를 사용하는 것이 권장됩니다. 입력된 저속 클럭은 내부 Prescaler에 의해 분주되어 최종적으로 1초 단위의 시간 기준을 생성합니다.

또한 RTC는 Backup Register를 포함하고 있어 간단한 데이터나 상태 정보를 유지할 수 있으며, 이를 활용하면 RTC 초기화 여부를 판단하는 등의 실용적인 기능 구현이 가능합니다. 이와 함께 Alarm, Wakeup Timer, Timestamp와 같은 부가 기능도 제공되어 저전력 기반의 시간 제어 시스템을 구성하는 데 유용하게 활용될 수 있습니다.

아래 그림은 STM32F446 RTC의 Block Diagram 입니다.

STM32의 RTC는 비교적 단순한 구조를 가지면서도 실무에서 요구되는 시간 유지 기능을 충분히 제공합니다. 보다 상세한 레지스터 구성과 동작 원리에 대해서는 STM32 User Manual을 참고하시기 바랍니다.
RM0390 Reference Manual

RTC 회로 구성(VBAT 및 크리스탈)

STM32의 RTC를 안정적으로 동작시키기 위해서는 외부 회로 구성이 매우 중요합니다. 특히 RTC의 핵심 요소인 VBAT 전원과 32.768kHz 크리스탈(LSE)은 시간 유지의 정확성과 직결되므로 반드시 올바르게 설계해야 합니다.

먼저 VBAT 핀은 RTC가 포함된 Backup Domain에 전원을 공급하는 역할을 합니다. 일반적으로 메인 전원(VDD)이 공급되는 동안에는 VBAT가 사용되지 않지만, 시스템 전원이 차단될 경우 VBAT를 통해 RTC와 Backup Register가 계속 유지됩니다. 따라서 VBAT에는 코인셀 배터리나 슈퍼커패시터를 연결하여 전원이 꺼진 상태에서도 시간 정보가 유지되도록 구성하는 것이 일반적입니다. 만약 VBAT를 연결하지 않을 경우, 전원이 꺼질 때마다 RTC 시간이 초기화되는 문제가 발생할 수 있습니다.

다음으로 RTC의 클럭 소스로 사용되는 32.768kHz 크리스탈(LSE)은 STM32의 OSC32_IN 및 OSC32_OUT 핀에 연결됩니다. 이 크리스탈은 매우 낮은 주파수에서 안정적으로 동작하도록 설계된 부품으로, 정확한 시간 측정을 위해 널리 사용됩니다. 크리스탈 양단에는 일반적으로 수 pF 수준의 로드 캐패시터를 GND에 연결하여 발진을 안정화시켜야 하며, 이 값은 사용되는 크리스탈의 데이터시트를 기준으로 선정하는 것이 중요합니다.

또한 크리스탈 회로는 노이즈에 민감하기 때문에 가능한 한 MCU에 가깝게 배치하고, 배선 길이를 최소화하는 것이 좋습니다. 특히 고속 신호선이나 전원 스위칭 회로와 인접하게 배치될 경우 발진 불안정이나 시간 오차의 원인이 될 수 있으므로 PCB 설계 시 주의가 필요합니다. 가능하다면 크리스탈 주변에는 GND 영역을 확보하여 외부 노이즈의 영향을 최소화하는 것이 좋습니다.

이와 같이 VBAT 전원과 LSE 크리스탈을 적절히 구성하면 STM32의 RTC를 보다 안정적이고 정확하게 운용할 수 있으며, 실제 제품에서는 이러한 기본적인 하드웨어 설계가 전체 시스템 신뢰성에 큰 영향을 미치게 됩니다. 아래는 그것을 적용한 회로도 입니다.

STM32CubeMX에서 RTC 설정하기

회로 구성이 완료되었다면 다음은 STM32CubeMX에서 RTC를 설정해야 합니다.
RTC는 단순히 기능만 활성화한다고 바로 동작하는 것이 아니라, 클럭 소스와 캘린더 기능, 그리고 백업 영역 동작까지 함께 고려해야 합니다. 특히 외부 32.768kHz 크리스탈을 사용하는 경우에는 RTC 클럭 소스를 반드시 LSE로 선택해야 정상적으로 동작합니다.

먼저 CubeMX의 Pinout & Configuration 화면에서 RTC에 사용할 외부 크리스탈을 설정해야 합니다.
다음 그림과 같이 OSC32_IN과 OSC32_OUT 핀을 각각 RCC_OSC32_IN, RCC_OSC32_OUT으로 설정하여 32.768kHz 크리스탈을 사용할 수 있도록 구성합니다.

이 설정은 RTC에서 사용할 LSE 클럭을 위한 하드웨어 연결을 정의하는 단계이며, 이후 RCC에서 LSE를 활성화해야 실제 클럭이 동작하게 됩니다.

다음 그림은 STM32CubeMX의 Clock Configuration 화면에서 LSE(Low Speed External) 클럭을 설정하는 장면입니다. RTC에서 사용할 저속 클럭 소스를 지정하는 단계로, 외부 32.768kHz 크리스탈을 사용하는 경우 반드시 LSE를 활성화해야 합니다.

그림과 같이 Low Speed Clock (LSE) 항목을 Crystal/Ceramic Resonator로 설정하면, 외부 크리스탈을 기반으로 한 안정적인 저속 클럭이 생성되며, 해당 클럭이 RTC의 시간 기준으로 사용됩니다.

다음 그림은 STM32CubeMX에서 RTC 기능을 활성화하고 기본 설정을 구성하는 화면입니다. 앞 단계에서 LSE 클럭을 설정한 이후, 실제로 RTC를 동작시키기 위해서는 해당 설정을 반드시 수행해야 합니다.

먼저 RTC Mode and Configuration 항목에서 Activate Clock SourceActivate Calendar를 체크합니다. 이 설정을 통해 RTC 내부 카운터가 동작을 시작하며, 시간 및 날짜 기능이 활성화됩니다. 이 두 항목이 활성화되지 않으면 RTC는 클럭이 공급되더라도 동작하지 않습니다.

그림의 하단 Parameter Settings 영역에서는 RTC의 분주값과 초기 시간 정보를 설정할 수 있습니다.

  • Asynchronous Predivider : 127
  • Synchronous Predivider : 255

이 값은 32.768kHz LSE 클럭을 기준으로 1초 주기를 생성하기 위한 설정이며, 일반적으로 기본값을 그대로 사용하는 것이 적절합니다.

위와 같이 설정하면 CubeMX에서의 RTC 설정은 완료됩니다.

초기화 코드(main.c)

CubeMX 설정을 완료하고 프로젝트를 생성하면 초기화 코드가 자동으로 생성됩니다. 생성된 코드를 살펴보면, 각 주변장치에 대한 초기화 함수가 main() 함수에서 순차적으로 호출되는 것을 확인할 수 있습니다.
다음 그림은 생성된 초기화 코드의 일부를 보여줍니다.
이 중에서 RTC를 사용하기 위해서는 MX_RTC_Init() 함수가 생성되며, 해당 함수가 호출되는 것을 확인할 수 있습니다. 이 함수는 RTC의 기본 설정과 초기 동작을 담당하며, 이후 RTC를 이용한 시간 설정 및 읽기 동작의 기반이 됩니다.

다음 그림은 CubeMX에서 생성된 MX_RTC_Init() 함수의 전체 코드입니다. 해당 코드는 rtc.c 파일에 위치하며, RTC의 초기 설정과 시간 초기화 동작을 함께 수행합니다.

코드를 보면 크게 두 부분으로 나눌 수 있습니다.

첫 번째 영역은 RTC의 기본 동작을 설정하는 부분입니다.

hrtc.Instance = RTC;
hrtc.Init.HourFormat = RTC_HOURFORMAT_24;
hrtc.Init.AsynchPrediv = 127;
hrtc.Init.SynchPrediv = 255;

이 부분에서는 다음과 같은 설정이 이루어집니다.

  • 24시간 형식 사용
  • Prescaler 설정 (1초 주기 생성)
  • RTC 출력 설정

즉, RTC 하드웨어가 정상적으로 동작하기 위한 기본 환경을 구성하는 단계입니다.

두 번째 영역은 RTC의 초기 시간과 날짜를 설정하는 부분입니다.

HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BCD);
HAL_RTC_SetDate(&hrtc, &sDate, RTC_FORMAT_BCD);

이 코드를 통해 RTC는 지정된 시간과 날짜로 시작하게 됩니다.
즉, 시스템이 처음 구동될 때 기준이 되는 시간을 설정하는 역할을 합니다.

Application 코드

CubeMX를 이용하여 RTC 초기화 코드가 생성되면, 다음 단계는 이를 실제 어플리케이션에서 사용할 수 있도록 별도의 모듈로 구성하는 것입니다. 이를 통해 RTC 관련 기능을 독립적으로 관리하고, 코드의 재사용성과 가독성을 높일 수 있습니다.

본 예제에서는 RTC 관련 기능을 rtc_util.crtc_util.h 파일로 분리하여 구현하였습니다.

아래는 rtc_util.h파일의 코드입니다. 이 파일에서는 RTC 관련 함수들의 프로토타입을 정의합니다.

다음은 위에 정의한 각 함수들의 구현이 들어가 있는 rtc_util.c를 설명하겠습니다.

위 코드는 rtc_util.c 파일의 상단부로, 필요한 헤더 파일을 포함하고 매직값을 정의하는 부분입니다.
rtc_util.h 파일은 앞에서 정의한 함수들의 프로토타입을 포함하고 있으며, usart3.h 파일은 디버깅을 위한 출력 기능을 사용하기 위해 포함하였습니다.
또한 RTC_BKP_MAGIC은 Backup Register를 이용하여 RTC 초기화 여부를 판단하기 위한 값으로 사용됩니다.
즉, 매직값이 레지스터에 기록이 되어 있으면 초기화 된 것으로 간주하고 초기화를 하지 않고 기록이 되어 있지 않으면 초기화를 합니다.

다음은 각 함수별로 설명을 하겠습니다.

RTC_InitOnce() 함수

이 함수는 RTC가 이미 초기화되었는지를 확인하고, 필요한 경우에만 시간과 날짜를 설정하도록 만든 함수입니다.

이 함수의 목적은 매우 단순합니다.
RTC가 처음 한 번만 초기화되도록 하고, 이후에는 기존 시간을 유지하도록 하는 것입니다.

아래는 그 함수가 구현된 것 입니다.

먼저 RTC_BKP_MAGIC 값이 Backup Register에 기록되어 있는지를 확인합니다.
기록되어 있지 않은 경우에는 RTC를 초기화하고, 이후 RTC_BKP_MAGIC 값을 기록합니다.

다음에 이 함수가 다시 호출되면, 이미 값이 기록되어 있으므로 초기화를 수행하지 않고 그대로 종료됩니다.

이 방식은 RTC가 매번 초기화되는 것을 방지하기 위한 핵심적인 구현 방법입니다.

RTC_SetTimeAndDate() 함수

이 함수는 RTC의 시간과 날짜를 설정하기 위한 함수입니다.
외부에서 전달받은 값을 이용하여 RTC에 원하는 시간 정보를 기록할 수 있습니다.

이 함수는 입력받은 시간과 날짜 값을 RTC_TimeTypeDefRTC_DateTypeDef 구조체에 저장한 뒤,
HAL_RTC_SetTime()HAL_RTC_SetDate() 함수를 통해 RTC에 적용합니다.

이때 데이터 형식은 RTC_FORMAT_BIN을 사용하여 사람이 이해하기 쉬운 형태로 값을 설정합니다.

조금 더 설명하면, RTC 하드웨어 내부에서는 실제로 BCD(Binary Coded Decimal) 형식을 사용하여 값을 저장합니다. 그러나 BCD 형식은 일반적인 숫자 표현 방식과 다르기 때문에 직접 사용하기에는 불편할 수 있습니다.

예를 들어 14시 35분 20초를 설정할 경우, BIN 형식에서는 단순히 14, 35, 20과 같이 값을 입력하면 되지만, BCD 형식에서는 각각 0x14, 0x35, 0x20과 같이 표현해야 합니다.

HAL 라이브러리를 사용할 경우 RTC_FORMAT_BIN을 선택하면 이러한 변환 과정을 내부적으로 자동 처리해 주므로, 일반적인 응용에서는 BIN 형식을 사용하는 것이 더 편리합니다.

RTC_GetTimeAndDate() 함수

이 함수는 RTC 모듈로 부터 시간과 날짜를 읽어오는 함수 입니다.

이 함수는 HAL_RTC_GetTime()HAL_RTC_GetDate() 함수를 이용하여 RTC의 현재 값을 읽은 뒤, 포인터를 통해 호출한 함수로 값을 전달합니다.

이때도 역시 데이터 형식은 RTC_FORMAT_BIN을 사용하여 사람이 이해하기 쉬운 형태로 값을 반환합니다.

여기서 주의해야 할 점은 RTC 값을 읽을 때는 반드시 HAL_RTC_GetTime()을 먼저 호출한 후 HAL_RTC_GetDate()를 호출해야 합니다.

시간 설정 및 읽기 실험

프로그램 구현이 완료되었으므로, 이제 RTC가 정상적으로 동작하는지 확인하기 위한 실험을 진행합니다.

시간 설정 및 읽기는 STM32의 USART3 포트를 통해 PC와 통신하여 확인하였습니다. PC 측 시리얼 통신 프로그램으로는 Docklight v2.4를 사용하였으며, 아래 그림은 프로그램 실행 화면입니다.

USART3를 통해 시간 설정 및 읽기를 위한 Command는 별도의 usart3 모듈에서 구현하였습니다.
본 글에서는 RTC 구현에 초점을 두고 있으므로 해당 부분에 대한 상세 설명은 생략합니다.

아래 화면은 보드를 처음 Boot했을 때 USART3를 통해 출력되는 메시지입니다.
화면에서 확인할 수 있듯이, “RTC already set. Skipping time init.” 메시지가 출력되고 있으며, 이는 RTC가 이미 초기화되어 있다는 의미입니다.
즉, RTC_InitOnce() 함수에서 Backup Register에 기록된 값을 확인한 결과, RTC가 이미 설정된 것으로 판단하여 시간 초기화를 수행하지 않고 그대로 동작하고 있음을 확인할 수 있습니다.

다음은 현재 시간을 읽어보겠습니다.
Command는 AT+RTCREAD이며, 좌측 목록의 가장 상단에 있는 항목입니다.

아래 그림과 같이 Command를 실행하면, USART3를 통해 현재 시간이 출력되는 것을 확인할 수 있습니다.

출력 결과를 보면 RTC가 정상적으로 동작하며, 시간이 지속적으로 증가하고 있음을 확인할 수 있습니다.

다음은 시간을 설정하는 것을 실험해 보겠습니다.
시간설정 Command는 아래 그림과 같습니다.

그림과 같이 AT+RTCSET Command를 이용하여 원하는 시간 값을 설정할 수 있습니다.
아래 그림은 시간 설정 Command를 실행한 결과입니다.
출력 내용을 보면 RTC 시간이 정상적으로 설정된 것을 확인 할 수 있습니다.

시간 설정 후 다시 AT+RTCREAD Command를 실행한 결과, 설정한 시간이 정확하게 반영되어 출력되는 것을 확인할 수 있었습니다.
이를 통해 RTC의 시간 설정 기능과 시간 유지 기능이 모두 정상적으로 동작함을 확인할 수 있습니다.

시간이 계속 초기화 될 때 문제 해결법

RTC가 정상적으로 설정되고 동작함을 확인하였습니다.
그러나 추가 개발을 진행하는 과정에서 RTC가 계속 초기화되는 문제가 발생하였습니다.

문제의 원인을 확인해 보니, CubeMX 설정을 다시 적용하는 과정에서 자동 생성된 코드에 시간과 날짜를 초기화하는 코드가 포함되어 있음을 확인할 수 있었습니다.

즉, MX_RTC_Init() 함수 내부에서 매번 다음과 같은 코드가 실행되면서 RTC 시간이 초기값으로 다시 설정되고 있었습니다.

이것을 제거 하니 정상적으로 작동하였습니다.

맺음말

본 글에서는 STM32의 RTC를 이용하여 시간 기능을 구현하는 방법을 단계별로 살펴보았습니다.
외부 32.768kHz 크리스탈을 이용한 하드웨어 구성부터 CubeMX 설정, 초기화 코드 분석, 그리고 실제 어플리케이션에서의 활용까지 전체 흐름을 정리하였습니다.

특히 Backup Register를 활용하여 RTC 초기화를 한 번만 수행하도록 구성함으로써, 전원 재인가 이후에도 시간이 유지되도록 구현할 수 있었습니다.

또한 USART를 이용한 실험을 통해 시간 설정 및 읽기 기능이 정상적으로 동작함을 확인하였으며, 이를 통해 RTC를 실제 시스템에 안정적으로 적용할 수 있음을 검증하였습니다.

STM32의 RTC는 단순한 시간 표시 기능을 넘어서, 로그 기록, 인쇄 데이터 생성, 이벤트 관리 등 다양한 응용에 활용할 수 있는 중요한 기능입니다.
본 글이 RTC를 처음 적용하거나 구현하려는 분들께 도움이 되었기를 바랍니다.

 

댓글 남기기