4. 使用MSI001解調(diào)8027發(fā)出的已知單音信號
4.1 輸出24Mhz和驗證SPI接口
-
硬件連接
本例中我們添加MSI001相關(guān)的引腳也連接到STM32H750開發(fā)板。程序中操作的管腳如下描述:
2. RCC時鐘輸出24MHz驅(qū)動Msi001
MSI001芯片需要輸入24MHz的時鐘作為參考信號,在這里使用專門的時鐘產(chǎn)生單元RCC產(chǎn)生24M的方波,提供給MSI001作為輸入?yún)⒖夹盘枴?/strong>
使能Master clock output1后,配置PLL1Q輸出為48M,MCO1選擇時鐘源為PLL1Q,經(jīng)過2分頻后,得到24M時鐘。
RCC產(chǎn)生24Mhz時鐘單元STM32CUBE配置如下:
3. 硬件SPI接口配置
芯片的控制接口是SPI協(xié)議,要使芯片正常工作,首先SPI接口的操作要正常。這里向MSI001芯片配置頻率為98.5Mhz,觀察配置前MSI001和配置后差分輸出管腳的波形變化。如果發(fā)生變化,說明SPI操作正常,芯片可以被控。這樣進行后續(xù)調(diào)試才有初步把握。
需要配置STM32H750的硬件SPI,然后發(fā)出控制字操作MSI001芯片,確認板卡和芯片正常工作。SPI工作速度設(shè)為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驅(qū)動代碼
#include "msi001/msi001.h"
SPI_HandleTypeDef *msi001_spi = &hspi4; ///
uint32_t g_msi001_reg[7]={0};//msi寄存器配置
//msi001的spi發(fā)送三個字節(jié),
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,改變寄存器參數(shù)配置不同頻率
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開發(fā)板的PA8(RCC_MCO_1)管腳,觀測有無24M的波形輸出。
6. MSI001寫測試
在前面程序中配置QN8027輸出的單音FM信號在98.5M上,下面我們把MSI001的接收頻點也配在98.5M,通過示波器查看MSI001芯片的IQ輸出的波形。
在main.c中,我們調(diào)用了SPI.c中的程序?qū)PI4進行初始化,配置SPI的時鐘,相位等;
在MSI001.c中,我們嘗試寫寄存器,使用示波器觀察MSI001的反應(yīng):
- 在keil中用debug單步調(diào)試,復(fù)位后,打斷點運行到初始化MSI001芯片前。
- 運行到下一行,配置寄存器0為0x143420后,示波器的表現(xiàn)如下:
- 再運行一行,配置寄存器0為0x243420后,示波器的表現(xiàn)如下:
- 再運行一行,配置寄存器0為0x043420后,示波器的表現(xiàn)如下:
如果IQ輸出能夠跟隨我們寫入的寄存器動作,這說明SPI時序正確,硬件也是好的,這時我們就可以進行下一步操作了。
注意,SPI時序?qū)懭脒@一步看上去雖然簡單,卻也是最經(jīng)常出問題的步驟。如果遇到MSI001沒有反應(yīng),建議用如下方法排查:
- 電源測試:MSI001供電是否正常;
- IO通斷測試:使用IO輸出高低電平,通過測量確定PCB焊接正確,且插對了孔位;
- SPI時序測試:使用示波器或邏輯分析儀捕獲發(fā)出的SPI時序,判斷是否SPI配置寄存器有錯誤;FPGA寫的SPI程序,則要特別留意是否有代碼bug。
- 如果管腳上的SPI時序正確,但MSI001如果沒有應(yīng)答,觀察是否有虛焊等情況(開發(fā)板發(fā)貨前經(jīng)過測試,基本上可以排除電源和8027的焊接問題)
- 為減少MSI001死掉的幾率,使能STM32或FPGA管腳內(nèi)部的下拉或上拉電阻,SPI時序正常的情況下沒有反應(yīng),可以全板掉電重啟試試。
如果沒有示波器,可以使用STM32內(nèi)部ADC采集后通過UART傳到上位機觀察波形,請查看下一節(jié)內(nèi)容。
4.2 ADC采集和UARTPlot
1. 硬件連接
本例中我們使用CMSIS-DAP上自帶的UART2USB功能,把ADC采集到的數(shù)據(jù)發(fā)到電腦,通過UARTPlot軟件觀察采集的波形。程序中操作的管腳如下描述:
2. 配置ADC1/2同步差分輸入DMA采集
STM32處理數(shù)據(jù)流的能力遠不如FPGA,要實現(xiàn)實時信號處理,在STM32中我們需要使用雙緩沖的方法,即準備兩個數(shù)據(jù)段,采集A段數(shù)據(jù)的時候處理B數(shù)據(jù)段,采集B數(shù)據(jù)段的時候處理A數(shù)據(jù)段,這樣才能確保波形的連續(xù)實時處理。
本例中我們要實現(xiàn)的功能是定時器TIM1輸出TRG信號觸發(fā)ADC1和ADC2,實現(xiàn)500KSPS采樣率的DMA雙緩沖采集,采集完后通過UART發(fā)送IQ數(shù)據(jù)給上位機,通過上位機軟件觀察波形。
實現(xiàn)思路如下:將DMA采集輸出長度設(shè)為2000個點,采集完前半部分1000個點后,進入半回調(diào)函數(shù)HAL_ADC_ConvHalfCpltCallback中,標志位置1;后半部分1000個點采集完成后,進入HAL_ADC_ConvCpltCallback,標志位置2,停止DMA采集;在主程序中如果標志位等于2,就將IQ數(shù)據(jù)發(fā)送到PC上位機顯示,再開啟DMA進行下一幀數(shù)據(jù)采集。這樣確保我們看到的波形正確后,就可以進入下一節(jié),進行后面的FM解調(diào)處理。
實現(xiàn)思路框圖如下:
在while循環(huán)中,一直進行標志位判斷,標志位是2時,發(fā)送數(shù)據(jù)。回調(diào)函數(shù)和半回調(diào)函數(shù)都是通過DMA1_Stream0_IRQHandler中斷進入。
具體ADC同步DMA采集介紹,可以回顧基礎(chǔ)實驗 “實驗二十四 ADC定時器觸發(fā)配合DMA雙緩沖實現(xiàn)實時采集”
在程序中,添加了串口打印函數(shù)printf用于發(fā)送數(shù)據(jù)到上位機,可以回顧基礎(chǔ)實驗 “實驗十六 串口通信”
3. 程序解讀
while (1)
{
if(g_adc1_dma_complete_flag == 2) //采集數(shù)據(jù)完成
{
for(i=0;i< ADC_DATA_LENGTH;i++)
{
adc1_I_voltage[i] = ((float)3.3*((g_adc1_dma_data1[i])&0x0000ffff))/65536; //轉(zhuǎn)換碼值為電壓值
adc2_Q_voltage[i] = ((float)3.3*((g_adc1_dma_data1[i] >>16)&0x0000ffff))/65536; //轉(zhuǎn)換碼值為電壓值
}
//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將波形數(shù)據(jù)發(fā)送給UARTPlot (pyserial_display) 軟件
使用串口數(shù)據(jù)接收軟件“pyserial_display.exe”的步驟:
- 選擇CMSIS-DAP對應(yīng)的串口號
- 設(shè)定串口波特率115200
- 數(shù)據(jù)位8,校驗位N,停止位1
- 波形長度2000,浮點類型,雙通道模式
- CH1是I通道波形,CH2是Q通道波形。
- 點擊開始采集,等待下位機數(shù)據(jù)。
UartPlot的使用注意事項:
- 確保上位機設(shè)置的UART參數(shù)(波特率、數(shù)據(jù)位、校驗位、停止位)與大拇指開發(fā)板中的程序設(shè)定一致。
- 檢查波形長度,通道數(shù),顯示數(shù)據(jù)格式是否和大拇指開發(fā)板中程序一致;
- 由于本軟件沒有使用幀頭等傳輸協(xié)議,在使用軟件時, 先在UARTPlot 上位機界面上點擊開始采集,然后在大拇指開發(fā)板上啟動數(shù)據(jù)傳輸 ,確保上位機軟件捕獲到數(shù)組的起始點,如果沒有遵循上述啟動流程,會出現(xiàn)波形截斷現(xiàn)象。停止上位機并重復(fù)上述流程即可修復(fù)。
- H750例程使用的是板載DAP調(diào)試器的UART2USB功能,波特率設(shè)定為115200,上位機界面選擇USB串行設(shè)備。注意:DAP調(diào)試器在Debug模式下同時使用UART2USB功能傳輸數(shù)據(jù)可能導致調(diào)試器死機(死機后表現(xiàn)為DAP連不上芯片,Keil報No Debug Device Found),此時按住H750板上的BOOT0按鈕不放,重新拔插USB后下載已知可運行程序可以解決。
一幀數(shù)據(jù)顯示
按住鼠標左鍵,可以上下左右移動波形,按住鼠標右鍵,可以放大或者縮小X軸或Y軸:
點擊CH1或者CH2可以關(guān)閉或打開指定通道的波形顯示
如果鼠標不能用,在波形顯示界面點擊鼠標右鍵,可以選擇是否在X軸或Y軸啟用鼠標:
在Plot Options里,可以對波形做FFT等處理
在Export里可以導出波形數(shù)據(jù)為JPG,或EXCEL文件
4.3 FM解調(diào)算法
1. FM解調(diào)算法回顧
求解頻率,F(xiàn)M解調(diào)
在利用相位差分計算瞬時頻率f(n)時,由于計算相位要用到除法和反正切運算,這對于非專用數(shù)字處理器來說是較復(fù)雜的,在用軟件實現(xiàn)時,也可用下面的方法來計算瞬時頻率f(n)
**對于FM信號,其振幅近似恒定,可以設(shè)定 **,則
這就是利用XI(n)和XQ(n)計算f(n)的近似公式。這種方法只有乘法和減法,計算簡便。也是開發(fā)板例程中用到的方法。
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解調(diào)測試
CH1是I通道波形,CH2是解調(diào)后的波形。
解調(diào)后波形(紅色)可以看出1KHz的成分,需要后續(xù)進行濾波處理。
4.3 實時信號抽取和DAC輸出
1. 硬件連接
程序中操作的管腳如下描述:
2. 實時信號采集和500K信號抽取
STM32和FPGA不同的地方在于,MCU處理數(shù)據(jù)流的能力較弱,對于500KSPS的連續(xù)FIR濾波軟件開銷較大,因此在STM32程序中我們先抽取降速后再進行FIR濾波(下一節(jié)介紹),降低系統(tǒng)的運算量。
本例中我們要實現(xiàn)的功能為:定時器TIM1輸出TRG信號觸發(fā)500KSPS采樣率的ADC1和ADC2,實現(xiàn)DMA雙緩沖采集,進行實時采集->解調(diào)->每20個數(shù)據(jù)平均為1個數(shù)據(jù)->通過TIM6觸發(fā)的刷新率為25KSPS的DAC發(fā)出。最后通過示波器觀察DAC管腳波形。
定時器觸發(fā)ADC做DMA雙緩沖數(shù)據(jù)傳輸?shù)膶崿F(xiàn)思路是:
- 將DMA采集輸出長度設(shè)為2000個點,DMA配置為循環(huán)模式一直進行自動采集;
- 采集完成前1000個點,調(diào)用半回調(diào)函數(shù)HAL_ADC_ConvHalfCpltCallback,進行解調(diào),20次平均,如果平均后數(shù)據(jù)達到12500個后,標志位置1,while(1)中進行前半段12500個數(shù)據(jù)處理(DAC幅度變換,更新DAC數(shù)組);
- 采集完成后1000個點,調(diào)用回調(diào)函數(shù)HAL_ADC_ConvCpltCallback,進行解調(diào),20次平均,平均后數(shù)據(jù)如果達到25000個后,標志位置2,在While(1)中進行后半段12500個數(shù)據(jù)處理(DAC幅度變換,更新DAC數(shù)組)。
實現(xiàn)思路框圖如下:
具體ADC同步DMA采集介紹,可以回顧基礎(chǔ)實驗 “實驗二十四 ADC定時器觸發(fā)配合DMA雙緩沖實現(xiàn)實時采集”:
3. While循環(huán)處理
在while循環(huán)中,一直進行標志位判斷,標志位是1時,處理前半部分12500個數(shù)據(jù)處理,標志位是2時,處理后半部分12500個數(shù)據(jù)處理。完成回調(diào)函數(shù)和半完成回調(diào)函數(shù)都是通過DMA1_Stream0_IRQHandler中斷進入。
4. 編寫代碼
在main中初始化接口,配置芯片,while循環(huán)中處理數(shù)據(jù)
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個數(shù)據(jù)后,進入這個部分
{
for(i=0;i< ADC_FIR_DATA_LENGTH/2;i++)//將fir濾波器輸出值幅度縮小范圍,再將直流偏置調(diào)整到1.65v,再計算出DAC對應(yīng)的碼值
{
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;
}
//////////////////////////////////////////////