背景
在项目实际应用中,刚好有需求需要使用多路ADC同时采样,这里就选择STM32 ADC多路ADC同时采样,这里简单说明下配置过程,以及使用步骤
原理图
如下图所示,使用四路ADC输入
ADC_Voltage -> 电压信号的采样,外部输入信号,交流电的输入信号,正选信号
ADC_Current -> 电流电流的采样,外部输入信号,交流电的输入信号,正选信号
ADC_Compensation -> 热敏电阻的采样,温度补偿
SCR_NTC -> 同样的热敏电阻的采样,温度补偿
一共使用上述四路ADC输入信号,进入STM32F103C8T6进行采样
外部输入电流、电压采用信号,这里做个保护电路
NTC热敏电阻采样电路如下
这里不仔细说明NTC补偿温度的过程,网上也比较多资料,这里侧重说明一下STM ADC的配置
软件设计
采用STM32F103C8T6 使用固件库开发方式
ADC初始化如下
定义DMA搬运ADC采样原始数据的到RAM的地址空间
#define SAMPL_TIMES_PRE_CHANNEL 1000 //每通道采样次数
#define NUM_OF_CHANNEL 4 //供4个通道
uint16_t __IO AD_Value[SAMPL_TIMES_PRE_CHANNEL][NUM_OF_CHANNEL]; //ADC转换结果,DMA目标地址
由于主时钟倍频到72MHZ,这里使用ADC1,首先6分频,主要是不需要特别高的采样率
1路、2路ADC信号,是正选信号,需要匹配其频率,这里经过计算的配置如下
3路、4路ADC信号,无所谓频率,所以只需要匹对1路、2路即可
这里采用独立模式连续多通道扫描模式,针对原理图中设计的
0、1、6、7通道进行扫描
根据需要也可以选择是否打开扫描完成后的中断,实际使用过程中,没有打开,因为感觉没啥用
如果有需要的话也是可以打开的。
打开扫描完成后的中断,需要注意,清除中断标志
void bspNTCAdcInit(void)
{
// NVIC_InitTypeDef NVIC_InitStructure;
ADC_InitTypeDef ADC_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
DMA_InitTypeDef DMA_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
/* Configure ADC PA1 PA2 PA6 PA7 */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_6 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/* 配置ADC时钟,为PCLK2的8分频,即72/8=9MHz,ADC最大不超过14MHz */
//RCC_ADCCLKConfig(RCC_PCLK2_Div8);
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 , ENABLE);
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1 , ENABLE);
/* ADC1 configuration ------------------------------------------------------*/
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 = NUM_OF_CHANNEL; /* 扫描通道数 */
ADC_Init(ADC1, &ADC_InitStructure);
/* ADC1 regular channe0 configuration */
//通过查找数据手册可知,PA5对应的是channel_5 转换时间为:(239.5+12.5)/9 = 28us
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_6, 3, ADC_SampleTime_239Cycles5); /* ADC1,ADC通道x,规则采用顺序值为1,采样时间为239.5周期 */
ADC_RegularChannelConfig(ADC1, ADC_Channel_7, 4, ADC_SampleTime_239Cycles5); /* ADC1,ADC通道x,规则采用顺序值为2,采样时间为239.5周期 */
/* Enable the temperature sensor and vref internal channel */
//ADC_TempSensorVrefintCmd(ENABLE);
ADC_DMACmd(ADC1, ENABLE);//开启ADC的DMA支持
/* Enable ADC1 */
ADC_Cmd(ADC1, ENABLE);
/* Enable ADC1 reset calibaration register */
ADC_ResetCalibration(ADC1);
/* Check the end of ADC1 reset calibration register */
while(ADC_GetResetCalibrationStatus(ADC1));
/* Start ADC1 calibaration */
ADC_StartCalibration(ADC1);
/* Check the end of ADC1 calibration */
while(ADC_GetCalibrationStatus(ADC1));
/* Start ADC1 Software Conversion */
//ADC_SoftwareStartConvCmd(ADC1, ENABLE);
DMA_DeInit(DMA1_Channel1); //将DMA的通道1寄存器重设为缺省值
DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&ADC1->DR; //DMA外设ADC基地址
DMA_InitStructure.DMA_MemoryBaseAddr = (u32)&AD_Value; //DMA内存地址
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //内存作为数据传输的目的地
DMA_InitStructure.DMA_BufferSize = NUM_OF_CHANNEL * SAMPL_TIMES_PRE_CHANNEL; //DMA通道的DMA缓存大小
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设寄存器地址不变
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //内存地址寄存器递增
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; //数据宽度为16位
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; //数据宽度为16位
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; //工作在循环缓存模式
DMA_InitStructure.DMA_Priority = DMA_Priority_High; //DMA通道x拥有高优先级
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //DMA通道x没有设置为内存到内存传输
DMA_Init(DMA1_Channel1, &DMA_InitStructure); //
// NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel1_IRQn;
// NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 8;// 设置抢占优先级
// NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
// NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
// NVIC_Init(&NVIC_InitStructure);
DMA_Cmd(DMA1_Channel1, ENABLE); //启动DMA通道
// DMA_ITConfig(DMA1_Channel1, DMA_IT_TC, ENABLE);
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
}
对采样的数据进行均值滤波
这里根据自己的需要,对采样的数据进行均值滤波
可以一起计算滤波,也可以分开计算,这个计算过程比较消耗资源,所以,我这里分开计算了
使用哪一路的ADC进行计算哪一路
只有需要更新ADC时才计算实际ADC的数值
void filter_0_1(void)
{
uint64_t sum = 0;
uint16_t count , i;
i = 0;
for(count = 0; count < SAMPL_TIMES_PRE_CHANNEL; count += 1)
{
sum += AD_Value[count][i] * AD_Value[count][i];
//sum += AD_Value[count][i];
}
after_filter[i]=sum / SAMPL_TIMES_PRE_CHANNEL;
sum=0;
//printf("i:%d,%4.2fv\r\n", i,((float)after_filter[i]/4095)*3.3);
i = 1;
for(count = 0; count < SAMPL_TIMES_PRE_CHANNEL; count += 1)
{
sum += AD_Value[count][i] * AD_Value[count][i];
//sum += AD_Value[count][i];
}
after_filter[i]=sum / SAMPL_TIMES_PRE_CHANNEL;
//printf("i:%d,%4.2fv\r\n", i,((float)after_filter[i]/4095)*3.3);
}
//static u8 ntct_fresh = 1;
void filter_2_3(void)
{
uint32_t sum = 0;
uint16_t count , i;
i = 2;
//if(ntct_fresh == 1)
{
for(count = 0; count < SAMPL_TIMES_PRE_CHANNEL; count += 1)
{
sum += AD_Value[count][i];
}
after_filter[i]=sum / SAMPL_TIMES_PRE_CHANNEL;
}
sum=0;
//printf("i:%d,%4.3fv\r\n", i,((float)after_filter[i]/4095)*3.3);
// i = 3;
// for(count = 0; count < SAMPL_TIMES_PRE_CHANNEL; count += 1)
// {
// sum += AD_Value[count][i];
// }
// after_filter[i]=sum / SAMPL_TIMES_PRE_CHANNEL;
// //printf("i:%d,%4.3fv\r\n", i,((float)after_filter[i]/4095)*3.3);
}
如果打开DMA中断标识,需要清除中断标志
void DMA1_Channel1_IRQHandler(void)
{
portBASE_TYPE xHigherPriorityTaskWoken = pdFALSE;
// uint16_t msg = MSG_SYS_ADC;
if(DMA_GetFlagStatus(DMA1_FLAG_TC1)==SET)
{
DMA_ClearFlag(DMA1_FLAG_TC1);
//xQueueSendFromISR( tempctrlxQueue, &msg, &xHigherPriorityTaskWoken );
}
portEND_SWITCHING_ISR( xHigherPriorityTaskWoken );
}