你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

【经验分享】利用STM32F103单片机输出SPWM波

[复制链接]
STMCU小助手 发布时间:2022-3-20 20:46
最近需要用到单片机输出SPWM波功能,在网上找了好多资料,发现都不完整,有算法的没有代码,有代码的看不懂算法。于是只好自己摸索,现将方法整理如下。
关于什么是SPWM波,为什么要用SPWM波,网上的介绍有很多,就不多说了。主要说一下在STM32F103C8T6上是如何实现的。
要产生SPWM波,核心就是调节PWM波的占空比,在一定时间段内使输出PWM波所占的面积和对应的正弦波面积相等。占空比的调节需要根据正弦波上对应的点来调节。那么首先就是要生成一组正弦波数据。生成正弦波的数据代码如下:

  1. //point 一个周期内的点数
  2. //maxnum 最大值
  3. void get_sin_tab1( u16 point, u16 maxnum )
  4. {
  5.     u16 i = 0, j = 0, k = 0;
  6.     float hd = 0.0;                //弧度
  7.     float fz = 0.0;               //峰值
  8.     u16 tem = 0;
  9.     j = point / 2;                        //水平线位置 单片机没有负电压
  10.     hd = PI / j;                // π/2 内每一个点对应的弧度值
  11.     k = maxnum / 2;              //最大值一半
  12.     for( i = 0; i < point; i++ )
  13.     {     
  14.                 fz = k * sin( hd * i ) + k;                                       
  15.         tem = ( u16 )fz ;                                                
  16.                 printf("%d\r\n",tem);
  17.         sinData = tem;
  18.     }
复制代码


通过串口波形软件可以看到生成的数据和波形如下:

9KJ@O%~{`5BB3_4$Q}Z897K.png

数据生成好之后,下来设置定时器输出PWM,

  1. void TIM1_PWM_Init(u16 arr, u16 psc)
  2. {
  3.     TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
  4.     TIM_OCInitTypeDef TIM_OCInitStructure;
  5.     GPIO_InitTypeDef GPIO_InitStructure;

  6.     RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);
  7.     RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //复用时钟

  8.     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
  9.     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
  10.     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  11.     GPIO_Init(GPIOA, &GPIO_InitStructure);

  12.     TIM_TimeBaseInitStructure.TIM_Period = arr;
  13.     TIM_TimeBaseInitStructure.TIM_Prescaler = psc;
  14.     TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;                                //只有高级定时器需要设置,其他定时器可以不设置。
  15.     TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
  16.     TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
  17.     TIM_TimeBaseInit(TIM1, &TIM_TimeBaseInitStructure);

  18.     TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
  19.     TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
  20.     TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;

  21.     TIM_OC1Init(TIM1, &TIM_OCInitStructure);                                                        //TIM1_OC1   

  22.     TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable);

  23.     TIM_Cmd(TIM1, ENABLE);
  24.     TIM_CtrlPWMOutputs(TIM1, ENABLE);                                                                        //高级定时器才有  必须打开
  25. }
复制代码

设置定时器1输出PWM波,定时器初始化为:

定时器 TIM1_PWM_Init(1000 - 1, 3);               

定时器1输出18K的PWM波,此时输出的PWM波占空比是固定的,还不能随着正弦规律变化。下来利用定时器2的定时中断,在中断中改变PWM的占空比。

  1. //APB1时钟分频为2  TIM2-7 时钟数为APB1 2倍
  2. // Tout= (arr+1)*(psc+1) / Tclk
  3. void TIM2_Init(u16 arr, u16 psc)
  4. {
  5.     TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
  6.     NVIC_InitTypeDef NVIC_InitStructure;

  7.     RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);

  8.     TIM_TimeBaseInitStructure.TIM_Period = arr;
  9.     TIM_TimeBaseInitStructure.TIM_Prescaler = psc;
  10.     TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;                        //只有高级定时器需要设置,其他定时器可以不设置。
  11.     TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
  12.     TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
  13.     TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);

  14.     TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);

  15.     NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
  16.     NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x01;
  17.     NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x01;
  18.     NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  19.     NVIC_Init(&NVIC_InitStructure);
  20.     TIM_Cmd(TIM2, ENABLE);
  21. }

  22. u8 dc_cnt = 0;                                                        //占空比计数
  23. //定时器2中断中改变 定时器1的占空比值
  24. void TIM2_IRQHandler(void)
  25. {
  26.     if(TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET)
  27.     {
  28.         TIM_SetCompare1(TIM1, sinData[dc_cnt]);
  29.         dc_cnt++;
  30.         if(dc_cnt >= PointMax)
  31.             dc_cnt = 0;
  32.         TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
  33.     }
  34. }
复制代码

在定时器2中断中,依次将sinData数组中存放的正弦数据做为PWM的占空比值。由于生成的正弦数据最大值是1000,输出PWM的ARR值也是1000,那么生成的正弦值就可以直接做为占空比用。如果生成的正弦波数据和ARR值不一样,在此处需要一个转换,用比例系数调整正弦波中的最大值刚好是占空比最大值就行。
这样在定时器1输出PWM比的时候,在定时器2中断中调整PWM的占空比,这样输出的PWM波经过外部电容后,就是一个正弦波。在PA8引脚接105电容。生成的波形如下:
E]3`1G4L%[T25O2G~~7_A9I.png

生成了正弦波之后,初步目的算是达到了。那么如何用生成的正弦波,和外部市电波形同步呢。
可以用过零检测电路将外部波形测出来。在每个零过点的时候输出一个脉冲。然后用中断检测这个脉冲,每次过零点到来时,在中断中将正弦波数组下标设置为0,这样每个过零点时,输出的正弦波也从0点开始输出。
外部中断代码如下:

  1. void IO_Exti_Init(void)
  2. {
  3.     EXTI_InitTypeDef EXTI_InitStructure;
  4.     NVIC_InitTypeDef NVIC_InitStructure;
  5.     GPIO_InitTypeDef GPIO_InitStructure;

  6.     RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
  7.     RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);

  8.     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
  9.     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;
  10.     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
  11.     GPIO_Init(GPIOB, &GPIO_InitStructure);

  12.     GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource0);

  13.     EXTI_InitStructure.EXTI_Line = EXTI_Line0;
  14.     EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
  15.     EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
  16.     EXTI_InitStructure.EXTI_LineCmd = ENABLE;
  17.     EXTI_Init(&EXTI_InitStructure);

  18.     NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;
  19.     NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02;
  20.     NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x01;
  21.     NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  22.     NVIC_Init(&NVIC_InitStructure);
  23. }

  24. extern u8 dc_cnt;                                                        //占空比计数
  25. //10ms中断一次
  26. void EXTI0_IRQHandler(void)
  27. {
  28.     if(EXTI_GetITStatus(EXTI_Line0) != RESET)
  29.     {
  30.         dc_cnt = 0;
  31.         EXTI_ClearITPendingBit(EXTI_Line0);
  32.     }
  33. }
复制代码

在外部中断中将正弦波数组的下标控制值dc_cnt 清零,这样每个过零点时,输出的正弦波数据正好是第一个。
这样输出的SPWM波就可以通过中断和外部波形进行同步了。

@K]5DH~30](6AD%IIFNY8J7.png

通过波形可以看出,在过零点位置,输出正弦波也刚好在零点。
如果要调整输出正弦波数据的初始位置怎么办呢?只需要在生成正弦波数据时,设置初始位置就行。

  1. void get_sin_tab1( u16 point, u16 maxnum )
  2. {
  3.     u16 i = 0, j = 0, k = 0;
  4.     float hd = 0.0;                //弧度
  5.     float fz = 0.0;               //峰值
  6.     u16 tem = 0;
  7.     j = point / 2;                        //水平线位置 单片机没有负电压 水平线为点值数量的一半
  8.     hd = PI / j;                // π/2 内每一个点对应的弧度值
  9.     k = maxnum / 2;              //最大值一半
  10.     for( i = 0; i < point; i++ )
  11.     {
  12.                 fz = k * sin( hd * i ) + k;                                                //起点为中间值
  13.         //fz = k * sin( hd * i + PI / 2) + k;                        //起点位置偏移到 π/2  起点为最大值
  14.                 //fz = k * sin( hd * i + PI / 2 + PI ) + k;                //起点位置偏移到 π/2 + π 起点为最小值

  15.         tem = ( u16 )fz ;                                                
  16.         sinData = tem;
  17.     }
  18. }
复制代码

在生成数据是 sin( hd * i ) ,的值就是初始相位,如果要调整初始位置,只需要将括号中的值加上偏移量就行,默认的起始位置在正弦波的0度位置,如果要让初始位置在90度,那么给括号中的值在加上 π/2就可以了。

](`2(GOO1_@@Y8$MFY_3K)A.png

如果要让起始位置在270度时,在括号中加上 3/2π就可以了。

BQ3R6SHM5213R_M}D{E710J.png


收藏 评论1 发布时间:2022-3-20 20:46

举报

1个回答
rayman 回答时间:8 小时前

代码下载

所属标签

相似分享

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版