New article on Waveshare e-ink displays
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
parent
454cf05d56
commit
5ea7af48e8
Binary file not shown.
After Width: | Height: | Size: 162 KiB |
Binary file not shown.
After Width: | Height: | Size: 56 KiB |
Binary file not shown.
After Width: | Height: | Size: 19 KiB |
@ -0,0 +1,367 @@
|
||||
---
|
||||
title: "Using Waveshare e-ink screens without Raspberry Pi"
|
||||
date: !!timestamp '2023-08-02 11:37:00'
|
||||
image: /post/waveshare-e-paper-display-on-linux-without-raspberry-pi/og.webp
|
||||
tags:
|
||||
- linux
|
||||
- e-paper
|
||||
- kernel
|
||||
- spi
|
||||
aliases:
|
||||
- waveshare
|
||||
---
|
||||
|
||||
When it comes to e-ink displays, Waveshare is a rare manufacturer that allows you to buy displays of any size.
|
||||
Advertised as ESP32, Arduino and Raspberry Pi compatible, they are in fact compatible with any development board exposing the SPI protocol.
|
||||
|
||||
Since Raspberry Pi boards have become hard to find in recent months, we'll take a look in this article at how to use another Linux-based board to run a Waveshare display.
|
||||
|
||||
<!-- more -->
|
||||
|
||||
## Display communication protocol
|
||||
|
||||
Waveshare's e-ink displays are driven by the SPI protocol.
|
||||
Like USB or I²C, this is a common standard for communication between a machine and a peripheral.
|
||||
|
||||
To communicate, the protocol uses 3 wires + 1 wire per peripheral:
|
||||
|
||||
- `SCLK` (or `SCK` or `SCL`) is the clock signal to synchronize communication;
|
||||
- `MOSI` (or `SDO` or `SDA`) is the data flow from the machine to the peripheral;
|
||||
- `MISO` (or `SDI`) is the data flow from the peripheral to the machine.
|
||||
|
||||
Several devices can share the SPI bus, the 3 wires described above.
|
||||
To keep track of who the information is intended for, there is an additional wire for each peripheral.
|
||||
With 3 devices on the SPI bus, we'll have 6 wires: the 3 bus wires, shared, and 1 wire per device, non-shared.
|
||||
|
||||
This additional wire, called `CS` for Chip Select (or `SS`), is powered when the machine wishes to talk to the designated device.
|
||||
Only 1 device is active at a time, to avoid collisions on the bus (imagine two devices sending two separate messages at the same time... on the same wire).
|
||||
A device only talks and listens when its `CS` is low (at 0).
|
||||
|
||||
When a device sends or receives data, it synchronizes to the clock frequency transmitted by the machine on the `SCLK` wire.
|
||||
At clock top, 1 bit of information can be read from `MISO` and `MOSI`.
|
||||
|
||||
The machine and the peripheral can send data simultaneously, since we have 1 wire dedicated to transmission and 1 wire dedicated to reception.
|
||||
In the case of Waveshare displays, only uplink communication to the peripheral is used; the display will never respond on the SPI bus.
|
||||
There is therefore no wire for `MISO`.
|
||||
To communicate its status, it uses dedicated wires.
|
||||
|
||||
|
||||
## Other wires used
|
||||
|
||||
In addition to the power supply (+3.3 V and ground) and the wires used for the SPI protocol, 3 wires complete the communication:
|
||||
|
||||
- `DC`: 0 when sending commands on the SPI bus. Set to 1 when sending data.
|
||||
- `RST`: when this wire is set to 0, it causes the screen to restart, resetting all its registers. It is reset to 1 to start using it.
|
||||
- `BUSY`: the chip controlling the screen uses this wire to indicate whether the screen or controller is busy, performing an action (when set to 0) or ready to receive information (when set to 1).
|
||||
|
||||
With these specifications well established, there's nothing to limit us to the Raspberry Pi alone!
|
||||
|
||||
|
||||
## Recognizing a compatible card
|
||||
|
||||
To be used with the display, the board must expose an SPI bus on its GPIOs, and you must have 3 generic slots available in addition to 3.3 V and ground.
|
||||
|
||||
All boards, recent and old, expose these interfaces.
|
||||
|
||||
For example, [here's the pin assignment for the Pine64](https://files.pine64.org/doc/Pine%20A64%20Schematic/Pine%20A64%20Pin%20Assignment%20160119.pdf):
|
||||
|
||||
![Pin assignment Pi-2 on the Pine64-LTS](pine64-pi2-pinout.webp)
|
||||
|
||||
And [for the Cubieboard](http://docs.cubieboard.org/cubieboard1_and_cubieboard2_gpio_pin) :
|
||||
|
||||
![Cubieboard U15 pin assignment](cubieboard-u15-pinout.webp)
|
||||
|
||||
Some boards have GPIOs identical to those on the Raspberry Pi, so you can place the supplied HAT directly on them.
|
||||
When the pin organization is different, you'll have to cable it manually.
|
||||
|
||||
I used a MIPS Creator CI 20 board:
|
||||
|
||||
[![CI20 pin assignment](ci20-pinout.webp)](https://elinux.org/CI20_Hardware#Primary_expansion_header)
|
||||
|
||||
Here is the wiring:
|
||||
|
||||
![My CI20 wired](ci20-attached.webp)
|
||||
|
||||
|
||||
## Linux configuration
|
||||
|
||||
The [Waveshare documentation](https://www.waveshare.com/wiki/7.5inch_e-Paper_HAT_Manual#Enable_SPI_Interface) suggests using the utilities supplied with Raspbian.
|
||||
We don't have the same simplified utilities, so we'll see how to do it directly.
|
||||
|
||||
### Activate SPI
|
||||
|
||||
Usually, a kernel module is loaded to communicate with an SPI-connected device.
|
||||
This module will then create an abstraction layer and expose the abstraction in the `/dev` folder.
|
||||
|
||||
Here, we don't have a kernel module dedicated to the screen; in fact, the program we're about to launch, the Waveshare demo, communicates directly via SPI.
|
||||
It's as if we'd written a program to read and write to a USB stick by sending the raw USB commands, rather than having the kernel display the device under `/dev/sdb`.
|
||||
|
||||
In order to drive the SPI bus from a user-space program, we'll need to tell our kernel that it must use the `spidev` driver to manage the bus in question.
|
||||
|
||||
If you've compiled your kernel yourself, make sure you have this module:
|
||||
|
||||
```
|
||||
42sh$ zgrep SPI_DEV /proc/config. gz
|
||||
CONFIG_SPI_SPIDEV=y
|
||||
```
|
||||
|
||||
The kernel obtains information on the devices and modules to be used by consulting a file called *Device Tree*, specific to each SBC.
|
||||
|
||||
Generally, when a device is not in use, it is marked as disabled in the *Device Tree*.
|
||||
If you don't have a `/dev/spidev*` file, you'll have to start by enabling it.
|
||||
|
||||
### Activation using a Device Tree Overlay
|
||||
|
||||
The Raspbian tool that activates the SPI bus in the Waveshare documentation will in fact activate a pre-designed *Device Tree Overlay*.
|
||||
|
||||
We can do the same by [creating our own *Device Tree Overlay*](https://bootlin.com/blog/using-device-tree-overlays-example-on-beaglebone-boards/).
|
||||
The aim is to activate our SPI bus.
|
||||
|
||||
From the basic DTB file for our board, we should have somewhere (potentially in the `#include`) a block like this:
|
||||
|
||||
```c
|
||||
spi0: spi@10043000 {
|
||||
compatible = "ingenic,jz4780-spi";
|
||||
reg = <0x10043000 0x1c>;
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
interrupt-parent = <&intc>;
|
||||
interrupts = <8>;
|
||||
|
||||
clocks = <&cgu JZ4780_CLK_SSI0>;
|
||||
clock-names = "spi";
|
||||
|
||||
dmas = <&dma JZ4780_DMA_SSI0_RX 0xffffffff>,
|
||||
<&dma JZ4780_DMA_SSI0_TX 0xffffffff>;
|
||||
dma-names = "rx", "tx";
|
||||
|
||||
status = "disabled";
|
||||
};
|
||||
```
|
||||
|
||||
We can see that it is disabled (`status = "disabled"`).
|
||||
|
||||
In relation to our pins, if these are indeed the ones we wish to use, we will then establish the following *overlay* (`my_spidev.dts`) :
|
||||
|
||||
```c
|
||||
/dts-v1/;
|
||||
/plugin/;
|
||||
|
||||
&spi0 {
|
||||
status = "okay";
|
||||
|
||||
spidev0: spidev@0{
|
||||
compatible = "spidev";
|
||||
reg = <0>;
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
spi-max-frequency = <500000>;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
Now let's compile our *overlay* :
|
||||
|
||||
```
|
||||
dtc -O dtb -o MY_SPIDEV.dtbo my_spidev.dts
|
||||
```
|
||||
|
||||
We then place it in our boot partition, alongside the kernel and our `dtb`.
|
||||
For example:
|
||||
|
||||
```
|
||||
mv MY_SPIDEV.dtbo /boot/overlay/MY_SPIDEV.dtbo
|
||||
```
|
||||
|
||||
Then reboot and load this *overlay* in u-boot:
|
||||
|
||||
```
|
||||
# What is currently done
|
||||
load mmc 0:1 0x820000000 ci20.dtb
|
||||
fdt addr 0x82000000
|
||||
|
||||
# Steps to add after device tree loading
|
||||
load mmc 0:1 0x83000000 overlays/MY_SPIDEV.dtbo
|
||||
fdt resize 8192
|
||||
fdt apply 0x83000000
|
||||
|
||||
# Then boot as usual
|
||||
bootz ...
|
||||
```
|
||||
|
||||
Once we've tested that it works, we can add it to the startup script.
|
||||
|
||||
|
||||
### Doing without an SPI bus
|
||||
|
||||
If you don't have an SPI bus available, you can still use GPIO pins and dedicate them to this purpose.
|
||||
To do this, we'll use the `spi-gpio` kernel module, in addition to `spidev`.
|
||||
|
||||
First of all, let's make sure we have its support:
|
||||
|
||||
```
|
||||
42sh$ zgrep SPI_GPIO /proc/config.gz
|
||||
CONFIG_SPI_GPIO=y
|
||||
```
|
||||
|
||||
Here's the *overlay* we could write to simulate an SPI bus on pins 19, 21, 23, 24 and 26:
|
||||
|
||||
```c
|
||||
/dts-v1/;
|
||||
/plugin/;
|
||||
|
||||
#include <dt-bindings/gpio/gpio.h>
|
||||
|
||||
spi_gpio {
|
||||
compatible = "spi-gpio";
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
gpio-sck = <&gpf 15 GPIO_ACTIVE_HIGH>; /* PE15 */
|
||||
gpio-miso = <&gpf 14 GPIO_ACTIVE_HIGH>; /* PE14 */
|
||||
gpio-mosi = <&gpf 17 GPIO_ACTIVE_HIGH>; /* PE17 */
|
||||
num-chipselects = <2>;
|
||||
cs-gpios = <&gpf 16 GPIO_ACTIVE_HIGH &gpf 18 GPIO_ACTIVE_HIGH>; /* PE16, PE18 */
|
||||
|
||||
spidev@0 {
|
||||
compatible = "spidev";
|
||||
reg = <0>;
|
||||
spi-max-frequency = <1000000>;
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
The overlay is applied at startup, as described above.
|
||||
|
||||
In both cases, it is also possible to make the changes directly in the original DTS file and replace the DTB used to boot the board.
|
||||
You'll have to do this again each time you update the kernel.
|
||||
|
||||
|
||||
## Configuring dedicated GPIOs
|
||||
|
||||
We've seen that the display uses 3 GPIOs in addition to the SPI bus.
|
||||
|
||||
In my case, I chose to use pins 18 (`PF2`), 11 (`PD26`) and 22 (`PE8`).
|
||||
Let's declare them with:
|
||||
|
||||
```sh
|
||||
# BUSY: PF2
|
||||
echo 162 > /sys/class/gpio/export
|
||||
|
||||
# RST: PD26
|
||||
echo 122 > /sys/class/gpio/export
|
||||
echo out > /sys/class/gpio/gpio122/direction
|
||||
|
||||
# DC: PE8
|
||||
echo 136 > /sys/class/gpio/export
|
||||
echo out > /sys/class/gpio/gpio136/direction
|
||||
```
|
||||
|
||||
|
||||
## Adapting the demo code
|
||||
|
||||
The Waveshare demo expects to run on a Raspberry Pi, so there are a number of hard-coded elements.
|
||||
In addition, it makes use of a Python dependency that doesn't work outside this platform.
|
||||
|
||||
Here are the [implementation](https://git.nemunai.re/nemunaire/waveshareteam-e-Paper/commit/dfa74b004198794d01bdc461ea88d1cf9cdb80b5) I've made to adapt the code to my needs:
|
||||
|
||||
```diff
|
||||
--- a/RaspberryPi_JetsonNano/python/lib/waveshare_epd/epdconfig.py
|
||||
+++ b/RaspberryPi_JetsonNano/python/lib/waveshare_epd/epdconfig.py
|
||||
@@ -230,6 +230,89 @@ class SunriseX3:
|
||||
self.GPIO.cleanup([self.RST_PIN, self.DC_PIN, self.CS_PIN, self.BUSY_PIN], self.PWR_PIN)
|
||||
|
||||
|
||||
+class CreatorCI20:
|
||||
+ # Pin definition
|
||||
+ RST_PIN = 122
|
||||
+ DC_PIN = 136
|
||||
+ CS_PIN = 0
|
||||
+ BUSY_PIN = 162
|
||||
+ PWR_PIN = 18
|
||||
+ Flag = 0
|
||||
+
|
||||
+ def __init__(self):
|
||||
+ import spidev
|
||||
+
|
||||
+ self.SPI = spidev.SpiDev()
|
||||
+
|
||||
+ def digital_write(self, pin, value):
|
||||
+ if pin == 0:
|
||||
+ return
|
||||
+
|
||||
+ with open("/sys/class/gpio/gpio"+str(pin)+"/value", "w") as f:
|
||||
+ f.write(str(value))
|
||||
+
|
||||
+ def digital_read(self, pin):
|
||||
+ v = "0"
|
||||
+ if pin != 0:
|
||||
+ with open("/sys/class/gpio/gpio"+str(pin)+"/value") as f:
|
||||
+ v = f.readline()
|
||||
+ return int(v)
|
||||
+
|
||||
+ def delay_ms(self, delaytime):
|
||||
+ time.sleep(delaytime / 1000.0)
|
||||
+
|
||||
+ def spi_writebyte(self, data):
|
||||
+ self.SPI.writebytes(data)
|
||||
+
|
||||
+ def spi_writebyte2(self, data):
|
||||
+ # for i in range(len(data)):
|
||||
+ # self.SPI.writebytes([data[i]])
|
||||
+ self.SPI.xfer3(data)
|
||||
+
|
||||
+ def module_init(self):
|
||||
+ if self.Flag == 0:
|
||||
+ self.Flag = 1
|
||||
+
|
||||
+ # BUSY
|
||||
+ with open("/sys/class/gpio/export", "w") as f:
|
||||
+ f.write(str(self.BUSY_PIN))
|
||||
+
|
||||
+ # RST
|
||||
+ with open("/sys/class/gpio/export", "w") as f:
|
||||
+ f.write(str(self.RST_PIN))
|
||||
+ with open("/sys/class/gpio/gpio" + str(self.RST_PIN) + "/direction", "w") as f:
|
||||
+ f.write("out")
|
||||
+
|
||||
+ # DC
|
||||
+ with open("/sys/class/gpio/export", "w") as f:
|
||||
+ f.write(str(self.DC_PIN))
|
||||
+ with open("/sys/class/gpio/gpio" + str(self.DC_PIN) + "/direction", "w") as f:
|
||||
+ f.write("out")
|
||||
+
|
||||
+ # SPI device, bus = 0, device = 0
|
||||
+ self.SPI.open(32766, 0)
|
||||
+ self.SPI.max_speed_hz = 4000000
|
||||
+ self.SPI.mode = 0b00
|
||||
+ return 0
|
||||
+ else:
|
||||
+ return 0
|
||||
+
|
||||
+ def module_exit(self):
|
||||
+ logger.debug("spi end")
|
||||
+ self.SPI.close()
|
||||
+
|
||||
+ logger.debug("close 5V, Module enters 0 power consumption ...")
|
||||
+ self.Flag = 0
|
||||
+
|
||||
+ self.digital_write(self.RST_PIN, 0)
|
||||
+ self.digital_write(self.DC_PIN, 0)
|
||||
+
|
||||
+ # Clean up
|
||||
+ for pin in [self.BUSY_PIN, self.RST_PIN, self.DC_PIN]
|
||||
+ with open("/sys/class/gpio/unexport", "w") as f:
|
||||
+ f.write(str(pin))
|
||||
+
|
||||
+
|
||||
if os.path.exists('/sys/bus/platform/drivers/gpiomem-bcm2835'):
|
||||
implementation = RaspberryPi()
|
||||
elif os.path.exists('/sys/bus/platform/drivers/gpio-x3'):
|
||||
```
|
||||
|
||||
|
||||
## Conclusion
|
||||
|
||||
You can't expect a manufacturer to spend time documenting the use of its device for all existing platforms.
|
||||
We'd like to thank Waveshare for [posting very detailed specifications](https://www.waveshare.com/w/upload/6/60/7.5inch_e-Paper_V2_Specification.pdf) that enable advanced users to understand how the screen works and how to communicate with it. and how to communicate with it.
|
Binary file not shown.
After Width: | Height: | Size: 27 KiB |
Binary file not shown.
After Width: | Height: | Size: 83 KiB |
@ -0,0 +1,366 @@
|
||||
---
|
||||
title: "Utiliser les écrans à encre électronique Waveshare sans Raspberry Pi"
|
||||
date: !!timestamp '2023-08-02 11:37:00'
|
||||
image: /post/waveshare-e-paper-display-on-linux-without-raspberry-pi/og.webp
|
||||
tags:
|
||||
- linux
|
||||
- e-paper
|
||||
- kernel
|
||||
- spi
|
||||
aliases:
|
||||
- waveshare
|
||||
---
|
||||
|
||||
En terme d'écran à encre électronique (e-ink), Waveshare est un rare constructeur permettant d'acheter des écrans de toute taille.
|
||||
Annoncés compatibles ESP32, Arduino et Raspberry Pi, ils sont en fait compatibles avec n'importe quelle carte de développement exposant le protocole SPI.
|
||||
|
||||
Les Raspberry Pi était devenues difficiles à trouver ces derniers mois, nous allons voir dans cet article comment utiliser une autre carte sous Linux pour utiliser un écran Waveshare.
|
||||
|
||||
<!-- more -->
|
||||
|
||||
## Le protocole de communication de l'écran
|
||||
|
||||
Les écrans à encre électronique de Waveshare sont pilotés par le protocole SPI.
|
||||
Au même titre que l'USB ou l'I²C, il s'agit d'un standard courant de communication entre une machine et un périphérique.
|
||||
|
||||
Pour communiquer, le protocole utilise 3 fils + 1 fil par périphérique :
|
||||
|
||||
- `SCLK` (ou `SCK` ou `SCL`) est le signal d'horloge pour synchroniser la communication ;
|
||||
- `MOSI` (ou `SDO` ou `SDA`) est le flux de données allant de la machine vers le périphérique ;
|
||||
- `MISO` (ou `SDI`) est le flux de données allant du périphérique vers la machine.
|
||||
|
||||
Plusieurs périphériques peuvent partager le bus SPI, les 3 fils décrits ci-dessus.
|
||||
Pour savoir à qui sont destinées les informations, il y a donc un fil supplémentaire pour chaque périphérique.
|
||||
Avec 3 périphériques sur le bus SPI, on aura donc 6 fils : les 3 fils du bus, partagés, et 1 fil par périphérique, non-partagés.
|
||||
|
||||
Ce fil supplémentaire, nommé `CS` pour Chip Select (ou `SS`), est alimenté lorsque la machine souhaite parler au périphérique désigné.
|
||||
1 seul périphérique est actif à la fois pour éviter les collisions sur le bus (imaginer deux périphériques envoyer deux messages distinct en même temps ... sur le même fil).
|
||||
Un périphérique ne parle et n'écoute que lorsque son `CS` est à l'état bas (à 0).
|
||||
|
||||
Lorsqu'un périphérique envoie ou reçoit des données, il se synchronise sur la fréquence d'horloge transmise par la machine sur le fil `SCLK`.
|
||||
Au top d'horloge, on peut lire 1 bit d'information sur `MISO` et `MOSI`.
|
||||
|
||||
La machine et le périphérique peuvent envoyer simultanément des données puisque nous avons 1 fil dédié à l'émission et 1 fil dédié à la réception.
|
||||
Dans le cas des écrans Waveshare, seule la communication montante vers le périphérique est utilisée, l'écran ne répondra jamais sur le bus SPI.
|
||||
Il n'y a donc pas de fil pour `MISO`.
|
||||
Pour communiquer son statut, il utilise des fils dédiés.
|
||||
|
||||
|
||||
## Les autres fils utilisés
|
||||
|
||||
En plus de l'alimentation électrique (+3.3 V et la masse) et des fils utilisés pour le protocole SPI, 3 fils viennent compléter la communication :
|
||||
|
||||
- `DC` : à 0 lorsque l'on enverra des commandes sur le bus SPI. À 1 lorsque ce sera des données qui y seront envoyées.
|
||||
- `RST` : lorsque ce fil est à 0, cela provoque le redémarrage de l'écran, la réinitialisation de tous ses registres. On le replace à 1 pour commencer à l'utiliser.
|
||||
- `BUSY` : la puce controlant l'écran utilise ce fil pour indiquer si l'écran ou le contrôleur est occupé, en train de réaliser une action (lorsqu'il est à 0) ou s'il est prêt à recevoir des informations (il est alors à 1) .
|
||||
|
||||
Ces spécifications étant bien établie, rien ne nous limite aux seules Raspberry Pi !
|
||||
|
||||
|
||||
## Reconnaître une carte compatible
|
||||
|
||||
Pour être utilisée avec l'écran, la carte doit exposer un bus SPI sur ses GPIO, et vous devez disposer de 3 emplacements génériques disponibles en plus du 3,3 V et de la masse.
|
||||
|
||||
Toutes les cartes, récentes et anciennes, exposant ces interfaces.
|
||||
|
||||
Voici par exemple [la correspondance des broches de la Pine64](https://files.pine64.org/doc/Pine%20A64%20Schematic/Pine%20A64%20Pin%20Assignment%20160119.pdf) :
|
||||
|
||||
![Correspondance des broches Pi-2 de la Pine64-LTS](pine64-pi2-pinout.webp)
|
||||
|
||||
Et [ceux de la Cubieboard](http://docs.cubieboard.org/cubieboard1_and_cubieboard2_gpio_pin) :
|
||||
|
||||
![Correspondance des broches U15 de la Cubieboard](cubieboard-u15-pinout.webp)
|
||||
|
||||
Certaines cartes exposent des GPIO identiques à ceux de la Raspberry Pi, vous pourrez donc placer le HAT fourni directement dessus.
|
||||
Lorsque l'organisation des broches est différente, il faudra câble manuellement.
|
||||
|
||||
Pour ma part, j'ai utilisé une carte MIPS Creator CI 20 :
|
||||
|
||||
[![Correspondance des broches de la CI20](ci20-pinout.webp)](https://elinux.org/CI20_Hardware#Primary_expansion_header)
|
||||
|
||||
Et voici donc le câblage :
|
||||
|
||||
![La CI20 câblée](ci20-attached.webp)
|
||||
|
||||
|
||||
## Configuration Linux
|
||||
|
||||
La [documentation de Waveshare](https://www.waveshare.com/wiki/7.5inch_e-Paper_HAT_Manual#Enable_SPI_Interface) propose d'utiliser les utilitaires livrés avec Raspbian.
|
||||
Nous n'avons pas les mêmes utilitaires simplifiés, nous allons donc voir comment faire directement.
|
||||
|
||||
### Activer le SPI
|
||||
|
||||
Habituellement, on charge un module noyau pour Communiquer avec un appareil relié en SPI.
|
||||
Ce module créera alors une couche d'abstraction etexposera l'abstraction dans le dossier `/dev`.
|
||||
|
||||
Ici, nous n'avons pas de module noyau dédié à l'écran, en fait le programme que l'on va lancer, la démo de Waveshare, communique directement en SPI.
|
||||
C'est comme si on avait écrit un programme pour lire et écrire sur une clef USB en envoyant les commandes USB brutes, plutôt que de faire en sorte que le noyau affiche le périphérique sous `/dev/sdb`.
|
||||
|
||||
Afin de piloter le bus SPI depuis un programme de l'espace utilisateur, on va devoir indiquer à notre noyau qu'il doit utiliser le pilote `spidev` pour gérer le bus en question.
|
||||
|
||||
Si vous avez compilé vous-même votre noyau, assurez-vous d'avoir ce module :
|
||||
|
||||
```
|
||||
42sh$ zgrep SPI_DEV /proc/config. gz
|
||||
CONFIG_SPI_SPIDEV=y
|
||||
```
|
||||
|
||||
Le noyau obtient les informations sur les périphériques et les modules à utiliser en consultant un fichier dit *Device Tree*, propre à chaque SBC.
|
||||
|
||||
Généralement, lorsqu'un périphérique n'est pas utilisé, il est marqué comme désactivé dans le *Device Tree*.
|
||||
Si vous n'avez pas de fichier `/dev/spidev*`, il va donc falloir commencer par l'activer.
|
||||
|
||||
### Activation par Device Tree Overlay
|
||||
|
||||
L'outil de Raspbian qui active le bus SPI dans la documentation de Waveshare va en fait activer un *Device Tree Overlay* préconçu.
|
||||
|
||||
Nous pouvons faire de même en [créant notre propre *Device Tree Overlay*](https://bootlin.com/blog/using-device-tree-overlays-example-on-beaglebone-boards/).
|
||||
Le but étant d'activer notre bus SPI.
|
||||
|
||||
À partir du fichier DTB de base pour notre carte, nous devrions avoir quelque part (potentiellement dans les `#include`) un bloc comme celui-ci :
|
||||
|
||||
```c
|
||||
spi0: spi@10043000 {
|
||||
compatible = "ingenic,jz4780-spi";
|
||||
reg = <0x10043000 0x1c>;
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
interrupt-parent = <&intc>;
|
||||
interrupts = <8>;
|
||||
|
||||
clocks = <&cgu JZ4780_CLK_SSI0>;
|
||||
clock-names = "spi";
|
||||
|
||||
dmas = <&dma JZ4780_DMA_SSI0_RX 0xffffffff>,
|
||||
<&dma JZ4780_DMA_SSI0_TX 0xffffffff>;
|
||||
dma-names = "rx", "tx";
|
||||
|
||||
status = "disabled";
|
||||
};
|
||||
```
|
||||
|
||||
On constate qu'il est bien désactivé (`status = "disabled"`).
|
||||
|
||||
Par rapport à nos broches, s'il s'agit bien de celles que l'on souhaite utiliser, on va alors établir l'*overlay* suivant (`my_spidev.dts`) :
|
||||
|
||||
```c
|
||||
/dts-v1/;
|
||||
/plugin/;
|
||||
|
||||
&spi0 {
|
||||
status = "okay";
|
||||
|
||||
spidev0: spidev@0{
|
||||
compatible = "spidev";
|
||||
reg = <0>;
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
spi-max-frequency = <500000>;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
Compilons maintenant notre *overlay* :
|
||||
|
||||
```
|
||||
dtc -O dtb -o MY_SPIDEV.dtbo my_spidev.dts
|
||||
```
|
||||
|
||||
On le place ensuite dans notre partition de démarrage, aux côtés du noyau et de notre `dtb`.
|
||||
Par exemple :
|
||||
|
||||
```
|
||||
mv MY_SPIDEV.dtbo /boot/overlay/MY_SPIDEV.dtbo
|
||||
```
|
||||
|
||||
Il faut ensuite redémarrer et dans u-boot, charger cet *overlay* :
|
||||
|
||||
```
|
||||
# What is currently done
|
||||
load mmc 0:1 0x820000000 ci20.dtb
|
||||
fdt addr 0x82000000
|
||||
|
||||
# Steps to add after device tree loading
|
||||
load mmc 0:1 0x83000000 overlays/MY_SPIDEV.dtbo
|
||||
fdt resize 8192
|
||||
fdt apply 0x83000000
|
||||
|
||||
# Then boot as usual
|
||||
bootz ...
|
||||
```
|
||||
|
||||
Une fois que l'on aura testé que cela fonctionne, on pourra l'ajouter au script de démarrage.
|
||||
|
||||
|
||||
### Faire sans bus SPI
|
||||
|
||||
Si vous n'avez pas de bus SPI disponible, il reste possible d'utiliser des broches GPIO et de les dédier à cet usage.
|
||||
On utilisera pour cela le module noyau `spi-gpio`, en plus de `spidev`.
|
||||
|
||||
Vérifions d'abord qu'on dispose bien de son support :
|
||||
|
||||
```
|
||||
42sh$ zgrep SPI_GPIO /proc/config.gz
|
||||
CONFIG_SPI_GPIO=y
|
||||
```
|
||||
|
||||
Voici l'*overlay* que l'on pourrait écrire pour simuler un bus SPI sur les broches 19, 21, 23, 24 et 26 :
|
||||
|
||||
```c
|
||||
/dts-v1/;
|
||||
/plugin/;
|
||||
|
||||
#include <dt-bindings/gpio/gpio.h>
|
||||
|
||||
spi_gpio {
|
||||
compatible = "spi-gpio";
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
gpio-sck = <&gpf 15 GPIO_ACTIVE_HIGH>; /* PE15 */
|
||||
gpio-miso = <&gpf 14 GPIO_ACTIVE_HIGH>; /* PE14 */
|
||||
gpio-mosi = <&gpf 17 GPIO_ACTIVE_HIGH>; /* PE17 */
|
||||
num-chipselects = <2>;
|
||||
cs-gpios = <&gpf 16 GPIO_ACTIVE_HIGH &gpf 18 GPIO_ACTIVE_HIGH>; /* PE16, PE18 */
|
||||
|
||||
spidev@0 {
|
||||
compatible = "spidev";
|
||||
reg = <0>;
|
||||
spi-max-frequency = <1000000>;
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
On applique l'overlay au démarrage, comme vu précédemment.
|
||||
|
||||
Dans les deux cas, il est aussi possible de faire les modifications directement dans le fichier DTS original et de remplacer le DTB utiliser pour démarrer la carte.
|
||||
Il faudra alors aille à refaire cela à chaque mise à jour du noyau.
|
||||
|
||||
|
||||
## Configuration des GPIO dédiés
|
||||
|
||||
On avait vu que l'écran utilisait 3 GPIO en plus du bus SPI.
|
||||
|
||||
Dans mon cas, j'ai choisi d'utiliser les broches 18 (`PF2`), 11 (`PD26`) et 22 (`PE8`).
|
||||
Déclarons-les avec :
|
||||
|
||||
```sh
|
||||
# BUSY: PF2
|
||||
echo 162 > /sys/class/gpio/export
|
||||
|
||||
# RST: PD26
|
||||
echo 122 > /sys/class/gpio/export
|
||||
echo out > /sys/class/gpio/gpio122/direction
|
||||
|
||||
# DC: PE8
|
||||
echo 136 > /sys/class/gpio/export
|
||||
echo out > /sys/class/gpio/gpio136/direction
|
||||
```
|
||||
|
||||
|
||||
## Adapter le code de la démo
|
||||
|
||||
La démo de Waveshare s'attend à être exécutée sur un Raspberry Pi et on retrouve donc un certain nombre d'éléments écrit en dur.
|
||||
De plus, elle fait usage d'une dépendance Python qui ne fonctionne pas en dehors de cette plateforme.
|
||||
|
||||
Voici [l'implémentation](https://git.nemunai.re/nemunaire/waveshareteam-e-Paper/commit/dfa74b004198794d01bdc461ea88d1cf9cdb80b5) que j'ai effectué afin d'adapter le code à mon usage :
|
||||
|
||||
```diff
|
||||
--- a/RaspberryPi_JetsonNano/python/lib/waveshare_epd/epdconfig.py
|
||||
+++ b/RaspberryPi_JetsonNano/python/lib/waveshare_epd/epdconfig.py
|
||||
@@ -230,6 +230,89 @@ class SunriseX3:
|
||||
self.GPIO.cleanup([self.RST_PIN, self.DC_PIN, self.CS_PIN, self.BUSY_PIN], self.PWR_PIN)
|
||||
|
||||
|
||||
+class CreatorCI20:
|
||||
+ # Pin definition
|
||||
+ RST_PIN = 122
|
||||
+ DC_PIN = 136
|
||||
+ CS_PIN = 0
|
||||
+ BUSY_PIN = 162
|
||||
+ PWR_PIN = 18
|
||||
+ Flag = 0
|
||||
+
|
||||
+ def __init__(self):
|
||||
+ import spidev
|
||||
+
|
||||
+ self.SPI = spidev.SpiDev()
|
||||
+
|
||||
+ def digital_write(self, pin, value):
|
||||
+ if pin == 0:
|
||||
+ return
|
||||
+
|
||||
+ with open("/sys/class/gpio/gpio"+str(pin)+"/value", "w") as f:
|
||||
+ f.write(str(value))
|
||||
+
|
||||
+ def digital_read(self, pin):
|
||||
+ v = "0"
|
||||
+ if pin != 0:
|
||||
+ with open("/sys/class/gpio/gpio"+str(pin)+"/value") as f:
|
||||
+ v = f.readline()
|
||||
+ return int(v)
|
||||
+
|
||||
+ def delay_ms(self, delaytime):
|
||||
+ time.sleep(delaytime / 1000.0)
|
||||
+
|
||||
+ def spi_writebyte(self, data):
|
||||
+ self.SPI.writebytes(data)
|
||||
+
|
||||
+ def spi_writebyte2(self, data):
|
||||
+ # for i in range(len(data)):
|
||||
+ # self.SPI.writebytes([data[i]])
|
||||
+ self.SPI.xfer3(data)
|
||||
+
|
||||
+ def module_init(self):
|
||||
+ if self.Flag == 0:
|
||||
+ self.Flag = 1
|
||||
+
|
||||
+ # BUSY
|
||||
+ with open("/sys/class/gpio/export", "w") as f:
|
||||
+ f.write(str(self.BUSY_PIN))
|
||||
+
|
||||
+ # RST
|
||||
+ with open("/sys/class/gpio/export", "w") as f:
|
||||
+ f.write(str(self.RST_PIN))
|
||||
+ with open("/sys/class/gpio/gpio" + str(self.RST_PIN) + "/direction", "w") as f:
|
||||
+ f.write("out")
|
||||
+
|
||||
+ # DC
|
||||
+ with open("/sys/class/gpio/export", "w") as f:
|
||||
+ f.write(str(self.DC_PIN))
|
||||
+ with open("/sys/class/gpio/gpio" + str(self.DC_PIN) + "/direction", "w") as f:
|
||||
+ f.write("out")
|
||||
+
|
||||
+ # SPI device, bus = 0, device = 0
|
||||
+ self.SPI.open(32766, 0)
|
||||
+ self.SPI.max_speed_hz = 4000000
|
||||
+ self.SPI.mode = 0b00
|
||||
+ return 0
|
||||
+ else:
|
||||
+ return 0
|
||||
+
|
||||
+ def module_exit(self):
|
||||
+ logger.debug("spi end")
|
||||
+ self.SPI.close()
|
||||
+
|
||||
+ logger.debug("close 5V, Module enters 0 power consumption ...")
|
||||
+ self.Flag = 0
|
||||
+
|
||||
+ self.digital_write(self.RST_PIN, 0)
|
||||
+ self.digital_write(self.DC_PIN, 0)
|
||||
+
|
||||
+ # Clean up
|
||||
+ for pin in [self.BUSY_PIN, self.RST_PIN, self.DC_PIN]
|
||||
+ with open("/sys/class/gpio/unexport", "w") as f:
|
||||
+ f.write(str(pin))
|
||||
+
|
||||
+
|
||||
if os.path.exists('/sys/bus/platform/drivers/gpiomem-bcm2835'):
|
||||
implementation = RaspberryPi()
|
||||
elif os.path.exists('/sys/bus/platform/drivers/gpio-x3'):
|
||||
```
|
||||
|
||||
## Conclusion
|
||||
|
||||
On ne peut pas s'attendre à ce qu'un constructeur passe du temps à documenter l'usage de son périphérique pour toutes les plateformes existantes.
|
||||
On remerciera Waveshare pour avoir [mis en ligne des spécifications très détaillées](https://www.waveshare.com/w/upload/6/60/7.5inch_e-Paper_V2_Specification.pdf) qui permettent aux utilisateurs avancés de comprendre le fonctionnement de l'écran et la manière de communiquer avec lui.
|
Loading…
Reference in New Issue
Block a user