0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

使用STM32调试FMSDR模块及解调FM电台(3)

冬至子 来源:硬木课堂 作者:硬木课堂 2023-07-06 11:00 次阅读

4. 使用MSI001解调8027发出的已知单音信号

4.1 输出24Mhz和验证SPI接口

  1. 硬件连接

    本例中我们添加MSI001相关的引脚也连接到STM32H750开发板。程序中操作的管脚如下描述:
    1.jpg

2. RCC时钟输出24MHz驱动Msi001

MSI001芯片需要输入24MHz的时钟作为参考信号,在这里使用专门的时钟产生单元RCC产生24M的方波,提供给MSI001作为输入参考信号。

使能Master clock output1后,配置PLL1Q输出为48M,MCO1选择时钟源为PLL1Q,经过2分频后,得到24M时钟。

RCC产生24Mhz时钟单元STM32CUBE配置如下:

3. 硬件SPI接口配置

芯片的控制接口是SPI协议,要使芯片正常工作,首先SPI接口的操作要正常。这里向MSI001芯片配置频率为98.5Mhz,观察配置前MSI001和配置后差分输出管脚的波形变化。如果发生变化,说明SPI操作正常,芯片可以被控。这样进行后续调试才有初步把握。

需要配置STM32H750的硬件SPI,然后发出控制字操作MSI001芯片,确认板卡和芯片正常工作。SPI工作速度设为3.75Mhz.

4. 编写代码

在main中使能RCC输出,和写MSI001寄存器

int main(void)
{
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_TIM2_Init();
  MX_I2C2_Init();
  MX_DAC1_Init();
  MX_TIM6_Init();
  MX_SPI4_Init();
  HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_1);//tim2开启pwm,输出24Mhz
	for(i=0;i< SIN_ROM_LENGTH;i++)//生成sin表
	{
		sin_25_rom[i] = (uint16_t)(sin(2*3.14*i/(SIN_ROM_LENGTH))*1000 +2047);
	}
	HAL_TIM_Base_Start_IT(&htim6);//tim6开启
	HAL_DAC_Start(&hdac1,DAC_CHANNEL_1);//dac1的通道1开启
	Qn8027_Init();	//qn8027初始化
	Msi001_Init();//msi初始化
  while (1)
  {
  }
}

添加MSI001驱动代码

#include "msi001/msi001.h"

SPI_HandleTypeDef *msi001_spi = &hspi4; ///

uint32_t g_msi001_reg[7]={0};//msi寄存器配置

//msi001的spi发送三个字节,
HAL_StatusTypeDef Msi001_SPI_Transmit(uint32_t Data)
{
	HAL_StatusTypeDef errorcode = HAL_OK;
	uint8_t pData[4];

	pData[0] = (Data >>16)&0xFF;
	pData[1] = (Data >>8)&0xFF;
	pData[2] = (Data)&0xFF;

	errorcode = HAL_SPI_Transmit(msi001_spi,pData,3,10);
	return errorcode;
}

//msi001初始化,初始化成98.5M,改变寄存器参数配置不同频率
HAL_StatusTypeDef Msi001_Init(void)
{
	uint32_t i=0;
	HAL_StatusTypeDef errorcode = HAL_OK;

//labview上位机配置为98.5m
	g_msi001_reg[0] = 0x043420;
	g_msi001_reg[1] = 0x00C141;
	g_msi001_reg[2] = 0x20BA12;
	g_msi001_reg[3] = 0x00FFF3;
	g_msi001_reg[4] = 0x000004;
	g_msi001_reg[5] = 0x28DF55;
	g_msi001_reg[6] = 0x200016;

	for(i=0;i< 6;i++)
	{
		errorcode = Msi001_SPI_Transmit(g_msi001_reg[i]);
	}
	return errorcode;
}

5. 测试MCO输出的24MHz时钟

如果方便,可以使用示波器测试stm32开发板的PA8(RCC_MCO_1)管脚,观测有无24M的波形输出。

6. MSI001写测试

在前面程序中配置QN8027输出的单音FM信号在98.5M上,下面我们把MSI001的接收频点也配在98.5M,通过示波器查看MSI001芯片的IQ输出的波形。

在main.c中,我们调用了SPI.c中的程序对SPI4进行初始化,配置SPI的时钟,相位等;

在MSI001.c中,我们尝试写寄存器,使用示波器观察MSI001的反应:

  1. keil中用debug单步调试,复位后,打断点运行到初始化MSI001芯片前。

  1. 运行到下一行,配置寄存器0为0x143420后,示波器的表现如下:

  1. 再运行一行,配置寄存器0为0x243420后,示波器的表现如下:

  1. 再运行一行,配置寄存器0为0x043420后,示波器的表现如下:

如果IQ输出能够跟随我们写入的寄存器动作,这说明SPI时序正确,硬件也是好的,这时我们就可以进行下一步操作了。

注意,SPI时序写入这一步看上去虽然简单,却也是最经常出问题的步骤。如果遇到MSI001没有反应,建议用如下方法排查:

  1. 电源测试:MSI001供电是否正常;
  2. IO通断测试:使用IO输出高低电平,通过测量确定PCB焊接正确,且插对了孔位;
  3. SPI时序测试:使用示波器或逻辑分析仪捕获发出的SPI时序,判断是否SPI配置寄存器有错误;FPGA写的SPI程序,则要特别留意是否有代码bug。
  4. 如果管脚上的SPI时序正确,但MSI001如果没有应答,观察是否有虚焊等情况(开发板发货前经过测试,基本上可以排除电源和8027的焊接问题)
  5. 为减少MSI001死掉的几率,使能STM32或FPGA管脚内部的下拉或上拉电阻,SPI时序正常的情况下没有反应,可以全板掉电重启试试。

