NRF52832學習筆記(7)——ADC接口使用

一、簡介

1.1 主要特點

NRF52832 中 ADC 爲一個 逐次逼近(SAADC) 模擬數字轉換器

  • 8/10/12 位分辨率,採用過採樣可以達到 14 位分辨率。
  • 多達 8 個輸入通道:
    單端輸入時有 1 個通道,2 個通道組成差分輸入。
    單端和差分輸入時可以配置成掃描模式。
  • 滿量程輸入範圍爲 0 和 VDD
  • 可以通過軟件觸發採樣任務啓動採樣,也可以使用低功耗 32.768KHz 的 RTC 定時器或更精確的 1/16MHz 定時器通過 PPI 來觸發採樣任務。
  • NRF52832 的 SAADC 支持 單次模式和掃描模式
    單次的採集模式只使用一個採集通道。
    掃描模式是按照順序採樣一系列通道。Sample delay between channels is tack + tconv
    which may vary between channels according to user configuration of tack.
  • 通過 EasyDMA 可以直接將採樣結果保存到 RAM。
  • 無需外部定時器即可實現連續採樣。
  • 可配置通道輸入負載電阻。
  • 具備採樣值門限檢測功能。

1.2 採樣模式

NRF52832 的 ADC 有 16 個通道(其中 8個正端輸入通道N ,和 8個負端輸入P),因此信號的採樣模式可分爲單端輸入和差分輸入。

默認狀態下,ADC 的配置模式爲 單端輸入 (即 CH[n].CONFIG 寄存器中的 MODE = 0),此時芯片內部將 ADC 負極輸入短接到地。

差分輸入 (即 CH[n].CONFIG 寄存器中的 MODE = 1),則是把負極通過負向端輸入,通過計算兩端的差值來換算出採樣結果。

使用單端模式時,是將內部地和外部待測電壓的參考地假設爲一樣來考慮的,但是地彈噪聲會導致 ADC 產生誤差,如果這個誤差超過我們能接受的範圍,建議使用差分輸入。

在參考源上,相對於 nRF51822,nRF52832 取消了外部參考源,只能使用內部參考源。

ADC 輸出的採樣取決與 CH[n].CONFIG 和 RESOLUTION 寄存器配置的參數,採樣結果計算公式如下:

其中:

  • V§:ADC 輸入正極。
  • V(N):ADC 輸入負極。
  • GAIN:CH[n].CONFIG 寄存器中設置的增益 GAIN(1/6、1/5、1/4、1/3、1/2、1、2、4)。
  • REFERENCE:參考電壓(兩種方式:一個是內部參考電壓0.6V,另一個是VDD/4爲參考電壓)。
  • RESOLUTION:採樣精度(8/10/12,採用過採樣可以達到 14 位)。
  • m:如果 ADC 配置爲單端模式,m = 0,如果 ADC 配置爲差分模式,m = 1。

1.3 工作模式

SAADC 有三種工作模式:單次轉換、連續轉換和掃描模式。

1.3.1 單次轉換

配置ADC的寄存器CH[n].PSELP、CH[n].PSELN 和 CH[n].CONFIG 使得ADC工作於單次模式。觸發採樣任務後,ADC 開始採樣輸入電壓,採樣時間通過 CH[n].CONFIG.TACQ 配置。EVENTS_DONE 事件表明了一次採樣的完成。
在沒有過採樣發生的情況下 EVENTS_RESULTDONE 事件等同於 EVENTS_DONE 事件,注意在實際採樣數據通過EasyDMA 被保存到 RAM 之前,這兩個事件都會產生。

1.3.2 連續轉換

可以通過下面兩種方式實現連續模式:

  • 使用 ADC 內部定時器實現定時採樣,ADC 有一個 SAMPLERATE 寄存器,該寄存器可以配置爲 Timer,配置 SAMPLERATE 的比較值 SAMPLERATE.CC 即可實現定時採樣。這種方式下,觸發一次採樣任務即可對所有使能的通道進行採樣。
  • 使用 nRF52832 的通用定時器定時通過 PPI 觸發採樣,實現連續採樣。這種方式不能算是 ADC 本身的連續採樣功能,因爲它是藉助了其他外設實現的連續採樣。
     採樣速率由 SAMPLERATE.CC 控制,即定時時間長,採樣速率慢,定時時間短,採樣速率快,需要注意的是,使用連續採樣模式的時候,採樣速率應符合下面的公式:

使用 SAMPLERATE 的 Timer 實現的連續採樣不能和掃描模式結合使用,連續採樣模式下只能使用一個通道。
EVENTS_DONE 事件表明了一次採樣的完成,連續採樣模式下,在沒有過採樣發生的情況下 EVENTS_RESULTDONE 事件等同於 EVENTS_DONE 事件,注意在實際採樣數據通過 EasyDMA 被保存到 RAM 之前,這兩個事件都會產生。

