新聞中心

EEPW首頁 > 嵌入式系統(tǒng) > 設(shè)計應(yīng)用 > 實現(xiàn)stm32在FSK調(diào)制解調(diào)器的綜合設(shè)計

實現(xiàn)stm32在FSK調(diào)制解調(diào)器的綜合設(shè)計

作者: 時間:2016-11-13 來源:網(wǎng)絡(luò) 收藏
大致要求:設(shè)計一個FSK調(diào)制解調(diào)器,基帶信號碼速率為2000B/s,載波速率為4khz和8khz,解調(diào)信號要能完整還原基帶信號。實現(xiàn)方法多種多樣,通信領(lǐng)域內(nèi)調(diào)制解調(diào)器的設(shè)計大多數(shù)用的都是硬件電路,鑒于筆者對編程情有獨鐘(其實筆者還是懂一點電路設(shè)計知識的~),所以最終決定用stm32來設(shè)計,純編程實現(xiàn)??雌饋砀叽笊?,但實際做起來不難,不過有挺多東西要考慮的。

總的設(shè)計思路如下:

本文引用地址:http://butianyuan.cn/article/201611/316111.htm

首先是基帶信號的產(chǎn)生,它也是我們要調(diào)制和解調(diào)的目標(biāo)?;鶐盘栍梢贿B串隨機的碼元序列構(gòu)成,為了模擬隨機的碼元序列,筆者用定時器設(shè)計8位的PN碼序列,碼元速率為2000B/s。定時器3定時0.5ms,每進(jìn)入一次中斷,變量num加一,設(shè)置一次IO引腳電平,8位PN碼只需設(shè)置8次,然后num清零。

TIM3_Init(499,71); //基帶信號
u8 num=0;
void TIM3_IRQHandler(void)
{
if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET)
{
num++;
switch (num)
{
case 1: Base_Signal = 1; break;
case 2: Base_Signal = 0; break;
case 3: Base_Signal = 0; break;
case 4: Base_Signal = 0; break;
case 5: Base_Signal = 1; break;
case 6: Base_Signal = 0; break;
case 7: Base_Signal = 1; break;
case 8: Base_Signal = 0; break; //pn碼序列
}
if(num == 8)
num = 0;
TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
}
}

接下來要產(chǎn)生載波,載波就是正弦波無疑。這里筆者的載波頻率要求是4khz和8khz。正弦波的產(chǎn)生用的是stm32的DMA+DAC+TIM2。正弦波的數(shù)據(jù)用正弦波數(shù)據(jù)發(fā)生器產(chǎn)生,采樣點數(shù)64,精度12位,保存在Sine12bit[]數(shù)組,但是傳送給DMA的正弦波數(shù)據(jù)不是這些原始的數(shù)據(jù),而是將這些數(shù)據(jù)進(jìn)行了進(jìn)一步的處理:

