Как стать автором
Обновить

Подключаем монохромный LCD дисплей 12864 на контроллере ST7920 по SPI интерфейсу к ZYNQ-7010. Практическое руководство

Уровень сложностиСредний
Время на прочтение5 мин
Количество просмотров2.9K

О платформе

Опишу, как "поднять" 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()
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

Теги:
Хабы:
Всего голосов 8: ↑7 и ↓1+10
Комментарии6

Публикации

Ближайшие события