일단 부저 (Buzzer)에는 수동부저 (Passive) 와 능동부저 (Active) 가 있다.
차이점은 Passive는 다리길이가 똑같고 Active 는 +쪽 다리가 더 길다. 하지말 둘 다 극성이 있어서 +라고 적힌 곳에 데이터선 그리고 다른곳에 GND를 연결해야 한다.
바닥면 생김새도 다르고 또 하나 큰 차이점은 Active Buzzer는 HIGH 신호를 공급하기만 하면 바로 소리가 난다. 반면 Passive Buzzer는 아무 소리도 안난다. 음계에 맞는 주파수를 조절해줘야 한다.
지난번 글에서는 서보모터 제어를 위해 펄스폭을 조정했었다.
한편 부저는 소리크기를 전압레벨 또는 펄스폭을 조정하거나 저항을 달아서 조절할 수 있는데 지금은 항상 같은 소리크기로 출력할 것이기 때문에 펄스폭은 50% 고정(ARR보다 적기만 하면 상관 없다)이고, PWM주기인
주파수를 조절해 제어해보도록 하겠다.
회로는
TIM2의 PWM CH1을 사용했고 PA0 핀을 Passiver Buzzer의 +에 연결하고 다른쪽을 GND에 연결했다.
타이머의 모드 설정은 다음과 같다.
다음 Configuration
APB1에서 TIM2로 공급되는 클락은 90MHz으로 세팅돼있다.
파라미터에서 ARR (Counter Period) 값은 앞으로 소스코드에서 계속 바꿀거긴한데 초기값은 500Hz로 해두려고 Prescaler를 180-1, ARR을 1000-1로 해주었다.
아무튼 처음 세팅은 이렇게 해주고
아래 코드처럼 실시간으로 ARR값을 변경해 주파수를 바꾸려고 했는데 계속 동작이 멈추는 현상이 발생했다.
심지어 처음 시작 후 동작이 멈추는 지점도 일정하지 않고 무작위인 것처럼 느껴졌다.
무엇이 문제였을까?
이전글에서 올렸지만 타이머의 CNT 값이 계속 증가하다가 ARR과 일치하는 순간 overflow가 발생하며 다시 0으로 되돌아간다.
실시간으로 ARR값을 조정하려고 하면, 원래의 ARR값이 1000이고 내가 ARR을 다시 500정도로 조정했는데 그 순간 CNT의 값이 500 밑을 카운트하고 있었다면 문제가 없다. 근데 한 700정도까지 카운트하던 중이었는데 내가 갑자기 ARR을 500으로 바꾸니까 계속 한계치까지 증가하게 되는 것이다.
이에 대한 해결방법이 3개가 있다.
1. Down Counter mode 쓰기
Down Counting mode 는 처음을 ARR 값에서 시작해서 0에 도달하면 underflow가 나고 다시 ARR 값으로 되돌아오는 구조라서 이렇게하면 내가 중간에 ARR값을 조정했을 때 그 다음 주기부터 새 ARR 값이 적용 될 것이다. 코드는 바꿀 필요 없다.
2. Preload 기능 Enable하기
이런 상황을 위해 있는 기능이다. 내가 실행중에 조정한 ARR값이 바로 Auto-Reload 레지스터에 대입되는게 아니라 Preload 레지스터에 먼저 들어가서 문제가 없으면 대입이 된다.
예를들어 원래 ARR이 1000이고 내가 500으로 조정했는데 그 순간 CNT가 700이었다면 1000까지 마저 Count 할때까지 기다리고 다음 주기부터 500이 적용되는 식이다.
코드는 똑같이 사용하면 된다.
3. PSC와 ARR을 바꿔 제어하기
맨처음에 설정했던 PSC (Prescaler) 값과 ARR (Counter Period) 값을 서로 바꾼다. 그리고 ARR 대신 Prescaler 를 바꿔 주파수를 변경하는 것이다.
그리고 소스코드에선 원래 ARR에 대입해 줬던 것을 PSC로 바꾸기만 하면 된다.
단 ARR을 변경시에는 PWM의 펄스폭을 50%로 고정하기로 했으니까 ARR을 바꾼 뒤에 바로 그 절반 값으로 CCR1을 계속 바꿔줘야 하는데
PSC를 조정하는 버전에선 ARR값이 고정이니까 CCR1을 조정할 필요는 없다.
(밑의 0에서 90으로 바꾸는 건 단지 소리를 mute했다가 일정 time interval 후에 다시 내기 위한 부분이다.)
세 방법 모두 끊기지 않고 음악이 계속 출력되는 것을 확인했다.
밑에서는 다운카운터 모드를 사용해 해결한 코드를 공유한다.
소스코드
다음 음계별 표준 주파수를 보고 각 음계마다 조정할 ARR 값을 정해둔다.
5옥타브를 사용했고 나비야 를 연주할거라 도레미파솔만 정했다.
공급클락 90MHz / (PSC(180) * ARR) = 이 주파수니까 ARR 값은 500,000 / 음계별 주파수 해서 나온값이 바로 아래값이다. 편의를 위해 enum으로 정의했다.
/* USER CODE BEGIN PV */
enum notes {
C = 956,
D = 852,
E = 758,
F = 716,
G = 638
};
/* USER CODE END PV */
/* USER CODE BEGIN 2 */
uint16_t bell[] = {G, E, E, F, D, D, C, D, E, F, G, G, G, G, E, E, E, F, D, D, C, E, G, G, E, E, E,
D, D, D, D, D, E, F, E, E, E, E, E, F, G, G, E, E, E, F, D, D, C, E, G, G, E, E, E};
uint8_t bell_length = sizeof(bell)/sizeof(uint16_t);
uint8_t interval[] = {10, 10, 100, 10, 10, 10, 10, 10, 10, 10, 10, 10, 244,
10, 10, 10, 10, 10, 10, 150, 10, 10, 10, 10, 10, 10, 244,
10, 10, 10, 10, 10, 10, 244,
10, 10, 10, 10, 10, 10, 244,
10, 10, 10, 10, 10, 10, 150, 10, 10, 10, 10, 10, 10, 244};
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);
/* USER CODE END 2 */
다음 나비야 연주를 위한 bell과 interval 배열을 USER CODE BEGIN 2에 만들었다. interval이 없으면 주파수가 같은 값일 땐 계속 연속적인 소리가 난다.
for문에 쓰려고 bell_length도 구해주었다.
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1); 문장을 추가한다.
while (1)
{
for(int i=0; i < bell_length; i++) {
TIM2->ARR = bell[i];
TIM2->CCR1 = TIM2->ARR / 2;
HAL_Delay(500);
TIM2->CCR1 = 0;
HAL_Delay(interval[i]);
TIM2->CCR1 = TIM2->ARR / 2;
}
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
bell_length 까지 반복하는데 TIM2->ARR으로 변경할 ARR 값을 대입해주고 듀티비는 50%로 고정하기로 했으니까 TIM2->CCR1 = TIM2->ARR / 2; 를 대입한 후 500ms 딜레이를 준다.
그다음 문장은 음계별 간격을 주기위해 펄스폭을 0으로 해서 mute하고 다시 원래대로 소리를 내게 하는 부분이다.
'임베디드 개발 > STM32 (ARM Cortex-M)' 카테고리의 다른 글
STM32 , 74HC595 시프트 레지스터로 FND 제어하기 , 카운터 / 시계 ( SysTick 타이머 사용) (0) | 2022.06.02 |
---|---|
STM32 , UART 통신으로 피아노 연주하기 , PWM Frequency 제어 ( Passive Buzzer ) (0) | 2022.05.27 |
STM32 , UART 통신 ( 수신 ) 을 이용한 PWM 서보모터 ( SG90 ) 제어 + 펄스폭 찾아내는 팁 (3) | 2022.05.26 |
STM32 , 난수 발생 회로 프로그래밍으로 구현 ( EXTI 사용 ) (0) | 2022.05.25 |
STM32 , Timer Interrupt 타이머 인터럽트 사용하기 (TIM2) (5) | 2022.05.25 |