【全国电子设计竞赛电源类】SPWM波——入门教程

2019-04-14 08:57发布

  本教程针对第一次参见全国大学生电子设计竞赛并有一定嵌入式基础的同学 本教程用到的嵌入式板子的芯片型号为是STM32F103RCT(正点原子的mini板) 一.实验目的 基于STM32F103RCT输出载波频率(计数器溢出频率)为20KHz,调制波频率(400次溢出输出的整个波的频率)为50Hz的SPWM波。 二.实验准备 1.一块正点原子STM32min板子,用到高级定时器1的CH1(PA8) 与NCH1 (PB13) 2.熟练掌握STM32输出互补的PWM波的方法 3.百度自行了解什么是SPWM波SPWM等效面积法 三.实验原理 本次实验计划制造400个样本点(或者说成采400个点),即计满溢出的次数为400次。计数器每溢出一次改变一次占空比,计数周期为50us,也就是说每个样本点之间的时间间隔为50us,采样频率即为1/[50*10^(-6)] = 20KHz。因为我们需要制造400个样本点,故400个样本点总持续时间为400*50us = 20000us=20ms ,总频率为1/[20000*10^(-6)]=50Hz,最后得到周期为20ms,频率为50Hz的SPWM波。我这么说可能还会有人不明白,如下图所示:
图为等效面积法的原理,图b)为半个正弦周期的SPWM波,红 {MOD}标注是为了说明我们在用软件生成的每一个样本点的样本周期都为50us,只不过不同样本点的样本周期占空比不同罢了。
越是稀疏的地方,每50us的样本周期内的占空比越大,且疏密程度以20ms为周期循环变化。
放大来看从左到右,每50us的样本周期内的占空比是逐渐变大的。
四.代码详解 1.spwm.c文件 #include"stm32f10x.h" #include"sys.h" extern u16 x; //引用main.c文件的变量x const u32 spwm[400] = { //正弦波样本码表 0,28,56,84,113,141,169,197,226,254,282,310,338,366,395,423, 451,479,507,535,563,591,618,646,674,702,730,757,785,812,840,867, 895,922,949,977,1004,1031,1058,1085,1112,1139,1166,1192,1219,1246,1272,1298, 1325,1351,1377,1403,1429,1455,1481,1507,1532,1558,1583,1609,1634,1659,1684,1709, 1734,1759,1783,1808,1832,1856,1880,1905,1928,1952,1976,2000,2023,2046,2070,2093, 2116,2138,2161,2184,2206,2228,2250,2272,2294,2316,2338,2359,2380,2401,2422,2443, 2464,2484,2505,2525,2545,2565,2585,2604,2624,2643,2662,2681,2700,2719,2737,2755, 2773,2791,2809,2827,2844,2861,2878,2895,2912,2928,2945,2961,2977,2993,3008,3024, 3039,3054,3069,3084,3098,3112,3127,3140,3154,3168,3181,3194,3207,3220,3232,3245, 3257,3269,3281,3292,3303,3315,3325,3336,3347,3357,3367,3377,3387,3396,3405,3414, 3423,3432,3440,3449,3457,3464,3472,3479,3486,3493,3500,3507,3513,3519,3525,3530, 3536,3541,3546,3551,3555,3559,3564,3567,3571,3575,3578,3581,3584,3586,3588,3591, 3592,3594,3596,3597,3598,3599,3599,3599,3600,3599,3599,3599,3598,3597,3596,3594, 3592,3591,3588,3586,3584,3581,3578,3575,3571,3567,3564,3559,3555,3551,3546,3541, 3536,3530,3525,3519,3513,3507,3500,3493,3486,3479,3472,3464,3457,3449,3440,3432, 3423,3414,3405,3396,3387,3377,3367,3357,3347,3336,3325,3315,3303,3292,3281,3269, 3257,3245,3232,3220,3207,3194,3181,3168,3154,3140,3127,3112,3098,3084,3069,3054, 3039,3024,3008,2993,2977,2961,2945,2928,2912,2895,2878,2861,2844,2827,2809,2791, 2773,2755,2737,2719,2700,2681,2662,2643,2624,2604,2585,2565,2545,2525,2505,2484, 2464,2443,2422,2401,2380,2359,2338,2316,2294,2272,2250,2228,2206,2184,2161,2138, 2116,2093,2070,2046,2023,2000,1976,1952,1928,1905,1880,1856,1832,1808,1783,1759, 1734,1709,1684,1659,1634,1609,1583,1558,1532,1507,1481,1455,1429,1403,1377,1351, 1325,1298,1272,1246,1219,1192,1166,1139,1112,1085,1058,1031,1004,977,949,922, 895,867,840,812,785,757,730,702,674,646,618,591,563,535,507,479, 451,423,395,366,338,310,282,254,226,197,169,141,113,84,56,28 }; void rcc_init(void) { RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1,ENABLE); //开启//TIM1时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOB,ENABLE); //开启 // PA8(CH1) PB13(NCH1) 时钟 } void gpio_init(void) //PA8,PB13配置 { GPIO_InitTypeDef PA8; GPIO_InitTypeDef PB13; PA8.GPIO_Mode=GPIO_Mode_AF_PP; //复用推挽输出 PA8.GPIO_Pin=GPIO_Pin_8; PA8.GPIO_Speed=GPIO_Speed_50MHz; GPIO_Init(GPIOA,&PA8); PB13.GPIO_Mode=GPIO_Mode_AF_PP; //复用推挽输出 PB13.GPIO_Pin=GPIO_Pin_13; PB13.GPIO_Speed=GPIO_Speed_50MHz; GPIO_Init(GPIOB,&PB13); } void nvic_init(void) //中断优先级管理 { NVIC_InitTypeDef nvic_time1; nvic_time1.NVIC_IRQChannel=TIM1_UP_IRQn; nvic_time1.NVIC_IRQChannelCmd=ENABLE; nvic_time1.NVIC_IRQChannelPreemptionPriority=3; nvic_time1.NVIC_IRQChannelSubPriority=3; NVIC_Init(&nvic_time1); } void spwm_init(u16 arr,u16 psc) { TIM_TimeBaseInitTypeDef time1; //TIM1参数配置 TIM_OCInitTypeDef time1_oc1; //输出比较配置 // TIM_BDTRInitTypeDef TIM_BDTRInitStructure; time1.TIM_CounterMode=TIM_CounterMode_Up; //向上计数模式 time1.TIM_Period=arr; //计数周期 time1.TIM_Prescaler=psc; //时钟分频 //CK_CNT=72MHz/psc time1.TIM_ClockDivision=0; TIM_TimeBaseInit(TIM1,&time1); TIM_ClearFlag(TIM1, TIM_FLAG_Update); //清中断标志位 TIM_ITConfig(TIM1,TIM_IT_Update,ENABLE); //开启TIM1更新中断 time1_oc1.TIM_OCMode=TIM_OCMode_PWM2; //模式2, CNT>CCR 输出有效电平(高电平) time1_oc1.TIM_OCPolarity=TIM_OCPolarity_High; //有效电平设为高 time1_oc1.TIM_Pulse=0; time1_oc1.TIM_OutputState= TIM_OutputState_Enable; //开启通道CH1 CCER 捕获/比较使能寄存器 time1_oc1.TIM_OutputNState=TIM_OutputNState_Enable; //开启通道NCH1(CH1的互补输出) time1_oc1.TIM_OCNPolarity=TIM_OCPolarity_High; time1_oc1.TIM_OCIdleState = TIM_OCIdleState_Reset; //CH1 空闲状态为低电平 time1_oc1.TIM_OCNIdleState = TIM_OCIdleState_Reset; //NCH1空闲状态为低电平 TIM_OC1Init(TIM1,&time1_oc1); //TIM_BDTRInitStructure.TIM_OSSRState = TIM_OSSRState_Enable; //TIM_BDTRInitStructure.TIM_OSSIState = TIM_OSSIState_Enable; //TIM_BDTRInitStructure.TIM_LOCKLevel = TIM_LOCKLevel_OFF; //TIM_BDTRInitStructure.TIM_DeadTime = 0; //设置死区事件 //TIM_BDTRInitStructure.TIM_Break = TIM_Break_Disable; //禁止刹车输入 //TIM_BDTRInitStructure.TIM_BreakPolarity = TIM_BreakPolarity_Low; //TIM_BDTRInitStructure.TIM_AutomaticOutput = TIM_AutomaticOutput_Disable; //TIM_BDTRConfig(TIM1, &TIM_BDTRInitStructure); TIM_OC1PreloadConfig(TIM1,TIM_OCPreload_Enable); //CH1预装载使能 TIM_ARRPreloadConfig(TIM1, ENABLE); //使能TIMx在ARR上的预装载寄存器 允许定时器工作时像ARR缓冲器写入新值(改变计数周期),更新事件发生时载入新值覆盖ARR影子寄存器,下一次CNT要计数到ARR的新值 TIM_CtrlPWMOutputs(TIM1,ENABLE); //开启BDTR MOE 控制输出总开关 TIM_Cmd(TIM1,ENABLE); //TIM1使能 } void TIM1_UP_IRQHandler(void) //50us溢出一次,进入中断,改变一次占空比 { static u16 i=0; if(TIM_GetITStatus(TIM1,TIM_IT_Update)==1) { if(i<400) //溢出400次一个循环,也就是半个正弦波周期的SPWM波 { TIM_SetCompare1(TIM1,(u16)(spwm[i])) ; i++; } else i=0; TIM_ClearITPendingBit(TIM1,TIM_IT_Update); //清除中断标志位 } }  
从代码里可以看到我在将刹车死区的配置给注释了, 刹车死区设置是为了防止全桥同时导通而烧坏电路而出现的,今后会讲到的,这里暂时一笔带过。
2.main.c文件  #include "stm32f10x.h" #include "spwm.h" #include "sys.h" u16 x=3600; //TIM1计数上限 int main(void) { rcc_init(); //开启时钟 gpio_init(); //配置GPIO口 nvic_init(); //中断优先级管理 spwm_init(x,0); //TIM1计数上限为3600;不分频;默认计数器时钟频率为72MHz NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //2位抢占优先级,2位响应优先级 while(1) { } } 五.实验结果
PA8口输出
双路互补输出(PA8 PB13)