본문 바로가기
DSP, MCU/STM32 (ARM Cortex-M)

STM32 , RTC 와 FND 로 시계 만들기 + UART로 시간 제어 (FND 라이브러리 공유)

by eteo 2022. 6. 9.

 

 

RTC를 사용하기 위해서 외부에 장착된 32.768KHz의 LSE 클락소스를 사용한다.

 

먼저 CubeMX 설정

 

 

 

 

 

RTC (Real Time Clock) 설정

 

위와같이 Activate 하고 일단 알람기능은 사용하지 않는다.

 

 

 

 

 

Hourformat 12를 선택하면 12:00:00 부터 11:59:59 까지 표시되며 AM/PM value를 갖는다.

Hourformat 24를 선택하면 12:00:00 부터 23:59:59 까지 표시되는 것 같다.

 

stm32f4xx_hal_rtc.h 파일에 설명이 나와 있다.

 

Data Format은 BIN 와 BCD 중에 선택할 수 있는데 BCD를 선택했다.

 

그리고 UART 커맨드로 시간을 재설정해줄 예정이기 때문에 보드에 업로드시 적용되는 초기 시간값은 크게 신경쓰지 않았다.

 

 

 

 

 

+ usart3 인터럽트모드로 설정해준다.

 

 

 

 

 

 

main.c의 전역변수 구간에 RTC_TimeTypeDef 의 구조체 변수를 선언하는데 전역구간에 선언하는 건 UART Receive 인터럽트로 제어를 할 예정이기 때문이다.

 

RTC_TimeTypeDef 타입은 rtc.c 파일에서 복붙해와도 된다.

 

/* USER CODE BEGIN PV */
RTC_TimeTypeDef sTime;
RTC_DateTypeDef sDate;
char temp[100];
char ampm[2][3] = {"AM", "PM"};
uint8_t rx;
uint8_t bufindex=0;
uint8_t buf[30];
/* USER CODE END PV */

 

 

 

 

while문 아래 이렇게만 써주면 시리얼 터미널에 매초마다 temp 변수에 담긴 시간이 출력된다. 

  while (1)
  {
	  HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BCD); // GetTime to sTime in BCD format
	  HAL_RTC_GetDate(&hrtc, &sDate, RTC_FORMAT_BCD); // GetDate to sDate in BCD format


	  sprintf(temp,"\r\n20%02x-%02x-%02x %s %02x:%02x:%02x", sDate.Year, sDate.Month,
		      sDate.Date, ampm[sTime.TimeFormat], sTime.Hours, sTime.Minutes,
		      sTime.Seconds); // sprintf to temp
	  HAL_UART_Transmit(&huart3, (uint8_t*)temp, strlen(temp), 10);

	  HAL_Delay(1000);

//	  DisplayFND(Decto7Seg(sTime.Hours >> 4),1);
//	  DisplayFND(Decto7Seg(sTime.Hours & 0xf),2);
//	  DisplayFND(Decto7Seg(sTime.Minutes >> 4),3);
//	  DisplayFND(Decto7Seg(sTime.Minutes & 0xf),4);


    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }

 

HAL_RTC_GetTime 함수로 sTime 구조체의 값을 계속 최신화해준다.

 

RTC_FORMAT_BIN 이면 %02d로 출력할텐데, RTC_FORMAT_BCD 라서 %02x 형태로 출력해준 것이다.

 

예를들어 시간이 11시라고 하면 sTime.Hours 한바이트에

RTC_FORMAT_BIN 이면 0000 1011 이라고 저장되어 있을텐데

RTC_FORMAT_BCD 에는 0001 0001 이라고 저장되어 있는 것이니 바이너리 네자리씩 헥사코드로 읽으면 된다.

 

sDate.Year 년도같은 경우 뒤에 두자리만 출력이 되기 때문에 2000년대라고 보고 앞에 20%02x라고 붙여줘야한다.

 

ampm은 위에서 배열로 선언해줬다. 널문자 포함 행이 2개인 [2][3]의 이차원 배열이다.

출력시엔 해당 행의 시작주소로 %s형식으로 출력한다.

char ampm[2][3] = {"AM", "PM"};

 

 

참고로

AM 11:59:59 다음에는 PM 12:00:00 으로 바뀌고

12:59:59 다음에는 01:00:00 으로 바뀐다. 0시 대신 12시라고 표현하는 것 같다.

 

 

아래는 UART 커맨드로 시간이 바뀌기 5초전으로 설정해 확인해 본 것.

 

 

 

 

 

 

 

먼저 __HAL_UART_ENABLE_IT 하는 부분

 

  /* USER CODE BEGIN 2 */
  HAL_UART_Receive_IT(&huart3, &rx, 1);
  /* USER CODE END 2 */

HAL_UART_RxCpltCallback 함수

/* USER CODE BEGIN 4 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
	if(huart->Instance == USART3){
		if(rx=='\n'){
			buf[bufindex]=0;
			char *p;
			if((p=strstr((char*)buf,"SetTime")) != NULL){

				sTime.Hours = ((*(p+7)-'0') << 4) + (*(p+8)-'0');
				sTime.Minutes = ((*(p+9)-'0') << 4) + (*(p+10)-'0');
				sTime.Seconds = ((*(p+11)-'0') << 4) + (*(p+12)-'0');

				HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BCD);

			}else if((p=strstr((char*)buf,"SetDate")) != NULL){

				sDate.Year = ((*(p+7)-'0') << 4) + (*(p+8)-'0');
				sDate.Month = ((*(p+9)-'0') << 4) + (*(p+10)-'0');
				sDate.Date = ((*(p+11)-'0') << 4) + (*(p+12)-'0');

				HAL_RTC_SetDate(&hrtc, &sDate, RTC_FORMAT_BCD);
			}
			bufindex=0;
		}else{
			if(bufindex <30){
				buf[bufindex++]=rx;
			}
		}
		HAL_UART_Receive_IT(&huart3, &rx, 1);	// enable IT again
	}
}
/* USER CODE END 4 */

 

UART 커맨드에 따라 시간을 재설정 해주는 부분이다.

개행문자까지 받아서 버퍼에 저장하고 SetTime 또는 SetDate 명령어가 있으면 sTime 또는 sDate 구조체 멤버변수값을 UART로 수신된 데이터 값으로 바꿔준 뒤

HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BCD);

HAL_RTC_SetDate(&hrtc, &sDate, RTC_FORMAT_BCD);

함수를 호출해 적용시킨다. 

 

 

 

strstr 함수의 반환값은 첫번째 매개변수에서 두번째 매개변수인 문자열을 찾은 경우 해당 문자가 시작지점인 주소값이고,

- '0' 해주는 이유는 아스키코드값을 정수형으로 바꾸기 위함이다.

<< 4 해주는 이유는 시/분/초의 십의자리에 해당하는 부분은 비트이동해서 더해  uint8_t 타입인 Hours/Miniutes/Seconds 멤버변수에 BCD 타입으로 저장하기 위함이다.

 

 

예를 들어 사용자가 SetTime125959 라는 명령어가 들어왔을 때 시간부분 '1''2'는 sTime.Hours 에 0001 0010 형태로 저장될 것이다.

 

 

 

 

 

 

그리고 다시 while문 내에서는 HAL_RTC_GetTime 함수를 계속 호출해서 sTime, sDate 구조체를 최신화하고 이전에 만들어 둔 DisplayFND 함수를 사용하여 FND에 현재시간을 출력한다.

 

 /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
	  HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BCD); // GetTime to sTime in BCD format
	  HAL_RTC_GetDate(&hrtc, &sDate, RTC_FORMAT_BCD); // GetDate to sDate in BCD format

//	  sprintf(temp,"\r\n20%02x-%02x-%02x %s %02x:%02x:%02x", sDate.Year, sDate.Month,
//		      sDate.Date, ampm[sTime.TimeFormat], sTime.Hours, sTime.Minutes,
//		      sTime.Seconds); // sprintf to temp
//	  HAL_UART_Transmit(&huart3, (uint8_t*)temp, strlen(temp), 10);
//
//	  HAL_Delay(1000);

	  DisplayFND(Decto7Seg(sTime.Hours >> 4),1);
	  DisplayFND(Decto7Seg(sTime.Hours & 0xf),2);
	  DisplayFND(Decto7Seg(sTime.Minutes >> 4),3);
	  DisplayFND(Decto7Seg(sTime.Minutes & 0xf),4);


    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }

 

시/분 부분을 FND에 출력하는데,

십의자리는 >> 4 비트이동하고 일의자리는 &0xf 해서 해당자리의 BCD 코드 값을 정수형으로 변환해 Decto7seg 함수에 넘겨준다.

그리고 FND 4자리를 동시에 출력하는데 while문 내에서 HAL_Delay를 쓰면 안되므로 그부분을 주석처리 해두었다.

 

 

 

만약 UART 커맨드로 현재시간을 전송하게끔 하려면 flag 변수를 하나 둬서 콜백함수 내에서 ex. PrintTime 커맨드가 들어오면 flag를 on 시키고 while문 내에 flag가 1일때 UART transmit 하게끔 하고 다시 flag를 0으로 끄는 식으로 구현하면 될 것 같다.

 

 

 

참고로 HAL_RTC_SetTime 함수의 내부를 들여다보면 tmpreg에 시간 값을 OR연산해서 대입하고 그 값을 다시  hrtc->TR (Time Register)에 대입하는 구조로 되어있다.

 

main에 선언한 구조체변수는 순간 값을 저장하기 위한 그릇같은 거고 실제 HAL_RTC_SetTime 과 HAL_RTC_GetTime 함수로 RTC 타임 레지스터에 값을 쓰고 읽어오는 것 같다.

 

 

 

 

 

 

느낀점 1

 

앞으로 4 digit FND를 다룰 때마다 소스파일과 헤더파일만 포함시켜서 사용할 수 있게 함수를 묶어 라이브러리화 해두었다. 이렇게 하면 코드의 재사용성을 높일 수 있어 매우 편리한 것 같다. 해당 파일을 여기에도 공유한다.

fourfnd.h
0.00MB
fourfnd.c
0.00MB

 

 

 

 

 

 

 

 

느낀점 2

 

커맨드가 복잡하고 많아질수록 금방 처리하고 다시 while문으로 돌아올 수 있게끔 콜백함수 내부을 간결화하려는 노력이 필요하다고 느꼈다.

현재는 HAL_UART_Receive_IT(&huart3, &rx, 1); 이므로 매개변수인 size와 무관하게 1 byte 데이터  UART4_IRQHandler가 호출되고 다시 IRQ핸들러가 HAL_UART_RxCpltCallback 함수를 호출한다. 만약 HAL_UART_Receive_IT(&huart3, &rx, 100); 으로 되어있으면 IRQ핸들러 함수는 1byte 수신시마다 호출되고 콜백함수는 100byte수신이 완료되었을 때 호출될 것이다.

아무튼..지금까진 계속 콜백함수를 가져와 재정의해서 썼는데 핸들러 함수를 수정하여 써보고 싶다는 생각이 든다. 가변길이의 문자열을 uart로 수신하는데 링버퍼라는 아주 좋은 방법도 있다고 들었는데 조금 더 공부하고 후기를 남겨보겠다.