首页 > 其他分享 >STM32F407 HAL库:双DAC的信号发生器+双ADC采集

STM32F407 HAL库:双DAC的信号发生器+双ADC采集

时间:2024-10-21 21:49:04浏览次数:9  
标签:HAL DAC FFT SAMPLE uint16 NUM ADC

文章目录

概要

使用F407内部的DAC由定时器触发并加上DMA操作实现如正弦波、方波和三角波的输出,并且频率可控。ADC同样也是由定时器控制,可以实现较准确的波形采集,以及误差较小的FFT分析和相位计算。

CubeMX配置

  1. 时钟配置

    基础时钟配置

时钟树配置

  1. 串口配置
    在这里插入图片描述
    波特率可以自己设置,这里设置为9600。接收中断也要配置。
    在这里插入图片描述
  2. 定时器配置
    打算使用TIM2和TIM4去触发DAC的通道0和通道1,这样能独立设置两个通道的频率,互不影响。使用TIM3来触发两个ADC。需要注意的是TIM2、TIM3和TIM4都是通用定时器挂载在APB1,84Mhz的频率。
    TIM2
    在这里插入图片描述
    同样的可以去设置另外两个定时器,根据自己的需要去修改自动重装载值。
    TIM3
    !

TIM4
!
4. DAC
在这里插入图片描述
在这里插入图片描述
设置为循环模式

  1. ADC
    ADC1触发事件选择
    6.
    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库函数:
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

相关文章

  • Shadcn UI:现代前端的灵活组件库
    简要介绍ShadcnUI与其他UI和组件库如MaterialUI、AntDesign、ElementUI的设计理念截然不同。这些库一般通过npm包提供对组件的访问,而ShadcnUI允许用户将单个UI组件的源代码直接下载到项目中,提供了更大的灵活性和定制空间。按照ShadcnUI的说法,ShadcnUI实际......
  • Halcon 创建变异模型
    *create_variation_model(::Width,Height,Type,Mode:ModelID)说明:创建一个ID为ModelID,宽为Width,高为Height,类型为Type的VariationModel,参数Mode决定了创建标准图像和相应的variation图像的方法。'standard'表示标准的训练方法,标准图像的位置是各训练图像位置的平均......
  • 使用audacity工具来导出wav文件
    1、打开audacity应用程序2、在audacity中打开mp3文件,3、选取一段需要导出的音频,ctrl+x剪切。4、新建一个文件,ctrl+v粘贴上一步剪切的内容。5、点击文件中的导出功能,选择wav格式,按确定。......
  • PYNQ Z2 读取xadc外部通道电压
    使用XADC或者JTAG只能读取XADC的内部电压,而无法读取外部通道的电压现在使用xsysmon.h库里面的函数进行XADC外部通道的电压为了方便观察,增加了PLGPIOKEYLED进行观察1.配置ZYNQ7000勾选FCLK_RESET0勾选UART0,这是BANK电压勾选PS给PL提供的时钟,设置PS的输入时钟......
  • 【芯智雲城】Broadcom(博通)光耦合器选型
    Broadcom(博通)的光耦芯片类型丰富,包括用于低速模拟量、故障检测、功率控制等应用普通的模拟光耦,用于各种数字电路的数字光耦,用于IGBT栅极驱动高速低功耗光耦,以及用于电流检测应用的和高线性度隔离放大器。产品具备高隔离性能、高速传输、低功耗和高可靠性等优点,在电子行业中具有......
  • 【芯智雲城】Broadcom博通BCM5389IFBG以太网控制器应用
    Broadcom公司的BCM5389IFBG以太网控制器芯片,适用于独立的千兆以太网交换机和千兆以太网控制平面及背板应用。一、芯片特点集成度高:BCM5389IFBG将数据包缓冲区、SerDes(串行解串器)、媒体访问控制器(MAC)、地址管理和非阻塞交换结构集成到一个0.13µmCMOS器件中,减少了系统的复杂......
  • 【芯智雲城】Boradcom(博通) 多领域技术解决方案介绍
    BroadcomInc.是一家全球领先的技术企业,业务范围囊括多种半导体、企业用软件和安全解决方案的设计、开发和供应。Broadcom的类别领先产品组合在许多重要的市场中发挥作用,其中包括云、数据中心、网络、带宽、无线技术、存储,以及工业和企业用软件。我们的解决方案包括服务提供......
  • STM32 ADC实例解析(1)-寄存器方式
    文章目录一、寄存器列表二、示例代码三、总结优点:缺点:在很长的一段时间里我在项目中都是使用寄存器方式一、寄存器列表__IOuint32_tSR;/!<ADC状态寄存器,地址偏移量:0x00/__IOuint32_tCR1;/!<ADC控制寄存器1,地址偏移量:0x04/__IOuint32_tCR2;/!<ADC控制寄存......
  • STM32 ADC实例解析(1)-HAL+DMA方式
    文章目录一、STM32ADC与DMA的结合使用初始化ADC:配置DMA:启动DMA传输:中断处理(可选):二、示例代码初始化GPIO设置中断设置三、应用优势高效性:减轻CPU负担:灵活性:易用性:随着开发项目的累计,将ADC与DMA结合使用,可以实现高效的数据采集和处理。一、STM32ADC与DMA的结合......
  • PYNQ z2 使用xadcps读取xadc内部电压温度
    使用xadcps只能和JTAG一样读取温度值和电压值,属于内部通道,读取不了外部通道的数据添加zynq700核后进行配置1.在PS-PLConfiguration中,取消勾选general里面的FCLK_RSTEN_N以及M_AXI_GP0_Interface2.在PeripheralIOPins中勾选1415对应的UART0,同时对板卡电压进行配置,BA......