简介:ADC在实际使用的时候都要进行误差校准,那Nordic的nrf52系列如何进行校准,如果不校准又有什么影响尼,接下来我将通过实验进行测试,验证不校准和校准的影响(本测试的基础是,默认输入阻抗和采样时间都是合理范围的,没有超标)。

测试环境:

硬件:nrf52DK(nrf52832)

软件:基于nRF5_SDK_17.1.0_ddde560中的SAADC例子进行修改

一、误差确定

  在数据手册ADC的电器章节有这样一个数据表,这个表中对应不同的增益模式有不同的误差范围,如我本次测试采用的就是1/6增益,那么对于芯片来说,误差范围在3%以内都是正常的

上面提到了误差,那这个误差影响什么值尼,主要影响了采样精度,如配置一个1/6增益,然后12bit分辨率的ADC然后进行采样,2^12=4096,也就是说在满量程也就是采样电压等于VCC的时候,理论值的采样值应该为4096,也就是说如果我给芯片供电为3.3V,那么我去采样一个3.3V的电源(公地)是采样值应该是4096,采样GND时应该是0,这都是理论值,实际情况是肯定有偏差的,而且没一个芯片的偏差还不一样,但是对于正常的芯片这个偏差都在一个范围,也就是我上表截图的范围。根据表格有这样一个计算,4096*3%=90 ,也就是说在采集3.3V时,值可以为4006~4096都是正常的,因为有90的偏差。

而我们进行校准就是减小这个误差,为什么是减小,是因为误差是不可能消除的。

二、代码

1、测试方式

  添加校准代码,在校准ADC后启动单次采样,单次采样使用定时器触发,每采样10次就进行一次校准,校准期间不进行采样,如果有转换也先进行停止。保证结果的准确性。

2、代码添加

由于是基于历程:SDK\examples\peripheral\saadc 进行测试,已经有想过的timer源文件加入,所以不用再加入timer相关的源文件了,如果你不是用这个例子,是使用自己的工程代码进行添加,那么应该注意timer的选择,如果是ble的项目,ble默认使用的timer0,timer0就不能用了,需要使用timer1,需要启动sdk_config中的关于timer1的所有宏定义,不然就会编译报错。

在下面这份代码中通过设置宏 Y_and_N_calibrate_change 来确定是否启用校准功能。

