시계 프로젝트 전반적인 기능 흐름도는 다음과 같다.
시계는 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
'임베디드 (Embedded) > STM32' 카테고리의 다른 글
[STM32] RTC Alarm 기능 사용하기 (0) | 2023.11.14 |
---|---|
[STM32] LCD 커서 이동 (0) | 2023.11.14 |
[STM32] STM32cubeIDE 버튼 한 번 클릭, 더블 클릭, 길게(n초 이상) 클릭하기 (0) | 2023.11.10 |
[STM32] STM32CubeIDE i2C-LCD 사용하기 (1) | 2023.11.08 |
[STM32] STM32CubeIDE 조이스틱 설정 ADC, DMA (0) | 2023.11.07 |