Connecting a 12864 screen on an ST7920 controller to a ZYNQ-7010 via SPI. A practical guide

About the platform: https://habr.com/ru/articles/842958/

I will describe how to “raise” SPI in Linux (Ubuntu), connect a 12864 screen with an ST7920 controller (there are other controller options) to a ZYNQ-7010 and display text, a line and a rectangle on it using Python.

Test, slanted line, empty rectangle and filled rectangle

Test, slanted line, empty rectangle and filled rectangle

Connected to such a board

AntMiner S9

AntMiner S9

1. We study the developments on the topic.

Programming the display on the ST7920 controller

pvsm.ru

GitHub – JMW95/pyST7920: Python library to control ST7920 128×64 monochrone LCD panel using Raspberry Pi and SPI

github.com

Raspberry Pi. Data exchange via SPI interface.

microtechnics.ru

SPIdev Tutorial for Zynq-7000 FPGA Devices

hackster.io

2. We make the Vivado design block as in the picture

Connect INPUT SPI0_SS_I to constant (1)

Connect INPUT SPI0_SS_I to constant (1)

3. Synthesize and open the synthesis design, assign the outputs of our newly-made to the legs (Package Pins) of the xc7z010clg400 microcircuit

MOSI=Tx3, MISO=Rx3, SCLK=Plug3, SS0=Speed1, SS1=Speed2 (on A9 board)

MOSI=Tx3, MISO=Rx3, SCLK=Plug3, SS0=Speed1, SS1=Speed2 (on A9 board)

the pin assignments will be displayed on the plan

the squares are the legs of the microcircuit (on which "roll the balls")

the squares are the legs of the microcircuit (on which the balls are “rolled”)

4. Then everything is as usual: Synthesis, Implementation, Bitstream Generation, Export “Hardware Include Bitstream”, launch SDK (Vitis)

5. Generate FSBL and BOOT.bin

6. Generate Device Tree and compile devicetree.dtb

7. Copy these two files to the boot partition. Boot and look for spi in /dev. (It is quite possible that no SPI will be found in the system, I will explain how to resolve this situation later.)

8. Launch Python and add 2 files – the display driver and the test itself.

driver st7920.py:

_____________________________________________________________________________

