最近项目中需要用到数码管显示,于是买了一个TM1637芯片驱动的四位数码显示模块,现将调试过程记录一下,方便以后参考。$ W2 y) w- n2 m; q9 k
. j3 R, o, B. O. [' S& E7 h5 d
使用的单片机是STM32F103C8T6最小系统1 ~0 S# } r- o+ P) p8 ?8 ?9 h
" z: i* N& {( d' \9 y9 ]
, t6 J( b7 a- M8 N; O# G' H( V5 A. Y f$ D& `8 `+ Z8 Z: U
使用的数码管模块是TM1637四位数码管显示模块
2 D& ?2 B, D( O& P, e' E& Y4 |6 `0 k7 U0 ^ N$ x; f% K
7 R5 ~; z2 `- W8 f) |
9 ]) L* C3 D. P) j4 \实际运行效果
4 B: [8 Q3 R) N$ l* U j# l1 k0 R) m. N# A! c8 U; H9 ~5 n" G
9 S* P$ \. Q- y t/ @8 l
% N" l, D: D- H- x下面先看一下TM1637和数码管连接的具体线路图0 ^1 n6 v0 Y& m% D }
6 k9 w' Q; R8 b$ ~* J9 F% I& o
- e$ ]' \) E5 C2 e
; i$ Z! t3 l6 L* G X- d" {
实际使用的模块没有带按键,只用了4个数码管,模块和单片机连接只需要4根线VCC、GND、CLK、DIO。芯片和单片机通信使用的是I2C总线,下面就来说一下如何通过I2C总线驱动这个数码管模块。
D6 H2 m. O8 u) T" V
' T1 t h0 u9 P- o+ V 为了方便移植,这里使用 IO口模拟I2C总线,所以首先要将延时函数准备好,延时函数使用任何一种方式都可以,可以根据自己的习惯使用自己的延时函数。
( B2 S, I9 B2 N% N2 v+ [" M4 t! E5 \: Z
) z" Y4 p2 r1 W0 |4 J5 D& {- /*
0 |- @! }: ]$ Z. [' Z/ O* |& T( U - * t : 定时时间
( G4 E7 Y* U4 @8 P5 [" { - * Ticks : 多少个时钟周期产生一次中断8 l! F) y' ^2 w/ U) ?
- * f : 时钟频率 720000004 S: n6 l* o9 A# M6 e
- * t = Ticks * 1/f = (72000000/1000000) * (1/72000000) = 1us) T+ i0 T7 O2 v# z3 ]
- */
, l( ?( c; \( u- u0 t - 7 t! t( T9 J, Q, l' c/ o2 J& [
- void SysTick_Delay_Us( __IO uint32_t us )- w* \/ W' _% X4 |2 X I3 m
- {
P+ I% U, n& z* s" u - uint32_t i;
& S1 ~& d8 ^7 j4 G1 J" P4 r! R/ { - SysTick_Config( SystemCoreClock / 1000000 );
* d" e) ?: R j' L" j - 4 o6 d; s# G! b
- for( i = 0; i < us; i++ )
8 f: x) q- J0 e+ q, e/ ~ - {
- O. R7 H- w) S - // 当计数器的值减小到0的时候,CRTL寄存器的位16会置1/ @2 p* h2 A: B$ \4 r( k) K& [
- while( !( ( SysTick->CTRL ) & ( 1 << 16 ) ) );" T& I1 V: w5 ^7 R, ]: F. W
- }# l( E8 [! O% B+ h
- // 关闭SysTick定时器
2 N' v. b0 O6 O8 h P8 N) o, Z- D - SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;
+ q! A$ Z# p* A& Q - }+ X+ |1 W3 r( ~( M: G
- 3 o+ n+ V2 O! a. m9 ^' Y
- void SysTick_Delay_Ms( __IO uint32_t ms )( {1 ^! s$ ~" @3 k6 f+ `
- {. ^- M! i @0 z8 c
- uint32_t i;' b" K2 b+ A* z" K% I6 O P; R
- SysTick_Config( SystemCoreClock / 1000 );* g" Q& j) I V5 W' o# `; U' T
- * Y8 D6 W# L' I1 y
- for( i = 0; i < ms; i++ ): q5 z$ m/ J- z: h# M3 b! ~) a
- {
1 p" v% }. @3 W( ~- F! p - // 当计数器的值减小到0的时候,CRTL寄存器的位16会置1
# z6 w! ~# @) d' B* W. B. v - // 当置1时,读取该位会清0
. C! r5 @# s* q* \ - while( !( ( SysTick->CTRL ) & ( 1 << 16 ) ) );
8 ^" \# B% s+ E: w d% v& B - }
7 {8 U( N C5 X( }- l, d - // 关闭SysTick定时器+ P/ B. U2 ?. T5 _+ J
- SysTick->CTRL &= ~ SysTick_CTRL_ENABLE_Msk;2 i8 ^/ I/ _) ^, I
- }3 ?7 Q5 |2 T- P, O2 [8 f
复制代码
# G& x& j2 r E( f; R+ f3 T 这里直接读取系统定时器的标志位来进行延时,设置系统定时器1us中断一次。直接判断系统的中断次数就可以实现us级的延时了。( [/ b" O s6 H; d$ p
" v: i( p$ n6 C0 u 下来就需要编写I2C的时序了,官方资料上也提提供了参考代码,这个代码也是参考官方代码修改的。( f3 A- j. T. C7 V' M: [
$ s7 I( {. ]' Z; w& M, a, ]# d8 W 首先要定义需要用到的IO口,为了方便移植,将所用到的IO口直接在头文件中定义,需要更改IO口的时候,只需要在头文件中修改就行。% {3 O: q! ^1 F, {1 ~: D$ p, ?
1 o. c+ h$ v# l( ~3 L/ Y9 i
- /* 定义IIC连接的GPIO端口, 用户只需要修改下面的代码即可改变控制的LED引脚 */. ]9 x# I$ R; W8 u x5 Z; r. B1 r
- #define TM1637_CLK_GPIO_PORT GPIOB /* GPIO端口 */
, r5 ~; Q1 A& A4 [5 V - #define TM1637_CLK_GPIO_CLK RCC_APB2Periph_GPIOB /* GPIO端口时钟 */
% {+ |8 A& n r0 V- `, ~ - #define TM1637_CLK_GPIO_PIN GPIO_Pin_6
( g# }) u8 w' h$ z - / v: G( K& s4 k
- #define TM1637_DIO_GPIO_PORT GPIOB /* GPIO端口 */; {3 A& W# @; ?# N) a0 k) w
- #define TM1637_DIO_GPIO_CLK RCC_APB2Periph_GPIOB /* GPIO端口时钟 */* y8 J) b! ~0 U3 z1 ~
- #define TM1637_DIO_GPIO_PIN GPIO_Pin_7
复制代码
9 c) N! |2 w/ \% F) y在模拟时序的时候为了方便编写代码,将用到的时钟口和数据口也重新定义。
# C0 w- ^0 Q+ y7 `0 ^: }6 Y- m9 b0 N, t
- //使用 位带 操作. P1 a! C& z6 d$ B; W. `
- #define TM1637_CLK PBout(6)0 E5 F6 N- [1 R1 F; ~- G5 [
- #define TM1637_DIO PBout(7)# e: i1 D9 ]5 R# V
- #define TM1637_READ_DIO PBin(7)
8 d% O. d" A5 w2 ~ - ) H ]3 f$ c8 c! f, N# o+ e) i) k
- //IO方向设置 0011输出模式 1000上下拉输入模式
4 c S. ^: P! {( `% @ \ Z - #define TM1637_DIO_IN() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)8<<28;}
6 T9 r! v! @# Q8 N Z: M - #define TM1637_DIO_OUT() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)3<<28;}
复制代码
q* e, ^2 } P _% }* |) U+ {下来需要初始化 IO口
( l' S3 B2 j0 Y" Q2 O2 M
: u6 G( v, d: B9 z! D' h- //端口初始化1 H6 i" k+ s+ B S: T$ F! M- k$ ^: T
- void TM1637_Init( void )
5 J" ~+ S3 u/ j! I- | - {
3 X% ~* }/ }" s' j# T/ o - GPIO_InitTypeDef GPIO_InitStructure;; v* U4 e, u- X' z
2 P/ C9 x0 {0 @) I- RCC_APB2PeriphClockCmd( TM1637_CLK_GPIO_CLK | TM1637_DIO_GPIO_CLK, ENABLE );
, K! N, `+ n9 Q) o- ]3 |6 D: |1 l8 a - 0 @" I' ~' J1 P8 E
- GPIO_InitStructure.GPIO_Pin = TM1637_CLK_GPIO_PIN | TM1637_DIO_GPIO_PIN;5 F a( s% E7 o: ]+ @' G
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
- l6 o6 p* z- k: U8 L: O+ F - GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
5 a; J! U9 c/ M% V% n - GPIO_Init( TM1637_CLK_GPIO_PORT, &GPIO_InitStructure );1 i* O5 Y! h; u5 S# a
- }
复制代码
* H0 f) J+ M: A7 P下来模拟I2C的时序- , U. h3 a) h1 x. ]
- //起始位 CLK为高电平时,DIO由高变低! `3 X6 p5 T. ~. D# ?7 M" g
- void TM1637_Start( void )
6 x+ g2 |, m$ q - {0 \4 v6 H! D- I' g3 U2 ~+ d. |( |
- TM1637_DIO_OUT();, @# d% D( u6 c
- TM1637_CLK = 1;- s: r# l. ^2 y# g q
- TM1637_DIO = 1;
) ?+ H* _% ?' f5 U) V6 D# ~ - delay_us( 2 );
' l9 b' M9 K r& {2 f! o& l - TM1637_DIO = 0;
- P! ?' J- R$ r' B+ s- x6 M - }8 h/ C* @1 t6 v' F- k
- / d- q/ {8 i& g9 o
- //等待应答 传输数据正确时,在第八个时钟下降沿,芯片内部会产生一个ACK信号,将DIO管脚拉低,在第九个时钟结束之后释放DIO总线。
8 k& n8 X6 g& `# F" o0 z @4 Q - void TM1637_Ack( void )7 A/ y! S" E+ E8 [5 s" Z/ K4 m
- {
% ?5 i% J# V! N/ t - TM1637_DIO_IN();# J# a- [# K! k' q$ i% w2 ?
- TM1637_CLK = 0;2 _* M0 d+ \$ N% P) F3 Z: }
- delay_us( 5 ); //在第八个时钟下降沿之后延时 5us,开始判断 ACK 信号7 f( f* x1 A' x2 ]/ {5 a$ H( d
- while( TM1637_READ_DIO ); //等待应答位 这一行代码也可以不要 不影响实际使用效果 在使用软件仿真的时候需要屏蔽这句代码,否则程序就会卡在这里。
7 ~9 @, T9 H: U/ G8 f3 D8 R - TM1637_CLK = 1;
( k0 K2 B( n; J/ d" l3 m - delay_us( 2 );$ L8 ~, U; ^/ v; c* y
- TM1637_CLK = 0;
8 D! V9 p1 \3 M% { - }' [7 `. T- l, Q$ C
+ [! k9 N3 O' f9 u& N2 d% |* F- //停止位 CLK为高电平时,DIO由低变高
( R' m C/ {! I - void TM1637_Stop( void )
4 L. b. G5 ^" ^# ~$ c- m - {1 J/ ^' @1 y. f' L, t
- TM1637_DIO_OUT();
. Y% R/ E J# h6 R. R - TM1637_CLK = 0;8 H- N, \5 b* ^% j1 _
- delay_us( 2 );0 f+ P9 e5 z& T) p4 o
- TM1637_DIO = 0;
& {# |6 u/ C' v) @ V - delay_us( 2 );
) n6 l, w9 {2 | - TM1637_CLK = 1;
6 S8 s, J. ^5 r2 d6 i - delay_us( 2 );- ~! u, Z+ p$ D9 i A
- TM1637_DIO = 1;# N3 h( r8 F( G4 T
- }
2 p$ P2 K1 j2 a; |( t5 W6 Y - //输入数据在CLK的低电平变化,在CLK的高电平被传输。; {, `8 Q" Q+ \, k! C
- //每传输一个字节,芯片内部在第八个时钟下降沿产生一个ACK
; I' d- Q- B1 @* v: r9 _$ t; X0 B - // 写一个字节+ n4 O7 ? w8 O# l6 I; v
- void TM1637_WriteByte( unsigned char oneByte )
+ R+ I; q( _- H. k0 h - {; [& m6 y9 }9 p/ W2 n( H
- unsigned char i;
( W1 g0 d7 W1 F6 i8 g - TM1637_DIO_OUT();. _' K2 N8 f5 i: x
- for( i = 0; i < 8; i++ )
8 x' ]3 E% G) t i. [' | - {& P6 Q/ [: T2 Q* @, U- L1 q
- TM1637_CLK = 0;
- q" _+ C7 l4 P1 z) U - if( oneByte & 0x01 ) //低位在前
# [% z" f/ g2 V+ f; M# l1 z - {6 r& Y3 x/ \) R( y: s
- TM1637_DIO = 1;) V( x/ D' r% h2 W
- }) M' R3 w! K' a9 n5 S" x$ G
- else( q9 F3 f8 D' f. r
- {0 b( ^) _4 @: S) d* b2 @+ ]
- TM1637_DIO = 0;
9 P7 b8 `: f3 i* X }1 \ - }3 H* J% ^# n* I" F& K6 f7 X% }
- delay_us( 3 );, M) |) k1 ^) ~) M5 j
- oneByte = oneByte >> 1;
& A2 r6 R _+ z% l8 N; \ - TM1637_CLK = 1;
, P" s# j' U2 B/ I5 G - delay_us( 3 );
1 _. K% M7 c/ H# ?* S - }; w2 z1 a/ _; c* B7 \- ^
- }
& C& Q6 ? t& _1 q7 K
复制代码
8 @7 w& x! i* p 需要用到的时序主要有开始位、停止位、等待应答位、写一个字节。通过上面这四个函数就可以直接操作TM1637芯片了。* H6 q: g8 n5 e8 u
) X5 H8 R) x4 H& K8 \& v* f( @* a9 V
根据官方的资料,有两种写数据的方式,第一种是地址自加,第二种是地址固定。先来实现地址自加的模式。
& I5 M! Z$ [* ]+ Z1 c3 p# f' B- _" ^) k( s5 ]" m3 c
l- F! x; U9 z }5 D V# [6 r; _2 O1 `0 C# J
根据这个时序看,首先要发送起始位,接着发送设置数据命令,下来等待应答,最后发送停止位。下来在发送起始位、设置地址命令、等待应答,发送显示数据1、等待应答、发送显示数据2,等待应答,……发送显示数据N、等待应答、停止位、起始位、发送显示命令、等待应答、停止位。
+ j( P f9 E/ H! J' k5 ?6 ^/ M+ K/ J. S" B9 Q$ v
下面看看官方的命令表1 E) d/ q3 _3 e7 E# a8 K% e1 ]
$ b4 C+ u6 O& B2 W6 j+ @; X
1 ?4 V! G* V' V) Y2 B2 b& `2 Y
1 V5 F8 s) e- I9 k' S5 ?, P5 Y
根据上面命令表可以看出,数据命令中,自动地址增加命令 B6为1,其他的都为0。也就是0x40就是地址自增命令。接下来看地址命令,显示地址从00---05表示6个数码管的地址,此时B7和B6必须为1,也就是显示地址范围是0xC0-----0xC5。最后是显示控制,这个命令是控制开关显示和亮度的。开显示需要B7和B3为1,也就是0x88,最后3位0--7表示8级亮度。这样显示控制的值的范围就是0x88-----0x8F。/ P. C1 s8 {5 G. K. u$ g, E
" r1 \# k9 K% ~
命令分析完之后就可以编写代码了$ _, T( q8 _/ I9 ?9 j% {
$ D" ~2 L/ _& \' W4 Y5 T- //写显示寄存器 地址自增
^8 {$ B6 Y! F( X - void TM1637_Display_INC( void )
5 @4 f2 y3 g# A* m. f& ^; r+ @ - {) L7 o; z! P3 f j
- unsigned char i;
+ B! y- y: d1 W - TM1637_Start();, ^' p1 T; @1 }8 @
- TM1637_WriteByte( 0x40 ); //写数据到显示寄存器 40H 地址自动加1 模式,44H 固定地址模式,本程序采用自加1模式$ Q- ^% v' k L8 C9 I4 K4 \
- TM1637_Ack();
; ~. F6 B& {) S# @/ k; Y/ S C( \ - TM1637_Stop();& \! w: E4 O5 y2 P K7 S' v2 X1 Q% y1 O
3 f, d: p$ a8 G9 m! l2 D- TM1637_Start();3 j; `" l- Q; D5 Y$ r" ?
- TM1637_WriteByte( 0xC0 ); //地址命令设置 显示地址 00H; ?, e8 i' O2 Y0 }9 O
- TM1637_Ack();: P7 Q4 }# I+ A8 k! g G0 v
% p% }" V1 {: o0 S# e/ X. q- for( i = 0; i < 6; i++ ) //地址自加,不必每次都写地址9 I0 w- B* A# }$ F! j$ a G
- {; b- _: K# s1 L" o
- TM1637_WriteByte( disp_num); //发送数据 disp_num[]中存储6个数码管要显示的内容5 I7 Q4 M' s, Q) T; ~
- TM1637_Ack();
# v0 e& Q# d; ^# F* w/ j) O - }
# Z' Y% ^. x; Y - TM1637_Stop();
9 V' R) r1 f! ~, f1 y5 Q1 |
/ w2 r1 _9 A0 B( b- TM1637_Start();1 j/ o8 {6 L* [
- TM1637_WriteByte( 0x88 | 0x07 ); //开显示,最大亮度-----调节脉冲宽度控制0---7 脉冲宽度14/16
1 a: v, G) X4 G# [ - TM1637_Ack();0 Q$ I! q1 l5 b% X, n+ m
- TM1637_Stop();
$ M+ n: _! m' `/ }# d1 X( m
6 J8 A- q s9 q' i1 M- }
复制代码
* {8 t- q0 h6 {% \: m1 N, g, g0 }( f 发送数据显示命令和显示亮度命令时都需要停止位,但是发送地址命令和显示数据内容时,是不需要停止位的,可以连续发送。数据循环发送结束后再发送停止位就行。其中 disp_num[]数组中依次存放6个数码管需要显示的内容。这样如下需要改变哪个数码管显示的内容是,只需要给disp_num[]数组中的对应位置重新赋值就行。
- V: d( ^1 {0 t
~; Z4 z; t$ w3 U+ a 下面编写地址固定的写数据模式+ }, q" P/ w3 h# ]
/ n* ?( V8 O1 d y* G7 }
1 C" [: A+ D4 E( q* [0 t+ R2 J, R h1 @" J
时序和上面地址自增的基本一样,只是设置数据的命令不同,根据上面的命令表格可以看出,地址固定命令B6和B2都为1,也就是0x44。 + T7 W0 B* I, x7 F r" ^
; Y) i; D: W& k( a% X/ m* _- //写显示寄存器 地址不自增2 u1 K# Q/ f9 [* ^
- // add 数码管的地址 0--50 ?- E: O7 ]( }) t; p
- // value 要显示的内容" `4 S$ ^( j) P" Y2 m+ @- K
- void TM1637_Display_NoINC( unsigned char add, unsigned char value )) Q* @; q$ I4 A
- {
, F$ w( \, x$ P/ e5 C [4 Z - unsigned char i;3 `; y: o4 Y. O; M' w
- TM1637_Start();
% q3 U# E$ m/ @( m) f3 M, i$ j - TM1637_WriteByte( 0x44 ); //写数据到显示寄存器 40H 地址自动加1 模式,44H 固定地址模式,本程序采用自加1模式/ Z4 r2 p/ @/ P( U9 v2 u3 d8 s
- TM1637_Ack();
5 A. |: x- N( e7 `. j. N4 @" Q - TM1637_Stop();
) }$ S" e' u7 b; I9 D |5 x
3 s6 r# {0 L; d0 b, I T! Q' i- TM1637_Start();& i7 r1 ]" f& l `8 L! M' j! d
- TM1637_WriteByte( 0xC0 | add ); //地址命令设置 显示地址 C0H---C5H
7 S W3 L, }4 R2 f* d- j - TM1637_Ack();
5 `' z6 ^0 v/ N3 M9 `4 C - 1 }$ I1 N% W0 }" Y: y
- TM1637_WriteByte( value ); //发送数据 value存储要显示的内容* Z, M1 n0 Y1 V/ ^5 t! E& k
- TM1637_Ack();3 }6 Z; [! s! n$ i; A" w+ T. z
- TM1637_Stop();( V- D+ u5 J2 J' _* ]- D% c% {
- 9 k" J# ^$ r3 V: X+ q% C" e' o
- TM1637_Start();! y: y( `+ e, }* |
- TM1637_WriteByte( 0x88 | 0x07 ); //开显示,最大亮度-----调节脉冲宽度控制0---7 脉冲宽度14/16, P* c" {" h5 w0 o1 Y* j
- TM1637_Ack();
6 T. u# C7 V* v X) Y' z - TM1637_Stop();
7 j! L, q0 l! W; e - }
复制代码
2 i8 |& A7 b& p! C2 R7 d 地址固定模式每次只写一个固定的地址,地址值是由低三位值控制的,所以这里将高5位的值固定不变,只需要将低3位的值和高5位的值进行位或运算就行。这样在传递地址参数的时候,只需要发送0--5就可以了。同样亮度的设置也是用这个方法,亮度值由低3位值决定,将低3位值和高5位值进行位或运算。设置亮度的时候直接发送0--7就行。这里亮度没有使用参数,直接使用的是定值。想要改变亮度,可以把亮度也设置为参数传递进来。 e- S1 l3 Y! ?- A7 c% l: J# [
9 y7 s+ B- q# A3 c 到这里就可以直接调用这两个函数,控制数码管显示了。4 q# k$ _, q& G$ c2 d2 m7 H
5 V, E6 f5 ]( N' l6 A
为了方便查看代码,下面贴出完整代码TM1637.c完整代码' b/ W0 \9 f( i0 H3 Z
2 t$ x' W, M7 [( q- #include "TM1637.h"
1 E8 C3 W; T. q- O/ t3 L! @ - #include "bsp_SysTick.h"0 L% H: o# z, D m9 n
- , S2 a, \) a2 u/ u# y& L
- unsigned char tab[] =
: `# n" y" D6 c% L - {! f8 g7 N9 b$ J7 \8 D1 J
- 0x3F,/*0*/& ?: x* f/ C& k! o! ]: O d- _
- 0x06,/*1*/' J4 t& _0 G, l' Q+ K
- 0x5B,/*2*/, U. A6 J5 }! f* m; N& ]$ }2 m& j
- 0x4F,/*3*/
, A( F. k- q" A, a$ i5 ]! v - 0x66,/*4*/
: m& _; P* X6 ` K8 r - 0x6D,/*5*/+ d) [5 v1 [0 Q3 X
- 0x7D,/*6*/1 F2 t+ {7 v6 l, S
- 0x07,/*7*/* B$ k, R: M( v A. _
- 0x7F,/*8*/6 W' o: V/ {- S. U; h
- 0x6F,/*9*/
1 w7 A' A5 [! c7 q( G+ z2 p( b - 0x77,/*10 A*/3 B% {$ w! [0 s' @7 {
- 0x7C,/*11 b*/+ B/ s/ J! l, u9 c
- 0x58,/*12 c*/3 j( o2 k6 [' ]# y
- 0x5E,/*13 d*// X1 S: P! Q- A8 P8 g4 M1 l2 m
- 0x79,/*14 E*/
2 Q5 n P- F. b/ f) b - 0x71,/*15 F*/
0 v" u8 S+ m4 l4 k) q - 0x76,/*16 H*/
) }( L/ W% c2 {* Y$ V/ ?( y( G$ ]% n - 0x38,/*17 L*/
/ e X. F3 g @0 K# F' d - 0x54,/*18 n*/
5 C0 `4 ~2 y3 m' p# A; j - 0x73,/*19 P*/
\4 ^5 t( E4 g8 n - 0x3E,/*20 U*/
% G2 u& z5 G( |0 ` - 0x00,/*21 黑屏*/- C* f6 S! y1 A: e; a
- };$ n& i; N+ W4 G# K6 @
- , Q- N( l) m4 J; N' _7 o
- // 最高位设置为1时显示 数码管上的":" 符号, y" e+ e8 j% Q0 F/ } p
- unsigned char disp_num[] = {0x3F, 0x06 | 0x80, 0x5B, 0x4F, 0x66, 0x6D}; //存放6个数码管要显示的内容2 R5 c4 U+ ]8 h3 k U' F
+ @$ h) E; Y1 v$ b- //端口初始化% a& D K7 n, }! W
- void TM1637_Init( void ). b* x4 f$ X" _; ]! I/ B; E- P+ X4 g
- {
, S' |4 e; a4 x- Z
2 X- o# J0 H; m; s: z1 I- GPIO_InitTypeDef GPIO_InitStructure;
$ n" O: z/ ~+ G" L" Q - 2 O; c0 y( f( V' M, p
- RCC_APB2PeriphClockCmd( TM1637_CLK_GPIO_CLK | TM1637_DIO_GPIO_CLK, ENABLE );
- F3 r6 g8 k! a ~: L - 2 A0 p0 G! N0 P6 i; L" H
- GPIO_InitStructure.GPIO_Pin = TM1637_CLK_GPIO_PIN | TM1637_DIO_GPIO_PIN;
+ h& x* Z( j' t0 a - GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;2 c+ w' N7 X) h, ^
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;) z+ E7 c5 L$ W/ c; g) N
- GPIO_Init( TM1637_CLK_GPIO_PORT, &GPIO_InitStructure );
. S2 h* \2 n H1 N* j - }
0 l7 p2 Z0 ^- |; |" H7 H - . C n+ q8 b' Z
- //起始位 CLK为高电平时,DIO由高变低" O: a7 r; c( I; c# w! g
- void TM1637_Start( void ); [: c# @3 Y& }
- {" Z& T P5 g! J8 ` V) H1 ~
- TM1637_DIO_OUT();
( J0 }. `" e" A! j - TM1637_CLK = 1;
- u# H: ^' a/ k8 U - TM1637_DIO = 1;2 ^3 @% r; g+ ^) Z( [4 R/ k" T) O
- delay_us( 2 );
+ n9 z8 u: F0 r% ]) j* o" S - TM1637_DIO = 0;
! V& h |( E8 P9 s - }, e% }$ m P5 o% M
- 8 p U6 j" K1 P. I
- //等待应答 传输数据正确时,在第八个时钟下降沿,芯片内部会产生一个ACK信号,将DIO管脚拉低,在第九个时钟结束之后释放DIO总线。1 {8 B2 u; s: ` a3 V- N! \
- void TM1637_Ack( void ): M! X, d6 @3 l
- {
9 m" z" y8 W' t9 W - TM1637_DIO_IN();/ \( b" a2 G1 [4 @/ S
- TM1637_CLK = 0;& A5 o" i0 U+ L: c, [& }+ X
- delay_us( 5 ); //在第八个时钟下降沿之后延时 5us,开始判断 ACK 信号
& J, I% E' ~& `: p - while( TM1637_READ_DIO ); //等待应答位 这一行代码也可以不要 不影响实际使用效果 在使用软件仿真的时候需要屏蔽这句代码,否则程序就会卡在这里。
6 \. c3 @" p1 i) g! ?+ ` - TM1637_CLK = 1;
" m% F& ~0 q+ c+ c" W" P - delay_us( 2 );) W) L( h0 e4 |3 _- g
- TM1637_CLK = 0;
3 N; o8 M# P/ t: l( Q - }
2 r4 Y& R" ~) u e& v
) U' v, r) w) U9 n( j w; Y0 W- b- o- //停止位 CLK为高电平时,DIO由低变高7 v8 T7 ~1 C- c9 U% f
- void TM1637_Stop( void )1 Q" c: g8 C. k, |8 m
- {
+ r" M3 K$ v! p# {7 B - TM1637_DIO_OUT();" f. D3 [- p3 F
- TM1637_CLK = 0;
O4 S7 ~7 a2 l6 N+ e5 x* ~4 Y2 c - delay_us( 2 );
! h7 P m2 U: c% o - TM1637_DIO = 0;
+ Z! J& Y& k2 [ - delay_us( 2 );) d1 i$ q/ N4 z2 j2 t! e
- TM1637_CLK = 1;. ]. `+ o1 |: L- v; Y
- delay_us( 2 );2 t& {+ W9 f* f \+ k
- TM1637_DIO = 1;3 y/ v w' Y ~7 E/ b
- }6 ~/ g J/ G- l8 d% U7 F5 _
- //输入数据在CLK的低电平变化,在CLK的高电平被传输。' Q' T( u5 J( j+ g: `) k8 K
- //每传输一个字节,芯片内部在第八个时钟下降沿产生一个ACK# }. z6 `9 a. m7 i4 H' o
- // 写一个字节
% X0 U+ W& v2 w, c1 z9 s* p - void TM1637_WriteByte( unsigned char oneByte )
# x/ m* v; _7 Z. Q/ { - {
0 r- t% v7 H, i$ f& F8 N5 N$ Z - unsigned char i;
3 `2 f+ i7 s( B9 d9 \( w$ V - TM1637_DIO_OUT();$ j: H0 j, W6 O) M4 ^; ?
- for( i = 0; i < 8; i++ )2 y8 c% X2 K% F3 ~
- {3 V/ ?/ m; g/ @! a
- TM1637_CLK = 0;
9 F" P# X) ]& b Z0 m5 @ - if( oneByte & 0x01 ) //低位在前; E! f: S5 d$ `$ g( a) @
- {
% @$ O. @" _. p! A" s \7 @* R - TM1637_DIO = 1;
" y* i4 y4 W, ~5 U8 h - }
4 O$ M8 ?- @4 _: N! M - else
- D2 d9 `3 O* H( q - {
/ |/ O5 A2 ~2 Y3 t7 w# \ - TM1637_DIO = 0;
7 F9 T. Q# o& m9 ?+ S/ H- D - }
7 q1 Z" W! j7 o - delay_us( 3 );: v8 k1 e" W& f; B5 V- \
- oneByte = oneByte >> 1;
5 K3 ?) i4 @5 z' i3 Q - TM1637_CLK = 1;- z" D( d; O- g/ f
- delay_us( 3 );- J9 N8 P y/ l. O i3 M
- }8 G6 u# x' i! A. B- z% \
- }
- q) }4 }: V: T3 J+ G3 [
1 N' _/ O4 _( Y% p- //写显示寄存器 地址自增9 x) N7 A3 F, P, ~
- void TM1637_Display_INC( void )6 ^$ J( S) B6 A3 x' [
- {
8 R: P! ?, x0 L/ q' o/ f - unsigned char i;% t$ I- c' w& Z, y
- TM1637_Start();
* B [7 Y0 [& I0 j. N2 z - TM1637_WriteByte( 0x40 ); //写数据到显示寄存器 40H 地址自动加1 模式,44H 固定地址模式,本程序采用自加1模式* U: Z8 p1 }+ E' N8 g+ E9 p, j; y, j
- TM1637_Ack();
4 Z Y; C1 b @( F+ d - TM1637_Stop();$ T. f% h& z' D1 b) T% s
- TM1637_Start();) k2 \. c% M0 Z6 ^* ]) o
- TM1637_WriteByte( 0xC0 ); //地址命令设置 显示地址 00H: I. f+ z2 D8 O% p: w% o
- TM1637_Ack();2 Q# f2 D2 Z. x% j4 [ `. [" N
$ z4 I- r: J; J3 B1 W3 n- for( i = 0; i < 6; i++ ) //地址自加,不必每次都写地址
% |4 b0 u; [ A% M$ @. U, W% Y - {
2 R' g% R/ c, `: ~/ x' b; ~ - TM1637_WriteByte( disp_num ); //发送数据 disp_num[]中存储6个数码管要显示的内容
6 @: a" X- e5 B7 D/ w; T - TM1637_Ack();
# @2 W0 r% v5 p n - }
: U1 o$ Q, Y3 H9 g3 q; J0 ? - TM1637_Stop();* w4 P2 S3 _8 z" n
- #if 0
* ]$ h0 o7 q' f$ N3 ?, r; i - TM1637_Start();
* m9 j7 q' }2 u - TM1637_WriteByte( 0x88 | 0x07 ); //开显示,最大亮度-----调节脉冲宽度控制0---7 脉冲宽度14/16" ^, L& A2 D$ _* \( p* U
- TM1637_Ack();- ?( ?- ?# W3 o0 F4 \5 `) U) h
- TM1637_Stop();
+ U/ s3 Y9 {" \( d - #endif
' F0 F. ]5 _* z5 T - }
( w6 B" M3 v- K0 [ D+ y, Y
4 Q" I- p! B0 p& g; {; b6 R) P- //写显示寄存器 地址不自增
; C f" j, b9 g& ~; `( T0 X - // add 数码管的地址 0--57 Q3 N1 k7 l1 q6 ?- ~
- // value 要显示的内容
, @8 K8 G" L4 o4 Y$ y# y/ f- s! A8 M; n - void TM1637_Display_NoINC( unsigned char add, unsigned char value )- b1 `7 v9 T i. t0 V' }+ z
- {
0 c6 N' }- y1 p6 O7 Q - unsigned char i;
J# a. E/ Q/ ?+ @ - TM1637_Start();, y* d- d2 n! z% h( {% [4 I* X
- TM1637_WriteByte( 0x44 ); //写数据到显示寄存器 40H 地址自动加1 模式,44H 固定地址模式,本程序采用自加1模式6 E; ?: N) u2 j3 N! C4 r
- TM1637_Ack();1 t2 q3 E/ I) z4 V* w
- TM1637_Stop();
: X! K1 @9 c) r q
' v! w$ P& E; {( U6 y( M- TM1637_Start();! s( f' m0 {1 K4 d4 R
- TM1637_WriteByte( 0xC0 | add ); //地址命令设置 显示地址 C0H---C5H
8 Y8 \7 f7 g! x& L; E0 T" h - TM1637_Ack();
0 v% |8 M: ?, B k. ` - \4 K; o3 M8 W- a6 @
- TM1637_WriteByte( value ); //发送数据 value存储要显示的内容
3 L3 D- p- |1 c, J+ @ - TM1637_Ack();; a' }, d, Q: T' o
- TM1637_Stop();& l1 ]# w+ ?& Y4 n6 b
- #if 0
# Q5 D- D8 I* s' Z' {9 G% \: S - TM1637_Start();
3 e4 f7 g4 M+ {; [ - TM1637_WriteByte( 0x88 | 0x07 ); //开显示,最大亮度-----调节脉冲宽度控制0---7 脉冲宽度14/16
4 `# ~( L$ X$ e - TM1637_Ack();# y9 J( a. G# \- W1 c& [4 @" [
- TM1637_Stop();
4 J- s( c+ `6 _% O - #endif4 l1 n* d' F4 x7 j" x6 }
- }
# O$ m$ D; x0 x: _* P6 `# x - ) {7 K7 R/ J. e( N% J" t [. Q1 E
- // level : 设置亮度等级 0---7
5 n; n' D$ f. X8 q" Q - void TM1637_SetBrightness( unsigned char level )7 K" e" D5 y5 F4 ^# x
- {$ U' {% A u; d& |" X: P7 N$ D
- TM1637_Start();1 s0 l* Q1 [ ~! z! I8 R& g
- TM1637_WriteByte( 0x88 | level ); //开显示,最大亮度-----调节脉冲宽度控制0---7 脉冲宽度14/165 y8 A* T* ~' n$ ~, O% n+ \" c0 ?
- TM1637_Ack();' S* [4 ]9 u6 r9 [2 k
- TM1637_Stop();4 I+ A6 n' U/ W$ V3 w& H+ X
- }) s3 Z0 s" z) B h
- ; x3 r4 O: ]( ~9 U
: Q) z$ j0 s) a! P- 7 j. \: F1 m7 {+ `7 r, o
) y7 Y j1 h; s- //读按键 读按键时,时钟频率应小于 250K,先读低位,后读高位。
' }6 {0 N- X( x9 Y - unsigned char TM1637_ScanKey( void )
3 c/ K5 i/ O/ } - {
# h* Q: O4 q& u f F- ^* X4 V - unsigned char reKey, i;3 C7 S4 K2 ] P# U) N2 l! w5 A
- TM1637_Start();
0 N8 a$ [0 w2 Z$ S9 Q - TM1637_WriteByte( 0x42 ); //读键扫数据+ M! v6 A% }/ g# z* |
- TM1637_Ack();3 {, S) X9 d c) _
- TM1637_DIO = 1; //在读按键之前拉高数据线3 C& u0 [5 F$ W8 D/ v
- TM1637_DIO_IN();5 a& W8 _# L2 V) R7 a
- for( i = 0; i < 8; i++ ) //从低位开始读
/ @6 s( j" R4 B- G7 [ - {* R2 N, n3 U% H' }& u
- TM1637_CLK = 0;" _! S+ R+ c! u+ p' k
- reKey = reKey >> 1;1 X- C- ~+ c8 [# @3 ?
- delay_us( 30 );0 f" z) R; [* Y- r+ o4 K
- TM1637_CLK = 1;; B2 E' p; V; }
- if( TM1637_READ_DIO )
( T* a1 }" s( x. K/ I - {
0 u) O) j! w+ m V/ N; }, L - reKey = reKey | 0x80;: S9 x0 ~! I4 r$ j2 }8 g9 l/ c
- }
( T0 y: [; z% T" z - else
( _- B0 M1 \) m - {
) }" ?/ ]0 e8 o9 |: i; u - reKey = reKey | 0x00;8 b/ ^. N0 ]( l" S; [
- }
& D7 J5 d) M% J) _3 n1 r, ? - delay_us( 30 );
3 y, R+ t1 S! L; q7 u - }$ L! U' Z1 w3 K3 J1 A
- TM1637_Ack();
9 _& u" Q! C; o/ u3 d9 O M - TM1637_Stop();4 Z0 s) Q( T, A; F, G/ o
- return( reKey );1 g/ J0 h1 ^1 E: y1 C$ x
- }
q* R& D; @# e" y! q! C - ! c! ^$ a1 D% }
7 M# L' }( w4 `7 @- //按键处理函数,按键数据低位在前高位在后 L9 z: v$ d1 Q5 K" t
- unsigned char TM1637_KeyProcess( void )
, x2 x' y& \8 h7 T4 x/ Z6 u2 u - {2 h; A1 ?1 G! y) ~8 ]7 u I
- unsigned char temp;
3 k. s9 L7 `. y4 } s& E$ ~6 m8 A+ H - unsigned char keyNum = 0;
j: k% j% u& m( G - temp = TM1637_ScanKey(); //读取按键返回值' O6 ~& L( e: W# L% g" m! s! t
- if( temp != 0xff )
9 e. z& d+ P D( `' ]5 n - {! e8 [: n& I. M! n- N" P2 u9 ^
- switch( temp )
7 q7 W: D7 ~3 \1 b6 M - {
7 i2 x7 U8 V- ]; G+ P4 C |) t( b - case 0xf7 : //K1与SG1对应按键按下" }' B' E0 L) q. ^% n9 Z& f9 E
- keyNum = 1;9 }- }" u7 E- H o
- break;
, |+ |" v. K/ r P - case 0xf6 : //K1与SG2对应按键按下8 U! w8 p$ I2 a- {! O3 O" l* p
- keyNum = 2;
' N+ I* v! w( M, ^: J - break;
. J& {$ v( h" q( J. Y - case 0xf5 : //K1与SG3对应按键按下
& |$ y" J' ]8 T$ K. z2 P3 } - keyNum = 3;
* l! s7 N- v7 g! f( p* b. B& c( x - break;
0 u! \( a7 H8 v( ^7 |+ b) T - case 0xf4 : //K1与SG4对应按键按下5 `1 A& I/ I. a* A" U9 b
- keyNum = 4;9 |, e$ M' g/ ^0 j. E# B
- break;
% B: g6 I) M; z( E3 ~3 J - case 0xf3 : //K1与SG5对应按键按下
, H* K5 S( K# { - keyNum = 5;
. f5 q: C' G; g+ z - break;& J" Y# O; Z+ W( S8 q$ X; j
- case 0xf2 : //K1与SG6对应按键按下
, ?* W$ {1 T3 z- y& i - keyNum = 6;
( Z0 e, Z* i( _& [ - break;+ f2 u9 S/ _2 _$ `" O- f
- case 0xf1 : //K1与SG7对应按键按下
+ K$ K) _ s9 ]* {8 X - keyNum = 7;4 ^0 m$ Y5 o( O5 m" m0 r% F$ M
- break;
# L" v8 I- j% m( ^, G - case 0xf0 : //K1与SG8对应按键按下7 Y( C6 m1 l; z6 L7 b# M0 N- D; z) F
- keyNum = 8;
% x7 }- X1 \9 V - break;
6 M1 w$ G, c$ o3 z+ G$ ~ - ; e& ]4 N/ ~2 y9 r/ l8 W
- case 0xef : //K2与SG1对应按键按下
! g! b( S: |" X7 l+ o - keyNum = 9;4 b# @, h4 Y5 L, m2 t
- break;
0 D2 n) x8 j4 `. Z7 p* q: [ - case 0xee : //K2与SG2对应按键按下0 X1 w. S$ d; | x I
- keyNum = 10;
- P/ p9 h+ K6 K4 Y& j/ O" W" l" {8 _ - break;4 `/ `- T- }' J) b$ h
- case 0xed : //K2与SG3对应按键按下
" V8 N7 m% m( E Z3 s' m - keyNum = 11;
4 R/ g) Z" q3 N - break;' v! e) B0 S3 b/ b
- case 0xec : //K2与SG4对应按键按下
" b, R2 m3 d5 ^0 |2 ] - keyNum = 12;9 P0 e7 r0 ]3 v* I
- break;
Q) d# N0 g/ n Z% f7 I+ w- u - case 0xeb : //K2与SG5对应按键按下
- |: {6 k0 t" E- O. J: N- ]( w - keyNum = 13;
9 Y4 I7 V% I5 S% n - break;
- D( w( z! T) J - case 0xea : //K2与SG6对应按键按下, A/ W; {' h" a: R# U" V9 g
- keyNum = 14;
; B2 P' `6 [1 \ - break;4 ?1 m7 C- k7 h ?( N& a
- case 0xe9 : //K2与SG7对应按键按下
3 P) {; A$ B1 S# \0 @+ p5 M/ R - keyNum = 15;2 X2 ^' v s- n- E
- break;8 e2 i% X8 e! |2 R
- case 0xe8 : //K2与SG8对应按键按下
; ]! B6 c, Q" z1 f+ O1 @* d - keyNum = 16;0 A, R' B" X2 C4 m2 _
- break;
$ o5 \: z' {5 Y6 P* G$ _ - default :
+ K2 E- M) k" C: z1 g - keyNum = 0;1 f% Q1 J6 u* D! H% H7 d$ m7 s
- break;
; l- `* q( C. p6 t, A/ J - }9 A* _% ]# L& L# I2 M2 e
- }8 ^: P6 ^3 B! E
- return keyNum;
4 B! Q! c; u! t6 t0 W - }
复制代码
0 c1 _1 b' y+ w9 e% J& v下面是主函数代码
# N9 T, K! O! t$ g2 y5 k5 f+ @- }! S- e9 S1 ]* V
- #include "stm32f10x.h"
; O" e/ b1 `- v J - #include "bsp_led.h"
* G j5 f7 @# ~ - #include "bsp_SysTick.h"
; f( z/ T: e& x3 b( E3 ? i/ y - #include "TM1637.h"
, Z6 F8 e+ T# d1 |9 i; l& h2 z - - \3 [( v' `6 _6 K! i8 I% Q
- int main( void )
. M% B' J+ ?: E O H6 _/ a - {) A6 `( f( U* {- w7 [0 `1 Y: i1 {9 R
- u8 flag_s = 0;* M7 f5 p9 Z, x
- NVIC_PriorityGroupConfig( NVIC_PriorityGroup_2 );9 q/ V2 r$ z( F% q8 X1 E0 ~4 O+ j# h
- LED_GPIO_Config();' f" \+ V8 F1 j y
- TM1637_Init();
# I7 N8 O8 v' c' S& M2 j0 C& p, ~ - 9 m5 n' n- m! S$ m, y$ z7 |% _+ T
- // delay_config(); //使用方法二延时的时候调用3 a7 L0 x" q6 U$ `1 }
- ) A& c+ ~- g. ~ M$ ~% m4 H
- while ( 1 ). p2 J/ I1 T+ X0 b; w' l
- {
/ [) Z1 G; K8 @5 x+ M" n8 D" ?/ } - #if 1
6 n# ]& o& @% D6 `9 u - LED1( ON );
- |) f' z- c( x8 U - delay_ms( 1000 );
/ w' Q$ K6 N# \( ]) `. S& @ - LED1( OFF );: u( v4 a" {0 p, K" a
# O" }1 w3 g2 ]. T; o& R- d, B, n- flag_s = ~flag_s;
1 R# T* v# R0 [$ K. C. R. _$ M3 I - disp_num[0] = tab[0];
; h4 Y- G7 @) Y3 E" ^- T - if( flag_s ); o7 {1 {; g9 u
- disp_num[1] = tab[1] | 0x80; //最高位设置为1 显示":"
1 \5 S5 P( A, f, a/ e - else# g3 O) @0 }0 \
- disp_num[1] = tab[1];3 P' t; o5 z* T; D: |
- disp_num[2] = tab[2];
$ [0 _: s; b. V8 y; ]! q2 g, p9 Y - disp_num[3] = tab[3];
5 [1 C+ g2 o, D - TM1637_Display_INC();
$ ?) T! G! S8 h( G/ h7 Q - TM1637_SetBrightness( 3 ); //设置亮度等级 0---7
9 Q' h+ t/ P8 T - - ~4 _8 G7 ~- i. }: c
- #else //延时精确测试代码2 \5 p5 @0 x7 O6 \
. K2 S: `- C- @, y- LED1_ON;7 Y5 a; a) J, f. i/ m$ ^
- delay_us( 1 );
- M. H) l/ N, I+ j - LED1_OFF;9 J6 s- R f* v5 D4 [3 g e
- delay_us( 1 );, i5 {. o b. V, D
9 t' | |* s% z0 ]+ M' e5 s- #endif4 |! q# P! |/ l! L! c/ {
- }; {6 N/ P0 c" j
2 K$ h0 r* V; V- }
9 F, D, I) @1 g( t! I2 J
复制代码
4 m c. _5 f' e- ]. k) }+ x | 主函数中设置四个数码管依次显示0、1、2、3,冒号是在第二个数码管的dp引脚接着,dp为最高位,所以给最高位写1就可以点亮时钟的冒号。通过一个标志位控制冒号1秒钟闪烁一次。
& ?- r+ ?% y# Y9 Q f+ }" o1 L% c* ]8 {
|