STM32F103R8T6 双ADC4通道+DMA中断数据处理采集电压,电流并计算有功电能的软硬件设计

2019-07-21 06:04发布

本人菜鸟一枚,因工作需要,边学习边实践完成了项目中的一块功能,功能简单,发上来给大家共享,也当给自己这一两个月的工作的总结,废话不多说,先从硬件上说起,主芯片用的STM32F103R8T6,中容量的产品,主要功能是要采集三相四线的电压电流来计算电能,硬件这块也不是自己一个人设计,菜鸟只是应用之人,具体的原理只能用理解来形容,有一些元件具体的作用还不能完全理解透彻,若能有大牛看到,请不吝赐教,感谢!电压电流的模拟信号采集大致原理相同,先说电压的,电压(以220V为例)先经过4个56K的分压电阻分压限流,再经过一个电流互感器(CT)将高低压之间隔离,低压侧得到电流信号,经过一个100欧姆的负载电阻将电流信号转换成电压信号,大概得到有效值位0.1V(220V的输入)左右的电压,这个值再经过运放的放大,放大到+(-)1V左右,运放的输出口再接一个迟滞比较器,迟滞比较器运放的同向端接地,即与0V相比较,这个比较器在本功能中暂时没有用的,但也在次提下比较器的作用,用来检测信号的过零点来计算信号的频率,如交流信号过零时迟滞比较器也会出现高低电平的变化,通过中断或查询的方式可以捕获到所测信号的过零点来计算信号频率。再回到放大得到+(-)1V左右的电压信号后再在运放的输出口加上一个加法电路,即将信号里的负电压成分抬高到正值,方便输入到单片机的ADC采集,这里叠加了一个3V的正电压,然后经过两个电阻分压,得到输入到单片机的AD的IO口,这样AD口电压的输入范围是1V到2V的正玄波形,说了这么多,不想看的可以直接看图片好了。电流信号的采集原理类似,只是电流采集是通过柔性线圈采集的电压型信号,输出在+-3V的信号,将+-3V的信号缩小2.5倍得到+-1.2V的电压信号,再经过电压类似的加法电路叠加,最后得到0.9V到2.1V的电压范围输入到ADC采样,电路图就不一 一上传了。接下来就说说软件的处理,因为电压电流要同时采集,正好采用双ADC,4通道同时进行采样,两个ADC在初始化时都需要初始化4个通道,虽然电压的只需要3路就可以了,但为了数据的不错乱必须要定义相同的通道数,采样后ADC1和ADC2的数据分别存放在ADC-DR的高16位和低16位,只需要定义一个u32的数据缓存区,将DMA取出的数据放入这个u32的缓存,再将高16位和低16位分离出来,再分别处理数据。废话不多说,上程序看吧,可能会有点乱,多点耐心~~~

void DMA_Configuration(void)
{
    DMA_InitTypeDef DMA_InitStructure;
    //通道1---------------------------------------------------------------------
    DMA_DeInit(DMA1_Channel1);//将DMA1的通道1重新设置为缺省值
    DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)(&(ADC1->DR));//定义DMA外设基地址
    DMA_InitStructure.DMA_MemoryBaseAddr = (u32)&AD_Value;//定义DMA内存基地址
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;//规定外设是作为数据传输的目的地还是来源。DST=目的地/SRC=来源
   
    DMA_InitStructure.DMA_BufferSize = DMA_BUF_SIZE; //指定DMA通道的DMA缓存的大小,单位为数据单位。
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外设地址不变
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;  //内存地址递增
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;////////
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;////////
    //循环模式开启,Buffer写满后,自动回到初始地址开始传输
    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通道 ,这里将使能放在了ADC2中,主要是DMA的开启必须要等到ADC1和ADC2都初始化完成后,否则数据通道会乱
   // DMA_Cmd(DMA1_Channel1, ENABLE);   
}

void ADC1_Configuration(void)
{
    ADC_InitTypeDef ADC_InitStructure;
    ADC_PortInit();     //ADC端口设置
   
    ADC_DeInit(ADC1);   //复位
    ADC_InitStructure.ADC_Mode = ADC_Mode_RegSimult;  //ADC1与ADC2工作在双同步模式
    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 = 4;     //转换序列长度为Channel_Num
    ADC_Init(ADC1, &ADC_InitStructure);
    //ADC内置温度传感器使能(要使用片内温度传感器,切忌要开启它)
    //ADC_TempSensorVrefintCmd(ENABLE);
    //常规转换序列:通道5-Iin         PA5
    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); //该通道的定义是为了匹配ADC2的四个通道数
   
    // Enable ADC1
    ADC_Cmd(ADC1, ENABLE);
    // 下面是ADC自动校准,开机后需执行一次,保证精度
    // 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));
    // ADC自动校准结束---------------
   
}

