GD32_ADC采样+DMA多通道扫描传输
文章目录
- GD32_ADC采样+DMA多通道扫描传输
- 前言
- 一、资源介绍
- 二、原理
- 1.ADC连续扫描模式
- 2.DMA传输
- 3.ADC内部通道
- 三、配置
- 1.ADC配置
- 2.DMA配置
- 3.注意事项
- 四、计算
- 1.分压转换
- 2.数据转换
前言
<1>、硬件平台:可运行软件程序的GD32单片机(本项目使用GD32F103CBT6硬件平台)
<2>、软件平台:基于使用标准库GD32F10x_Firmware_Library_V2.2.4固件库编写
一、资源介绍
所使用的MCU 片上集成了 12 位逐次逼近式模数转换器模块(ADC),可以采样来自于 16 个外部通道和 2 个内部通道上的模拟信号。这 18 个 ADC 采样通道都支持多种运行模式,采样转换后,转换结果可以按照最低有效位对齐或最高有效位对齐的方式保存在相应的数据寄存器中。我们主要介绍其多通道扫描转换和DMA传输功能,在多个通道轮询采集ad数据,并使用DMA分别保存各个数据值,其能大大提高ADC的工作效率。以下为ADC 模块框图:
二、原理
1.ADC连续扫描模式
在配置多个通道采集时,如图:CH2、CH1、CH5、CH7、CH11,在扫描模式下,会对各个通道一次进行数据采样,再使用连续模式时,会对循环对上述通道进行连续数据采样,配置原理如下:
扫描运行模式可以通过将 ADC_CTL0 寄存器的 SM 位置 1 来使能。在此模式下, ADC 扫描转换所有被 ADC_RSQ0~ADC_RSQ2 寄存器选中的所有通道。一旦 ADCON 位被置 1,当相应软件触发或者外部触发产生, ADC 就会一个接一个的采样和转换常规序列通道。转换数据存储在 ADC_RDATA 寄存器中。常规序列转换结束后, EOC 位将被置 1。如果 EOCIE 位被置1,将产生中断。当常规序列工作在扫描模式下时, ADC_CTL1 寄存器的 DMA 位必须设置为1。如果 ADC_CTL1 寄存器的 CTN 位也被置 1,则在常规序列转换完之后,这个转换自动重新开始。
常规序列扫描运行模式的软件流程:
- 设置 ADC_CTL0 寄存器的 SM 位和 ADC_CTL1 寄存器的 DMA 位为 1;
- 配置 ADC_RSQx 和 ADC_SAMPTx 寄存器;
- 如果有需要,配置 ADC_CTL1 寄存器中的 ETERC 和 ETSRC 位;
- 准备 DMA 模块,用于传输来自 ADC_RDATA 的数据;
- 设置 SWRCST 位,或者给常规序列产生一个外部触发;
- 等待 EOC 标志位置 1;
- 写 0 清除 EOC 标志位。
2.DMA传输
当ADC使用连续扫描时,采样数据非常快且数据都暂存在ADC_RDATA寄存器中,我们可使用DMA自动将采样数据依次保存到内存中备用,各个通道采样数据经DMA传输到内存后的映射关系如图:
如图:一共五个通道需要连续扫描,我们定义一个二维数组Value[3][5]储存扫描值,3表示每个通道保存最近3次扫描的数据,5表示五个通道,该二维数组的内存分布如图所示,每个数据为两个字节大小。扫描时会依次将各通道数据保存到ADC_data寄存器,DMA会将寄存器数据依次搬迁到Value[][]数组中备用,Value空间30个字节,为当第四次扫描时会覆盖第一次扫描的数据,依次覆盖更新。
3.ADC内部通道
将 ADC_CTL1 寄存器的 TSVREN 位置 1 可以使能温度传感器通道(ADC0_IN16)和 VREFINT 通道(ADC0_IN17)。温度传感器可以用来测量器件周围的温度。传感器输出电压能被 ADC 转换成数字量。建议温度传感器的采样时间至少设置为 ts_temp µs(具体数值请参考datasheet 文档)。温度传感器不用时,复位 TSVREN 位可以将其置于掉电模式。温度传感器的输出电压随温度线性变化,由于生产过程的多样化,温度变化曲线的偏移在不同的芯片上会有不同(最多相差 45°C)。内部温度传感器更适合于检测温度的变化,而不是测量绝对温度。如果需要测量精确的温度,应该使用一个外置的温度传感器来校准这个偏移错误。内部电压参考(VREFINT)提供了一个稳定的(带隙基准)电压输出给 ADC 和比较器。 VREFINT 内部连接到 ADC0_IN17 输入通道
使用温度传感器:
- 配置温度传感器通道(ADC0_IN16)的转换序列和采样时间为 ts_temp µs
- 置位 ADC_CTL1 寄存器中的 TSVREN 位,使能温度传感器
- 置位 ADC_CTL1 寄存器的 ADCON 位,或者由外部触发启动 ADC 转换
- 读取内部温度传感器输出电压 Vtemperature, 并由下面公式计算出实际温度:
温度 ( ° C ) = ( V 25 – V t e m p e r a t u r e ) / A v g S l o p e + 25 温度 (°C) = {(V_{25} – V_{temperature}) / A_{vg_Slope}} + 25 温度(°C)=(V25–Vtemperature)/AvgSlope+25
V25: 内部温度传感器在 25°C 下的电压,典型值请参考相关型号 datasheet。
Avg_Slope:温度与内部温度传感器输出电压曲线的均值斜率,典型值请参考相关型号datasheet。
三、配置
我们使用通道ADC_CHANNEL_8和通道ADC_CHANNEL_1分别采样3.3V和12V电压,再使用通道ADC_CHANNEL_16和通道ADC_CHANNEL_17分别采样内部温度传感器和内部参考电压,一共四个通道,每个通道采集五个数据,计算时候再取平均值,因此定义一个二维数组ADC0_Vallue保存数据。
1.ADC配置
代码如下(示例):
static void ADC_Init(void)
{
uint16_t i = 0;
/* enable ADC0 clock */
rcu_periph_clock_enable(RCU_ADC0);
/* config ADC clock */
rcu_adc_clock_config(RCU_CKADC_CKAPB2_DIV8);
/* ADC mode config */
adc_mode_config(ADC_MODE_FREE);
/* ADC scan function enable */
adc_special_function_config(ADC0, ADC_CONTINUOUS_MODE, ENABLE);
/* ADC continous function enable */
adc_special_function_config(ADC0, ADC_SCAN_MODE, ENABLE);
/*ADC data alignment config */
adc_data_alignment_config(ADC0,ADC_DATAALIGN_RIGHT);
/* ADC channel length config */
adc_channel_length_config(ADC0,ADC_REGULAR_CHANNEL,ADC_CHANNEL_NUM);
/* enable the temperature sensor and Vrefint channel */
adc_tempsensor_vrefint_enable();
/* ADC regular channel config,一个通道转换时长是2.06us */
adc_regular_channel_config(ADC0, 0, ADC_CHANNEL_8, ADC_SAMPLETIME_239POINT5); //12V
adc_regular_channel_config(ADC0, 1, ADC_CHANNEL_1, ADC_SAMPLETIME_239POINT5); //3V3
adc_regular_channel_config(ADC0, 2, ADC_CHANNEL_16,ADC_SAMPLETIME_239POINT5); //内部温度
adc_regular_channel_config(ADC0, 3, ADC_CHANNEL_17,ADC_SAMPLETIME_239POINT5); //内部参考电压
/* ADC trigger config */
adc_external_trigger_source_config(ADC0, ADC_REGULAR_CHANNEL, ADC0_1_2_EXTTRIG_REGULAR_NONE);
adc_external_trigger_config(ADC0, ADC_REGULAR_CHANNEL, ENABLE);
/* enable ADC0 interface */
adc_enable(ADC0);
/*延迟14个ADCCLK以等待ADC稳定*/
for(i = 1000u; i > 0; i--)
{}
/* ADC calibration and reset calibration */
adc_calibration_enable(ADC0);
/* ADC DMA function enable */
adc_dma_mode_enable(ADC0);
/* ADC software trigger enable */
adc_software_trigger_enable(ADC0, ADC_REGULAR_CHANNEL);
}
2.DMA配置
代码如下(示例):
#define CHANNEL_NUM (4u)
#define FILTER_NUM (5U)
uint16_t ADC0_Vallue[FILTER_NUM][CHANNEL_NUM];
static void ADC_DMA_Init(void)
{
/* ADC_DMA_channel configuration */
dma_parameter_struct dma_data_parameter;
rcu_periph_clock_enable(RCU_DMA0);
dma_deinit(DMA0, DMA_CH0);
/* initialize DMA single data mode */
dma_data_parameter.periph_addr = (uint32_t)(&ADC_RDATA(ADC0));
dma_data_parameter.periph_inc = DMA_PERIPH_INCREASE_DISABLE;
dma_data_parameter.memory_addr = (uint32_t)(&ADC0_Vallue);
dma_data_parameter.memory_inc = DMA_MEMORY_INCREASE_ENABLE;
dma_data_parameter.periph_width = DMA_PERIPHERAL_WIDTH_16BIT;
dma_data_parameter.memory_width = DMA_MEMORY_WIDTH_16BIT;
dma_data_parameter.direction = DMA_PERIPHERAL_TO_MEMORY;
dma_data_parameter.number = ADC_FILTER_NUM * ADC_CHANNEL_NUM;
dma_data_parameter.priority = DMA_PRIORITY_HIGH;
dma_init(DMA0, DMA_CH0, &dma_data_parameter);
dma_circulation_enable(DMA0,DMA_CH0);
/* enable DMA channel */
dma_channel_enable(DMA0, DMA_CH0);
}
3.注意事项
- ADC_CTL1 寄存器中的 ADCON 位是 ADC 模块的使能开关。如果该位为 0,则 ADC 模块保持复位状态。为了省电,当 ADCON 位为 0 时, ADC 模拟子模块将会进入掉电模式。ADC 使能后需要等待 tsu时间后才能采样 , tsu 数值详见芯片数据手册;
- 实际电压值最好先拿万用表测一下,避免踩了硬件的坑;
四、计算
1.分压转换
因GPIO口无法直接连接3.3v和12v,所以需要使用分压电阻使得接入到adc通的电压小于3.3v,ADC 电阻值应该将为千欧级的电阻,分压原理图如图所示:
这里以为12v为例,根据初中物理电阻串联分压知识,计算12V实际值:
V i n = V o u t ∗ ( ( R 2 + R 1 ) / R 1 ) V_{in} = V_{out} *((R2+R1)/R1) Vin=Vout∗((R2+R1)/R1)
2.数据转换
以为12bit分辨率为例,读取到的模拟电压转换后是一个12位的数字值,但这个值对于使用者没什么概念,所以需要再次转换成可读性较好的电压值,也就是用万用表量到的电压值。这里主要用到一个比例概念:一般情况下,ADC的输入电压范围在0~3.3v,所以12位满量程对应的电压值为3.3v,数字值为2^12。我们假设ADC转换后的12位的值为x,其对应的电压值为y,那么:
y x = 3.3 2 12 − 1 (1) \frac{y}{x}=\frac{3.3}{2^{12}-1} \tag{1} xy=212−13.3(1)
得
y = x ∗ ( 3.3 / 2 12 − 1 ) y = x * (3.3 / 2^{12}-1) y=x∗(3.3/212−1)
总结公式为:
V i n = A D V v a l ∗ ( V r e f / 2 N − 1 ) (2) V_{in} = ADV_{val} * (V_{ref}/ 2^N-1)\tag{2} Vin=ADVval∗(Vref/2N−1)(2)
Vin 为实际电压值,ADCval 为ADC采集得到的数字信号值,Vref 为参考电压,N分辨率,
顺便一提:同样的原理通过电流转化公式和电流芯片,也可以采集电流信号
我们以1.2v作为参考电压标准值,17通道数据作为参数电压值,根据上述公式和数据参数,温度转换函数如下:
static uint8_t ADC_GetTemp(float* Temp)
{
ReturnType_u8 ret = 0;
uint32_t FilterVltg_Temp;
uint32_t SampleVltg_1V2;
SampleVltg_1V2 = (uint32_t)(ADC0_Vallue[0][3] + ADC0_Vallue[1][3] + ADC0_Vallue[2][3] + ADC0_Vallue[3][3] + ADC0_Vallue[4][3]) / 5u;
FilterVltg_Temp = (uint32_t)(ADC0_Vallue[0][2] + ADC0_Vallue[1][2] + ADC0_Vallue[2][2] + ADC0_Vallue[3][2] + ADC0_Vallue[4][2]) / 5u;
if(Temp != NULL_PTR)
{
*Temp = ((float)((1.45 - (FilterVltg_Temp * 1.2 / SampleVltg_1V2)) / 0.0041) + 25);
ret = 1;
}
else
{
ret = 0;
}
return ret;
}
对于12V电压的计算原理也一样。
static uint8_t ADC_Get12VVltg(float* Vltg)
{
uint8_t ret = 0;
uint32_t FilterVltg_Temp;
uint32_t SampleVltg_1V2;
SampleVltg_1V2 = (uint32_t)(ADC0_Vallue[0][3] + ADC0_Vallue[1][3] + ADC0_Vallue[2][3] + ADC0_Vallue[3][3] + ADC0_Vallue[4][3]) / 5u;
FilterVltg_Temp = (uint32_t)(ADC0_Vallue[0][0] + ADC0_Vallue[1][0] + ADC0_Vallue[2][0] + ADC0_Vallue[3][0] + ADC0_Vallue[4][0]) / 5u;
if(Vltg != NULL_PTR)
{
*Vltg = (float)((uint64_t)FilterVltg_Temp * 1.2u * (1u + 11u) / SampleVltg_1V2 / 1u);
ret = 1;
}
else
{
ret = 0;
}
return ret;
}