STM32F030 定时器 AD 单通道 交流电流采样转换

虽然用定时器定时器进行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的转换值,放入到数组中,保证在一个周期内采集到足够多的点使得原来的波形不失真。




  • 17
    点赞
  • 113
    收藏
    觉得还不错? 一键收藏
  • 16
    评论
要基于STM32F4进行50Hz交流电的ADC采集,你需要将ADC采样速率设置为至少100Hz(根据奈奎斯特定理,采样频率应为信号频率的两倍)。以下是一个基于STM32F4的ADC采集50Hz交流电的示例代码: ```c #include "stm32f4xx.h" void ADC_Init(void) { ADC_InitTypeDef ADC_InitStruct; GPIO_InitTypeDef GPIO_InitStruct; // 使能 ADC1 时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); // 使能 GPIOA 时钟 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); // 配置 ADC1 的输入引脚(PA0) GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AN; GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL; GPIO_Init(GPIOA, &GPIO_InitStruct); // ADC1 初始化配置 ADC_InitStruct.ADC_Resolution = ADC_Resolution_12b; ADC_InitStruct.ADC_ScanConvMode = DISABLE; ADC_InitStruct.ADC_ContinuousConvMode = ENABLE; ADC_InitStruct.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None; ADC_InitStruct.ADC_DataAlign = ADC_DataAlign_Right; ADC_InitStruct.ADC_NbrOfConversion = 1; ADC_Init(ADC1, &ADC_InitStruct); // 配置 ADC1 的通道 0 ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_15Cycles); // 使能 ADC1 ADC_Cmd(ADC1, ENABLE); } uint16_t ADC_Read(void) { // 启动 ADC1 转换 ADC_SoftwareStartConv(ADC1); // 等待转换完成 while(ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET); // 读取转换结果 return ADC_GetConversionValue(ADC1); } int main(void) { ADC_Init(); while(1) { uint16_t adcValue = ADC_Read(); // 将ADC采样转换为电压(假设使用的是3.3V参考电压) float voltage = (float)adcValue * 3.3 / 4096; // 计算交流电频率(假设采样频率为100Hz) float frequency = 100.0 / 2; // 打印采样值和频率 printf("ADC Value: %d, Frequency: %.2f Hz\n", adcValue, frequency); // 延时一段时间(根据需要调整延时时间) delay_ms(10); } } ``` 在这个示例代码中,我们根据输入的ADC采样值计算电压,并假设使用了3.3V的参考电压。然后,我们假设采样频率为100Hz,并根据采样频率计算交流电的频率。最后,我们使用printf函数将采样值和频率打印出来,并延时一段时间。你可以根据需要修改代码和计算公式。
评论 16
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值