본문 바로가기
임베디드 (Embedded)/STM32

[STM32] LCD, 조이스틱, RTC, 부저 이용하여 시계 만들기

by 기딩 2023. 11. 21.
728x90

시계 프로젝트 전반적인 기능 흐름도는 다음과 같다.

 
시계는 LCD 화면에 보여진다.
 
-------------------------------------<모드>----------------------------------------
1. NORMAL_STATE : 현재시간 출력

플래시 메모리에서 읽어와서 그 시간부터 흘러간다.

  • 스위치 한 번 클릭 → time_setting
  • 스위치 2초 이상 클릭 → alarm_time_setting
  • 스위치 더블 클릭 → music_select

 
2. TIME_SETTING : 현재시간 조정하기

이 모드에 들어오면 현재 시간이 나와있고, 조이스틱으로 바꿀 현재 시간을 조정하면 된다.
커서 위치를 구분하기 위해 lcd화면에 A, H, M, S 텍스트로 표현하였다.

  • 조이스틱 좌우로 커서 위치 이동 (ampm, 시, 분, 초)
  • 조이스틱 상하로 시간 조정 (am/pm, 증가/감소)
  • 스위치 클릭 → 변경사항 저장 & normal_state

 
3. ALARM_TIME_SETTING : 알람 울릴 시간 조정하기

알람이 울릴 시간을 조정한다.
커서 위치를 구분하기 위해 lcd화면에 A, H, M, S 텍스트로 표현하였다.

  • 조이스틱 좌우로 커서 위치 이동 (ampm, 시, 분, 초)
  • 조이스틱 상하로 시간 조정 (am/pm, 증가/감소)
  • 스위치 클릭 → 변경사항 저장 & normal_state

 
4. MUSIC_SELECT : 알람음 선택하기

현재시간=알람시간일 때 울릴 노래를 선택한다.

  • 조이스틱 상하로 노래 번호 변경 (증가/감소)
  • 스위치 클릭 → 변경사항 저장 & normal_state

 
 
--------------------------------------------<전체 회로 구성>----------------------------
 

 
 
 
-------------------------------------------------------<STM Project Report>-----------------------------------------
1. Description

Board Name NUCLEO-F429ZI
Generated with: STM32CubeMX 6.9.2
MCU name  STM32F429ZITx
Core(s) Arm Cortex-M4
Pin Number GPIO mode GPIO Pull-up/down User Label
PF7 External Interrupt Mode
with Rising/Falling edge trigger detectoin
Pull-up  
PB0 Output Push Pull No pull-up and
No pull-down
LD1[Green]
PB1 Output Push Pull No pull-up and
No pull-down
LD2[Blue]
PB2 Output Push Pull No pull-up and
No pull-down
LD3[Red]

 
led는 디버깅이나 기능 확인 용도로만 사용하여 없어도 상관없다.
 
2. Peripherals and Middlewares Configuration
ADC1 : 조이스틱x, y값을 입력 받기 위한 설정

PC0 ADC1_IN 10 Rank 1 84 Cycles 조이스틱 x축 값
PC1 ADC1_IN 13 Rank 2 84 Cycles 조이스틱 y축 값

 

 
ADC1:DMA2 설정

 
 
I2C1

Pin Name Signal on Pin
PB8 I2C1_SCL
PB9 I2C1_SDA

Timers
- RTC

 
- Tim2, Tim3

Tim2
Clock Source Internal Clock
Prescaler 8400-1
Counter Period
(ARR)
100-1
global interrupt Enabled

 

Tim3
Channel2 PWM Generation CH2
Prescaler 84-1
Counter Period
(ARR)
200-1
Pulse 50-1
PC7 TIM3_CH2

 
 
-------------------------------------------<코드 설명>-------------------------------
상태 머신을 구현하여 모드 정의 및 구조체

/* USER CODE BEGIN PTD */
enum CLOCK_MODE{
	NORMAL_STATE,
	TIME_SETTING,
	ALARM_TIME_SETTING,
	MUSIC_SELECT
};
struct clock_state{
	enum CLOCK_MODE mode;
	int music_num;
};
struct clock_state current_state;