“`

import spidev
import png
from copy import deepcopy

class ST7920:
def __init__(self):
self.spi = spidev.SpiDev()
self.spi.open(0,1)
self.spi.cshigh = True # use inverted CS
self.spi.max_speed_hz = 1000000 # set SPI clock to 1.8MHz, up from 125kHz

self.send(0,0,0×30) # basic instruction set
self.send(0,0,0×30) # repeated
self.send(0,0,0x0C) # display on

self.send(0,0,0×34) #enable RE mode
self.send(0,0,0×34)
self.send(0,0,0×36) #enable graphics display

self.set_rotation(0) # rotate to 0 degrees

self.fontsheet = self.load_font_sheet(“fontsheet.png”, 6, 8)

self.clear()
self.currentlydisplayedfbuff = None
self.redraw()

def set_rotation(self, rot):
if rot==0 or rot==2:
self.width = 128
self.height = 64
elif rot==1 or rot==3:
self.width = 64
self.height = 128
self.rot = rot

def load_font_sheet(self, filename, cw, ch):
img = png.Reader(filename).read()
rows = list(img[2])
height = len(rows)
width = len(rows[0])
sheet = []
for y in range(height//ch):
for x in range(width//cw):
char = []
for sy in range(ch):
row = rows[(y*ch)+sy]
char.append(row[(x*cw):(x+1)*cw])
sheet.append(char)
return (sheet, cw, ch)

def send(self, rs, rw, cmds):
if type(cmds) is int: # if a single arg, convert to a list
cmds = [cmds]
b1 = 0b11111000 | ((rw&0x01)<<2) | ((rs&0x01)<<1)
bytes = []
for cmd in cmds:
bytes.append(cmd & 0xF0)
bytes.append((cmd & 0x0F)<<4)
return self.spi.xfer2([b1] + bytes)

def clear(self):
self.fbuff = [[0]*(128//8) for i in range(64)]

def line(self, x1, y1, x2, y2, set=True):
diffX = abs(x2-x1)
diffY = abs(y2-y1)
shiftX = 1 if (x1 < x2) else -1
shiftY = 1 if (y1 < y2) else -1
err = diffX – diffY
drawn = False
while not drawn:
self.plot(x1, y1, set)
if x1 == x2 and y1 == y2:
drawn = True
continue
err2 = 2 * err
if err2 > -diffY:
err -= diffY
x1 += shiftX
if err2 < diffX:
err += diffX
y1 += shiftY

def fill_rect(self, x1, y1, x2, y2, set=True):
for y in range(y1,y2+1):
self.line(x1,y,x2,y,set)

def rect(self, x1, y1, x2, y2, set=True):
self.line(x1,y1,x2,y1,set)
self.line(x2,y1,x2,y2,set)
self.line(x2,y2,x1,y2,set)
self.line(x1,y2,x1,y1,set)

def plot(self, x, y, set):
if x<0 or x>=self.width or y<0 or y>=self.height:
return
if set:
if self.rot==0:
self.fbuff[y][x//8] |= 1 << (7-(x%8))
elif self.rot==1:
self.fbuff[x][15 – (y//8)] |= 1 << (y%8)
elif self.rot==2:
self.fbuff[63 – y][15-(x//8)] |= 1 << (x%8)
elif self.rot==3:
self.fbuff[63 – x][y//8] |= 1 << (7-(y%8))
else:
if self.rot==0:
self.fbuff[y][x//8] &= ~(1 << (7-(x%8)))
elif self.rot==1:
self.fbuff[x][15 – (y//8)] &= ~(1 << (y%8))
elif self.rot==2:
self.fbuff[63 – y][15-(x//8)] &= ~(1 << (x%8))
elif self.rot==3:
self.fbuff[63 – x][y//8] &= ~(1 << (7-(y%8)))

def put_text(self, s, x, y):
for c in s:
try:
font, cw, ch = self.fontsheet
char = font[ord(c)]
sy = 0
for row in char:
sx = 0
for px in row:
self.plot(x+sx, y+sy, px == 1)
sx += 1
sy += 1
except KeyError:
pass
x += cw

def _send_line(self, row, dx1, dx2):
self.send(0,0,[0x80 + row%32, 0x80 + ((dx1//16) + (8 if row>=32 else 0))]) # set address
self.send(1,0,self.fbuff[row][dx1//8:(dx2//8)+1])

def redraw(self, dx1=0, dy1=0, dx2=127, dy2=63, full=False):
if self.currentlydisplayedfbuff == None: # first redraw always affects the complete LCD
for row in range(0, 64):
self._send_line(row, 0, 127)
self.currentlydisplayedfbuff = deepcopy(self.fbuff) #currentlydisplayedfbuff is initialized here
else: # redraw has been called before, since currentlydisplayedfbuff is already initialized
for row in range(dy1, dy2+1):
if full or (self.currentlydisplayedfbuff[row] != self.fbuff[row]): # redraw row if full=True or changes are detected
self._send_line(row, dx1, dx2)
self.currentlydisplayedfbuff[row][dx1//8:(dx2//8)+1] = self.fbuff[row][dx1//8:(dx2//8)+1]
__________________________________________________________________________________________
The indents will most likely shift when published, and you will have to edit them manually.

display test 12864.py:

+++++++++++++++++++++++++++++++++++++++++++++

from st7920 import ST7920
from time import sleep
s = ST7920()

s.clear()
sleep(0.5)
s.put_text(“Privet, Bamboo!”, 5, 50)
sleep(0.5)
s.redraw()
sleep(1)
s.clear()
s.fill_rect(1,30,120,40)
s.rect(1,1,120,60)
s.line(1,1,120,60)
s.put_text(“SPI on Spidev0.1!”, 20, 5)
s.redraw()
sleep(2)
s.clear()
sleep(0.5)
s.put_text(“Privet, Bamboo!”, 5, 50)
sleep(0.5)
s.redraw()
sleep(1)
#s.clear()
s.fill_rect(1,30,120,40)
s.rect(1,1,120,60)
s.line(1,1,120,60)
s.put_text(“SPI on Spidev0.1!”, 20, 5)
s.redraw()
+++++++++++++++++++++++++++++++++++++++++++++

It is quite possible that no SPI will be found in the system, I will explain how to resolve this situation.

You can create a beautiful design in Vivado, you can correctly solder an SPI device to the board, but it will not work until it is registered in the device.

In our case, we need to find the construction in devicetree.dts

spi@e0006000 {
compatible = “xlnx,zynq-spi-r1p6”;
reg = <0xe0006000 0x1000>;
status = “disabled”;
interrupt-parent = ;
interrupts = <0x00 0x1a 0x04>;
clocks = <0x01 0x19 0x01 0x22>;
clock-names = “ref_clk\0pclk”;
#address-cells = ;
#size-cells = ;
};
and bring it to the form:

spi@e0006000 {
compatible = “xlnx,zynq-spi-r1p6”;
reg = <0xe0006000 0x1000>;
status = “okay”;
interrupt-parent = ;
interrupts = <0x00 0x1a 0x04>;
clocks = <0x01 0x19 0x01 0x22>;
clock-names = “ref_clk\0pclk”;
#address-cells = ;
#size-cells = ;
is-decoded-cs = ;
num-cs = ;

spidev@0x00 {
compatible = “spidev”;
spi-max-frequency = <0xf4240>;
reg = ;
};
};

This will allow you to connect arbitrary devices via the SPI bus that do not have drivers in the kernel.

There is one more nuance. In the Linux kernel (uImage file) you need to enable spidev support.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *