前言

电源方向资料免费开源到QQ群280730348,欢迎进群交流沟通。博客地址edadong.com,博文同步发布在知乎、bilibili,其中bilibili主要以视频为主。建议去B站听详细视频解说。

交流电压检测需要通过单片机ADC分点采集交流电压周期内多点电压,求均方根后才能求得。STM32F4系列单片机内部有DSP库,所以直接调用DSP库内函数就可以得到交流信号有效值。但是STM32F1并没有,所以只能自己写函数去实现。虽然均方根的原理很简单,但是更重要的是我们将代码移植到STM32F1上面的时候,会出现计算力不足的情况,导致波形出现问题。因此为了解决这个问题,我们需要对采样和最终的均方根做一点点小的优化,以避免这个问题的产生。

交流电压检测代码思路

交流电压存在周期,我们需要设定如下思路,才能够还原交流电压信号的有效值:

  1. 确认PWM驱动波频率,如24KHz驱动情况下,采集交流信号的频率必须是这个频率的1/整数倍。通常我们在定时器中断中完成这个功能。
  2. 确认采样点数,一个周期内采样点数越高,得到的有效值就更稳定可靠。比如一个周期内采集200个点做均方根,但这个过程会出现一个问题,采样点数过高,系统运行过于缓慢,会影响到波形的产生,拉宽波形频率,并且在最终均方根运算的时候占用过多的时间。
  3. 确认ADC采样路数,采样时间,计算单次采样所需要的时间,避免时间不够无法获得最新的电压数据。
  4. 确认基准电压大小,在内部转换为对应ADC采集值,降低浮点数运算量,提升运算时间。
  5. 确认交流电压缩放倍数,用于还原对应真实交流电压大小。
  6. 采集到足够多的数据后,执行均方根算法,放在另外一个定时器中断中执行,优先级要低于做采集和PID的定时器,避免被打断。

以上就是采集交流电压的代码思路,那我们开始介绍每一段代码

(1)确认PWM驱动频率和采样点数,开启采集交流信号的对应频率中断,此处我们采取同频定时器,中断内部计数的方式。由于我们采样点数确认为100,交流正弦波输出为60Hz,交流信号频率为24KHz,所以一个完整周期会执行400次中断。每进入4次中断执行一次交流单点电压采集。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
//这个请在主函数中调用
TIM3_Int_Init(3000-1,1-1);//24KHZ

//这个是定时器三定时器中断和内部采集函数
void TIM3_Int_Init(u16 arr,u16 psc)
{
NVIC_InitTypeDef NVIC_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;

RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);//TIM3时钟使能

//定时器TIM7初始化
TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值
TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割:TDTS = Tck_tim
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); //根据指定的参数初始化TIMx的时间基数单位

TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE ); //使能指定的TIM3中断,允许更新中断

TIM_Cmd(TIM3,ENABLE);//开启定时器3

NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0 ;//抢占优先级0
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //子优先级3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器
}

void TIM3_IRQHandler(void)
{
static u8 ca_count=0;
if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET)//是更新中断
{
ca_count++;
if(ca_count>=4)
{
if(jisuan_status==0)calculate_rms();
ca_count=0;
}
TIM_ClearITPendingBit(TIM3, TIM_IT_Update ); //清除TIM3更新中断标志
}
}

(2)配置ADC和高速DMA采集代码,一个通道输出,同时配置采样精度(也就是配置采样时间),这里选用PA0进行采样。

adc.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include "adc.h"
#define ADC1_DR_Address ((u32)0x40012400+0x4c)
__IO uint16_t ADC_ConvertedValue[1];//全局变量//AD采样存放空间
void Init_adc(void)//初始化ADC
{
ADC_InitTypeDef ADC_InitStructure;
DMA_InitTypeDef DMA_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
//---------------------充电AD初始化--------------------
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);//启动DMA时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);//启动ADC1时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//采样脚设置
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_Init(GPIOA, &GPIO_InitStructure);

