<1>、硬件平台:可运行软件程序的GD32单片机(本项目使用GD32F103CBT6硬件平台)
<2>、软件平台:基于使用标准库GD32F10x_Firmware_Library_V2.2.4固件库编写
所使用的MCU 片上集成了 12 位逐次逼近式模数转换器模块(ADC),可以采样来自于 16 个外部通道和 2 个内部通道上的模拟信号。这 18 个 ADC 采样通道都支持多种运行模式,采样转换后,转换结果可以按照最低有效位对齐或最高有效位对齐的方式保存在相应的数据寄存器中。我们主要介绍其多通道扫描转换和DMA传输功能,在多个通道轮询采集ad数据,并使用DMA分别保存各个数据值,其能大大提高ADC的工作效率。以下为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使用连续扫描时,采样数据非常快且数据都暂存在ADC_RDATA寄存器中,我们可使用DMA自动将采样数据依次保存到内存中备用,各个通道采样数据经DMA传输到内存后的映射关系如图:
如图:一共五个通道需要连续扫描,我们定义一个二维数组Value[3][5]储存扫描值,3表示每个通道保存最近3次扫描的数据,5表示五个通道,该二维数组的内存分布如图所示,每个数据为两个字节大小。扫描时会依次将各通道数据保存到ADC_data寄存器,DMA会将寄存器数据依次搬迁到Value[][]数组中备用,Value空间30个字节,为当第四次扫描时会覆盖第一次扫描的数据,依次覆盖更新。
将 ADC_CTL1 寄存器的 TSVREN 位置 1 可以使能温度传感器通道(ADC0_IN16)和 VREFINT 通道(ADC0_IN17)。温度传感器可以用来测量器件周围的温度。传感器输出电压能被 ADC 转换成数字量。建议温度传感器的采样时间至少设置为 ts_temp µs(具体数值请参考datasheet 文档)。温度传感器不用时,复位 TSVREN 位可以将其置于掉电模式。温度传感器的输出电压随温度线性变化,由于生产过程的多样化,温度变化曲线的偏移在不同的芯片上会有不同(最多相差 45°C)。内部温度传感器更适合于检测温度的变化,而不是测量绝对温度。如果需要测量精确的温度,应该使用一个外置的温度传感器来校准这个偏移错误。内部电压参考(VREFINT)提供了一个稳定的(带隙基准)电压输出给 ADC 和比较器。 VREFINT 内部连接到 ADC0_IN17 输入通道
使用温度传感器:
我们使用通道ADC_CHANNEL_8和通道ADC_CHANNEL_1分别采样3.3V和12V电压,再使用通道ADC_CHANNEL_16和通道ADC_CHANNEL_17分别采样内部温度传感器和内部参考电压,一共四个通道,每个通道采集五个数据,计算时候再取平均值,因此定义一个二维数组ADC0_Vallue保存数据。
代码如下(示例):
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);
}
代码如下(示例):
#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);
}
因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)
以为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;
}