基于stm32的FSK调制解调器的设计

发布时间:2023-10-19  

大致要求:设计一个FSK调制解调器,基带信号码速率为2000B/s,载波速率为4khz和8khz,解调信号要能完整还原基带信号。实现方法多种多样,通信领域内调制解调器的设计大多数用的都是硬件电路,鉴于笔者对编程情有独钟(其实笔者还是懂一点电路设计知识的~),所以最终决定用stm32来设计,纯编程实现。看起来高大上,但实际做起来不难,不过有挺多东西要考虑的。


总的设计思路如下:

51

首先是基带信号的产生,它也是我们要调制和解调的目标。基带信号由一连串随机的码元序列构成,为了模拟随机的码元序列,笔者用定时器设计8位的PN码序列,码元速率为2000B/s。定时器3定时0.5ms,每进入一次中断,变量num加一,设置一次IO引脚电平,8位PN码只需设置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);  

}

}

接下来要产生载波,载波就是正弦波无疑。这里笔者的载波频率要求是4khz和8khz。正弦波的产生用的是stm32的DMA+DAC+TIM2。正弦波的数据用正弦波数据发生器产生,采样点数64,精度12位,保存在Sine12bit[]数组,但是传送给DMA的正弦波数据不是这些原始的数据,而是将这些数据进行了进一步的处理:

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)

{



...                                          //省去无关代码

for (Idx = 0; Idx < 64; Idx++)

{

Sine12bit[Idx] = Sine12bit[Idx]*8/10+500;        //防止出现底部失真

}

...                                          //省去无关代码

}

为什么要这么处理呢?在讲到DAC的配置时还会再提到这一点,在这里先不做解释。经过处理后的正弦波数据可以直接传送到DMA通道,等TIM2的触发时间一到,就可以依次把数据给到DAC,转换成正弦波输出。笔者用DAC通道2(对应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;  //设为向下计数

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);        

}



这里笔者把两个模块的配置同时放在一个初始化函数里面,只是图个方便,在官方例程里是将DAC和DMA的配置放在一起。这段代码有两个地方需要注意:一是TIM2计数模式设为向下计数,二是使能了DAC的输出缓存。设为向下计数是为了在两个正弦波频率切换时不会因为计数溢出而出现问题,在FSK产生环节里还会详细说到这一点;使能输出缓存是因为stm32的DAC在输出缓存关闭时输出阻抗太大,带负载能力弱,在输入捕获时正弦波严重失真,故需要开启输出缓存,但同时也存在一个问题:使能输出缓存后,DAC没办法使输出达到0,这就使得原始正弦波的峰值数据丢失,导致底部失真。于是我们需要用上面的代码对原始正弦波数据做一个处理——先乘上8除以10防止峰值超过12位精度的最大值4096(不能直接乘上0.8,因为数组存储的数据必须是整形),然后再加上500,将正弦波数据整体抬高。
        生成正弦波后自然是要把两个正弦波组合在一起形成FSK信号,这个组合当然不是随意组合,是要在基带信号的控制下进行。代码在主函数执行,如下:

int main(void)

{

...  //初始化代码

while(1)

{

if(Base_Signal == 1)

{

TIM2->ARR = 140;;

}

if(Base_Signal == 0)

{

TIM2->ARR = 280;

}

}

while(1)循环里if语句判断基带信号的码元序列,“1”对应8khz载波,“0”对应4khz载波。通过改变TIM2的自动重装载寄存器(ARR)的值实现两个载波的频率切换。解释一下这里为什么选择140和280:采样64个点,8khz对应的DAC转换速率为8000*64hz,那么TIM2就要每隔8000/64/72 000 000 = 1/140s触发一次DAC,故TIM2的ARR值为140;同样的,4khz对应的ARR值为280。在这里还要注意:TIM2的计数模式应配置为向下计数。一般例程都会把定时器配置为向上计数,但用在这里会出现一个问题:在基带信号由0变为1时,FSK信号也要相应的从4khz正弦波跳变到8khz正弦波。我们知道向上计数模式是TIM2->CNT寄存器从0开始计数,一直计到ARR的值,进入中断,然后重新清零,继续计数直到又达到ARR设定的值。。。假设FSK信号在4khz正弦波时TIM2->CNT一度计数到140以上(此时ARR的值为280),突然基带信号变为1,FSK信号由4khz正弦波变为8khz,ARR值被设定为140,这时候CNT寄存器将一直往上计数,永远不会停止,直到溢出(ARR寄存器为16位)。实际上笔者在调试时,当基带信号为“1“,输出的FSK信号为一条直线。把计数模式改为向下计数,问题解决。
       经过上述一番折腾,调制总算是搞定了。



接下来就是解调。笔者用了两次解调才把基带信号完整复现出来。先来看看初步解调代码,用的是TIM1的输入捕获模块,TIM1属于高级定时器,和通用定时器的代码还是有些地方不一样的,比如输入捕获中断函数名为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默认的高电平在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);//设置下一次触发为下降沿触发

