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

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

[复制链接]
STMCU小助手 发布时间:2022-3-20 20:46
最近需要用到单片机输出SPWM波功能,在网上找了好多资料,发现都不完整,有算法的没有代码,有代码的看不懂算法。于是只好自己摸索,现将方法整理如下。
; @- S% \" K1 @0 t关于什么是SPWM波,为什么要用SPWM波,网上的介绍有很多,就不多说了。主要说一下在STM32F103C8T6上是如何实现的。! T8 m! G. z$ n  ~
要产生SPWM波,核心就是调节PWM波的占空比,在一定时间段内使输出PWM波所占的面积和对应的正弦波面积相等。占空比的调节需要根据正弦波上对应的点来调节。那么首先就是要生成一组正弦波数据。生成正弦波的数据代码如下:9 Z! Y& E- t1 a$ ^3 z
  k/ `" W7 k. t. {, U2 Q
  1. //point 一个周期内的点数
    6 j, S; J% e4 W* {: P& \2 ?# r* o
  2. //maxnum 最大值) ^( O! o- h2 E% B& U0 @
  3. void get_sin_tab1( u16 point, u16 maxnum )2 ]" [* j8 E/ r5 p% v
  4. {' A. _: y  y; \' O: \
  5.     u16 i = 0, j = 0, k = 0;& u$ x' N: l( D* r
  6.     float hd = 0.0;                //弧度% j' p1 h; T8 K
  7.     float fz = 0.0;               //峰值; d% @3 K$ z' e2 p, `( X5 Q! A  L/ _
  8.     u16 tem = 0;5 ]7 t) e, H; H* J! L9 K, |5 L' L
  9.     j = point / 2;                        //水平线位置 单片机没有负电压
    - U7 Y8 }$ Z- f1 b. |; J" j
  10.     hd = PI / j;                // π/2 内每一个点对应的弧度值
    $ f7 ?. a4 T, X' I6 y; s% i
  11.     k = maxnum / 2;              //最大值一半
    : W9 ?9 M! \; t
  12.     for( i = 0; i < point; i++ )4 H9 y5 [. |1 F9 r
  13.     {     5 Z4 P& C, v7 N2 E9 W4 k4 G
  14.                 fz = k * sin( hd * i ) + k;                                       
    - E9 C& C) H$ u: \! n$ i% d
  15.         tem = ( u16 )fz ;                                                  q$ l- y2 |2 ~% G+ B9 L" X
  16.                 printf("%d\r\n",tem);
    9 u* g" I: E7 I! s( V+ J, y! O# u' c
  17.         sinData = tem;4 M* L+ i) d6 l$ D, H& S- i5 j
  18.     }
复制代码
" K/ m5 y' Z+ }2 z  k

  s( m$ V: x. V' H$ O通过串口波形软件可以看到生成的数据和波形如下:' h  d) Z7 h$ m3 p6 f0 ]! W1 a