typedef struct {
	int8_t music_num;
	char music_title[16];
	int music_length;
} MusicTypeDef;

MusicTypeDef alarm_music[] =
{
  {0, "Harry Potter   ", 64},
  {1, "School Bell    ", 24}
};

/* USER CODE END PTD */

 
 
앞으로 쓰일 전역 변수

char showTime[30] = {0};
char alarmTime[30] = {0};
char ampm[2][3] = {"AM", "PM"};

uint32_t XY[2];

RTC_TimeTypeDef sTime;
RTC_DateTypeDef sDate;
RTC_AlarmTypeDef aTime = {0};

 
개인 프로젝트여서 전역을 사용하긴 했지만, 프로젝트에서 전역변수를 많이 쓰는 것은 지양하는 것이 좋다.
하지만, 바꾼 현재/알람 시간을 여러 함수에서 받아야 해서 전역으로 두었다.
 
함수
플래시읽고 가져오기 함수

/* USER CODE BEGIN 0 */

HAL_StatusTypeDef update_nvitems(void)
{
	uint32_t FirstSector,NbOfSectors,SECTORError;
	FLASH_EraseInitTypeDef EraseInitStruct;
	HAL_StatusTypeDef error= HAL_OK;
	uint32_t Address,i;
	uint64_t Data;
	uint8_t *ptr;

	HAL_FLASH_Unlock();
	FirstSector = FLASH_SECTOR_11;
	NbOfSectors = 1;

	EraseInitStruct.TypeErase     = FLASH_TYPEERASE_SECTORS;
	EraseInitStruct.VoltageRange  = FLASH_VOLTAGE_RANGE_3;
	EraseInitStruct.Sector        = FirstSector;
	EraseInitStruct.NbSectors     = NbOfSectors;

	error = HAL_FLASHEx_Erase(&EraseInitStruct, &SECTORError);
	if(error != HAL_OK)
	{
		return error;
	}

	ptr = (uint8_t*)&default_nvitem;

	for(i=0;i<sizeof(NVitemTypeDef);i++)
	{
		Address = (uint8_t*)nv_items+i;
		Data = *((uint8_t*)ptr+ i);
		error = HAL_FLASH_Program(FLASH_TYPEPROGRAM_BYTE,Address,Data);
		if(error != HAL_OK)
		{
			return error;
		}
	}

	HAL_FLASH_Lock();
}

플래시 섹터 11에 저장
 
RTC에 저장된 시간 가져오기

void get_time(void)
{
	HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BIN);
	HAL_RTC_GetDate(&hrtc, &sDate, RTC_FORMAT_BIN);
	sprintf((char*)showTime, "%s %02d : %02d : %02d      ", ampm[sTime.TimeFormat], sTime.Hours, sTime.Minutes, sTime.Seconds);
}
void get_alarm(void)
{
	aTime.Alarm = RTC_ALARM_A;
	HAL_RTC_GetAlarm(&hrtc, &aTime, RTC_ALARM_A, RTC_FORMAT_BIN);
	sprintf((char*)alarmTime, "%s %02d : %02d : %02d      ", ampm[aTime.AlarmTime.TimeFormat], aTime.AlarmTime.Hours, aTime.AlarmTime.Minutes, aTime.AlarmTime.Seconds);
}

 
모드 별로 화면 구성

