虽然用定时器定时器进行AD采样的方法在网上一查一大堆,官网上给的例程也很多,实现起来也不是太难。但是当自己亲手去编代码,实操的时候还是会发现很多的细节需要注意的,任意一个细节忽略或者出差错的话,就会导致结果有很大的误差,甚至结果根本就不对、更坏就是没有现象。很幸运的是这几种情况我都遇到了。下面我记录一下我遇到的一些问题与解决的方法。
对电压电流的采集,有直流与交流,对于直流的话,比较简单,就是多采集些点,然后在对这些值进行求平均值,就能够得到想要的结果。但是对于交流量的采集转换的话就不是简单的求平均值的问题啦,因为对于交流量的话,需要将采集的离散的点进行求它的有效值,就是求出均方根。(采集的每个点的值平方 累加和)/点数 再开方。 需要注意的是采集的点数要保证原始的波形不会失真,我是用一个62us定时器定时采样,对于正常的交流电的频率就是50HZ,每一个周期20ms。也就是说,每一个周期可以采集到320个点。
AD的基本知识
AD就是将模拟量转换成数字量,STM32的AD转换的电压范围就是0~3.3V,当我们要测的模拟量大于这个范围的时候,就需要外部的硬件电路对模拟信号转换到这个电压范围内,我是用的一个1000:1的电流互感线圈,也就是1A的电流感应出1ma的电流。然后在做一个1.65V的偏置电压,因为AD不能对负的量进行采集转换。这都涉及到硬件电路,我一个电路白痴是吃不透的,硬件工程师给我们一个转换关系,我们根据这个转换关系将采集到的数据做处理后得到最终的电流或者电压的有效值。下面开始干货。
adc.c 文件
/******************AD*********************/
uint8_t ADC_Flag = 0; //AD缓存数组存满标志 1处理 0继续采集
uint32_t Iac_Final = 0; //补偿后的有效值
uint32_t ADC_Value[Iac_Cnt] = {0}; //AD转换值缓存区
//int32_t ResultIAC = 0; //单次转换值
//uint64_t ADC_ValueSum = 0;
uint16_t Iac_Num = 0; //电流 数组下标
float Iactemp_last = 0.0;
/************************************************
函数名称:ADC_RCC_Configration
功 能:
参 数:
返 回 值:
*************************************************/
void ADC_RCC_Configuration(void)
{
/*使能APB2时钟 */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
RCC_ADCCLKConfig(RCC_ADCCLK_PCLK_Div4); //ADC时钟不能大于14M
}
/************************************************
函数名称:ADC_GPIO_Configuration
功 能:
参 数:
返 回 值:
作 者: DW
*************************************************/
void ADC_GPIO_Configuration(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; //ADC_IN6
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN; //模拟模式
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; //无上下拉 (浮空)
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
/************************************************
函数名称: ADC_Configuration
功 能:
参 数:
返 回 值:
作 者: DW
*************************************************/
void ADC_Configuration(void)
{
ADC_InitTypeDef ADC_InitStructure;
ADC_DeInit(ADC1); //复位并初始化
ADC_StructInit(&ADC_InitStructure); // 把结构体中的每一个参数按缺省值填入
ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b; //12位分辨率
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; //转换使能
ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None;
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //右对齐
ADC_InitStructure.ADC_ScanDirection = ADC_ScanDirection_Upward; //浏览方向
ADC_Init(ADC1, &ADC_InitStructure);
//通道配置
ADC_ChannelConfig(ADC1, ADC_Channel_6, ADC_SampleTime_239_5Cycles);
ADC_GetCalibrationFactor(ADC1); //校验
ADC_Cmd(ADC1, ENABLE); //使能ADC
while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_ADRDY)); //等待就绪
ADC_StartOfConversion(ADC1); //启动ADC
}
/************************************************
函数名称: ADC_Init_Hall
功 能:
参 数:
返 回 值:
作 者: DW
*************************************************/
void ADC_Init_Hall(void)
{
ADC_RCC_Configuration(); //ADC时钟配置
ADC_GPIO_Configuration(); //引脚配置
ADC_Configuration();
Timer15_Init(); //定时器的初始化
}
/**************************************************
函数名称:ADC_Deal
功 能: 电流有效值的计算 有效值^2 = (X1^2+X2^2+X3^2+...+Xn^2)/n^2
参 数:
返 回 值:
作 者: DW
**************************************************/
void ADC_Deal(void)
{
uint64_t ADC_ValueSum = 0;
uint32_t i, linshi = 0,linshi1 = 0;
float Iactemp = 0.0, Iac_RC = 0.0;
float Iac_Orignal = 0.0; //没经过补偿的有效值
for(i = 0; i < Iac_Cnt; i++)
{
ADC_ValueSum += ADC_Value[i]*ADC_Value[i];
}
Iactemp = ADC_ValueSum/Iac_Cnt;
Iac_RC = (Iactemp_last*80+ Iactemp*20)/100; //RC一阶低通滤波
Iactemp_last = Iactemp;
Iac_Orignal = sqrt(Iac_RC)/75; //滤波后得到的有效值
linshi = Iac_Orignal*1000;
linshi1 = linshi+((linshi/1000)/2)*100; //硬件上精度不够 通过软件上的方法补偿
Iac_Final = linshi1/10-5;
ADC_ValueSum = 0;
ADC_Flag = 0;
}
time.c
/**************************************************
函数名称: Timer15_Init
功 能: 定时 62us
参 数:
返 回 值:
作 者: DW
((1+TIM_Prescaler )/48M)*(1+TIM_Period )=((1+5)/48M)*(1+496)=62us
**************************************************/
void Timer15_Init()
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
volatile uint32_t KEY_TIM_CLKValuekHz = 0; /* Contains the frequency input of KEY_TIM in Khz */
volatile uint16_t TimeIntCount = 0;
/* Get frequency input of Decode_TIM in Khz */
KEY_TIM_CLKValuekHz = SystemCoreClock/((ADC1_TIM_PRESCALER + 1)*1000); // 48M/((5+1)*1000)=8000
TimeIntCount = (KEY_TIM_CLKValuekHz * ADC1_TIM_TIME_US)/1000; // (8000*62)/1000=496
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM15, ENABLE);
TIM_DeInit(TIM15); //复位定时器
TIM_ClearITPendingBit(TIM15,TIM_IT_Update); //清除TIM15的中断待处理位
TIM_TimeBaseStructure.TIM_Prescaler = ADC1_TIM_PRESCALER;
TIM_TimeBaseStructure.TIM_Period = TimeIntCount;
TIM_TimeBaseStructure.TIM_ClockDivision = ADC1_TIM_CLKDIV;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; /* Time 上升沿计算模式
TIM_TimeBaseInit(TIM15, &TIM_TimeBaseStructure);
NVIC_InitStruct.NVIC_IRQChannel = TIM15_IRQn;
NVIC_InitStruct.NVIC_IRQChannelPriority = 0; //优先级为最高
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStruct);
TIM_ClearFlag(TIM15,TIM_FLAG_Update);
TIM_ITConfig(TIM15, TIM_IT_Update, ENABLE); //使能TIM15中断
TIM_Cmd(TIM15, ENABLE);
}
TIM15_IRQHandler.c
/**************************************************
函数名称: TIM15_IRQHandler
功 能: TIM15中断处理函数
参 数:
返 回 值:
作 者: DW
**************************************************/
void TIM15_IRQHandler(void)
{
int32_t ResultIAC = 0; //单次转值
if (TIM_GetITStatus(TIM15, TIM_IT_Update) != RESET)
{
TIM_ClearITPendingBit(TIM15,TIM_IT_Update); //请标志
while(ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET); //等待转换完成
ResultIAC = (uint32_t) ADC_GetConversionValue(ADC1);
ResultIAC = (ResultIAC*3300) >> 12; //为方便计算 将数值扩大1000倍 1.25 -> 1250
ResultIAC = ABS(ResultIAC-Bias_Vol); // 复原(a-1650) 取绝对值 放入数组 电路有1.65V的偏置
ADC_Value[Iac_Num] = ResultIAC;
if(Iac_Num > Iac_Cnt-1)
{
ADC_Flag = 1;
Iac_Num = 0;
}
else
{
ADC_Flag = 0;
Iac_Num++;
}
}
}
注意:我使用的不是定时器触发AD转换,而是定时器定时器去读取AD的转换值,放入到数组中,保证在一个周期内采集到足够多的点使得原来的波形不失真。