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