Опишу, как "поднять" 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() import LCDClock LCDClock.main() """ s.clear() s.put_text("Hello world!", 10, 50) 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.
Этой плате посвящён форум https://astra.org.ru и группа в Телеграм https://t.me/+R_oA68EGEtM4NmM6
