一 环境准备
之前做了PICO驱动ST7735,这次再进一步,用树莓派3B来驱动。还是先上图。
最开始还是根据GPT的指引来做的。SPI的细节就不多说了,之前在PICO的时候说过了。
总线学习3--SPI-CSDN博客
二 实现细节
连接方式如下:
- VCC → 3.3V
- GND → GND
- SCL (SCK) → GPIO 11 (Pin 23)
- SDA (MOSI) → GPIO 10 (Pin 19)
- RES (Reset) → GPIO 25 (Pin 22)
- DC (Data/Command) → GPIO 24 (Pin 18)
- CS (Chip Select) → GPIO 8 (Pin 24)
首先还是配置打开SPI。
就是用sudo raspi-config,或者直接改config.txt也可以。
然后是安装需要的库。
sudo apt-get update
sudo apt-get install python3-pip python3-dev python3-spidev python3-pil python3-numpy
sudo pip3 install st7735
这里遇到了两个坑。
首先是GPT说的在config.txt增加内容:
如果你想通过 Linux 帧缓冲设备(`/dev/fb1`)来控制 ST7735,可以使用设备树配置 ST7735
```bash
dtoverlay=spi0-hw-cs
dtoverlay=pitft28-resistive,rotate=90,speed=32000000,fps=60
```
这个部分加进去,立马SPI整个启动都有问题了。
[ 5.560311] stmpe-spi spi0.1: unknown chip id: 0x0
[ 5.560351] stmpe-spi: probe of spi0.1 failed with error -22
查了一下午,最后发现GPT搞成需要触屏的驱动了,pitft28-resistive一般是用在需要触屏的地方,然后dtoverlay的用法也不是很熟悉,看起来应该是设备树的匹配,这块后面有时间再研究研究。
最后直接删掉这个不加,一下就好了。
第二个坑是直接用的python的st7735库,也就是pip3 install的。按照它说的初始化
disp = ST7735.ST7735(
port=0,
cs=0,
dc=24,
backlight=18,
rst=25,
width=160,
height=128,
rotation=90,
spi_speed_hz=4000000
)
最后屏幕能驱动,但是整个都是花屏的,没法正常显示。试了很久都没解决。
最后还是被三哥拯救了。看到三哥的一个视频。
https://www.youtube.com/watch?v=SYdGNpfLxKw
三哥用的是这个库:
https://github.com/degzero/Python_ST7735
Adafruit,好像之前PICO的时候也看过,算了,不细究了。直接把库和库上的example拿下来,立刻出图。。。。就是最开始那张。
这里有两个小疑惑。
不知道3B的SPI是不是只有这一组,我看基本上都是用的11,10两个口。DC和RST也基本都是24,25两个口。
另外CS好像没配置,但是也能用。难道说默认就试直接拉低?等有时间有心情看看波形吧。。。
三 驱动代码
驱动的原理也不多说了,以前PICO的时候写过:显示学习5(基于树莓派Pico) -- 彩色LCD的驱动_树莓派pico将摄像头画面显示到spi显示屏-CSDN博客
image.py代码如下:
# Copyright (c) 2014 Adafruit Industries
# Author: Tony DiCola
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
from PIL import Image
import ST7735 as TFT
import Adafruit_GPIO as GPIO
import Adafruit_GPIO.SPI as SPI
WIDTH = 128
HEIGHT = 160
SPEED_HZ = 4000000
# Raspberry Pi configuration.
DC = 24
RST = 25
SPI_PORT = 0
SPI_DEVICE = 0
# BeagleBone Black configuration.
# DC = 'P9_15'
# RST = 'P9_12'
# SPI_PORT = 1
# SPI_DEVICE = 0
# Create TFT LCD display class.
disp = TFT.ST7735(
DC,
rst=RST,
spi=SPI.SpiDev(
SPI_PORT,
SPI_DEVICE,
max_speed_hz=SPEED_HZ))
# Initialize display.
disp.begin()
# Load an image.
print('Loading image...')
image = Image.open('cat.jpg')
# Resize the image and rotate it so matches the display.
image = image.rotate(90).resize((WIDTH, HEIGHT))
# Draw the image on the display hardware.
print('Drawing image')
disp.display(image)
驱动库ST7735.py如下:
# Copyright (c) 2014 Adafruit Industries
# Author: Tony DiCola
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
import numbers
import time
import numpy as np
from PIL import Image
from PIL import ImageDraw
import Adafruit_GPIO as GPIO
import Adafruit_GPIO.SPI as SPI
# SPI_CLOCK_HZ = 64000000 # 64 MHz
SPI_CLOCK_HZ = 4000000 # 4 MHz
# Constants for interacting with display registers.
ST7735_TFTWIDTH = 128
ST7735_TFTHEIGHT = 160
ST7735_NOP = 0x00
ST7735_SWRESET = 0x01
ST7735_RDDID = 0x04
ST7735_RDDST = 0x09
ST7735_SLPIN = 0x10
ST7735_SLPOUT = 0x11
ST7735_PTLON = 0x12
ST7735_NORON = 0x13
# ILI9341_RDMODE = 0x0A
# ILI9341_RDMADCTL = 0x0B
# ILI9341_RDPIXFMT = 0x0C
# ILI9341_RDIMGFMT = 0x0A
# ILI9341_RDSELFDIAG = 0x0F
ST7735_INVOFF = 0x20
ST7735_INVON = 0x21
# ILI9341_GAMMASET = 0x26
ST7735_DISPOFF = 0x28
ST7735_DISPON = 0x29
ST7735_CASET = 0x2A
ST7735_RASET = 0x2B
ST7735_RAMWR = 0x2C
ST7735_RAMRD = 0x2E
ST7735_PTLAR = 0x30
ST7735_MADCTL = 0x36
# ST7735_PIXFMT = 0x3A
ST7735_COLMOD = 0x3A
ST7735_FRMCTR1 = 0xB1
ST7735_FRMCTR2 = 0xB2
ST7735_FRMCTR3 = 0xB3
ST7735_INVCTR = 0xB4
# ILI9341_DFUNCTR = 0xB6
ST7735_DISSET5 = 0xB6
ST7735_PWCTR1 = 0xC0
ST7735_PWCTR2 = 0xC1
ST7735_PWCTR3 = 0xC2
ST7735_PWCTR4 = 0xC3
ST7735_PWCTR5 = 0xC4
ST7735_VMCTR1 = 0xC5
# ILI9341_VMCTR2 = 0xC7
ST7735_RDID1 = 0xDA
ST7735_RDID2 = 0xDB
ST7735_RDID3 = 0xDC
ST7735_RDID4 = 0xDD
ST7735_GMCTRP1 = 0xE0
ST7735_GMCTRN1 = 0xE1
ST7735_PWCTR6 = 0xFC
# Colours for convenience
ST7735_BLACK = 0x0000 # 0b 00000 000000 00000
ST7735_BLUE = 0x001F # 0b 00000 000000 11111
ST7735_GREEN = 0x07E0 # 0b 00000 111111 00000
ST7735_RED = 0xF800 # 0b 11111 000000 00000
ST7735_CYAN = 0x07FF # 0b 00000 111111 11111
ST7735_MAGENTA = 0xF81F # 0b 11111 000000 11111
ST7735_YELLOW = 0xFFE0 # 0b 11111 111111 00000
ST7735_WHITE = 0xFFFF # 0b 11111 111111 11111
def color565(r, g, b):
"""Convert red, green, blue components to a 16-bit 565 RGB value. Components
should be values 0 to 255.
"""
return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3)
def image_to_data(image):
"""Generator function to convert a PIL image to 16-bit 565 RGB bytes."""
# NumPy is much faster at doing this. NumPy code provided by:
# Keith (https://www.blogger.com/profile/02555547344016007163)
pb = np.array(image.convert('RGB')).astype('uint16')
color = ((pb[:,:,0] & 0xF8) << 8) | ((pb[:,:,1] & 0xFC) << 3) | (pb[:,:,2] >> 3)
return np.dstack(((color >> 8) & 0xFF, color & 0xFF)).flatten().tolist()
# Define a function to hard code that we are using a raspberry pi
def get_platform_gpio_for_pi(**keywords):
import RPi.GPIO
return GPIO.RPiGPIOAdapter(RPi.GPIO, **keywords)
class ST7735(object):
"""Representation of an ST7735 TFT LCD."""
def __init__(self, dc, spi, rst=None, gpio=None, width=ST7735_TFTWIDTH,
height=ST7735_TFTHEIGHT):
"""Create an instance of the display using SPI communication. Must
provide the GPIO pin number for the D/C pin and the SPI driver. Can
optionally provide the GPIO pin number for the reset pin as the rst
parameter.
"""
self._dc = dc
self._rst = rst
self._spi = spi
self._gpio = gpio
self.width = width
self.height = height
if self._gpio is None:
#self._gpio = GPIO.get_platform_gpio()
self._gpio = get_platform_gpio_for_pi()
# Set DC as output.
self._gpio.setup(dc, GPIO.OUT)
# Setup reset as output (if provided).
if rst is not None:
self._gpio.setup(rst, GPIO.OUT)
# Set SPI to mode 0, MSB first.
spi.set_mode(0)
spi.set_bit_order(SPI.MSBFIRST)
spi.set_clock_hz(SPI_CLOCK_HZ)
# Create an image buffer.
self.buffer = Image.new('RGB', (width, height))
def send(self, data, is_data=True, chunk_size=4096):
"""Write a byte or array of bytes to the display. Is_data parameter
controls if byte should be interpreted as display data (True) or command
data (False). Chunk_size is an optional size of bytes to write in a
single SPI transaction, with a default of 4096.
"""
# Set DC low for command, high for data.
self._gpio.output(self._dc, is_data)
# Convert scalar argument to list so either can be passed as parameter.
if isinstance(data, numbers.Number):
data = [data & 0xFF]
# Write data a chunk at a time.
for start in range(0, len(data), chunk_size):
end = min(start+chunk_size, len(data))
self._spi.write(data[start:end])
def command(self, data):
"""Write a byte or array of bytes to the display as command data."""
self.send(data, False)
def data(self, data):
"""Write a byte or array of bytes to the display as display data."""
self.send(data, True)
def reset(self):
"""Reset the display, if reset pin is connected."""
if self._rst is not None:
self._gpio.set_high(self._rst)
time.sleep(0.500)
self._gpio.set_low(self._rst)
time.sleep(0.500)
self._gpio.set_high(self._rst)
time.sleep(0.500)
def _init(self):
# Initialize the display. Broken out as a separate function so it can
# be overridden by other displays in the future.
self.command(ST7735_SWRESET) # Software reset
time.sleep(0.150) # delay 150 ms
self.command(ST7735_SLPOUT) # Out of sleep mode
time.sleep(0.500) # delay 500 ms
self.command(ST7735_FRMCTR1) # Frame rate ctrl - normal mode
self.data(0x01) # Rate = fosc/(1x2+40) * (LINE+2C+2D)
self.data(0x2C)
self.data(0x2D)
self.command(ST7735_FRMCTR2) # Frame rate ctrl - idle mode
self.data(0x01) # Rate = fosc/(1x2+40) * (LINE+2C+2D)
self.data(0x2C)
self.data(0x2D)
self.command(ST7735_FRMCTR3) # Frame rate ctrl - partial mode
self.data(0x01) # Dot inversion mode
self.data(0x2C)
self.data(0x2D)
self.data(0x01) # Line inversion mode
self.data(0x2C)
self.data(0x2D)
self.command(ST7735_INVCTR) # Display inversion ctrl
self.data(0x07) # No inversion
self.command(ST7735_PWCTR1) # Power control
self.data(0xA2)
self.data(0x02) # -4.6V
self.data(0x84) # auto mode
self.command(ST7735_PWCTR2) # Power control
self.data(0x0A) # Opamp current small
self.data(0x00) # Boost frequency
self.command(ST7735_PWCTR4) # Power control
self.data(0x8A) # BCLK/2, Opamp current small & Medium low
self.data(0x2A)
self.command(ST7735_PWCTR5) # Power control
self.data(0x8A)
self.data(0xEE)
self.command(ST7735_VMCTR1) # Power control
self.data(0x0E)
self.command(ST7735_INVOFF) # Don't invert display
self.command(ST7735_MADCTL) # Memory access control (directions)
self.data(0xC8) # row addr/col addr, bottom to top refresh
self.command(ST7735_COLMOD) # set color mode
self.data(0x05) # 16-bit color
#
self.command(ST7735_CASET) # Column addr set
self.data(0x00) # XSTART = 0
self.data(0x00)
self.data(0x00) # XEND = 127
self.data(0x7F)
self.command(ST7735_RASET) # Row addr set
self.data(0x00) # XSTART = 0
self.data(0x00)
self.data(0x00) # XEND = 159
self.data(0x9F)
#
self.command(ST7735_GMCTRP1) # Set Gamma
self.data(0x02)
self.data(0x1c)
self.data(0x07)
self.data(0x12)
self.data(0x37)
self.data(0x32)
self.data(0x29)
self.data(0x2d)
self.data(0x29)
self.data(0x25)
self.data(0x2B)
self.data(0x39)
self.data(0x00)
self.data(0x01)
self.data(0x03)
self.data(0x10)
self.command(ST7735_GMCTRN1) # Set Gamma
self.data(0x03)
self.data(0x1d)
self.data(0x07)
self.data(0x06)
self.data(0x2E)
self.data(0x2C)
self.data(0x29)
self.data(0x2D)
self.data(0x2E)
self.data(0x2E)
self.data(0x37)
self.data(0x3F)
self.data(0x00)
self.data(0x00)
self.data(0x02)
self.data(0x10)
self.command(ST7735_NORON) # Normal display on
time.sleep(0.10) # 10 ms
self.command(ST7735_DISPON) # Display on
time.sleep(0.100) # 100 ms
def begin(self):
"""Initialize the display. Should be called once before other calls that
interact with the display are called.
"""
self.reset()
self._init()
def set_window(self, x0=0, y0=0, x1=None, y1=None):
"""Set the pixel address window for proceeding drawing commands. x0 and
x1 should define the minimum and maximum x pixel bounds. y0 and y1
should define the minimum and maximum y pixel bound. If no parameters
are specified the default will be to update the entire display from 0,0
to width-1,height-1.
"""
if x1 is None:
x1 = self.width-1
if y1 is None:
y1 = self.height-1
self.command(ST7735_CASET) # Column addr set
self.data(x0 >> 8)
self.data(x0) # XSTART
self.data(x1 >> 8)
self.data(x1) # XEND
self.command(ST7735_RASET) # Row addr set
self.data(y0 >> 8)
self.data(y0) # YSTART
self.data(y1 >> 8)
self.data(y1) # YEND
self.command(ST7735_RAMWR) # write to RAM
def display(self, image=None):
"""Write the display buffer or provided image to the hardware. If no
image parameter is provided the display buffer will be written to the
hardware. If an image is provided, it should be RGB format and the
same dimensions as the display hardware.
"""
# By default write the internal buffer to the display.
if image is None:
image = self.buffer
# Set address bounds to entire display.
self.set_window()
# Convert image to array of 16bit 565 RGB data bytes.
# Unfortunate that this copy has to occur, but the SPI byte writing
# function needs to take an array of bytes and PIL doesn't natively
# store images in 16-bit 565 RGB format.
pixelbytes = list(image_to_data(image))
# Write data to hardware.
self.data(pixelbytes)
def clear(self, color=(0,0,0)):
"""Clear the image buffer to the specified RGB color (default black)."""
width, height = self.buffer.size
self.buffer.putdata([color]*(width*height))
def draw(self):
"""Return a PIL ImageDraw instance for 2D drawing on the image buffer."""
return ImageDraw.Draw(self.buffer)
四 后续
对了,再说说ST7735的理论刷新率。
- SPI 时钟速度:ST7735 的数据手册中提到 SPI 时钟可以达到 15MHz,但树莓派 3B 的 SPI 最高可以达到 125MHz(这取决于配置和线缆质量)。
- 屏幕分辨率:160x128 像素。
计算刷新率:
假设你将 SPI 速度配置为 32MHz:
每帧的数据量:
- 160 x 128 像素,假设每个像素使用 16 位(2 字节)颜色数据。
- 每帧数据量 = 160 * 128 * 2 = 40960 字节。
传输时间:
- 传输时间 = 数据量 / SPI 时钟速度 = 40960 字节 / 32MHz = 1.28 毫秒。
理论最大刷新率:
- 刷新率 = 1 / 传输时间 = 1 / 1.28 毫秒 ≈ 781 Hz。
看起来接口刷新率是足够了,就看图形侧行不行了。。。
好了,虽然被GPT坑了一下,最后还是在今天搞定了,当然自己确实还是很多不懂的。这个只是前菜,过两天会改成直接在内核支持,然后全部用C/C++实现。好了,就这样吧。。。
五 GPT参考
要在树莓派 3B 上驱动 ST7735 LCD 屏幕并显示 "Hello World",你可以使用 Linux 内核中的 SPI 驱动程序以及帧缓冲驱动程序来控制显示屏。下面是一个详细的步骤,包括硬件连接、内核配置、驱动安装以及代码实现。
### 1. 硬件连接
首先,将 ST7735 屏幕连接到树莓派 3B 的 SPI 接口。以下是标准的连接方式:
- **VCC** → 3.3V
- **GND** → GND
- **SCL (SCK)** → GPIO 11 (Pin 23)
- **SDA (MOSI)** → GPIO 10 (Pin 19)
- **RES (Reset)** → GPIO 25 (Pin 22)
- **DC (Data/Command)** → GPIO 24 (Pin 18)
- **CS (Chip Select)** → GPIO 8 (Pin 24)### 2. 启用 SPI 接口
SPI 是默认禁用的,因此需要启用它:
1. 打开 Raspberry Pi 配置工具:
```bash
sudo raspi-config
```2. 选择 **Interfacing Options** → **SPI**,然后选择 **Yes** 启用 SPI。
3. 重启 Raspberry Pi:
```bash
sudo reboot
```### 3. 安装必要的库和工具
安装 Python 库和工具来与 ST7735 屏幕交互:
```bash
sudo apt-get update
sudo apt-get install python3-pip python3-dev python3-spidev python3-pil python3-numpy
sudo pip3 install st7735
```### 4. 配置设备树
如果你想通过 Linux 帧缓冲设备(`/dev/fb1`)来控制 ST7735,可以使用设备树配置 ST7735:
1. 打开设备树文件:
```bash
sudo nano /boot/config.txt
```2. 添加以下行来启用 SPI 并配置 ST7735:
```bash
dtoverlay=spi0-hw-cs
dtoverlay=pitft28-resistive,rotate=90,speed=32000000,fps=60
```这里的 `rotate` 参数可以根据需要调整屏幕方向。
3. 保存文件并退出,然后重启 Raspberry Pi:
```bash
sudo reboot
```### 5. 使用 Python 脚本显示 "Hello World"
以下是一个简单的 Python 脚本,使用 `st7735` 库在屏幕上显示 "Hello World":
```python
import ST7735
from PIL import Image, ImageDraw, ImageFont# 创建ST7735 LCD实例
disp = ST7735.ST7735(
port=0,
cs=0,
dc=24,
backlight=18,
rst=25,
width=160,
height=128,
rotation=90,
spi_speed_hz=4000000
)# 初始化显示屏
disp.begin()# 创建一个图像对象
width = disp.width
height = disp.height
image = Image.new('RGB', (width, height))
draw = ImageDraw.Draw(image)# 填充背景颜色
draw.rectangle((0, 0, width, height), outline=0, fill=(0, 0, 0))# 加载字体
font = ImageFont.load_default()# 定义显示的文本
text = "Hello World"# 计算文本的位置
text_size = draw.textsize(text, font)
text_x = (width - text_size[0]) // 2
text_y = (height - text_size[1]) // 2# 绘制文本
draw.text((text_x, text_y), text, font=font, fill=(255, 255, 255))# 显示图像
disp.display(image)
```### 6. 运行脚本
将上述 Python 脚本保存为 `hello_world.py`,然后在命令行中运行:
```bash
python3 hello_world.py
```### 7. 验证显示
运行脚本后,ST7735 屏幕应该会显示 "Hello World" 这几个字。如果一切正常,这意味着你成功地在树莓派 3B 上驱动了 ST7735 显示屏并显示了文本。
### 总结
通过这些步骤,你可以在树莓派 3B 上成功驱动 ST7735 LCD 屏幕,并在屏幕上显示 "Hello World"。你可以进一步定制显示的内容或创建更复杂的图形界面来适应你的项目需求。