Опишу, как "поднять" SPI в Линуксе (Убунту) , подключить экранчик 12864 с контроллером ST7920 (бывают и другие варианты контроллеров) к ZYNQ-7010 и отобразить на нём текст, линию и прямоугольник , используя Питон.
Подключен к такой плате
1. Изучаем наработки по теме.
Программирование дисплея на контроллере ST7920
Raspberry Pi. Обмен данными по интерфейсу SPI.
SPIdev Tutorial for Zynq-7000 FPGA Devices
2. Блок дизайн Вивадо делаем, как на картинке
3. Синтезируем и открываем синтез-дизайн , назначаем выводы нашего новоиспечённого на ножки (Package Pins) микросхемы xc7z010clg400
на плане отобразятся назначения пинов
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)
100h
13.09.2024 17:25А чем эта плата отличается от EBAZ?
Astranome Автор
13.09.2024 17:25+1У платы ЕБАЗ ОЗУ в 2 раза меньше, эзернет подключен по другому, пинов выведено меньше, требует "доработки напильником". Есть огромный плюс - на них энтузиасты сделали документацию, куча примеров, имеется несколько репо на гитхабе и большая группа в ТГ.
iliasam
А где-то есть информация о том, куда и как физически подключены выводы ПЛИС на плате?
Astranome Автор
Отличный вопрос! Схема платы : 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
Но тут нюанс: окончательная, реальная распиновка определяется констрейн-файлом
А в схеме платы отображено, какие выводы ZYNQ куда разведены. Если потребуется, объясню более наглядно
iliasam
Спасибо.
К слову, попалось неплохое описание платы - https://mysku.club/blog/aliexpress/84832.html