|
我们已经介绍了STM32F1 的通用定时器作为输入捕获的使用方法, 这一章我们就利用输入捕获功能设计一个电容触摸按键。本章要实现的功能是:通过 TIM5的通道 2(PA1)捕获电容触摸按键输入信号的高电平脉宽,根据捕获到高电平时间大小控制D2指示灯开关, 同时D1指示灯不断闪烁表示系统正常运行。 学习可以参考前面“输入捕获实验”和《STM32F10x 中文参考手册》-14 通用定时器章节。 电容触摸按键介绍 触摸按键与传统的机械按键相比,不仅美观而且耐用、寿命长,它颠覆了传统意义上的机械按键控制,只要轻轻触摸,就可以实现按键开关的控制、量化调节甚至方向控制。触摸按键已广泛应用于手机、DVD、洗衣机等消费类电子产品中。本文我们就介绍一种简单的触摸按键:电容式触摸按键。 我们开发板上的电容触摸按键其实就是一小块覆铜区域, 也称之为触摸感应区,如下图中笑脸即为触摸感应区。
0 a. K$ }. F2 v/ L' l1 g m
通常我们会将四周的铜片与电路板地信号连通, 触摸感应区设计成方便手指触摸大小,并将其连接在输入捕获通道上。触摸感应区与四周的铜片区域就形成了一个电容, 通过检测电容充放电时间即可判断是否有触摸。电容触摸按键原理,实现原理如下图所示。 上图中 R 是外接的电容充电电阻,Cs 是没有触摸按下时 TOUCH(触摸感应区)与四周的铜片区域的杂散电容。而 Cx 则是有手指按下的时候,手指与 TOUCH 之间形成的电容。图中的开关是电容放电开关(实际使用时,由STM32F1 的 IO 代替)。 先用开关将 Cs(或 Cs+Cx)上的电放尽,然后断开开关,让 R 给 Cs(或 Cs+Cx)充电,当没有手指触摸的时候,Cs 的充电曲线如图中的 A 曲线。而当有手指触摸的时候,手指和 TOUCH 之间引入了新的电容 Cx,此时电容量相当于它们的累加,充电时间自然就长了,Cs+Cx 的充电曲线如图中的 B 曲线。从上图可以看出,A、B 两种情况下,Vc 达到 Vth 的时间分别为 Tcs 和 Tcs+Tcx。 其中,除了 Cs 和 Cx 我们需要计算,其他都是已知的,根据电容充放电公式:
* A) x+ ]- @5 |$ y5 v ^ 其中 Vc 为电容电压,V0 为充电电压,R 为充电电阻,C 为电容容值,e 为自然底数,t 为充电时间。根据这个公式,我们就可以计算出 Cs 和 Cx。因此我们可以把开发板当做一个简单的电容计,直接可以测电容容量了。 在本文中,其实我们只要能够区分 Tcs 和 Tcs+Tcx,就已经可以实现触摸 检测了,当充电时间在 Tcs 附近,就可以认为没有触摸,而当充电时间大于 Tcs+Tx 时,就认为有触摸按下( Tx 为检测阀值)。 本文我们使用 TIM5的通道2(PA1)来检测触摸按键是否按下,每次检测前,我们需要先将电容 Cs(或 Cs+Cx)放电,即配置 PA1 引脚为推挽输出模式,输出一个低电平,才能使电容放电。然后配置 PA1 为浮空输入模式,利用外部上拉电阻给电容 Cs(Cs+Cx)充电,同时开启 TIM5_CH2 的输入捕获,配置极性为上升沿,当检测到上升沿的时候,就认为电容充电完成了,完成一次捕获检测。 每次系统重启时,我们执行一次捕获检测(可认为没有触摸),记录此时捕获到上升沿的值。在后续的捕获检测中,我们就可以通过与记录的值进行对比,判断是否发生触摸。 关于输入捕获的配置,在上一章我们已经有详细介绍,这里我们就不多说,不清楚的可以回过头看下。
2 Q$ V1 O; e) ?
硬件设计 本实验使用到硬件资源如下: (1)D1、D2 指示灯 (2)TIM5 的通道 2 (3)串口 1 (4)电容触摸按键 D1、D2 指示灯、串口1 电路在前面章节都介绍过,至于 TIM5 的通道2 它属于 STM32F1 芯片内部的资源。电容触摸按键电路如下图所示。
5 |8 g! h8 _3 X/ C. g' y4 }. b' g6 l Z
原理图中的 TOUCH 即为电容触摸感应区,通过 P_TOUCH 引出。而 TIM5_CH2是在 PA1 脚,它在 P_touch 端子的插针 2 上,要捕获电容触摸按键的信号,我们需要用短接片将 P_touch排针上的 1 和 2 短接(开发板丝印上是 TP 和 A1)。而此端子的 2、3 短接是用于对ADC 的检测,这个在后面章节会介绍到。D1指示灯用来提示系统正常运行,D2 指示灯用来指示触摸是否按下,捕获上升沿值可通过串口 1 的printf 函数打印出来。 所要实现的功能是:通过 TIM5 的通道 2(PA1)捕获电容触摸按键输入 信号的高电平脉宽,根据捕获到高电平时间大小控制 D2 指示灯开关,同时 D1指示灯不断闪烁表示系统正常运行。程序框架如下: (1)初始化 PA1 管脚为TIM5 通道 2 输入捕获功能,设置上升沿捕获等 (2)读取一次捕获高电平的值 (3)电容触摸按键初始化 (4)检测电容触摸按键是否按下 (5)编写主函数 在之前帖子中“输入捕获实验”中就已经讲解如何配置输入捕获。下面我们打开“电容触摸按键实验”工程,在APP 工程组中添加了 touch_key.c 文件(里面包含了电容触摸按键驱动程序),在StdPeriph_Driver 工程组中添加stm32f10x_tim.c 库文件。定时器操作的库函数都放在 stm32f10x_tim.c和stm32f10x_tim.h 文件中, 所以使用到定时器就必须加入 stm32f10x_tim.c文件,同时还要包含对应的头文件路径。 这里我们分析几个重要函数,其他部分程序大家可以打开工程查看。 6 m) P% S v3 j
TIM5 的 CH2 初始化函数 要使用定时器输入捕获功能,我们必须先对它进行配置,代码如下: - /****************************************************************$ P4 R1 o9 T+ Y7 u. H% v4 K
- * 函 数 名 : TIM5_CH2_Input_Init) j: ]: Y& ]" p5 C# X m
- * 函数功能 : TIM5_CH2 输入捕获初始化函数9 Y% M' F* i; `' c; F6 I H7 R
- * 输 入 : arr:自动重装载值 psc:预分频系数
: i/ [4 C# V9 k* Q# f - * 输 出 : 无
: x) V1 f7 T( ]" } - *****************************************************************/
0 U, {3 M- u- C4 y - void TIM5_CH2_Input_Init(u16 arr,u16 psc)1 W0 k7 C( x4 X0 d# O$ Q
- {( b, f9 u6 _4 n0 o3 Q) N5 X9 S, w: ]
- TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
& Q8 }: Z' @' Z6 O% l - TIM_ICInitTypeDef TIM_ICInitStructure;7 ?" W, [3 Y+ n) i. Q0 e' t& A) q
- GPIO_InitTypeDef GPIO_InitStructure;4 y6 S, V. Q" S0 Z
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
3 l# r; x& \, H8 A - RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5,ENABLE);//使能 TIM5时钟* N6 Z0 Y$ }- Y
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;- T4 J A/ v8 S ?+ m
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; // 浮空输入模式4 l8 {/ X8 W6 }# w5 o3 o
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //IO 口速度为 50MHz C' k. |1 y# U
- GPIO_Init(GPIOA, &GPIO_InitStructure); // PA0, A, {1 n N6 ]) i" \5 R. u
- TIM_TimeBaseInitStructure.TIM_Period=arr; //自动装载值$ p0 L9 L6 t+ y* z! q1 G9 g5 v* P
- TIM_TimeBaseInitStructure.TIM_Prescaler=psc; //分频系数
0 B* m( Q6 s. v. X" L - TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;2 @0 w) K3 I' j/ N7 J1 N# l
- TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up;
: y0 D% @, I& H1 u- J - //设置向上计数模式
( p9 ~6 [4 l j8 M; A7 e5 c" a4 n" C - TIM_TimeBaseInit(TIM5,&TIM_TimeBaseInitStructure);% O' [9 n( W, T, @" _
- TIM_ICInitStructure.TIM_Channel=TIM_Channel_2; //通道2& p) l, t$ Y2 v
- TIM_ICInitStructure.TIM_ICFilter=0x00; //滤波
) j! y/ `! v# S/ y0 H - TIM_ICInitStructure.TIM_ICPolarity=TIM_ICPolarity_Rising;// 捕获极性
( F, _. H/ P: y" i0 j3 N2 S! p - TIM_ICInitStructure.TIM_ICPrescaler=TIM_ICPSC_DIV1; //分频系数( F; x: R* ?/ _7 i; d1 L
- TIM_ICInitStructure.TIM_ICSelection=TIM_ICSelection_DirectTI;//直接映射到 TI1* `" n& ~0 _6 g) l9 ^7 M( v
- TIM_ICInit(TIM5,&TIM_ICInitStructure);
7 C* @( h$ V1 R1 a* @ - TIM_Cmd(TIM5,ENABLE); //使能定时器9 F5 _' N- K4 L4 W) u* ?6 {
- }
复制代码 % Q) c, Z; m( b J4 N/ @
TIM5_CH2_Input_Init()函数与前面输入捕获实验的 TIM5 初始化是类似的,只不过换了通道而已。通过这样的设置,我们就可以捕获 PA1 引脚上的上升沿。 TIM5_CH2_Input_Init()函数有两个参数,用来设置定时器的自动装载值和分频系数,方便大家修改计数器频率,具体计算参考定时器中断实验章节。因为TIM5是16 位的定时器,所以arr 为 u16 类型。 高电平捕获函数 上一步已经将 PA1 引脚初始化为输入捕获功能, 当电容两端电压逐渐上升置高电平时,就会发生捕获。我们通过查询标志位方式检测是否发生上升沿捕获,代码如下: - /****************************************************************2 t' E: x% K; y5 m8 D
- * 函 数 名 : Touch_Get_Val s8 ~" d0 a/ x* t K1 Y) w
- * 函数功能 : 返回捕获高电平值
6 V! X* b" \) Y8 s1 [+ L1 E - * 输 入 : 无
8 ], ]' ^% A2 N8 z3 f - * 输 出 : 捕获高电平值2 b: F( @! l# G: ~$ b# D {
- *****************************************************************/ y9 S0 `5 p" a7 L
- u16 Touch_Get_Val(void)9 N. f0 t% E: D5 e
- {# _6 {/ w' l% I5 z* h `5 I
- Touch_Reset();7 b8 W& Y0 Z2 t8 R7 | H, X K
- while(TIM_GetFlagStatus(TIM5,TIM_FLAG_CC2)==0) //等待捕获到高电平标志" y/ P2 C! f8 D4 D3 y# ?
- {! U/ o" v! Q1 W% r$ A( x
- if(TIM_GetCounter(TIM5)>Touch_ARR_MAX_VAL-500) //超时了直接返回CNT 值
3 R) C3 {) `5 n3 \5 i5 w) B - {' H- _0 {; p8 F. J$ i( q
- return TIM_GetCounter(TIM5);
% H! U6 }4 S' u# t$ u# P, ~ - }# x' m* f: m; n* B" z
- }" {+ g8 @. I4 w$ C
- return TIM_GetCapture2(TIM5); //返回捕获高电平值6 i7 N6 k' @4 j
- }
复制代码3 _! W' [ W: H- Y- x
该函数用于得到定时器的一次捕获值。函数内首先调用 Touch_Reset()函
! m" E* t$ s" r W 数,将 PA1 配置为输出模式,并输出低电平,使电容完全放电。然后清楚定时器相应的状态标志(溢出/捕获)并调用 TIM_SetCounter 库函数将 TIM5 计数器值清零, 同时将 PA1 配置为浮空模式。最后循环等待发生上升沿捕获 (或计数溢出) ,将捕获到的值(或溢出值)作为函数返回值返回。 : E& B7 {: d$ O- x& X9 A
电容触摸按键初始化函数 电容触摸按键初始化函数代码如下: - /****************************************************************3 j1 D9 n0 s* t: ?
- * 函 数 名 : Touch_Key_Init1 \: j* u) O) x% Q. q8 S' L0 ~( E7 k
- * 函数功能 : 触摸按键初始化/ \# i% r6 _* O" P1 S3 x
- * 输 入 : 无
& f& n9 ~7 k0 B I1 Q - * 输 出 : 0:正常 1:不正常
8 W5 i6 J/ g. v - *****************************************************************/
1 f: S0 O. a- q6 G - u8 Touch_Key_Init(u8 psc) ^' N- T5 ?) X% f
- {7 N6 S* \) R. C3 V. a2 v/ m0 H/ m: }
- u8 i;
4 ~8 o5 ?$ }4 N - u16 buf[10];
. q$ u$ n$ ^. x - u8 j;
8 p6 f* q: L! V& q - u16 temp;
7 I9 ^# \8 @9 s& }7 S! K9 C - TIM5_CH2_Input_Init(Touch_ARR_MAX_VAL,psc);
7 {$ C0 F+ `! @# @ - for(i=0;i<10;i++) //读取 10 次为按下时候的触摸值) C+ g. ^5 d; J: g1 H) b$ d3 Q
- {+ Z+ ?+ u5 |- L1 y$ w
- buf[i]=Touch_Get_Val();
9 n/ O* S; p$ N1 M$ b% x - delay_ms(10);
7 G4 o) p) G, _; S - }4 A: [5 P6 r7 u% J
- for(i=0;i<9;i++) //从小到大排序- k' ~7 p0 `, r4 z
- {
2 ?" r- }/ y& N4 _7 ? U1 O+ Y% h - for(j=i+1;j<10;j++); u2 B0 c( I7 v/ k; H b
- {9 V3 _5 r2 u7 e% W
- if(buf[i]>buf[j]) 4 |; d) ]4 O; x8 [& G2 i' x- d# T
- {
. i9 l) s3 h$ X, L( O - temp=buf[i];
4 H. }$ U" A2 }0 X - buf[j]=buf[j];! x+ A% l9 C& |. l
- buf[j]=temp;
5 `3 g! q7 @2 v( f q K$ L! m5 Q - }/ Q I" Q7 d$ m$ z# W9 H+ W0 o1 ?
- }& W' C* F% G; a+ K5 O n
- } Z% ^2 i+ Z" s: h9 Y3 V
- temp=0;
) q5 B' F( H% Q - for(i=2;i<8;i++) //取中间 6 个数值求和 取其平均数* P( f% X; h8 J& W3 G2 }6 s: p1 @
- {
. N, ?# D8 D. c+ u# u - temp+=buf[i];
6 W7 t4 c) C" I% g' H0 v - }
0 R4 x/ f. `; I/ {6 b- D( G - touch_default_val=temp/6;/ T% U" U3 l( X( k! Y1 b
- printf("touch_default_val=%d \r\n",touch_default_val);5 |5 T( B# K6 F/ b+ [1 c: N( c7 a
- if(touch_default_val>Touch_ARR_ MAX_VAL/2)
; [% b6 J" ]+ w0 l% [! M - {+ R8 z/ e! d4 |* r( ]3 o
- return 1;//初始化遇到超过 Touch_ARR_MAX_VAL/2 的数值,不正常!2 @7 {" g; r6 t1 o
- }
. o9 O8 m! e: [' j - return 0;
/ N: {9 X8 Y, m7 s6 \" f) \. G' L" f - }
复制代码1 G8 P+ e- u# X, K+ X4 s
由于电容触摸按键是通过 PA1 进行捕获采集的,所以首先调用 TIM5_CH2 初始化函数 TIM5_CH2_Input_Init,此函数具有 2 个参数,用于设置定时器自动重装载值和预分频系数,这里我们将自动重装载值设置为 Touch_ARR_MAX_VAL,它是一个宏,在 touch_key.c 开始处就已定义,其值为 0xffff。预分频系数是通过函数形参传入,通过此参数即可确定计数时钟的大小。然后通过一个 for循环读取 10 次高电平的捕获值,按照由小到大进行排序,取中间 6 个再取平均,这样做可以减少误差。将计算后的值保存到一个 u16 类型的全局变量touch_default_val 中,作为后续触摸判断的标准。
) g0 t: K% H2 W 5 \! ?9 |6 h& {- [# j
电容触摸按键检测函数 获取了 touch_default_val判断比较值, 我们就可以开始检测电容触摸按键是否按下,代码如下: - /****************************************************************4 [. S0 O6 ?$ [1 i
- * 函 数 名 : Touch_Key_Scan
8 a) r5 m: }6 U6 `& S0 K2 H - * 函数功能 : 触摸按键扫描
1 Y! U. I7 c1 W! Q) H# ? - * 输 入 : 0:不支持连续触发(按下一次必须松开才能按下一次): G- E( t! t: W+ [- p n3 T# o3 D
- 1:支持连续触发(可以一直按下)
. j- F+ r6 x$ x. i; H4 ^6 [ - * 输 出 : 0:没有按下 1:有按下
+ r0 q) y5 S5 U, @' J. F5 ? - *****************************************************************/* I) |' z3 s8 y- R c6 E
- u8 Touch_Key_Scan(u8 mode)
+ f+ \: U$ I3 S - {# F9 b/ d& E. z; O) p+ f
- static u8 keyen=0; //0,可以开始检测;>0,还不能开始检测1 m4 d' K5 b J5 q4 T
- u8 res=0;
) ], j* X! ?. @3 S+ n! w - u8 sample=3; //默认采样次数为 3 次
4 a( A& H1 j! ^9 S, E+ A5 m8 T - u16 rval;& Z/ L X6 k2 D( k+ p# R
- if(mode)
: j4 d/ @5 D4 l& q" D - {' F3 [9 Z( x2 u9 f
- sample=6; //支持连按的时候,设置采样次数为 6次. K: E/ x1 |3 ?; f. N4 A* y& D
- keyen=0; //支持连按
7 F1 T: @- I5 d6 B5 C( F9 {( v% l - }
/ t: S0 B& p: `7 A/ Y - rval=Touch_Get_MaxVal(sample);
1 p! t! A% d. S, H5 c/ m - if(rval>(touch_default_val+TOUCH_GATE_VAL)&&rval<(10*touch_default_val)) // 大 于 touch_default_val+TPAD_GATE_VAL, 且 小 于 10 倍touch_default_val,则有效
, d' a: V' d- J8 V' r9 p - {% e4 k, x" p. j4 t! }
- if((keyen==0)&&(rval>(touch_default_val+TOUCH_GATE_VAL))); Y' z" X! U9 f, D$ U0 j
- //大于 touch_default_val+TOUCH_GATE_VAL,有效0 }4 H. t2 ]: |* D! \
- {
% A6 c/ w# U2 j0 U9 K - res=1;: X9 p# c6 _8 V% w+ a$ ~
- }; u* {1 f' ^, U. K) d- {
- printf("触摸后捕获高电平值为:%d\r\n",rval);& ]% r2 Q$ p# _5 t" |
- keyen=3; //至少要再过 3 次之后才能按键有效
- C' t/ `# w, h' h4 k( a/ h - }+ ~1 t, w+ s& m8 m P" v
- if(keyen)keyen--;
- Y" A, Q0 F; [! q w - return res;
2 e% W3 y: [' D3 m* c+ ] - }
复制代码
* @% W' U% {+ T- X& m $ t% m! v. w& P
/ C, f; Y' m8 f$ D1 L. o
该函数用于检测电容触摸按键是否有触摸,该函数的参数 mode,用于设置是否支持连续触发。如果 mode=1,表示支持连续触摸,如果 mode=0,表示支持单次触摸。该函数还具有返回值,如果是 0,说明没有触摸,如果是 1,则说明有触摸。函数内部定义了一个静态变量,用于检测控制。 函数中我们通过连续读取 3 次(不支持连续按的时候)捕获高电平的值,取它们中的最大值与 tpad_default_val+TOUCH_GATE_VAL 比较, 如果大于则说明有触摸,如果小于,则说明无触摸。其中 tpad_default_val 是我们在调用Touch_Key_Init 函数的时候得到的值,而 TPAD_GATE_VAL 则是我们设定的一个门限值(这个大家可以通过实验数据得出,根据实际情况选择适合的值就好了),这里我们设置为 100。该函数,我们还做了一些其他的条件限制,让触摸按键有更好的效果,这个就请大家看代码自行参悟了。 主函数 编写好 TIM5_CH2 初始化和触摸检测函数后,接下来就可以编写主函数了,代码如下: - /****************************************************************( t m. v: T$ \$ M/ q: D' C. B
- * 函 数 名 : main8 d z+ q! v& R- [( K6 \' l/ R
- * 函数功能 : 主函数/ C$ J( N! W& r2 m" v! b; Z0 p
- * 输 入 : 无: [; r- ^! a( ?& u4 C6 _
- * 输 出 : 无
) [. D: m. R! N5 ?- T4 f - *****************************************************************// u8 T- L8 S$ F5 s4 d1 H' Q" n' O
- int main()0 u, W& g# F1 X+ `/ E
- {8 y( {, s" A% }1 B0 \! v7 Y1 s
- u8 i=0;
4 i& K6 h1 I7 m) ^+ h4 @1 w3 x - SysTick_Init(72);
% m# y" \! n: k; S* U1 p - NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //中断优先级分组 分2 组
6 ^7 J# v1 o+ G$ M; ?- P5 c& a$ k; _' d - LED_Init();+ A8 W' o5 v; s( ^5 l2 d
- USART1_Init(9600);
8 W6 {4 n) U& \+ G - Touch_Key_Init(6); //计数频率为 12M
9 T* H( p/ o; |$ g" S+ \+ J - while(1)
0 f9 d% a4 d4 C( \" ~" G - {( J7 A4 v) N: y
- if(Touch_Key_Scan(0)==1)
, ~8 l) G- J) @( P f+ f* B - {7 k8 u7 K5 o" q; m {6 Q; Q6 E# r
- led2=!led2;
) k: e& }% Z( N/ B - }# f; {4 `+ `% }# k
- i++;
2 x1 Y( I2 r; q - if(i%20==0)0 y9 a8 j' O' s6 A
- {/ T; Z& A9 y+ c' B
- led1=!led1;
}8 y- m$ y( F4 [ - }. K9 z$ R: N) q; \, S S2 L
- delay_ms(10);
/ b6 D, p! p2 H& a9 H& y0 W' B8 b - }. R4 w+ D) D9 Z0 W' V7 P
- }
复制代码: q! `7 W* K- w' X( I
主函数实现的功能很简单,首先调用之前编写好的硬件初始化函数,包括SysTick 系统时钟,中断分组,LED 初始化等。然后调用我们前面编写的
/ q8 |# ?% H7 G. v- ?4 a Touch_Key_Init 函数,我们将 TIM5 的预分频系数设置为 6,此时 TIM5 计数频率为 12MHz,即计数 12 次时间为 1us。最后进入 while 循环,调用 Touch_Key_Scan函数,不断检测触摸按键是否按下,如果按下函数返回值为 1,将进入 if 语句控制 D2指示灯状态翻转。D1指示灯会间隔 200ms 闪烁,提示系统正常运行。 将工程程序编译后下载到开发板内,可以看到 D1 指示灯不断闪烁,表示程序正常运行。当触摸电容按键时,D2 指示灯状态翻转一次,同时打印触摸后捕获高电平值。如果想在串口调试助手上看到输出信息,可以打开“串口调试助手”,首先勾选下标号 1 DTR框,然后再取消勾选。这是因为此串口助手启动时会把系统复位住, 通过 DTR 状态切换下即可。 然后设置好波特率等参数后, 串口助手上即会收到 printf 发送过来的信息。(前提一定要用短接片将P_touch排针上的TP与A1短接, 图中笑脸即为触摸感应区,串口助手上先勾选下标号1 DTR框,然后再取消勾选)如下图 所示。
0 l% [5 J/ t P : ]7 I6 |% y9 h0 f6 p9 N6 C$ U
! L( {; i; v% Z8 G- d+ _( \. F! c. |
|