void time_display(void) {
  if (current_state.mode == NORMAL_STATE) {
	  get_time();
	  LCD_SendCommand(LCD_ADDR, 0b10000000);
	  LCD_SendString(LCD_ADDR, "CLOCK              ");
	  LCD_SendCommand(LCD_ADDR, 0b11000000);
	  LCD_SendString(LCD_ADDR, showTime);
  }
  else if (current_state.mode == TIME_SETTING){
	  LCD_SendCommand(LCD_ADDR, 0b10000000);
	  LCD_SendString(LCD_ADDR, "Time Setting      ");
	  LCD_SendCommand(LCD_ADDR, 0b11000000);
	  LCD_SendString(LCD_ADDR, showTime);
	  LCD_SendCommand(LCD_ADDR, 0b10001101);
  }
  else if (current_state.mode == ALARM_TIME_SETTING) {
	  get_alarm();
	  LCD_SendCommand(LCD_ADDR, 0b10000000);
	  LCD_SendString(LCD_ADDR, "Alarm Time       ");
	  LCD_SendCommand(LCD_ADDR, 0b11000000);
	  LCD_SendString(LCD_ADDR, alarmTime);
	  LCD_SendCommand(LCD_ADDR, 0b10001101);
  }
  else if (current_state.mode == MUSIC_SELECT) {
	  LCD_SendCommand(LCD_ADDR, 0b10000000);
	  LCD_SendString(LCD_ADDR, "Music Select       ");
	  music_select();
  }
}

lcd의 1행 1열에 커서를 두고, 어떤 모드인지 텍스트 출력
2행에는 시간이나 music title 출력
 
클릭 관련 변수 및 함수

uint32_t ctime, ltime, interval;
uint32_t double_key_cnt, tmptime;
int tmpcnt, click_flag, level;
uint32_t currentTime, lastTime;

enum CLICK_STATE {
	NO_CLICK,
	FIRST_PUSH,
	FIRST_PULL,
	SECOND_PUSH,
	SECOND_PULL
} click_state;
enum CLICK_STATE click_state = NO_CLICK;

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
	level = HAL_GPIO_ReadPin(GPIOF, GPIO_PIN_7);
	if (seq > 0) { // alarm ringing...
		seq = alarm_music[current_state.music_num].music_length;
		if (level == 1) return;
	}
	else {
		currentTime = HAL_GetTick();
		interval = currentTime - lastTime;
		lastTime = currentTime;

		if (interval > 50) { // debouncing
			if (level == 0 && (click_state == NO_CLICK || click_state == SECOND_PULL)) {
				click_state = FIRST_PUSH;
				ctime = HAL_GetTick();
			}
			else if (level == 1 && click_state == FIRST_PUSH) {
				click_state = FIRST_PULL;
				ltime = HAL_GetTick();
				if (ltime - ctime > 1000 && current_state.mode == NORMAL_STATE) {
					printf("Long click \r\n");
					click_state = NO_CLICK;
					current_state.mode = ALARM_TIME_SETTING;
				}
				else {
	//				printf("first pull \r\n");
				}
			}
			else if (level == 0 && click_state == FIRST_PULL) {
				click_state = SECOND_PUSH;
			}
			else if (level == 1 && click_state == SECOND_PUSH) {
				click_state = SECOND_PULL;
				printf("double click \r\n");
				if (current_state.mode == NORMAL_STATE) {
					current_state.mode = MUSIC_SELECT;
				}
				click_state = NO_CLICK;
			}
		}
	}

}

노래 울리는 중에 버튼 클릭되면 노래 끄고 no_click 상태
디바운싱 현상을 해결하기 위해 interval 50보다 큰 것만 읽기
 
버튼 상태는 rising/falling mode
- no_click 상태에서 버튼 누르면 first_push상태 (그 순간을 ctime)
 
- first_push에서 버튼 떼면 first_pull 상태(그 순간을 ltime)
-- first_push(누른 상태)로 일정 시간(ltime-ctime>1000)이 지속되면 long click->알람시간 조정하는 모드
-- 와 동시에 다시 조이스틱 no_click 상태
- 길게 누른 게 아니면 first_pull (버튼 뗀 상태)로 유지
 
- one click, long click도 아닌데 버튼 한 번 더 눌리면 second_push상태
(one click인지는 while에서 확인)
- second_push에서 버튼 떼면 second_pull로 인식 -> 알람 음악 선택 모드
- 다시 조이스틱 no_click 상태
 
# one, double, long Click구분 방법
버튼은 rising/falling edge를 둘 다 켠 상태 -> 눌러도, 떼도, 인터럽트 콜백 실행
(이전 글에서 구현을 했지만, 프로젝트에서 여러 기능을 추가하며 원활하지 않아 다른 방법으로 구현했다.)

