23.10.16 (월) 20일차
RTC(Real Time Clock) => 실시간 제어
RTC에서 Clock Source와 Calendar를 activate시킨다.
현재 시각과 날짜로 바꿔준다
// main.c
RTC_HandleTypeDef hrtc;
static void MX_RTC_Init(void);
MX_RTC_Init();
int main(void)
{
while (1)
{
get_rtc();
}
}
// internal_rtc.c
#include "main.h" // for GPIO & HAL
#include "i2c_lcd.h"
void get_rtc(void);
void set_rtc(void);
extern RTC_HandleTypeDef hrtc;
RTC_TimeTypeDef sTime = {0}; // time information
RTC_DateTypeDef sDate = {0}; // date information
// 23년이 save된 binary format
// 7654 3210
// 0010 0011
unsigned char bin2dec(unsigned char byte)
{
unsigned char high, low;
low = byte & 0x0f; // 하위 4bit(low nibble)
high = (byte >> 4) * 10; // 상위 4bit(high nibble)
return high + low;
}
// decimal -> BCD ex) 23: 0010 0011
unsigned char dec2bin(unsigned char byte)
{
unsigned char high, low;
low = byte % 10;
high = (byte / 10) << 4;
return high + low;
}
// STM32의 RTC로부터 날짜와 시각 정보를 읽어 오는 함수
void get_rtc(void)
{
static RTC_TimeTypeDef oldTime; // 이전 시각 정보를 가지고 있기 위해. static : 변한 값이 리셋되지 않고 그대로 유지하기 위함
HAL_RTC_GetDate(&hrtc, &sDate, RTC_FORMAT_BCD);
HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BCD);
if (oldTime.Seconds != sTime.Seconds)
{
// YYYY-MM-DD HH:mm:ss
printf("%04d-%02d-%02d %02d:%02d:%02d\n",
bin2dec(sDate.Year) + 2000, bin2dec(sDate.Month), bin2dec(sDate.Date),
bin2dec(sTime.Hours), bin2dec(sTime.Minutes), bin2dec(sTime.Seconds));
}
oldTime.Seconds = sTime.Seconds;
}
1초에 한 번씩 날짜와 시간이 찍히는 것을 확인할 수 있다.
Send를 누르면 시각이 보정된다
// internal_rtc.c
// setrtc231016103900
// 678901234567
void set_rtc(char *date_time)
{
char yy[4], mm[4], dd[4]; // date
char hh[4], min[4], ss[4]; // time
strncpy(yy, date_time+6, 2); // yy[0] = '2' yy[1] = '3' yy[2] = NULL
strncpy(mm, date_time+8, 2);
strncpy(dd, date_time+10, 2);
strncpy(hh, date_time+12, 2);
strncpy(min, date_time+14, 2);
strncpy(ss, date_time+16, 2);
// ascii --> int --> bcd
sDate.Year = dec2bin(atoi(yy));
sDate.Month = dec2bin(atoi(mm));
sDate.Date = dec2bin(atoi(dd));
sTime.Hours = dec2bin(atoi(hh));
sTime.Minutes = dec2bin(atoi(min));
sTime.Seconds = dec2bin(atoi(ss));
HAL_RTC_SetDate(&hrtc, &sDate, RTC_FORMAT_BCD);
HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BCD);
}
// uart.c
extern void void set_rtc(char *date_time);
void pc_command_processing(void)
{
if (newline_detect_flag) // \n을 만나면
{
newline_detect_flag = 0;
printf("%s\n", rx_buff);
if (!strncmp(rx_buff, "setrtc", strlen("setrtc"))) // if (strncmp(rx_buff, "ledallon", strlen("ledallon")) == 0)
{
set_rtc(rx_buff);
return;
}
}
}
Send를 누르면 미리 설정한 시간으로 보정된다
버튼을 누르면 원하는 시간으로 설정할 수 있다.
// button.c
uint8_t lcd_display_mode_flag = 0;
void lcd_display_mode_select(void)
{
char lcd_buff[40];
if (get_button(GPIOC, GPIO_PIN_13, 4) == BUTTON_PRESS)
{
lcd_display_mode_flag++;
lcd_display_mode_flag %= 4;
if (lcd_display_mode_flag == 3)
{
HAL_RTC_GetTime(&hrtc, &mTime, RTC_FORMAT_BCD);
sprintf(lcd_buff, "TIME: %02d:%02d:%02d",
bin2dec(mTime.Hours), bin2dec(mTime.Minutes), bin2dec(mTime.Seconds));
move_cursor(1, 0);
lcd_string(lcd_buff);
move_cursor(1, 6); // 시간 정보 field로 커서 이동
}
}
}
// main.c
RTC_HandleTypeDef hrtc;
extern void lcd_display_mode_select(void);
extern void set_time_button_ui(void);
int main(void)
{
while (1)
{
lcd_display_mode_select();
set_time_button_ui();
}
}
// internal_rtc.c
#include "main.h" // for GPIO & HAL
#include "i2c_lcd.h"
#include "button.h"
void get_rtc(void);
void set_rtc(char *date_time);
void set_time_button_ui(void);
extern RTC_HandleTypeDef hrtc;
extern uint8_t lcd_display_mode_flag;
extern RTC_TimeTypeDef mTime; // time information
RTC_TimeTypeDef sTime = {0}; // time information
RTC_DateTypeDef sDate = {0}; // date information
// button0: 시간 정보 변경 버튼 00~23 (up counter)
// button1: 분을 변경하는 버튼 00~59 (up counter)
// button2: 초를 변경하는 버튼 00~59 (up counter)
// button3: 변경 완료 버튼 현재까지 변경된 내용을 저장
// HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BCD);
void set_time_button_ui(void)
{
int hour = mTime.Hours;
int min = mTime.Minutes;
int sec = mTime.Seconds;
char lcd_buff[40];
if (lcd_display_mode_flag == 3)
{
if (get_button(BUTTON0_GPIO_Port, BUTTON0_Pin, 0) == BUTTON_PRESS)
{
// 시간 정보 modify
mTime.Hours++;
}
if (get_button(BUTTON1_GPIO_Port, BUTTON1_Pin, 1) == BUTTON_PRESS)
{
// 분 정보 modify
mTime.Minutes++;
}
if (get_button(BUTTON2_GPIO_Port, BUTTON2_Pin, 2) == BUTTON_PRESS)
{
// 초 정보 modify
mTime.Seconds++;
}
mTime.Hours %= 24;
mTime.Minutes %= 60;
mTime.Seconds %= 60;
sprintf(lcd_buff, "CHG:%02d:%02d:%02d", mTime.Hours, mTime.Minutes, mTime.Seconds);
move_cursor(0, 0);
lcd_string(lcd_buff);
if (get_button(BUTTON3_GPIO_Port, BUTTON3_Pin, 3) == BUTTON_PRESS)
{
mTime.Hours = dec2bin(hour);
mTime.Minutes = dec2bin(min);
mTime.Seconds = dec2bin(sec);
HAL_RTC_SetTime(&hrtc, &mTime, RTC_FORMAT_BCD);
hour = 0;
min = 0;
sec = 0;
lcd_display_mode_flag = 0;
}
}
}
이렇게 생긴 게 부저이다.
더 긴 쪽에 PA3을 연결하고, 짧은 쪽은 GND에 연결한다.
PA3를 GPIO_Output으로 변경한 후 save 한다.
// internal_rtc.c
void set_time_button_ui(void)
{
if (get_button(BUTTON3_GPIO_Port, BUTTON3_Pin, 3) == BUTTON_PRESS)
{
for (int i = 0; i < 5; i++)
{
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_3, 1);
HAL_Delay(200);
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_3, 0);
HAL_Delay(500);
}
}
}
}
버튼 4번을 눌러 부저가 울리는지 실험한다.
Tim5: APB1에 연결되어 있다. 80 MHz가 공급되고 있다.
84000000 Hz --> 1.6 MHz --> 52.5
Internal clock을 활성화시켜주고, Channel 4를 PWM Generation CH4로 변경한다.
NVIC Settings의 interrupt를 Enabled 시켜준다.
Counter Settings에 Prescaler와 Counter Period 값을 52.5-1과 400-1로 바꿔준다.
PWM Generation Channel 4의 Pulse를 200-1로 바꾼다.
부저로 다른 주파수의 소리 내기
// main.c
extern void buzzer_main();
TIM_HandleTypeDef htim5;
int main(void)
{
MX_TIM5_Init();
HAL_TIM_PWM_Start_IT(&htim5, TIM_CHANNEL_4); // PIEZO Buzzer
buzzer_main();
}
// buzzer.c
#include "main.h"
/*
=== 피에조 부저 제어 방법 ====
피에조 부저 Resonant Frequency(공진 주파수): 4kHz
STM32에서 주파수를 만들 때 3개의 레지스터를 설정한다.
PSC(Prescaler), ARR(Peroid), CCRx(Duty)
다음과 같이 적용 하면 된다.
- PSC : Timer Clock / 기준으로 사용할 주파수 - 1
- ARR : 기준 주파수 / 실제 주파수 - 1
- CCRx : ARR값 * (적용할 백분율 값 0 ~ 100) / 100
예를 들어 동작 클럭이 84Mhz이고 4Khz에 50%비율로 동작하는 PWM을 만들고 싶다면 식은 다음과 같다.
Prescaler(기준 클럭) : 1.6Mhz을 만든다면 - 84,000,000(타이머 클럭) / 1,600,000(만들 클럭) = 52.5
실제 레지스터 PSC에 적용시 1을 뺀 51.5값을 적용한다.
PSC = 52.5-1
Period : 4khz (실제 주파수) - 1,600,000(기준 클럭) / 4000(실제 주파수) = 400 실제 레지스터 ARR에 적용할땐 1을 뺀 399값을 적용한다.
ARR = 399; Duty : 50% - 399(ARR) * 50(퍼센트) / 100 = 199
CCRx = 199
*/
extern TIM_HandleTypeDef htim5;
enum notes
{
C4 = 262, // 도 261.63Hz
D4 = 294, // 래 293.66Hz
E4 = 330, // 미 329.63Hz
F4 = 349, // 파 349.23Hz
G4 = 392, // 솔 392.00Hz
A4 = 440, // 라 440.00Hz
B4 = 494, // 시 493.88Hz
C5 = 523 // 도 523.25Hz
};
// 학교종이 떙떙땡
unsigned int school_bell[] =
{
G4,G4,A4,A4,G4,G4,E4,G4,G4,E4,E4,D4,
G4,G4,A4,A4,G4,G4,E4,G4,E4,D4,E4,C4
};
// happybirthday to you
unsigned int happy_birthday[] =
{
C4,C4,D4,C4,F4,E4,C4,C4,D4,C4,G4,
F4,C4,C4,C5,A4,F4,E4,D4,B4,B4,A4,
A4,G4,F4
};
unsigned int duration[] = {1,1,2,2,2,2,1,1,2,2,2,2,1,1,2,2,2,2,2,1,1,2,2,2,2};
void noTone()
{
htim5.Instance->CCR1=0;
HAL_Delay(50);
}
void buzzer_main()
{
int divide_freq = 1600000;
while (1)
{
// 학교 종이 땡땡땡
for (int i=0; i < 24; i++)
{
__HAL_TIM_SET_AUTORELOAD(&htim5, divide_freq / school_bell[i]);
__HAL_TIM_SET_COMPARE(&htim5, TIM_CHANNEL_4, divide_freq / school_bell[i] / 2);
HAL_Delay(500);
noTone(); /* note 소리 내고 50ms 끊어주기 */
}
/* 음악 끝나고 3초 후 시작*/
HAL_TIM_PWM_Stop(&htim5, TIM_CHANNEL_4) ;
HAL_Delay(3000);
HAL_TIM_PWM_Start(&htim5, TIM_CHANNEL_4) ;
// happy birthday to you
for (int i=0; i < 25; i++)
{
__HAL_TIM_SET_AUTORELOAD(&htim5, divide_freq / happy_birthday[i]);
__HAL_TIM_SET_COMPARE(&htim5, TIM_CHANNEL_4, divide_freq / happy_birthday[i] / 2);
HAL_Delay(300*duration[i]);
noTone();
}
/* 음악 끝나고 3초 후 시작 */
HAL_TIM_PWM_Stop(&htim5, TIM_CHANNEL_4) ;
HAL_Delay(3000);
HAL_TIM_PWM_Start(&htim5, TIM_CHANNEL_4) ;
}
}