前言 从51到STM32F4学习这么久了,总算找到点头绪了,目前学习了GPIO,中断,定时器,看门狗的基本使用,所以想试着看看能不能做个什么东西,就是想复习一下最近学习的知识。正好上学期单片机课程设计做过一个可以蓝牙、按键校准、带温度显示的时钟,所以我想看能不能将程序移植到STM32上呢?说做就做,经过三天的时间,几次的程序修改和调试,终于成功了!
" ~" ]& M: _6 m2 G1 {+ z& x$ X由于STM32内部定时器的精度还是很高的(一小时慢1s),所以就没有使用DS1302时钟芯片(关键是手里没有),显示上和课程设计做的有些不一样,没有增加日期和星期显示,就时间、闹钟和温度的显示。
7 B: A1 Z5 ?+ r- V1 D3 \今天先学习一下1602液晶的显示,那么如何用STM32控制LCD1602显示字符,字符串或数字呢?( i4 X. ^- z. T6 s) T! Z7 U
1602液晶介绍工业字符型液晶,能够同时显示16x02即32个字符。1602液晶也叫1602字符型液晶,它是一种专门用来显示字母、数字、符号等的点阵型液晶模块。它由若干个5X7或者5X11等点阵字符位组成,每个点阵字符位都可以显示一个字符,每位之间有一个点距的间隔,每行之间也有间隔,起到了字符间距和行间距的作用,正因为如此所以它不能很好地显示图形(用自定义CGRAM,显示效果也不好)。市面上字符液晶大多数是基于HD44780液晶芯片的,控制原理是完全相同的,因此基于HD44780写的控制程序可以很方便地应用于市面上大部分的字符型液晶。1602LCD是指显示的内容为16X2,即可以显示两行,每行16个字符液晶模块(显示字符和数字)。
E7 G: N% \5 \; k2 u4 p+ J& x由于1602液晶是5V供电,引脚高电平为5v。不像51单片机IO输出高电平5v,STM32高电平只有3.3v,低电平0v,找到了1602的数据手册,里面居然没有说明高电平电压范围,到底能不能驱动呢?我又查找了1602驱动芯片HD44780的数据手册,其中有一个电压特性,如下图 m" J/ a+ Y1 B# h7 E
" Y. |+ i1 q# _1 q
' @+ l2 G1 }+ P9 X2 ^ 可见用STM32的3.3v电平驱动显示完全没问题,看到这我就放心了,代码经过多次的修改后,终于成功显示了,所以还是要多看看数据手册。
}/ `4 o* X( X# _* l2 `硬件电路连接
- f# M6 h9 R; X, t, T6 h# B: D5 G) o# C+ K0 u5 n4 s. D% Q! i
, V) w2 O0 Q; }: {
5 X3 S0 M8 k& K# F
. Z9 L) W. }/ t5 j, }# g4 K4 ] ' u3 e- f& d4 D9 J. @
程序设计
/ ?/ C5 j2 r: t5 n8 o% N1.控制线宏定义,通过位带操作,以后就可以像51那样RS=1来操作了,是不是很熟悉呢?这里还将数据线的8个端口定义为了一个LCD_DB6 z, M) z* i3 B4 `4 M$ ]3 S! i
$ m! U; f- [4 J, K g( w" i
- #define LCD_RS PAout(1)3 Z' ^2 G+ b# W$ C8 u2 O
- #define LCD_RW PAout(4)
1 V0 `. @: P; {: r - #define LCD_EN PAout(6)3 q% F/ I' [, r4 [) M' \. N
- #define LCD_DB GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10 | GPIO_Pin_11 |GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15
复制代码
, g" v7 e+ F1 i, z, ]* H* ?3 |2.LCD相连的GPIO配置,将数据线端口配置为OD(开漏)输出模式,可以做双向IO使用,在检测LCD是否忙时,需要读取D7位的状态! t& M( i3 p+ p
- void LCD_GPIO_Config(void)
* y, O0 c+ T1 O" w/ A) Q - {9 J, ?) i! G+ {3 S% k7 `
- GPIO_InitTypeDef IO_Init;- P7 `' C' S) s5 U* R# a$ @
- * E) n. H9 A1 X- u: l: P
- RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA | RCC_AHB1Periph_GPIOE,ENABLE);8 m3 {# J* w S U5 k" R. d. a0 q
- & S% W& t) L! _! J5 W& t% X
- /*控制线初始化 : */
2 B) R q6 {0 V1 d5 F" [5 y3 w - IO_Init.GPIO_Mode=GPIO_Mode_OUT; //输出$ c7 h! Q4 C8 L( R! _; T- j2 k5 p0 _
- IO_Init.GPIO_OType=GPIO_OType_PP; //推挽模式
; B' ^5 R9 C y" x5 y3 Z - IO_Init.GPIO_Pin=GPIO_Pin_1 | GPIO_Pin_4 | GPIO_Pin_6;
1 H6 v- h/ l! i3 u& [ - IO_Init.GPIO_PuPd=GPIO_PuPd_UP; //% e# R7 X- _; U9 x, a- J
- IO_Init.GPIO_Speed=GPIO_Speed_2MHz; //GPIO_Speed_2MHz/ Y& R5 }& }/ P" L6 O/ L
- GPIO_Init(GPIOA,&IO_Init);+ E- [4 {$ p% i; W" z
- 0 u2 [' z8 |( Z: T2 A
- /*数据线初始化*/
, Z4 I. O5 `( g- O. Y - IO_Init.GPIO_Mode=GPIO_Mode_OUT;
|- ~" i; p8 e% ] - IO_Init.GPIO_OType=GPIO_OType_OD; //开漏输出可双向5 F. ]/ {' m+ g3 }: ?
- IO_Init.GPIO_Pin=LCD_DB;
! ^- {' H( p4 `( Q& f$ k8 C3 y - IO_Init.GPIO_PuPd=GPIO_PuPd_UP; //上拉9 [; O' ?- s. [7 M* T* |. O
- IO_Init.GPIO_Speed=GPIO_Speed_2MHz;
$ h K; u5 f( k1 M! J3 | - GPIO_Init(GPIOE,&IO_Init); + Y/ U M& g% a9 G( v% ^: Z( `( F
- /*测试 : 高电平3.3v
- x" y) d3 l4 d* y) L# o: L - LCD_RS=1;
a# q: l1 p: l, u1 a - LCD_RW=1;
( n& J& f w1 A1 A" P6 V4 s# j - LCD_EN=1;
* v# `+ I4 {; q: d" {; x - GPIO_SetBits(GPIOE,LCD_DB); */! w- D6 o% l. k4 K# {/ N) N
- }
复制代码 ; _7 T! }0 M- |8 E
* E3 \* f) D$ Y9 A7 Z+ i1 Q; i; H
3.实现P1=0xff功能的函数,带参数
+ \! v, M" y# r7 w: k- void GPIO_OutData(u8 Dat)
/ Z3 |% P3 U4 K# B: ]7 | - {
5 f! ]1 x& H7 I6 {5 [8 p6 i - u16 tmp;
) b1 F, ^ }7 y a' S - tmp = 0;
A; \& x- W6 m0 E, h' @ - tmp =Dat;
- j0 y, k3 y) l - tmp <<= 8; //数据左移到高8位
9 |! [( m4 ~" X* ]; s) o( n8 j# \ - GPIO_Write(GPIOE,tmp); //数据写入到GPIOE高8位
! m3 C: h# z7 ~; [6 l! q - }
复制代码 + @$ Q1 z5 \0 {
4.检测LCD是否忙. f5 R' S5 E& R& ~/ f
- void LCD_CheckBusy(void)# z, e( M; j2 O1 ^4 |+ M
- {
) X; C2 T0 q0 I: d - u8 sta;. ]; M5 J9 H$ z7 g( S6 s' J, n
- GPIO_OutData(0xff);# a* C* T2 e3 e) I
- LCD_RS=0;! A/ {& e4 K6 ?: q8 ^& _- q
- LCD_RW=1;
* A5 ?* W# ^& z, L$ a" o0 C( _ - do{6 _3 S6 Z$ v2 l- @% e G- x) I
- LCD_EN=1;; a9 p: m( ^ K7 S4 R
- delay_ms(5);) X: Q W9 m G
- sta = GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_15);
/ m' Z/ e" d2 s* {5 F" i8 W - LCD_EN =0;/ K0 E8 F. _* G7 v+ E0 q
- }while(sta & 0x80);6 S- h. x' W: b# E0 L
- }
复制代码
& k4 J/ T$ F# F3 b9 o' _
0 w9 L' g! ]/ A, O5.向LCD写一字节数据
+ U- L! X' C L4 T2 l- /* LCD_RS = 1, LCD_RW = 0 */" }4 Z) |9 X. o: F! N
- void LCD_WriteData(u8 Dat)2 y3 \" }- ?. ^7 J
- {
4 b6 J$ s, r$ T
H- d8 [$ N4 A5 n0 a- LCD_CheckBusy(); //忙则等待
7 ]2 M/ ]# a7 Z+ ]2 ?7 W - LCD_RS=1;
c9 x1 q- G: D9 B' k - LCD_RW=0;5 r& m, h p0 `( }
- GPIO_OutData(Dat);
2 x0 f2 k3 Q- G7 o9 n' M - LCD_EN=1;
3 b) ]: W. V: a9 g! u; J( [7 W& R+ _1 s - delay_ms(1);
# m# Z$ j2 e& z8 v* t& G" [6 U. W - LCD_EN = 0;
- v$ Q8 K$ v. A! E2 e8 K" Q - 9 D8 m3 i0 l- L, l/ ]! p7 `8 O0 u
- }
复制代码
& T* ~8 j+ R Z 6.向LCD写一字节命令
2 o8 }7 ?8 v2 u, Z8 c/ t6 j9 |" w- /*LCD_RS = 0, LCD_RW = 0*/
, A* r" p$ l. y& _& Q$ T) e - void LCD_WriteCmd(u8 Cmd)) S# R; \ O% S5 u& u; j8 e
- {6 E% w! T0 T2 E U5 p {- X6 f
- LCD_CheckBusy(); //忙则等待+ h2 z+ `7 b3 V3 [1 m' I6 E
- LCD_RS = 0;
$ c0 [- g4 h. }% @' i4 O: H3 S/ i - LCD_RW = 0;
% ~) I5 C% q8 p - GPIO_OutData(Cmd);
; D1 S8 Z! O2 C - LCD_EN = 1;/ E: e$ X* H. q
- delay_ms(1);1 O; ?# h) V6 Q' i1 {9 t
- LCD_EN = 0; 8 ?" K5 K+ Z( `8 `' T3 T5 N
- }
复制代码
7 [) k4 A3 ^# ?; c4 Q/ n/ a# d! `. z7.LCD初始化8 T- e ?) b; d. w
- void LCD_Init(void)
6 U/ P# A" d! o1 B - {9 ?7 B2 A* g. e o
- LCD_WriteCmd(0x38);
* E: W' a: h; Y+ N( U6 i; { - LCD_WriteCmd(0x0C);9 t' r+ J& I, d8 _8 w% G! {
- LCD_WriteCmd(0x06); /*显示光标移动设置*/ delay_ms(1);
3 L) m# K$ X9 t, `; o* p - LCD_WriteCmd(0x01); /*显示清屏*/
' b$ t1 d0 O$ h) D+ n - }
复制代码 ) H) }- P$ h G& C
8.LCD清屏% |& c( ~+ J: K
- <div>void LCD_ClearScrren(void)</div><div>{</div><div> LCD_WriteCmd(0x01);</div><div>}</div>
复制代码
! l0 r: ^9 e" H% u; Z8 G- |0 `
* Y1 h M: m4 Q" r, r/ X8 e! W' X2 x
9.根据xy坐标,写入对应的数据位置& p; y; K8 l& p, Y- j: L+ Q
- void LCD_SetCursor(u8 x, u8 y), q/ J- Q- H# R# u+ s
- {
3 h; L) Y; ]+ @ F- G; D# k$ e' m - u8 addr;
P) \8 t9 ^6 c) C6 F1 `. ^ - if (y == 0)
0 w: f+ S7 N. a( ]' R$ S3 I W+ Y8 ^ - addr = 0x00 + x;
* X5 j: z4 [- u5 e - else: I2 y) H: w& N; n
- addr = 0x40 + x;& t @& k$ k! x
- LCD_WriteCmd(addr | 0x80);! Z7 u" p" w3 z w- x
- }
复制代码
9 k$ Y1 P6 K$ ~- s% Z7 p6 X 10.根据xy坐标显示一个字符
1 P7 f% b6 h0 X, b9 g- void LCD_DisChar(u8 x,u8 y,u8 ch)# f' T. o/ X, o7 T. a' P7 }
- {
! I2 H/ x* D1 F: M* w2 J& o - LCD_SetCursor(x,y); //字符显示位置设定
; P7 F. O8 d4 m V' P - LCD_WriteData(ch);
, i' N" b/ c" S) K9 n8 l1 W - }
复制代码 ) w& E/ u1 ~+ C( f& F, o
11.显示两位的数字% x# N. T) G/ Q3 x: n* d( K
- void LCD_DisNumber(u8 x,u8 y,u8 Num)
4 X: |: U( }, | - {; K- O- s7 b* o6 B9 W
- LCD_SetCursor(x,y);. Y4 ~, }7 [* _1 V1 a
- LCD_WriteData(0x30+Num/10);! }- |4 x+ y3 |/ p- H/ L7 E" v
- LCD_SetCursor(x+1,y);
3 d" T+ \5 z5 R$ @6 A6 l - LCD_WriteData(0x30+Num%10);. {- J, ~' h4 u; r& p2 u! c
- }
复制代码
6 [. [* |5 _# b/ i
" z$ I( ]2 b0 C! Q12.显示字符串+ o& w/ \' a0 @( c7 a5 c$ N
- void LCD_DisString(u8 x,u8 y,u8 *str)
% `. V& p" W( |* I; D - {
3 {. N+ y: |4 U5 L7 z* b# q - LCD_SetCursor(x, y);8 [# A4 g3 F5 G4 W# G0 R
- while(*str != '\0')5 ~ w& ~8 Q# R
- {
; a8 f9 I% S# z8 J, W+ h" m - LCD_WriteData(*str++);
! b7 k: \: K6 r1 v3 _& N) N - }( _9 K0 h% n3 g5 ]7 x" A) l
- }
复制代码 ( A- @# u4 L* Y# q$ A, w1 p# }
主函数
4 @) I& Q$ V. Z: `) j( `# j) y; k- int main(void) O! H9 ]5 e# E4 Q- J2 ]
- {
5 j6 }2 J) D) s" _; t5 M, K - delay_init(168);. y& b* w5 g. c& z2 S8 X+ C& C
- LED_Init();
/ D. [- t4 a6 j* m9 b" n - 3 O& g H8 k. F* \, M' A
- LCD_GPIO_Config();+ c' q4 N2 k) T+ c; d N* C
- LCD_Init();* h7 X# ]0 h3 T7 a+ x9 A
- LCD_ClearScrren();; C. r+ t8 q `% G: _% K
- while(1)
) I( u1 h( r9 M2 W9 G" z* e1 m - {/ H& l4 H1 m6 V. N& ?/ Y4 S
- delay_ms(500);
0 Y7 Q( a$ q9 C: M; E- q - LED1_ON;! t4 P4 D z& {2 ~) ]
- LCD_DisString(0,0,"abcdefghijklmnop");. C/ P4 U- Y5 V! T7 w* |
- delay_ms(500);
! k- s$ n' S' j7 J7 D - LED1_OFF;: ^. ]" b9 L9 g: ?/ w9 b
- LCD_DisNumber(0,1,56);# {; |; q4 `+ z( ?$ ]9 [
- LCD_DisChar(2,1,'a');
7 _' G* [2 K) u5 e) y - LCD_DisString(3,1," Hello World!"); l& B% T$ J# C; V
- } ' @8 d+ m/ B) W+ \) }$ W
- }
复制代码
) M5 W0 `0 z W4 j$ {5 E& `( Q" E$ c7 b' S$ \7 N) |
实际显示效果:1 g8 `5 x- d* N$ y) D1 n7 a
# C, }; x) A* L v, x, x1 Y
$ j( }: V, R4 M' t2 g( x5 f6 |% m3 }8 s& f) ] l
显示非常完美,和51驱动没有什么区别 ' N7 L5 ]1 _) @( c% O3 W J
( h3 n: Y1 l5 g7 S+ n% Y n
总结:
7 }2 B* |6 I' d: {实际运行时,不使用检测忙函数也行,1602显示正常了,接下来就是增加定时器显示时钟了。在设置IO口模式时,可以将模式设置为开漏模式,就可以实现双向IO的目的,即可输出,又可以读取外部输入。通过位带操作和宏定义就可以像51那样直接操作IO口输出高低电平了。
- r' B1 \& {( l) r+ B# d7 B6 L/ c* o6 Q# n3 b1 J8 i
# y0 {6 }3 {5 l' W( r3 ^! _
& T) P) z6 T, n7 D |