uint16_t Sine12bit[64] = {
0x7FF,0x8C8,0x98E,0xA51,0xB0F,0xBC4,0xC71,0xD12,0xDA7,0xE2E,0xEA5,0xF0D,0xF63,0xFA6,0xFD7,0xFF5
,0xFFE,0xFF5,0xFD7,0xFA6,0xF63,0xF0D,0xEA5,0xE2E,0xDA7,0xD12,0xC71,0xBC4,0xB0F,0xA51,0x98E,0x8C8
,0x7FF,0x736,0x670,0x5AD,0x4EF,0x43A,0x38D,0x2EC,0x257,0x1D0,0x159,0x0F1,0x09B,0x058,0x027,0x009
,0x000,0x009,0x027,0x058,0x09B,0x0F1,0x159,0x1D0,0x257,0x2EC,0x38D,0x43A,0x4EF,0x5AD,0x670,0x736
};
uint32_t Idx = 0;
int main(void)
{
... //省去無關(guān)代碼
for (Idx = 0; Idx < 64; Idx++)
{
Sine12bit[Idx] = Sine12bit[Idx]*8/10+500; //防止出現(xiàn)底部失真
}
... //省去無關(guān)代碼

}
為什么要這么處理呢?在講到DAC的配置時還會再提到這一點,在這里先不做解釋。經(jīng)過處理后的正弦波數(shù)據(jù)可以直接傳送到DMA通道,等TIM2的觸發(fā)時間一到,就可以依次把數(shù)據(jù)給到DAC,轉(zhuǎn)換成正弦波輸出。筆者用DAC通道2(對應(yīng)PA5引腳)輸出波形,所以需要使能和配置DMA2通道4,DMA的配置如下:
#define DAC_DHR12R2_Address 0x40007414
void DMAx_Init(void)
{
DMA_InitTypeDef DMA_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
/* DMA1 clock enable */
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA2, ENABLE);
/* GPIOA Periph clock enable */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
/* DAC Periph clock enable */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE);
/* Once the DAC channel is enabled, the corresponding GPIO pin is automatically
connected to the DAC converter. In order to avoid parasitic consumption,
the GPIO pin should be configured in analog */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;//配置為模擬輸入,抗噪聲干擾
GPIO_Init(GPIOA, &GPIO_InitStructure);
/* DMA1 channel4 configuration */
DMA_DeInit(DMA2_Channel4);
DMA_InitStructure.DMA_PeripheralBaseAddr = DAC_DHR12R2_Address;//DAC通道2的12位右對齊寄存器地址
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)&Sine12bit;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
DMA_InitStructure.DMA_BufferSize = 64;//采樣64點,故緩存大小為64
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA2_Channel4,&DMA_InitStructure);
DMA_Cmd(DMA2_Channel4, ENABLE);
}
TIM2和DAC的配置如下:
void TIM2_DAC_Init(u16 arr,u16 psc)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
DAC_InitTypeDef DAC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
TIM_TimeBaseStructure.TIM_Period = arr;
TIM_TimeBaseStructure.TIM_Prescaler = psc;
TIM_TimeBaseStructure.TIM_ClockDivision = 0x0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Down; //設(shè)為向下計數(shù)
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
TIM_SelectOutputTrigger(TIM2, TIM_TRGOSource_Update);
DAC_InitStructure.DAC_Trigger = DAC_Trigger_T2_TRGO;
DAC_InitStructure.DAC_WaveGeneration = DAC_WaveGeneration_None;
DAC_InitStructure.DAC_OutputBuffer = DAC_OutputBuffer_Enable; //使能輸出緩存
DAC_Init(DAC_Channel_2, &DAC_InitStructure);
DAC_Cmd(DAC_Channel_2, ENABLE);
DAC_DMACmd(DAC_Channel_2, ENABLE);
TIM_Cmd(TIM2, ENABLE);
}
這里筆者把兩個模塊的配置同時放在一個初始化函數(shù)里面,只是圖個方便,在官方例程里是將DAC和DMA的配置放在一起。這段代碼有兩個地方需要注意:一是TIM2計數(shù)模式設(shè)為向下計數(shù),二是使能了DAC的輸出緩存。設(shè)為向下計數(shù)是為了在兩個正弦波頻率切換時不會因為計數(shù)溢出而出現(xiàn)問題,在FSK產(chǎn)生環(huán)節(jié)里還會詳細(xì)說到這一點;使能輸出緩存是因為stm32的DAC在輸出緩存關(guān)閉時輸出阻抗太大,帶負(fù)載能力弱,在輸入捕獲時正弦波嚴(yán)重失真,故需要開啟輸出緩存,但同時也存在一個問題:使能輸出緩存后,DAC沒辦法使輸出達(dá)到0,這就使得原始正弦波的峰值數(shù)據(jù)丟失,導(dǎo)致底部失真。于是我們需要用上面的代碼對原始正弦波數(shù)據(jù)做一個處理——先乘上8除以10防止峰值超過12位精度的最大值4096(不能直接乘上0.8,因為數(shù)組存儲的數(shù)據(jù)必須是整形),然后再加上500,將正弦波數(shù)據(jù)整體抬高。
生成正弦波后自然是要把兩個正弦波組合在一起形成FSK信號,這個組合當(dāng)然不是隨意組合,是要在基帶信號的控制下進(jìn)行。代碼在主函數(shù)執(zhí)行,如下:
int main(void)
{
... //初始化代碼
while(1)
{
if(Base_Signal == 1)
{
TIM2->ARR = 140;;
}
if(Base_Signal == 0)
{
TIM2->ARR = 280;
}
}
}
while(1)循環(huán)里if語句判斷基帶信號的碼元序列,“1”對應(yīng)8khz載波,“0”對應(yīng)4khz載波。通過改變TIM2的自動重裝載寄存器(ARR)的值實現(xiàn)兩個載波的頻率切換。解釋一下這里為什么選擇140和280:采樣64個點,8khz對應(yīng)的DAC轉(zhuǎn)換速率為8000*64hz,那么TIM2就要每隔8000/64/72 000 000 = 1/140s觸發(fā)一次DAC,故TIM2的ARR值為140;同樣的,4khz對應(yīng)的ARR值為280。在這里還要注意:TIM2的計數(shù)模式應(yīng)配置為向下計數(shù)。一般例程都會把定時器配置為向上計數(shù),但用在這里會出現(xiàn)一個問題:在基帶信號由0變?yōu)?時,F(xiàn)SK信號也要相應(yīng)的從4khz正弦波跳變到8khz正弦波。我們知道向上計數(shù)模式是TIM2->CNT寄存器從0開始計數(shù),一直計到ARR的值,進(jìn)入中斷,然后重新清零,繼續(xù)計數(shù)直到又達(dá)到ARR設(shè)定的值。。。假設(shè)FSK信號在4khz正弦波時TIM2->CNT一度計數(shù)到140以上(此時ARR的值為280),突然基帶信號變?yōu)?,F(xiàn)SK信號由4khz正弦波變?yōu)?khz,ARR值被設(shè)定為140,這時候CNT寄存器將一直往上計數(shù),永遠(yuǎn)不會停止,直到溢出(ARR寄存器為16位)。實際上筆者在調(diào)試時,當(dāng)基帶信號為“1“,輸出的FSK信號為一條直線。把計數(shù)模式改為向下計數(shù),問題解決。
經(jīng)過上述一番折騰,調(diào)制總算是搞定了。
接下來就是解調(diào)。筆者用了兩次解調(diào)才把基帶信號完整復(fù)現(xiàn)出來。先來看看初步解調(diào)代碼,用的是TIM1的輸入捕獲模塊,TIM1屬于高級定時器,和通用定時器的代碼還是有些地方不一樣的,比如輸入捕獲中斷函數(shù)名為TIM1_CC_IRQHandler()。
void TIM1_Cap_Init(u16 arr,u16 psc)
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_ICInitTypeDef TIM1_ICInitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_ResetBits(GPIOA,GPIO_Pin_8);
TIM_TimeBaseStructure.TIM_Period = arr;
TIM_TimeBaseStructure.TIM_Prescaler =psc;
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure);
TIM1_ICInitStructure.TIM_Channel = TIM_Channel_1; //CC1S=01
TIM1_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
TIM1_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
TIM1_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
TIM1_ICInitStructure.TIM_ICFilter = 0x00;
TIM_ICInit(TIM1, &TIM1_ICInitStructure);
NVIC_InitStructure.NVIC_IRQChannel = TIM1_CC_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
TIM_ITConfig(TIM1,TIM_IT_CC1,ENABLE);
TIM_Cmd(TIM1,ENABLE );
}
選擇輸入捕獲是因為對于FSK信號來說,它由兩個不同頻率的正弦波組成,stm32默認(rèn)的高電平在2V以上,低電平在0.8V以下。通過測量從上升沿到下降沿這段時間,與閾值100us比較(4khz的正弦波半個周期為125us,8khz的正弦波半個周期為62.5us),大于100者碼元即為“0”,反之則為“1”。
u8 flag_falling;
int TIM1CH1_CAPTURE_VAL;
void TIM1_CC_IRQHandler(void)
{
if(flag_falling == 0) //檢測到上升沿
{
TIM_OC1PolarityConfig(TIM1,TIM_ICPolarity_Falling);//設(shè)置下一次觸發(fā)為下降沿觸發(fā)
TIM_SetCounter(TIM1,0);//清空TIM1->CCR1寄存器的值
TIM1CH1_CAPTURE_VAL = 0;//變量TIM1CH1_CAPTURE_VAL用于存儲TIM1->CCR1寄存器的值
flag_falling = 1;//置位標(biāo)志位,標(biāo)志下一次進(jìn)入中斷后檢測到下降沿
}
else //檢測到下降沿
{
TIM_OC1PolarityConfig(TIM1,TIM_ICPolarity_Rising);//設(shè)置下一次觸發(fā)為上升沿觸發(fā)
TIM1CH1_CAPTURE_VAL=TIM_GetCapture1(TIM1);//讀取TIM1->CCR1寄存器的值
flag_falling = 0;//清除標(biāo)志位,標(biāo)志下一次進(jìn)入中斷后檢測到上升沿
if(TIM1CH1_CAPTURE_VAL >= 100)//設(shè)定閾值,與TIM1CH1_CAPTURE_VAL進(jìn)行比較
{
First_jietiao = 0;
}
else
{
First_jietiao = 1;
}
}
TIM_ClearITPendingBit(TIM1, TIM_IT_CC1);
}
在這里筆者小小地偷了個懶——沒有配置TIM1的更新中斷,而只是配置了捕獲中斷。這是鑒于筆者的TIM1初始化為:
TIM1_Cap_Init(0XFFFF,71); //以1MHZ的頻率計數(shù)