如果没有示波器,可以使用STM32内部ADC采集后通过UART传到上位机观察波形,请查看下一节内容。

4.2 ADC采集和UARTPlot

1. 硬件连接

本例中我们使用CMSIS-DAP上自带的UART2USB功能,把ADC采集到的数据发到电脑,通过UARTPlot软件观察采集的波形。程序中操作的管脚如下描述:

1.jpg

1.jpg

2. 配置ADC1/2同步差分输入DMA采集

STM32处理数据流的能力远不如FPGA,要实现实时信号处理,在STM32中我们需要使用双缓冲的方法,即准备两个数据段,采集A段数据的时候处理B数据段,采集B数据段的时候处理A数据段,这样才能确保波形的连续实时处理。

本例中我们要实现的功能是定时器TIM1输出TRG信号触发ADC1和ADC2,实现500KSPS采样率的DMA双缓冲采集,采集完后通过UART发送IQ数据给上位机,通过上位机软件观察波形。

实现思路如下:将DMA采集输出长度设为2000个点,采集完前半部分1000个点后,进入半回调函数HAL_ADC_ConvHalfCpltCallback中,标志位置1;后半部分1000个点采集完成后,进入HAL_ADC_ConvCpltCallback,标志位置2,停止DMA采集;在主程序中如果标志位等于2,就将IQ数据发送到PC上位机显示,再开启DMA进行下一帧数据采集。这样确保我们看到的波形正确后,就可以进入下一节,进行后面的FM解调处理。

实现思路框图如下:

在while循环中,一直进行标志位判断,标志位是2时,发送数据。回调函数和半回调函数都是通过DMA1_Stream0_IRQHandler中断进入。

具体ADC同步DMA采集介绍,可以回顾基础实验 “实验二十四 ADC定时器触发配合DMA双缓冲实现实时采集”

在程序中,添加了串口打印函数printf用于发送数据到上位机,可以回顾基础实验 “实验十六 串口通信

3. 程序解读

