对于PWM的捕获,我这里一共使用两种方法实现:第一种是PWM输入模式,采用一个定时器的两个通道(通道一和通道二),配置从模式为复位模式,没有进行溢出处理,所以需要考虑捕获的最低频率;第二种是普通的输入捕获模式,采用一个定时器一个通道,有进行溢出处理,所以没有最低频率的限制。6 a2 p- M! K% X7 o0 v9 [- ^: O/ }5 {
9 u: p7 m$ m6 c' D5 M* i
实验内容:使用高级定时器输入捕获测量PWM的周期和占空比。, s/ n9 |8 A0 n; T( O$ F
# R( k ?$ }. ?
一、原理图2 S0 q+ y, x* |
" D7 f6 g2 k }6 M
; Y. I% A; B* H7 R9 L h! [
1 o( u6 a! k C9 P& b; b/ j—— —— —— —— —— —— —— ——PWM输入模式—— —— —— —— —— —— —— ——5 \, |5 m6 D% Y. \: \) r
4 Z! f( k; J8 h/ [7 _% A
二、 CubeMX配置/ H! G. ?) _; Q1 J% M
) Z* J! ] L$ \
Step1.打开 STM32CubeMX,点击“New Project”,选择芯片型号,STM32F103VETx。
5 G, w8 M9 g0 b1 N1 P
% k: ^9 o- E$ E- V; x T- t* D% r
: g+ T: w2 q* ~6 }9 H
0 {) J1 j, Y1 Y& J+ M Step2.选择时钟源,并配置时钟树。选择Crystal/Ceramic Resonator,并配置系统时钟为72M。
7 q* F7 F! P. F, X' g6 E6 n/ {$ K) l1 F; u/ n u/ O8 q
4 ~' p) e- A- j& q8 M& T: T7 H7 C$ [' I* W6 _( n
$ M5 N1 s _' I* Q9 G( M i/ F: m! x" f( A
Step3.配置SYS,我们这里选择的是Serial Wire。(正常情况配置不配置不影响,debug可以使用。但是你不可以把这两个引脚用于其他复用功能,如果用于其他复用功能,debug就不起作用了。)
0 `8 J! _6 l; A+ p( `, X0 T4 W# @* I/ w+ |$ V7 P# h3 W. g+ O
! w1 k. m. k5 l+ N) ~
* }2 P! y+ S# u9 ?
Step4.串口配置(主要为了在串口调试助手显示测试的时间),因为没有用到中断和DMA所以我们就不过多讲解。
1 p! p2 o1 v. M7 d3 L) X. {9 R
5 Q2 M) T& \+ \% i: I* a# e
( c: T. [( l- a0 ~! Q$ o8 R W. |# k4 R }/ z5 g2 o
9 N0 Z" P! C) N; C5 ^
9 k+ B6 O& V, n1 o( m Step5.接下来进行最主要的定时器配置。首先我们先配置PWM信号,用于等会捕获实验。我这边选用的定时器3通道4(PB1引脚),如原理图所示也就是我们蓝灯的控制IO口,我们更直观的观察到PWM信号的情况。$ I, a# L6 ^; ~6 W1 ~5 J0 W
1 b4 u* s, g3 [4 S1 y: B
" y' R4 j+ U: h) ~5 S' Z
, a6 m6 m B4 x6 B0 U
; C/ _0 Y% Y( g( w' a+ z# q' N; B, ^! p% n6 {+ m
7 t( S2 J, J/ U' V. G Step6.定时器输入捕获参数的配置,因为我们使用的PWM捕获模式,我选用的定时器8通道1和通道2.(更改一下:直接模式和间接模式应该叫直连模式和非直连模式)
( `7 d& X- x6 l: y8 K0 F- i2 I* ~$ O
o \4 l4 d0 L2 b/ F; H/ }
2 O5 m# E9 h1 q: B& ?2 }8 G
* C+ \/ C3 t# K4 \$ E$ u6 @
\% K, I& Y5 N8 L2 G) E
2 L- d3 M% _* @7 ?" @
5 O8 l1 u5 ]1 e: T5 J# z f7 E 到这里关于相关参数配置基本已经完成,只需要根据之前文章《STM32Cube HAL:GPIO输入/输出(一)》Step4-Step8,设置相关工程参数和生成代码。
`" e. P( h$ `; C) M: [2 @8 _0 w2 E; i7 L7 a" {
三、添加功能代码
# e' ]; s8 u0 I% p3 O- [
& H, ?$ A/ H6 T* B) u+ v; E6 V1、我们等会会向串口调试助手发送数据,进行实验结果的验证。 发送数据我们采用printf函数,所有需要重定向c库函数printf到串口。注意使用时需要在keil设置中勾选微库(use mircolib),同时需要添加头文件#include <stdio.h>。重定向代码如下(usart.c)$ p9 f3 q. T; S! W9 z( @
+ T( y, z, B- _$ R o0 D/ F
- //重定向c库函数printf到串口DEBUG_USART,重定向后可使用printf函数
& E) O' M4 N+ Q4 j' E- b; ` - int fputc(int ch, FILE *f)2 z+ ]+ o% \! w# s, W3 }1 E o
- {
]# Z; }5 Q1 Y$ P' u - /* 发送一个字节数据到串口DEBUG_USART */
% ]* @5 M2 N! Q/ I0 B" @ - HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 1000); & k: |1 O1 Z! s& p# O' h! k& V
-
% V( R; m" y t4 x- s7 V - return (ch);& C2 N& T7 R0 f8 x1 G! m7 U
- }
' C) K4 k: a" f* J4 m+ H
. c1 Y0 i3 \0 S& Y4 ~- //重定向c库函数scanf到串口DEBUG_USART,重写向后可使用scanf、getchar等函数
& J0 t8 E& H4 d$ Z9 m/ z - int fgetc(FILE *f)
# p u) R& |! @4 { - {
* z- N$ N6 }2 Z8 Y* e" T. b - int ch;3 H+ Q' f! {* l- e# x0 \; z
- HAL_UART_Receive(&huart1, (uint8_t *)&ch, 1, 1000); : Z( [8 P6 E0 A! U
- return (ch);
; ~: t5 Z6 M q. O" z. D- n - }
2 G7 [7 |. Q: | `# }
复制代码
- ^# P( w8 b; i. \3 S/ Y) X" A 2、定义相关变量,以及使能相关定时器和计数处理代码(main.c)7 ?/ e9 v6 O x2 Q* m
6 \. O- D+ F6 H0 a; Q; }
- 宏定义、全局变量3 N) z6 o- U& \
- #define cnt_clk 72000000/(71+1)//计数器频率0 {8 f$ Y7 D; {2 \& v9 k
- #define arr 65535//自定重装载值
2 y8 n! ~/ b6 C9 X - uint32_t CCR1,CCR2,end_flag;//存捕获寄存器获取的值的变量
+ Z. j/ T$ k3 u, R* ]- k/ ] - float duty_cycle,frequency;//频率,占空比
复制代码- //使能相关定时器以及计数结果处理代码3 |0 ~* P$ @5 ~6 ~3 h0 ~
- HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_4);//开启PWM输出,不需要中断: B( y6 W/ Y A& O8 D1 K1 a
- HAL_TIM_IC_Start_IT(&htim8,TIM_CHANNEL_1);- `1 o2 ^3 K. h! H% J1 M$ ^
- HAL_TIM_IC_Start_IT(&htim8,TIM_CHANNEL_2);
复制代码 - t' {% ]/ j$ v" C: k c- _; `
4 W j( s# }* O! G, j- //处理代码+ o$ P* U; H+ L
- while (1)
+ U4 U% h% N, I% ` - {
. T; i2 G- g0 u) X9 n" t - if(end_flag==1)
! R) {" |# |0 b1 q5 T9 j - {
$ ]; F- L, M, d$ U* ?' }/ k+ h - printf("\r\n频率=%.2fHZ,占空比=%.2f%%\r\n",frequency,duty_cycle);! T% D2 }2 s' f1 t0 {6 P
- end_flag=0;
: }. g+ A! H. b# S8 @& d7 i - }, g( R: M' g& [$ Q2 `. q
- HAL_Delay(1000);
$ E5 E+ ^' K; M& O( b; k - }
复制代码
6 P4 z1 G6 E* H) k3 z. D 3、中断回调函数(捕获中断)(main.c)
% W8 ~1 x, ?/ e- G( p2 R! r) S) R
f& b' z/ ]8 g* y: d4 q8 C- void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
/ r L8 l9 l# J2 Y9 q& r/ g - {
+ I& ?, j* _4 x" X' d - /*PWM 信号的第一个上升沿时,定时器产生中断,计数器,CCR寄存器被复位。; F: d- I1 |& L: q. I
- 当下降沿到来时,IC2 会捕获,对应的是脉冲宽度测量,但不会产生中断。当第
3 d; z; u3 r0 l2 T; Y - 二个上升沿时,IC1会捕获,对应的是周期宽度测量,而且会再次进入中断*/
" B5 V* K$ n3 L( O - //printf("0");# E) B# H5 z0 h" B# B: W7 `- B
- CCR1=HAL_TIM_ReadCapturedValue(&htim8, TIM_CHANNEL_1);. M2 A6 l: B' c2 Q! f. i* v V
- if(CCR1!=0)
5 ]+ e5 ~: a2 P8 C0 [; ] - {: J7 e Z4 c9 W, F
- CCR2=HAL_TIM_ReadCapturedValue(&htim8, TIM_CHANNEL_2);
0 R; s+ k C9 j' a2 ^ ?2 i - frequency=(float)cnt_clk/(CCR1+1);* |3 x! k- Y4 h3 q& p. Q
- duty_cycle=(float)(CCR2+1)*100/(CCR1+1); 0 x2 Z/ ]! o6 }: D
- end_flag=1;& `4 D- m1 B. W+ A$ v" D9 S9 y" k
- }
# a! J' L" U( a5 D! ^! R - else2 U! u* ^& s0 b5 b3 i8 e! R# w
- {5 \ S, d1 G" L! @; d
- frequency=0;
/ ]& s& D8 o4 B& C: l! @8 R - duty_cycle=0;) A( Y h1 ~* Q5 C) y/ z
- } a" {; v+ q$ M, |6 t+ `. G: B
- }
复制代码
9 h. M9 ? N# C$ B0 _, R9 v7 x. L1 ]7 z' s- {5 {1 t
关于PWM模式主要需要的注意有两点:
9 _- f `# b( @/ u/ O* v) w" Q& F `/ I: A* W$ w
一、PWM模式只能使用通道1和通道2,PWM输入模式工作原理如下图:/ n* z4 Z+ L, k: W! P5 S/ B
% w7 o- w8 [+ y+ W- G
/ Q4 x: q0 x- R6 b( H
5 j* r! ?# `, ?# k
* ^; V X& {9 ?: r+ T" X
' O: y' j7 r$ ^3 Z4 d二、用于捕获的定时器时基设置,需要考虑到能捕获的最低频率。/ f$ x3 d" b$ I2 E; I* G
. ?' o4 H/ L$ N: P( z h* m
公式:能捕获的最低频率f=(时钟频率/(分频系数PSC+1))/(重装载值ARR+1)
/ i3 u: D2 A: p% H/ Q9 G+ }4 m7 s3 U5 {7 l! {( W. N& I3 C* _
结合这次配置的时基,可得:f=(72000000/(71+1))/(65535+1)=15.2HZ0 Y) h& A! d1 H4 I" u1 q
& R% p* k2 o! u! o
—— —— —— —— —— —— —— 普通的输入捕获模式—— —— —— —— —— —— —— A+ I- R" ] _7 h8 g7 F
: i; {8 b. n: Y+ A8 `7 s( R' V 二、 CubeMX配置! l# c5 Y+ ~8 m m7 \; y k: ]
8 z, X. S6 P+ x y4 `
step1-step4与上面PWM输入模式一致# K( @) v5 Y6 ?/ {9 {
9 _( n. s% S* y) M# j9 e3 x3 p; x
step5.同样是配置PWM信号,用于等会捕获实验。我这边选用的同样是定时器3通道4(PB1引脚)。唯一不同的是,为了验证溢出处理的功能,我做了一点小改动。配置为频率20HZ,占空比为60%的PWM。
2 ^- w( }# R3 U: U, E) o
' P6 F& T6 x2 w k9 E4 B* e- J
9 ]* l) B; ]( ~ [* M# I3 z/ ]
7 W/ H" T6 L2 k! M
. O. Q9 g* j! [5 W! b9 x7 g3 w7 v2 O- a* ~( R2 Y
; N) h/ y* z. c' L \" M2 Z" B( w" S( v8 r8 [! A/ ~4 p
Step6.定时器输入捕获参数的配置,因为我们使用的普通的输入捕获模式,我选用的定时器8通道1。这里没有用到从模式,所以不进行配置。捕获频率最低频率设置为33.3HZ,增加使能更新中断,对溢出进行处理,解决了最小捕获频率的限制。因此我们可以捕获小于33.3HZ的频率。& N' G5 v4 q0 p; Y: ]
1 B+ Y6 w8 ^/ O+ g- [7 e
$ n2 _/ I$ P% Y: Y3 Y! j
# p3 V7 A; e& ]
$ p4 z; a; A& T- d3 D/ O+ r/ b# F* _4 C9 ~# L2 x: S
# V, F T/ q. g* Q' m" L6 I9 B! Q
9 p/ A- W: o6 j [% ?8 R
@! I" X0 l, q' X& {( b. v3 j) v8 m/ i* a3 V
到这里关于串口参数配置基本已经完成,只需要根据之前文章《STM32Cube HAL:GPIO输入/输出(一)》Step4-Step8,设置相关工程参数和生成代码。9 Q: J6 [# D4 c) o' f
6 K8 W6 M6 s- S4 U 三、添加功能代码
3 e7 C& {, w4 w2 m5 ]9 V6 C8 y F7 M
1、我们等会会向串口调试助手发送数据,进行实验结果的验证。 发送数据我们采用printf函数,所有需要重定向c库函数printf到串口。注意使用时需要在keil设置中勾选微库(use mircolib),同时需要添加头文件#include <stdio.h>。(代码同上)
1 h, v6 g1 u1 u/ l5 I7 L8 @* ~4 q. s" S" N8 f/ K) w' M) z
2、定义相关变量,以及使能相关定时器和处理代码(main.c)+ |+ z! \& n" I; D( [1 J
) ?- K" B A s, _' P+ V- //宏定义,及全局变量; p7 W* F% G+ V7 X0 W
- #define cnt_clk 72000000/(71+1)//计时器时钟) U/ U; Y+ [- U% t8 |3 A
- #define arr 30000 //重装载寄存器的值,根据实际情况设置。 u4 R; {, [* q+ F' B3 ~: ~8 A
- uint32_t ccr_cnt1,ccr_cnt2;//存捕获寄存器获取的值的变量 R6 G7 P! H) N* K5 ^% g
- uint32_t Period_cnt,Period_cnt1,Period_cnt2;//更新中断次数以及存放更新中断次数的变量
: D, `9 ^0 R" O9 Q/ o9 { - uint32_t ic_flag,end_flag;//触发标志位,捕获完成标志
" |7 P+ I; h3 l/ u - float duty_cycle,frequency;//频率,占空比
复制代码- //使能相关定时器功能
9 P% P. ]; D- }. x2 l2 s$ D( M; B# ~ - HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_4);//开启PWM输出,不需要中断9 i/ Q! A4 {6 b/ h6 Q! k
-
1 o* t- X$ j7 B. _- @& V - __HAL_TIM_CLEAR_IT(&htim8,TIM_CHANNEL_1);//清除更新中断标志位,防止一使能就进入更新中断
. f5 ]) I& m' M9 S# \& m - HAL_TIM_Base_Start_IT(&htim8);//使能定时器,更新中断
' m- ^" e& ~5 S3 c0 T - HAL_TIM_IC_Start_IT(&htim8,TIM_CHANNEL_1);//使能定时器,使能捕获输入以及捕获中断
复制代码- //捕获数据处理
4 t: Q& o; i# U I6 n - while (1)$ m! m' m7 d( M- R& d
- {
4 H6 K4 y o! K$ S2 e0 ~" j _
5 t) t4 ^5 y( k5 {) E- if(end_flag==1)//捕获完成标志位
& l* r/ `* N" s+ H - {" O4 G0 x. A8 U. K' }' Q* z i
- duty_cycle=(float)(Period_cnt1*(arr+1)+ccr_cnt1+1)*100/(Period_cnt2*(arr+1)+ccr_cnt2+1);( ?2 F, ^* Z8 T- C* B( `
- frequency=(float)cnt_clk/(Period_cnt2*(arr+1)+ccr_cnt2+1);
3 s- f# m$ h% V/ V0 V- M - printf("\r\n频率=%.2fHZ,占空比=%.2f%%\r\n",frequency,duty_cycle);/ O# T& s' J+ J/ ^9 E
- end_flag=0;//复位捕获完成标志
# ~& q# C2 A2 e3 u% T: r- s* W -
8 P8 J$ m4 D( e& s8 ^ - }
4 B; Y% Z# w7 X - }
复制代码
0 f% \! ?+ _% c, b- O' A" R# l 3、中断回调函数(更新中断、捕获中断)(main.c) |$ e; ~ E. a6 N" W4 z/ Q
# Z R$ W' C0 _
- void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
% `" g. d1 }- |" I4 V1 A - {7 q& T5 K# s9 K
- Period_cnt++;
+ w; t+ y; |! S4 G - }
复制代码- void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)5 r: W, e. ~0 j3 m* F+ r+ r
- {
2 @; x# a: _( R8 T0 J7 n2 Z - switch(ic_flag)//触发标志位判断& G3 Q2 u- ~9 ?* M! G% R, G U
- {7 \7 ]( b' V" R4 W( C* j
- case 0://第一个上升沿捕获
; ^- a, j0 a( ?& n - {5 @. [9 h4 s4 U8 I2 A$ o
- __HAL_TIM_SET_COUNTER(&htim8,0);//清除计数器的计数
6 {3 J/ o9 E6 Z1 ~2 G - ccr_cnt1=0;//初始化相关变量) f) x& W% x1 k0 I4 b
- ccr_cnt2=0;' W- i! O! S! t- R( d! a
- Period_cnt=0;
& |+ E. q$ C1 d t - Period_cnt1=0;
( X! o0 M s, x3 ~, ^$ S - Period_cnt2=0;
8 y# g# A( z- I - __HAL_TIM_SET_CAPTUREPOLARITY(&htim8,TIM_CHANNEL_1,TIM_INPUTCHANNELPOLARITY_FALLING); //设置成下降沿触发 6 ]' t# S' g5 D, M# T' B6 r
- ic_flag=1;//更改捕获标志位,进入case1进行相关变量的处理。/ ~6 N2 L: }& c3 |, C S4 c6 F: u
- break;0 s( A0 A' j/ Q* T
- }
1 [* W3 G/ i/ X& J2 o5 Y! o7 t# t9 s - case 1://第一个下降沿捕获; D/ h: e8 c& z
- {' ~- r- e9 Y3 | O' \+ d8 T, \
- ccr_cnt1=__HAL_TIM_GET_COMPARE(&htim8,TIM_CHANNEL_1);//获取存放在CCR寄存器的值(捕获值)
9 q5 C2 N, x- w" r" a4 x - Period_cnt1=Period_cnt;//获取第一个下降沿到来时的进入更新中断的次数* L4 a7 d2 s# V$ {: f
- __HAL_TIM_SET_CAPTUREPOLARITY(&htim8,TIM_CHANNEL_1,TIM_INPUTCHANNELPOLARITY_RISING); //设置成上升沿触发
' u" Q" @( q. \8 @ - ic_flag=2; //更改捕获标志位,进入case2进行相关变量的处理4 V/ d! g C! ~, y4 I2 k' l' j
- break;
1 L; b5 Q: k2 N# w* D$ w - }8 W/ E; S j0 ?4 e) {. @4 u
- case 2://第二个上沿捕获 . k) \# O2 U+ N5 w3 G2 p
- {
& c6 m$ Z3 P: Z$ H6 F - ccr_cnt2=__HAL_TIM_GET_COMPARE(&htim8,TIM_CHANNEL_1);//获取存放在CCR寄存器的值(捕获值)" O" F- d0 Q$ K! }) f
- Period_cnt2=Period_cnt;//获取第二个上升沿到来时的进入更新中断的次数7 S2 M# s- ?# i( i! N2 Y
- ic_flag=0; //更改捕获标志位,进入case0进行相关变量的处理。: `7 ?3 m2 u! |4 X
- end_flag=1;//捕获完成标志
' N. Q0 \. Y7 L3 Y' X - break;0 h C+ k; ~) y; k! p
- } , Z7 u9 P1 V7 E3 I
- default:+ m& M; R4 H l* O" `- |: ~3 x
- break;) R! H( ~0 I: ~) o. V
- }
& @6 ]/ Y& L. z - }
复制代码
^& J6 O5 ?; u" a$ h1 a7 A1 H& t0 a: E) w
关于普通的输入模式捕获PWM。主要工作流程:8 _% E4 o/ m# q
5 Y' Z% \- ^ d0 D. }7 f. I4 f1、第一次捕获到上升沿,计数器清零,存捕获寄存器值的变量清零,存中断次数的变量清零。改变触发边沿为下降沿触发,并将触发标志位为设置为1,使下次下降沿触发时,能进行相对应的处理。8 F$ V. k$ i2 X
7 o5 ^1 d; Y9 O! q) C4 |2、第一次捕获到下降沿,将捕获寄存器(CCR寄存器)的值,中断次数存入相应的变量。同时更改触发边沿为上升沿触发,并将触发标志位为设置为2,使下次上升沿触发时,能进行相对应的处理。
4 p, |. i' N. u
3 N1 |& r+ T& ]( Q2 @; Y7 `3、第二次捕获到上升沿,再次将捕获寄存器(CCR寄存器)的值,中断次数存入相应的变量。将触发标志设置为0,目的为能够循环捕获。同时捕获完成标志为置1。1 c9 \% l1 b- _
/ |* F% T& l0 P5 n 4、在main函数中,使用在while循环,使用if语句进行捕获标志的轮询,从而在捕获完成后进行相应的数据处理。) [/ F9 ^3 `5 t4 X: _* ]' i. s
6 l) `( \/ z; v
虽然目前代码的配置以及实验结果是没有问题的。但是在调试时候,碰到一个比较疑惑的问题,ARR的设置影响了捕获的准确性,比如在设置为999和1000的时候,同样是存在溢出的,也进行了溢出处理。但是AAR=999时候频率不稳定,AAR=1000频率正常。暂时还是没法找到合理的解释。
$ C8 h5 o6 F- H7 c* l; o7 w4 Y: s f" @3 I
; A G% V- m. y, y9 A# L
|