大致要求:設(shè)計(jì)一個(gè)FSK調(diào)制解調(diào)器,基帶信號(hào)碼速率為2000B/s,載波速率為4khz和8khz,解調(diào)信號(hào)要能完整還原基帶信號(hào)。實(shí)現(xiàn)方法多種多樣,通信領(lǐng)域內(nèi)調(diào)制解調(diào)器的設(shè)計(jì)大多數(shù)用的都是硬件電路,鑒于筆者對(duì)編程情有獨(dú)鐘(其實(shí)筆者還是懂一點(diǎn)電路設(shè)計(jì)知識(shí)的~),所以最終決定用stm32來設(shè)計(jì),純編程實(shí)現(xiàn)。看起來高大上,但實(shí)際做起來不難,不過有挺多東西要考慮的。
總的設(shè)計(jì)思路如下:
首先是基帶信號(hào)的產(chǎn)生,它也是我們要調(diào)制和解調(diào)的目標(biāo)?;鶐盘?hào)由一連串隨機(jī)的碼元序列構(gòu)成,為了模擬隨機(jī)的碼元序列,筆者用定時(shí)器設(shè)計(jì)8位的PN碼序列,碼元速率為2000B/s。定時(shí)器3定時(shí)0.5ms,每進(jìn)入一次中斷,變量num加一,設(shè)置一次IO引腳電平,8位PN碼只需設(shè)置8次,然后num清零。
TIM3_Init(499,71); ? ? ? ? ? ? ? ? ? ? ? ?//基帶信號(hào)
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)生,采樣點(diǎ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的配置時(shí)還會(huì)再提到這一點(diǎn),在這里先不做解釋。經(jīng)過處理后的正弦波數(shù)據(jù)可以直接傳送到DMA通道,等TIM2的觸發(fā)時(shí)間一到,就可以依次把數(shù)據(jù)給到DAC,轉(zhuǎn)換成正弦波輸出。筆者用DAC通道2(對(duì)應(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位右對(duì)齊寄存器地址
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)&Sine12bit;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
DMA_InitStructure.DMA_BufferSize = 64;//采樣64點(diǎn),故緩存大小為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è)為向下計(jì)數(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); ? ? ? ?
}
這里筆者把兩個(gè)模塊的配置同時(shí)放在一個(gè)初始化函數(shù)里面,只是圖個(gè)方便,在官方例程里是將DAC和DMA的配置放在一起。這段代碼有兩個(gè)地方需要注意:一是TIM2計(jì)數(shù)模式設(shè)為向下計(jì)數(shù),二是使能了DAC的輸出緩存。設(shè)為向下計(jì)數(shù)是為了在兩個(gè)正弦波頻率切換時(shí)不會(huì)因?yàn)橛?jì)數(shù)溢出而出現(xiàn)問題,在FSK產(chǎn)生環(huán)節(jié)里還會(huì)詳細(xì)說到這一點(diǎn);使能輸出緩存是因?yàn)閟tm32的DAC在輸出緩存關(guān)閉時(shí)輸出阻抗太大,帶負(fù)載能力弱,在輸入捕獲時(shí)正弦波嚴(yán)重失真,故需要開啟輸出緩存,但同時(shí)也存在一個(gè)問題:使能輸出緩存后,DAC沒辦法使輸出達(dá)到0,這就使得原始正弦波的峰值數(shù)據(jù)丟失,導(dǎo)致底部失真。于是我們需要用上面的代碼對(duì)原始正弦波數(shù)據(jù)做一個(gè)處理——先乘上8除以10防止峰值超過12位精度的最大值4096(不能直接乘上0.8,因?yàn)閿?shù)組存儲(chǔ)的數(shù)據(jù)必須是整形),然后再加上500,將正弦波數(shù)據(jù)整體抬高。
? ?? ???生成正弦波后自然是要把兩個(gè)正弦波組合在一起形成FSK信號(hào),這個(gè)組合當(dāng)然不是隨意組合,是要在基帶信號(hào)的控制下進(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語(yǔ)句判斷基帶信號(hào)的碼元序列,“1”對(duì)應(yīng)8khz載波,“0”對(duì)應(yīng)4khz載波。通過改變TIM2的自動(dòng)重裝載寄存器(ARR)的值實(shí)現(xiàn)兩個(gè)載波的頻率切換。解釋一下這里為什么選擇140和280:采樣64個(gè)點(diǎn),8khz對(duì)應(yīng)的DAC轉(zhuǎn)換速率為8000*64hz,那么TIM2就要每隔8000/64/72 000 000 = 1/140s觸發(fā)一次DAC,故TIM2的ARR值為140;同樣的,4khz對(duì)應(yīng)的ARR值為280。在這里還要注意:TIM2的計(jì)數(shù)模式應(yīng)配置為向下計(jì)數(shù)。一般例程都會(huì)把定時(shí)器配置為向上計(jì)數(shù),但用在這里會(huì)出現(xiàn)一個(gè)問題:在基帶信號(hào)由0變?yōu)?時(shí),F(xiàn)SK信號(hào)也要相應(yīng)的從4khz正弦波跳變到8khz正弦波。我們知道向上計(jì)數(shù)模式是TIM2->CNT寄存器從0開始計(jì)數(shù),一直計(jì)到ARR的值,進(jìn)入中斷,然后重新清零,繼續(xù)計(jì)數(shù)直到又達(dá)到ARR設(shè)定的值。。。假設(shè)FSK信號(hào)在4khz正弦波時(shí)TIM2->CNT一度計(jì)數(shù)到140以上(此時(shí)ARR的值為280),突然基帶信號(hào)變?yōu)?,F(xiàn)SK信號(hào)由4khz正弦波變?yōu)?khz,ARR值被設(shè)定為140,這時(shí)候CNT寄存器將一直往上計(jì)數(shù),永遠(yuǎn)不會(huì)停止,直到溢出(ARR寄存器為16位)。實(shí)際上筆者在調(diào)試時(shí),當(dāng)基帶信號(hào)為“1“,輸出的FSK信號(hào)為一條直線。把計(jì)數(shù)模式改為向下計(jì)數(shù),問題解決。
? ?? ? 經(jīng)過上述一番折騰,調(diào)制總算是搞定了。
接下來就是解調(diào)。筆者用了兩次解調(diào)才把基帶信號(hào)完整復(fù)現(xiàn)出來。先來看看初步解調(diào)代碼,用的是TIM1的輸入捕獲模塊,TIM1屬于高級(jí)定時(shí)器,和通用定時(shí)器的代碼還是有些地方不一樣的,比如輸入捕獲中斷函數(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 ); ? ? ? ??
}
選擇輸入捕獲是因?yàn)閷?duì)于FSK信號(hào)來說,它由兩個(gè)不同頻率的正弦波組成,stm32默認(rèn)的高電平在2V以上,低電平在0.8V以下。通過測(cè)量從上升沿到下降沿這段時(shí)間,與閾值100us比較(4khz的正弦波半個(gè)周期為125us,8khz的正弦波半個(gè)周期為62.5us),大于100者碼元即為“0”,反之則為“1”。
u8 flag_falling;
int ? ? ? ?TIM1CH1_CAPTURE_VAL; ? ? ? ?
void TIM1_CC_IRQHandler(void)
{
if(flag_falling == 0) ? ? ? ?//檢測(cè)到上升沿
{
TIM_OC1PolarityConfig(TIM1,TIM_ICPolarity_Falling);//設(shè)置下一次觸發(fā)為下降沿觸發(fā)
TIM_SetCounter(TIM1,0);//清空TIM1->CCR1寄存器的值
TIM1CH1_CAPTURE_VAL = 0;//變量TIM1CH1_CAPTURE_VAL用于存儲(chǔ)TIM1->CCR1寄存器的值
flag_falling = 1;//置位標(biāo)志位,標(biāo)志下一次進(jìn)入中斷后檢測(cè)到下降沿
}
else ? ? ? ? ? ? ? ? ? ? //檢測(cè)到下降沿 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
{
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)入中斷后檢測(cè)到上升沿
if(TIM1CH1_CAPTURE_VAL >= 100)//設(shè)定閾值,與TIM1CH1_CAPTURE_VAL進(jìn)行比較
{
First_jietiao = 0;?
}
else
{
First_jietiao = 1; ? ? ? ? ? ? ? ?
} ? ? ? ?
}
?
TIM_ClearITPendingBit(TIM1, TIM_IT_CC1);?
}
在這里筆者小小地偷了個(gè)懶——沒有配置TIM1的更新中斷,而只是配置了捕獲中斷。這是鑒于筆者的TIM1初始化為:
TIM1_Cap_Init(0XFFFF,71); ? ? ? ? ? ? ? ?//以1MHZ的頻率計(jì)數(shù)?
看到了吧,0xFFFF,多大的數(shù)~其實(shí)也不大,只不過對(duì)于我們要捕獲的FSK信號(hào)來說它避免了更新中斷對(duì)捕獲造成的影響,也就是說當(dāng)我們捕獲到下降沿時(shí)得到的TIM1->CCR1寄存器的值就是我們想得到的時(shí)間,與計(jì)數(shù)值溢出多少次并無關(guān)系。注意:當(dāng)捕獲的波形頻率較高時(shí)可以這么做,但是如果波形頻率較低時(shí)最好使能更新中斷,在更新中斷里保存中斷次數(shù),得到的結(jié)果更準(zhǔn)確。
然而這只是我們初步解調(diào)出來的結(jié)果,由于4khz與8khz之間的過渡帶影響,最終得到的碼元序列“1”的持續(xù)時(shí)間長(zhǎng)于碼元為“0”的持續(xù)時(shí)間,信號(hào)的碼速率不是2000B/s,所以我們需要進(jìn)行二次解調(diào)。
二次解調(diào)的關(guān)鍵在于定時(shí)器TIM5的同步作用。筆者用TIM5定時(shí)2khz,在初步解調(diào)信號(hào)的邊沿處先延時(shí)150us,然后開始同步,通過判斷初步解調(diào)信號(hào)的碼元序列,得到二次解調(diào)信號(hào)的碼元。
?
在TIM1中斷函數(shù)里面:
u8 a=1; ?//a為全局變量
if(flag_falling == 0 && a == 1)//捕獲到下降沿時(shí)開始同步(下降沿亦即初步解調(diào)信號(hào)的邊沿)
{
delay_us(150);
TIM_Cmd(TIM5, ENABLE); ?//只需要執(zhí)行一次
a = 0;
}
在這里為什么要延時(shí)150us呢?為何不在初步解調(diào)信號(hào)的邊沿處就開始同步呢?這是考慮到初步解調(diào)信號(hào)高電平持續(xù)的時(shí)間比低電平的長(zhǎng),如果不延時(shí),則可能出現(xiàn)低電平碼元誤判。
?
?
?
接下來就是定時(shí)器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時(shí)鐘的同步作用,得到的二次解調(diào)信號(hào)能完整地復(fù)現(xiàn)基帶信號(hào),實(shí)現(xiàn)解調(diào)。至此,整個(gè)2FSK調(diào)制解調(diào)系統(tǒng)設(shè)計(jì)完成。
評(píng)論
查看更多