香橙派Zero2 通过SPI协议驱动TFT屏幕

HSM
HSM
2022-10-23 / 2 评论 / 2,736 阅读 / 正在检测是否收录...

成果

screen_orange_zero2.png

材料和设备

orangepi zero2 硬件设备

  • ubuntu server 20系统

屏幕

da532366d3f44b7388495dddca04b969.jpeg

  • 1.8英寸
  • 驱动芯片:ST7735S
  • 分辨率:128x160
  • 淘宝仅售13元(好便宜!太心动了才买的 表情

[quote color="info"]这块板子采用的是RGB565的编码方式,每一个像素点占两个字节。[/quote]

杜邦线

  • 八根杜邦线,双母头

教程

1、接线。关于屏幕和zero2开发板的接线看下面的表

ST7735SOrangePi zero2物理引脚编号
BLKPC611
CSCE.124
DCPC815
RSTPC513
SDAMOSI.119
SCLSCLK.123
VCC3.3V1
GNDGND9

开发版接口图
orang-zero2.png

开发板引脚图
zero2-1.png

按照文中所说接好引脚。

2、找到屏幕的spi设备,代码在下面可以看到存在两个spi设备。看一下spi设备名,为什么要看设备名呢?因为下面的代码里,可以看到需要打开屏幕的spi设备。经过测试,spidev1.1为这块屏幕的spi设备名。

hsm@orangepizero2 ~> ls /dev/ |grep spi
spidev0.0
spidev1.1

3、编写代码,保存文件为screen_print.py,命令:sudo vim /home/hsm/screen_print.py

# coding : UTF-8
import time #用于计算spi刷新整个屏幕所用时长
import OPi.GPIO as GPIO #用于操作引脚
#树莓派与屏幕的交互协议为SPI,说明见:https://github.com/doceme/py-spidev
import spidev 
#用于创建画布,或者读取具体路径下的图片。给图片添加文字。
from PIL import Image, ImageFont, ImageDraw 
import socket
import platform

def hardReset(): #重置电平时序
    GPIO.output(PinReset, 0)
    time.sleep(.2)
    GPIO.output(PinReset, 1)
    time.sleep(.5)

def sendCommand(command, *bytes): #发送指令(DC为低电平)和数据(DC为高电平)
    GPIO.output(PinDC, 0)
    spi.writebytes([command])
    if len(bytes) > 0:
        GPIO.output(PinDC, 1)
        spi.writebytes(list(bytes))

def reset(): #屏幕初始化
    sendCommand(0x11);
    sendCommand(0x26, 0x04);  # Set Default Gamma
    sendCommand(0xB1, 0x0e, 0x10);  # Set Frame Rate
    sendCommand(0xC0, 0x08, 0x00);  # Set VRH1[4:0] & VC[2:0] for VCI1 & GVDD
    sendCommand(0xC1, 0x05);  # Set BT[2:0] for AVDD & VCL & VGH & VGL
    sendCommand(0xC5, 0x38, 0x40);  # Set VMH[6:0] & VML[6:0] for VOMH & VCOML
    sendCommand(0x3a, 0x05);  # Set Color Format
    sendCommand(0x36, 0xc8);  # RGB
    sendCommand(0x2A, 0x00, 0x00, 0x00, 0x7F);  # Set Column Address
    sendCommand(0x2B, 0x00, 0x00, 0x00, 0x9F);  # Set Page Address
    sendCommand(0xB4, 0x00);
    sendCommand(0xf2, 0x01);  # Enable Gamma bit
    sendCommand(0xE0, 0x3f, 0x22, 0x20, 0x30, 0x29, 0x0c, 0x4e, 0xb7, 0x3c, 0x19, 0x22, 0x1e, 0x02, 0x01, 0x00);
    sendCommand(0xE1, 0x00, 0x1b, 0x1f, 0x0f, 0x16, 0x13, 0x31, 0x84, 0x43, 0x06, 0x1d, 0x21, 0x3d, 0x3e, 0x3f);
    sendCommand(0x29);  # Display On
    sendCommand(0x2C);

def sendManyBytes(bytes): #发送屏幕数据
    GPIO.output(PinDC, 1)
    spi.writebytes(bytes)

def drawImg_old(img160x128): #入参为160x128像素的image对象
    picReadStartTime = time.time()
    bytes = []
    i = 0  
    for x in range(0, screenWidth):
        for y in range(screenHeight - 1, -1, -1):
            colorValue = img160x128.getpixel((x, y))
            red = colorValue[0]
            green = colorValue[1]
            blue = colorValue[2]
            red = red >> 3;  # st7735s的红色占5位
            green = green >> 2;  # st7735s的绿色占6位
            blue = blue >> 3;  # st7735s的蓝色占5位
            highBit = 0 | (blue << 3) | (green >> 3);  # 每个像素写入个字节,highBit高字节,lowBit低字节
            lowBit = 0 | (green << 5) | red;
            bytes.append(highBit)
            bytes.append(lowBit)
    picReadTimeConsuming = time.time() - picReadStartTime
    startTime = time.time()
    
    # screenWidth*screenHeight*2 每个像素写入个字节。以下for循环是为了控制每次传入的数组长度,防止这个报错,:OverflowError: Argument list size exceeds 4096 bytes.
    for j in range(2000, screenWidth * screenHeight * 2, 2000):  
        sendManyBytes(bytes[i:j])
        i = i + 2000

#    showPic()
    sendManyBytes(bytes[i:screenWidth * screenHeight * 2])
    SpiTimeConsuming = time.time() - startTime
    print("picReadTimeConsuming = %.3fs , SpiTimeConsuming = %.3fs" % (picReadTimeConsuming, SpiTimeConsuming))
def print_screen(bytes1):
    i = 0
    #  screenWidth*screenHeight*2 每个像素写入个字节。以下for循环是为了控制每次传入的数组长度,防止这个报错,:OverflowError: Argument list size exceeds 4096 bytes.
    for j in range(2000, screenWidth * screenHeight * 2, 2000):  
        sendManyBytes(bytes1[i:j])
        i = i + 2000
    sendManyBytes(bytes1[i:screenWidth * screenHeight * 2])



def extract_ip():
    st = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    try:       
        st.connect(('10.255.255.255', 1))
        IP = st.getsockname()[0]
    except Exception:
        IP = '127.0.0.1'
    finally:
        st.close()
    return IP
print(extract_ip())

if __name__ == '__main__':

    screenWidth = 160 #屏幕长度
    screenHeight = 128 #屏幕宽度
    PinDC = 15 #G5IO.BOARD引脚模式,第15号引脚
    PinReset = 13#GPIO.BOARD引脚模式,第13号引脚
    BLK = 11
        #GPIO.setwarnings(False)
    GPIO.setboard(GPIO.H616)
    GPIO.setmode(GPIO.BOARD)
    GPIO.setup(PinDC, GPIO.OUT)
    GPIO.setup(PinReset, GPIO.OUT)

    local_ip = extract_ip()
    # set blink
    GPIO.setup(BLK,GPIO.OUT)

    spi = spidev.SpiDev() #https://github.com/doceme/py-spidev
    spi.open(1, 1) 
    spi.max_speed_hz = 24000000 #通信时钟最大频率
    spi.mode = 0x00 #SPI的模式,ST7735S为模式0,可以参看我这篇内容:
    hardReset()
    reset()

    image = Image.new('RGB', (160, 128)) #可以使用代码新建画布
    draw = ImageDraw.Draw(image)
    setFont = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/yahei.ttf", 15) 
    draw.text((5, 0), "IP:"+local_ip, font=setFont, fill="#FFFFFF", direction=None)
    setFont2 = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/yahei.ttf", 20)
    import datetime
    now_time = datetime.datetime.now()    
    draw.text((5, 40),datetime.datetime.strftime(now_time,'%m月%d日 %H:%M'), font=setFont2, fill="#ffd966", direction=None)
    draw.text((70, 95), "Author:HSM", font=setFont, fill="#7fc9d8", direction=None)
    drawImg_old(image)
    GPIO.cleanup() #退出的时候将本程序运行时修改的端口都重置成之前的样子
    

4、在Linux上设置设置定时任务:在linux终端输入:vim /var/spool/cron/crontabs/root,编辑文件,加入一行配置,如下:

# 每隔一分钟刷新一次
*/1 * * * *  python3 /home/hsm/screen_print.py

5、测试

  • 看屏幕是否正确显示
  • 如果屏幕显示不正常,检查流程是否操作正确。检查接线,步骤1、2、3

原理

学习一下原理,看不明白就放弃吧。

了解ST7735S

ST7735S是一款用于262K彩色、图形型TFT-LCD的单芯片控制器/驱动器。 它由396条源极线和162条栅极线驱动电路组成。 该芯片能够直接与外部微处理器连接,并接受串行外设接口(SPI)、8bit/9bit/16bit/18bit并行接口。 显示数据可以存储在132×162×18位的片上显示数据RAM中。 它可以在没有外部操作时钟的情况下执行显示数据RAM读/写操作,以最大限度地减少功耗。 此外,由于驱动液晶所需的集成电源电路,可以用较少的元件制造显示系统。

驱动IC数据手册下载:
驱动IC数据手册.pdf

颜色模式

ST7735S支持多种可编程像素颜色格式(颜色深度),用于各种显示数据输入格式

  • 12位/像素:RGB=(444)使用384K位帧存储器和LUT
  • 16位/像素:RGB=(565)使用384K位帧存储器和LUT
  • 18位/像素:RGB=(666)使用384K位帧存储器和LUT

[quote color="info"]我这次采用RGB565的颜色格式[/quote]

RGB565

RGB565的详细文档可以查看驱动IC数据手册45页
rgb565-1.png

数据帧的格式为:
rgb565-2.png

颜色格式初始化

sendCommand(0x3a, 0x05);  # Set Color Format

spidev

spidev模块主要用于通过spidev linux内核驱动程序从用户空间与SPI设备连接。

使用

import spidev
spi = spidev.Spidev()
#方法--open
bus = 0    #supporyed values:0,1
device = 0   #supported values:0,1   default: 0
spi.open(bus,device)    #连接到指定的spi设备     /dev/spidev<bus>.<device>
#方法--readbytes/writebytes
spi.readbytes(n)   #从spi设备读取n个字节
spi.writebytes(list of values)    #将数据列表写入spi设备
spi.writebytes2(list of values)   #接受大型列表,支持numpy字节数组
spi.xfer(send_list)   #传输数据
#方法--close
spi.close()    #关闭连接

#配置
max_speed_hz           #通信时钟最大频率
mode                   #spi mode   0b00~0b11
no_cs                  #设置 SPI_NO_CS标志是否使用CS
threewire              #共享SI/SO信号

OPI.GPIO

关于官方库

RPi的一个替换库。用于Orange Pi Zero和其他sbc的GPIO。使用sysfs只复制基本的GPIO功能:这允许从用户空间访问GPIO引脚。
https://github.com/eutim/OPI.GPIO

[quote color="warning"]我们不能直接使用OrangePi官方提供的OPI.GPIO库,因为这个库年久失修并未提供对zero2这款型号的支持,我在github上找到了一款对Zero2提供支持的Python库[/quote]

编译修改过的OPI.GPIO库

经过我的尝试,最终在github上发现了别人写好对zero2提供支持的库。

修改版本的OrangePi。GPIO带来对Orange Pi Zero2, Orange Pi 3和Orange Pi Lite2的支持

sudo apt-get update
sudo apt-get install python-dev git
git clone https://github.com/eutim/OPI.GPIO
cd OPI.GPIO
sudo python setup.py install

现在,就可以正常使用这个库啦。

关于显示图片的代码函数

def produceImage(file_in, width, height, file_out):
    image = Image.open(file_in)
    resized_image = image.resize((width, height), Image.ANTIALIAS)
    resized_image.save(file_out)


def png2rgb565(png_path:str):
    
    try:
        im=Image.open(png_path)
    except:
        print ("Fail to open png file ", png_path)
        return False
    image_height = 128
    image_width = 160
    screen_data = []
    pix = im.load()  #load pixel array
    for h in range(image_width):
        # 高
        for w in range(image_height-1,-1,-1):
            if w < im.size[0]:
                R=pix[w,h][0]>>3
                G=pix[w,h][1]>>2
                B=pix[w,h][2]>>3
                # print("red5:",red)
                highBit = 0 | (B << 3) | (G >> 3);  # 每个像素写入个字节,highBit高字节,lowBit低字节
                lowBit = 0 | (G << 5) | R;
                screen_data.append(highBit)
                screen_data.append(lowBit)
    # print(screen_data)
    return screen_data


def drawImg(png_path:str): #入参为160x128像素的image对象
    im=Image.open(png_path)
    bytes1 = []
    i = 0  
    # print(img160x128.getpixel((0, 0)))
    for x in range(0, screenWidth):
        # 循环160
        for y in range(screenHeight - 1, -1, -1):
            # 循环128次
            colorValue = im.getpixel((x, y))
            # print(img160x128.getpixel((x, y)))
            # print(i,colorValue)
            red = colorValue[0]
            green = colorValue[1]
            blue = colorValue[2]
            # print("red:",red)
            # 本来8位的,改成6位
            red = red >> 3;  # st7735s的红色占5位
            green = green >> 2;  # st7735s的绿色占6位
            blue = blue >> 3;  # st7735s的蓝色占5位
            # print("red5:",red)
            highBit = 0 | (blue << 3) | (green >> 3);  # 每个像素写入个字节,highBit高字节,lowBit低字节
            lowBit = 0 | (green << 5) | red;
            bytes1.append(highBit)
            bytes1.append(lowBit)

    return bytes1


def drawImg1():

    produceImage("6.png",160,128,"5.png")
    rgb565 = png2rgb565("5.png")
    i = 0
    for j in range(2000, screenWidth * screenHeight * 2, 2000):  
        sendManyBytes(rgb565[i:j])
        i = i + 2000
    sendManyBytes(rgb565[i:screenWidth * screenHeight * 2])

更多

  1. 树莓派 python 驱动 lcd tft spi 1.8寸 ST7735S
  2. [python中的spidev模块](
0

评论 (2)

取消
  1. 头像
    1
    MacOS · FireFox

    有香橙派3LTS驱动这个屏幕的代码嘛,大佬,求一份

    回复
    1. 头像
      HSM 作者
      MacOS · Google Chrome
      @ 1

      这个代码应该也可以用的

      回复