对于PWM的捕获,我这里一共使用两种方法实现:第一种是PWM输入模式,采用一个定时器的两个通道(通道一和通道二),配置从模式为复位模式,没有进行溢出处理,所以需要考虑捕获的最低频率;第二种是普通的输入捕获模式,采用一个定时器一个通道,有进行溢出处理,所以没有最低频率的限制。% J# u- V8 O# ?3 e2 ~
$ k$ o8 l; O9 \ 实验内容:使用高级定时器输入捕获测量PWM的周期和占空比。
. I; e& _: I' \# i0 @8 g+ H. I% c, Z. c6 e
一、原理图. V7 Q! ^- ?, e4 g+ K7 m. d
. g3 n% ?! Z; s& h+ j
0 N; i; A7 [8 U% X: s# `6 d7 U9 M) @. z- h
—— —— —— —— —— —— —— ——PWM输入模式—— —— —— —— —— —— —— ——
: m. u2 X) U4 t: T/ m
8 E* J( P* `8 g+ D9 K+ ~ 二、 CubeMX配置
6 |6 [' ]" [" h) {4 E, q% o3 C6 a# d! \# H7 M4 P+ J
Step1.打开 STM32CubeMX,点击“New Project”,选择芯片型号,STM32F103VETx。0 t" \4 X+ U; M% G/ h; R, {
, F1 W* _! m5 {8 A
7 h3 l5 s1 X0 E3 ?" G
* M. ~- O2 K* j* P# ^9 z: a Step2.选择时钟源,并配置时钟树。选择Crystal/Ceramic Resonator,并配置系统时钟为72M。
8 P/ Y4 p+ m! B# P; p- y8 m3 n( N2 `5 v% N. y# m
, D" \: k+ Z5 ?, j- a
2 g, O& a8 C% p; |! f
, j' C2 s4 ~0 z3 T/ N7 O5 h; X; p0 y* p; x+ s5 T8 Y( O _0 t4 C( R
Step3.配置SYS,我们这里选择的是Serial Wire。(正常情况配置不配置不影响,debug可以使用。但是你不可以把这两个引脚用于其他复用功能,如果用于其他复用功能,debug就不起作用了。)2 p7 Y! y$ V5 w# D
4 J; s( m, G: w/ M- h. B
- j1 H/ u G \- H0 Z4 s& q
* v7 @, ^! X$ T7 l3 tStep4.串口配置(主要为了在串口调试助手显示测试的时间),因为没有用到中断和DMA所以我们就不过多讲解。
; u ~- J7 ]) A$ d% w% y+ I$ s
) I; N- j8 {, W& n/ m: a4 U+ y
' o7 L% G: y0 l$ k7 X: Y- H6 L" I5 p. [
' A$ h. V* V6 X. v: T$ \
+ X. l5 i( d+ W0 e$ o8 [
Step5.接下来进行最主要的定时器配置。首先我们先配置PWM信号,用于等会捕获实验。我这边选用的定时器3通道4(PB1引脚),如原理图所示也就是我们蓝灯的控制IO口,我们更直观的观察到PWM信号的情况。
5 s# I$ P- h. V' q. r
6 U* J# e' `# i" A5 o
, i5 Q7 _; t5 B) P6 M% k8 q5 s) r( Y$ m
7 `* h( Z; q( [$ @! b, u* r7 v) h2 x
" h6 y3 J6 s3 H/ }2 ?7 F7 F! S* x% G O; ~9 V7 m1 w
% S( M5 i9 S0 H$ ^4 Y. Y Step6.定时器输入捕获参数的配置,因为我们使用的PWM捕获模式,我选用的定时器8通道1和通道2.(更改一下:直接模式和间接模式应该叫直连模式和非直连模式)- \2 z) \+ `& P: X
$ j0 L5 z0 F: Y, G8 X2 O
' G5 [, s1 Z7 ~0 F
. {" @, X- u5 h5 n
. ^+ M5 ^% ~; Y+ |
/ ^5 N9 u B2 R% \
4 Q" T1 W% S4 f: Y( l+ r7 u# m; w* e1 u2 r2 b. l
到这里关于相关参数配置基本已经完成,只需要根据之前文章《STM32Cube HAL:GPIO输入/输出(一)》Step4-Step8,设置相关工程参数和生成代码。
, R/ H* g# [9 G4 x; f y6 Y5 X, g4 a; }/ `/ v! X
三、添加功能代码
3 W. v/ B* |# |- b5 y4 w: O5 L
9 H2 r+ C& f$ z4 {1、我们等会会向串口调试助手发送数据,进行实验结果的验证。 发送数据我们采用printf函数,所有需要重定向c库函数printf到串口。注意使用时需要在keil设置中勾选微库(use mircolib),同时需要添加头文件#include <stdio.h>。重定向代码如下(usart.c)
1 Y% Y8 d6 {8 _" |
+ I6 F# R& N& ~) A% J! [- //重定向c库函数printf到串口DEBUG_USART,重定向后可使用printf函数
' B6 n2 ^1 @# V' Q6 ?$ [! k& v; d - int fputc(int ch, FILE *f)
2 i5 y8 F* F$ E" Y7 L( v) Y& P - {
, | v8 _* b* A( B+ l - /* 发送一个字节数据到串口DEBUG_USART */, }- l& _9 J% X! H7 ]7 i$ G" }! R5 r
- HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 1000);
" H6 @) R: h8 h$ E( g! k - 9 ]2 m6 H; t/ V; M* w4 V
- return (ch);. t9 w$ q2 |7 [$ e% Q' p
- }6 T( R( p0 V9 C" t$ t+ d) I
% A3 m& W ^' E$ p3 v: i. ]- //重定向c库函数scanf到串口DEBUG_USART,重写向后可使用scanf、getchar等函数
5 X1 [3 N2 @/ b( z1 j' R - int fgetc(FILE *f)# i. l. H* ]' ]0 x6 b/ A z
- { / B* w' B5 f! O/ ]
- int ch;
* z7 [" Y( y) B7 P - HAL_UART_Receive(&huart1, (uint8_t *)&ch, 1, 1000); ! f$ m7 @2 {- w* K' y8 ?7 U$ K8 \
- return (ch);
& Z+ f1 M5 W! ^- i* N( {' w1 O" e - }% h+ t: h! k: }1 c
复制代码 8 k$ Z+ i+ V" d$ }
2、定义相关变量,以及使能相关定时器和计数处理代码(main.c)
" ~6 l0 s3 e$ Y5 y. V. g Y, J. o6 t9 N* \7 y4 g; I" N; G! S
- 宏定义、全局变量! F6 N( M9 E* s% f7 X
- #define cnt_clk 72000000/(71+1)//计数器频率2 I3 n1 t$ Q0 k e3 p7 R* A: Z: y
- #define arr 65535//自定重装载值 % h. B/ v1 E. J
- uint32_t CCR1,CCR2,end_flag;//存捕获寄存器获取的值的变量
" m w, p2 \. d S4 c - float duty_cycle,frequency;//频率,占空比
复制代码- //使能相关定时器以及计数结果处理代码' D( [: C/ q3 S; ^' g/ O
- HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_4);//开启PWM输出,不需要中断
, S$ ?3 Y( b ?% \! \) n; S - HAL_TIM_IC_Start_IT(&htim8,TIM_CHANNEL_1); G& X3 m" ]4 Q
- HAL_TIM_IC_Start_IT(&htim8,TIM_CHANNEL_2);
复制代码
. `7 O* R! {+ {9 c! ?: y9 }: O 1 c8 A! ^. a" v5 `! f% U
- //处理代码
- H( j" x8 E# N% V- ~; p - while (1)7 S% E+ q* d# |1 ], t" {8 _
- {2 U6 H- ^) P& U
- if(end_flag==1)
( m* ]* M- g: w& m" p - {* I; r/ I+ b! L, e E
- printf("\r\n频率=%.2fHZ,占空比=%.2f%%\r\n",frequency,duty_cycle);3 L- e7 _5 n7 S, L
- end_flag=0;8 w$ M2 ~) E) n/ Z: N; X
- }% M' b8 S5 i& \- s+ ], @
- HAL_Delay(1000);
- g! |. g* H. }# ]3 ^5 i" f - }
复制代码
% ] Q8 U5 {- k. N& a 3、中断回调函数(捕获中断)(main.c)
; w* w1 w' U4 V( k a
7 j; x- |6 ]) A* y" ?4 J- void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)" h( P* u1 f( J0 c# Z
- {
" F/ _6 \% c% Z2 z8 H4 Q# S - /*PWM 信号的第一个上升沿时,定时器产生中断,计数器,CCR寄存器被复位。# |# N$ n2 k/ @3 y! ?6 Y. N
- 当下降沿到来时,IC2 会捕获,对应的是脉冲宽度测量,但不会产生中断。当第: m0 N6 r7 p& \1 _8 k3 q/ _; d
- 二个上升沿时,IC1会捕获,对应的是周期宽度测量,而且会再次进入中断*/
2 S4 Q( ]& D1 M: @& @' ~8 Q - //printf("0");
! P/ C: w1 E1 E - CCR1=HAL_TIM_ReadCapturedValue(&htim8, TIM_CHANNEL_1); W0 z6 |! G4 @' J: A/ y
- if(CCR1!=0)
7 o( D b7 ?8 l6 s - { Y: [. I# K# b
- CCR2=HAL_TIM_ReadCapturedValue(&htim8, TIM_CHANNEL_2);
; }6 o2 C$ f8 q/ b% u - frequency=(float)cnt_clk/(CCR1+1);
( c/ L8 ]: @0 }' V) E0 d$ o: T - duty_cycle=(float)(CCR2+1)*100/(CCR1+1);
6 M1 h! [) k( g* ^% u - end_flag=1;
7 B8 C2 a0 B5 ?5 i - }
% g" q5 L% z8 X. |. q/ m4 a - else
# {8 T5 }) `7 P - {2 A" s& k& ?+ P8 h* p
- frequency=0;
( Y$ d: F9 F# S# U4 K( k4 Q - duty_cycle=0;
* u: j3 P3 v& O% m - }/ J0 M9 X0 `. I2 r0 Y) Q
- }
复制代码 ( `- p& R2 U5 e, R0 P
4 t6 }7 x1 j p
关于PWM模式主要需要的注意有两点:
' D1 A4 C0 G3 l! k+ C* i5 ?! P
- V9 |2 `/ i0 q% o一、PWM模式只能使用通道1和通道2,PWM输入模式工作原理如下图:
7 U3 F5 r7 W9 Z; j' h$ l* \2 ?& S; w/ }& D0 H
* [2 {+ Y3 P4 Y
# R1 J' k n( @
' _6 H( V! j& j6 R1 G( ~( x
( ~" X, y1 k* c1 [二、用于捕获的定时器时基设置,需要考虑到能捕获的最低频率。
6 h' e% ?; F `/ r; Z1 m& |# X
2 d; r7 c: O; v; V5 Y3 O J+ [公式:能捕获的最低频率f=(时钟频率/(分频系数PSC+1))/(重装载值ARR+1)
3 J( C- \ h9 N8 i1 t
$ M0 ] l; ?& f' G 结合这次配置的时基,可得:f=(72000000/(71+1))/(65535+1)=15.2HZ
P% o3 ^9 H/ D' D8 j
% U0 g2 o) T/ v1 }' A0 [; O( {0 X—— —— —— —— —— —— —— 普通的输入捕获模式—— —— —— —— —— —— ——+ v( l& ]- W" U, |4 J# I! p4 a
. b$ D& `4 d- x; y1 k9 \0 a4 r
二、 CubeMX配置( w( {1 L$ F4 j! t0 f" p, u% w
' a. @" j7 W0 u+ `# ^
step1-step4与上面PWM输入模式一致
& e5 B# I9 ]/ _( g8 h D2 W' g3 o& i: n1 x: ~+ N0 P0 x- H
step5.同样是配置PWM信号,用于等会捕获实验。我这边选用的同样是定时器3通道4(PB1引脚)。唯一不同的是,为了验证溢出处理的功能,我做了一点小改动。配置为频率20HZ,占空比为60%的PWM。
3 f5 w2 ? O5 `+ a: k
* d' k& T5 r6 [3 q8 [6 d
' o' Z$ {8 G% Q* @2 _
) Q" u- n! _7 v7 p/ m' p4 _
! t4 r; [. E2 V4 B- f/ N6 K- `
5 ^! s: `. t" L$ F
, ?- f2 A; N$ R+ C/ V9 N
0 e; B7 y: Z' M1 b" W$ k5 P Step6.定时器输入捕获参数的配置,因为我们使用的普通的输入捕获模式,我选用的定时器8通道1。这里没有用到从模式,所以不进行配置。捕获频率最低频率设置为33.3HZ,增加使能更新中断,对溢出进行处理,解决了最小捕获频率的限制。因此我们可以捕获小于33.3HZ的频率。
7 a% j" B; q* {8 R/ X3 V: c& [* y& I. u) |# u: o
% s: q( I) n* A$ U V
/ G0 ~- [9 ^! t8 Z* _* l" m
0 Q) D6 \: G8 j- v
' u( @% n+ ]( {% S7 ?
$ o5 a1 s0 B: P- k
I* ~1 i% ~4 T. m# F. p
/ ?$ w1 e& g) k) h7 V3 s/ y' I
# A2 H8 F a9 Z* l到这里关于串口参数配置基本已经完成,只需要根据之前文章《STM32Cube HAL:GPIO输入/输出(一)》Step4-Step8,设置相关工程参数和生成代码。9 I6 M+ F1 }" ~, |0 k
8 b1 s S4 i# Y! G$ S( N9 }+ y 三、添加功能代码
! G; B8 m& s/ n; k! _. p# v6 z" w9 h% s- f: [1 c# J+ ~4 E0 L
1、我们等会会向串口调试助手发送数据,进行实验结果的验证。 发送数据我们采用printf函数,所有需要重定向c库函数printf到串口。注意使用时需要在keil设置中勾选微库(use mircolib),同时需要添加头文件#include <stdio.h>。(代码同上)
. E8 W' V8 L- n, B, X7 F% R- O$ p; e+ m7 _
2、定义相关变量,以及使能相关定时器和处理代码(main.c)9 F6 y/ W: ~# \* U& U- u
# ]% {; ?$ U) N5 ]
- //宏定义,及全局变量
0 u M5 o# p: R2 e0 O4 j3 m5 f9 P - #define cnt_clk 72000000/(71+1)//计时器时钟
' F* J) ]- p$ X- ? - #define arr 30000 //重装载寄存器的值,根据实际情况设置。
- _" }, p6 m/ @* n. f - uint32_t ccr_cnt1,ccr_cnt2;//存捕获寄存器获取的值的变量
1 l! u6 _$ r; Z/ V - uint32_t Period_cnt,Period_cnt1,Period_cnt2;//更新中断次数以及存放更新中断次数的变量
& b) O; E4 ~, b" p+ E1 E9 I - uint32_t ic_flag,end_flag;//触发标志位,捕获完成标志/ _ L; X1 k; c8 @9 R3 x# }* z
- float duty_cycle,frequency;//频率,占空比
复制代码- //使能相关定时器功能. ]# s6 H: o( L4 c; B) k
- HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_4);//开启PWM输出,不需要中断 S j# m- A% S
-
) B0 k3 o) K. B1 ~% P3 O# O' v - __HAL_TIM_CLEAR_IT(&htim8,TIM_CHANNEL_1);//清除更新中断标志位,防止一使能就进入更新中断
! K- e8 k9 w* h7 |" T* P$ [ - HAL_TIM_Base_Start_IT(&htim8);//使能定时器,更新中断
/ l6 o8 j0 @- l/ k! G8 L1 Q, ` W - HAL_TIM_IC_Start_IT(&htim8,TIM_CHANNEL_1);//使能定时器,使能捕获输入以及捕获中断
复制代码- //捕获数据处理; Z: d: B }( U! S! L
- while (1)6 [1 t9 y" M7 Y8 e% J7 o( T
- {
7 K9 H7 ]: `7 I! o - 0 G2 n8 p- M7 B
- if(end_flag==1)//捕获完成标志位
+ i: G8 t+ n$ V1 P - {$ K8 y% Z a) [8 n2 R/ X
- duty_cycle=(float)(Period_cnt1*(arr+1)+ccr_cnt1+1)*100/(Period_cnt2*(arr+1)+ccr_cnt2+1);) S2 o4 h8 \3 U- b6 V
- frequency=(float)cnt_clk/(Period_cnt2*(arr+1)+ccr_cnt2+1);/ x# f& p: A; G: @+ Z+ d8 ~
- printf("\r\n频率=%.2fHZ,占空比=%.2f%%\r\n",frequency,duty_cycle);* u) y2 c3 `# Q8 Z2 W A
- end_flag=0;//复位捕获完成标志
# L3 v( n$ F; ~$ [: M7 v3 ` -
/ \) K, d7 j6 v - }
2 s! [ l/ ^# c' V& ], {9 x - }
复制代码 6 l9 Z0 z+ Z/ C
3、中断回调函数(更新中断、捕获中断)(main.c)+ d- j. f" Z) l& V
0 d; Q$ w- _6 P1 _1 c- void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
" _8 d- L3 f; H0 E8 b2 I - {
+ }& l: ^" s% v: X7 } - Period_cnt++;& ]; K3 }# I/ m8 h: n
- }
复制代码- void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim). |2 q3 J+ h2 U( A
- {/ w, H* S2 i% P* O0 H1 j* I
- switch(ic_flag)//触发标志位判断
- M, W& k- j' f- f! r, w7 | - {
6 M' U- y- U4 w. C5 j! L3 E - case 0://第一个上升沿捕获
: q# |- [0 p }4 P6 n6 ]* Q' v - {* |' R' o9 |% _% p' [
- __HAL_TIM_SET_COUNTER(&htim8,0);//清除计数器的计数
. O. u$ f9 I& A* B5 L& L. S3 q - ccr_cnt1=0;//初始化相关变量9 s; y5 e/ i1 v% ?5 K
- ccr_cnt2=0;
" o. g& t0 ]% E! I9 l+ j5 B - Period_cnt=0;
/ t" ]5 T$ n, C2 y O8 P - Period_cnt1=0;! U+ Y e; {' ~( N. s
- Period_cnt2=0;
2 g, c1 B/ [8 K2 Z2 A; C - __HAL_TIM_SET_CAPTUREPOLARITY(&htim8,TIM_CHANNEL_1,TIM_INPUTCHANNELPOLARITY_FALLING); //设置成下降沿触发 % J! t2 s; Y5 x) s2 I
- ic_flag=1;//更改捕获标志位,进入case1进行相关变量的处理。
& }1 J6 i- {0 ^0 O - break;6 e3 T* s/ `& j+ G5 Y+ o% G
- }" P/ U: i; T; T
- case 1://第一个下降沿捕获" G6 k4 e, H4 E; w+ H& J
- {1 i/ F, S; W. u. r: M
- ccr_cnt1=__HAL_TIM_GET_COMPARE(&htim8,TIM_CHANNEL_1);//获取存放在CCR寄存器的值(捕获值)4 ]3 x+ N6 T+ k+ B( V
- Period_cnt1=Period_cnt;//获取第一个下降沿到来时的进入更新中断的次数9 K9 W( ?; t& O* Q$ B5 h( {5 n
- __HAL_TIM_SET_CAPTUREPOLARITY(&htim8,TIM_CHANNEL_1,TIM_INPUTCHANNELPOLARITY_RISING); //设置成上升沿触发 + U4 a* T9 O: L7 \4 G i3 }1 j
- ic_flag=2; //更改捕获标志位,进入case2进行相关变量的处理" j9 G J0 W( a5 X. n
- break;, v8 U8 _8 v4 z( n6 f& W' W
- }9 x4 ~7 ~, r) L# ?* K [; |
- case 2://第二个上沿捕获
7 b9 `' N/ `- N* a% V6 n. r. K - {
4 a m' H7 p% f' g - ccr_cnt2=__HAL_TIM_GET_COMPARE(&htim8,TIM_CHANNEL_1);//获取存放在CCR寄存器的值(捕获值)1 }) `8 Y; ]7 u5 f3 \
- Period_cnt2=Period_cnt;//获取第二个上升沿到来时的进入更新中断的次数 i( Z* T/ ]4 o, V$ D/ y
- ic_flag=0; //更改捕获标志位,进入case0进行相关变量的处理。6 y6 z' F; M) k9 [# s( Y) i( \
- end_flag=1;//捕获完成标志( J( b2 e0 H% y8 U. t a
- break;! a( B3 S' p2 X* x
- } ( R/ N9 K/ N' l Z$ e6 c) O4 m# s- `7 h
- default:, b. y& [! |4 b% C: b
- break;" K8 T) Q% l, D( k: E& d
- }4 U1 N8 B* k# x1 |' M( M* l
- }
复制代码
* }* Y/ X# {& x; j# H; u. S4 n v3 q& o2 l* K+ o7 v
关于普通的输入模式捕获PWM。主要工作流程:# b; [$ K2 A( A8 r
6 j2 X+ w- `6 D' G2 ?7 |1、第一次捕获到上升沿,计数器清零,存捕获寄存器值的变量清零,存中断次数的变量清零。改变触发边沿为下降沿触发,并将触发标志位为设置为1,使下次下降沿触发时,能进行相对应的处理。5 z6 x" [0 v7 a- p ?3 c
$ j; ?1 u& C$ e- p2、第一次捕获到下降沿,将捕获寄存器(CCR寄存器)的值,中断次数存入相应的变量。同时更改触发边沿为上升沿触发,并将触发标志位为设置为2,使下次上升沿触发时,能进行相对应的处理。
. H9 @' {8 j! o; l" i A p- W
# ]: H. M C/ y b7 X3、第二次捕获到上升沿,再次将捕获寄存器(CCR寄存器)的值,中断次数存入相应的变量。将触发标志设置为0,目的为能够循环捕获。同时捕获完成标志为置1。( h4 i4 q) U' E d" N& w
. k* g" f, F0 Q S$ F9 m 4、在main函数中,使用在while循环,使用if语句进行捕获标志的轮询,从而在捕获完成后进行相应的数据处理。9 V4 J1 b p9 o, {& ?, s
6 M& R1 e$ d7 Q0 p8 l
虽然目前代码的配置以及实验结果是没有问题的。但是在调试时候,碰到一个比较疑惑的问题,ARR的设置影响了捕获的准确性,比如在设置为999和1000的时候,同样是存在溢出的,也进行了溢出处理。但是AAR=999时候频率不稳定,AAR=1000频率正常。暂时还是没法找到合理的解释。3 @9 K* _+ \4 x$ a
2 ?: I- d9 \1 s+ ^9 a1 \# o
% c: d; r ~0 a+ d |