1.3.3 連續轉換

當我們使能一個 ADC 通道,ADC 工作於單次模式,當使能的通道數量大於 1 個,ADC進入掃描模式。
掃描模式下,採樣所有通道花費的總時間如下式:

EVENTS_DONE 事件表明了一次採樣的完成,掃描模式下,在沒有過採樣發生的情況下EVENTS_RESULTDONE 事件等同於 EVENTS_DONE 事件,注意在實際採樣數據通過EasyDMA 被保存到 RAM 之前,這兩個事件都會產生。

二、硬件連接

功能口 引腳
AIN0 0
AIN1 2
AIN2 3
AIN3 4
AIN4 5

三、移植文件(單次採樣)

注意:以下出現缺失common.h文件錯誤,去除即可。uint8改爲uint8_t或unsigned char或自己宏定義
鏈接:https://pan.baidu.com/s/1rsa6nkGuU1QgEsQ0i1PGZQ 提取碼:vpyt
board_adc.cboard_adc.h 兩個文件加入工程的Application文件夾下

3.1board_adc.c

/*********************************************************************
 * INCLUDES
 */
#include "nrfx_saadc.h"
#include "nrf_drv_saadc.h"
#include "app_error.h" 

#include "board_adc.h"
#include "User/user_battery.h"

#include "nrf_log.h"

static void adcCallbackFunc(nrf_drv_saadc_evt_t const *pEvent);

/*********************************************************************
 * LOCAL VARIABLES
 */
static nrf_saadc_value_t s_bufferPool[SAMPLES_IN_BUFFER];
	
/*********************************************************************
 * PUBLIC FUNCTIONS
 */
/**
 @brief ADC的初始化函數
 @param 無
 @return 無
*/
void ADC_Init(void)
{
	ret_code_t errCode;
	// ADC初始化
	errCode = nrf_drv_saadc_init(NULL, adcCallbackFunc);
	APP_ERROR_CHECK(errCode);
	// ADC通道配置
	nrf_saadc_channel_config_t channelConfig = NRF_DRV_SAADC_DEFAULT_CHANNEL_CONFIG_SE(NRF_SAADC_INPUT_AIN0);	// 單端輸入
	// ADC通道初始化
	errCode = nrf_drv_saadc_channel_init(0, &channelConfig);
	APP_ERROR_CHECK(errCode);
	// 緩衝配置
	errCode = nrf_drv_saadc_buffer_convert(s_bufferPool, SAMPLES_IN_BUFFER);
	APP_ERROR_CHECK(errCode);
}

/**
 @brief ADC讀取
 @param 無
 @return 結果在回調函數的緩衝區中
*/
void ADC_Read(void)
{
	ret_code_t errCode;
	errCode = nrf_drv_saadc_sample();
	APP_ERROR_CHECK(errCode);
}

/**
 @brief 開啓ADC,與初始化沒有區別,爲了與Disable成對出現
 @param 無
 @return 無
*/
void ADC_Enable(void)
{
	ADC_Init();
}

/**
 @brief 禁用ADC
 @param 無
 @return 無
*/
void ADC_Disable(void)
{
	nrfx_saadc_uninit();
}


/*********************************************************************
 * LOCAL FUNCTIONS
 */
/**
 @brief ADC中斷處理回調函數
 @param 無
 @return 無
*/
static void adcCallbackFunc(nrf_drv_saadc_evt_t const *pEvent)
{
	if(pEvent->type == NRF_DRV_SAADC_EVT_DONE)																	// 採樣完成
	{
		nrf_saadc_value_t adcResult;
		uint16 batteryVoltage;
		uint8 batteryPercentage;
		ret_code_t errCode;
		
		// 設置好緩存,爲下次轉換緩衝做準備,並且把導入到緩衝的值提取出來
		errCode = nrf_drv_saadc_buffer_convert(pEvent->data.done.p_buffer, SAMPLES_IN_BUFFER);					
		APP_ERROR_CHECK(errCode);
		adcResult = pEvent->data.done.p_buffer[0];
		// 電池電壓轉換計算
		batteryVoltage = ADC_RESULT_IN_MILLI_VOLTS(adcResult);
	}
}

/****************************************************END OF FILE****************************************************/

3.2 board_adc.h

#ifndef _BOARD_ADC_H_
#define _BOARD_ADC_H_

/*********************************************************************
 * INCLUDES
 */
#include "common.h"

/*********************************************************************
 * DEFINITIONS
 */
