О платформе

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

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

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

AntMiner S9
AntMiner S9

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

Программирование дисплея на контроллере ST7920

pvsm.ru

GitHub - JMW95/pyST7920: Python library to control ST7920 128x64 monochrone LCD panel using Raspberry Pi and SPI

github.com

Raspberry Pi. Обмен данными по интерфейсу SPI.

microtechnics.ru

SPIdev Tutorial for Zynq-7000 FPGA Devices

hackster.io

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.

Комментарии (6)


  1. iliasam
    13.09.2024 17:25

    А где-то есть информация о том, куда и как физически подключены выводы ПЛИС на плате?


    1. Astranome Автор
      13.09.2024 17:25
      +1

      Отличный вопрос! Схема платы : https://github.com/astranome/Astra_S9_FPGA/blob/main/AntMiner_ControlBoard_XC7010_V1.01.pdf

      Распиновка Камня : https://github.com/astranome/Astra_S9_FPGA/blob/main/xc7z010clg400pkg.txt

      Но тут нюанс: окончательная, реальная распиновка определяется констрейн-файлом

      В зелёной рамке - назначение выходов SPI на физические пины камня
      В зелёной рамке - назначение выходов SPI на физические пины камня

      А в схеме платы отображено, какие выводы ZYNQ куда разведены. Если потребуется, объясню более наглядно


      1. iliasam
        13.09.2024 17:25

        Спасибо.

        К слову, попалось неплохое описание платы - https://mysku.club/blog/aliexpress/84832.html


  1. 100h
    13.09.2024 17:25

    А чем эта плата отличается от EBAZ?


    1. Astranome Автор
      13.09.2024 17:25
      +1

      У платы ЕБАЗ ОЗУ в 2 раза меньше, эзернет подключен по другому, пинов выведено меньше, требует "доработки напильником". Есть огромный плюс - на них энтузиасты сделали документацию, куча примеров, имеется несколько репо на гитхабе и большая группа в ТГ.


  1. NutsUnderline
    13.09.2024 17:25

    оченьбыстротекст