nordic的nrf52系列——ADC在使用时如何校准增益误差(基于SDK)
简介: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)的更多相关文章
- Nordic nRF52系列/nRF5340硬件设计(一)选型及原理图设计
Nordic 的BLE系列芯片从第一代的nRF51系列,到第二代的nRF52系列,发展到目前最新的第三代的nRF5340.目前市场中使用最多的nRF52系列一共有七款芯片,它们是:nRF52805.n ...
- nRF52系列来袭,Nordic的低功耗蓝牙方案大有可为
坐落在北欧的挪威不像他的邻居芬兰那样,可以先后依靠NOKIA和愤怒的小鸟在世界科技界享有盛名.在一般人看来,挪威除了一个逐渐式微的Opera浏览器以外,并没有更多拿得出手的科技企业.而事实证明这只 ...
- nordic——nrf52系列SWD设置回读保护
在开发时可能需要回读保护功能,在产品出厂后这个功能可以让你的代码更加安全,无法用SEGGER或者其余方式读取你的代码HEX文件,也就是禁用SWD下载接口.但是SWD锁住了,还想使用(从新下载代码)也是 ...
- nRF52系列——nRF52832来袭
nRF52系列——nRF52832来袭 Nordic凭借着在无线技术的数十年深耕,推出第一个μBlue芯片-- nRF8001.其低功耗等特性在当时吸引了无数厂商的目光,并将这产品应用到多个领域,再之 ...
- NRF52840相对于之前的NRF52系列、NRF51系列增加了什么功能
现在广大客户的蓝牙采用NORDIC越来越多了,NORDIC一直在不断进行技术改进更好的满足市场需求 推出了新款NRF52840.NRF52840更为先进些,支持的功能也多点,比如IEEE802.15. ...
- Nordic nRF51/nRF52开发环境搭建
本文将详述Nordic nRF51系列(包括nRF51822/nRF51802/nRF51422等)和nRF52系列(包括nRF52832/nRF52810/nRF52840)开发环境搭建. 1. 强 ...
- Nordic nRF51/nRF52开发流程说明
Nordic nRF51系列包括nRF51822/nRF51422/nRF51802等芯片,nRF52系列包括nRF52832/nRF52840/nRF52810等芯片,硬件工程师可以按照如下流程去评 ...
- Cookie使用时需要注意个数及大小限制
各浏览器对Cookie有一定的限制,在使用时需要格外注意. 各浏览器之间对cookie的不同限制: IE6.0 IE7.0/8.0/9.0+ Opera FF Safari Chrome cook ...
- EntityFrameWork 使用时碰到的小问题
EntityFrameWork 使用时碰到的小问题 1,在使用orm访问数据库的相目里,也要引用EntityFrameWork.dll,否则无法使用orm 否则,编译错误 错误 5 "Sys ...
- MySQL 安装和启动服务,“本地计算机 上的 MySQL 服务启动后停止。某些服务在未由其他服务或程序使用时将自动停止。”
MySQL 安装和启动服务,以及遇到的问题 MySQL版本: mysql-5.7.13-winx64.zip (免安装,解压放到程序文件夹即可,比如 C:\Program Files\mysql-5. ...
随机推荐
- Kubernetes 稳定性保障手册 -- 可观测性专题
简介: 伴随大家对稳定性重视程度的不断提升.社区可观测性项目的火热,可观测性成为了一个很热门的话题,站在不同的角度会产生不同的理解. 我们从软件开发的生命周期出发,尝试形成对可观测性的一个宏观理解,并 ...
- 行业实战 | 5G+边缘计算+“自由视角” 让体育赛事更畅快
简介: 世界本是多维的.进入5G时代,观众对多维度视觉体验的需求日益增长,5G MEC网络与边缘计算的结合,具备大带宽.低延迟特性,使视频多维视觉呈现成为现实.在第二十三届CUBA中国大学生篮球联赛期 ...
- [FAQ] Goland 始终没有包代码的提示 ?
表现:import 引入的包始终是红色的,表示没有找到引入的包. 注意,在这里开启Go Modules: 然后在 Exteneral Libraries 里看到 Go Modules 即可. Refe ...
- dotnet DirectX 通过 Vortice 控制台使用 ID2D1DeviceContext 绘制画面
在上一篇博客里面告诉大家,如何使用 Vortice 从零开始控制台创建 Direct2D1 窗口.上一篇博客采用的是 CreateDxgiSurfaceRenderTarget 的方式拿到了 ID2D ...
- 【进阶篇】基于 Redis 实现分布式锁的全过程
目录 前言 一.关于分布式锁 二.RedLock 红锁(不推荐) 三.基于 setIfAbsent() 方法 四.使用示例 4.1RedLock 使用 4.2setIfAbsent() 方法使用 五. ...
- 11.IO 流
1.IO 流引入 概述:以应用程序为参照物,读取数据为输入流(Input),写数据为输出流(Output),大量输入输出数据简称 IO 流 原理: 2.IO 流的分类 读写的文件分类 二进制文件:打开 ...
- blazor优雅的方式导入组件相关的js脚本
基本的组件导入方式为: 1 await JsRuntime.InvokeVoidAsync("import", $"XXXXX.js"); 优雅的组件导入方式: ...
- linux系统共享文件夹到局域网
python3 -m http.server https://blog.csdn.net/a772304419/article/details/113338103 Debian下配置Samba服务器 ...
- ansible系列(31)--ansible实战之部署WEB集群架构(1)
目录 1. WEB集群环境说明 2. ansible部署WEB集群实现思路 3. ansible基础环境部署 1. WEB集群环境说明 WEB集群环境说明如下: 客户端:模拟外网主机,地址:192.1 ...
- C++ placement new学习
通常创建对象使用new操作,但这样无法指定在具体某一块内存开辟空间创建对象.而如果 可以指定开辟空间的内存位置,我们可以编写内存池高效的复用同一个内存位置,这样可以避免系统频繁申请可用内存 所占用的时 ...