日期作者版本说明
2020.10.22TaoV0.0完成了基于STM32F103与F407的片上ADC扩展函数库源代码的撰写;2020.10.23TaoV0.1完成了基于STM32F103与F407的片上ADC扩展函数库主体内容的撰写,并修复了源代码中的一处bug:在获取ADC采样数据平均值的函数中,for循环的计数控制变量应为uint16_t类型;2020.11.06TaoV0.21.增加了GPIO端口配置函数;2. 部分细节的优化调整;2020.11.21TaoV0.31.使用了GPIO扩展库实现ADC端口的配置;2.增加了在FreeRTOS中使用ADC采集数据的操作说明;
目录
片上ADC介绍扩展库特性扩展库源码基于STM32F103头文件源文件
基于STM32F407头文件源文件
使用指南单次单通道采样(不使用DMA)连续多通道采样(使用DMA)在FreeRTOS任务中
片上ADC介绍
STM32F103系列与STM32F407系列ADC的功能类似。它们总共有 3 个 ADC,精度为 12 位,每个ADC 有 16 个外部通道。另外还有两个内部 ADC 源和 VBAT通道挂在 ADC1 上。ADC 具有独立模式、双重模式和三重模式,对于不同 AD 转换要求几乎都有合适的模式可选。
电压输入范围 ADC 输入范围为:VREF- ≤ VIN ≤ VREF+。由 VREF-、VREF+ 、VDDA 、VSSA、这四个外部引脚决定。在设计原理图的时候一般把 VSSA和 VREF-接地,把 VREF+和 VDDA 接 3V3,得到ADC 的输入电压范围为:0~3.3V。输入通道 STM32 的 ADC 多达 19 个通道,其中外部的 16 个通道就是框图中的 ADCx_IN0、ADCx_IN1…ADCx_IN5。这 16 个通道对应着不同的 IO 口。
对STM32F103系列单片机,ADC1的通道16连接到芯片内部的温度传感器,通道17连接到了内部参考电压 Vrefint。ADC2 的模拟通道 16 和 17 连接到了内部的 VSS。ADC3 的模拟通道 9、14、15、16 和 17 连接到了内部的 VSS。
对STM32F407系列单片机,ADC1的通道16连接到芯片内部的温度传感器,通道17连接到了内部参考电压 Vrefint,通道18连接到了备用电源 Vbat。ADC2 和 ADC3 的通道 16、17、18 全部连接到了内部的 VSS。
扩展库特性
本扩展库针对ADC最常见的操作方式,编写了两种ADC配置模板,其一是不使用DMA传输的单次单通道采样(void ADC_UserConfig1()),其二是使用DMA传输的连续多通道采样(void ADC_UserConfig2())。可根据需要在程序初始化的时候调用对应的配置函数实现ADC的快速配置。同时提供了在两种配置模式下的获取ADC数据与平均值数据的函数,也可在适合的位置直接调用。
使用了本扩展库,ADC的配置与读取数据将会变得十分简洁高效。
扩展库源码
STM32F103与STM32F407的ADC在使用的时候还是有一些区别的:
STM32F103的ADC需要校准,而STM32F407的ADC无需校准。STM32F103与F407的ADC时钟配置、外设配置、DMA配置等不同。
基于STM32F103
头文件
#ifndef __ADC_EXT_H__
#define __ADC_EXT_H__
#include "stm32f10x_conf.h"
#include "stm32f10x.h"
#define ADC_CHANNEL_NUM 4
#define ADC_DMA_CHANNEL_DEEPTH 10
#define ADC_DMA_CHANNEL_NUM ADC_CHANNEL_NUM
#define ADC_DMA_BUFFER_SIZE (ADC_DMA_CHANNEL_NUM * ADC_DMA_CHANNEL_DEEPTH)
extern volatile uint16_t ADC_Data
[ADC_CHANNEL_NUM
];
extern volatile uint16_t ADC_DMA_Value
[ADC_DMA_CHANNEL_DEEPTH
][ADC_DMA_CHANNEL_NUM
];
void ADC_UserConfig1();
void ADC_UserConfig2();
void ADC_Config_NotUseDMA();
void ADC_Config_UseDMA();
void ADC_Config_SetDMA();
uint16_t
ADC_GetData_NotUseDMA(uint8_t channel
);
uint16_t
ADC_GetAverageData_NotUseDMA(uint8_t channel
, uint16_t count
);
uint16_t
ADC_GetData_UseDMA(uint8_t channel
);
uint16_t
ADC_GetAverageData_UseDMA(uint8_t channel
);
#endif
源文件
#include "adc_ext.h"
volatile uint16_t ADC_Data
[ADC_CHANNEL_NUM
];
volatile uint16_t ADC_DMA_Value
[ADC_DMA_CHANNEL_DEEPTH
][ADC_DMA_CHANNEL_NUM
];
void ADC_UserConfig1()
{
GPIO_ConfigPort('A', 0, GPIO_Mode_AIN
, 0);
ADC_Config_NotUseDMA();
}
void ADC_UserConfig2()
{
GPIO_ConfigPort('A', 0, GPIO_Mode_AIN
, 0);
GPIO_ConfigPort('A', 1, GPIO_Mode_AIN
, 0);
GPIO_ConfigPort('A', 2, GPIO_Mode_AIN
, 0);
GPIO_ConfigPort('A', 3, GPIO_Mode_AIN
, 0);
ADC_Config_SetDMA();
ADC_Config_UseDMA();
}
void ADC_Config_NotUseDMA()
{
ADC_InitTypeDef ADC_InitStructure
;
RCC_ADCCLKConfig(RCC_PCLK2_Div6
);
ADC_DeInit(ADC1
);
ADC_InitStructure
.ADC_Mode
= ADC_Mode_Independent
;
ADC_InitStructure
.ADC_ScanConvMode
= DISABLE
;
ADC_InitStructure
.ADC_ContinuousConvMode
= DISABLE
;
ADC_InitStructure
.ADC_ExternalTrigConv
= ADC_ExternalTrigConv_None
;
ADC_InitStructure
.ADC_DataAlign
= ADC_DataAlign_Right
;
ADC_InitStructure
.ADC_NbrOfChannel
= ADC_CHANNEL_NUM
;
ADC_Init(ADC1
, &ADC_InitStructure
);
ADC_Cmd(ADC1
, ENABLE
);
ADC_ResetCalibration(ADC1
);
while (ADC_GetResetCalibrationStatus(ADC1
))
;
ADC_StartCalibration(ADC1
);
while (ADC_GetCalibrationStatus(ADC1
))
;
}
void ADC_Config_UseDMA()
{
ADC_InitTypeDef ADC_InitStructure
;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1
, ENABLE
);
RCC_ADCCLKConfig(RCC_PCLK2_Div6
);
ADC_DeInit(ADC1
);
ADC_InitStructure
.ADC_Mode
= ADC_Mode_Independent
;
ADC_InitStructure
.ADC_ScanConvMode
= ENABLE
;
ADC_InitStructure
.ADC_ContinuousConvMode
= ENABLE
;
ADC_InitStructure
.ADC_ExternalTrigConv
= ADC_ExternalTrigConv_None
;
ADC_InitStructure
.ADC_DataAlign
= ADC_DataAlign_Right
;
ADC_InitStructure
.ADC_NbrOfChannel
= ADC_CHANNEL_NUM
;
ADC_Init(ADC1
, &ADC_InitStructure
);
ADC_RegularChannelConfig(ADC1
, ADC_Channel_0
, 1, ADC_SampleTime_239Cycles5
);
ADC_RegularChannelConfig(ADC1
, ADC_Channel_1
, 2, ADC_SampleTime_239Cycles5
);
ADC_RegularChannelConfig(ADC1
, ADC_Channel_2
, 3, ADC_SampleTime_239Cycles5
);
ADC_RegularChannelConfig(ADC1
, ADC_Channel_3
, 4, ADC_SampleTime_239Cycles5
);
ADC_DMACmd(ADC1
, ENABLE
);
ADC_Cmd(ADC1
, ENABLE
);
ADC_ResetCalibration(ADC1
);
while (ADC_GetResetCalibrationStatus(ADC1
))
;
ADC_StartCalibration(ADC1
);
while (ADC_GetCalibrationStatus(ADC1
))
;
ADC_SoftwareStartConvCmd(ADC1
, ENABLE
);
}
void ADC_Config_SetDMA()
{
DMA_InitTypeDef DMA_InitStructure
;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1
, ENABLE
);
DMA_DeInit(DMA1_Channel1
);
DMA_InitStructure
.DMA_PeripheralBaseAddr
= (uint32_t
) &(ADC1
->DR
);
DMA_InitStructure
.DMA_MemoryBaseAddr
= (uint32_t
) &ADC_DMA_Value
;
DMA_InitStructure
.DMA_DIR
= DMA_DIR_PeripheralSRC
;
DMA_InitStructure
.DMA_BufferSize
= ADC_DMA_BUFFER_SIZE
;
DMA_InitStructure
.DMA_PeripheralInc
= DMA_PeripheralInc_Disable
;
DMA_InitStructure
.DMA_MemoryInc
= DMA_MemoryInc_Enable
;
DMA_InitStructure
.DMA_PeripheralDataSize
= DMA_PeripheralDataSize_HalfWord
;
DMA_InitStructure
.DMA_MemoryDataSize
= DMA_MemoryDataSize_HalfWord
;
DMA_InitStructure
.DMA_Mode
= DMA_Mode_Circular
;
DMA_InitStructure
.DMA_Priority
= DMA_Priority_High
;
DMA_InitStructure
.DMA_M2M
= DMA_M2M_Disable
;
DMA_Init(DMA1_Channel1
, &DMA_InitStructure
);
DMA_Cmd(DMA1_Channel1
, ENABLE
);
}
uint16_t
ADC_GetData_NotUseDMA(uint8_t channel
)
{
ADC_RegularChannelConfig(ADC1
, channel
, 1, ADC_SampleTime_71Cycles5
);
ADC_SoftwareStartConvCmd(ADC1
, ENABLE
);
while (!ADC_GetFlagStatus(ADC1
, ADC_FLAG_EOC
))
;
return ADC_GetConversionValue(ADC1
);
}
uint16_t
ADC_GetAverageData_NotUseDMA(uint8_t channel
, uint16_t count
)
{
uint32_t temp_val
= 0;
for (uint8_t t
= 0; t
< count
; t
++)
{
temp_val
+= ADC_GetData_NotUseDMA(channel
);
}
ADC_Data
[channel
] = temp_val
/ count
;
return ADC_Data
[channel
];
}
uint16_t
ADC_GetData_UseDMA(uint8_t channel
)
{
return ADC_DMA_Value
[0][channel
];
}
uint16_t
ADC_GetAverageData_UseDMA(uint8_t channel
)
{
uint32_t sum
= 0;
for (uint8_t j
= 0; j
< ADC_DMA_CHANNEL_DEEPTH
; j
++)
{
sum
+= ADC_DMA_Value
[j
][channel
];
}
return sum
/ ADC_DMA_CHANNEL_DEEPTH
;
}
基于STM32F407
头文件
#ifndef __ADC_EXT_H__
#define __ADC_EXT_H__
#include "stm32f4xx_conf.h"
#include "stm32f4xx.h"
#define ADC_CHANNEL_NUM 4
#define ADC_DMA_CHANNEL_DEEPTH 10
#define ADC_DMA_CHANNEL_NUM ADC_CHANNEL_NUM
#define ADC_DMA_BUFFER_SIZE (ADC_DMA_CHANNEL_NUM * ADC_DMA_CHANNEL_DEEPTH)
extern volatile uint16_t ADC_Data
[ADC_CHANNEL_NUM
];
extern volatile uint16_t ADC_DMA_Value
[ADC_DMA_CHANNEL_DEEPTH
][ADC_DMA_CHANNEL_NUM
];
void ADC_UserConfig1();
void ADC_UserConfig2();
void ADC_Config_NotUseDMA();
void ADC_Config_UseDMA();
void ADC_Config_SetDMA();
uint16_t
ADC_GetData_NotUseDMA(uint8_t channel
);
uint16_t
ADC_GetAverageData_NotUseDMA(uint8_t channel
, uint16_t count
);
uint16_t
ADC_GetData_UseDMA(uint8_t channel
);
uint16_t
ADC_GetAverageData_UseDMA(uint8_t channel
);
#endif
源文件
#include "adc_ext.h"
volatile uint16_t ADC_Data
[ADC_CHANNEL_NUM
];
volatile uint16_t ADC_DMA_Value
[ADC_DMA_CHANNEL_DEEPTH
][ADC_DMA_CHANNEL_NUM
];
void ADC_UserConfig1()
{
GPIO_ConfigPort('A', 0, GPIO_Mode_AN
, GPIO_OType_OD
, GPIO_PuPd_NOPULL
, 0);
ADC_Config_NotUseDMA();
}
void ADC_UserConfig2()
{
GPIO_ConfigPort('A', 0, GPIO_Mode_AN
, GPIO_OType_OD
, GPIO_PuPd_NOPULL
, 0);
GPIO_ConfigPort('A', 1, GPIO_Mode_AN
, GPIO_OType_OD
, GPIO_PuPd_NOPULL
, 0);
GPIO_ConfigPort('A', 2, GPIO_Mode_AN
, GPIO_OType_OD
, GPIO_PuPd_NOPULL
, 0);
GPIO_ConfigPort('A', 3, GPIO_Mode_AN
, GPIO_OType_OD
, GPIO_PuPd_NOPULL
, 0);
ADC_Config_SetDMA();
ADC_Config_UseDMA();
}
void ADC_Config_NotUseDMA()
{
ADC_CommonInitTypeDef ADC_CommonInitStructure
;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1
, ENABLE
);
ADC_CommonInitStructure
.ADC_Mode
= ADC_Mode_Independent
;
ADC_CommonInitStructure
.ADC_Prescaler
= ADC_Prescaler_Div4
;
ADC_CommonInitStructure
.ADC_DMAAccessMode
= ADC_DMAAccessMode_Disabled
;
ADC_CommonInitStructure
.ADC_TwoSamplingDelay
= ADC_TwoSamplingDelay_20Cycles
;
ADC_CommonInit(&ADC_CommonInitStructure
);
ADC_InitTypeDef ADC_InitStructure
;
ADC_StructInit(&ADC_InitStructure
);
ADC_InitStructure
.ADC_Resolution
= ADC_Resolution_12b
;
ADC_InitStructure
.ADC_ScanConvMode
= ENABLE
;
ADC_InitStructure
.ADC_ContinuousConvMode
= ENABLE
;
ADC_InitStructure
.ADC_ExternalTrigConv
= ADC_ExternalTrigConvEdge_None
;
ADC_InitStructure
.ADC_ExternalTrigConv
= ADC_ExternalTrigConv_T1_CC1
;
ADC_InitStructure
.ADC_DataAlign
= ADC_DataAlign_Right
;
ADC_InitStructure
.ADC_NbrOfConversion
= 1;
ADC_Init(ADC1
, &ADC_InitStructure
);
ADC_RegularChannelConfig(ADC1
, ADC_Channel_0
, 1, ADC_SampleTime_56Cycles
);
ADC_Cmd(ADC1
, ENABLE
);
}
void ADC_Config_UseDMA()
{
ADC_CommonInitTypeDef ADC_CommonInitStructure
;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1
, ENABLE
);
ADC_CommonInitStructure
.ADC_Mode
= ADC_Mode_Independent
;
ADC_CommonInitStructure
.ADC_Prescaler
= ADC_Prescaler_Div4
;
ADC_CommonInitStructure
.ADC_DMAAccessMode
= ADC_DMAAccessMode_Disabled
;
ADC_CommonInitStructure
.ADC_TwoSamplingDelay
= ADC_TwoSamplingDelay_20Cycles
;
ADC_CommonInit(&ADC_CommonInitStructure
);
ADC_InitTypeDef ADC_InitStructure
;
ADC_StructInit(&ADC_InitStructure
);
ADC_InitStructure
.ADC_Resolution
= ADC_Resolution_12b
;
ADC_InitStructure
.ADC_ScanConvMode
= ENABLE
;
ADC_InitStructure
.ADC_ContinuousConvMode
= ENABLE
;
ADC_InitStructure
.ADC_ExternalTrigConv
= ADC_ExternalTrigConvEdge_None
;
ADC_InitStructure
.ADC_ExternalTrigConv
= ADC_ExternalTrigConv_T1_CC1
;
ADC_InitStructure
.ADC_DataAlign
= ADC_DataAlign_Right
;
ADC_InitStructure
.ADC_NbrOfConversion
= ADC_CHANNEL_NUM
;
ADC_Init(ADC1
, &ADC_InitStructure
);
ADC_RegularChannelConfig(ADC1
, ADC_Channel_0
, 1, ADC_SampleTime_3Cycles
);
ADC_RegularChannelConfig(ADC1
, ADC_Channel_1
, 2, ADC_SampleTime_3Cycles
);
ADC_RegularChannelConfig(ADC1
, ADC_Channel_2
, 3, ADC_SampleTime_3Cycles
);
ADC_RegularChannelConfig(ADC1
, ADC_Channel_3
, 4, ADC_SampleTime_3Cycles
);
ADC_DMARequestAfterLastTransferCmd(ADC1
, ENABLE
);
ADC_DMACmd(ADC1
, ENABLE
);
ADC_Cmd(ADC1
, ENABLE
);
ADC_SoftwareStartConv(ADC1
);
}
void ADC_Config_SetDMA()
{
DMA_InitTypeDef DMA_InitStructure
;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2
, ENABLE
);
DMA_DeInit(DMA2_Stream0
);
DMA_InitStructure
.DMA_PeripheralBaseAddr
= (uint32_t
) &(ADC1
->DR
);
DMA_InitStructure
.DMA_Memory0BaseAddr
= (uint32_t
) &ADC_DMA_Value
;
DMA_InitStructure
.DMA_DIR
= DMA_DIR_PeripheralToMemory
;
DMA_InitStructure
.DMA_BufferSize
= ADC_DMA_BUFFER_SIZE
;
DMA_InitStructure
.DMA_PeripheralInc
= DMA_PeripheralInc_Disable
;
DMA_InitStructure
.DMA_MemoryInc
= DMA_MemoryInc_Enable
;
DMA_InitStructure
.DMA_PeripheralDataSize
= DMA_PeripheralDataSize_HalfWord
;
DMA_InitStructure
.DMA_MemoryDataSize
= DMA_MemoryDataSize_HalfWord
;
DMA_InitStructure
.DMA_Mode
= DMA_Mode_Circular
;
DMA_InitStructure
.DMA_Priority
= DMA_Priority_High
;
DMA_InitStructure
.DMA_FIFOMode
= DMA_FIFOMode_Disable
;
DMA_InitStructure
.DMA_FIFOThreshold
= DMA_FIFOThreshold_HalfFull
;
DMA_InitStructure
.DMA_MemoryBurst
= DMA_MemoryBurst_Single
;
DMA_InitStructure
.DMA_PeripheralBurst
= DMA_PeripheralBurst_Single
;
DMA_InitStructure
.DMA_Channel
= DMA_Channel_0
;
DMA_Init(DMA2_Stream0
, &DMA_InitStructure
);
DMA_Cmd(DMA2_Stream0
, ENABLE
);
}
uint16_t
ADC_GetData_NotUseDMA(uint8_t channel
)
{
ADC_RegularChannelConfig(ADC1
, channel
, 1, ADC_SampleTime_56Cycles
);
ADC_SoftwareStartConv(ADC1
);
while (!ADC_GetFlagStatus(ADC1
, ADC_FLAG_EOC
));
return ADC_GetConversionValue(ADC1
);
}
uint16_t
ADC_GetAverageData_NotUseDMA(uint8_t channel
, uint16_t count
)
{
uint32_t temp_val
= 0;
for (uint16_t t
= 0; t
< count
; t
++)
{
temp_val
+= ADC_GetData_NotUseDMA(channel
);
}
ADC_Data
[channel
] = temp_val
/ count
;
return ADC_Data
[channel
];
}
uint16_t
ADC_GetData_UseDMA(uint8_t channel
)
{
return ADC_DMA_Value
[0][channel
];
}
uint16_t
ADC_GetAverageData_UseDMA(uint8_t channel
)
{
uint32_t sum
= 0;
for (uint16_t j
= 0; j
< ADC_DMA_CHANNEL_DEEPTH
; j
++)
{
sum
+= ADC_DMA_Value
[j
][channel
];
}
return sum
/ ADC_DMA_CHANNEL_DEEPTH
;
}
使用指南
由于使用了相同的功能逻辑与函数名(即头文件相同),STM32F103与STM32F407的ADC扩展函数库在使用方法上是相同的。由于在ADC扩展库中提供的模板配置函数中已经完成了GPIO外设与ADC外设的配置,因此可以直接调用一步完成ADC使用前的初始化工作。
单次单通道采样(不使用DMA)
配置ADC
void Periphl_ConfigAll()
{
ADC_UserConfig1();
}
启动采样并获取数据 采集的数据保存在全局数组ADC_Data[ADC_CHANNEL_NUM]中,同时也可以调用相应的函数返回对应的结果。
调用uint16_t ADC_GetData_NotUseDMA(uint8_t channel)完成一次数据采集。调用uint16_t ADC_GetAverageData_NotUseDMA(uint8_t channel, uint16_t count)连续采集count个数据后,返回数据采集的平均值。
连续多通道采样(使用DMA)
配置ADC
void Periphl_ConfigAll()
{
ADC_UserConfig2();
}
获取数据 在配置完ADC的DMA传输后就已经自动打开了ADC采样,采样的数据保存在全局二维数组ADC_DMA_Value[ADC_DMA_CHANNEL_DEEPTH][ADC_DMA_CHANNEL_NUM]中,同时也可以调用相应的函数返回对应的结果。
调用uint16_t ADC_GetData_UseDMA(uint8_t channel)返回一次数据采集结果。调用uint16_t ADC_GetAverageData_UseDMA(uint8_t channel)返回数组中保存的最近的ADC_DMA_CHANNEL_DEEPTH个数据的平均值。
一般情况下,由于DMA的采样速度很快,调用uint16_t ADC_GetAverageData_UseDMA(uint8_t channel)返回一个平均值作为采样的结果是比较推荐的。这也是利用了过采样技术提高了数据的稳定性与分辨率。
在FreeRTOS任务中
可以创建一个任务完成ADC初始化与数据轮询的工作,但需要注意的是片上ADC的快速配置函数(ADC_UserConfig2())只能调用一次,否则可能会出错。也就是说要么在程序启动的地方配置ADC,要么在任务开始的地方配置ADC,但不能两处同时配置ADC。
void McuAdcDaq_task(void *pvParameters
)
{
ADC_UserConfig2();
TickType_t ticks
= xTaskGetTickCount();
while (1)
{
User_RefreshSensor();
User_MB_RefreshInputRegister();
vTaskDelayUntil( &ticks
, 100);
}
}