TIM_SetCounter(TIM1,0);//清空TIM1->CCR1寄存器的值

TIM1CH1_CAPTURE_VAL = 0;//变量TIM1CH1_CAPTURE_VAL用于存储TIM1->CCR1寄存器的值

flag_falling = 1;//置位标志位,标志下一次进入中断后检测到下降沿

}

else                     //检测到下降沿                                                        

{

TIM_OC1PolarityConfig(TIM1,TIM_ICPolarity_Rising);//设置下一次触发为上升沿触发

TIM1CH1_CAPTURE_VAL=TIM_GetCapture1(TIM1);//读取TIM1->CCR1寄存器的值

flag_falling = 0;//清除标志位,标志下一次进入中断后检测到上升沿

if(TIM1CH1_CAPTURE_VAL >= 100)//设定阈值,与TIM1CH1_CAPTURE_VAL进行比较

{

First_jietiao = 0; 

}

else

{

First_jietiao = 1;                

}        

}

 

TIM_ClearITPendingBit(TIM1, TIM_IT_CC1); 

}

在这里笔者小小地偷了个懒——没有配置TIM1的更新中断,而只是配置了捕获中断。这是鉴于笔者的TIM1初始化为:

TIM1_Cap_Init(0XFFFF,71);                //以1MHZ的频率计数 


看到了吧,0xFFFF,多大的数~其实也不大,只不过对于我们要捕获的FSK信号来说它避免了更新中断对捕获造成的影响,也就是说当我们捕获到下降沿时得到的TIM1->CCR1寄存器的值就是我们想得到的时间,与计数值溢出多少次并无关系。注意:当捕获的波形频率较高时可以这么做,但是如果波形频率较低时最好使能更新中断,在更新中断里保存中断次数,得到的结果更准确。

然而这只是我们初步解调出来的结果,由于4khz与8khz之间的过渡带影响,最终得到的码元序列“1”的持续时间长于码元为“0”的持续时间,信号的码速率不是2000B/s,所以我们需要进行二次解调。

二次解调的关键在于定时器TIM5的同步作用。笔者用TIM5定时2khz,在初步解调信号的边沿处先延时150us,然后开始同步,通过判断初步解调信号的码元序列,得到二次解调信号的码元。

文章来源于:电子工程世界    原文链接
本站所有转载文章系出于传递更多信息之目的,且明确注明来源,不希望被转载的媒体或个人可与我们联系,我们将立即进行删除处理。

我们与500+贴片厂合作,完美满足客户的定制需求。为品牌提供定制化的推广方案、专属产品特色页,多渠道推广,SEM/SEO精准营销以及与公众号的联合推广...详细>>

利用葫芦芯平台的卓越技术服务和新产品推广能力,原厂代理能轻松打入消费物联网(IOT)、信息与通信(ICT)、汽车及新能源汽车、工业自动化及工业物联网、装备及功率电子...详细>>

充分利用其强大的电子元器件采购流量,创新性地为这些物料提供了一个全新的窗口。我们的高效数字营销技术,不仅可以助你轻松识别与连接到需求方,更能够极大地提高“闲置物料”的处理能力,通过葫芦芯平台...详细>>

我们的目标很明确:构建一个全方位的半导体产业生态系统。成为一家全球领先的半导体互联网生态公司。目前,我们已成功打造了智能汽车、智能家居、大健康医疗、机器人和材料等五大生态领域。更为重要的是...详细>>

我们深知加工与定制类服务商的价值和重要性,因此,我们倾力为您提供最顶尖的营销资源。在我们的平台上,您可以直接接触到100万的研发工程师和采购工程师,以及10万的活跃客户群体...详细>>

凭借我们强大的专业流量和尖端的互联网数字营销技术,我们承诺为原厂提供免费的产品资料推广服务。无论是最新的资讯、技术动态还是创新产品,都可以通过我们的平台迅速传达给目标客户...详细>>

我们不止于将线索转化为潜在客户。葫芦芯平台致力于形成业务闭环,从引流、宣传到最终销售,全程跟进,确保每一个potential lead都得到妥善处理,从而大幅提高转化率。不仅如此...详细>>