#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include "nrf.h"
#include "nrf_drv_saadc.h"
#include "nrf_drv_ppi.h"
#include "nrf_drv_timer.h"
#include "boards.h"
#include "app_error.h"
#include "nrf_delay.h"
#include "app_util_platform.h"
#include "nrf_pwr_mgmt.h" #include "nrf_log.h"
#include "nrf_log_ctrl.h"
#include "nrf_log_default_backends.h" /*是否启用校准代码*/
#define Y_and_N_calibrate_change 0 nrf_saadc_value_t test_value; //ADC原始采样值 float V_test=0; //ADC转换后的采样值 static const nrf_drv_timer_t m_timer = NRF_DRV_TIMER_INSTANCE(0); typedef struct{
#if Y_and_N_calibrate_change
bool offset_calibrate_flag; //校准标志
#endif
bool adc_sample_flag; //采样标志
uint32_t adc_sample_timer; //采样时间
}ADC_sample_t; ADC_sample_t m_ADC; #if Y_and_N_calibrate_change
void saadc_callback(nrf_drv_saadc_evt_t const * p_event)
{
switch(p_event->type)
{
case NRF_DRV_SAADC_EVT_CALIBRATEDONE:
m_ADC.offset_calibrate_flag = true;//校准完成回调
break; default:
break;
}
} /*确保要在ADC模块初始化完成后调用校准函数*/
void adc_offset_calibrate(void)
{
ret_code_t err_code; /*检查是否使能的ADC,如果没有就直接返回,不能进行校准*/
if(!nrf_saadc_enable_check())
{
NRF_LOG_INFO("Cannot be calibrated without ADC enabled");
return;
}
err_code = nrf_drv_saadc_calibrate_offset();
APP_ERROR_CHECK(err_code); while(!m_ADC.offset_calibrate_flag)
{
__WFE();
};
NRF_LOG_INFO("End of calibration");
} #endif void timer_handler(nrf_timer_event_t event_type, void * p_context)
{
#if Y_and_N_calibrate_change
static uint8_t counter=0;
#endif
switch(event_type)
{
case NRF_TIMER_EVENT_COMPARE0:
#if Y_and_N_calibrate_change
if(counter<10)
{
counter++;
#endif
m_ADC.adc_sample_flag = true;
NRF_LOG_INFO("ADC sample start");
#if Y_and_N_calibrate_change
}
else
{
counter=0;
/*定时时间到开始校验*/
NRF_LOG_INFO("offset starting....");
nrf_drv_timer_disable(&m_timer);
/*如果有转化,终止转化准备开始校验*/
nrfx_saadc_abort();
/*校准标志位*/
m_ADC.offset_calibrate_flag =false;
}
#endif
break;
default:
break;
} } void saadc_init(void)
{
/*本次配置为使用了单端模式,使用了AIN0通道*/
ret_code_t err_code;
nrf_saadc_channel_config_t channel_config =
NRF_DRV_SAADC_DEFAULT_CHANNEL_CONFIG_SE(NRF_SAADC_INPUT_AIN0); /*ADC采样配置,配置为12bit,不使用过采样,中断优先级,低功耗模式*/
nrf_drv_saadc_config_t config;
config.resolution = NRF_SAADC_RESOLUTION_12BIT;
config.oversample = NRF_SAADC_OVERSAMPLE_DISABLED;
config.low_power_mode = NRFX_SAADC_CONFIG_IRQ_PRIORITY;
config.low_power_mode = NRFX_SAADC_CONFIG_LP_MODE;
#if Y_and_N_calibrate_change
/*nrf_drv_saadc_init的第一个参数为NULL的话将使用默认配置(NRFX_SAADC_DEFAULT_CONFIG),这将使用10bit的分辨率*/
err_code = nrf_drv_saadc_init(&config, saadc_callback);
APP_ERROR_CHECK(err_code);
#else
/*nrf_drv_saadc_init的第一个参数为NULL的话将使用默认配置(NRFX_SAADC_DEFAULT_CONFIG),这将使用10bit的分辨率*/
err_code = nrf_drv_saadc_init(&config, NULL);
APP_ERROR_CHECK(err_code);
#endif
/*配置AIN0通道*/
err_code = nrf_drv_saadc_channel_init(0, &channel_config);
APP_ERROR_CHECK(err_code); } void offset_timer_init(void)
{
ret_code_t err_code; nrf_drv_timer_config_t timer_cfg = NRF_DRV_TIMER_DEFAULT_CONFIG;
timer_cfg.bit_width = NRF_TIMER_BIT_WIDTH_32; err_code = nrf_drv_timer_init(&m_timer, &timer_cfg, timer_handler);
APP_ERROR_CHECK(err_code); /* 通道0作为采样定时*/
uint32_t adc_sample_ticks = nrf_drv_timer_ms_to_ticks(&m_timer, m_ADC.adc_sample_timer);
nrf_drv_timer_extended_compare(&m_timer,
NRF_TIMER_CC_CHANNEL0,
adc_sample_ticks,
NRF_TIMER_SHORT_COMPARE0_CLEAR_MASK,
true); nrf_drv_timer_enable(&m_timer);
} void adc_sample(void)
{
m_ADC.adc_sample_flag = false;
/*采样通道1的值并给到 test_value*/
nrfx_saadc_sample_convert(0,&test_value);
/*
根据数据手册有如下的转换公式: V =[V(P)-V(N)]* GAIN / reference *2^(resolution - m)
V : 采样的实际电压
V(P): 使用采用函数获取到的值
V(N): 使用采用函数获取到的值(只在差分采样才有,如果是单端采样,这为0)
GAIN: 增益值,本例程看channel_config,配置为1/6
reference:参考电压,可以为0.6V的内部电压,或者为VCC/4(四分之一的VCC),
本例程看channel_config(NRF_SAADC_REFERENCE_INTERNAL),配置为0.6V
resolution:采样精度,
m: 单端输入为0,差分输入为1
*/ V_test=test_value* 3.6/4096;
//串口查看打印值
NRF_LOG_INFO("%d",test_value);
NRF_LOG_INFO("V=" NRF_LOG_FLOAT_MARKER "\r\n", NRF_LOG_FLOAT(V_test));
NRF_LOG_FLUSH();
} /**
* @brief Function for main application entry.
*/
int main(void)
{
ret_code_t err_code; /*添加了log*/
err_code = NRF_LOG_INIT(NULL);
APP_ERROR_CHECK(err_code);
NRF_LOG_DEFAULT_BACKENDS_INIT(); /*清零实体*/
memset(&m_ADC,0,sizeof(m_ADC));
/*设置轮训采样时间,当前为1s*/
m_ADC.adc_sample_timer = 1000; saadc_init(); /*开启定时器,启动采样*/
offset_timer_init(); NRF_LOG_INFO("adc test");
while (1)
{
if(m_ADC.adc_sample_flag)
{
adc_sample();
}
#if Y_and_N_calibrate_change
if(!m_ADC.offset_calibrate_flag)
{
adc_offset_calibrate();
/* 校准过程中停止采样,校准完毕后开始采样 */
nrf_drv_timer_enable(&m_timer);
}
#endif
NRF_LOG_FLUSH();
}
}