첫 누름, 첫 뗌, 두 번째 누름, 두 번째 뗌, no click 상태를 구조체로 정의하여 진행했다.

  • 한 번 눌렀다가 뗐을 때, 버튼을 누르는 등의 이벤트가 없으면 one click으로 인식
    (이는 while문에서 검사)
  • 한 번 눌렀다가 뗐을 때의 간격이 2초 이상이면 long click
    (눌렀을 때, 뗐을 때, 각각 HAL_GetTick()으로 읽어서 interval 값 비교 조건문)
  • 한 번 눌렀다가 뗐을 때, 빠른 시간 내에 다시 버튼 누르는 이벤트가 발생하면 double click으로 인식
    (빠른 시간 내 = while문에서 마지막으로 버튼 뗀 시간과 다시 눌러진 시간 간격 검사)

 
@주의
나는 이 프로젝트에서 버튼 인터럽트를 하나만 사용해서, 어떤 핀으로 인터럽트가 발생했는지 굳이 구별하지 않았다.
여러 버튼을 사용할 것이라면, if (HAL_GPIO_Pin == 7) 이런 식으로 조건문을 써줘야 한다.
 
 
시간 조정하는 함수

void setTime_Position() {
	char blink[30] = {0};
	RTC_TimeTypeDef* selectedTime;
	if (current_state.mode == TIME_SETTING) {
		selectedTime = &sTime;
	} else {
		selectedTime = &(aTime.AlarmTime);
		get_alarm();
		HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BIN);
//		HAL_RTC_DeactivateAlarm(&hrtc, RTC_ALARM_A);
	}

	if (XY[0] < 60) hourMinSec--;
	if (XY[0] > 4000) hourMinSec++;

	if (hourMinSec > 3) hourMinSec = 0;
	if (hourMinSec < 0) hourMinSec = 3;

//	printf("time_position : %d \r\n", hourMinSec);

	switch(hourMinSec) {
	case 0:
//		LCD_SendCommand(LCD_ADDR, 0b11000000);
		LCD_SendString(LCD_ADDR, "A");
		sprintf(blink, "%s", ampm[selectedTime->TimeFormat]);
		if (XY[1] < 60 ) selectedTime->TimeFormat++;
		if (XY[1] > 4000)  selectedTime->TimeFormat--;
		break;

	case 1:
//		LCD_SendCommand(LCD_ADDR, 0b11000011);
		LCD_SendString(LCD_ADDR, "H");
		sprintf(blink, "%02d", selectedTime->Hours);
		if (XY[1] < 60) selectedTime->Hours++;
		if (XY[1] > 4000) selectedTime->Hours--;
		if (selectedTime -> Hours == 0)     selectedTime->Hours = 12;
		else if (selectedTime-> Hours > 12 ) selectedTime->Hours = 1;
		break;

	case 2:
//		LCD_SendCommand(LCD_ADDR, 0b11001000);
		LCD_SendString(LCD_ADDR, "M");
		sprintf(blink, "%02d", selectedTime->Minutes);
		if (XY[1] < 60) selectedTime->Minutes++;
		if (XY[1] > 4000) selectedTime->Minutes--;
		if (selectedTime->Minutes > 250)     selectedTime->Minutes = 59;
		else if (selectedTime->Minutes > 59) selectedTime->Minutes = 0;
		break;

	case 3:
//		LCD_SendCommand(LCD_ADDR, 0b11001101);
		LCD_SendString(LCD_ADDR, "S");
		sprintf(blink, "%02d", selectedTime->Seconds);
		if (XY[1] < 60) selectedTime->Seconds++;
		if (XY[1] > 4000) selectedTime->Seconds--;
		if (selectedTime->Seconds > 250)     selectedTime->Seconds = 59;
		else if (selectedTime->Seconds > 59) selectedTime->Seconds = 0;
		break;
	}

	HAL_Delay(400);
