树莓派性能非常强悍,但是对于某些复杂的项目来说,会出现心有余而口不足的情况,为了解决这类问题,可以在树莓派上使用扩展板,我们介绍几款常见的扩展板,不仅可以扩展到树莓派,其他单片机或嵌入式处理器均可以扩展。这几种扩展板分别是IIC Bus Expansion Board 、TM1638、PCA9685、PCF8574。
1、IIC Bus Expansion Board
这个也叫IIC集线器,Hub,我们之前介绍过,凡是IIC总线设备均可以接,一共可扩展8个IIC设备,如OLED、PCF8591、气压传感器,光照强度传感器等,使用IIC总线通信的芯片还是很多的,这个设备不仅可以扩展,还可以级联,用起来非常方便。
2、TM1638
下图是扩展板的正面图,方方正正,规规整整,看着真舒服,比接一堆杜邦线好看多了。而且淘宝售价仅5元。它是按键数码管LED显示模块,只需要连接树莓派3根GPIO线,就可以实现基本功能的输入输出。玩过单片机的同学都知道,按键LED数码管各8个是非常消耗IO口的,至少也需要十几个端口把,通过这个芯片全部解决,我后面的实验都用这个来做输入输出了,但是要想玩好,还需要理解Python语言的多线程与异步编程,后面有机会我也会详细介绍。
(1)简介
TM1638是深圳市天微电子有限公司设计的一款带键盘扫描接口的LED(发光二极管显示器)驱动控制专用芯片,内部集成有MCU数字接口、数据锁存器、LED高压驱动、键盘扫描等电路。主要应用于冰箱、空调 、家庭影院等产品的高段位显示屏驱动。
(2)器件特性
- 采用功率CMOS 工艺
- 显示模式 10 段×8 位
- 键扫描(8×3bit)
- 8级辉度可调
- 串行接口(CLK,STB,DIO)
- 振荡方式:RC 振荡(450KHz+5%)
- 内置上电复位电路
- 采用SOP28封装
(3)TM1638引脚图
(4)TM1638的寄存器
TM1638芯片寄存器还是很多的,这个还是要看数据手册,网上的资料也有很多,我本来不想再复制过来了,但是我觉得这个资料还是经常需要参考学习,毕竟如果不了解这些,驱动程序就没有办法写。
a、显示寄存器(LED与数码管)
b、按键地址
(5)指令表
(7)数据格式
- TM1638的数据读取和发送都在CLK的上升沿进行,因为DIO在时钟的下降沿控制N管动作,此时读数不稳定。
- TM1638采取低位在前的数据格式,每次发送和读取都是1byte长度,即8位二进制数据
- 每次STB拉低之后的第一个字节作为指令,处理指令时当前其它处理被终止。
3、Python下的驱动程序
这个驱动需要根据时序写,我找了mircoPython的驱动,引入了GPIO库,对此进行了修改,整个代码太长,我把用到的放一下。
from micropython import const
import time
import RPi.GPIO as GPIO
# 指定编号规则为BOARD
GPIO.setmode(GPIO.BOARD)
# 关闭警告
GPIO.setwarnings(False)
TM1638_CMD1 = const(64) # 0x40 data command
TM1638_CMD2 = const(192) # 0xC0 address command
TM1638_CMD3 = const(128) # 0x80 display control command
TM1638_DSP_ON = const(8) # 0x08 display on
TM1638_READ = const(2) # 0x02 read key scan data
TM1638_FIXED = const(4) # 0x04 fixed address mode
# 0-9, a-z, blank, dash, star
_SEGMENTS = bytearray(b'\x3F\x06\x5B\x4F\x66\x6D\x7D\x07\x7F\x6F\x77\x7C\x39\x5E\x79\x71\x3D\x76\x06\x1E\x76\x38\x55\x54\x3F\x73\x67\x50\x6D\x78\x3E\x1C\x2A\x76\x6E\x5B\x00\x40\x63')
class TM1638(object):
"""Library for the TM1638 LED display driver."""
def __init__(self, stb, clk, dio, brightness=7):
self.stb = stb
self.clk = clk
self.dio = dio
if not 0 <= brightness <= 7:
raise ValueError("Brightness out of range")
self._brightness = brightness
self._on = TM1638_DSP_ON
GPIO.setup(self.clk, GPIO.OUT,initial=1)
GPIO.setup(self.dio, GPIO.OUT,initial=0)
GPIO.setup(self.stb, GPIO.OUT,initial=1)
self.clear()
self._write_dsp_ctrl()
def _write_data_cmd(self):
# data command: automatic address increment, normal mode
self._command(TM1638_CMD1)
def _set_address(self, addr=0):
# address command: move to address
self._byte(TM1638_CMD2 | addr)
def _write_dsp_ctrl(self):
# display command: display on, set brightness
self._command(TM1638_CMD3 | self._on | self._brightness)
def _command(self, cmd):
GPIO.output(self.stb,0)
self._byte(cmd)
GPIO.output(self.stb,1)
def _byte(self, b):
GPIO.setup(self.dio, GPIO.OUT)
for i in range(8):
GPIO.output(self.clk,0)
GPIO.output(self.dio,(b >> i) & 1)
#self.dio((b >> i) & 1)
GPIO.output(self.clk,1)
def _scan_keys(self):
"""Reads one of the four bytes representing which keys are pressed."""
pressed = 0
GPIO.setup(self.dio, GPIO.IN, pull_up_down=GPIO.PUD_UP)
for i in range(8):
GPIO.output(self.clk,0)
#if self.dio.value():
if GPIO.input(self.dio):
pressed |= 1 << i
GPIO.output(self.clk,1)
#GPIO.output(self.clk,0)
GPIO.setup(self.dio, GPIO.OUT)
return pressed
def power(self, val=None):
"""Power up, power down or check status"""
if val is None:
return self._on == TM1638_DSP_ON
self._on = TM1638_DSP_ON if val else 0
self._write_dsp_ctrl()
def brightness(self, val=None):
"""Set the display brightness 0-7."""
# brightness 0 = 1/16th pulse width
# brightness 7 = 14/16th pulse width
if val is None:
return self._brightness
if not 0 <= val <= 7:
raise ValueError("Brightness out of range")
self._brightness = val
self._write_dsp_ctrl()
def clear(self):
"""Write zeros to each address"""
self._write_data_cmd()
GPIO.output(self.stb,0)
self._set_address(0)
for i in range(16):
self._byte(0x00)
GPIO.output(self.stb,1)
def write(self, data, pos=0):
"""Write to all 16 addresses from a given position.
Order is left to right, 1st segment, 1st LED, 2nd segment, 2nd LED etc."""
if not 0 <= pos <= 15:
raise ValueError("Position out of range")
self._write_data_cmd()
GPIO.output(self.stb,0)
self._set_address(pos)
for b in data:
self._byte(b)
GPIO.output(self.stb,1)
def led(self, pos, val):
"""Set the value of a single LED"""
self.write([val], (pos << 1) + 1)
def leds(self, val):
"""Set all LEDs at once. LSB is left most LED.
Only writes to the LED positions (every 2nd starting from 1)"""
self._write_data_cmd()
pos = 1
for i in range(8):
GPIO.output(self.stb,0)
self._set_address(pos)
self._byte((val >> i) & 1)
pos += 2
GPIO.output(self.stb,1)
def segments(self, segments, pos=0):
"""Set one or more segments at a relative position.
Only writes to the segment positions (every 2nd starting from 0)"""
if not 0 <= pos <= 7:
raise ValueError("Position out of range")
self._write_data_cmd()
for seg in segments:
GPIO.output(self.stb,0)
self._set_address(pos << 1)
self._byte(seg)
pos += 1
GPIO.output(self.stb,1)
def keys(self):
"""Return a byte representing which keys are pressed. LSB is SW1"""
keys = 0
GPIO.output(self.stb,0)
self._byte(TM1638_CMD1 | TM1638_READ)
for i in range(4):
keys |= self._scan_keys() << i
GPIO.output(self.stb,1)
return keys
def encode_digit(self, digit):
"""Convert a character 0-9, a-f to a segment."""
return _SEGMENTS[digit & 0x0f]
def encode_string(self, string):
"""Convert an up to 8 character length string containing 0-9, a-z,
space, dash, star to an array of segments, matching the length of the
source string excluding dots, which are merged with previous char."""
segments = bytearray(len(string.replace('.','')))
j = 0
for i in range(len(string)):
if string[i] == '.' and j > 0:
segments[j-1] |= (1 << 7)
continue
segments[j] = self.encode_char(string[i])
j += 1
return segments
def encode_char(self, char):
"""Convert a character 0-9, a-z, space, dash or star to a segment."""
o = ord(char)
if o == 32:
return _SEGMENTS[36] # space
if o == 42:
return _SEGMENTS[38] # star/degrees
if o == 45:
return _SEGMENTS[37] # dash
if o >= 65 and o <= 90:
return _SEGMENTS[o-55] # uppercase A-Z
if o >= 97 and o <= 122:
return _SEGMENTS[o-87] # lowercase a-z
if o >= 48 and o <= 57:
return _SEGMENTS[o-48] # 0-9
raise ValueError("Character out of range: {:d} '{:s}'".format(o, chr(o)))
def show(self, string, pos=0):
"""Displays a string"""
segments = self.encode_string(string)
self.segments(segments[:8], pos)
(1)这里的数码管定义SEGMENTS = bytearray(b'\x3F\x06\x5B\x4F\x66\x6D\x7D\x07\x7F\x6F\x77\x7C\x39\x5E\x79\x71\x3D\x76\x06\x1E\x76\x38\x55\x54\x3F\x73\x67\x50\x6D\x78\x3E\x1C\x2A\x76\x6E\x5B\x00\x40\x63')
可以看出0为3F,是共阴极数码管的段码。bytearray() 是 Python 的一个内置函数,用于创建一个可变字节序列。与 bytes 类型不同,bytearray 对象的内容是可以修改的。它主要用于在需要字节级别的数据操作时,提供一种更灵活、可变的存储方式。这个和C51的字符型变量用一个字节存储契合。
这样我们可以在文件中自己定义个列表。0-9的段码放进去
dispaly_list=[b'\x3F',b'\x06',b'\x5B',b'\x4F',b'\x66',b'\x6D',b'\x7D',b'\x07',b'\x7F',b'\x6F']
我们就可以让单个数码管显示了。调用类的segments方法,这个地方和C语言不同,因为Python是面向对象,而C是面向过程,后面实验也会提到。如果让第0个数码管显示0那么则:
tm.segments(dispaly_list[0],0),如果让第7个数码管显示3那么则:tm.segments(dispaly_list[3],7)。
想显示哪个数码管就显示哪个数码管,想关哪个就关哪个。有了这个方法,然后我们就可以利用数码管一次显示8个字符、数字,当然我们主要还是显示数字。其他的方法可以自行编写。
(2)LED灯可以点亮一个,也可以点亮多个。注意看led和leds函数
tm.leds(0b01010101)同时给8个灯二进制数0-1,1为亮,0为灭
tm.led(0,1)这是对单个LED控制,第0个灯点亮
(3)tm.keys()获取键值,按键1按下得到的键值为1,第二个是2,第三个是4,第四个是8,第5个是16,2**(n-1),因此要转成1、2、3、4需要数学的取对数函数才行。
4.实验代码与现象
实验就是按键按第几个,数码管第一个显示按键的键值,对应的LED灯亮
import TM1638
import time
import math
tm=TM1638.TM1638(stb=36,clk=38,dio=40) ##标号一致 板子有标记
tm.brightness(2)
#循环读取按键值并更新数码管显示
while True:
keys = tm.keys()
if keys:
print(keys)
key_num = int(math.log(keys,2))
print(key_num)
# 读取到按键,这里简单地显示第一个按下的键值
tm.show(str(key_num))
#tm.number(int(keys))
tm.leds(2**key_num)
time.sleep(0.15)
就这几行代码完全可以实现,当然程序的问题关键仍然是time.sleep函数的休眠时间,不能给的太长,太长则按键不灵,这里将sleep设置为0.15,显示和按键都没有问题。