nRF5 SDK (15) - nRF52840-DK SAADC 제어
Updated:
15. nRF52840-DK SAADC 제어
이번 포스팅에서는 nRF5 SDK
에서 제공되는 예제 중 SAADC 관련 예제를 간단하게 살펴보고자 한다.
15.1 SAADC in nRF52840 SoC
현재 필자는 펌웨어 개발을 주로 하고 있어서 하드웨어 분야에 대해서는 잘 모르지만, ADC 설계는 파이프라인, 델타-시그마
등 어느 정도 용도에 따라 규격화된 몇몇 구조가 있는 듯 하다. 그 중 nRF52
시리즈에는 Successive approximation(SA)
구조로 설계된 8-channel ADC 가 내장되어 있다. (차동모드 적용시 4-channel)
주요 스펙은 다음과 같으며, 보다 자세한 특성은 데이터 시트를 참고하면 된다.
- Maximum sampling rate:
200 kHz
- Resolution:
8, 10, 12, 14 bits
- Oversampling:
0, 2 ~ 256x
15.2 SAADC example
nRF5 SDK
는 대부분의 주변기기 활용 방법에 대한 소스코드를 …examples/peripherals 폴더 내에서 제공해주고 있다.
참고로 해당 폴더에 있는 예제 프로젝트들은
SoftDevice
를 사용하지 않으므로,BLE
통신을 활성화 하는 프로젝트의 소스 코드와는 다소 차이가 있을 수는 있지만,nRF5 SDK
에서 제공되는 주변기기 라이브러리를(e.g. UART, TWI, SPI, SAADC, GPIOTE...)
를 어떻게 제어하면 되는지 대략적으로 참고할 수 있다.
SAADC 의 경우는 아래의 폴더에 있는 프로젝트에 그 활용 예가 정리되어 있다.
- nRF5 SDK 버전:
17.1.0
📁 nRF5_SDK.../examples/peripheral/saadc
다음은 위 프로젝트의 소스 코드의 main
함수 부분이다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | /** * @brief Function for main application entry. */ int main(void) { uint32_t err_code = NRF_LOG_INIT(NULL); APP_ERROR_CHECK(err_code); NRF_LOG_DEFAULT_BACKENDS_INIT(); ret_code_t ret_code = nrf_pwr_mgmt_init(); APP_ERROR_CHECK(ret_code); saadc_init(); saadc_sampling_event_init(); saadc_sampling_event_enable(); NRF_LOG_INFO("SAADC HAL simple example started."); while (1) { nrf_pwr_mgmt_run(); NRF_LOG_FLUSH(); } } | cs |
위 소스 코드 중 핵심이 되는 부분은 다음의 세 줄이다.
1 2 3 4 5 6 7 8 9 10 11 | /** * @brief Function for main application entry. */ int main(void) { //... saadc_init(); saadc_sampling_event_init(); saadc_sampling_event_enable(); //... } | cs |
15.3 SAADC example 소스코드 분석
15.3.1 saadc_init();
먼저, saadc_init
함수 내에서는 saadc 모듈을 초기화 하는 함수를 호출하며, 이때 ADC 의 기본적인 채널 설정 값을 입력하도록 되어있다.
SE (Single Ended) 모드로 동작시킬지, Differential 모드로 동작시킬지 또한, 어떤 핀을 활성화 할 것인지 등을 선택할 수 있으며, 아래 예제 코드에서는 제공되고 있지 않으나,
nrf_saadc_channel_config_t
타입의 변수를 이용해서 인터럽트의 우선순위나, ADC 해상도 (resolution
), 오버 샘플링 기능등을 설정할 수도 있다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | void saadc_init(void) { ret_code_t err_code; nrf_saadc_channel_config_t channel_config = NRF_DRV_SAADC_DEFAULT_CHANNEL_CONFIG_SE(NRF_SAADC_INPUT_AIN0); //NRF_SAADC_INPUT_AIN0 err_code = nrf_drv_saadc_init(NULL, saadc_callback); APP_ERROR_CHECK(err_code); err_code = nrf_drv_saadc_channel_init(0, &channel_config); APP_ERROR_CHECK(err_code); err_code = nrf_drv_saadc_buffer_convert(m_buffer_pool[0], SAMPLES_IN_BUFFER); APP_ERROR_CHECK(err_code); err_code = nrf_drv_saadc_buffer_convert(m_buffer_pool[1], SAMPLES_IN_BUFFER); APP_ERROR_CHECK(err_code); } | cs |
다음의 그림은 필자가 별도로 구성한 예제 프로그램 내의 소스 코드 중 하나 이상의 ADC 채널을 초기화 하는 부분의 코드이다. 약간 수정된 부분은 있지만, 기본적인 구조는 하나의 ADC 채널을 초기화 하는 로직과 유사한 것을 볼 수 있을 것이다
참고로 ADC
init()
함수 마지막 부분을 보면nrf_drv_saadc_buffer_convert(,)
함수를 호출하는 것을 볼 수 있다.nRF5 SDK
예제에서 이를 두 번 호출하는 이유에 대해서는Double Buffering
기능을 활성화하여 데이터 누락이 없도록 하기 위함이라 하는데, 정확히 맞는 설명인지는 모르겠다. (하나 이상의 ADC 초기화 할 때는Double Buffering
컨셉이 제대로 적용되지 않았던 것 같다😴)
15.3.2 saadc_sampling_event_init();
본 예제에서는 PPI
기능을 활용해서 ADC 를 제어하는 방법을 보여주고 있으며, saadc_sampling_event_init
함수에서는 ADC 초기화 이후 PPI
기능을 이용해 ADC
와 Timer
를 연동시키는 작업을 수행한다.
PPI
는 노르딕 칩에서 제공되는 기능으로 CPU
개입 없이 내부 모듈 사이의 event
와 task
를 연동하기 위해 사용된다. 보다 자세한 내용은 사용하고자 하는 노르딕 칩의 데이터 시트를 확인하자.
가령, 본 예제에서처럼
Timer
와ADC
사이의 동작을 연동시키면,CPU
개입 없이Timer
의 카운터 값이 설정값에 도달했을 때 라는event
가 발생하면,ADC
전압을 측정해라 라는task
를 자동으로 수행하게 할 수 있다.
예제의 소스 코드를 보면 400 ms 마다 Timer
틱 (tick)이 발생하도록 설정한 뒤에, 해당 틱이 발생했을 때 (event
), ADC
샘플링을 수행하라고 task
를 설정해주는 함수가 작성되어있는 것을 볼 수 있을 것이다.
ADC
연동이 마무리 되면, saadc_sampling_event_enable()
함수를 호출해서 해당 기능을 활성화 하는 것으로 샘플링을 시작하게 된다.
15.3.4 활용 예시
앞서 살펴본 예제에서는 PPI
기능을 활용해 ADC
를 일정 시간마다 제어하고 있으나, ADC
초기화 이후에 nrf_drv_saadc_sample();
함수를 호출하는 것으로 원하는 시점에 샘플링을 수행할 수도 있다.
또한, 위 예제에서의 틱 값을 조절하면 CPU
개입 없이도 고속으로 ADC
채널의 전압을 샘플링 할 수 있는데 (물론 충분한 버퍼를 할당해두어야할 것이다), 이를 이용하면 일정 주파수 범위 내의 교류 전압도 측정해볼 수 있을 것이다.
국내에서 일반 가정에 보급되는 교류의 주파수는
60 Hz
이므로 초당 120 개 이상의 샘플을 측정해서 저장하면 원본 파형을 복원할 수 있을 것이다. 실제로 테스트 해보면, 초당1000
개의 샘플 측정 시 (측정 간격을1 ms
로 설정) 별도의 후속처리 없이도 원본 파형에 가까운 그래프가 얻어짐을 확인해볼 수 있었다
- 소스코드 적용 예시
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | void saadc_sampling_event_init(void) { ret_code_t err_code; // ... /* setup m_timer for compare event every 400ms */ uint32_t ticks = nrf_drv_timer_ms_to_ticks(&m_timer, 1); nrf_drv_timer_extended_compare(&m_timer, NRF_TIMER_CC_CHANNEL0, ticks, NRF_TIMER_SHORT_COMPARE0_CLEAR_MASK, false); nrf_drv_timer_enable(&m_timer); // ... } | cs |
Comments