Найти в Дзене

Платформа Astra9: подключаем экранчик 12864 на контроллере ST7920 к ZYNQ-7010 через SPI. Практическое руководство.

О платформе: https://dzen.ru/a/ZNl3aWiUFm1X5II6 Опишу, как "поднять" SPI в Линуксе (Убунту) , подключить экранчик 12864 с контроллером ST7920 (бывают и другие варианты контроллеров) к ZYNQ-7010 и отобразить на нём текст, линию и прямоугольник , используя Питон. Подключен к такой плате 1. Изучаем наработки по теме. 2. Блок дизайн Вивадо делаем, как на картинке 3. Синтезируем и открываем синтез-дизайн , назначаем выводы нашего новоиспечённого на ножки (Package Pins) микросхемы xc7z010clg400 на плане отобразятся назначения пинов 4. Далее всё, как обычно, Синтез, Имплементация, Генерация Битстрима, Экспорт "Хардварэ инклуде битстрим", запуск SDK (Vitis) 5. Генерируем FSBL и BOOT.bin 6. Генерируем Device Tree и собираем devicetree.dtb 7. Копируем эти два файла в загрузочный раздел. Загружаемся и ищем spi в /dev. (Вполне вероятно, что никаких SPI в системе не обнаружится, как разрулить такую ситуацию объясню позже.) 8. Запускаем Питон и добавляем 2 файла - драйвер дисплейчика и соб

О платформе: https://dzen.ru/a/ZNl3aWiUFm1X5II6

Опишу, как "поднять" SPI в Линуксе (Убунту) , подключить экранчик 12864 с контроллером ST7920 (бывают и другие варианты контроллеров) к ZYNQ-7010 и отобразить на нём текст, линию и прямоугольник , используя Питон.

Тест, наклонная линия, пустой прямоугольник и прямоугольник с заливкой
Тест, наклонная линия, пустой прямоугольник и прямоугольник с заливкой

Подключен к такой плате

-2

1. Изучаем наработки по теме.

Программирование дисплея на контроллере ST7920
GitHub - JMW95/pyST7920: Python library to control ST7920 128x64 monochrone LCD panel using Raspberry Pi and SPI
Raspberry Pi. Обмен данными по интерфейсу SPI.
SPIdev Tutorial for Zynq-7000 FPGA Devices

2. Блок дизайн Вивадо делаем, как на картинке

Подключим ВХОД SPI0_SS_I к константе (1)
Подключим ВХОД SPI0_SS_I к константе (1)

3. Синтезируем и открываем синтез-дизайн , назначаем выводы нашего новоиспечённого на ножки (Package Pins) микросхемы xc7z010clg400

MOSI=Tx3, MISO=Rx3, SCLK=Plug3, SS0=Speed1, SS1=Speed2 (на плате А9)
MOSI=Tx3, MISO=Rx3, SCLK=Plug3, SS0=Speed1, SS1=Speed2 (на плате А9)

на плане отобразятся назначения пинов

квадратики - это ножки микросхемы , (на которые "накатывают шары")
квадратики - это ножки микросхемы , (на которые "накатывают шары")

4. Далее всё, как обычно, Синтез, Имплементация, Генерация Битстрима, Экспорт "Хардварэ инклуде битстрим", запуск SDK (Vitis)

5. Генерируем FSBL и BOOT.bin

6. Генерируем Device Tree и собираем devicetree.dtb

7. Копируем эти два файла в загрузочный раздел. Загружаемся и ищем spi в /dev. (Вполне вероятно, что никаких SPI в системе не обнаружится, как разрулить такую ситуацию объясню позже.)

8. Запускаем Питон и добавляем 2 файла - драйвер дисплейчика и собственно тест.

драйвер 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,0x30) # basic instruction set
self.send(0,0,0x30) # repeated
self.send(0,0,0x0C) # display on

self.send(0,0,0x34) #enable RE mode
self.send(0,0,0x34)
self.send(0,0,0x36) #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]
__________________________________________________________________________________________
```

тест дисплея 12864.py :

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

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

s.clear()
sleep(0.5)
s.put_text("Privet, Bambuk!", 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, Bambuk!", 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()
++++++++++++++++++++++++++++++++++++++++++++

Вполне вероятно, что никаких SPI в системе не обнаружится, объясню, как разрулить такую ситуацию.

Можно создать прекрасный дизайн в Вивадо, можно правильно припаять SPI устройство к плате, но оно не заработает, пока не будет прописано в девайстри.

В нашем случае необходимо найти в devicetree.dts конструкцию

spi@e0006000 {
compatible = "xlnx,zynq-spi-r1p6";
reg = <0xe0006000 0x1000>;
status = "disabled";
interrupt-parent = <0x04>;
interrupts = <0x00 0x1a 0x04>;
clocks = <0x01 0x19 0x01 0x22>;
clock-names = "ref_clk\0pclk";
#address-cells = <0x01>;
#size-cells = <0x00>;
};
и привести её к виду:

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

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

Это позволит подключать по шине SPI произвольные устройства, на которые нет драйверов в ядре.

Есть ещё один нюанс. В ядре линукса (файл uImage) нужно включить поддержку spidev.