while (1)
  {

			if(g_adc1_dma_complete_flag == 2) //采集数据完成
			{
				for(i=0;i< ADC_DATA_LENGTH;i++)
				{
					adc1_I_voltage[i] = ((float)3.3*((g_adc1_dma_data1[i])&0x0000ffff))/65536; //转换码值为电压值
					adc2_Q_voltage[i] =	((float)3.3*((g_adc1_dma_data1[i] >>16)&0x0000ffff))/65536; //转换码值为电压值
				}

				//Send I Channel Data to PC
				for(i=0;i< ADC_DATA_LENGTH;i++)
				{
					printf("%f\\r\\n", adc1_I_voltage[i]); 
		        }
				HAL_Delay(100);//Delay

        //Send Q Channel Data to PC			
				for(i=0;i< ADC_DATA_LENGTH;i++)
				{
					printf("%f\\r\\n",adc2_Q_voltage[i]); 
				}
				HAL_Delay(100);//Delay

				//Restart DMA
				g_adc1_dma_complete_flag = 0;		
				memset(&g_adc1_dma_data1[0],0,ADC_DATA_LENGTH);				HAL_ADCEx_MultiModeStart_DMA(&hadc1,g_adc1_dma_data1,ADC_DATA_LENGTH);
			}
			/* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }

4. 使用UART将波形数据发送给UARTPlot (pyserial_display) 软件

使用串口数据接收软件“pyserial_display.exe”的步骤:

  • 选择CMSIS-DAP对应的串口号
  • 设定串口波特率115200
  • 数据位8,校验位N,停止位1
  • 波形长度2000,浮点类型,双通道模式
  • CH1是I通道波形,CH2是Q通道波形。
  • 点击开始采集,等待下位机数据。

UartPlot的使用注意事项:

  • 确保上位机设置的UART参数(波特率、数据位、校验位、停止位)与大拇指开发板中的程序设定一致。
  • 检查波形长度,通道数,显示数据格式是否和大拇指开发板中程序一致;
  • 由于本软件没有使用帧头等传输协议,在使用软件时, 先在UARTPlot 上位机界面上点击开始采集,然后在大拇指开发板上启动数据传输 ,确保上位机软件捕获到数组的起始点,如果没有遵循上述启动流程,会出现波形截断现象。停止上位机并重复上述流程即可修复。
  • H750例程使用的是板载DAP调试器的UART2USB功能,波特率设定为115200,上位机界面选择USB串行设备。注意:DAP调试器在Debug模式下同时使用UART2USB功能传输数据可能导致调试器死机(死机后表现为DAP连不上芯片,Keil报No Debug Device Found),此时按住H750板上的BOOT0按钮不放,重新拔插USB后下载已知可运行程序可以解决。

一帧数据显示

按住鼠标左键,可以上下左右移动波形,按住鼠标右键,可以放大或者缩小X轴或Y轴:

点击CH1或者CH2可以关闭或打开指定通道的波形显示

如果鼠标不能用,在波形显示界面点击鼠标右键,可以选择是否在X轴或Y轴启用鼠标:

在Plot Options里,可以对波形做FFT等处理

在Export里可以导出波形数据为JPG,或EXCEL文件

4.3 FM解调算法

1. FM解调算法回顾

求解频率,FM解调

在利用相位差分计算瞬时频率f(n)时,由于计算相位要用到除法和反正切运算,这对于非专用数字处理器来说是较复杂的,在用软件实现时,也可用下面的方法来计算瞬时频率f(n)

**对于FM信号,其振幅近似恒定,可以设定 **,则

这就是利用XI(n)和XQ(n)计算f(n)的近似公式。这种方法只有乘法和减法,计算简便。也是开发板例程中用到的方法。

2. 编写代码

if(g_adc1_dma_complete_flag == 2)
			{
				for(i=0;i< ADC_DATA_LENGTH;i++)
				{
					adc1_I_voltage[i] = ((float)3.3*((g_adc1_dma_data1[i])&0x0000ffff))/65536;
					adc2_Q_voltage[i] =	((float)3.3*((g_adc1_dma_data1[i] >>16)&0x0000ffff))/65536;
				}

				// Send I Channel Data to PC
				for(i=0;i< ADC_DATA_LENGTH;i++)
				{
					printf("%f\\r\\n",adc1_I_voltage[i]);
				}
				HAL_Delay(200);//Delay

				// FM demodulate
				for(i=0;i< ADC_DATA_LENGTH;i++)
				{
					if (i==ADC_DATA_LENGTH-1)
					{
					adc12_fm_out[i] = adc12_fm_out[i-1];
					}
					else
					{
					adc12_fm_out[i] = (adc1_I_voltage[i]*adc2_Q_voltage[i+1] - adc1_I_voltage[i+1]*adc2_Q_voltage[i])*1;
					}	
				}

				// Send demodulated data to PC
				for(i=0;i< ADC_DATA_LENGTH;i++)
				{
					printf("%f\\r\\n",adc12_fm_out[i]);
				}
					HAL_Delay(200);//Delay

				//Restart DMA
				g_adc1_dma_complete_flag = 0;		
				memset(&g_adc1_dma_data1[0],0,ADC_DATA_LENGTH);				HAL_ADCEx_MultiModeStart_DMA(&hadc1,g_adc1_dma_data1,ADC_DATA_LENGTH);

3. FM解调测试

CH1是I通道波形,CH2是解调后的波形。

解调后波形(红色)可以看出1KHz的成分,需要后续进行滤波处理。

4.3 实时信号抽取和DAC输出

1. 硬件连接

程序中操作的管脚如下描述:

1.jpg

2. 实时信号采集和500K信号抽取

STM32和FPGA不同的地方在于,MCU处理数据流的能力较弱,对于500KSPS的连续FIR滤波软件开销较大,因此在STM32程序中我们先抽取降速后再进行FIR滤波(下一节介绍),降低系统的运算量。

本例中我们要实现的功能为:定时器TIM1输出TRG信号触发500KSPS采样率的ADC1和ADC2,实现DMA双缓冲采集,进行实时采集->解调->每20个数据平均为1个数据->通过TIM6触发的刷新率为25KSPS的DAC发出。最后通过示波器观察DAC管脚波形。

定时器触发ADC做DMA双缓冲数据传输的实现思路是:

  1. 将DMA采集输出长度设为2000个点,DMA配置为循环模式一直进行自动采集;
  2. 采集完成前1000个点,调用半回调函数HAL_ADC_ConvHalfCpltCallback,进行解调,20次平均,如果平均后数据达到12500个后,标志位置1,while(1)中进行前半段12500个数据处理(DAC幅度变换,更新DAC数组);
  3. 采集完成后1000个点,调用回调函数HAL_ADC_ConvCpltCallback,进行解调,20次平均,平均后数据如果达到25000个后,标志位置2,在While(1)中进行后半段12500个数据处理(DAC幅度变换,更新DAC数组)。

实现思路框图如下:

具体ADC同步DMA采集介绍,可以回顾基础实验 “实验二十四 ADC定时器触发配合DMA双缓冲实现实时采集”:

3. While循环处理

在while循环中,一直进行标志位判断,标志位是1时,处理前半部分12500个数据处理,标志位是2时,处理后半部分12500个数据处理。完成回调函数和半完成回调函数都是通过DMA1_Stream0_IRQHandler中断进入。

4. 编写代码

在main中初始化接口,配置芯片,while循环中处理数据

int main(void)
{
uint32_t i=0;
float iq_temp=0;//临时存储
uint32_t dac2_start_flag=0;//第一次dac开启标志  HAL_Init();
  SystemClock_Config();
PeriphCommonClock_Config();
  MX_GPIO_Init();
  MX_TIM2_Init();
  MX_I2C2_Init();
  MX_DAC1_Init();
  MX_TIM6_Init();
  MX_DMA_Init();
  MX_ADC1_Init();
  MX_ADC2_Init();
  MX_TIM1_Init();
  MX_SPI4_Init();
MX_UART4_Init(); 
HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_1);//tim2开启pwm,输出24Mhz
for(i=0;i< SIN_ROM_LENGTH;i++)//生成sin表
	{
		sin_25_rom[i] = (uint16_t)(sin(2*3.14*i/(SIN_ROM_LENGTH))*1000 +2047);
	}
	HAL_TIM_Base_Start_IT(&htim6);//tim6开启
	HAL_DAC_Start(&hdac1,DAC_CHANNEL_1);//dac1的通道1开启
	Qn8027_Init();	//qn8027初始化
	Msi001_Init();//msi初始化

	HAL_Delay(100);
	HAL_TIM_Base_Start_IT(&htim1);//tim1开启
	HAL_ADCEx_MultiModeStart_DMA(&hadc1,g_adc1_dma_data1,ADC_DATA_LENGTH);//ADC的dma开始采集
  while (1)
  {
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			if(g_adc1_dma_complete_flag==1)//采集完前12500个数据后,进入这个部分
			{
				for(i=0;i< ADC_FIR_DATA_LENGTH/2;i++)//将fir滤波器输出值幅度缩小范围,再将直流偏置调整到1.65v,再计算出DAC对应的码值
				{					
					iq_fir_out[i] = iq_fir_in[i]*0.9;
					iq_temp = iq_fir_out[i]+1.65;
					iq_temp = iq_temp/3.3;
					iq_temp = iq_temp * 4095;
					audio_out_dac[i] = ((uint16_t)iq_temp)&0x0fff;
				}

				if(dac2_start_flag ==0)	//第一次进入dac开启,只需第一次开启
				{					
					HAL_DAC_Start(&hdac1,DAC_CHANNEL_2);//dac1的通道2开启
					dac_phase=0;
					dac2_start_flag=1;
				}
				g_adc1_dma_complete_flag=0;
			}
			///////////////////////////////////////////////////////////////////////////////////////////////////////////////
			
			if(g_adc1_dma_complete_flag==2)//采集完后12500个数据后,进入这个部分
			{
				for(i=ADC_FIR_DATA_LENGTH/2;i< ADC_FIR_DATA_LENGTH;i++)//将fir滤波器输出值幅度缩小范围,再将直流偏置调整到1.65v,再计算出DAC对应的码值
				{					
					iq_fir_out[i] = iq_fir_in[i]*0.9;
					iq_temp = iq_fir_out[i]+1.65;
					iq_temp = iq_temp/3.3;
					iq_temp = iq_temp * 4095;
					audio_out_dac[i] = ((uint16_t)iq_temp)&0x0fff;
				}
				g_adc1_dma_complete_flag=0;
			}  }
}

5. ADC中断回调函数

采集完成前1000个点,调用半回调函数HAL_ADC_ConvHalfCpltCallback,解调,20次平均,如果平均后数据达到12500个后,标志位置1。

采集完成后1000个点,调用回调函数HAL_ADC_ConvCpltCallback,解调,20次平均,平均后数据如果达到12500个后,标志位置2

驱动代码:

#define ADC_DATA_LENGTH  2000   //定义采集数据长度为2000
uint32_t g_adc1_dma_data1[ADC_DATA_LENGTH];//定义adc1采集数据存放数组
uint8_t g_adc1_dma_complete_flag = 0;	//adc1数据dma采集完成标志,在dma采集完成回调函数设置
float adc1_I_voltage[ADC_DATA_LENGTH];//定义I采集数据存放数组
float adc2_Q_voltage[ADC_DATA_LENGTH];//定义Q采集数据存放数组
float adc12_iq_out[ADC_DATA_LENGTH];//定义out存放数组

#define ADC_FIR_DATA_LENGTH  25000   //定义采集数据长度为1000
uint16_t audio_out_dac[ADC_FIR_DATA_LENGTH] = {0};//输出dac
float iq_fir_in[ADC_FIR_DATA_LENGTH];//滤波前数据
float iq_fir_out[ADC_FIR_DATA_LENGTH];//滤波后数据

uint32_t iq_fir_in_cnt =0; //平均后,存放数据的下标


//ADC回调函数,ADC采集完成后进入回调函数
//adc端口
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc)
{	
	uint32_t i=0,j=0;
	if(hadc- >Instance==ADC1)
		{
		for(i=ADC_DATA_LENGTH/2;i< ADC_DATA_LENGTH;i++)
		{
			adc1_I_voltage[i] = ((float)3.3*((g_adc1_dma_data1[i])&0x0000ffff))/65536;//采集到的值转换为电压
			adc2_Q_voltage[i] =	((float)3.3*((g_adc1_dma_data1[i] >>16)&0x0000ffff))/65536;//采集到的值转换为电压
		}

		//IQ通道解调
			for(i=ADC_DATA_LENGTH/2;i< ADC_DATA_LENGTH;i++)
		{			
			if (i==ADC_DATA_LENGTH-1)
					{
					adc12_iq_out[i] = adc12_iq_out[i-1];
					}
					else
					{
					adc12_iq_out[i] = (adc1_I_voltage[i]*adc2_Q_voltage[i+1] - adc1_I_voltage[i+1]*adc2_Q_voltage[i])*1;
					}
			}
		//20倍抽取:每20个点求平均
		for(i=ADC_DATA_LENGTH/2/20;i< ADC_DATA_LENGTH/20;i++)
		{
			iq_fir_in[iq_fir_in_cnt] =0;
			for(j=0;j< 20;j++)
			{
				iq_fir_in[iq_fir_in_cnt] += adc12_iq_out[20*i + j]; 
			}
			iq_fir_in[iq_fir_in_cnt] = iq_fir_in[iq_fir_in_cnt]/20;//求平均
			iq_fir_in_cnt++;
			if(iq_fir_in_cnt==ADC_FIR_DATA_LENGTH/2)
			{
				g_adc1_dma_complete_flag = 1;//前半段采集完成标志
			}
			if(iq_fir_in_cnt==ADC_FIR_DATA_LENGTH)
			{
				g_adc1_dma_complete_flag = 2;//后半段采集完成标志
				iq_fir_in_cnt=0;
			}
		}

	}
}

//ADC半传输回调函数,ADC采集完成一半后后进入回调函数
//adc端口号
void HAL_ADC_ConvHalfCpltCallback(ADC_HandleTypeDef *hadc)
{
	uint32_t i=0,j=0;
	if(hadc- >Instance==ADC1)
	{

		for(i=0;i< ADC_DATA_LENGTH/2;i++)
		{
			adc1_I_voltage[i] = ((float)3.3*((g_adc1_dma_data1[i])&0x0000ffff))/65536;//采集到的值转换为电压
			adc2_Q_voltage[i] =	((float)3.3*((g_adc1_dma_data1[i] >>16)&0x0000ffff))/65536;//采集到的值转换为电压
		}

		//IQ通道解调
			for(i=0;i< ADC_DATA_LENGTH/2;i++)
		{
				  if (i==ADC_DATA_LENGTH/2-1)
					{
					adc12_iq_out[i] = adc12_iq_out[i-1];					}
					else
					{
					adc12_iq_out[i] = (adc1_I_voltage[i]*adc2_Q_voltage[i+1] - adc1_I_voltage[i+1]*adc2_Q_voltage[i])*1;
					}	
		}
		//20倍抽取,20次求平均
		for(i=0;i< ADC_DATA_LENGTH/2/20;i++)
		{
			iq_fir_in[iq_fir_in_cnt] =0;
			for(j=0;j< 20;j++)
			{
				iq_fir_in[iq_fir_in_cnt] += adc12_iq_out[20*i + j]; 
			}
			iq_fir_in[iq_fir_in_cnt] = iq_fir_in[iq_fir_in_cnt]/20;//求平均
			iq_fir_in_cnt++;
			if(iq_fir_in_cnt==ADC_FIR_DATA_LENGTH/2)
			{
				g_adc1_dma_complete_flag = 1;//前半段采集完成标志
			}
			if(iq_fir_in_cnt==ADC_FIR_DATA_LENGTH)
			{
				g_adc1_dma_complete_flag = 2;//后半段采集完成标志
				iq_fir_in_cnt=0;
			}
		}
	}
}

6. DAC中断输出

STM32H750的DAC是双通道,其中一个通道用于在PA4管脚上输出1k正弦波送给8027调制,另一个通道用于在PA5管脚上输出经过FM解调/平均/DAC变换后数据

//tim6中断处理函数,每进入一次中断,输出新码表值
void  TIM6_DAC_IRQHandler(void)
{

		__HAL_TIM_CLEAR_IT(&htim6, TIM_IT_UPDATE);//清除tim6的事件更新标志

		if(dac_phase >= ADC_FIR_DATA_LENGTH)
		{
			dac_phase = 0;
		}
				HAL_DAC_SetValue(&hdac1,DAC_CHANNEL_2,DAC_ALIGN_12B_R,audio_out_dac[dac_phase]);//通道2输出
		dac_phase++;	

		if(sin_phase >= SIN_ROM_LENGTH)
		{
			sin_phase = 0;
		}
		HAL_DAC_SetValue(&hdac1,DAC_CHANNEL_1,DAC_ALIGN_12B_R,sin_25_rom[sin_phase]);//通道1输出
		sin_phase++;
}

7. 中断优先级

需要注意中断优先级,否则DAC输出波形会出错。DAC优先级必须最高。

DAC(TIM6)>DMA/ADC>TIM1.

8. PA5管脚输出

下载编译好的程序到开发板。

示波器CH1是开发板PA5管脚输出的解调后的1KHz波形,CH2是经过板上硬件滤波器后的1KHz。

可以看到解调后输出波形(CH1)很多毛刺,通过模拟滤波器后,波形得到改善(CH2)。

在下一节中,我们将在MCU里给解调、抽取之后的数据使用Fir数字滤波器,这样DAC发出波形的时候就会得到改善。

通过喇叭可以听到声音。

4.4 FIR滤波器设计

**本例中是在前面3.3.3例程基础上,添加FIR滤波器实现,设计带通滤波器,高截止频率为3kHz,低截止频率为100Hz。将滤波后的信号通过PA5管脚输出。通过示波器观察处理后DAC管脚PA5波形. **

1. 硬件连接同4.3.1节

2. 滤波器介绍

线性时不变的数字滤波器包括无限长脉冲响应滤波器(IIR滤波器)和有限长脉冲响应滤波器(FIR滤波器)两种。这两种滤波器的系统函数可以统一以Z变换表示为:

当M≥1时,M就是IIR滤波器阶数,表示系统反馈环的个数,由于反馈的存在,IIR滤波器的脉冲响应为无限长,因此得名,若A(z) = 1,则系统的脉冲响应的长度为N+1,故称为FIR滤波器。

IIR滤波器的优点在于,其设计可以直接利用模拟滤波器设计的成果,因为模拟滤波器本身就是无限长冲激响应的。通常IIR滤波器设计的过程如下:首先根据滤波器参数要求设计对应的模拟滤波器(如巴特沃斯滤波器、切比雪夫滤波器等等),然后通过映射(如脉冲响应不变法、双线性映射等等)将模拟滤波器变换为数字滤波器,从而决定IIR滤波器的参数。IIR滤波器的重大缺点在于,由于存在反馈其稳定性不能得到保证。另外,反馈还使IIR滤波器的数字运算可能溢出。

FIR滤波器最重要的优点就是由于不存在系统极点,FIR滤波器是绝对稳定的系统。FIR滤波器还确保了线性相位,这在信号处理中也非常重要。此外,由于不需要反馈,FIR滤波器的实现也比IIR滤波器简单。FIR滤波器的缺点在于它的性能不如同样阶数的IIR滤波器,不过由于数字计算硬件的飞速发展,这一点已经不成为问题。再加上引入计算机辅助设计,FIR滤波器的设计也得到极大的简化。基于上述原因,FIR滤波器比IIR滤波器的应用更广。

具体DSP滤波器介绍,可以回顾DSP实验 “实验十三 DSP滤波器基础知识”, “实验十四 DSP FIR有限冲击滤波器设计”, “实验十五 DSP FIR滤波器的matlab设计(低通,高通,带通,带阻)”。

3. Matlab生成FIR滤波器系数

下面我们讲解下如何通过Matlab的filterDesigner工具生成C头文件,也就是生成滤波器系数。打开应用程序“Filter Design & Analysis”,

**filterDesigner界面打开效果如下: **

这里重点介绍设置后相应参数后如何生成滤波器系数。

**降采样率后进入FIR滤波器的采样率为25KHz,现设计一个带通滤波器,高截止频率3000Hz,低截止频率100Hz,采用函数fir1进行设计(注意这个函数是基于窗口的方法设计FIR滤波,默认是hamming窗),滤波器阶数设置为28。filterDesigner的配置如下: **

点击Design Filter按钮以后就生成了所需的滤波器系数,生成滤波器系数以后点击filterDesigner界面上的菜单Targets->Generate C header** ,选择单精度浮点输出,打开后显示如下界面: **

然后点击Generate,生成如下界面:

再点击保存,并打开fir_bandpass.h文件,可以看到生成的系数,一共有29个系数:

/*
 * Discrete-Time FIR Filter (real)
 * -------------------------------
 * Filter Structure  : Direct-Form FIR
 * Filter Length     : 29
 * Stable            : Yes
 * Linear Phase      : Yes (Type 1)
 */

const int BL = 29;
const real32_T B[29] = {
-0.002292175079,-0.001557604875,0.0002387679269, 0.003326080972, 0.005709242076,
   0.003126602387,-0.007875042036,  -0.0252452381, -0.03892513365, -0.03386418894,
   0.001657098066,  0.06692832708,   0.1452077776,   0.2092580795,   0.2339902967,  0.2092580795,   0.1452077776,  0.06692832708, 0.001657098066, -0.03386418894,  -0.03892513365,  -0.0252452381,-0.007875042036, 0.003126602387, 0.005709242076, 0.003326080972,0.0002387679269,-0.001557604875,-0.002292175079
};

4. KEIL添加库文件

因需要用到DSP库,在工程中添加如下源文件。

浮点运算使能

添加DSP预定义结构

添加文件路径

5. 编写代码

在main中初始化接口,配置芯片,while循环中处理数据。

在前面工程基础上添加滤波器初始化函数****arm_fir_f32_bp_init();

在while循环中添加fir滤波函数****arm_fir_f32_bp。

数据达到12500个后,处理前部分12500数据的fir滤波。

数据达到25000个后,处理后部分12500数据的fir滤波。

int main(void)
{
uint32_t i=0;
float iq_temp=0;//临时存储
uint32_t dac2_start_flag=0;//第一次dac开启标志  HAL_Init();
  SystemClock_Config();
PeriphCommonClock_Config();
  MX_GPIO_Init();
  MX_TIM2_Init();
  MX_I2C2_Init();
  MX_DAC1_Init();
  MX_TIM6_Init();
  MX_DMA_Init();
  MX_ADC1_Init();
  MX_ADC2_Init();
  MX_TIM1_Init();
  MX_SPI4_Init();
MX_UART4_Init(); 
HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_1);//tim2开启pwm,输出24Mhz
	for(i=0;i< SIN_ROM_LENGTH;i++)//生成sin表
	{
		sin_25_rom[i] = (uint16_t)(sin(2*3.14*i/(SIN_ROM_LENGTH))*1000 +2047);
	}
arm_fir_f32_bp_init();//带通滤波器初始化
HAL_TIM_Base_Start_IT(&htim6);//tim6开启
HAL_DAC_Start(&hdac1,DAC_CHANNEL_1);//dac1的通道1开启
Qn8027_Init();	//qn8027初始化
Msi001_Init();//msi初始化

HAL_Delay(100);
HAL_TIM_Base_Start_IT(&htim1);//tim1开启
HAL_ADCEx_MultiModeStart_DMA(&hadc1,g_adc1_dma_data1,ADC_DATA_LENGTH);//ADC的dma开始采集
  while (1)
  {
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			if(g_adc1_dma_complete_flag==1)//采集完前12500个数据后,进入这个部分
			{
arm_fir_f32_bp(&iq_fir_in[0],&iq_fir_out[0]);//fir滤波器
				for(i=0;i< ADC_FIR_DATA_LENGTH/2;i++)//将fir滤波器输出值幅度缩小范围,再将直流偏置调整到1.65v,再计算出DAC对应的码值
				{					
					iq_fir_out[i] = iq_fir_in[i]*0.9;
					iq_temp = iq_fir_out[i]+1.65;
					iq_temp = iq_temp/3.3;
					iq_temp = iq_temp * 4095;
					audio_out_dac[i] = ((uint16_t)iq_temp)&0x0fff;
				}

				if(dac2_start_flag ==0)	//第一次进入dac开启,只需第一次开启
				{					
					HAL_DAC_Start(&hdac1,DAC_CHANNEL_2);//dac1的通道2开启
					dac_phase=0;
					dac2_start_flag=1;
				}
				g_adc1_dma_complete_flag=0;
			}
			///////////////////////////////////////////////////////////////////////////////////////////////////////////////
			
			if(g_adc1_dma_complete_flag==2)//采集完后12500个数据后,进入这个部分
			{
			arm_fir_f32_bp(&iq_fir_in[ADC_FIR_DATA_LENGTH/2],&iq_fir_out[ADC_FIR_DATA_LENGTH/2]);//fir滤波器
					for(i=ADC_FIR_DATA_LENGTH/2;i< ADC_FIR_DATA_LENGTH;i++)//将fir滤波器输出值幅度缩小范围,再将直流偏置调整到1.65v,再计算出DAC对应的码值
				{					
					iq_fir_out[i] = iq_fir_in[i]*0.9;
					iq_temp = iq_fir_out[i]+1.65;
					iq_temp = iq_temp/3.3;
					iq_temp = iq_temp * 4095;
					audio_out_dac[i] = ((uint16_t)iq_temp)&0x0fff;
				}
				g_adc1_dma_complete_flag=0;
			}  }
}

