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