이번 예제는 PWM입니다.
Timer를 이용하여 LED를 toggle하는데, 버튼을 누를때마다
토글되는 간격이 변하는 PWM예제입니다.
main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | int main(void) { gpiote_init(); bsp_configuration(); ppi_init(); timer2_init(); NRF_POWER->TASKS_CONSTLAT = 1; // Enable interrupt on Timer 2. NVIC_EnableIRQ(TIMER2_IRQn); __enable_irq(); *(uint32_t *)0x4000AC0C = 1; // Start the timer. NRF_TIMER2->TASKS_START = 1; while (true) { // Do nothing. } } | cs |
Line 3 : GPIOTE 초기화
1 2 3 4 5 6 | static void gpiote_init(void) { nrf_gpio_cfg_output(PWM_OUTPUT_PIN_NUMBER); nrf_gpiote_task_config(0, PWM_OUTPUT_PIN_NUMBER, \ NRF_GPIOTE_POLARITY_TOGGLE, NRF_GPIOTE_INITIAL_VALUE_LOW); } | cs |
여기서 PWM_OUTPUT_PIN_NUMBER은 LED1을 뜻합니다.
LED1을 출력으로 설정하고, GPIOTE Task에서 LED1을 toggle방식으로 설정합니다.
Line 4 : bsp 설정
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | static void bsp_configuration() { // Start oscillator for app_timer NRF_CLOCK->LFCLKSRC = (CLOCK_LFCLKSRC_SRC_Xtal << CLOCK_LFCLKSRC_SRC_Pos); NRF_CLOCK->EVENTS_LFCLKSTARTED = 0; NRF_CLOCK->TASKS_LFCLKSTART = 1; while (NRF_CLOCK->EVENTS_LFCLKSTARTED == 0) { // Do nothing. } APP_TIMER_INIT(APP_TIMER_PRESCALER, APP_TIMER_MAX_TIMERS, APP_TIMER_OP_QUEUE_SIZE, false); APP_GPIOTE_INIT(1); uint32_t err_code; err_code = bsp_init(BSP_INIT_BUTTONS, APP_TIMER_TICKS(100, APP_TIMER_PRESCALER), bsp_evt_handler); err_code = bsp_buttons_enable( (1 << BUTTON_PREV_ID) | (1 << BUTTON_NEXT_ID) ); APP_ERROR_CHECK(err_code); } | cs |
4~11 : APP_Timer를 위해 LFCLK을 설정하는 부분이고,
13 : APP_Timer 초기화.
16~19 : button을 사용하기 위한 bsp setting입니다. BUTTON_PREV_ID는 0, BUTTON_NEXT_ID는 1입니다.
버튼을 눌렀을때 Callback되는 함수는 bsp_evt_handler()입니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | void bsp_evt_handler(bsp_event_t evt) { switch (evt) { case BSP_EVENT_KEY_0: duty_cycle -= INCREMENT_VALUE; if ( duty_cycle <= 0 ) duty_cycle = 1; break; case BSP_EVENT_KEY_1: duty_cycle += INCREMENT_VALUE; if ( duty_cycle >= MAX_SAMPLE_LEVELS ) duty_cycle = MAX_SAMPLE_LEVELS - 1; break; default: break; // no implementation needed } } | cs |
5~10 : 버튼 0을 눌렀을 때이며, 버튼이 눌릴때마다 duty_cycle이 줄어듦니다.
duty_cycle 초기값은 1, INCREMENT_VALUE는 16입니다.
12~17 : 버튼 1이 눌려졌을 경우이며, 버튼이 눌려질때마다 duty_cycle이 증가합니다.
다시 main함수에서
Line 5 : PPI 초기화부분입니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | static void ppi_init(void) { NRF_PPI->CH[0].EEP = (uint32_t)&NRF_TIMER2->EVENTS_COMPARE[0]; NRF_PPI->CH[0].TEP = (uint32_t)&NRF_GPIOTE->TASKS_OUT[0]; NRF_PPI->CH[1].EEP = (uint32_t)&NRF_TIMER2->EVENTS_COMPARE[1]; NRF_PPI->CH[1].TEP = (uint32_t)&NRF_GPIOTE->TASKS_OUT[0]; NRF_PPI->CH[2].EEP = (uint32_t)&NRF_TIMER2->EVENTS_COMPARE[2]; NRF_PPI->CH[2].TEP = (uint32_t)&NRF_GPIOTE->TASKS_OUT[0]; NRF_PPI->CHEN = (PPI_CHEN_CH0_Enabled << PPI_CHEN_CH0_Pos) | (PPI_CHEN_CH1_Enabled << PPI_CHEN_CH1_Pos) | (PPI_CHEN_CH2_Enabled << PPI_CHEN_CH2_Pos); } | cs |
3~4 : PPI 채널0설정이며, TIMER2의 COMPARE[0]이 event되면 GPIOTE에서 세팅한 LED1이 토글됩니다.
6~7 : PPI 채널1이며, TIMER2의 COMPARE[1]이 event되면 역시 LED1을 토글합니다.
9~10 : PPI 채널2이며, TIMER2의 COMPARE[2]가 event되면 LED1을 토글합니다.
12~14 : PPI 채널 0~2를 모두 enable시킵니다.
Timer2의 compare0~2가 event발생하면 LED1을 토글 시키겠다는 거네요.
Line 6 : Timer2 초기화입니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | static void timer2_init(void) { NRF_CLOCK->EVENTS_HFCLKSTARTED = 0; NRF_CLOCK->TASKS_HFCLKSTART = 1; while (NRF_CLOCK->EVENTS_HFCLKSTARTED == 0) { // Do nothing. } NRF_TIMER2->MODE = TIMER_MODE_MODE_Timer; NRF_TIMER2->BITMODE = TIMER_BITMODE_BITMODE_16Bit << TIMER_BITMODE_BITMODE_Pos; NRF_TIMER2->PRESCALER = TIMER_PRESCALERS; NRF_TIMER2->TASKS_CLEAR = 1; NRF_TIMER2->CC[0] = MAX_SAMPLE_LEVELS + duty_cycle; NRF_TIMER2->CC[1] = MAX_SAMPLE_LEVELS; NRF_TIMER2->CC[2] = 0; NRF_TIMER2->INTENSET = (TIMER_INTENSET_COMPARE1_Enabled << TIMER_INTENSET_COMPARE1_Pos); } | cs |
3~9 : HFCLK를 시작시키는 거고
11~13 : 보면 타이머를 설정합니다. 모드는 타이머이며, 비트는 16bit, prescalers는 6입니다.
계산해보면 f=16M/₂6=25kHz가 나옵니다. 즉 4us마다 Timer 카운트가 1씩 증가됩니다.
15 : Timer2값을 0으로 초기화하고
17~19 : CC(Capture/Compare)값을 설정합니다.
CC[0]은 MAX_SAMPLE_LEVELS+duty_cycle로 설정합니다. 즉 256+1=257
CC[1]은 MAX_SAMPLE_LEVELS=256
CC[2]는 0
21 : 타이머2의 COMPARE1만 인터럽트를 enable 합니다.
다시 main으로와서
Line 11~17 : Timer2 인터럽트를 enable한 후 Timer를 동작시킵니다.
초기 세팅대로라면, Timer2의 COMPARE[1]값이 256이므로 4us*256=약 1ms쯤에 인터럽트가 걸릴 것입니다.
그리고 LED는 꺼지고, Timer2가 257이 되는 4us 후에 LED는 다시 켜지게 됩니다.
타이머 인터럽트를 보시면
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | void TIMER2_IRQHandler(void) { static bool cc0_turn = false; /**< Keeps track of which CC register to be used. */ if ((NRF_TIMER2->EVENTS_COMPARE[1] != 0) && ((NRF_TIMER2->INTENSET & TIMER_INTENSET_COMPARE1_Msk) != 0)) { NRF_TIMER2->EVENTS_COMPARE[1] = 0; NRF_TIMER2->CC[1] = (NRF_TIMER2->CC[1] + MAX_SAMPLE_LEVELS); if (cc0_turn) { NRF_TIMER2->CC[0] = NRF_TIMER2->CC[1] + duty_cycle; } else { NRF_TIMER2->CC[2] = NRF_TIMER2->CC[1] + duty_cycle; } cc0_turn = !cc0_turn; } } | cs |
Timer2가 256이되어 인터럽트에 걸리게 되면 위 TIMER2_IRQHandler로 들어오게 됩니다.
3 : cc0_turn은 static 변수라 전역변수의 역할을 하게됩니다.
5~21 : if문으로 들어오게되고,
9 : COMPARE[1]레지스터를 초기화하고,
10 : CC[1]에 CC[1]+MAX_SAMPLE_LEVELS를 넣게됩니다. 즉 256+256=512가 됩니다.
18 : 초기 cc0_turn이 false이므로 실행되고, CC[2]은 CC[1]+duty_cycle=512+1=513이 됩니다.
20 : cc0_turn은 true가 되고, 다음 인터럽트 때는
14 : CC[0]값이 CC[1]보다 1큰수가 들어가게 됩니다.
즉, Timer2가 256[LED off]->257[LED on]->512[LED off]->513[LED on]->768[LED off]->769[LED on]....
==>> LED on(1ms)->LED off(4us) 이런식이 되는거죠.
이건 어디까지나 초기상태일때 얘깁니다. duty_cycle이 1일때!!!
버튼2를 누르면 어떻게 될까요??
버튼2를 누를때마다 duty_cycle이 증가하게되고, 최고 255까지 증가합니다.
duty_cycle이 255라고 가정하게되면
Timer2가 256[LED off]->257[LED on]->512[LED off]->767[LED on]->768[LED off]->1023[LED on]....
==>> LED on(4us)->LED off(1ms) 로 duty_cycle이 1일때와 반대로 되어버립니다.
버튼1을 누르면 duty_cycle은 다시 줄어들며 최소 1까지 줄어들게 됩니다.
이 pwm출력은 LED1와 맞물려있어 LED1의 밝기가 달라지게 됩니다.
결과는 아래의 영상을 참고하세요^^
'Study > nRF51xxx(BLE)' 카테고리의 다른 글
nRF51 DK 예제 11 - radiotest (2) (2) | 2015.03.06 |
---|---|
nRF51 DK 예제 11 - radiotest (1) (0) | 2015.03.05 |
nRF51 DK 예제 9 - PPI (0) | 2015.03.04 |
nRF51 DK 예제 8 - Pin change interrupt (0) | 2015.03.03 |
nRF51 DK 예제 7 -Radio Receiver (0) | 2015.03.03 |