最近需要用到单片机输出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
- //point 一个周期内的点数
6 j, S; J% e4 W* {: P& \2 ?# r* o - //maxnum 最大值) ^( O! o- h2 E% B& U0 @
- void get_sin_tab1( u16 point, u16 maxnum )2 ]" [* j8 E/ r5 p% v
- {' A. _: y y; \' O: \
- u16 i = 0, j = 0, k = 0;& u$ x' N: l( D* r
- float hd = 0.0; //弧度% j' p1 h; T8 K
- float fz = 0.0; //峰值; d% @3 K$ z' e2 p, `( X5 Q! A L/ _
- u16 tem = 0;5 ]7 t) e, H; H* J! L9 K, |5 L' L
- j = point / 2; //水平线位置 单片机没有负电压
- U7 Y8 }$ Z- f1 b. |; J" j - hd = PI / j; // π/2 内每一个点对应的弧度值
$ f7 ?. a4 T, X' I6 y; s% i - k = maxnum / 2; //最大值一半
: W9 ?9 M! \; t - for( i = 0; i < point; i++ )4 H9 y5 [. |1 F9 r
- { 5 Z4 P& C, v7 N2 E9 W4 k4 G
- fz = k * sin( hd * i ) + k;
- E9 C& C) H$ u: \! n$ i% d - tem = ( u16 )fz ; q$ l- y2 |2 ~% G+ B9 L" X
- printf("%d\r\n",tem);
9 u* g" I: E7 I! s( V+ J, y! O# u' c - sinData = tem;4 M* L+ i) d6 l$ D, H& S- i5 j
- }
复制代码 " 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
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- void TIM1_PWM_Init(u16 arr, u16 psc)
) k( ~& R4 Y! X) [( a# _ - {6 c6 j3 V/ ]. o1 B' l7 s0 l
- TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;" ~5 @; d2 ?: E
- TIM_OCInitTypeDef TIM_OCInitStructure;% v/ X/ s( J7 U2 t& x" j
- GPIO_InitTypeDef GPIO_InitStructure;
2 h: J# S5 v% C
! L9 C! F0 d+ {; ~9 ^; c% E$ j- RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);
4 r8 z. K; }& ^: T. L1 s& E - RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //复用时钟- d3 n5 f5 Z4 o' c9 l; o+ Y/ M, c
3 D6 e5 T$ v, B3 @# t/ I0 [: b- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
: }2 J5 l3 O, o6 X9 w - GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
$ _; X4 O3 ]+ P; E: n3 ? - GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;# e$ w% L; s+ {9 z4 L+ |. r
- GPIO_Init(GPIOA, &GPIO_InitStructure);
# N* r* [! s/ U( u- B
% v+ Q% t! O, b2 S# j. B; U- TIM_TimeBaseInitStructure.TIM_Period = arr;
" u. s7 f3 e0 [ - TIM_TimeBaseInitStructure.TIM_Prescaler = psc;
% u5 j; C, y. s - TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; //只有高级定时器需要设置,其他定时器可以不设置。
9 T0 k0 B" G: k* a. h! w( R, ~$ i( o$ ^ - TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
, A. n3 e9 Q. A( d' p# x3 h - TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;9 b8 G; u# \8 m, v, W- u( f
- TIM_TimeBaseInit(TIM1, &TIM_TimeBaseInitStructure);5 q; [. @: H6 a4 q! u: B, P
- : I+ @* B# t8 @4 c0 S9 h
- TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
- A, D% { X4 r& Z& Z4 c1 \( z - TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;8 b3 }6 v9 c$ Z' }- |% W2 v- l
- TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;; I" w4 L: U6 R, }6 E9 g2 p
- 2 \* r8 w r. l: q3 s0 ~& N
- TIM_OC1Init(TIM1, &TIM_OCInitStructure); //TIM1_OC1
$ c9 j3 B, K% @* u3 S
8 o' {6 P0 ^) J0 x- TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable);8 _# {) m* n& u, B, v% d& Q
- , ^2 X% V" A9 E. H. C5 b# |/ e4 V% Y
- TIM_Cmd(TIM1, ENABLE);5 S3 Y7 r7 V* ~* w2 h
- TIM_CtrlPWMOutputs(TIM1, ENABLE); //高级定时器才有 必须打开2 c- T3 a9 Y0 K5 T1 t. l
- }
复制代码
! 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! ?- //APB1时钟分频为2 TIM2-7 时钟数为APB1 2倍" C8 L; B2 I, c3 S1 d
- // Tout= (arr+1)*(psc+1) / Tclk* F. X+ u/ Z3 L0 _5 [# v8 s; F
- void TIM2_Init(u16 arr, u16 psc)
8 k7 [" X* I8 [: M7 K; C - {
8 e" x- L" L- P - TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
) c7 f5 K4 r" p6 d) \8 t2 |8 K2 q - NVIC_InitTypeDef NVIC_InitStructure;2 h4 Y6 o( W$ H: j; X
- 1 @% i8 t5 I4 b# N6 B9 l
- RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
: @ @' Q- w' T- W5 z' Z; t
& @8 r3 Q ~0 x. G, g8 O- TIM_TimeBaseInitStructure.TIM_Period = arr;
) I1 Q2 o; v& C6 v - TIM_TimeBaseInitStructure.TIM_Prescaler = psc;
- l( I: N, |, t8 Y- G - TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; //只有高级定时器需要设置,其他定时器可以不设置。
+ K9 w% k s3 i* x - TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;, e2 e7 A! ?* M, w* f- c5 L. ?' I
- TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
) g {% g; s9 C& C# Q - TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);
- ]2 W) G- j8 U% K* p8 _/ S
" F9 T; Q% h5 B- TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);0 ~. z' [4 E: a1 y4 r3 M R/ q; c
, n% t: F$ R+ I$ l' [: `- NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;' U" ? W# N( r- q6 w% I$ k
- NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x01;
# x& ~: _# \# k" z7 G% U - NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x01;; m: e- d5 x+ r! H, B% I& r* F& B
- NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
, |: S; M B/ F0 W6 ?. \5 T; j: W - NVIC_Init(&NVIC_InitStructure);
6 L. x3 l% {0 g& g+ c - TIM_Cmd(TIM2, ENABLE);
$ d( y/ R0 j8 C( |' I - }7 Y% A2 b. G3 q- T* C. A- E5 {
2 H+ e/ U, R$ ]/ F5 u) D- u8 dc_cnt = 0; //占空比计数
" D" ^& w$ f# u6 S* m* j - //定时器2中断中改变 定时器1的占空比值
- \4 W' N. @1 E' o - void TIM2_IRQHandler(void)$ e, ^. t' Y) y) M& I2 l) O
- {
# p |( R5 G+ ]- g$ D/ \ - if(TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET)
: n& a% d# I" c3 w; r1 |$ u - {
) I; m/ b( d5 s - TIM_SetCompare1(TIM1, sinData[dc_cnt]);
* R* Z K( ?# ?8 k1 ~ - dc_cnt++;. a! L! _7 x- w
- if(dc_cnt >= PointMax)2 T- e% [1 Y9 r- P% j' V2 X
- dc_cnt = 0;* g W6 \1 P6 w& c3 `& Z# _6 K
- TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
) S% K2 y5 x" k- \$ A4 A - }& y1 t* ^% w: O6 R
- }
复制代码
" [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
/ 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
- void IO_Exti_Init(void)1 v- T2 c: @5 R4 k, u' F3 {
- {
0 Y. Q5 q$ a8 R& Z" O2 L - EXTI_InitTypeDef EXTI_InitStructure;9 U1 C! e$ N* F3 e( v8 V
- NVIC_InitTypeDef NVIC_InitStructure;+ @6 |1 ^8 [: H
- GPIO_InitTypeDef GPIO_InitStructure;
) l) u6 [8 Y, Z- ~! A/ o" I - / p5 I# F c4 Z
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
; x0 ^; a) t: j, j9 Y( Q - RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);- g2 C& r/ ]$ q! i3 R9 \
; D, ]3 T; j$ P2 ^8 [ L" W/ v- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;8 D# y( I# M# @; V
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;
( Z) N0 f, t: f/ S - GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;$ |2 }' z/ g7 q- `# J9 f
- GPIO_Init(GPIOB, &GPIO_InitStructure);
* G7 K8 O0 _" a8 A* N3 }
; O& L0 J$ ^- D8 s- GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource0);
/ ~' T+ x+ {1 P8 \% |
2 c) j4 y. b7 L' d7 a- EXTI_InitStructure.EXTI_Line = EXTI_Line0;
, T3 j9 E7 w* O7 c; U+ ~! y: f4 v - EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
- Y* X8 ^6 K3 z6 ]0 l- c1 S% ~& Z - EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
: h3 `* ?/ }$ | w* ~ - EXTI_InitStructure.EXTI_LineCmd = ENABLE;, h/ J, \6 s* t0 k' d6 n2 _
- EXTI_Init(&EXTI_InitStructure);0 I% v# b) R$ t2 a. z _! W
0 D8 g9 d' O8 v' x; O V# }' i1 X7 C- NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;
( | ~! c% t# r4 C' {. J9 ? - NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02;
1 m m; `3 Y; o+ l! I I - NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x01;
' K | U2 x* X! \# O2 R2 c& F+ p9 n - NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;( U [- N: n! T/ o" s7 ~, Z
- NVIC_Init(&NVIC_InitStructure);
A. L+ {& _- c5 v. i) O* y - }
2 Y5 Z- |& s0 W( y1 ?3 r, N
0 U& U' l1 R: Z5 w# P1 e, B3 U- extern u8 dc_cnt; //占空比计数* Z' v0 l! t E4 X$ U
- //10ms中断一次4 r d7 Y9 [0 ~ W
- void EXTI0_IRQHandler(void)
1 h, T8 t% ?* L$ O - {* F; s8 e2 w- G( |+ k; F/ ?/ H! h
- if(EXTI_GetITStatus(EXTI_Line0) != RESET)" m) J2 m( n- `& ~+ y1 \' w
- {
% w a/ R8 `/ p. a - dc_cnt = 0;( H4 }: l; Y: e7 N
- EXTI_ClearITPendingBit(EXTI_Line0);
; N7 u2 T8 I1 D - }( x/ V7 n8 y9 h) _/ U
- }
复制代码
+ 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 ]
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- void get_sin_tab1( u16 point, u16 maxnum )3 E6 [6 v6 @& q0 @5 ], o7 w/ p0 s. X% \
- {2 ^9 I) P; P+ c0 V& N& Z: A0 q
- u16 i = 0, j = 0, k = 0;2 ~) u M3 N# {, x3 ^' V% f2 x4 Q
- float hd = 0.0; //弧度
t1 N: u. }8 |) M2 E - float fz = 0.0; //峰值
8 g+ S7 O4 o; Y- H - u16 tem = 0;) Q! A X8 w) B4 _
- j = point / 2; //水平线位置 单片机没有负电压 水平线为点值数量的一半1 ~, g3 Z: K' A& t/ c+ G
- hd = PI / j; // π/2 内每一个点对应的弧度值3 A! F; ?* @5 q o: ]8 J+ Q3 H3 J
- k = maxnum / 2; //最大值一半& r/ t" [0 J5 W; {7 S% s' B
- for( i = 0; i < point; i++ ); u7 L1 ?$ N ~. `
- {
' ]8 v% I/ m9 z% `) ^2 y8 E - fz = k * sin( hd * i ) + k; //起点为中间值1 y" ~5 R& Y$ x* W1 S) T
- //fz = k * sin( hd * i + PI / 2) + k; //起点位置偏移到 π/2 起点为最大值3 ?$ x1 Q6 Q* X% {( z
- //fz = k * sin( hd * i + PI / 2 + PI ) + k; //起点位置偏移到 π/2 + π 起点为最小值
* R4 G8 M0 f5 {2 o
+ B; U4 q+ V# n1 b5 @3 j4 n- tem = ( u16 )fz ;
0 `/ n% T! V* G. i, c1 j - sinData = tem;- T/ G, w% k' e3 G* N
- }
7 l- D0 {2 w3 `! ^( Z2 I5 Y - }
复制代码 ) s& k' m' S" H8 v
在生成数据是 sin( hd * i ) ,的值就是初始相位,如果要调整初始位置,只需要将括号中的值加上偏移量就行,默认的起始位置在正弦波的0度位置,如果要让初始位置在90度,那么给括号中的值在加上 π/2就可以了。
, I7 H, ~( g$ |" @
+ d' L: F! Z: C7 c( T
- V# a6 N+ t2 ~
! C' M# u* ]( Y- D" b如果要让起始位置在270度时,在括号中加上 3/2π就可以了。 s \! S. w, Y" r
/ K$ [* }! }/ n5 s+ c
; [; \) Q! g% }6 @/ L5 X' H
$ a7 I0 P8 F' R
; q4 ^* F9 r4 B |
代码下载