6. FIR实现

FIR滤波实现先初始化滤波参数,在进行数据滤波。

具体FIR滤波器实现讲解,可以回顾DSP实验 “实验十六 STM32H7的FIR低通滤波器实现”, “实验十七 STM32H7的FIR高通滤波器实现”。

实现代码:

#define TEST_LENGTH_SAMPLES  12500    /* 采样点数 */
#define BLOCK_SIZE           1    	 /* 调用一次arm_fir_f32处理的采样点个数 */
#define NUM_TAPS             29      /* 滤波器系数个数 */

uint32_t blockSize = BLOCK_SIZE;
uint32_t numBlocks = TEST_LENGTH_SAMPLES/BLOCK_SIZE;            /* 需要调用arm_fir_f32的次数 */

//static float32_t testInput_f32_50Hz_200Hz[TEST_LENGTH_SAMPLES]; /* 采样点 */
//static float32_t testOutput[TEST_LENGTH_SAMPLES];               /* 滤波后的输出 */
static float32_t firStateF32[BLOCK_SIZE + NUM_TAPS - 1];        /* 状态缓存,大小numTaps + blockSize - 1*/


/* 滤波器系数 通过fadtool获取*/
const float32_t firCoeffs32BP[NUM_TAPS] = {
	 -0.002292175079,-0.001557604875,0.0002387679269, 0.003326080972, 0.005709242076,
   0.003126602387,-0.007875042036,  -0.0252452381, -0.03892513365, -0.03386418894,
   0.001657098066,  0.06692832708,   0.1452077776,   0.2092580795,   0.2339902967,
     0.2092580795,   0.1452077776,  0.06692832708, 0.001657098066, -0.03386418894,
   -0.03892513365,  -0.0252452381,-0.007875042036, 0.003126602387, 0.005709242076,
   0.003326080972,0.0002387679269,-0.001557604875,-0.002292175079
};