#define ADC_REF_VOLTAGE_IN_MILLIVOLTS   600					/**< Reference voltage (in milli volts) used by ADC while doing conversion. */
#define ADC_PRE_SCALING_COMPENSATION    6					/**< The ADC is configured to use VDD with 1/3 prescaling as input. And hence the result of conversion is to be multiplied by 3 to get the actual value of the battery voltage.*/
#define DIODE_FWD_VOLT_DROP_MILLIVOLTS  1000					/**< Typical forward voltage drop of the diode . */
#define ADC_RES_10BIT                   1024				/**< Maximum digital value for 10-bit ADC conversion. */

// VP = (RESULT * REFERENCE / 2^10) * 6
#define ADC_RESULT_IN_MILLI_VOLTS(ADC_VALUE)\
        ((((ADC_VALUE) * ADC_REF_VOLTAGE_IN_MILLIVOLTS) / ADC_RES_10BIT) * ADC_PRE_SCALING_COMPENSATION) 
 
#define SAMPLES_IN_BUFFER				1

/*********************************************************************
 * API FUNCTIONS
 */
void ADC_Init(void);
void ADC_Read(void);
void ADC_Enable(void);
void ADC_Disable(void);

#endif /* _BOARD_ADC_H_ */

四、API調用

需包含頭文件 board_adc.h

ADC_Init

功能 初始化ADC驅動
函數定義 void ADC_Init(void)
參數
返回

ADC_Read

功能 ADC讀取函數
函數定義
參數
返回

ADC_Enable

功能 使能ADC,內部重新初始化ADC驅動,在ADC_Disable之後使用
函數定義 void ADC_Enable(void)
參數
返回

ADC_Disable

功能 禁用ADC,以進入低功耗
函數定義 void ADC_Disable(void)
參數
返回

五、SDK配置

點擊 sdk_config.h 文件

選擇 Configuration Wizard

nRF_Drivers 中勾選SAADC相關選項

六、使用例子

1)添加頭文件

#include "board_adc.h"

2)添加初始化代碼(SDK15.3 中 ble_peripheral 的 ble_app_template 工程 main() 函數中)
加入 ADC_Init() 並在初始化後調用 ADC_Disable 進入低功耗,在需要用ADC時調用 ADC_Enable 開啓ADC

int main(void)
{
	bool erase_bonds;

    /*-------------------------- 外設驅動初始化 ---------------------------*/
	// Initialize.
    log_init();																	// 日誌驅動初始化																	
    timers_init();																// 定時器驅動初始化(在此加入自定義定時器)
	ADC_Init();																    // ADC驅動初始化	
	
	/*-------------------------- 藍牙協議棧初始化 ---------------------------*/
    power_management_init();
    ble_stack_init();															// 協議棧初始化
    gap_params_init();
    gatt_init();
    advertising_init();															// 廣播初始化
    services_init();															// 服務初始化
    conn_params_init();															// 連接參數初始化
    peer_manager_init();
	
	/*-------------------------- 開啓應用 ---------------------------*/
	// Start execution.
    NRF_LOG_INFO("Template example started."); 
    advertising_start(erase_bonds);												// 開啓廣播	
	application_timers_start();													// 定時器應用開啓(在此開啓自定義定時器)	
	ADC_Disable();																// 禁用ADC,進入低功耗模式,等待讀取時再開啓
	
    // Enter main loop.
    for(;;)
    {
        idle_state_handle();
    }
}

3)在定時器回調中開啓ADC讀取,然後再開一個10ms定時器關閉ADC進入低功耗

static void timer_batteryMeasureOnCallback(void *arg)
{
	UNUSED_PARAMETER(arg);
	ADC_Enable();																			// 開啓ADC
	ADC_Read();
	app_timer_start(s_batteryMeasureOffTimer, BATTERY_MEASURE_OFF_PERIOD, NULL);	
}

4)在ADC中斷處理回調函數中,將採集ADC值進行電壓值轉換

/**
 @brief ADC中斷處理回調函數
 @param 無
 @return 無
*/
static void adcCallbackFunc(nrf_drv_saadc_evt_t const *pEvent)
{
	if(pEvent->type == NRF_DRV_SAADC_EVT_DONE)																	// 採樣完成
	{
		nrf_saadc_value_t adcResult;
		uint16 batteryVoltage;
		ret_code_t errCode;
		
		// 設置好緩存,爲下次轉換緩衝做準備,並且把導入到緩衝的值提取出來
		errCode = nrf_drv_saadc_buffer_convert(pEvent->data.done.p_buffer, SAMPLES_IN_BUFFER);					
		APP_ERROR_CHECK(errCode);
		adcResult = pEvent->data.done.p_buffer[0];
		// 電池電壓轉換計算
		batteryVoltage = ADC_RESULT_IN_MILLI_VOLTS(adcResult);
	}
}

• 由 Leung 寫於 2020 年 1 月 6 日

• 參考:青風電子社區
    Nordic–nrf52832–ADC

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章