大致要求:规划一个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; //避免呈现底部失真}… //省去无关代码}
#define DAC_DHR12R2_Address 0x40007414void 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 automaticallyconnected 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点,故缓存巨细为64DMA_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);}
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);}
生成正弦波后自然是要把两个正弦波组合在一同构成FSK信号,这个组合当然不是随意组合,是要在基带信号的操控下进行。代码在主函数履行,如下:
int main(void){… //初始化代码while(1){if(Base_Signal == 1){TIM2->ARR = 140;;}if(Base_Signal == 0){TIM2->ARR = 280;}}}
经过上述一番折腾,调制总算是搞定了。
void TIM1_Cap_Init(u16 arr,u16 psc){GPIO_InitTypeDef GPIO_InitStructure;TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;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=01TIM_ITConfig(TIM1,TIM_IT_CC1,ENABLE);TIM_Cmd(TIM1,ENABLE );}
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_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;}
void TIM5_IRQHandler(void){if (TIM_GetITStatus(TIM5, TIM_IT_Update) != RESET){if(First_jietiao == 1)out_put = 1;elseout_put = 0;TIM_ClearITPendingBit(TIM5, TIM_IT_Update );}}