/*
*********************************************************************************************************
*	函 数 名: arm_fir_f32_bp_ini
*	功能说明: 调用函数arm_fir_init_f32初始化S
*	形    参:无
*	返 回 值: 无
*********************************************************************************************************
*/

arm_fir_instance_f32 S;//初始化结构值,算fir时需要用到

void arm_fir_f32_bp_init(void)
{
	/* 初始化结构体S */
	arm_fir_init_f32(&S,                            
					 NUM_TAPS, 
	                (float32_t *)&firCoeffs32BP[0], 
	                 &firStateF32[0], 
	                 blockSize);
}

/*
*********************************************************************************************************
*	函 数 名: arm_fir_f32_bp
*	功能说明: 调用函数arm_fir_f32_bp实现带通滤波器
*	形    参:无
*	返 回 值: 无
********************************************************************************************************
*/
void arm_fir_f32_bp(float32_t  *inputF32,float32_t *outputF32)
{
	uint32_t i;

	HAL_GPIO_WritePin(GPIOD,GPIO_PIN_3,GPIO_PIN_SET);
	/* 实现FIR滤波,这里每次处理1个点 */
	for(i=0; i < numBlocks; i++)
	{				
		arm_fir_f32(&S, inputF32 + (i * blockSize),  outputF32 + (i * blockSize),  blockSize);
	}
	HAL_GPIO_WritePin(GPIOD,GPIO_PIN_3,GPIO_PIN_RESET);

}