0 i% t0 z1 l: }% ^! z( w
9KJ@O%~{`5BB3_4$Q}Z897K.png
8 V' B; d/ b9 s4 G1 X
3 I( f: B9 M% D+ ?# y6 [' u数据生成好之后,下来设置定时器输出PWM,) k% E$ J7 w  x! d0 D

& K% \6 }+ T8 D7 X7 {: A5 A
  1. void TIM1_PWM_Init(u16 arr, u16 psc)
    ) k( ~& R4 Y! X) [( a# _
  2. {6 c6 j3 V/ ]. o1 B' l7 s0 l
  3.     TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;" ~5 @; d2 ?: E
  4.     TIM_OCInitTypeDef TIM_OCInitStructure;% v/ X/ s( J7 U2 t& x" j
  5.     GPIO_InitTypeDef GPIO_InitStructure;
    2 h: J# S5 v% C

  6. ! L9 C! F0 d+ {; ~9 ^; c% E$ j
  7.     RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);
    4 r8 z. K; }& ^: T. L1 s& E
  8.     RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //复用时钟- d3 n5 f5 Z4 o' c9 l; o+ Y/ M, c

  9. 3 D6 e5 T$ v, B3 @# t/ I0 [: b
  10.     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
    : }2 J5 l3 O, o6 X9 w
  11.     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    $ _; X4 O3 ]+ P; E: n3 ?
  12.     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;# e$ w% L; s+ {9 z4 L+ |. r
  13.     GPIO_Init(GPIOA, &GPIO_InitStructure);
    # N* r* [! s/ U( u- B

  14. % v+ Q% t! O, b2 S# j. B; U
  15.     TIM_TimeBaseInitStructure.TIM_Period = arr;
    " u. s7 f3 e0 [
  16.     TIM_TimeBaseInitStructure.TIM_Prescaler = psc;
    % u5 j; C, y. s
  17.     TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;                                //只有高级定时器需要设置,其他定时器可以不设置。
    9 T0 k0 B" G: k* a. h! w( R, ~$ i( o$ ^
  18.     TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
    , A. n3 e9 Q. A( d' p# x3 h
  19.     TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;9 b8 G; u# \8 m, v, W- u( f
  20.     TIM_TimeBaseInit(TIM1, &TIM_TimeBaseInitStructure);5 q; [. @: H6 a4 q! u: B, P
  21. : I+ @* B# t8 @4 c0 S9 h
  22.     TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
    - A, D% {  X4 r& Z& Z4 c1 \( z
  23.     TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;8 b3 }6 v9 c$ Z' }- |% W2 v- l
  24.     TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;; I" w4 L: U6 R, }6 E9 g2 p
  25. 2 \* r8 w  r. l: q3 s0 ~& N
  26.     TIM_OC1Init(TIM1, &TIM_OCInitStructure);                                                        //TIM1_OC1   
    $ c9 j3 B, K% @* u3 S

  27. 8 o' {6 P0 ^) J0 x
  28.     TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable);8 _# {) m* n& u, B, v% d& Q
  29. , ^2 X% V" A9 E. H. C5 b# |/ e4 V% Y
  30.     TIM_Cmd(TIM1, ENABLE);5 S3 Y7 r7 V* ~* w2 h
  31.     TIM_CtrlPWMOutputs(TIM1, ENABLE);                                                                        //高级定时器才有  必须打开2 c- T3 a9 Y0 K5 T1 t. l
  32. }
复制代码

! g! b& \; d) J/ Z设置定时器1输出PWM波,定时器初始化为:
- n1 _- Y8 W4 H" Z: M( I
8 I2 o$ o. q  {+ N 定时器 TIM1_PWM_Init(1000 - 1, 3);               
- v0 f4 Q* B3 l9 T! H0 Q
! Z& c0 d" v2 |定时器1输出18K的PWM波,此时输出的PWM波占空比是固定的,还不能随着正弦规律变化。下来利用定时器2的定时中断,在中断中改变PWM的占空比。
/ m9 H) H; l4 I/ B; r
; _7 Y" c$ z! A  u, A. V! f! ?
  1. //APB1时钟分频为2  TIM2-7 时钟数为APB1 2倍" C8 L; B2 I, c3 S1 d
  2. // Tout= (arr+1)*(psc+1) / Tclk* F. X+ u/ Z3 L0 _5 [# v8 s; F
  3. void TIM2_Init(u16 arr, u16 psc)
    8 k7 [" X* I8 [: M7 K; C
  4. {
    8 e" x- L" L- P
  5.     TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
    ) c7 f5 K4 r" p6 d) \8 t2 |8 K2 q
  6.     NVIC_InitTypeDef NVIC_InitStructure;2 h4 Y6 o( W$ H: j; X
  7. 1 @% i8 t5 I4 b# N6 B9 l
  8.     RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
    : @  @' Q- w' T- W5 z' Z; t

  9. & @8 r3 Q  ~0 x. G, g8 O
  10.     TIM_TimeBaseInitStructure.TIM_Period = arr;
    ) I1 Q2 o; v& C6 v
  11.     TIM_TimeBaseInitStructure.TIM_Prescaler = psc;
    - l( I: N, |, t8 Y- G
  12.     TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;                        //只有高级定时器需要设置,其他定时器可以不设置。
    + K9 w% k  s3 i* x
  13.     TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;, e2 e7 A! ?* M, w* f- c5 L. ?' I
  14.     TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
    ) g  {% g; s9 C& C# Q
  15.     TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);
    - ]2 W) G- j8 U% K* p8 _/ S

  16. " F9 T; Q% h5 B
  17.     TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);0 ~. z' [4 E: a1 y4 r3 M  R/ q; c

  18. , n% t: F$ R+ I$ l' [: `
  19.     NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;' U" ?  W# N( r- q6 w% I$ k
  20.     NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x01;
    # x& ~: _# \# k" z7 G% U
  21.     NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x01;; m: e- d5 x+ r! H, B% I& r* F& B
  22.     NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    , |: S; M  B/ F0 W6 ?. \5 T; j: W
  23.     NVIC_Init(&NVIC_InitStructure);
    6 L. x3 l% {0 g& g+ c
  24.     TIM_Cmd(TIM2, ENABLE);
    $ d( y/ R0 j8 C( |' I
  25. }7 Y% A2 b. G3 q- T* C. A- E5 {

  26. 2 H+ e/ U, R$ ]/ F5 u) D
  27. u8 dc_cnt = 0;                                                        //占空比计数
    " D" ^& w$ f# u6 S* m* j
  28. //定时器2中断中改变 定时器1的占空比值
    - \4 W' N. @1 E' o
  29. void TIM2_IRQHandler(void)$ e, ^. t' Y) y) M& I2 l) O
  30. {
    # p  |( R5 G+ ]- g$ D/ \
  31.     if(TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET)
    : n& a% d# I" c3 w; r1 |$ u
  32.     {
    ) I; m/ b( d5 s
  33.         TIM_SetCompare1(TIM1, sinData[dc_cnt]);
    * R* Z  K( ?# ?8 k1 ~
  34.         dc_cnt++;. a! L! _7 x- w
  35.         if(dc_cnt >= PointMax)2 T- e% [1 Y9 r- P% j' V2 X
  36.             dc_cnt = 0;* g  W6 \1 P6 w& c3 `& Z# _6 K
  37.         TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
    ) S% K2 y5 x" k- \$ A4 A
  38.     }& y1 t* ^% w: O6 R
  39. }
复制代码

" [9 Z% l  ~% o0 N在定时器2中断中,依次将sinData数组中存放的正弦数据做为PWM的占空比值。由于生成的正弦数据最大值是1000,输出PWM的ARR值也是1000,那么生成的正弦值就可以直接做为占空比用。如果生成的正弦波数据和ARR值不一样,在此处需要一个转换,用比例系数调整正弦波中的最大值刚好是占空比最大值就行。" p) V  T4 U" I. r5 @$ S- X& O
这样在定时器1输出PWM比的时候,在定时器2中断中调整PWM的占空比,这样输出的PWM波经过外部电容后,就是一个正弦波。在PA8引脚接105电容。生成的波形如下:+ x% T: t3 b: p) v
E]3`1G4L%[T25O2G~~7_A9I.png
/ o6 ?/ h0 K& \  e$ R; c7 m& M8 Y( U$ ~
生成了正弦波之后,初步目的算是达到了。那么如何用生成的正弦波,和外部市电波形同步呢。
3 c$ o) J. h1 b' Y* A5 E可以用过零检测电路将外部波形测出来。在每个零过点的时候输出一个脉冲。然后用中断检测这个脉冲,每次过零点到来时,在中断中将正弦波数组下标设置为0,这样每个过零点时,输出的正弦波也从0点开始输出。1 `! U* w8 L- u/ f) S
外部中断代码如下:# D, y  A. h2 x* n5 ~/ z' i5 ~  u" `
2 q2 P! P! T) D& x8 n
  1. void IO_Exti_Init(void)1 v- T2 c: @5 R4 k, u' F3 {
  2. {
    0 Y. Q5 q$ a8 R& Z" O2 L
  3.     EXTI_InitTypeDef EXTI_InitStructure;9 U1 C! e$ N* F3 e( v8 V
  4.     NVIC_InitTypeDef NVIC_InitStructure;+ @6 |1 ^8 [: H
  5.     GPIO_InitTypeDef GPIO_InitStructure;
    ) l) u6 [8 Y, Z- ~! A/ o" I
  6. / p5 I# F  c4 Z
  7.     RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
    ; x0 ^; a) t: j, j9 Y( Q
  8.     RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);- g2 C& r/ ]$ q! i3 R9 \

  9. ; D, ]3 T; j$ P2 ^8 [  L" W/ v
  10.     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;8 D# y( I# M# @; V
  11.     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;
    ( Z) N0 f, t: f/ S
  12.     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;$ |2 }' z/ g7 q- `# J9 f
  13.     GPIO_Init(GPIOB, &GPIO_InitStructure);
    * G7 K8 O0 _" a8 A* N3 }

  14. ; O& L0 J$ ^- D8 s
  15.     GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource0);
    / ~' T+ x+ {1 P8 \% |

  16. 2 c) j4 y. b7 L' d7 a
  17.     EXTI_InitStructure.EXTI_Line = EXTI_Line0;
    , T3 j9 E7 w* O7 c; U+ ~! y: f4 v
  18.     EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
    - Y* X8 ^6 K3 z6 ]0 l- c1 S% ~& Z
  19.     EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
    : h3 `* ?/ }$ |  w* ~
  20.     EXTI_InitStructure.EXTI_LineCmd = ENABLE;, h/ J, \6 s* t0 k' d6 n2 _
  21.     EXTI_Init(&EXTI_InitStructure);0 I% v# b) R$ t2 a. z  _! W

  22. 0 D8 g9 d' O8 v' x; O  V# }' i1 X7 C
  23.     NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;
    ( |  ~! c% t# r4 C' {. J9 ?
  24.     NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02;
    1 m  m; `3 Y; o+ l! I  I
  25.     NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x01;
    ' K  |  U2 x* X! \# O2 R2 c& F+ p9 n
  26.     NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;( U  [- N: n! T/ o" s7 ~, Z
  27.     NVIC_Init(&NVIC_InitStructure);
      A. L+ {& _- c5 v. i) O* y
  28. }
    2 Y5 Z- |& s0 W( y1 ?3 r, N

  29. 0 U& U' l1 R: Z5 w# P1 e, B3 U
  30. extern u8 dc_cnt;                                                        //占空比计数* Z' v0 l! t  E4 X$ U
  31. //10ms中断一次4 r  d7 Y9 [0 ~  W
  32. void EXTI0_IRQHandler(void)
    1 h, T8 t% ?* L$ O
  33. {* F; s8 e2 w- G( |+ k; F/ ?/ H! h
  34.     if(EXTI_GetITStatus(EXTI_Line0) != RESET)" m) J2 m( n- `& ~+ y1 \' w
  35.     {
    % w  a/ R8 `/ p. a
  36.         dc_cnt = 0;( H4 }: l; Y: e7 N
  37.         EXTI_ClearITPendingBit(EXTI_Line0);
    ; N7 u2 T8 I1 D
  38.     }( x/ V7 n8 y9 h) _/ U
  39. }
复制代码

+ g( F, g& Q' L" j; x5 Y在外部中断中将正弦波数组的下标控制值dc_cnt 清零,这样每个过零点时,输出的正弦波数据正好是第一个。
1 ~9 b0 u9 _& d$ _# {- U* p! y( x这样输出的SPWM波就可以通过中断和外部波形进行同步了。1 V/ h" i/ ?2 C/ j+ I! ^
# ~% a7 o* {# w- j3 y6 h2 ]
@K]5DH~30](6AD%IIFNY8J7.png 6 j& C0 q' l: v, Z

5 J/ n$ k3 r4 t9 H1 d# `通过波形可以看出,在过零点位置,输出正弦波也刚好在零点。
4 Y1 X4 D, c. \" Z. K如果要调整输出正弦波数据的初始位置怎么办呢?只需要在生成正弦波数据时,设置初始位置就行。( d, I' \. v; T' y

& c. {* a5 \8 Y4 K
  1. void get_sin_tab1( u16 point, u16 maxnum )3 E6 [6 v6 @& q0 @5 ], o7 w/ p0 s. X% \
  2. {2 ^9 I) P; P+ c0 V& N& Z: A0 q
  3.     u16 i = 0, j = 0, k = 0;2 ~) u  M3 N# {, x3 ^' V% f2 x4 Q
  4.     float hd = 0.0;                //弧度
      t1 N: u. }8 |) M2 E
  5.     float fz = 0.0;               //峰值
    8 g+ S7 O4 o; Y- H
  6.     u16 tem = 0;) Q! A  X8 w) B4 _
  7.     j = point / 2;                        //水平线位置 单片机没有负电压 水平线为点值数量的一半1 ~, g3 Z: K' A& t/ c+ G
  8.     hd = PI / j;                // π/2 内每一个点对应的弧度值3 A! F; ?* @5 q  o: ]8 J+ Q3 H3 J
  9.     k = maxnum / 2;              //最大值一半& r/ t" [0 J5 W; {7 S% s' B
  10.     for( i = 0; i < point; i++ ); u7 L1 ?$ N  ~. `
  11.     {
    ' ]8 v% I/ m9 z% `) ^2 y8 E
  12.                 fz = k * sin( hd * i ) + k;                                                //起点为中间值1 y" ~5 R& Y$ x* W1 S) T
  13.         //fz = k * sin( hd * i + PI / 2) + k;                        //起点位置偏移到 π/2  起点为最大值3 ?$ x1 Q6 Q* X% {( z
  14.                 //fz = k * sin( hd * i + PI / 2 + PI ) + k;                //起点位置偏移到 π/2 + π 起点为最小值
    * R4 G8 M0 f5 {2 o

  15. + B; U4 q+ V# n1 b5 @3 j4 n
  16.         tem = ( u16 )fz ;                                                
    0 `/ n% T! V* G. i, c1 j
  17.         sinData = tem;- T/ G, w% k' e3 G* N
  18.     }
    7 l- D0 {2 w3 `! ^( Z2 I5 Y
  19. }
复制代码
) s& k' m' S" H8 v
在生成数据是 sin( hd * i ) ,的值就是初始相位,如果要调整初始位置,只需要将括号中的值加上偏移量就行,默认的起始位置在正弦波的0度位置,如果要让初始位置在90度,那么给括号中的值在加上 π/2就可以了。
, I7 H, ~( g$ |" @
+ d' L: F! Z: C7 c( T ](`2(GOO1_@@Y8$MFY_3K)A.png - V# a6 N+ t2 ~

! C' M# u* ]( Y- D" b如果要让起始位置在270度时,在括号中加上 3/2π就可以了。  s  \! S. w, Y" r
/ K$ [* }! }/ n5 s+ c
BQ3R6SHM5213R_M}D{E710J.png ; [; \) Q! g% }6 @/ L5 X' H
$ a7 I0 P8 F' R

; q4 ^* F9 r4 B
赞 收藏 评论1 发布时间:2022-3-20 20:46

举报

1个回答
rayman 回答时间:2025-7-9 10:34:42

代码下载

所属标签

相似技术帖

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版