|
一、FSMC外扩内存SRAM FSMC(Flexible Static Memory Controller,灵活的静态存储控制器)是STM32系列采用的一种新型的存储器扩展技术,使用FSMC外设来管理扩展的存储器。在外部存储器扩展方面具有独特的优势,可根据系统的应用需要,方便地进行不同类型大容量静态存储器的扩展。FSMC可以用于驱动包括SRAM、NOR FLASH以及NAND FLSAH类型的存储器,不能驱动如SDRAM这种动态的存储器。而在STM32F429系列的控制器中,它具有FMC外设,支持控制SDRAM存储器。
: h6 D! k2 D9 X1 I) U$ S
FSMC是STM32系列中内部集成256 KB以上FlaSh,后缀为xC、xD和xE的高存储密度微控制器特有的存储控制机制。之所以称为“可变”,是由于通过对特殊功能寄存器的设置,FSMC能够根据不同的外部存储器类型,发出相应的数据/地址/控制信号类型以匹配信号的速度,从而使得STM32系列微控制器不仅能够应用各种不同类型、不同速度的外部静态存储器,而且能够在不增加外部器件的情况下同时扩展多种不同类型的静态存储器,满足系统设计对存储容量、产品体积以及成本的综合要求。 1 u# L7 Z+ B. \7 V
常用存储器介绍
% A# O& R2 @8 j# @: \
- [$ b! J2 n9 P6 \9 C1 b
存储器的种类:存储器是计算机结构的重要组成部分。存储器是用来存储程序代码和数据的部件,有了存储器计算机才具有记忆功能。
5 e8 |- g% z! u: f+ M
5 [4 M4 g% r8 E- Q1 j* r
1.RAM存储器:RAM是“Random Access Memory”的缩写,被译为随机存储器。所谓“随机存取”,指的是当存储器中的消息被读取或写入时,所需要的时间与这段信息所在的位置无关。这个词的由来是因为早期计算机曾使用磁鼓作为存储器,磁鼓是顺序读写设备,而RAM可随读取其内部任意地址的数据,时间都是相同的,因此得名。
# N% }$ _0 ^* `( W2 x% n9 c
实际上现在RAM已经专门用于指代作为计算机内存的易失性半导体存储器。根据RAM的存储机制,又分为动态随机存储器DRAM(Dynamic RAM)以及静态随机存储器SRAM(Static RAM)两种。 0 L8 f8 a5 q" Z
, Z* T1 j* @, R6 v8 z7 ^; U
2.DRAM存储器:动态随机存储器 DRAM的存储单元以电容的电荷来表示数据,有电荷代表 1,无电荷代表 0,,代表 1 的电容会放电,代表 0的电容会吸收电荷,因此它需要定期刷新操作,这就是“动态(Dynamic)”。 4 @; U. M0 ?7 ]0 y7 G1 f2 e& ^4 V
根据 DRAM的通讯方式,又分为同步和异步两种,这两种方式根据通讯时是否需要使用时钟信号来区分。由于使用时钟同步的通讯速度更快,所以同步 DRAM 使用更为广泛,这种DRAM 被称为 SDRAM(Synchronous DRAM)。常见的DRAM大多是SDRAM。
1 ]' ]& f7 d3 R/ X: n4 C
: i$ r; G3 E7 A" ~7 S: f4 _& R
为了进一步提高 SDRAM的通讯速度,人们设计了DDR SDRAM (Double DataRate SDRAM)存储器。DDR SDRAM在时钟的上升沿及下降沿各表示一个数据,也就是说在 1 个时钟周期内可以表示 2数据,在时钟频率同样的情况下,提高了一倍的速度。
4 I* }! t2 h7 H9 o3 H, ^
DDRII和 DDRIII,它们的通讯方式并没有区别主要是通讯同步时钟的频率提高了。 ! s3 B7 p9 M& @7 a& c! _1 q
" n- i- c2 X% ~3 b- r
3.SRAM存储器:静态随机存储器 SRAM的存储单元以锁存器来存储数据,见图。这种电路结构不需要定时刷新充电,就能保持状态(当然,如果断电了,数据还是会丢失的),所以这种存储器被称为“静态(Static)”RAM。 ) h8 D5 g# ~* n
同样地,SRAM 根据其通讯方式也分为同步SRAM和异步 SRAM,相对来说,异步SRAM用得较多。
! { ]; p. P9 v' B, A
所以在实际应用场合中,SRAM一般只用于CPU内部的高速缓存(Cache),而外部扩展的内存一般使用DRAM。DRAM和SRAM的特性对比如下:
. R' I% P% x! G A/ V0 ]8 ~) p
# Y) I2 m8 u2 D* J5 A8 o
4.非易失性存储器:非易失性存储器种类非常多,半导体类的有 ROM 和 FLASH,而其它的则包括光盘、软盘及机械硬盘。 + r4 J6 }8 g }0 h* {
ROM是“Read Only Memory”的缩写,意为只能读的存储器。由于技术的发展,后来设计出了可以方便写入数据的ROM,而这个“Read Only Memory”的名称被沿用下来了,现在一般用于指代非易失性半导体存储器,包括FLASH存储器,有些人也把它归到ROM类里边。 8 D' _: l9 q: A0 Q8 ] i
7 O/ E! Q% R! Z8 h6 I
FLASH存储器又称为闪存,它也是可重复擦写的储器,部分书籍会把FLASH存储器称为FLASH ROM,但它的容量一般比EEPROM大得多,且在擦除时,一般以多个字节为单位。根据存储单元电路的不同,FLASH存储器又分为NOR FLASH和NAND FLASH。
/ b7 `$ J. v* ?3 ^ X
l4 V% q. y3 x7 d/ k$ T
SRAM控制原理 ( X; _6 ]& R$ d1 _7 \
& Z6 M! L0 y0 |1 L/ O
STM32控制器芯片内部有一定大小的SRAM及FLASH作为内存和程序存储空间,但当程序较大,内存和程序空间不足时,就需要在STM32芯片的外部扩展存储器了。STM32F407系列芯片可以扩展外部SRAM用作内存。1 j1 r& t: }/ B; Q' U* @
$ g# g4 R* c- Q5 M, Q! P
给STM32芯片扩展内存与给PC扩展内存的原理是一样的,只是PC上一般以内存条的形式扩展,而且内存条实质是由多个内存颗粒(即SDRAM芯片)组成的通用标准模块,而STM32扩展时,直接与SRAM芯片连接。
! P: A" s! {+ [. f' j
SRAM外观与内部框图
$ {, c- e: b8 P/ N3 Y- ]* f& A
* _2 Z' I" t8 k _ z9 ] ]) N
' a/ i/ n p9 y8 Z
①地址数据接口:A0-A17用作地址线,I/O0-I/O15用于传输数据,一次性传输16位数据,并行传输 9 i: |" c3 z1 n
②存储矩阵:每个存储单元16位,SRAM内部包含的存储阵列,可以把它理解成一张表格,数据就填在这张表格上。和表格查找一样,指定一个行地址和列地址,就可以精确地找到目标单元格而这样的表则被称为存储矩阵。 / _0 x9 ~7 R2 r& i- ^
) ?/ U0 Q; Y& V L) M
当选中一个数据单元后,可以通过UB#或 LB#其中一个设置为低电平,I/O会对应输出该地址的高 8位和低 8位数据,因此它们被称为数据掩码信号。 - I2 D) m4 c3 C) u! _% ]9 ?
③控制电路:控制电路主要包含了片选、读写使能以及上面提到的宽度控制信号UB#和LB#。利用CE#片选信号,可以把多个SRAM芯片组成一个大容量的内存条。OE#和WE#可以控制读写使能,防止误操作。整个数据的传输都是在控制电路的控制下完成的,当想要读数据时,使用到OE线,把它拉低;想要写数据时应当把WE线拉低;只想读存储单元的高8位,则拉低UB线;只想读存储单元的低8位,则拉低LB线;一次性读26位都拉低即可。
& D; ^' @; `; \) ]3 P
SRAM的控制比较简单,只要控制信号线使能了访问,从地址线输入要访问的地址,即可从I/O数据线写入或读出数据。
. U1 P( E- @* D, p7 P" H( y) e
# M$ d4 |: ~8 @- N p% O5 D, X* W
SRAM的读时序 3 B: G% Y0 c) S7 |: G3 c" E
% D/ D8 {6 Y* O1 s! Z+ t+ S
0 M6 z" s% W) ]5 |* _
对什么时候使能读信号、什么时候拉低片选、地址线号线维持的时间等都有一定的要求,重点时序包括:读周期时间(tRC)、地址建立时间(tAA)、OE建立时间(tDOE),这些具体的时间都可在SRAM芯片的数据手册上找到。
* ?+ \- I% t3 Z8 h1 O
SRAM的写时序 4 X0 Y, j* a& X+ L# L
* z2 o0 w9 \/ h" H( T
. @; _$ J2 {% J: D1 J
重点时序:写周期时间(tWC)、地址建立时间(tSA)、WE脉宽(tPWE)
, \# ]) p) z) C: I2 L8 ?
SRAM的读写流程
4 y2 w5 I. n. j5 h3 m; D
$ v7 [( @6 y! x+ o
读写时序的流程很类似,过程如下: (1) 主机使用地址信号线发出要访问的存储器目标地址; (2) 控制片选信号CE#使能存储器芯片; (3) 若是要进行读操作,则控制读使能信号OE#表示要读数据,若进行写操作则控制写使能信号WE#表示要写数据; (4) 使用掩码信号LB#与UB#指示要访问目标地址的高、低字节部分; (5) 若是读取过程,存储器会通过数据线向主机输出目标数据,若是写入过程,主要使用数据线向存储器传输目标数据。
9 U- Q# ]0 m: \+ O3 ]8 o" T# q
STM32-FSMC控制器框图分析 - T3 Y+ Z( u" B4 t5 U5 }2 t5 V
, H9 ]( i7 L0 B b# S+ \: S: O1 H
' F$ R- l" A- K
①通讯引脚:由于控制不同类型存储器的时候会有一些不同的引脚,看起来有非常多,其中地址线FSMC_A和数据线FSMC_D是所有控制器都共用的。
( i% N+ E: g! |6 ]# j" @. X2 O/ v
) m! i# y+ Q* v/ o
注:其中比较特殊的FSMC_NE是用于控制SRAM芯片的控制信号线,STM32具有FSMC_NE1/2/3/4号引脚,不同的引脚对应STM32内部不同的地址区域。
2 t" F; I- p5 k
②存储器控制器:上面不同类型的引脚是连接到FSMC内部对应的存储控制器中NOR/PSRAM/SRAM设备使用相同的控制器,NAND/PC卡设备使用相同的控制器,不同的控制器有专用的寄存器用于配置其工作模式。
/ G& B- n- q4 E5 I2 @" V/ N4 T
控制SRAM的有FSMC_ BCR 、FSMC_ BTR以及FSMC_BWTR寄存器。每种寄存器都有4个,分别对应于4个不同的存储区域,各种寄存器介绍如下:
" H. ~- C3 o V u4 W0 Z; M4 \
a.FSMC_BCR控制寄存器:可配置要控制的存储器类型、数据线宽度以及信号有效极性能 参数。 b.FMC_BTR时序寄存器:用于配置SRAM访问时的各种时间延迟,如数据保持时间、地址保持时间等。 c.FMC_BWTR写时序寄存器:与FMC_BTR寄存器控制的参数类似,它专门用于控制写时序的时间参数。
4 n3 A, }0 D# P! o2 e: N* F
③时钟控制逻辑:FSMC外设挂载在AHB总线上,时钟信号来自于HCLK(默认168MHz),控制器的同步时钟输出就是由它分频得到。
6 x1 p- _, b: ]
例如,NOR控制器的FSMC_CLK引脚输出的时钟,它可用于与同步类型的SRAM芯片进行同步通讯,它的时钟频率可通过FSMC_BTR寄存器的CLKDIV位配置,可以配置为HCLK的1/2或1/3,也就是说,若它与同步类型的SRAM通讯时,同步时钟最高频率为84MHz。 4 z H; C- W6 y* q7 B5 n/ H# K4 c
后面示例中的SRAM为异步类型的存储器,不使用同步时钟信号,所以时钟分频配置不起作用。 - D; D0 o m: \4 e4 ~6 f6 N
FSMC地址映射
; I: y b% }3 V; X1 C
; ^, d% t3 u& \: T
FSMC连接好外部的存储器并初始化后,就可以直接通过访问地址来读写数据。
" R U/ z( a) ~* X7 e! h; J4 i6 U0 `. D
FSMC访问存储器的方式与I2C EEPROM、SPI FLASH的不一样,后两种方式都需要控制I2C或SPI总线给存储器发送地址,然后获取数据;在程序里,这个地址和数据都需要分开使用不同的变量存储,并且访问时还需要使用代码控制发送读写命令。
7 T, v. l9 w* B9 f: n& I
而使用FSMC外接存储器时,其存储单元是映射到STM32的内部寻址空间的;在程序里,定义一个指向这些地址的指针,然后就可以通过指针直接修改该存储单元的内容,FSMC外设会自动完成数据访问过程,读写命令之类的操作不需要程序控制。 ' E1 T6 ?. { A I
1 ^% B, \* C# Q; `, p. [- }+ q
FSMC的NOR/PSRAM/SRAM/NAND FLASH以及PC卡的地址被映射到了External RAM地址空间内,使得访问FSMC控制的存储器时,就跟访问STM32的片上外设寄存器一样。 . c6 }2 G' w6 g' _ ^1 |
FSMC把整个External RAM存储区域分成了4个Bank区域,并分配了地址范围及适用的存储器类型,如NOR及SRAM存储器只能使用Bank1的地址。 1 ]: L! }/ n _, K1 h# v1 f
在NOR及SRAM区域,每个Bank的内部又分成了4个小块,每个小块有相应的控制引脚用于连接片选信号,如FSMC_NE[4:1]信号线可用于选择BANK1内部的4小块地址区域,当STM32访问0x68000000-0x6BFFFFFF地址空间时,会访问到Bank1的第3小块区域,相应的FSMC_NE3信号线会输出控制信号。
( [- _# M% V+ N9 N9 X
6 s3 R' ]8 }6 \- ?' I
FSMC读时序如下:
" C9 L t$ i2 U+ s: l/ P" W7 V
3 z8 e" a" W& {* x. ]
FSMC写时序如下:
0 }) o+ m' m5 Y" V9 Z
: l8 C4 e# C. d# G, q. O2 r
FSMC外扩SRAM实例
m* g: s/ ]! Z/ r4 O
# `( z# e% V6 s! o" `
实验要求:配置STM32F407的FSMC以支持驱动IS61LV25616 SRAM芯片的读写。 4 j5 t1 t( U/ I" T! `
SRAM电器接线图如下 ; z6 X" }, `4 F
/ |, D2 l* V$ ?0 I
实验步骤:
2 c8 v4 z2 S1 [, k: f+ H
1.配置RCC
! u6 q- Q) v% O7 t# ~
2.配置FSMC管脚
5 o* |/ `* H) E3 Q* h
' J# s6 I f; Y9 i# G; N
, @5 H0 t) C' Z, r
3.配置FSMC协议
( V& B3 K" q0 O
% O' c4 K$ p" c9 G0 y6 o8 ^2 S/ |
tRC =ADDSET + DATAST,tRC是SRAM芯片的一个需求时间,可以通过查询芯片手册得到tRC的最小满足时间。IS61LV25616的型号有IS61LV25616-8、IS61LV25616-10、IS61LV25616-12、IS61LV25616-15,分别代表高速访问时间为8,10,12,15ns,本实验中以-10为例可知tRC最小时间为10ns ,tDOE最大时间为5ns,在配置是应当将tRC的值设置为大于10ns " a- M. f! y k7 ~* h
, Q2 ?3 v6 k) Q& @) t8 j7 k& `2 t
tDOE =DATASET,tDOE就是读使能信号拉低之后到得到有效数据之间的时间。 ADDSET一般是地址发出后到读使能信号开始拉低的时间,对于SRAM未作要求,大于0即可。FSMC的主频为168M,一个周期约等于6ns 4 V9 P% h& ? @( R- u; S( S
4 l5 [+ Z" N2 r& q N- H0 A! s
4.编写代码 - //main.c
0 ^) R3 } l% B! E: L+ m
3 s: r! i1 ]0 m" @; p/ Y+ j& J- #define SRAM_BANK_ADDR ((uint32_t)0x68000000)//外扩存储器首地址/ I% J! m6 o( L
3 M. W7 j2 ]' O- @4 f' d- int main(void) ?# o2 E& R3 J& u' Y! e
- { # ]9 z9 u! E( q- K
- uint8_t *p = (uint8_t *)SRAM_BANK_ADDR ; J+ R9 ^; g, R; q/ L" t; v$ ?. |
- uint8_t i;: g: Z( W5 E1 [' t
- HAL_Init();
6 {( [+ z& ~, q: h. ?! d - + u, Y$ x. \' q- r/ ~* l
( E2 {3 v0 ?1 L1 ?4 o- SystemClock_Config();
: t! O0 a5 n! M# i9 B$ m - MX_GPIO_Init();) }2 V$ `/ d+ }! [7 t, C
- MX_USART1_UART_Init();
3 y* T0 m8 o) ~3 z, Z - MX_FSMC_Init();
# o. k s+ w! ~ - I7 j3 A/ z5 l4 r
1 E, f1 _9 u1 x; L: i) m! w- printf("this is fsmc sram test\n");
& a4 C! z4 }' F! d) f# C - & x" t9 Z) p9 u
- //写内存( v/ _# X2 p0 M" m; W! ]1 N
- for(i=1; i<=10; i++)
( E0 E8 o1 [2 B/ p" r - {
+ A- w4 J" b4 G" r - p[i] = i;
% s. y8 F: d$ P - }) }% X) M. W! B9 P* b
4 k* X% x X$ Y8 P( {; h& u6 F- //读内存
; |$ C4 t4 X4 e& K4 O: q) \& n( Q$ G - for(i=1; i<=10; i++)
: G8 i* K# a5 f% F* D9 l. ~% ]. N - {
( V% B. F, ?0 w1 b2 X - printf("p[%d] = %d\n",i, p[i] );
/ i! M" E- @% `! s) n- V - }
4 {( y6 ~* y- H o# G - while (1): `6 ^' Z$ k# Q. X
- {( k) T: `9 i: T' b) j" _$ r3 ?
- Remote_Infrared_KeyDeCode();
* y( `8 p( \) j8 A - }
- l6 p2 J" O. ^! A - }
复制代码$ M% O2 c; [- p5 C3 {9 a
二、触摸屏专题讲解 & y2 |, D* j$ a
) B/ h' a4 g/ g3 @8 ?5 Q% p
触摸屏又称触控面板,它是一种把触摸位置转化成坐标数据的输入设备,可以响应用户点击的位置(对点击事件进行响应),是典型的人交互设备,使得人机交互更加人性化。根据触摸屏的检测原理,主要分为电阻式触摸屏和电容式触摸屏。 / ~1 \6 E; j" H$ I4 l
电阻触摸屏:电阻屏造价便宜,能适应较恶劣的环境,但它只支持单点触控(一次只能检测面板上的一个触摸位置,不管有多少触点同时被触控,一次只检测一个),触摸时需要一定的压力,使用久了容易造成表面磨损,影响屏幕寿命。 $ Q0 k$ ?1 d# j* E& [( p4 H8 S. _
电容触摸屏:具有支持多点触控(如玩手机游戏时可以同时有多个控键发生作用)、检测精度高的特点,电容屏通过与导电物体产生的电容效应来检测触摸动作,只能感应导电物体的触摸,湿度较大或屏幕表面有水珠时会影响电容屏的检测效果。 , w+ X1 y. ~# {. ~8 [/ u' f, g5 D
目前电容式触摸屏被大部分应用在智能手机、平板电脑等电子设备中,而在汽车导航、工控机等设备中电阻式触摸屏仍占主流。
4 v7 A7 z( N f% V. y0 }* L% r
3 I5 ~( O8 ]! N7 o% { T+ L1 `
触摸屏与显示屏的关系:触摸屏实际上是在液晶屏上面贴了一层大小相等的透明的薄膜,这个薄膜能够感知触碰,根据薄膜反馈的触摸位置,我们就能知道用户触碰在屏幕的什么位地方了。为了美观,电路一般都铺设在显示屏的边框上进行流通,最终通过fpc排线流出,所以绝对的无边框屏幕时不存在的。
! s- M6 `3 T# l6 j( h( [) U1 J
; z! Y3 q* h0 X: X) S
电容触摸屏检测原理 4 w8 B4 |/ R( p! U
. H7 A1 f$ l" x2 {/ S9 ^
与电阻式触摸屏不同,电容式触摸屏不需要通过压力使触点变形。它的基本原理是利用充电时间检测电容大小,若手指触摸屏幕,会影响触摸点附近两个电极之间的耦合,从而改变两个电极之间的电容量,若检测到某电容的电容量发生了改变,即可获知该电容处有触摸动作从而通过检测出电容值的变化来获知触摸信号。
, E. W+ s2 C/ k* X4 H5 F( ]9 n
简单理解为么一个电容容量大小固定,充电时间不变。但是手指触摸时,由于手指的导电性,会有一部分电流流过手指使得电容的充电速度变慢,充电时间变长,由充电时间发生变化可检测到触点的位置。
2 B* y, t* l! }$ N1 f( k8 P1 [1 B1 T$ w
2 S4 n8 E: Z% A4 G% W! Z# L- _2 u
电阻触摸屏检测原理
, Y# Y$ U* _: p4 \# j
) d- s+ E4 P* ?9 p/ u: Z
电阻式的触摸屏结构主要由表面硬涂层、两个ITO层、间隔点以及玻璃底层构成,这些结构层都是透明的,整个触摸屏覆盖在液晶面板上,透过触摸屏可看到液晶面板。表面涂层起到保护作用,玻璃底层起承载的作用,而两个ITO层是触摸屏的关键结构,它们是涂有铟锡金属氧化物的导电层。两个ITO层之间使用间隔点使两层分开,当触摸屏表面受到压力时,表面弯曲使得上层ITO与下层ITO接触,在触点处连通电路。 G7 l4 |& N: h) R7 ^
! X; I; a% ?5 E; L2 [& b5 }
我们可以把上面的ITO层看作X层,下面的ITO层看作Y层,每一层都会加上一个电压,ITO层是均匀的导电物质,那么整个一层的电阻是呈线性排布,不同地方的电阻大小不同。流过ITO层的电流恒定不变,假设对X层和Y层都加上一个5V电压,当有触控时,上下两层检测到触点的电压都为2.5V,那么理想状态下触点为中心,假设上层检测到触点的电压3V,那么上层触点在顺着电压方向屏幕的3/5处,下层检测到触点的电压2V,下层触点在顺着电压方向屏幕的2/5处,这样就相当于确定了一个坐标,从而确定触点的位置。
0 i. |, E! w R+ T3 W; l
两个ITO涂层的两端分别引出X-、X+、Y-、Y+四个电极,这是电阻屏最常见的四线结构,通过这些电极,外部电路向这两个涂层可以施加匀强电场或检测电压。 T C5 T2 _& F: p/ d/ r5 s
g2 c7 ~$ T# q8 Z5 W; A9 a% b9 T
电阻触摸屏校准
; x) Y$ [& z, G7 s, M
8 q0 F$ m2 {6 e' y4 y+ X0 K' b: H
电阻触摸屏的校准就是确定位置和对应的电压值之间的比例关系。假设添加X的场电压为5V,理想状体下,我们希望检测x方向最左侧为0V,最右侧为5V,但是实际上最左侧可能为大于0V,最右侧小于5V,但是ITO层在电压方向的电压变化仍是线性关系,那么最左侧的电压值称为偏移值,线性关系称为斜率。Y层同理。 " Y9 S9 y. h) d) M# L
, b. V2 r& I: Y; i. F
X0 = xfac*ADC_X + xoff,xfac是斜率, xoff是偏移值 Y0 = yfac*ADC_Y + yoff,yfac是斜率, yoff是偏移值 _; y' q/ }3 O
触摸控制芯片XPT2406
7 a9 b( f2 b4 N3 ]$ q# s
$ ^2 Z. G# ?6 y% u! D: {
& `9 w- t2 G% V4 ^6 Y- k
该芯片将触点的电压值数据通过SPI的方式传输到MCU,可以看到,红框内部由时钟线、片选线、数据输入、数据输出,这是典型的SPI传输结构。除此之外还有一个中断管脚PENIRQ,当触控一个点后,中断管脚发生作用,告诉MCU内部的转换数据已经准备好,让MCU进行读数据。 0 B8 H! S; a" h! ?$ Y9 m3 W
触摸屏操作实例
" @2 J, a/ k: c! N& r: R% r7 L
2 V( i. e7 N2 ?1 \9 f- M
实验内容:在触点处显示圆圈(本次涉及到的液晶屏代码将在后续讲解,本次内容很少而且简单,不影响阅读)
& L6 B0 A4 ?' a6 t# u
说明:当触摸屏有触点按下时,PENIRQ引脚会输出低电平,说明数据已经准备好。直到没有触摸的时候,它才会输出高电平。因此可以设置低电平中断,在中断处理过程中向控制芯片发送读数据的命令并读取数据。 * F1 @. b5 a' Y7 \9 u1 G
硬件连线如下,其中INT管脚与中断管脚及逆行连接,用于触发中断。
0 W, t+ m( X, j( r/ i+ Y" [
XPT2406命令字(控制字节)如下:
; f, \- X) p- i6 g
: {" n% _, _1 `2 g
位3选择0,说明读取的数值位12位分辨率,即读取的电压值位12位。发送命令后应当连续发送两个空命令(一个命令8位)来获得数据。至于为什么要继续发送两个空命令,具体原因见本公众号SPI文章传输原理部分。 _! _1 w& S) i
3 Q( [5 h' b: s8 C2 O
步骤:(部分配置省略,只进行重要配置说明) ! {: V- E" Y5 H' M. d7 F
1.配置RCC 8 I9 L N+ {$ y; |! Y# s! x2 y! r) D# Y
2.配置iSPI2
: O5 |! i1 r( Z8 z
" Y1 c& g c4 C3 W8 m
: F' `, k c" a2 a) p1 Z
3 ^; D1 u' L: _! \) ^1 J7 _
3.配置中断:将PG7管脚配置为中断模式 2 ]2 E2 z4 _, {0 o4 V; E9 [- A
1 x# t1 I8 L0 y* q m1 R
7 {# y& e+ P" o. j
3 [- h4 a' B8 c
4.编写代码 - //main.c4 \0 x$ D% j, F. u D% p5 A
- - l/ x8 ~5 v6 Y; |/ v
- #include "spi.h"
, [- [2 k' x! c9 E - #include "gpio.h"
1 G6 E: b% I) j+ P - #include "Touch.h"
h& ?* u, B7 j0 j6 A4 ^ - #include "lcd.h"
1 y% [: X' A2 |3 Z( `- m" \8 u
5 F2 b/ q% G4 `3 j- //校验参数(计算得出)
# |# |- V; A% H% m( z1 x" @% d3 [0 T - Pen_Holder Pen_Point ={
* \# P7 T1 ]. X3 _ - .xfac = 0.259067,
1 D+ y% i! t. k, t% U# [+ K k - .xoff = -20,& k O4 J# \. K, ?1 |/ ? r
- .yfac = 0.158228,
! a& Y5 O8 G( ?! ]" i% R2 o - .yoff = -38, ) D; a5 w" s) [8 i9 t$ f5 G
- };7 o! _6 C1 ?- W3 w6 `* Q6 [6 s
- 2 q5 `. R$ a. P. D) h2 [
- void touch_adjust()
$ I8 |; F1 S& e - {
% z: ~1 ]0 o6 |! ? - Draw_Circle(50, 50, 5,Yellow) ; ; t& n& W7 A6 ]6 }; J
- Draw_Circle(100, 100, 5,Yellow) ;
. }5 {3 E! k3 [; f6 [# K2 z3 I - }
8 o% i& H3 G4 ]" o$ k. x
) T* K/ d( m! W- int main(void)
H" b- X0 G2 z$ P - {) m- z/ j% |6 N* f$ P2 h0 m
- HAL_Init();
4 m" W2 d5 p1 w8 ? k0 p5 Z - SystemClock_Config();
* C8 F. \4 x% ^0 R3 a) X - MX_GPIO_Init();
" p) ?% P9 R; f7 P' o - MX_FSMC_Init();% x8 c/ j! w) ~( P' W a1 J
- MX_SPI2_Init();
; R/ J' M( f% D% v2 T" U! M - MX_USART1_UART_Init();( y( {' e8 W5 s0 P8 S
- ( b4 x6 l2 N2 s: R8 w) y7 v* ~
- printf("this is touch lcd test\n");
- I$ V& Y% g1 {% V9 Z x - lcd_init();. U; i( |* w+ V ` L4 c
- lcd_clear(Blue);" R3 g8 n9 c' S0 {! {# ?' R/ G
- //touch_adjust();
/ R3 N u1 @7 r5 _, P* K - while (1): S: }* e& |- K9 k, C# I" ]3 y4 v) W
- {4 }, J0 ~( R# h
7 C/ M% y8 J# l1 ^- if( Pen_Point.Key_Sta==Key_Down ) //触摸屏被按下$ }, {. m' B' j
- {$ ^& ?# J4 D' d [& E9 Y' D
- HAL_Delay(20); //按下之后会有抖动,因此延时6 w# d" \( _- D; u" l1 p9 S
- if(!HAL_GPIO_ReadPin(GPIOG,GPIO_PIN_7)) //确实按下
% n5 G2 e- o: @2 N. [( P8 W% k - {2 U) I8 R- p1 o* c" o% C: L& O
- HAL_NVIC_DisableIRQ(EXTI9_5_IRQn);//关闭中断
5 e( _# A9 c1 \3 q& ] - //如果不关可能会因为一些毛刺而再次触发中断
, t- ?- `+ _5 l: X6 k - printf("++++++touch+++++\r\n");
" X5 _# D) s; F: u: ` - if( Convert_Pos() )
! Z% k0 D% j, q* W6 v6 K" Q - //存放点击的X,Y坐标值,成功返回1失败返回0
/ h$ g+ G2 b+ J2 j1 \8 v" x - {
5 y. I3 ?! R1 {2 q4 I1 g - printf("x = %d,y = %d\r\n",Pen_Point.X0,Pen_Point.Y0); $ N3 V8 L" ^% D+ l/ C
- //画一个以X0,Y0为圆心,半径为10,颜色为黄色的圆
* A; z; ~) D) v - Draw_Circle(Pen_Point.X0, Pen_Point.Y0, 10,Yellow) ; 9 N Z! l! F( c2 x; ?5 I
- //恢复为0,以便存放下次触发位置
. m3 A7 M, H0 G8 U - Pen_Point.X0 = 0;
* b" h9 ]& L7 n! p9 j) F+ H* W9 ~ - Pen_Point.Y0 = 0;
# q3 O4 B- @ L, q2 I1 T - }
9 Z$ n1 T' y- w- Y6 N - HAL_Delay(200);//跳出毛刺
' e: e/ O/ v" t, r [$ D" W. U - //再次开启中断以便下次触发
6 S$ I9 U3 j* M4 L4 H7 M8 I - HAL_NVIC_EnableIRQ(EXTI9_5_IRQn);
5 T. H$ x0 u/ K8 C - }
5 Q& e0 \; b3 i- _) q1 \ - Pen_Point.Key_Sta=Key_Up;
# Y! A/ C. I8 l - } ! @1 w" @8 I' @% f4 P- y
- }
5 q- F' a2 v4 W. X - }
复制代码
U: b0 Q! y5 q说明:关于程序中的校验参数怎么得到。我们可以看到程序中有touch_adjust()这样一个函数,这个函数的功能就是在屏幕上分别画以(50,50)(100,100)为中心,半径为5的圆,在程序初始化时就调用此函数,之后用触笔分别点击这两个圆,将每个圆点击后检测到的X和Y电压值带入二元一次方程。解方程即可得参数。
3 g) p0 p1 L( b ( k8 C/ P) { }7 O$ ?
- 0 S1 S; B6 c/ L2 J
- //gpio.c
4 s2 f' W$ v' I3 P) f: _ - //中断处理函数: s% L9 w# D3 E
- extern Pen_Holder Pen_Point;
9 f. h% O# \1 H/ }/ A
7 F/ k: K( p$ I5 b0 |$ A- void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
( |: v/ b0 ^+ A& @! U' i - {
4 q# K' q( u- `( n. w - if(GPIO_Pin == GPIO_PIN_7)' J5 O# a, j9 g4 j8 s
- {- K- {$ z0 r7 y# q) _# x( e( B
- Pen_Point.Key_Sta = Key_Down;9 |! M9 n1 w; d1 x" C0 _: k3 Y
- }
6 o. M b9 ~1 E) `! z7 n9 \& p - }
复制代码- //Touch.h
5 F0 }/ L4 i! _' T2 k
3 G. D! ^" `3 [* f- #ifndef _TOUCH_H_
- q3 J3 ]1 a% W0 F - #define _TOUCH_H_ r8 \ z% k: x4 ~
- ! m; J- l7 r! t& T+ H$ e1 X4 }
- #include "stm32f4xx.h"
/ I0 m' w2 n2 X - : y, R* d; v$ ?; X
- //ADS7843/7846/UH7843/7846/XPT2046/TSC2046 指令集/ J& y$ J5 ~; l& _: j$ z
- #define CMD_RDY 0X90 //0B10010000即用差分方式读Y坐标
6 ^0 D' @* n" \6 f - #define CMD_RDX0XD0 //0B11010000即用差分方式读X坐标
( y8 q# q8 E. o9 R/ {$ D - ! R) r) F1 z% \
1 o. i8 `& Q" `3 P% }- /*按键状态*/ + B+ R% \& N! J7 K+ j1 P1 Q" u0 m
- #define Key_Down 0x018 y* M% r3 v" Z% _
- #define Key_Up 0x00
t! `0 ] S8 M9 I( d
+ w2 w3 m! a3 ^# I8 Y/ @# |- /* 笔杆结构体 */# v# r) D3 L! }2 K) N" N, M) V
- typedef struct ! T2 D8 p0 s3 ^1 V9 @
- {
$ |1 u. o4 ^" l/ ~ - uint16_t X0; //原始坐标
, T) n1 f+ K4 h+ t+ O w - uint16_t Y0;
3 ^, A* q, P! R0 q P7 v1 F - uint16_t ADC_X; //X方向的电压值
, @( o6 V9 c* L - uint16_t ADC_Y;//Y方向的电压值 - @2 x6 v. [# I( Z4 X! v
- uint8_t Key_Sta;//笔的状态
& Q$ j p5 l3 N* E# c; C2 D - //触摸屏校准参数
7 R: M: v, E9 r - float xfac;0 [/ c8 ]" q. D1 g' J3 {% ~
- short xoff; //X方向的电压值偏移值' ]/ F* I8 _ A5 t
-
- _/ U& i# G$ f - float yfac;+ q V' j8 K. b6 t7 y
- short yoff;
0 p$ N/ T* i# M5 f2 C5 o - }Pen_Holder;
% ?) U: [+ E% k( I3 N9 | o
2 t1 D- h3 J& |0 R2 P) g1 w! R& ?9 b- //宏定义,用于片选
, u; C8 J& A; M* M - #define SPI_TOUCH_CS_LOW() HAL_GPIO_WritePin(GPIOG, GPIO_PIN_15, GPIO_PIN_RESET)
6 U' W( K2 C6 N7 J' @ - #define SPI_TOUCH_CS_HIGH() HAL_GPIO_WritePin(GPIOG, GPIO_PIN_15, GPIO_PIN_SET)8 n1 B: {3 |# d1 @
. r' f8 O7 l% ~" K! c1 o- uint8_t Convert_Pos(void);
. k+ ?( k% _, H8 S h" D+ h2 v - #endif
复制代码
6 y' i, @: L5 t5 `/ V) y- //Touch.c
; a- f1 r' W" Q5 m# c - 7 [. R5 I \9 l8 Y5 V' K. V2 c
- #include "stm32f4xx.h"8 i+ s( I/ L. ^3 o; q# a
- #include "Touch.h"& t% {" Q) ~9 T, d
- #include "spi.h"
, C! ~+ t& n& K- r: n0 ~ - " \0 I* V( _/ y& w& N, ^
- extern Pen_Holder Pen_Point; /* 定义笔实体 全局*/
: Q+ y4 \5 D' U' x0 G - & t5 J2 `" O( W6 }0 M" ?
- extern SPI_HandleTypeDef hspi2;9 {8 K3 e1 M/ {8 o! `# s/ h
- /*封装了一个发送命令的函数*/
% o8 \5 F) d$ `" Y* E! p - static uint8_t WR_Cmd(uint8_t cmd)7 @5 r8 Y" o5 _! c. W% K
- { + ^1 \/ z3 g) `/ I' C: N
- uint8_t Tx_DATA[1] = {cmd};//将命令放到Tx_DATA中
& i7 S( v! X% ]6 g' y* V - uint8_t Rx_DATA[1] = {0};//Rx_DATA用来接收返回值
; d2 S3 r6 f9 G - HAL_SPI_TransmitReceive( &hspi2,Tx_DATA,Rx_DATA,1,0x1000);4 V" p- P* y' }0 ~ z
- return Rx_DATA[0];
) K4 }! \& `6 ~! p" O - }
0 P$ ?, j. B, Z3 j$ D3 w: I
. q" D" D* \1 m3 D' L- //从SPI读数据
' _# @2 n+ f$ K: S& _% \# e - //从7846/7843/XPT2046/UH7843/UH7846读取adcÖ值
% m' m- V: }9 M! A) J( K - 0 I+ i3 d B' N3 G( g
- /*************************************************/ m9 J0 w6 H, U: J" V) a
- /* 功 能:读取X轴或Y轴的ADC值*/
; f6 F+ i) D' N1 Y/ N4 e - /* 入口参数:CMD命令 */ 8 C* q. C! i: @ Z0 _+ `1 K- A
- /*************************************************/6 l7 [6 F9 r) h- Z- q
- uint16_t XPT_Read_AD(uint8_t CMD)+ p# z& r7 z+ ^6 w, k8 X
- {
- c& [ z! K% M2 q! g - uint16_t NUMH,NUML;
; ^8 v2 p0 H* e5 v0 J- n; G4 C) y - uint16_t Num = 0; 8 c4 Z" O6 i; t8 l
- SPI_TOUCH_CS_LOW(); // CS=0 开始SPI通信 * E4 d6 j6 e7 @/ \
- WR_Cmd(CMD); H, F0 r. t( Y9 A1 x( J: e8 Q; W$ f
- HAL_Delay(1); //延时等待转换完成
% I# p; ^- @% Q- q - . h: {+ t( ]5 K2 a+ H2 o
- NUMH=WR_Cmd(0x00);2 W5 o( U1 p& \; s. v
- NUML=WR_Cmd(0x00); //发送两次空命令来获取电压值
# H; Q# u' S" @/ o& T$ E - Num=((NUMH)<<8)+NUML;
' l) X$ i" O$ q* k - Num>>=4; //只有高12位有效+ j- d/ V) u; [ X$ b8 g
- ; s# \$ e$ v7 ?% |; q
- SPI_TOUCH_CS_HIGH(); // CS=1 结束通信 : d6 T. `+ M7 f
- + d' ]* V7 Z8 L
- //printf("num = %d\n", Num);
) d$ A, ]3 L. S$ P; t1 J - return(Num);
2 R- M Z, x7 P* R+ N - }# B0 G- u0 D; Y! A+ X
- , \% s! Y7 P& d5 r" v. U. K& M
- ) x# g: R/ C# {* V
- #define READ_TIMES 10 //读取次数" H5 q$ x% O. t. l$ K6 Q
- #define LOST_VAL 4 //丢弃值1 P9 v( F0 [) A+ l9 B/ _! {$ R. O
- /*************************************************/; j ?( ?) Z- R9 f+ c
- /*功 能:读取X轴或Y轴的ADC值 */3 Y% y. }% w/ Y
- /*入口参数:CMD_RDX:读取X的ADC值 */ $ m2 V- p% C5 }! c, h& X
- /* CMD_RDY:读取Y的ADC值 */; e( ?: D# O6 } e2 q0 n# i
- /*说 明:与上一个函数相比,这个带有滤波,多读几个数据,去掉一些后求平均*/. \9 z: }4 I1 W& j
- /*************************************************/
: g3 d- O9 e# D9 a - uint16_t XPT_Read_XY(uint8_t xy)
: B1 k0 u! \9 E - {
- t, [" b2 n3 e T; ]0 X/ ? - uint16_t i, j;
/ u9 R1 S( ^1 @7 j% w" y0 d$ p3 E' u - uint16_t buf[READ_TIMES];9 Y. {0 R5 }! g9 ^( x
- uint16_t sum=0;
# f3 \' G' j$ v+ V% a - uint16_t temp;, V7 X- v5 |% h% d
- for(i=0;i<READ_TIMES;i++)
9 U) z) }3 M1 s% m - { . z6 ]# w0 s' h& |
- buf[i]=XPT_Read_AD(xy);
) P9 s$ \" \0 p& F - } / t4 V$ d5 ~) ?/ R- R6 l
- for(i=0;i<READ_TIMES-1; i++)//排序7 O8 p. W6 M$ }2 z
- { f9 {8 Z5 o: \1 R
- for(j=i+1;j<READ_TIMES;j++); `0 d/ \) b1 n8 _5 c9 E
- {
9 h$ Z+ t7 W% V9 |6 G* O2 i - if(buf[i]>buf[j])//升序排列) h3 S2 c# y! P& h9 d
- {7 d7 P. d% O6 o1 \) j+ Q
- temp=buf[i];8 Q- d, G4 j& C# K# z
- buf[i]=buf[j];
4 D! b/ q- V4 P* o - buf[j]=temp;) ^' B# o8 Q: h' q- Z8 ^: S# m# \
- }
, ^) c( h& _4 a' q - }
0 v) U, u" B7 O! l- }) K - }
( q# M! d* I O+ s0 v# |; @ - sum=0;
o2 }. w- K4 h5 P4 Y0 T2 H9 d# r - for(i=LOST_VAL;i<READ_TIMES-LOST_VAL;i++)" Q1 c' t7 O9 b" `
- sum+=buf[i];* A# z1 T2 U6 q: E7 `: Q2 r
- temp=sum/(READ_TIMES-2*LOST_VAL);
9 e- T( _3 v4 y- ]7 s! r" ? - return temp; 6 F2 X5 C3 W# Q2 m! e
- }
+ P* X- r# d& ~- W% V4 _; e G3 _, ] - /*************************************************/6 [+ I# F, P) @7 n2 x9 H
- /*功 能:读取X轴和Y轴的ADC值 */
3 M9 H! `9 V2 d" M - /*入口参数:Pen_Point.X_ADC,&Pen_Point.Y_ADC */5 T9 p6 F2 W/ T" _/ e
- /*出口参数:0:成功(返回的X,Y_ADC有效) */, H/ \- [& J" [; R5 f# v4 t& G
- /* 1: 失败(返回的X,Y_ADC无效) */
% R, Y$ o$ c) N3 `# e V: I& Z3 l - /*************************************************/1 G1 b" R ^1 z+ @- k
- uint8_t Read_XPT(uint16_t *x,uint16_t *y)
" @+ `$ r* c! Z( s+ u - {
- b; M! d4 P$ g# _* T9 R2 g F - uint16_t xtemp,ytemp; & ?- H4 C# M8 Q& y" {+ o( K4 H
- xtemp=XPT_Read_XY(CMD_RDX);
% l1 N8 {0 m1 P5 z" |- W# h - ytemp=XPT_Read_XY(CMD_RDY); ; X6 j5 P- z2 i7 Z8 C) I
- if(xtemp<100||ytemp<100)
! P5 O' a- `; ^ - return 1;//读取失败
* l2 _% c/ M/ r, y4 R7 _' K/ J - *x=xtemp;
6 g4 |9 P' t) R0 U+ z0 o7 Q+ h& C - *y=ytemp;
- n4 L! _' ^' Y- ?& \0 v9 [7 M - return 0;//读取成功
- m+ t% e7 g" u# {- \9 _ - }
, r( L" ]% m6 [ i# p - /*************************************************/2 U) s5 g* v; q) K
- /*功 能:连续两次读取ADC值 */5 T# u$ u# V! B3 S9 i* D( `
- /*原 理: 把两次读取的值作比较,在误差范围内可取 */
( Q1 C& J1 [3 Y" V. S6 y - /*入口参数:Pen_Point.X_ADC,&Pen_Point.Y_ADC */: l9 v9 F5 D, V4 N
- /*出口参数:0:成功(返回的X,Y_ADC值有效) */) J: |) f& i0 N% _ f& ^ O
- /* 1: 失败(返回的X,Y_ADC值无效) */ . x. J* s" {) d) W x. |
- /*************************************************/
- t4 Y' q+ A" {5 E- X1 G - #define ERR_RANGE 50 //误差范围 . X7 d, g, J+ ?# T
- uint8_t Read_XPT2(uint16_t*x,uint16_t *y) 7 a$ m: A2 f7 {
- {# D' }) o3 g! Q, A* y( ^6 m
- uint16_t x1,y1;1 U" W( z$ y3 E; R# K0 J
- uint16_t x2,y2;
; U! o* r: j' R4 Y3 h - uint8_t res; " J$ T8 o& V( {1 z
- res=Read_XPT(&x1,&y1); //第一次读取ADC值
. {# A5 J$ @$ h4 \3 }; v! b, } - if(res==1)7 n' p% w7 }4 | q, b
- return(1);//读取失败返回1
2 c9 H2 S- F0 w, L, N! z7 R8 V - HAL_Delay(3);! U; y4 L( M& l% Z2 p& {6 b
- res=Read_XPT(&x2,&y2);//第二次读取ADC值
/ [; G1 q9 i' s* }: N/ d - if(res==1)
& w' s9 I; \% D" D - return(1); //读取失败返回1* S5 p7 m! e' _$ R+ r# B& s
- //前后两次采样在±50内0 ~% p3 T8 k6 _4 `! I! k
- if(((x2<=x1&&x1<x2+ERR_RANGE)||(x1<=x2&&x2<x1+ERR_RANGE)) 7 i, z$ s5 z0 y4 q' g* ~9 ?
- &&((y2<=y1&&y1<y2+ERR_RANGE)||(y1<=y2&&y2<y1+ERR_RANGE)))
9 `, t2 O' i* q I S - {* Z# b& v4 p& p9 h
- *x=(x1+x2)/2;8 B, \5 |7 U, T) L0 I& b
- *y=(y1+y2)/2;6 W8 M0 z# [2 M, R3 W
- return 0; //正确读取,返回06 K+ X3 o+ w& Q3 L e
- }else return 1; //误差太大,读取错误 & ^" P: @; j/ y' b3 y
- } - B( ]% Z( o# I$ O$ k' o$ ?
) Q: {4 a, y6 F+ H- //转换结果 b0 o3 w: u4 }
- //根据触摸屏的校准参数来决定转换后的结果,保存在X0,Y0中
% Z: q7 f8 D" i( |! g5 h - uint8_t Convert_Pos(void)
- [+ o+ a- f) [1 N# r - {
* Y, ~+ W, ^/ I# G) i# r - HAL_Delay(8);# K- v( V2 G7 m
- if(Read_XPT2(&Pen_Point.ADC_X,&Pen_Point.ADC_Y) ==0 )
0 \% E) K F9 W( {% w - {: O% x+ [) G( L: S; E
- //printf("adc_x= %d, adc_y = %d\n",Pen_Point.ADC_X,Pen_Point.ADC_Y);
$ Z, p7 Z# L+ \# G/ k7 ?$ w - Pen_Point.X0=Pen_Point.xfac*Pen_Point.ADC_X+Pen_Point.xoff;$ E* [2 W; @& `; V, z5 S' i
- Pen_Point.Y0=Pen_Point.yfac*Pen_Point.ADC_Y+Pen_Point.yoff; " m1 @. b7 I' S+ H
- return 1;( d- U+ p- E: z. A' Z
- }
4 O7 C) C! d. U& @ - else return 0;1 v3 R* T# B' K* r/ x# C
- }
复制代码
: z4 |+ B) |% e9 `$ C7 ` \转载自: 骆驼听海 如有侵权请联系删除
M$ [8 M! U- `
4 S1 Y$ Q$ _1 s1 _3 ~1 e. c |