1、概述
一个普通的数码管实际上为7+1个LED灯。
上图可知,A-G加上DP点8个LED,通过不同的亮暗来显示出所需的数字。
如果同时要控制多个数码管,则需要的GPIO未免太多。
我们选择控制4个数码管,通过串行转并行的方式实现控制。
所谓串行转并行,即与串口类似,在一根线上加上时间的维度,通过时序来转换为不同的并行输出。即,我们可以通过两根线控制4个数码管32个灯。 这种串行转并行,总线协议的前兆,这里的实现方式与I2C总线非常类似。
一般串行总线的实现,是协议+地址+寄存器+数据+……这种形式。
tm1637驱动方式:
CLK、DIO两个引脚加上VCC与GND。
- CLK:时钟线,与串口不同,不是通过波特率同步,而是通过CLK这根线的脉冲同步
- DIO:数据线,与CLK配合,实现串行数据的传输。
2、接口说明
微处理器的数据通过两线总线接口和 TM1637 通信,在输入数据时当 CLK 是高电平时,DIO 上的信号必须保持不变;只有 CLK 上的时钟信号为低电平时,DIO 上的信号才能改变。数据输入的开始条件是 CLK为高电平时,DIO 由高变低;结束条件是 CLK 为高时,DIO 由低电平变为高电平。TM1637 的数据传输带有应答信号 ACK,当传输数据正确时,会在第八个时钟的下降沿,芯片内部会产生一个应答信号 ACK 将 DIO 管脚拉低,在第九个时钟结束之后释放 DIO 口线。
Command:读按键指令;
S0、S1、S2、K1、K2 组成按键信息编码,S0、S1、S2为SGn 的编码,K1、K2 为 K1 和 K2 键的编码,
读按键时,时钟频率应小于 250K,先读低位,后读高位。
协议中的start(S)与stop(P)状态。
- start:CLK高电平时,DIO由高变低,则为start状态
- stop:CLK高电平时,DIO由低变高,则为stop状态
如uart串口协议可知,有开始于结束的状态,这里的状态即为start与stop两种。
切记CLK时钟线处于高电平,DIO不能随便发生变化,否则为start/stop状态中的任意一个,此时模块会“重新开始”数据传输。
CLK低电平时,DIO可以随意改变状态。数据的传输是发生在CLK的上升沿,每次数据传输8位,LSB低位在前。
协议中的ack(A)状态。 ack:模块的应答信号,自动将DIO拉低表示获取8位数据完毕。
计算设备与外设模块之间的通信,由CLK、DIO这两根线根据协议,以时间为轴线进行数据传输。
此类协议的详细步骤详细的解释如下:
- 平时不传输协议,CLK,DIO都拉高
- DIO变低,则start状态开启
- CLK拉低,DIO切换状态,CLK拉高,注意拉高之后DIO不能随意改状态,否则触发start或者stop。
- 此时一位数据的传输,发生在CLK变低后DIO确定状态后,拉高的上升沿触发,即CLK从低变高的一瞬间,外设模块迅速锁定DIO此时的状态,并 当做一位数据。
- 以此类推,重复8次,传输8位数据,低位在前。
- 第8个数据传输完毕后的CLK下降沿,触发外设芯片内部的ACK,强制拉低DIO,表示外设芯片告诉主设备收到了8个数据。主设备通过ACK获知传 输过程有没有错误,即手动拉高DIO后再读取DIO的电平,如果切换为低电平表示外设强制拉低了。
- 第9个时钟后,外设芯片释放DIO。
- 这种8位数据的传输根据需求连续发生多次,后stop状态切换一轮数据结束。
3、TM1637控制代码实现
class TM1637:
dio = 0
clk = 0
def __init__(self, dio, clk):
self.dio = dio
self.clk = clk
GPIO.setup(clk, GPIO.OUT, initial = GPIO.HIGH)
GPIO.setup(dio, GPIO.OUT, initial = GPIO.HIGH)
def start(self):
# 开始信号,clk为高时,dio由高变低
GPIO.output(self.clk, GPIO.HIGH)
GPIO.output(self.dio, GPIO.HIGH)
GPIO.output(self.dio, GPIO.LOW)
GPIO.output(self.clk, GPIO.LOW)
def start(self):
# 开始条件,默认clk高,dio高,clk高的时候,将dio从高拉低
GPIO.output(self.clk, GPIO.HIGH)
GPIO.output(self.dio, GPIO.HIGH)
GPIO.output(self.dio, GPIO.LOW)
GPIO.output(self.clk, GPIO.LOW)
def end(self):
# 结束条件,默认clk低,dio低,clk高的时候,将dio从低拉高
GPIO.output(self.clk, GPIO.LOW)
GPIO.output(self.dio, GPIO.LOW)
GPIO.output(self.clk, GPIO.HIGH)
GPIO.output(self.dio, GPIO.HIGH)
def write(self, data):
for i in range(0, 8):
# 拉低clk
GPIO.output(self.clk, GPIO.LOW)
# 切换dio状态,低位在前 LSB
if data & 0x01:
GPIO.output(self.dio, GPIO.HIGH)
else:
GPIO.output(self.dio, GPIO.LOW)
data >>= 1
# 上升沿触发数据锁存
GPIO.output(self.clk, GPIO.HIGH)
# 下降沿触发ack,第八个下降沿
GPIO.output(self.clk, GPIO.LOW)
# 强制拉高dio,判断ack回应数据,必须在clk低电平时操作,否则可能触发stop
GPIO.output(self.dio, GPIO.HIGH)
# 第九个时钟
GPIO.output(self.clk, GPIO.HIGH)
GPIO.setup(self.dio, GPIO.IN)
if GPIO.input(self.dio) == GPIO.HIGH:
# 没有响应,出错
GPIO.setup(self.dio, GPIO.OUT, initial = GPIO.HIGH)
return None
else:
# 有响应,对了
GPIO.setup(self.dio, GPIO.OUT, initial = GPIO.HIGH)
return
4、两种控制方式
4.1 写 SRAM 数据地址自动加1模式
数据命令设置
4.2 写 SRAM 数据固定地址模式
地址命令设置
该指令用来设置显示寄存器的地址;如果地址设为0C6H 或更高,数据被忽略,直到有效地址被设定;上电时,地址默认设为00H。
显示控制
5、控制实现代码
# 地址自动加一模式,data使用列表
def send_auto_data(self, data):
# command1:设置数据
# 01000000
self.start()
self.write(0B01000000)
self.end()
# command2:设置地址
# 11000000
self.start()
self.write(0B11000000)
# data1-n
self.write(num[data[0]])
self.write(num[data[1]] | 0x80) # 中间的冒号
self.write(num[data[2]])
self.write(num[data[3]])
self.end()
# command3:控制显示
# 10001111
self.start()
self.write(0B10001111)
self.end()
# 固定地址模式,0x0为第一个数码管,以此类推
def send_addr_data(self, addr, data):
if addr < 0 or addr > 3:
return None
# 设置数据
self.start()
self.write(0B01000100)
self.end()
# 设置地址
self.start()
addr |= (0B11000000)
self.write(addr)
# 设置数据
self.write(data)
self.end()
# 显示控制
self.start()
self.write(0B10001111)
self.end()
6、调用代码
#!/usr/bin/env python
#coding:utf-8
import TM167
import RPi.GPIO as GPIO
import time
nums=[0B00111111,0B00000110,0B01011011,0B01001111,0B01100110,0B01101101,0B1111101,0B00000111,0B01111111,0B01101111]
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
obj=TM167.TM1637(12,16)
#初始化
for i in range(4):
obj.send_addr_data(i,nums[0])
time.sleep(1)
for i in range(4):
obj.send_addr_data(i,0)
time.sleep(0.5)
flag=False
#封装函数 小时 分钟
def showinfo(h,m):
global flag
ts=[h/10,h%10,m/10,m%10]
for i in range(4):
if i==1:
if flag==False:
obj.send_addr_data(i,nums[ts[i]]|0B10000000)
flag=True
else:
obj.send_addr_data(i,nums[ts[i]])
flag=False
else:
obj.send_addr_data(i,nums[ts[i]])
while(1):
t=time.localtime(time.time())
#showinfo(t.tm_hour,t.tm_min)
h=t.tm_hour
m=t.tm_min
obj.send_auto_data((h/10,h%10,m/10,m%10),flag)
if flag ==False:
flag=True
else:
flag=False
#ts=t.tm_sec
time.sleep(0.5)
obj.send_addr_data(0,nums[0])