看到了吧,0xFFFF,多大的數(shù)~其實也不大,只不過對于我們要捕獲的FSK信號來說它避免了更新中斷對捕獲造成的影響,也就是說當(dāng)我們捕獲到下降沿時得到的TIM1->CCR1寄存器的值就是我們想得到的時間,與計數(shù)值溢出多少次并無關(guān)系。注意:當(dāng)捕獲的波形頻率較高時可以這么做,但是如果波形頻率較低時最好使能更新中斷,在更新中斷里保存中斷次數(shù),得到的結(jié)果更準(zhǔn)確。

然而這只是我們初步解調(diào)出來的結(jié)果,由于4khz與8khz之間的過渡帶影響,最終得到的碼元序列“1”的持續(xù)時間長于碼元為“0”的持續(xù)時間,信號的碼速率不是2000B/s,所以我們需要進(jìn)行二次解調(diào)。

二次解調(diào)的關(guān)鍵在于定時器TIM5的同步作用。筆者用TIM5定時2khz,在初步解調(diào)信號的邊沿處先延時150us,然后開始同步,通過判斷初步解調(diào)信號的碼元序列,得到二次解調(diào)信號的碼元。

在TIM1中斷函數(shù)里面:

u8 a=1; //a為全局變量

if(flag_falling == 0 && a == 1)//捕獲到下降沿時開始同步(下降沿亦即初步解調(diào)信號的邊沿)
{
delay_us(150);
TIM_Cmd(TIM5, ENABLE); //只需要執(zhí)行一次
a = 0;
}
在這里為什么要延時150us呢?為何不在初步解調(diào)信號的邊沿處就開始同步呢?這是考慮到初步解調(diào)信號高電平持續(xù)的時間比低電平的長,如果不延時,則可能出現(xiàn)低電平碼元誤判。
接下來就是定時器5的中斷服務(wù)函數(shù):
void TIM5_IRQHandler(void)
{
if (TIM_GetITStatus(TIM5, TIM_IT_Update) != RESET)
{
if(First_jietiao == 1)
out_put = 1;
else
out_put = 0;
TIM_ClearITPendingBit(TIM5, TIM_IT_Update );
}
}
由于TIM5的2khz時鐘的同步作用,得到的二次解調(diào)信號能完整地復(fù)現(xiàn)基帶信號,實現(xiàn)解調(diào)。至此,整個2FSK調(diào)制解調(diào)系統(tǒng)設(shè)計完成。


評論


技術(shù)專區(qū)

關(guān)閉