文章目录
概要
使用F407内部的DAC由定时器触发并加上DMA操作实现如正弦波、方波和三角波的输出,并且频率可控。ADC同样也是由定时器控制,可以实现较准确的波形采集,以及误差较小的FFT分析和相位计算。
CubeMX配置
- 时钟配置
基础时钟配置
- 串口配置
波特率可以自己设置,这里设置为9600。接收中断也要配置。
- 定时器配置
打算使用TIM2和TIM4去触发DAC的通道0和通道1,这样能独立设置两个通道的频率,互不影响。使用TIM3来触发两个ADC。需要注意的是TIM2、TIM3和TIM4都是通用定时器挂载在APB1,84Mhz的频率。
TIM2
同样的可以去设置另外两个定时器,根据自己的需要去修改自动重装载值。
TIM3
TIM4
4. DAC
设置为循环模式
- ADC
ADC1触发事件选择
ADC1的DMA
ADC1的中断
ADC2和ADC1保持一致,共用一个定时器的触发事件
代码细节
1. 串口发送:
#include <stdio.h>
int fputc(int ch, FILE *f)
{
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xffff);
return ch;
}
int fgetc(FILE *f)
{
uint8_t ch = 0;
HAL_UART_Receive(&huart1, &ch, 1, 0xffff);
return ch;
}
在main.c中使用时需要有#include <stdio.h>
,勾选Use
初学32时经常使用printf重定向来进行串口发数据,当后来发现要使用多个串口时这非常不方便,不如HAl库中的串口函数。
HAL_UART_Transmit(&huart1, "Toggle LED0!\r\n", sizeof("Toggle LED0!\r\n"),10000);
基础用法只能发送一些固定的字符,如果发送的数据中包含变量的话需要调用<string.h>
中的sprintf,使用如下:
char Tx_buf[50];//用于暂时存放将要发送的字符
float Num = 10.00;//以float类型变量为例
sprintf(Tx_Buf,"Num=%.1f\r\n",Num);
HAL_UART_Transmit(&huart1,(const uint8_t*)Tx_Buf,sizeof(Tx_Buf),10000);
2. 串口接收:
这里使用串口中断进行数据接收
串口中断使能:
HAL_UART_Receive_IT(&huart1, &cmd, 1);//使能接收函数
中断回调函数:
if(huart->Instance == USART1)//和串口屏的使用,可以在回调函数中根据收到的数据不同来置位相关的flag,在while(1)中执行对应的指令
{
switch(cmd)
{
case 'A':
led0_Toggle;
HAL_UART_Transmit(&huart1, "Toggle LED0!\r\n", sizeof("Toggle LED0!\r\n"),10000);
break;
case 'B':
led1_Toggle;
HAL_UART_Transmit(&huart1, "Toggle LED1!\r\n", sizeof("Toggle LED1!\r\n"),10000);
break
default : break;
}
HAL_UART_Receive_IT(&huart1, &cmd, 1);//使能接收函数
}
}
最后要加上使能函数为下次接收做准备
3. DAC+DMA
DAC是将数字量转换为模拟量,这个模拟量便是电压值。DMA是将数字量搬运给外设DAC不经由MCU,提高效率和减小MCU工作。数字量可以是在写程序时提前存好的,也可以是经过函数计算出来的。
const uint16_t Sine12bit[100]={
0x0800,0x0881,0x0901,0x0980,0x09FD,0x0A79,0x0AF2,0x0B68,0x0BDA,0x0C49,
0x0CB3,0x0D19,0x0D79,0x0DD4,0x0E29,0x0E78,0x0EC0,0x0F02,0x0F3C,0x0F6F,
0x0F9B,0x0FBF,0x0FDB,0x0FEF,0x0FFB,0x0FFF,0x0FFB,0x0FEF,0x0FDB,0x0FBF,
0x0F9B,0x0F6F,0x0F3C,0x0F02,0x0EC0,0x0E78,0x0E29,0x0DD4,0x0D79,0x0D19,
0x0CB3,0x0C49,0x0BDA,0x0B68,0x0AF2,0x0A79,0x09FD,0x0980,0x0901,0x0881,
0x0800,0x077F,0x06FF,0x0680,0x0603,0x0587,0x050E,0x0498,0x0426,0x03B7,
0x034D,0x02E7,0x0287,0x022C,0x01D7,0x0188,0x0140,0x00FE,0x00C4,0x0091,
0x0065,0x0041,0x0025,0x0011,0x0005,0x0001,0x0005,0x0011,0x0025,0x0041,
0x0065,0x0091,0x00C4,0x00FE,0x0140,0x0188,0x01D7,0x022C,0x0287,0x02E7,
0x034D,0x03B7,0x0426,0x0498,0x050E,0x0587,0x0603,0x0680,0x06FF,0x077F,
};
uint16_t Sin_data[SAMPLE_NUM_DAC];//dac正弦波的数据
uint16_t Squ_data[SAMPLE_NUM_DAC];
uint16_t Tri_data[SAMPLE_NUM_DAC];
uint8_t Squ_Duty = 50;
uint16_t vp = 1000;
void Generate_Sin_data(uint16_t vp)
{
uint16_t i;
double amp , offset;
if(vp>3300)vp=3300;
amp = (vp / 3300.0) * 4095.0;
offset = ( ( vp / 2.0 ) / 3300.0) * 4095.0; // 计算500mV对应的偏置值
for( i=0;i<SAMPLE_NUM_DAC;i++)
{
Sin_data[i] = (uint16_t)(amp * (sin((double)i * 2 * PI / SAMPLE_NUM_DAC) + 1) / 2 + 300);
//上式末尾的300是加的偏置,不加的时候波形底部变平了
}
}
void Generate_Squ_data(uint16_t vp)
{
uint16_t i;
float Duty;
double amp;
if(vp>3300)vp=3300;
amp = (vp / 3300.0) * 4095.0;
Duty = Squ_Duty / 100.0 ; //打点的数据一共100,可以直接使用占空比来分,Squ_Duty是希望设置的占空比,例50%,Squ_Duty=50
for( i=0;i<SAMPLE_NUM_DAC;i++)
{
if(i < Squ_Duty)
{
Squ_data[i] = (uint16_t)amp;
}
else
{
Squ_data[i] = 0;
}
}
}
void Generate_Tri_data(uint16_t vp)
{
uint16_t i;
double amp , slope;
if(vp>3300)vp=3300;
amp = (vp / 3300.0) * 4095.0;
//上升和下降的斜率
slope = amp / (SAMPLE_NUM_DAC / 2);
for(i = 0;i < SAMPLE_NUM_DAC / 2; i++)
{
Tri_data[i] = (uint16_t) (i * slope + 100);
}
for(i = SAMPLE_NUM_DAC / 2;i < SAMPLE_NUM_DAC ; i++)
{
Tri_data[i] = (uint16_t) ((amp - (i - SAMPLE_NUM_DAC / 2 ) * slope) + 100 );
}
}
使用函数生成的好处是可控性强,像幅值、占空比、三角波斜率等都可以调整。
DAC+DMA程序中调用:
HAL_DAC_Start_DMA(&hdac,DAC_CHANNEL_1,(uint32_t*)Sin_data,SAMPLE_NUM_DAC,DAC_ALIGN_12B_R);//开启输出.正弦
HAL_DAC_Start_DMA(&hdac,DAC_CHANNEL_2,(uint32_t*)Squ_data,SAMPLE_NUM_DAC,DAC_ALIGN_12B_R);//开启输出.方波
4. ADC+DMA
ADC是将外部模拟量采集后转换为数字量,这里使用定时器触发ADC,数据由DMA进行搬运。
ADC转换标志位:
在main.c中先声明下面两个标志位
__IO uint8_t AdcConvEnd = 0;
__IO uint8_t AdcConvEnd_2 = 0;
之后在在stm32f4xx_it里引入
在DMA完成的中断函数中完成置1的操作
ADC的使用:
初始需要调用的函数
#define SAMPLE_NUM 1024
uint16_t adc_buff[SAMPLE_NUM];//存放ADC1采集的数据
uint16_t adc_buff_2[SAMPLE_NUM];//存放ADC2采集的数据
HAL_ADC_Start_DMA(&hadc1, (uint32_t *)adc_buff, SAMPLE_NUM); //让ADC1去采集1024个数,存放到adc_buff数组里
HAL_ADC_Start_DMA(&hadc2, (uint32_t *)adc_buff_2, SAMPLE_NUM); //让ADC2去采集1024个数,存放到adc_buff数组里
下面是使用串口打印采集的数据
while(1)
{
//串口打印波形调试用
if(AdcConvEnd ) //等待转换完毕
{
for (i = 0; i < SAMPLE_NUM / 2; i++)
{
printf("%.3f\n", adc_buff[i] * 3.3 / 4095); //数据打印,查看结果
}
}
}
5. FFT操作
经过以上操作我们可以将波形数据分别采集到adc_buff[SAMPLE_NUM]和adc_buff_2[SAMPLE_NUM],那么可以调用STM的DSP库进行数据处理。关于DSP库的调用不在这里细讲。下面是对两个ADC采集到波形进行FFT,因为是共用同一个定时器,所以在计算相位差时误差会减小。
#include "arm_math.h"
#include "arm_const_structs.h"
int i,j,temp1;
uint16_t Re;
uint16_t Im;
float max1;
float FFT_inputbuf[2*SAMPLE_NUM];
float FFT_outputbuf[SAMPLE_NUM];
for ( i = 0; i < SAMPLE_NUM; i++)
{
FFT_inputbuf[2*i] = adc_buff[i] * 3.3 /4095;//输入数据要求第一个是实数下个是虚数交替存放,没有虚数的置零
FFT_inputbuf[2*i + 1] = 0;
}
for ( i = 0; i < SAMPLE_NUM; i++)
{
FFT_inputbuf_2[2*i] = adc_buff_2[i] * 3.3 /4095;//输入数据要求第一个是实数下个是虚数交替存放,没有虚数的置零
FFT_inputbuf_2[2*i + 1] = 0;
}
arm_cfft_f32(&arm_cfft_sR_f32_len1024,FFT_inputbuf,0,1);//对输入数据傅里叶变换
arm_cmplx_mag_f32(FFT_inputbuf, FFT_outputbuf, SAMPLE_NUM); //对变换后的数据取模,求mag
arm_cfft_f32(&arm_cfft_sR_f32_len1024,FFT_inputbuf_2,0,1);//对输入数据傅里叶变换
arm_cmplx_mag_f32(FFT_inputbuf_2, FFT_outputbuf_2, SAMPLE_NUM); //对变换后的数据取模,求mag
FFT_outputbuf[0] /= 100;//对输出的数据进行归一化1024
FFT_outputbuf_2[0] /= 100;//对输出的数据进行归一化1024
for (i = 1; i < SAMPLE_NUM; i++)//输出各次谐波幅值
{
FFT_outputbuf[i] /= 50;//512
}
for (i = 1; i < SAMPLE_NUM; i++)//输出各次谐波幅值
{
FFT_outputbuf_2[i] /= 50;//512
}
max1 = FFT_outputbuf[1];
max1_2 = FFT_outputbuf_2[1];
temp1 = 1;//赋初值遍历找最大,取出最大谐波的
temp1_2 = 1;
for(i = 1;i<SAMPLE_NUM / 2;i++)//跳过直流分量比较后面的
{
if(FFT_outputbuf[i] > max1)
{
max1 = FFT_outputbuf[i];
temp1 = i;
}
}
for(i = 1;i<SAMPLE_NUM / 2;i++)//跳过直流分量比较后面的
{
if(FFT_outputbuf_2[i] > max1_2)
{
max1_2 = FFT_outputbuf_2[i];
temp1_2 = i;
}
}
printf("下标1:%d \r\n下标2:%d\r\n",temp1,temp1_2);
最后输出的是频谱信号上最高值对应的下标,想要知道对于的频率值,还需×ADC采样分辨率
ADC采样分辨率 = 采样频率 / 采样点数
这里的采样频率是由触发ADC定时器的频率决定的,采样点数我设的是1024个点。
计算相位差
phase1 = atan2(FFT_inputbuf[2*(temp1) +1] , FFT_inputbuf[2*temp1]) * 180.0 / PI + 90.0;
phase2 = atan2(FFT_inputbuf_2[2*(temp1_2) +1] , FFT_inputbuf_2[2*temp1_2]) * 180.0 / PI +90.0;
phase_diff = (phase1 - phase2) ;
// 标准化到0到360度
phase_diff = fmod(phase_diff + 360.0, 360.0);//计算除以360的余数
phase = fabs(phase_diff) - 63.20;
printf("相位差:%.2f\r\n", phase ); // 确保相位差为正
总结
可以实现DAC双通道独立输出不相同的波形和频率,双ADC进行分析测出输入波形的频率值,当两通道输入有相位差的波时也能计算出正确的相位差,这个相位差大部分是准确的但是大概10次里有一会出现大概4度左右的偏差。对于频率的测量范围也不够大,大概在20K左右是会出现测出的频率不准。附的代码是通过串口发送指定字符串执行相应的操作。
标签:HAL,DAC,FFT,SAMPLE,uint16,NUM,ADC From: https://blog.csdn.net/2401_83192228/article/details/143117410