DMA_DeInit(DMA1_Channel1);//DMA1通道1配置
DMA_InitStructure.DMA_PeripheralBaseAddr = ADC1_DR_Address;//外设地址
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)ADC_ConvertedValue;//内存地址
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;//dma传输方向单向
DMA_InitStructure.DMA_BufferSize = 1;//设置DMA在传输时缓冲区的长度
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//设置DMA的外设递增模式,一个外设
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//设置DMA的内存递增模式
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;//外设数据字长
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;//内存数据字长
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;//设置DMA的传输模式:连续不断的循环模式
DMA_InitStructure.DMA_Priority = DMA_Priority_High; //设置DMA的优先级别
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;//设置DMA的2个memory中的变量互相访问
DMA_Init(DMA1_Channel1, &DMA_InitStructure);
DMA_Cmd(DMA1_Channel1, ENABLE);//使能通道1
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 = 1;//用于转换的通道数
ADC_Init(ADC1, &ADC_InitStructure);
ADC_RegularChannelConfig(ADC1, ADC_Channel_0 , 1, ADC_SampleTime_7Cycles5);//规则模式通道配置
ADC_DMACmd(ADC1, ENABLE);//使能ADC1的DMA
ADC_Cmd(ADC1, ENABLE);//使能ADC1
ADC_ResetCalibration(ADC1);//使能ADC1复位校准寄存器
while(ADC_GetResetCalibrationStatus(ADC1));//检查校准寄存器是否复位完毕
ADC_StartCalibration(ADC1);//开始校准
while(ADC_GetCalibrationStatus(ADC1));//检测是否校准完毕
ADC_SoftwareStartConvCmd(ADC1, ENABLE);//开启ADC1的软件转换
}

adc.h

1
2
3
4
5
6
#ifndef __ADC_H
#define __ADC_H
#include "stm32f10x.h"

void Init_adc(void);
#endif /* __ADC_H */

(3)定义基准电压和交流电压缩放倍数

1
2
u16 AC_V1_vref=1856;
float AC_V1_ratio=34.000f;

(4)定义采集函数

measure.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
#include "measure.h"
#include "adc.h"
#include "main.h"
#include "math.h"
/*
ADC1 CH0 PA0 采集交流电压输入
ADC1 CH1 PA1 采集交流电压输入
*/
extern __IO uint16_t ADC_ConvertedValue[1];
float ADC_ConvertedValueLocal;
#define SAMPLE_LEN 100 //采样的点数,经计算得知

rms_type AC_rms={0};//存放rms_type的五个变量
u16 AC_sample_mat[SAMPLE_LEN]={0};//用来暂时保存各点采样值并最终将结果送入有效值中

void calculate_rms(void) //计算采样函数
{
static u32 count=0;
u32 temp=count %SAMPLE_LEN;
count++;
AC_sample_mat[temp]= get_acv1_in(); //PA0
if(SAMPLE_LEN-1==temp)//如果检测到检测周期内的最后一个采样点
{
count=0;
jisuan_status=1;
// AC_rms.V1_rms=measure_rms(SAMPLE_LEN)/4095*3.3*AC_V1_ratio;//采集输入电压的均方根
}
}
////采集输入多路交流电压
//float get_acv1_in(void)
//{
// float temp=0;
// u32 sum=0;
// int i;
// for(i=0;i<ROWS;i++)
// {
// sum+=adc_result[i*COLS +0];//采集第一个通道,也就是第一个数据
// }
// temp =(double)sum/ROWS/4095*3.3;//将总值变成平均值
// return (temp-AC_V1_vref)*AC_V1_ratio;//返回直流电压大小,倍数乘以20.07倍(可测试得出)
//
//}

//采集输入直流电压
u16 get_acv1_in(void)
{
u16 temp=0;
temp = ADC_ConvertedValue[0];
if(temp>=AC_V1_vref)return (temp-AC_V1_vref);//返回直流电压大小,倍数乘以20.07倍(可测试得出)
else return (AC_V1_vref-temp);//返回直流电压大小,倍数乘以20.07倍(可测试得出)
}


//float measure_rms(uint8_t i,uint8_t size)//均方根算法
//{
// float sum=0,result;
// int j;
// for(j=0;j<i;j++)
// {
// sum+=(float)(AC_sample_mat[size][j]*AC_sample_mat[size][j]);
// }
// sum=(double)(sum/i);
// result=(float)(1.0*sqrt(sum));
// return result;
//
//}

float measure_rms(uint8_t i)//均方根算法
{
float sum=0,result;
int j;
for(j=0;j<i;j++)
{
sum+=(AC_sample_mat[j]*AC_sample_mat[j]);
}
sum=sum/i;
result=sqrt(sum);
return result;

}

measure.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#ifndef __MEASURE_H
#define __MEASURE_H
#include "stm32f10x.h"

typedef struct _AC_RMS //定义有效值结构体
{
float V1_rms; //输入交流电压有效值
}rms_type;

extern rms_type AC_rms;
void calculate_rms(void);
u16 get_acv1_in(void);
float measure_rms(uint8_t i);//均方根算法,i代表我们采样的点数,size代表我们要采样的数据行


#endif

总结

代码资料如下

通过网盘分享的文件:post8资料合集
链接: https://pan.baidu.com/s/1mh0XajzdhBUUIZntvJKvLg 提取码: enx6
–来自百度网盘超级会员v6的分享