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.
Connected to such a board
1. We study the developments on the topic.
Programming the display on the ST7920 controller
Raspberry Pi. Data exchange via SPI interface.
SPIdev Tutorial for Zynq-7000 FPGA Devices
2. We make the Vivado design block as in the picture
3. Synthesize and open the synthesis design, assign the outputs of our newly-made to the legs (Package Pins) of the xc7z010clg400 microcircuit
the pin assignments will be displayed on the plan
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.