//	LCD_SendString(LCD_ADDR, "  ");
	if (current_state.mode == TIME_SETTING) {
		HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BIN);
		default_nvitem.setting_time.TimeFormat = sTime.TimeFormat;
		default_nvitem.setting_time.Hours = sTime.Hours;
		default_nvitem.setting_time.Minutes = sTime.Minutes;
		default_nvitem.setting_time.Seconds = sTime.Seconds;
//		update_nvitems();
	}
	else {
		aTime.Alarm = RTC_ALARM_A;
		default_nvitem.alarm_time.TimeFormat = aTime.AlarmTime.TimeFormat;
		default_nvitem.alarm_time.Hours = aTime.AlarmTime.Hours;
		default_nvitem.alarm_time.Minutes = aTime.AlarmTime.Minutes;
		default_nvitem.alarm_time.Seconds = aTime.AlarmTime.Seconds;
		HAL_RTC_SetAlarm_IT(&hrtc, &aTime, RTC_FORMAT_BIN);
//		update_nvitems();
//		get_alarm();
	}
//	update_nvitems(); // 느려짐
}

현재시간 조정모드면 sTime 주소를,
알람시간 조정모드면 aTime.AlarmTime 주소를 selectedTime에 저장
XY[0]은 x값, 즉 왼쪽이나 오른쪽으로 조이스틱을 움직이면
TimeFormat(am/pm) -> 시 -> 분 -> 초 -> ampm -> ... 식으로 위치 이동
추가 기능) 포지션을 확인하기 위해 해당 위치만 공백을 출력하도록 반복해서 깜빡이도록 blink처리
+) 깜빡이는 게 딜레이를 길게 줘야 하고 그러면 조이스틱 읽는 게 느려져서, 주석처리하고 텍스트(A,H,M,S)로 위치 보이게 변경
 
XY[1]은 y값, 즉 위나 아래로 조이스틱을 움직이면
x로 움직인 해당 포지션의 값을 증/감 시킴
ex) position=1(hour), y를 위로 -> hour++
 
@시,분,초 감소 시 주의 사항
Hour는 1~12, Minute, Second는 0~59인데 해당 rtc 구조체를 F3(or Ctrl + Click)으로 들어가면
다음 사진과 같이 uint8_t로 정의되어 있다.
uintx_t는 부호가 없는 x비트의 unsigned int이다.
따라서 hour<0일 때, hour=12로 하고 싶지만, 0보다 감소하면 -1의 음수가 아니라 255로 들어가진다. (8비트니까)
감소가 빨리 되면 255보다 254,253처럼 더 작아지는 버그도 있을 수 있어,
아예 >250으로 걸러내었다.

 
 
조정한 시간은 default_nvitem에 전달하여 갱신된 값을 메모리에 저장할 수 있도록
마지막에 주석처리한 update_nvitem();을 계속 호출하면 메모리를 지우는 곳에서 오래 걸리기 때문에 속도가 현저히 느려진다. 따라서, 시간 변경이 다 끝나고 스위치를 클릭했을 때 호출하는 것으로 변경했다.
 
 
알람 음악 선택하기

void music_select(void) {
	unsigned int music_cnt = sizeof(alarm_music)/sizeof(alarm_music[0]); // total music cnt

	if (XY[1] < 60 ) current_state.music_num++;
	if (XY[1] > 4000 ) current_state.music_num--;
	current_state.music_num %= music_cnt;

	LCD_SendCommand(LCD_ADDR, 0b11000000);
	LCD_SendString(LCD_ADDR, alarm_music[current_state.music_num].music_title);
	default_nvitem.alarm_music_num = current_state.music_num;
}

위나 아래로 하면 music_num 증가/감소
해당 music_num은 default_nvitem에 저장
 
