电子说
4.1 输出24Mhz和验证SPI接口
硬件连接
本例中我们添加MSI001相关的引脚也连接到STM32H750开发板。程序中操作的管脚如下描述:
MSI001芯片需要输入24MHz的时钟作为参考信号,在这里使用专门的时钟产生单元RCC产生24M的方波,提供给MSI001作为输入参考信号。
使能Master clock output1后,配置PLL1Q输出为48M,MCO1选择时钟源为PLL1Q,经过2分频后,得到24M时钟。
RCC产生24Mhz时钟单元STM32CUBE配置如下:
芯片的控制接口是SPI协议,要使芯片正常工作,首先SPI接口的操作要正常。这里向MSI001芯片配置频率为98.5Mhz,观察配置前MSI001和配置后差分输出管脚的波形变化。如果发生变化,说明SPI操作正常,芯片可以被控。这样进行后续调试才有初步把握。
需要配置STM32H750的硬件SPI,然后发出控制字操作MSI001芯片,确认板卡和芯片正常工作。SPI工作速度设为3.75Mhz.
在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;
}
如果方便,可以使用示波器测试stm32开发板的PA8(RCC_MCO_1)管脚,观测有无24M的波形输出。
在前面程序中配置QN8027输出的单音FM信号在98.5M上,下面我们把MSI001的接收频点也配在98.5M,通过示波器查看MSI001芯片的IQ输出的波形。
在main.c中,我们调用了SPI.c中的程序对SPI4进行初始化,配置SPI的时钟,相位等;
在MSI001.c中,我们尝试写寄存器,使用示波器观察MSI001的反应:
如果IQ输出能够跟随我们写入的寄存器动作,这说明SPI时序正确,硬件也是好的,这时我们就可以进行下一步操作了。
注意,SPI时序写入这一步看上去虽然简单,却也是最经常出问题的步骤。如果遇到MSI001没有反应,建议用如下方法排查:
如果没有示波器,可以使用STM32内部ADC采集后通过UART传到上位机观察波形,请查看下一节内容。
本例中我们使用CMSIS-DAP上自带的UART2USB功能,把ADC采集到的数据发到电脑,通过UARTPlot软件观察采集的波形。程序中操作的管脚如下描述:
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用于发送数据到上位机,可以回顾基础实验 “实验十六 串口通信”
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 */
}
使用串口数据接收软件“pyserial_display.exe”的步骤:
UartPlot的使用注意事项:
一帧数据显示
按住鼠标左键,可以上下左右移动波形,按住鼠标右键,可以放大或者缩小X轴或Y轴:
点击CH1或者CH2可以关闭或打开指定通道的波形显示
如果鼠标不能用,在波形显示界面点击鼠标右键,可以选择是否在X轴或Y轴启用鼠标:
在Plot Options里,可以对波形做FFT等处理
在Export里可以导出波形数据为JPG,或EXCEL文件
求解频率,FM解调
在利用相位差分计算瞬时频率f(n)时,由于计算相位要用到除法和反正切运算,这对于非专用数字处理器来说是较复杂的,在用软件实现时,也可用下面的方法来计算瞬时频率f(n)
**对于FM信号,其振幅近似恒定,可以设定 **,则
这就是利用XI(n)和XQ(n)计算f(n)的近似公式。这种方法只有乘法和减法,计算简便。也是开发板例程中用到的方法。
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);
CH1是I通道波形,CH2是解调后的波形。
解调后波形(红色)可以看出1KHz的成分,需要后续进行滤波处理。
程序中操作的管脚如下描述:
STM32和FPGA不同的地方在于,MCU处理数据流的能力较弱,对于500KSPS的连续FIR滤波软件开销较大,因此在STM32程序中我们先抽取降速后再进行FIR滤波(下一节介绍),降低系统的运算量。
本例中我们要实现的功能为:定时器TIM1输出TRG信号触发500KSPS采样率的ADC1和ADC2,实现DMA双缓冲采集,进行实时采集->解调->每20个数据平均为1个数据->通过TIM6触发的刷新率为25KSPS的DAC发出。最后通过示波器观察DAC管脚波形。
定时器触发ADC做DMA双缓冲数据传输的实现思路是:
实现思路框图如下:
具体ADC同步DMA采集介绍,可以回顾基础实验 “实验二十四 ADC定时器触发配合DMA双缓冲实现实时采集”:
在while循环中,一直进行标志位判断,标志位是1时,处理前半部分12500个数据处理,标志位是2时,处理后半部分12500个数据处理。完成回调函数和半完成回调函数都是通过DMA1_Stream0_IRQHandler中断进入。
在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;
}
//////////////////////////////////////////////