一、前言
SPI是外設(shè)驅(qū)動(dòng)常用的通訊方式之一,相比于IIC,SPI數(shù)據(jù)傳輸的速率要更高,因?yàn)镾PI比IIC多了一根數(shù)據(jù)線,是全雙工的。具體的通訊原理這里就不多介紹了,我主要講解樹(shù)莓派的SPI編程部分。因?yàn)槲业臄U(kuò)展板只有OLED連的是SPI接口,所以這一講就以O(shè)LED為例。
二、使能樹(shù)莓派SPI接口
方法1:通過(guò)配置命令
sudo raspi-config // 打開(kāi)配置
sudo reboot // 重啟,如果之前沒(méi)有使能,配置完之后要重啟才能生效
方法2:在可視化界面的設(shè)置里面修改
如果之前沒(méi)有使能,配置完之后要重啟才能生效
三、編寫(xiě)程序及運(yùn)行
SPI通訊主要用的是spidev庫(kù),該庫(kù)的api可以參考下面這個(gè)鏈接。
spidev庫(kù):https://pypi.org/project/spidev
圖像文字顯示主要用的是python圖像處理PIL庫(kù),關(guān)于這個(gè)庫(kù)的詳細(xì)情況可以參考下面這篇博文,https://blog.csdn.net/zhangziju/article/details/79123275
代碼主要分成兩部分:驅(qū)動(dòng)部分和應(yīng)用部分
驅(qū)動(dòng)部分是通過(guò)spi和SSD1306芯片通信,實(shí)現(xiàn)顯示數(shù)據(jù)的傳輸。
應(yīng)用部分就是根據(jù)你的需求,在對(duì)應(yīng)的位置顯示你想要的內(nèi)容。
驅(qū)動(dòng)部分代碼如下:
SSD1306.py
import spidev
import RPi.GPIO as GPIO
import time
# Constants
SSD1306_SETCONTRAST = 0x81
SSD1306_DISPLAYALLON_RESUME = 0xA4
SSD1306_DISPLAYALLON = 0xA5
SSD1306_NORMALDISPLAY = 0xA6
SSD1306_INVERTDISPLAY = 0xA7
SSD1306_DISPLAYOFF = 0xAE
SSD1306_DISPLAYON = 0xAF
SSD1306_SETDISPLAYOFFSET = 0xD3
SSD1306_SETCOMPINS = 0xDA
SSD1306_SETVCOMDETECT = 0xDB
SSD1306_SETDISPLAYCLOCKDIV = 0xD5
SSD1306_SETPRECHARGE = 0xD9
SSD1306_SETMULTIPLEX = 0xA8
SSD1306_SETLOWCOLUMN = 0x00
SSD1306_SETHIGHCOLUMN = 0x10
SSD1306_SETSTARTLINE = 0x40
SSD1306_MEMORYMODE = 0x20
SSD1306_COLUMNADDR = 0x21
SSD1306_PAGEADDR = 0x22
SSD1306_COMSCANINC = 0xC0
SSD1306_COMSCANDEC = 0xC8
SSD1306_SEGREMAP = 0xA0
SSD1306_CHARGEPUMP = 0x8D
SSD1306_EXTERNALVCC = 0x1
SSD1306_SWITCHCAPVCC = 0x2
# Scrolling constants
SSD1306_ACTIVATE_SCROLL = 0x2F
SSD1306_DEACTIVATE_SCROLL = 0x2E
SSD1306_SET_VERTICAL_SCROLL_AREA = 0xA3
SSD1306_RIGHT_HORIZONTAL_SCROLL = 0x26
SSD1306_LEFT_HORIZONTAL_SCROLL = 0x27
SSD1306_VERTICAL_AND_RIGHT_HORIZONTAL_SCROLL = 0x29
SSD1306_VERTICAL_AND_LEFT_HORIZONTAL_SCROLL = 0x2A
class SSD1306(object):
"""class for SSD1306 128*64 0.96inch OLED displays."""
def __init__(self, rst, dc, spi):
self.width = 128
self.height = 64
self._pages = 8
self._buffer = [0]*(self.width*self._pages)
# Initialize DC RST pin
self._dc = dc
self._rst = rst
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
GPIO.setup(self._dc, GPIO.OUT)
GPIO.setup(self._rst, GPIO.OUT)
# Initialize SPI
self._spi = spi
def command(self, cmd):
"""Send command byte to display"""
GPIO.output(self._dc, GPIO.LOW)
self._spi.writebytes([cmd])
def data(self, val):
"""Send byte of data to display"""
GPIO.output(self._dc, GPIO.HIGHT)
self._spi.writebytes([val])
def begin(self, vccstate=SSD1306_SWITCHCAPVCC):
"""Initialize dispaly"""
self._vccstate = vccstate
self.reset()
self.command(SSD1306_DISPLAYOFF) # 0xAE
self.command(SSD1306_SETDISPLAYCLOCKDIV) # 0xD5
self.command(0x80) # the suggested ra tio 0x80
self.command(SSD1306_SETMULTIPLEX) # 0xA8
self.command(0x3F)
self.command(SSD1306_SETDISPLAYOFFSET) # 0xD3
self.command(0x0) # no offset
self.command(SSD1306_SETSTARTLINE | 0x0) # line #0
self.command(SSD1306_CHARGEPUMP) # 0x8D
if self._vccstate == SSD1306_EXTERNALVCC:
self.command(0x10)
else:
self.command(0x14)
self.command(SSD1306_MEMORYMODE) # 0x20
self.command(0x00) # 0x0 act like ks0108
self.command(SSD1306_SEGREMAP | 0x1)
self.command(SSD1306_COMSCANDEC)
self.command(SSD1306_SETCOMPINS) # 0xDA
self.command(0x12)
self.command(SSD1306_SETCONTRAST) # 0x81
if self._vccstate == SSD1306_EXTERNALVCC:
self.command(0x9F)
else:
self.command(0xCF)
self.command(SSD1306_SETPRECHARGE) # 0xd9
if self._vccstate == SSD1306_EXTERNALVCC:
self.command(0x22)
else:
self.command(0xF1)
self.command(SSD1306_SETVCOMDETECT) # 0xDB
self.command(0x40)
self.command(SSD1306_DISPLAYALLON_RESUME) # 0xA4
self.command(SSD1306_NORMALDISPLAY) # 0xA6
self.command(SSD1306_DISPLAYON)
def reset(self):
"""Reset the display"""
GPIO.output(self._rst, GPIO.HIGH)
time.sleep(0.001)
GPIO.output(self._rst, GPIO.LOW)
time.sleep(0.010)
GPIO.output(self._rst, GPIO.HIGH)
def display(self):
"""Write display buffer to physical display"""
self.command(SSD1306_COLUMNADDR)
self.command(0) # Cloumn start address
self.command(self.width-1) # Cloumn end address
self.command(SSD1306_PAGEADDR)
self.command(0) # Page start address
self.command(self._pages-1) # Page end address
# Write buffer data
GPIO.output(self._dc, GPIO.HIGH)
self._spi.writebytes(self._buffer)
def image(self, image):
"""Set buffer to value of Python Imaging Library image."""
if image.mode != '1':
raise ValueError('Image must be in mode 1.')
imwidth, imheight = image.size
if imwidth != self.width or imheight != self.height:
raise ValueError('Image must be same dimensions as display
({0}x{1}).' .format(self.width, self.height))
pix = image.load()
# Iterate through the memory pages
index = 0
for page in range(self._pages):
# Iterate through all x axis columns.
for x in range(self.width):
# Set the bits for the column of pixels at the current position.
bits = 0
# Don't use range here as it's a bit slow
for bit in [0, 1, 2, 3, 4, 5, 6, 7]:
bits = bits << 1
bits |= 0 if pix[(x, page*8+7-bit)] == 0 else 1
# Update buffer byte and increment to next byte.
self._buffer[index] = bits
index += 1
def clear(self):
"""Clear contents of image buffer"""
self._buffer = [0]*(self.width*self._pages)
def set_contrast(self, contrast):
"""Sets the contrast of the display.
Contrast should be a value between 0 and 255."""
if contrast < 0 or contrast > 255:
raise ValueError('Contrast must be a value from 0 to 255).')
self.command(SSD1306_SETCONTRAST)
self.command(contrast)
def dim(self, dim):
"""Adjusts contrast to dim the display if dim is True,
otherwise sets the contrast to normal brightness if dim is False."""
# Assume dim display.
contrast = 0
# Adjust contrast based on VCC if not dimming.
if not dim:
if self._vccstate == SSD1306_EXTERNALVCC:
contrast = 0x9F
else:
contrast = 0xCF
應(yīng)用部分代碼如下:
oled.py
import spidev as SPI
import SSD1306
import time
from PIL import Image,ImageDraw,ImageFont
# Raspberry Pi pin configuration:
RST = 19
DC = 16
bus = 0
device = 0
# 128x64 display with hardware SPI:
disp = SSD1306.SSD1306(RST, DC, SPI.SpiDev(bus, device))
# Initialize library.
disp.begin()
# Clear display.
disp.clear()
disp.display()
# Create blank image for drawing.
# Make sure to create image with mode '1' for 1-bit color.
width = disp.width
height = disp.height
image = Image.new('1', (width, height))
# Get drawing object to draw on image.
draw = ImageDraw.Draw(image)
# Draw a black filled box to clear the image.
draw.rectangle((0,0,width,height), outline=0, fill=0)
# Draw some shapes.
# First define some constants to allow easy resizing of shapes.
padding = 2
shape_width = 20
top = padding
bottom = height-padding
# Move left to right keeping track of the current x position for drawing shapes.
x = padding
# Draw an ellipse.
draw.ellipse((x, top , x+shape_width, bottom), outline=255, fill=0)
x += shape_width+padding
# Draw a rectangle.
draw.rectangle((x, top, x+shape_width, bottom), outline=255, fill=0)
x += shape_width+padding
# Draw a triangle.
draw.polygon([(x, bottom), (x+shape_width/2, top), (x+shape_width, bottom)], outline=255, fill=0)
x += shape_width+padding
# Draw an X.
draw.line((x, bottom, x+shape_width, top), fill=255)
draw.line((x, top, x+shape_width, bottom), fill=255)
x += shape_width+padding
# Load default font.
font = ImageFont.load_default()
# Write two lines of text.
draw.text((x, top), 'Hello', font=font, fill=255)
draw.text((x, top+20), 'World!', font=font, fill=255)
# Display image.
disp.image(image)
disp.display()
運(yùn)行結(jié)果:
運(yùn)行oled.py,OLED從左到右分別顯示的是一個(gè)橢圓形,一個(gè)矩形,一個(gè)三角形,一個(gè)字母“X”,兩個(gè)字符串Hello world!(攝像頭怎么拍都拍不好將就著看吧)
四、進(jìn)階學(xué)習(xí)
1、顯示中文
PIL默認(rèn)的字庫(kù)是沒(méi)有中文的,如果我們要顯示中文的話要用第三方的庫(kù),我這邊下載了一個(gè)帶中文的字庫(kù)wqy-zenhei.ttf(文泉驛正黑)。
示例代碼:
#-*- coding:utf-8 -*-
import spidev as SPI
import SSD1306
import time
# import sys
# reload(sys)
# sys.setdefaultencoding('utf-8')
from PIL import Image,ImageDraw,ImageFont
# Raspberry Pi pin configuration:
RST = 19
DC = 16
bus = 0
device = 0
# 128x64 display with hardware SPI:
disp = SSD1306.SSD1306(RST, DC, SPI.SpiDev(bus, device))
# Initialize library.
disp.begin()
# Clear display.
disp.clear()
disp.display()
# Create blank image for drawing.
# Make sure to create image with mode '1' for 1-bit color.
width = disp.width
height = disp.height
image = Image.new('1', (width, height))
# Get drawing object to draw on image.
draw = ImageDraw.Draw(image)
# Draw a black filled box to clear the image.
draw.rectangle((0,0,width,height), outline=0, fill=0)
# Draw some shapes.
# First define some constants to allow easy resizing of shapes.
padding = 2
shape_width 20
top = padding
bottom = height-padding
# Move left to right keeping track of the current x position for drawing shapes.
x = padding
# Draw an ellipse.
draw.ellipse((x, top , x+shape_width, bottom), outline=255, fill=0)
x += shape_width+padding
# Draw a rectangle.
draw.rectangle((x, top, x+shape_width, bottom), outline=255, fill=0)
x += shape_width+padding
# Draw a triangle.
draw.polygon([(x, bottom), (x+shape_width/2, top), (x+shape_width, bottom)], outline=255, fill=0)
x += shape_width+padding
# Draw an X.
draw.line((x, bottom, x+shape_width, top), fill=255)
draw.line((x, top, x+shape_width, bottom), fill=255)
x += shape_width+padding
# Load default font.
# font = ImageFont.load_default()
# 使用wqy-zenhei.ttf字庫(kù),字號(hào)設(shè)置為16,字庫(kù)需要自己下載并放到同一目錄下,如果不在同一目錄下需要指定路徑。字庫(kù)下載推薦:https://www.dafont.com/bitmap.php
font = ImageFont.truetype('wqy-zenhei.ttf', 16)
# Write two lines of text.
draw.text((x, top), 'Hello', font=font, fill=255)
draw.text((x, top+20), '你好', font=font, fill=255)
# txt2lcd('星期',x,top+40)
# Display image.
disp.image(image)
disp.display()
運(yùn)行結(jié)果:
注意:運(yùn)行該代碼要用python3版本,用1和2運(yùn)行都會(huì)報(bào)錯(cuò)。還有,python3有一點(diǎn)不同的是,空格和tab是不一樣的,如果你前面用的是tab,后面就要全部統(tǒng)一,否則會(huì)報(bào)錯(cuò)。
2、顯示圖片
1)首先要準(zhǔn)備一張2:1的圖片
為什么是2:1呢?因?yàn)槲矣玫?.96寸的oled屏是2:1的。
2)編輯圖片,把分辨率調(diào)節(jié)為128*64并另存為單色位圖的bmp格式
因?yàn)槲矣玫?.96寸的oled屏分辨率就是128*64的,而且每個(gè)像素點(diǎn)的顏色都是是固定的
3)編寫(xiě)代碼
示例代碼1:
# Copyright (c) 2015 WaveShare
# Author: My MX
import time
import spidev as SPI
import SSD1306
# import Image
from PIL import Image
# Raspberry Pi pin configuration:
RST = 19
DC = 16
bus = 0
device = 0
# 128x32 display with hardware I2C:
disp = SSD1306.SSD1306(rst=RST,dc=DC,spi=SPI.SpiDev(bus,device))
# Initialize library.
disp.begin()
# Clear display.
disp.clear()
disp.display()
# Load image based on OLED display height. Note that image is converted to 1 bit color.
image = Image.open('test.bmp').convert('1') # 加載
# Alternatively load a different format image, resize it, and convert to 1 bit color.
#image = Image.open('happycat.png').resize((disp.width, disp.height), Image.ANTIALIAS).convert('1')
# Display image.
disp.image(image)
disp.display()
4)運(yùn)行代碼
注意:圖片的名稱(chēng)和代碼寫(xiě)的要一致,要把做好的圖片和代碼放在同一目錄下,如果不在同一目錄,需要指明圖片的路徑。
運(yùn)行結(jié)果:
PS:實(shí)際效果要好一些,拍出來(lái)有色差
示例代碼2:
顯示一張ppm格式的圖片
import time
import spidev as SPI
import SSD1306
from PIL import Image
# Raspberry Pi pin configuration:
RST = 19
DC = 16
bus = 0
device = 0
# 128x32 display with hardware I2C:
disp = SSD1306.SSD1306(rst=RST,dc=DC,spi=SPI.SpiDev(bus,device))
# Initialize library.
disp.begin()
# Clear display.
disp.clear()
disp.display()
# Load image based on OLED display height. Note that image is converted to 1 bit color.
image = Image.open('happycat.ppm').convert('1')
# Alternatively load a different format image, resize it, and convert to 1 bit color.
#image = Image.open('happycat.png').resize((disp.width, disp.height), Image.ANTIALIAS).convert('1')
# Display image.
disp.image(image)
disp.display()
運(yùn)行結(jié)果:
五、結(jié)束語(yǔ)
總的來(lái)說(shuō),在應(yīng)用層的使用,SPI和IIC差不多,都是直接調(diào)用api接口即可,只是用的庫(kù)函數(shù)不一樣,并不需要管底層的通訊是怎樣實(shí)現(xiàn)的。
好了,這一講的內(nèi)容就這么多了,如果對(duì)你有幫助,可以給個(gè)收藏,如果想了解更多樹(shù)莓派的知識(shí)可以關(guān)注我,后續(xù)我會(huì)繼續(xù)更新更多的教程。
教程相關(guān)的軟件和源碼:https://pan.baidu.com/s/1-lVAZyH2s-VTn5qeSnEPhA ,提取碼:qwer