여기에서부턴 메인함수

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_ETH_Init();
  MX_USART3_UART_Init();
  MX_I2C1_Init();
  MX_RTC_Init();
  MX_ADC1_Init();
  MX_TIM3_Init();
  MX_TIM2_Init();

  /* Initialize interrupts */
  MX_NVIC_Init();
  /* USER CODE BEGIN 2 */
  I2C_Scan();
  LCD_Init(LCD_ADDR);

  HAL_ADC_Start_DMA(&hadc1, XY, 2);
  aTime.AlarmMask = RTC_ALARMMASK_DATEWEEKDAY; // So, mask = 1,0,0,0

  current_state.mode = NORMAL_STATE;
  click_state = NO_CLICK;
  current_state.music_num = 0;

  if(nv_items->magic_num == MAGIC_NUM) // get
  {
	  sTime.TimeFormat = *(uint8_t *)(ADDR_FLASH_SECTOR_11 + 4);
	  sTime.Hours = *(uint8_t *)(ADDR_FLASH_SECTOR_11 + 5);
	  sTime.Minutes = *(uint8_t *)(ADDR_FLASH_SECTOR_11 + 6);
	  sTime.Seconds = *(uint8_t *)(ADDR_FLASH_SECTOR_11 + 7);
	  HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BIN);

//	  HAL_RTC_DeactivateAlarm(&hrtc, RTC_ALARM_A);
	  aTime.AlarmTime.TimeFormat = *(uint8_t *)(ADDR_FLASH_SECTOR_11 + 8);
	  aTime.AlarmTime.Hours = *(uint8_t *)(ADDR_FLASH_SECTOR_11 + 9);
	  aTime.AlarmTime.Minutes = *(uint8_t *)(ADDR_FLASH_SECTOR_11 + 10);
	  aTime.AlarmTime.Seconds = *(uint8_t *)(ADDR_FLASH_SECTOR_11 + 11);
	  aTime.Alarm = RTC_ALARM_A;
	  HAL_RTC_SetAlarm_IT(&hrtc, &aTime, RTC_FORMAT_BIN);

	  current_state.music_num = *(uint8_t *)(ADDR_FLASH_SECTOR_11 + 12);
  }
  else // set
  {
	  update_nvitems();
  }

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
	  get_time();
	  time_display();
	  printf("%d  %d:%d:%d \r\n",aTime.AlarmTime.TimeFormat, aTime.AlarmTime.Hours, aTime.AlarmTime.Minutes, aTime.AlarmTime.Seconds);
	  if (current_state.mode == TIME_SETTING || current_state.mode == ALARM_TIME_SETTING) {
		  setTime_Position();
//		  printf("%d, %d \r\n", XY[0], XY[1]);
//		  printf("\r\n");
	  }

	  if (click_state == FIRST_PULL && (HAL_GetTick()-ltime) > 100) {
		printf("one click \r\n");
		if (seq > 0 ) {
			seq = alarm_music[current_state.music_num].music_length;
		}
		else {
			if (current_state.mode == NORMAL_STATE) {
				current_state.mode = TIME_SETTING;
			}
			else {
				current_state.mode = NORMAL_STATE;
				update_nvitems();

			}
		}

		click_state = NO_CLICK;
	 }

    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

 
lcd 사용을 위해 i2c_scan()과 lcd_init()
ADC1과 DMA를 활용한 조이스틱 사용을 위해 hal_adc_start_dma() (XY배열에 첫 번째, 두 번째 채널- ioc에서 rank로 결정)
aTime에 날짜만 mask를 1로 주어, 날짜와 관계없이 시분초만 고려해서 알람 울리도록 저장.
 
처음 시작했을 땐, 그냥 시간 흘러가는 normal_state와 조이스틱 아무것도 클릭 안 한 상태가 되도록
메모리가 저장되어 있으면 그 메모리를 읽어서 sTime(rtc현재시간), aTime.AlarmTime(rtc알람시간), music_num(알람음)에 넣기
메모리가 안 들어가 있으면 현재시간, 알람시간, 알람음악 번호 넣기
 
while에선
계속해서 rtc에서 흘러가는 현재시간을 get_time()으로 가져오고,
모드 변경을 확인해야 하므로 time_display()
현재 시간 조정이나 알람 시간 조정하는 모드면 setTime_Position() 호출
 
