您的位置 首页 ADAS

完成stm32在FSK调制解调器的归纳规划

大致要求:设计一个FSK调制解调器,基带信号码速率为2000B/s,载波速率为4khz和8khz,解调信号要能完整还原基带信号。实现方法多种多…

大致要求:规划一个FSK调制解调器,基带信号码速率为2000B/s,载波速率为4khz和8khz,解调信号要能完好复原基带信号。完结办法多种多样,通讯领域内调制解调器的规划大多数用的都是硬件电路,鉴于笔者对编程情有独钟(其实笔者仍是懂一点电路规划常识的~),所以终究决议用stm32来规划,纯编程完结。看起来巨大上,但实践做起来不难,不过有挺多东西要考虑的。

总的规划思路如下:

首先是基带信号的发生,它也是咱们要调制和解调的方针。基带信号由一连串随机的码元序列构成,为了模仿随机的码元序列,笔者用守时器规划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_%&&&&&%Polarity_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,然后开端同步,经过判别开端解调信号的码元序列,得到二次解调信号的码元。

在TIM1中止函数里边:

u8 a=1; //a为全局变量

if(flag_falling == 0 && a == 1)//捕获到下降沿时开端同步(下降沿亦即开端解调信号的边缘)
{
delay_us(150);
TIM_Cmd(TIM5, ENABLE); //只需求履行一次
a = 0;
}
在这儿为什么要延时150us呢?为何不在开端解调信号的边缘处就开端同步呢?这是考虑到开端解调信号高电平继续的时刻比低电平的长,假如不延时,则或许呈现低电平码元误判。
接下来便是守时器5的中止服务函数:
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时钟的同步效果,得到的二次解调信号能完好地复现基带信号,完结解调。至此,整个2FSK调制解调体系规划完结。

声明:本文内容来自网络转载或用户投稿,文章版权归原作者和原出处所有。文中观点,不代表本站立场。若有侵权请联系本站删除(kf@86ic.com)https://www.86ic.net/qiche/adas/272193.html

为您推荐

联系我们

联系我们

在线咨询: QQ交谈

邮箱: kf@86ic.com

关注微信
微信扫一扫关注我们

微信扫一扫关注我们

返回顶部