void ADC2_Configuration(void)
{
    ADC_InitTypeDef ADC_InitStructure;
   
    ADC_DeInit(ADC2);//复位
    ADC_InitStructure.ADC_Mode = ADC_Mode_RegSimult;    //ADC1和ADC2工作在同步规则模式
    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 = 4;     //转换序列长度为4
    ADC_Init(ADC2, &ADC_InitStructure);
    //ADC内置温度传感器使能(要使用片内温度传感器,切忌要开启它)
    //ADC_TempSensorVrefintCmd(ENABLE);
    //常规转换序列:通道10- Fin     PC0
    ADC_RegularChannelConfig(ADC2, ADC_Channel_10, 1, ADC_SampleTime_239Cycles5);  //A相电流
    ADC_RegularChannelConfig(ADC2, ADC_Channel_12, 2, ADC_SampleTime_239Cycles5);  //B相电流
    ADC_RegularChannelConfig(ADC2, ADC_Channel_11, 3, ADC_SampleTime_239Cycles5);  //C相电流
    ADC_RegularChannelConfig(ADC2, ADC_Channel_13, 4, ADC_SampleTime_239Cycles5);  //N相电流
   
    // Enable ADC2
    ADC_Cmd(ADC2, ENABLE);
    // 下面是ADC自动校准,开机后需执行一次,保证精度
    // Enable ADC1 reset calibaration register
    ADC_ResetCalibration(ADC2);
    // Check the end of ADC1 reset calibration register
    while(ADC_GetResetCalibrationStatus(ADC2));
    // Start ADC1 calibaration
    ADC_StartCalibration(ADC2);
    // Check the end of ADC1 calibration
    while(ADC_GetCalibrationStatus(ADC2));
    // ADC自动校准结束---------------
   
    // 开启ADC的DMA支持(要实现DMA功能,还需独立配置DMA通道等参数)
    ADC_DMACmd(ADC2, ENABLE);   
  //  ADC_ExternalTrigConvCmd(ADC2, ENABLE);      //使能ADC2的外部触发功能
    ADC_SoftwareStartConvCmd(ADC2, ENABLE);     //Start ADC2
        
     // 开启ADC1的DMA支持(要实现DMA功能,还需独立配置DMA通道等参数)
    ADC_DMACmd(ADC1, ENABLE);
   
    ADC_SoftwareStartConvCmd(ADC1, ENABLE);     //Start ADC1
   
    DMA_Cmd(DMA1_Channel1, ENABLE);   //启动DMA通道 与ADC的初始化顺序不能更改,必须先初始化好ADC再开DMA,这样不会出现通道错乱
    DMA_ITConfig(DMA1_Channel1, DMA_IT_TC, ENABLE); //使能DMA传输完成中断
}

void DMA1_Channel1_IRQHandler(void)      //DMA中断求数据的平均值,数据校准放在了主函数里
{
  if(DMA_GetITStatus(DMA1_IT_TC1) != RESET)
  {
    DMA_ClearITPendingBit(DMA1_IT_TC1);
    Average();
  }
}

void TIM3_IRQHandler(void)   //定时器计算电能
{
  if(TIM_GetITStatus(TIM3, TIM_IT_Update) == SET)  
  {
    TIM_ClearITPendingBit(TIM3 , TIM_FLAG_Update);
   // TimeCounter++;
    EA=EA+SimBuf.PA;
    EB=EB+SimBuf.PB;
    EC=EC+SimBuf.PC;
    SimBuf.EA=SimBuf.EA+EA/7200;
    SimBuf.EB=SimBuf.EB+EB/7200;
    SimBuf.EC=SimBuf.EC+EC/7200;
    SimBuf.ET=SimBuf.EA+SimBuf.EB+SimBuf.EC;
    EA=EA%7200;
    EB=EB%7200;
    EC=EC%7200;

  //  printf(" ADC2的值为:%d ",(result[0]*ADC_VREF/4096));
  }
//  ADC_SoftwareStartConvCmd(ADC1, DISABLE);  
}


数据的处理就用了平均滤波法,50次数据取平均值,剩下的就是数据校准的问题了,用了零点校准和斜率校准,数据精度还行,电压可以做到220V+-2v,电流采集的钳子量程是500A的,最终的数据的精度大概在千分之5,这个应该还算合格了,再就是电能的计算,采用定时器中断计算,每0.5s计算一次电能。电能的精度暂时还没有计算精度,

友情提示: 此问题已得到解决,问题已经关闭,关闭后问题禁止继续编辑,回答。
该问题目前已经被作者或者管理员关闭, 无法添加新回复
11条回答
xuande
1楼-- · 2019-07-21 09:59

这个当顶。


okziji
2楼-- · 2019-07-21 15:43
谢谢分享
wangguanfu
3楼-- · 2019-07-21 18:27
标记  学习  感谢
张册
4楼-- · 2019-07-21 20:58
 精彩回答 2  元偷偷看……
120542121
5楼-- · 2019-07-21 22:50
为什么要先缩小数据,然后再放大呢,直接缩小到合适量程送到ad这样不是更省事吗?
Chadlu
6楼-- · 2019-07-22 03:29
代码有错误  ADC_DMACmd(ADC2, ENABLE);

一周热门 更多>