三、对比

直接对GND进行采样,对比采样偏差的大小。

1、不添加校准:

Y_and_N_calibrate_change 为 0时:

可以看到对于我的板载芯片偏差基本都在-10以上,部分芯片偏差可能更大如-15,-20或者以上,如果在大批量时出现有部分芯片采样值不对,那么可以添加校准,说不定问题就解决了。

2、添加校准:

Y_and_N_calibrate_change 为 1 时:

可以看到,误差减小了,经过上面测试在nrf52系列使用ADC时校准是很有必要的。

结论:

1、因为校准只会减少误差,并不是把误差消除,所以对于在实际应用中如果有些芯片增益误差达到了3%的临界值,那么就算添加了校准,可能也只校准到了2%,比如12bit的分辨率,满值是4096,那么极限误差是±90,校准后误差还可能是±60,这个时候只能说软件处理了。

2、校准只能在初始化ADC模块,但是在采样开始前,或者结束后进行,所以在代码中进行了是否采样和是否使能了ADC模块的判断。

3、ADC精度还可能和晶振的精度有关,在有些时候晶振偏差过大,也会都在ADC采样偏差大

nordic的nrf52系列——ADC在使用时如何校准增益误差(基于SDK)的更多相关文章

  1. Nordic nRF52系列/nRF5340硬件设计(一)选型及原理图设计

    Nordic 的BLE系列芯片从第一代的nRF51系列,到第二代的nRF52系列,发展到目前最新的第三代的nRF5340.目前市场中使用最多的nRF52系列一共有七款芯片,它们是:nRF52805.n ...

  2. nRF52系列来袭,Nordic的低功耗蓝牙方案大有可为

      坐落在北欧的挪威不像他的邻居芬兰那样,可以先后依靠NOKIA和愤怒的小鸟在世界科技界享有盛名.在一般人看来,挪威除了一个逐渐式微的Opera浏览器以外,并没有更多拿得出手的科技企业.而事实证明这只 ...

  3. nordic——nrf52系列SWD设置回读保护

    在开发时可能需要回读保护功能,在产品出厂后这个功能可以让你的代码更加安全,无法用SEGGER或者其余方式读取你的代码HEX文件,也就是禁用SWD下载接口.但是SWD锁住了,还想使用(从新下载代码)也是 ...

  4. nRF52系列——nRF52832来袭

    nRF52系列——nRF52832来袭 Nordic凭借着在无线技术的数十年深耕,推出第一个μBlue芯片-- nRF8001.其低功耗等特性在当时吸引了无数厂商的目光,并将这产品应用到多个领域,再之 ...

  5. NRF52840相对于之前的NRF52系列、NRF51系列增加了什么功能

    现在广大客户的蓝牙采用NORDIC越来越多了,NORDIC一直在不断进行技术改进更好的满足市场需求 推出了新款NRF52840.NRF52840更为先进些,支持的功能也多点,比如IEEE802.15. ...

  6. Nordic nRF51/nRF52开发环境搭建

    本文将详述Nordic nRF51系列(包括nRF51822/nRF51802/nRF51422等)和nRF52系列(包括nRF52832/nRF52810/nRF52840)开发环境搭建. 1. 强 ...

  7. Nordic nRF51/nRF52开发流程说明

    Nordic nRF51系列包括nRF51822/nRF51422/nRF51802等芯片,nRF52系列包括nRF52832/nRF52840/nRF52810等芯片,硬件工程师可以按照如下流程去评 ...

  8. Cookie使用时需要注意个数及大小限制

    各浏览器对Cookie有一定的限制,在使用时需要格外注意. 各浏览器之间对cookie的不同限制:   IE6.0 IE7.0/8.0/9.0+ Opera FF Safari Chrome cook ...

  9. EntityFrameWork 使用时碰到的小问题

    EntityFrameWork 使用时碰到的小问题 1,在使用orm访问数据库的相目里,也要引用EntityFrameWork.dll,否则无法使用orm 否则,编译错误 错误 5 "Sys ...

  10. MySQL 安装和启动服务,“本地计算机 上的 MySQL 服务启动后停止。某些服务在未由其他服务或程序使用时将自动停止。”

    MySQL 安装和启动服务,以及遇到的问题 MySQL版本: mysql-5.7.13-winx64.zip (免安装,解压放到程序文件夹即可,比如 C:\Program Files\mysql-5. ...

随机推荐

  1. Kubernetes 稳定性保障手册 -- 可观测性专题

    简介: 伴随大家对稳定性重视程度的不断提升.社区可观测性项目的火热,可观测性成为了一个很热门的话题,站在不同的角度会产生不同的理解. 我们从软件开发的生命周期出发,尝试形成对可观测性的一个宏观理解,并 ...

  2. 行业实战 | 5G+边缘计算+“自由视角” 让体育赛事更畅快

    简介: 世界本是多维的.进入5G时代,观众对多维度视觉体验的需求日益增长,5G MEC网络与边缘计算的结合,具备大带宽.低延迟特性,使视频多维视觉呈现成为现实.在第二十三届CUBA中国大学生篮球联赛期 ...

  3. [FAQ] Goland 始终没有包代码的提示 ?

    表现:import 引入的包始终是红色的,表示没有找到引入的包. 注意,在这里开启Go Modules: 然后在 Exteneral Libraries 里看到 Go Modules 即可. Refe ...

  4. dotnet DirectX 通过 Vortice 控制台使用 ID2D1DeviceContext 绘制画面

    在上一篇博客里面告诉大家,如何使用 Vortice 从零开始控制台创建 Direct2D1 窗口.上一篇博客采用的是 CreateDxgiSurfaceRenderTarget 的方式拿到了 ID2D ...

  5. 【进阶篇】基于 Redis 实现分布式锁的全过程

    目录 前言 一.关于分布式锁 二.RedLock 红锁(不推荐) 三.基于 setIfAbsent() 方法 四.使用示例 4.1RedLock 使用 4.2setIfAbsent() 方法使用 五. ...

  6. 11.IO 流

    1.IO 流引入 概述:以应用程序为参照物,读取数据为输入流(Input),写数据为输出流(Output),大量输入输出数据简称 IO 流 原理: 2.IO 流的分类 读写的文件分类 二进制文件:打开 ...

  7. blazor优雅的方式导入组件相关的js脚本

    基本的组件导入方式为: 1 await JsRuntime.InvokeVoidAsync("import", $"XXXXX.js"); 优雅的组件导入方式: ...

  8. linux系统共享文件夹到局域网

    python3 -m http.server https://blog.csdn.net/a772304419/article/details/113338103 Debian下配置Samba服务器 ...

  9. ansible系列(31)--ansible实战之部署WEB集群架构(1)

    目录 1. WEB集群环境说明 2. ansible部署WEB集群实现思路 3. ansible基础环境部署 1. WEB集群环境说明 WEB集群环境说明如下: 客户端:模拟外网主机,地址:192.1 ...

  10. C++ placement new学习

    通常创建对象使用new操作,但这样无法指定在具体某一块内存开辟空间创建对象.而如果 可以指定开辟空间的内存位置,我们可以编写内存池高效的复用同一个内存位置,这样可以避免系统频繁申请可用内存 所占用的时 ...