一、课堂目标
- 了解数码管的显示原理
- 掌握tm1637的数据显示
二、原理分析
2.1 TM1637模块介绍
TM1637四位数码管模块是一种用于数字显示的模块。它可以通过接口与微控制器进行通信,并将数字信息显示在四个8段数码管上。 该模块具有以下功能:
- 显示数字0-9:模块能够将微控制器发送的数字信息显示在数码管上,可以显示0-9的任何数字。
- 显示符号:模块支持显示符号,如负号、小数点等。
- 亮度调整:可以通过接口控制模块的亮度,调整数码管的显示亮度。
- 多位数显示:模块支持同时显示多位数,最多可以同时显示四位数。
- 节约IO资源:使用TM1637芯片,模块只需要两个IO口即可完成与微控制器的通信,相比其他数码管模块节省了IO资源。
低功耗 :模块具有低功耗特性,可以有效节省电力。 通过对TM1637四位数码管模块的使用,可以方便地实现数字显示功能,适用于各种需要数字显示的应用场景,如计数器、计时器、电子钟等
2.2 7段/8段数码管
我们先来了解什么是7段数码管?上面图片看到正面是4个'8',它是利用发光二极管的原理,组成一个'8'的形状,这个'8'正好是七段组成,所以一般称为七段数码管。
而市面上有数字后面带有小数点的,称为8段数码管,由8个发光二极管组成。还有其他特殊的数码管,原理都相同。
这7段数码管分别命令为:A、B、C、D、E、F、G。如果显示数字0,那么亮起A、B、C、D、E、F这6段;如果显示数字1,那么亮起B、C这两段。
数字对应的段数据
0
-> 1111110
1
-> 0110000
2
-> 1101101
3
-> 1111001
2.3 引脚说明
TM1637焊接了4个引脚:
- GND:接地引脚
- VCC:电源引脚,3.3V和5V都可以
- DIO:数据输入输出引脚
- CLK:时钟信号引脚
仔细看背面的电路线,发现内部有12个针脚!
前面我们已经知道:一个LED灯有两个引脚,正极针脚和负极针脚,一个7段数码管就需要14个针脚,那TM1637模块有4个7段的数码管,为什么只有12个针脚了?
这里我们用到两种技术:共阳/共阴 、动态扫描显示。
2.4 共阳/共阴
LED灯只要有电压差就会被点亮,我们可以把所有LED灯的正极都联通在一起,只控制各段发光二极管的负极通电即可,这种叫做共阳 。反之亦然,叫做共阴。这样一个7段的数码管就只需要引出8个引脚就行了,1个阳(阴)级接到树莓派vcc(gnd)上,另外7个分别连到gpio口上,通过控制io口高低电平即可显示所需数字。比如说数字1,我们只需要点亮B段和C段就行了,其他全灭。那么连到共阳端引脚的io口输出高电平,连到引脚b、c的io口输出低电平,连到引脚a、d、e、f、g、dp的io口均输出高电平即可。
照上面的算法,我们需要一个共阳引脚,7个控制引脚,至少需要7*4+1=29个,数量还是对不上!再看下面的"动态扫描显示"技术。
2.5 动态扫描显示
先看看什么叫做静态显示?
静态显示,就是前面说的每一个数字需要占用8个io口,每多一个数字就需要额外的8个io口,如果数字位数不多,io口够用的话,这样做完全没问题。实际应用中往往需要显示多个数字,io口基本上是不够用的。这就需要动态扫描显示了。
再了解什么叫做动态显示?
动态驱动是将所有数码管的7个显示笔划"a,b,c,d,e,f,g"的同名端连在一起引出7个引脚,每个数字再单独引出共阳(阴)端,这样总引脚数就只要7 + 数字个数即可。当树莓派输出7个段信号时,所有数码管都会接收到相同的信号,但究竟是哪个数码管会显示出字形,取决于这个数码管对应的共阳(阴)极(后统称位选端)有无导通。所以我们只要将需要显示的数码管的位选端选通,该位就显示出字形,没有选通的数码管就不会亮。通过分时轮流控制各个数码管的的位选端,就使各个数码管轮流受控显示,这就是动态驱动。在轮流显示过程中,每位数码管的点亮时间为1~2ms,由于人的视觉暂留现象及发光二极管的余辉效应,尽管实际上各位数码管并非同时点亮,但只要扫描的速度足够快,给人的印象就是一组稳定的显示数据,不会有闪烁感,动态显示的效果和静态显示是一样的,能够节省大量的I/O端口,而且功耗更低。
所以接线图就是下面这个样子:
- DIG1~DIG4:代表4个数码管
- 一共有12个针脚。每个数码管由7段组成,每一段都是一个发光二极管。
- A-G 这7个针脚对应了数码管的7段。
- DP针脚对应的是中间的冒号。
A、B、C、D、E、F、G、DP就是8个针脚,再加上4个数字单独的针脚,就是12个了。
三、官方文档
微处理器怎么用两个管脚和12个针脚通信的?参考官方提供的文档
硬件接线图
显示寄存器
接口说明
微处理器的数据通过两线总线接口和 TM1637 通信,在输入数据时当 CLK 是高电平时,DIO 上的信号必须保持不变;只有 CLK 上的时钟信号为低电平时,DIO 上的信号才能改变。数据输入的开始条件是 CLK 为高电平时,DIO 由高变低;结束条件是 CLK 为高时,DIO 由低电平变为高电平。
TM1637 的数据传输带有应答信号 ACK,当传输数据正确时,会在第八个时钟的下降沿,芯片内部会产生一个应答信号 ACK 将 DIO 管脚拉低,在第九个时钟结束之后释放 DIO 口线。
数据指令
指令用来设置显示模式和LED 驱动器的状态。
在CLK下降沿后由DIO输入的第一个字节作为一条指令。经过译码,取最高B7、B6两位比特位以区别不同的指令。
流程图
总结
上面有点复杂,下面是提炼的信息:
- 通信是8位二进制数据
- 显示协议为:1.发送写显存数据命令 --> 2.发送设置地址命令 --> 3.传输段数据 --> 4.控制显示命令
- 传输的开始和结束标记
-
- start :CLK 为1, DIO从1变为0
- stop :CLK为1, DIO从0变为1
- CLK作为时钟总线,通过高低电平控制时钟频率
- 传输数据时,DIO数据在CLK高电平变化,CLK低电平时被传输
- DIO上传输数据正常的话,第8个时钟下降沿会收到ACK, DIO会被拉为低电平
四、驱动程序
根据上面的技术文档,具体的驱动脚本如下,80行的代码,也没那么难:
from RPi import GPIO
from time import time, sleep
class TM1637(object):
I2C_COMM1 = 0x40 # 写显存数据命令: 0b01000000
I2C_COMM2 = 0xC0 # 设置地址命令:0b11000000
I2C_COMM3 = 0x88 # 控制显示命令:0b10001000
def __init__(self, clk, dio, brightness=1.0):
self.clk_pin = clk # 时钟信号引脚
self.dio_pin = dio # 数据输入输出引脚
self.brightness = brightness # 明亮度
# 引脚初始化
GPIO.setup(self.clk_pin, GPIO.OUT)
GPIO.setup(self.dio_pin, GPIO.OUT)
def set_segments(self, segments, pos=0):
self.start()
self.write_byte(self.I2C_COMM1) # 写入命令1:写显存数据
self.br()
self.write_byte(self.I2C_COMM2 | pos) # 写入命令2:设置地址
for seg in segments:
self.write_byte(seg)
self.br()
self.write_byte(self.I2C_COMM3 + int(self.brightness)) # 写入命令3:显示控制,明暗度
self.stop()
def start(self):
""" 开始条件:待确认 """
GPIO.output(self.clk_pin, GPIO.HIGH)
GPIO.output(self.dio_pin, GPIO.HIGH)
GPIO.output(self.dio_pin, GPIO.LOW)
GPIO.output(self.clk_pin, GPIO.LOW)
def stop(self):
""" 结束条件:clk为高电位,dio由低电位变为高电位 """
GPIO.output(self.clk_pin, GPIO.LOW)
GPIO.output(self.dio_pin, GPIO.LOW)
GPIO.output(self.clk_pin, GPIO.HIGH)
GPIO.output(self.dio_pin, GPIO.HIGH)
def br(self):
""" 多条命令封装实现换行效果 """
self.stop()
self.start()
def write_byte(self, b):
# 写入二进制数据:8个bit
for i in range(8):
GPIO.output(self.clk_pin, GPIO.LOW)
if b & 0x01:
GPIO.output(self.dio_pin, GPIO.HIGH)
else:
GPIO.output(self.dio_pin, GPIO.LOW)
b = b >> 1
GPIO.output(self.clk_pin, GPIO.HIGH)
# wait for ACK
GPIO.output(self.clk_pin, GPIO.LOW)
GPIO.output(self.dio_pin, GPIO.HIGH)
GPIO.output(self.clk_pin, GPIO.HIGH)
GPIO.setup(self.dio_pin, GPIO.IN)
GPIO.setup(self.dio_pin, GPIO.OUT)
def show(self, colon=False):
segments = [63,63,63,63]
if colon:
point_data = 0x80
else:
point_data = 0x00
results = []
for segment in segments:
if segment == 0x7F:
results.append(0)
else:
results.append(segment + point_data)
self.set_segments(results)
完整功能参见github源码
https://github.com/tim2anna/micro-blue/blob/master/micro_blue/gpiozero_lib/tm1637.py