스위치 눌렀다 떼고 아무 일 없이(더블클릭 없이) 100ms이 지나면 한 번 클릭으로 인식
- seq>0 즉, 알람 시간이 돼서 알람 노래가 나오는 중 -> 노래 끄기
- normal_state에서 누름 -> time_setting 모드로
- 다른 모드에서 누름 -> normal_state로 돌아감 & 변경한 값들 플래시 메모리에 업데이트하기!
(update 함수를 자주 실행시키면 속도가 현저히 느려져서, 버튼을 눌러서 확정 됐을 때만 호출하게 했다)
셋 중에 하나 실행하고 다시 조이스틱 no_click 상태
 
알람 관련 함수
알람 시간 되면 RTC 알람A callback 함수 호출된다.

/* USER CODE BEGIN 4 */
void HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *hrtc) {
	HAL_UART_Transmit(&huart3, (uint8_t *)&showTime, strlen(showTime), 1000);
	printf("Alarm Callback Occurred!! \r\n");
	seq = 0;
	HAL_TIM_Base_Start_IT(&htim2);
	HAL_GPIO_TogglePin(GPIOB, LD1_Pin);
}

 
멜로디 배열 인덱스 (seq)를 0으로,
멜로디의 딜레이와 관련된 tim2 시작
 
타이머 콜백

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
	int selected_music_num = current_state.music_num;
	switch(selected_music_num) {
	case 0 :
		buzzer = harry;
		break;
	case 1 :
		buzzer = bell;
		break;
	}
	HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_2);
	uint16_t melody = (uint16_t)(1000000 / buzzer[seq].freq);
	printf("music num : %d \r\n", current_state.music_num);
	if(stop == 1){
		TIM2->ARR = 500;
		HAL_TIM_PWM_Stop(&htim3, TIM_CHANNEL_2);
		stop = 0;
	}
	else{
		if(seq == alarm_music[current_state.music_num].music_length){
			HAL_TIM_Base_Stop_IT(&htim2);
			HAL_TIM_PWM_Stop(&htim3, TIM_CHANNEL_2);
			seq = 0;
		}
		else{
			TIM3->ARR = melody;
			TIM3->CCR2 = melody / 2;
			TIM2->ARR = buzzer[seq].delay * 1500;
			HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_2);
			stop = 1;
			seq++;
		}
	}
	HAL_GPIO_TogglePin(GPIOB, LD3_Pin);
}

/* USER CODE END 4 */

 
알람 음악 번호에 따라 멜로디 배열 전달
부저로 멜로디에 따라 부저 울려야 하니까 타이머 3의 PWM 채널2 시작 
멜로디 다 끝나면 타이머2,3 stop
 
작동 영상
 

 
@@코드 주의사항
HAL_RTC_GetTime 후에 HAL_RTC_GetDate 꼭 해야 함.
RTC 알람 시간을 건드릴 때는 해당 알람을 비활성화(HAL_RTC_DeactivateAlarm)한 후에 조정하기
+) 알람 조정하는 동안은 알람을 안 울리게 하려는 부가적인 기능이지, 비활성화를 안 한다고 에러나 기능을 안 하는 건 아닌 듯하다.
MX_DMA_Init이 MX_ADC1_Init보다 먼저 나오기(설정 먼저하면 된다)
RTC의 변수를 바꿔도 get을 안 하면 바뀐 시간이 출력 안 된다. (rtc 시간은 흐르지만, 바뀐 값을 모르고 시간이 멈춘 것처럼 보임)
 
플래시에서 불러올 때(begin 2에서) aTime에 RTC_ALARM_A임을 저장하지 않으면,
리셋 버튼 눌렀을 시, ioc에 있는 초기 알람 값을 읽어온다. (리셋 전에는 변경이 잘 반영돼서 놓치기 쉬움)
이와 더불어 aTime의 AlarmMask에 마스크 설정도 새로 해주어야 한다. (하지 않으면, 마스크 값은 불러온 적이 없으므로 전역변수의 초기값인 0으로 저장되어, "날짜"/시/분/초 모두가 맞을 때만 알람이 울린다.)
 
 
lcd.c나 flash.h는 깃허브에 올려놓았다.
시계 프로젝트 깃허브 https://github.com/gwidding/STM/tree/main/clock
 

728x90