[Intel AI SW 아카데미] RTC와 부저 PWM 제어

Jimeaning·2023년 10월 16일
0

Intel AIoT

목록 보기
4/38

23.10.16 (월) 20일차

학습 목표

  • 서보 모터
  • RTC 제어
  • 부저 pwm 제어하기
  • 분초 시계. FND 제어하기

RTC 제어

RTC(Real Time Clock) => 실시간 제어

환경 설정

RTC에서 Clock Source와 Calendar를 activate시킨다.

현재 시각과 날짜로 바꿔준다

코드

  • main.c
// main.c

RTC_HandleTypeDef hrtc;

static void MX_RTC_Init(void);

MX_RTC_Init();

int main(void)
{

  while (1)
  {
	  get_rtc();
   }
}
  • internal_rtc.c
// 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초에 한 번씩 날짜와 시간이 찍히는 것을 확인할 수 있다.


RTC로 시각 조정하기

Send를 누르면 시각이 보정된다

코드

  • internal_rtc.c
// 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
// 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를 누르면 미리 설정한 시간으로 보정된다


RTC로 원하는 시각 설정하기

버튼을 누르면 원하는 시간으로 설정할 수 있다.

코드

  • button.c
// 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
// 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
// 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번을 눌러 부저가 울리는지 실험한다.


PWM 제어하기

Tim5: APB1에 연결되어 있다. 80 MHz가 공급되고 있다.

  • 부저 동작 주파수 4 KHz

84000000 Hz --> 1.6 MHz --> 52.5

  • PSC : 52.5
  • ARR (Auto Reload값) : 1600000 Hz /400 ==> 4000 Hz
  • CCR (Capture Compare Register) : 200
    Duty rate(50%) 400(ARR) / 2 ==> 200

환경설정

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
// 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
// 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) ;


  }
}
 
profile
I mean

0개의 댓글