7. PA5管脚输出

下载编译好的程序到开发板。

示波器CH1是开发板PA5管脚输出波形,CH2是PB1管脚波形。

可以看到FIR滤波输出波形CH1是个很干净的正弦波(台阶为采样率,可以数出一个周期25个点),频率为1kHz。CH2是通过硬件滤波器后的波形。

对比3.3.3节的波形,DAC输出的噪声明显变小。

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 示波器
    +关注

    关注

    111

    文章

    5664

    浏览量

    181827
  • RCC
    RCC
    +关注

    关注

    0

    文章

    91

    浏览量

    26706
  • 上位机
    +关注

    关注

    26

    文章

    861

    浏览量

    54057
  • PCB焊接
    +关注

    关注

    0

    文章

    13

    浏览量

    7665
  • 调试器
    +关注

    关注

    1

    文章

    288

    浏览量

    23416
收藏 人收藏

    评论

    相关推荐

    使用STM32调试FMSDR模块解调FM电台(1)

    示波器:本实验我们使用硬木课堂的口袋仪器产品进行测量
    的头像 发表于 10-13 15:52 2285次阅读
    使用<b class='flag-5'>STM32</b><b class='flag-5'>调试</b><b class='flag-5'>FMSDR</b><b class='flag-5'>模块</b>及<b class='flag-5'>解调</b><b class='flag-5'>FM</b><b class='flag-5'>电台</b>(1)

    使用STM32调试FMSDR模块解调FM电台(3)

    QN8027芯片需要输入24MHz的时钟作为参考信号,在这里通过STM32H750的TIMER2产生24M的方波,提供给QN8027作为输入参考信号。
    的头像 发表于 10-13 16:04 4139次阅读
    使用<b class='flag-5'>STM32</b><b class='flag-5'>调试</b><b class='flag-5'>FMSDR</b><b class='flag-5'>模块</b>及<b class='flag-5'>解调</b><b class='flag-5'>FM</b><b class='flag-5'>电台</b>(3)

    教你最简单做一个随身FM电台!X各种使用方法#电台

    电台FM
    Dimsmary
    发布于 :2022年07月12日 14:29:14

    FM解调器电路

    几种FM解调器电路
    发表于 04-21 11:49 1779次阅读
    <b class='flag-5'>FM</b><b class='flag-5'>解调</b>器电路

    FM解调器A电路

    FM解调器A电路
    发表于 03-21 19:00 765次阅读
    <b class='flag-5'>FM</b><b class='flag-5'>解调</b>器A电路

    FM解调器B电路

    FM解调器B电路
    发表于 03-21 19:01 729次阅读
    <b class='flag-5'>FM</b><b class='flag-5'>解调</b>器B电路

    FM解调器C电路

    FM解调器C电路
    发表于 03-21 19:02 954次阅读
    <b class='flag-5'>FM</b><b class='flag-5'>解调</b>器C电路

    线性FM解调器电路

    线性FM解调器电路
    发表于 03-21 19:14 909次阅读
    线性<b class='flag-5'>FM</b><b class='flag-5'>解调</b>器电路

    FM解调电路图

    FM解调电路图
    发表于 07-15 16:47 983次阅读
    <b class='flag-5'>FM</b><b class='flag-5'>解调</b>电路图

    FM解调

    FM解调器 LM311
    发表于 09-15 10:28 1423次阅读
    <b class='flag-5'>FM</b><b class='flag-5'>解调</b>器

    455KHz FM解调

    455KHz FM解调
    发表于 09-15 11:04 1055次阅读
    455KHz <b class='flag-5'>FM</b><b class='flag-5'>解调</b>器

    使用STM32调试FMSDR模块解调FM电台(2)

    当我们使用FPGA或者STM32模块配合FMSDR模块使用的时候,需要从零开始调试这个电路,逐步完成:硬件好坏判断、8027和MSI001寄
    的头像 发表于 10-13 15:56 3316次阅读
    使用<b class='flag-5'>STM32</b><b class='flag-5'>调试</b><b class='flag-5'>FMSDR</b><b class='flag-5'>模块</b>及<b class='flag-5'>解调</b><b class='flag-5'>FM</b><b class='flag-5'>电台</b>(2)

    使用STM32调试FMSDR模块解调FM电台(1)

    当我们使用FPGA或者STM32模块配合FMSDR模块使用的时候,需要从零开始调试这个电路,逐步完成:硬件好坏判断、8027和MSI001寄
    的头像 发表于 07-06 10:50 1315次阅读
    使用<b class='flag-5'>STM32</b><b class='flag-5'>调试</b><b class='flag-5'>FMSDR</b><b class='flag-5'>模块</b>及<b class='flag-5'>解调</b><b class='flag-5'>FM</b><b class='flag-5'>电台</b>(1)

    使用STM32调试FMSDR模块解调FM电台(2)

    本文中所有例子中我们都仅给MSI001使用天线,因为QN8027离得很近,发射端不需要使用天线
    的头像 发表于 07-06 10:54 900次阅读
    使用<b class='flag-5'>STM32</b><b class='flag-5'>调试</b><b class='flag-5'>FMSDR</b><b class='flag-5'>模块</b>及<b class='flag-5'>解调</b><b class='flag-5'>FM</b><b class='flag-5'>电台</b>(2)

    使用STM32调试FMSDR模块解调FM电台(4)

    本实验是在配置MSI001的频点为101.7Mhz已知交通广播电台,观察喇叭是否能听到交通广播声音,再调整MSI001的输出幅度。
    的头像 发表于 07-06 11:05 623次阅读
    使用<b class='flag-5'>STM32</b><b class='flag-5'>调试</b><b class='flag-5'>FMSDR</b><b class='flag-5'>模块</b>及<b class='flag-5'>解调</b><b class='flag-5'>FM</b><b class='flag-5'>电台</b>(4)