STM32学习笔记08—电容触摸实验 8.1 电容触摸简介 8.1.1 电容触摸概述 随着科技的发展,传统的机械按键正在逐步从设备上面消失,这个原因主要有机械按键由于是采用机械接触的方式,寿命比较短,从用户体验上看,机械按键也显得操作复杂,对比现在的电容按键,电容按键具有寿命长,因为不存在机械接触,占用空间少,以前的机械按键在设计外壳的时候需要考虑尺寸,现在换成电容按键后这个问题不再需要考虑。
7 E S3 s1 v' {/ S2 S, _0 _8.1.2 检测原理 常规的检测方式一般是通过计算电容放电时间来判断是否有手指按下,这是因为手指会与线路板的铜箔接触面上产生电容效应,当手指没有放在铜箔上的时候,铜箔与PCB之间存在杂散电容,这两个状态的电容值差别很大,检测原理如下图所示。
, h7 K1 K `/ c& G 在检测之前首先用开关将电容Cs里面的电荷放尽,然后此时CPU开始计算Cs的充电时间,这一部分是采用捕捉信号来测量,尽管单片机属于数字电路,但是数字电路的逻辑电平也是有电压限制的,比如在3.3V供电环境下,当电压大于2.4V则被认为是逻辑电平1,当电压小于0.4V则被认为是逻辑电平0,单片机的输入捕获功能来判断输入信号的电平是否为逻辑电平1,如果检测到逻辑电平1,则认为电容此时充电达到了2.4V以上,将这个时间记录下来,当手指放在铜箔上的时候,相当于增加了Cs的容值,此时我们继续进行输入捕获采样,将这个捕获的时间记录下来,两个时间求差值,这个差值高于某个阈值的时候就可以认为此时手指按下了电容按键,用这种方式就可以实现虚拟按键的使用了。这种检测原理实际是采用了在电路分析中学习到的RC电路的零状态响应来实现的。根据RC电路的零状态响应可以得出电容的充电公式为 + [! m* A" t# g; P+ h) Z
其中Vc表示电容的充电电压,VDD为RC电路的输入电压,R为电阻的阻值,C为充电电容的容值,通过这个公式我们可以反推得到充点电容的容值。也就是说我们可以利用这个公式实现电容的测量。 . O3 R( g: p7 c( N
8.1.3 预备知识 首先我们在进行电容触摸检测的时候需要用到STM32的输入捕获功能,从这一章开始,关于寄存器文件的添加,驱动文件的添加不再作为重点,重点开始转为程序的编写及小算法的编写。 输入捕获的工作原理如下图所示。
8 M- \# ] P$ e$ { 首先设置定时器的输入通道为上升沿捕获,检测到上升沿之后,将计数寄存器CNT中的数据存储在CCRx1中并清空CNT的数据,然后设置定时器的输入通道为下降沿捕获,检测到下降沿后将计数寄存器CNT中的数据存储在CCRx2中并清空CNT的数据,此时将CCRx2的值与CCRx1的值做差值就可以得到1个波形中高电平的时间,由于这两个数值获取的过程中,会由于高电平时间过长导致定时器产生多次中断,这个多次中断的值记为N,此时高电平的时间计算公式如下所示: 5 } V/ o6 R2 l1 K" x0 v
其中M为定时器的计数周期,N为定时器的溢出次数,ARR为自动重装载计数器的值,CCRx2为捕获到的数据。
- a7 c( U, P P: y) z2 s6 ^, H8.2 常用寄存器 8.2.1 捕获/比较寄存器1:TIMx_CCMR1 8 o/ ]4 E" ~! {$ X5 ]) I; i
ICxF[3:0]:输入捕获x滤波器(定义输入采样频率及数字滤波器长度) , O& A. |& e) P k( I
ICxPSC[1:0]:输入/捕获x预分频器(一旦CCxE=0,则预分频器复位) 00:每1个事件触发一次捕获 01:每2个事件触发一次捕获 10:每4个事件触发一次捕获 11:每8个事件触发一次捕获 CCxS[1:0]:捕获/比较x选择(用于定义通道x输入还是输出) 00:输出模式 01:输入模式,映射在TI1上 10:输入模式,映射在TI2上 11:输入模式,映射在TRC上,此模式引用于内部触发器输入被选中时 8.2.2 捕获/比较使能寄存器:TIMx_CCER CCxP:输入/捕获x输入/输出极性 通道在输出模式下 0:高电平有效 1:低电平有效 通道在输入模式下 0:不反相,上升沿触发 1:反相,下降沿触发 CCxE:输入/捕获x输入/输出使能 通道在输出模式下 0:关闭输出 1:开启输出 通道在输入模式下 0:禁止捕获 1:使能捕获
% [' s0 r) E5 c6 B2 h( Q1 ~8.3 电容触摸例程 现在PA1端口接一个触摸按键(一块铜箔),利用PA1的触摸按键控制PA0端口的LED状态,按下时LED点亮,抬起时LED熄灭。 (1)新建基础工程,并创建tpad.c,tpad.h,led.c和led.h文件,并导入工程,如下图所示。 (2)在tpad.h文件内添加以下代码。 (3)在led.h文件内添加以下代码
, m1 a$ Y* b5 \, |; Y(4)在led.c文件中添加以下代码 (5)在tpad.c文件内添加以下代码 - #include "tpad.h"
* g# T+ q0 I$ d* | - #include "delay.h"
6 z1 a! r- I4 p - /***************************************************
. h( O: c- I1 q; A1 Y1 k- y - Name :TPAD_Get_Value; h8 z5 o* q6 J
- Function :触摸按键值获取
: X* K0 X) E! j8 n4 Z: C9 f4 n0 L2 y - Paramater :None' p/ b7 [+ S3 N ?
- Return :获取的充电时间+ z6 n; c$ Q. s) Q8 Z
- ***************************************************/; L; D5 P. \( o9 U2 w; ~1 W2 n
- u16 TPAD_Get_Value()- x% v. u$ G( r! F
- {
* Y6 R$ W; u% L) S& h% n( I* U+ d - //电容放电
4 I8 o) u2 L' E3 W. l( } - GPIOA->CRL &= 0xFFFFFF0F ; //PA1输入
3 Y# n c& Z4 B% F4 N6 v$ A - GPIOA->CRL |= 0x00000030 ; //推挽输出6 w* H) Q B/ {
- GPIOA->ODR &= ~( 1<<1 ) ; //输出低电平放电1 o: g& @/ a2 Q/ N5 f. J- i
- delay_ms( 5 ) ;' W9 j5 m+ C# G$ O( p9 u4 H; D
- TIM5->SR = 0 ; //清除标记6 r3 e' @/ K+ H% _% b, @" B
- TIM5->CNT = 0 ; //归零$ I8 x' C) ]2 \/ B' B
- GPIOA->CRL &= 0xFFFFFF0F ; //PA1输入0 i* |* P+ S2 q* M: [
- GPIOA->CRL |= 0x00000040 ; //浮空输入
; O( a# I# O" O* T: s - //等待捕获上升沿
/ u8 t( g% k3 v1 S C3 S - while( ( TIM5->SR&0x04 )==0 )
% n' j* B9 D& V - {
# q% T6 K' J, a) G" E( p - if( TIM5->CNT>( 65535-500 ) )( [: o$ y8 e; F# f4 n
- return TIM5->CNT ;
4 e" s% K3 ?$ T. }5 B* w1 h. } - }7 `. o: d) Q+ V; ?5 j0 U
- return TIM5->CCMR2 ;
4 p5 C0 r' J4 A1 j; {9 \; ^* h - }
0 \2 t! F8 K$ O5 i - /***************************************************
" s0 F, n% f' a( R - Name :TPAD_Get_MaxVal
. {6 w% Y t: C5 q6 M% _& }9 ^) f - Function :触摸按键最大值获取
# ^ |! R0 ~4 U. S# E - Paramater :None
$ i* S1 K$ m; b! Y! i4 l3 t7 m) N - Return :获取的最大充电时间
6 n& [8 d: |4 _$ S4 L - ***************************************************/6 n3 h: g' ^- r* y, P: j
- u16 TPAD_Get_MaxVal( u8 n )4 ]+ ]) s2 a7 U! T+ B
- {, v5 I9 R/ s6 Q5 N+ A. D, t
- u16 temp=0;
8 q% e8 |6 Q8 n1 Z J! U - u16 res=0;! a' f* ~$ Y' [* z( I4 \
- while( n-- )' _, e0 N) m! J$ x. l
- {. [! Y0 G) Q, A
- temp = TPAD_Get_Value() ;
8 J, R% r. J4 Y% O - if( temp>res )3 ~( l0 d0 F; F& O1 Q$ `5 W
- res = temp ;* Y F4 p- L+ {" \
- }
, ]$ u2 C) M/ f& z/ g' s) l6 k - return res ;) N) ]* _1 q! O& J0 G& N5 I% k
- }
( V& F4 T# K$ H; i' ]* M! B# K! S - /***************************************************9 g2 U: A: P" B9 H" c9 N
- Name :TPAD_Init( ~" y- B$ t6 I* l% v
- Function :触摸按键初始化5 ]% V. c' B% u4 w U
- Paramater :None. x3 v. {$ x# r6 W! c: X' W4 ?# G
- Return :None0 a) O3 M0 Z G
- ***************************************************/( w( o' X( w6 N* ^6 h* O
- TPADData TPAD_Data ;4 ~0 K$ O3 f/ A0 A8 X" j, k
- void TPAD_Init()
$ G, T; e) C+ v# e - {
. M/ h' g) c7 Y, N# z3 A! Y - u16 buff[ 10 ], temp ;. a/ w' \$ N( W5 ]
- u8 i, j ;
; o6 j9 \4 {* A - RCC->APB2ENR |= 1<<2 ;
+ \; s8 Y' r" h - GPIOA->CRL &= 0xFFFFFF0F ; //PA1 输入: m% F! M- }; E
- GPIOA->CRL |= 0x00000040 ; //浮空输入; a' z7 j* Z( p' r
- RCC->APB1ENR |= 1<<3 ; //TIM5时钟开启
2 i8 I, @) G! D* o - TIM5->ARR = 65535 ;+ i9 g9 Q4 \8 i7 ] M
- TIM5->PSC = 5 ;* r$ s( {4 U, I6 T) ]( M l
- TIM5->CCMR1 |= 1<<8 ; //选择输入端IC2映射到TI2上1 X) E: q. H0 F. @6 `. z
- TIM5->CCMR1 &= ~( 1<<12 ) ; //采样频率8分频
$ O; S# u% V p8 r- N% t) X - TIM1->CCMR1 &= ~( 1<<10 ) ; //配置输入不分频
! Q; u3 B% w! I - TIM5->CCER &= ~( 1<<5 ) ; //上升沿捕获3 {# p% W+ v! b: W! d- c$ i
- TIM5->CCER |= 1<<4 ; //允许捕获功能 F! ]9 O0 [. V4 g9 y+ m
- TIM5->CR1 |= 1<<0 ; //开启定时器5
7 u5 Y+ H: I4 n* ] - //连续采样10次9 Z9 M( N8 [% U2 J9 t2 W b0 Q% N
- for( i=0; i<10; i++ )
5 v K% W) k0 V0 t - {
0 B# y3 N {2 z4 _0 R6 X - buff[ i ] = TPAD_Get_Value() ;- P; v: `) ]+ A& \0 B
- delay_ms( 10 ) ;0 `+ H5 l7 a. p+ B3 b
- }
/ f, |8 ]. w9 Z% a - //排序
; F* c, ]8 r0 Q$ c6 Z' k - for( i=0; i<9; i++ )( m0 w5 ^$ d' ]! s3 b4 R
- {
! N2 U5 a5 y6 v; t$ f' v7 x - for( j=i+1; j<10; j++ )) z3 ^% \8 Z7 L# `
- {7 v$ o7 b" ~, L# \9 E* D. p
- //升序排列$ d+ D/ G; e( b
- if( buff[ i ]>buff[ j ] )2 H3 n$ C# ?7 F4 [/ J
- {
- N; m* R/ W3 c* }/ ]. z6 T - temp= buff[ i ] ;! N6 i) z1 r P% `
- buff[ i ] = buff[ j ] ;
. _6 _" h) G+ N: ~" C: Q9 f5 J* J - buff[ j ] = temp ;
# E$ @. [1 m4 o" ~4 ~& D7 d( B- N - }' ]' d$ Q& c1 \! R2 O9 n- U3 H$ o
- }
6 f5 g6 x: Y$ @8 B! {% K - }
0 i2 N. D, N, ]% M - temp = 0 ;
( `; N5 C5 Z" B6 J( Q( C% \/ x3 k - //获取中间数据的均值7 t& P* u" `7 w6 r P8 i; Y" T
- for( i=2; i<8; i++ )' P0 s/ T9 r4 l: S$ p
- temp += buff[ i ] ;% ^5 q+ n, W, S
- TPAD_Data.value = temp/6 ;
* Z: h! b, B4 g$ b( S* T - }- h+ p# u) ?9 a! d) @. b& s
- /***************************************************
- S( y2 j/ ?# Y5 E* d, n/ v - Name :TPAD_Scan
$ h5 p4 s% D# n - Function :触摸按键扫描% X" X9 Q) Q6 t( @7 O, B1 p4 X
- Paramater :None3 l' r# {. j; z/ M) H
- Return :None
4 L3 B0 T: h- n7 g# j - ***************************************************/4 B" j+ I4 i/ L+ x/ k7 S- S0 M
- void TPAD_Scan() Y0 f( n9 b, l$ f8 h2 _7 L
- {: i" S- ]+ G. d, q6 Z5 ]
- u16 rval ;3 C& F/ E& B0 w% e+ }4 j* W. C/ c
- rval = TPAD_Get_MaxVal( 3 ) ;
/ ^! r: l% t; @* I; | - if( rval>( TPAD_Data.value+100 ) )2 ]! H: t( u9 ]# V8 |- A2 @/ J
- TPAD_Data.State = 1 ; //手指按下
5 W. i8 |7 T) a2 k, R% m - }
复制代码(6)在1.c文件内添加主函数 - #include "sys.h"9 D$ F C- Q f
- #include "delay.h"
o9 e1 l/ G1 |( \( ? - #include "usart1.h"5 q/ z2 y5 y* H& m
- #include "led.h"
7 k9 p9 X. ^. \+ c9 `1 { - #include "tpad.h"
' y: ^' |4 a: Y& P0 F: l" ` - int main()& z2 C2 J4 R7 `/ e2 Q7 ?% M: [
- {, T( P0 |, I T) z! i
- STM32_Clock_Init( 9 ) ; //STM32时钟初始化( T6 L+ {7 E" ^/ `3 W1 _1 B" l
- SysTick_Init( 72 ) ; //SysTick初始化+ f; V; x* M& P( l
- USART1_Init( 72, 115200 ) ; //初始化串口1波特率115200
9 Q' d/ D: c. ~. C0 l% c- B; z - LED_Init() ; //LED初始化
0 ~+ u. t, o& w( X! e - TPAD_Init() ; //触摸按键初始化
" u7 @3 @; Y, `7 i( A$ t6 ^ - while( 1 )
+ O, ~0 G* O: o! X0 u, t8 Q - { I! P; Y$ T6 P; S2 a
- TPAD_Scan() ; //触摸按键扫描
0 n3 u( E* J [; v6 g, Q - if( TPAD_Data.State==1 )
' b4 f# f. W1 B5 f - {8 [+ q* _* B; l% E7 B1 B+ J
- LED = 0 ;
- d% r! X; T- W) b2 V - TPAD_Data.State = 0 ;0 N( T/ E, T) |1 C- C$ N
- }
) Y H; S+ `7 O( S) D8 |1 w! L6 o - else7 M: Q8 y5 y1 j5 d8 r1 u* j/ Q
- LED = 1 ;9 k; M- Q" j; }' I
- }
) G0 E* ?* B3 U# e# n Z - }& V" m8 \6 `) d0 x; `
复制代码 0 }, p1 s! \' }, X9 m6 _1 h
3 j7 L+ h/ N9 j( M& D8 U
8.4 STM32工程管理 这里的总结是针对与之前所学的所有嵌入式内容进行总结,随着后续代码越来越多,对于工程的管理显得格外重要,不仅仅是采用本章节介绍的方法管理工程代码,对于后续的Linux开发和操作系统移植也最好是按照固定的工程格式进行管理。 对于STM32的工程可以划分为以下几个部分: (1)过程文件以及最终生成的hex文件:这些文件通通放置在工程1级目录下的OBJECT文件中,文件中的内容如下图所示。 图中标注部分就是最终生成的hex文件,其他的文件均属于过程文件,集成开发系统IDE编译的实质其实就是采用gcc交叉编译器将我们编写的代码转换成对应的机器代码的过程,具体的步骤会在Linux系统移植中描述,这里只需要知道利用交叉编译器进行编译的时候会产生大量的链接文件和过程文件,这些文件统统放置在OBJECT目录下。将工程文件中的过程文件放置在该目录下的设置方式如下图所示。 (2)主函数文件及主工程文件:在Keil中创建的工程,以及包括main函数的c文件都位于这个目录内。 图中标注的3个文件都是必不可少的文件,带有Keil图标的文件就是工程文件,里面包含了整个工程所需的头文件,文件路径以及编译器位置,文本文件图标的则是c文件,main函数就放在这个位置,.s后缀的为汇编启动文件,用于帮助芯片创建C代码运行环境,初始化堆栈等功能,为了达到最快效率,所以采用汇编语言来编写这个文件。 (3)自定义系统文件:自定义的系统文件位于SYSTEM目录中,目录内有3个子目录,分别是sys,delay和usart1,如下图所示。 delay目录:是利用SysTick时钟来实现的延时功能,主要有毫秒级延时和微秒级延时; usart1目录:则是利用串口1来实现printf打印输出的功能,主要用于调试数据的输出; sys目录:是最基础的工程文件,这个目录内有1个c文件和2个h文件,c文件主要实现STM32时钟树的配置和中断系统的设置功能,是C函数中最重要的一个文件,stmxx.h文件则是最底层的寄存器定义文件,里面定义了STM32芯片内部所有寄存器的地址和结构,通过C语言来调用这里面的寄存器实现芯片的控制功能。 (4)驱动文件:驱动文件位于HEADERWARE目录中,用于存放项目所用到的驱动,比如之前实验所用到的LED,KEY,TIM这些外设都属于驱动文件内容。 至此整个工程的目录结构如下图所示。
; M9 i! v: w _
/ N6 P/ U1 e' {7 j% B文章出处: 电子技术园地 滑小稽笔记
) f$ k- x) S- ^/ |
) ~" g' j: D D9 j) J |