ADC极限采样率实验
项目背景
项目为用单片机采集VGA图像的应用,当前所用的单片机为GD32F330,用于降成本的替代方案原本选择的是GD32E230,咨询过代理商那边说是新出的GD32F310是未来主力的型号,供货情况更加稳定一些,且价格上比较有优势。所以当在公众号中看到有GD32F310开发板试用的时候,就申请了来。趁着周末的时间来试用一下同时评估一下用来替换现在使用的GD32F330的可能性。
当前项目是用来采集VGA图像,类似这种应用其实并不适合单片机来,但是为了极致降低项目的成本,我司还是使用了单片机,选用单片机最看重的一点就是ADC的速度。搜索市面上国产的单片机,主流的都是1MSPS采样率左右,雅特力的AT32F421是2MSPS,而GD32E230的可以达到2.3MSPS,而330系列的更是能达到2.86MSPS,是目前市场上这个价位ADC速度最高的单片机了,而310与330是基于同一个平台的单片机,虽然其主频不如330高,但是ADC外设部分应该是相同的,本篇评测就是从手册分析到实际使用测试一下F310的ADC速度到底能达到多少。
ADC参数分析
首先查看数据手册GD32F310xx_Datasheet_Rev1.1.pdf中的3.6章ADC参数部分,其标定的ADC采集速率可以达到2.86MSPS。
而通过查找其ADC参数表,得到其在12-bit的时候采样率为2.57MSPS,ADC时钟最大为36MHz。这与3.6章的简介部分冲突。
这里正确的值应该是2.57MSPS,因2.57MSPS=36MHz/(14Hz/SPS)=2.57MSPS
而简介中的ADC可以达到2.86MSPS是以40MHzADC时钟计算的(2.857MSPS=40MHZ/(14Hz/SPS))
这种参数不一致的情况是比较令人困惑的,使用F330的时候也是同样的状况,希望GD方面可以完善一下技术手册,或者增加一些解释进去。
下面就是看如何得到手册中标定的最快ADC采样速率,即12Bit/2.86MSPS。这里要查看时钟树。
简介中的2.86MSPS假定的是ADC时钟是40MHZ的时候,也就是说ADC外设的极限值时这个时钟频率,这里跟时钟树的标定一致,即ADC最大支持40MHz的频率,但是这里从时钟树上来看是无法产生40MHZ的ADC时钟的。
这里ADC的时钟来源可以选择三个。
- 28MHz内部振荡器÷1或÷2,则最高ADC频率是28MHz.
- APB2时钟分频,最高是CK_APB2/2。CK_APB2的频率最大是36MHz,则ADC频率最大只能到18MHz.
- AHB时钟分频,最高是CK_AHB/3。CK_AHB最高频率是72MHz,则ADC频率最大只能到24MHz。
经过上面的分析可以看到,按照手册的限制值来是远达不到36MHz甚至40MHz的ADC输入时钟的,这里就很令人困惑了,这里设计成AHB的分频如果可选2468,岂不是正好就可以实现36MHzADC最大频率了,真的是令人费解。当然F330也是一样的情况,时钟树无论如何设置都是达不到设计值。经过联系代理商的技术支持,得知要达到ADC设计值,只能进行超频了。
从时钟树进行反推,要想实现2.86MSPS采样,只能使得
- CK_APB2运行到80MHz,APB2分频系数设置成1,系统时钟也是80MHz.
- CK_AHB运行到120MHz,ADC频率来源为CK_AHB/3=40MSPS。
第1种方案是进行局部超频,影响的只是APB2时钟树上的外设,波及范围小一些。第2种方式是对系统主频进行超频,超频之后的速度甚至超过了F330(84MHz)和F350(108MHz)。这令人有点心里没底天下会有这种把奔腾当成酷睿的好事。但是据供应商说法,这三款芯片的平台都是相同的,F310是可以跑到这个频率。
理论分析完毕,那么我们就都实验一下这两种方式,进行一下实际的测试。
ADC配置
由于项目此前已经使用的是F330,其固件包都是同一个,只要在目标中进行切换一下器件即可。
这里项目预计可以在多个芯片上跑,所以这里针对每个芯片都建立一个不同的Targets,这样只要在项目中下拉选择不同的Target即可选择不同的芯片平台不用每次都要配置Device等。这个应该是比较基本的东西,STM32的固件库例程中用的都是这样的方式。
ROM和RAM由于与F330一致,这里就不需要更改。按照图中所设置的即可,
如果是其他不同的配置,那就需要参考手册中的参数进行相应的修改。
开发板上板载的是CMSIS-DAP Debugger,需要修改修改并配置好这里。
点击Settings选择一个最大的频率
点击Flash Download选项卡,看到其已经配置好了Flash编程算法,否则需要自己添加进去。
编译了一下,没什么错误,可以跟GD32F330一样的代码(这里发现是要更改预定义才是真正切换了器件,参考后文)。
那么现在跑到ADC初始化的部分去检查一下系统时钟的设定部分。
查看代码system_dg32f3x0.c中的系统初始化部分,其是需要#define GD32F310 才可以使用正确的配置,那么这里回过头来在Target配置中将之前#define的GD32F330改成GD32F310.
好戏来了。不过也是我代码中针对不同MCU包含了不同文件导致的,待我修复一下。
#ifdef GD32E230
#include "./GD32E23X/BSP_GD32E23X.h"
#elif AT32F421
#include "./AT32F421/BSP_AT32F421.h"
#elif GD32F330
#include "./GD32F3X0/BSP_GD32F3X0.h"
#endif
增加
#elif GD32F310
#include "./GD32F3X0/BSP_GD32F3X0.h"
因为这两个的配置基本都相同,我就使用了同一个BSP头文件。
现在开始查看一下GD32F310的ADC配置部分。
首先看系统时钟配置,位于system_gd32f3x0.c
中
绿色的部分是固件库中标定的频率,为72M,红框中的是我自己定义的80M主频的配置,将他拷贝过来。
#if defined (GD32F310)
//#define __SYSTEM_CLOCK_8M_HXTAL (__HXTAL)
//#define __SYSTEM_CLOCK_8M_IRC8M (__IRC8M)
//#define __SYSTEM_CLOCK_72M_PLL_HXTAL (uint32_t)(72000000)
#define __SYSTEM_CLOCK_80M_PLL_HXTAL (uint32_t)(80000000)
//#define __SYSTEM_CLOCK_72M_PLL_IRC8M_DIV2 (uint32_t)(72000000)
//#define __SYSTEM_CLOCK_72M_PLL_IRC48M_DIV2 (uint32_t)(72000000)
#endif /* GD32F310 */
在这里增加配置如下
在此函数中增加如下配置
至此我们自己的80MHz的配置可以被系统正确调用。
下面我们看一下配置的具体代码部分。
根据上文的分析,系统时钟需要为80MHz,APB2分频系数需要设置为1。
static void system_clock_80m_hxtal(void)
{
uint32_t timeout = 0U;
uint32_t stab_flag = 0U;
/* enable HXTAL */
RCU_CTL0 |= RCU_CTL0_HXTALEN;
/* wait until HXTAL is stable or the startup time is longer than HXTAL_STARTUP_TIMEOUT */
do {
timeout++;
stab_flag = (RCU_CTL0 & RCU_CTL0_HXTALSTB);
} while((0U == stab_flag) && (HXTAL_STARTUP_TIMEOUT != timeout));
/* if fail */
if(0U == (RCU_CTL0 & RCU_CTL0_HXTALSTB)) {
return;
}
/* HXTAL is stable */
/* AHB = SYSCLK */
RCU_CFG0 |= RCU_AHB_CKSYS_DIV1;
/* APB2 = AHB/1 @80M */
RCU_CFG0 |= RCU_APB2_CKAHB_DIV1;
/* APB1 = AHB/2 */
RCU_CFG0 |= RCU_APB1_CKAHB_DIV2;
/* PLL = HXTAL /2 * 20 = 80 MHz */
RCU_CFG0 &= ~(RCU_CFG0_PLLSEL | RCU_CFG0_PLLMF | RCU_CFG0_PLLMF4 | RCU_CFG0_PLLPREDV);
RCU_CFG1 &= ~(RCU_CFG1_PLLPRESEL | RCU_CFG1_PLLMF5 | RCU_CFG1_PREDV);
RCU_CFG0 |= (RCU_PLLSRC_HXTAL_IRC48M | (RCU_PLL_MUL20 & (~RCU_CFG1_PLLMF5)));
RCU_CFG1 |= (RCU_PLLPRESEL_HXTAL | RCU_PLL_PREDV2);
RCU_CFG1 |= (RCU_PLL_MUL20 & RCU_CFG1_PLLMF5);
/* enable PLL */
RCU_CTL0 |= RCU_CTL0_PLLEN;
/* wait until PLL is stable */
while(0U == (RCU_CTL0 & RCU_CTL0_PLLSTB)) {
}
/* select PLL as system clock */
RCU_CFG0 &= ~RCU_CFG0_SCS;
RCU_CFG0 |= RCU_CKSYSSRC_PLL;
/* wait until PLL is selected as system clock */
while(0U == (RCU_CFG0 & RCU_SCSS_PLL)) {
}
}
这里配置成80MHz有好几种方式,我这里沿用了F330的配置,不必非要跟我这里一致。
现在实际测试一下ADC速度。
这里顺便提一下,要用这个开发板做自己的应用之前需要注意这个开发板是默认连接了几个引脚的,PA0/PA1。特别是如果用PA1直接测ADC的值,将会得出来被平滑的结果,这是由于0.1uf的电容 C1导致的。
所以在使用PA0/PA1之前,要先将R2/C1/R1焊掉。(这里建议一下GD后面可以把这两个连接做成跳帽,直接拔下来即可断开连接会比较方便)
实际运行效果
这里使用了HDMI解码成VGA信号,通过杜邦线连接到开发板上,并通过串口输出到PC上位机进行观察显示。连线图下图所示:
- windows桌面捕捉效果
- 某壁纸引擎效果
自带马赛克效果。
分析一下当前的ADC采集速度。
通过图片的像素可知,当前每行采集的像素是40个,此时桌面的分辨率为1920*1080@60Hz。
使用逻辑分析仪观察到每一行有效像素的时间是12.92uS。
则每个像素采样时间为323ns。
则计算得到采样率为1S/323ns=3,095,975SPS即3M多一点。当前项目中设置的ADC是10Bit分辨率。
参考ADC特性表,在36MHzADC时钟下,其采样速度应为3.00MSPS。理论上在40MHz时钟下其采样率应该在3.00*(40/36)≈3.33MSPS。
由于当前图像并没有完全采集到最右侧 因此此处的差是可以解释的。至少ADC必定是运行超过了在36MHz的性能。