Bonjour,

À défaut de trouver un forum qui corresponde vraiment à la classification de mon problème (qui n'est pas lié à un langage ou à une plateforme), je le place ici...


Je dois gérer un afficheur LCD 1602 avec contrôleur Hitachi HD44780. Je l'avais fait sans aucun problème dans un ancien projet en Java via un adaptateur USB->UART car une bibliothèque adéquate existait pour cela.
Mais là je dois tout faire par moi-même parce que je passe directement par les ports I²C d'un Raspberry Pi en python.

Mon code fonctionne, mais le rafraîchissement est très lent... Les caractères d'une ligne s'affichent l'un après l'autre de manière visible. À la limite, cela ne dérange pas outre mesure pour l'affichage d'une simple ligne, mais dès lors qu'il faut faire défiler un texte trop long pour la ligne cela n'est tout simplement pas acceptable.

Peut-être que l'un d'entre vous aura un solution à me proposer (bibliothèque I²C différente, commande non nécessaires au contrôleur à supprimer, ...).

Voici mon code (basé sur un exemple trouvé au détour d'un forum) :

Code i2c_lib.py : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#!/usr/bin/python3
# -*-coding:Utf-8 -*

import smbus


class I2CDevice:
    """Classe représentant un périphérique connecté au bus I2C."""

    def __init__(self, addr, port=1):
        """Crée un objet I2CDevice."""

        self.addr = addr
        self.bus = smbus.SMBus(port)

    def write_cmd(self, cmd):
        """Ecrit une commande simple."""

        self.bus.write_byte(self.addr, cmd)

    def write_cmd_arg(self, cmd, data):
        """Ecrit une commande paramétrée."""

        self.bus.write_byte_data(self.addr, cmd, data)

    def write_block_data(self, cmd, data):
        """Ecrit un bloc de données."""

        self.bus.write_block_data(self.addr, cmd, data)

    def read(self):
        """Lit un byte."""

        return self.bus.read_byte(self.addr)

    def read_data(self, cmd):
        """Lit les données."""

        return self.bus.read_byte_data(self.addr, cmd)

    def read_block_data(self, cmd):
        """Lit un bloc de données."""
        return self.bus.read_block_data(self.addr, cmd)

Code lcd_driver.py : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
#!/usr/bin/python3
# -*-coding:Utf-8 -*

import threading
from random import randrange
from time import sleep
from time import time
from unicodedata import normalize

import tymsoft.lcd.i2c_lib as i2c_lib
from tymsoft.interruptable import Interruptable

# commands
LCD_CLEARDISPLAY = 0x01
LCD_RETURNHOME = 0x02
LCD_ENTRYMODESET = 0x04
LCD_DISPLAYCONTROL = 0x08
LCD_CURSORSHIFT = 0x10
LCD_FUNCTIONSET = 0x20
LCD_SETCGRAMADDR = 0x40
LCD_SETDDRAMADDR = 0x80

# flags for display entry mode
LCD_ENTRYRIGHT = 0x00
LCD_ENTRYLEFT = 0x02
LCD_ENTRYSHIFTINCREMENT = 0x01
LCD_ENTRYSHIFTDECREMENT = 0x00

# flags for display on/off control
LCD_DISPLAYON = 0x04
LCD_DISPLAYOFF = 0x00
LCD_CURSORON = 0x02
LCD_CURSOROFF = 0x00
LCD_BLINKON = 0x01
LCD_BLINKOFF = 0x00

# flags for display/cursor shift
LCD_DISPLAYMOVE = 0x08
LCD_CURSORMOVE = 0x00
LCD_MOVERIGHT = 0x04
LCD_MOVELEFT = 0x00

# flags for function set
LCD_8BITMODE = 0x10
LCD_4BITMODE = 0x00
LCD_2LINE = 0x08
LCD_1LINE = 0x00
LCD_5x10DOTS = 0x04
LCD_5x8DOTS = 0x00

# flags for backlight control
LCD_BACKLIGHT = 0x08
LCD_NOBACKLIGHT = 0x00

En = 0b00000100  # Enable bit
Rw = 0b00000010  # Read/Write bit
Rs = 0b00000001  # Register select bit


class LCD(Interruptable):
    """Classe permettant d'utiliser des afficheurs LCD de type 1602, 1604, 2002, 2004 contrôlés par une puce
    de type Hitachi HD44780.
    """

    def __init__(self, i2c_address=0x27, height=2, width=16):
        """Crée un objet permettant l'initialisation et l'utilisation d'un afficheur LCD."""

        self.address = i2c_address
        self.height = height
        self.width = width
        self.device = i2c_lib.I2CDevice(self.address)
        self.lock = threading.RLock()
        self.last_ticket = 0.

        self.write(0x03)
        self.write(0x03)
        self.write(0x03)
        self.write(0x02)

        self.write(LCD_FUNCTIONSET | LCD_2LINE | LCD_5x8DOTS | LCD_4BITMODE)
        self.write(LCD_DISPLAYCONTROL | LCD_DISPLAYON)
        self.write(LCD_CLEARDISPLAY)
        self.write(LCD_ENTRYMODESET | LCD_ENTRYLEFT)
        sleep(0.2)
        self.backlight_on(False)

        Interruptable.__init__(self)

    def take_a_ticket(self):
        """Crée un nombre unique et le stock dans l'objet comme étant l'identifiant du dernier ordre envoyé."""

        new_ticket = time()
        new_ticket += randrange(0, 9999) / 10000
        self.last_ticket = new_ticket
        return new_ticket

    def strobe(self, data):
        """Envoi une impulsion EN pour relâcher une commande."""

        self.device.write_cmd(data | En | LCD_BACKLIGHT)
        sleep(.0005)
        self.device.write_cmd(((data & ~En) | LCD_BACKLIGHT))
        sleep(.0001)

    def write_four_bits(self, data):
        self.device.write_cmd(data | LCD_BACKLIGHT)
        self.strobe(data)

    def write(self, cmd, mode=0):
        """Envoi une commande à l'afficheur LCD."""

        self.write_four_bits(mode | (cmd & 0xF0))
        self.write_four_bits(mode | ((cmd << 4) & 0xF0))

    def display_string(self, string, line=1):
        """Affiche une chaîne de caractères sur une ligne de l'afficheur."""

        if line not in range(1, self.height):
            line = 1

        def run():
            norm_string = normalize('NFKD', string).encode('ascii', 'ignore').decode("utf-8")
            with self.lock:
                if line == 1:
                    self.write(0x80)
                elif line == 2:
                    self.write(0xC0)
                elif line == 3:
                    self.write(0x94)
                elif line == 4:
                    self.write(0xD4)
                for char in norm_string:
                    self.write(ord(char), Rs)
        thread = threading.Thread(target=run)
        thread.start()

    def display_scrolling_string(self, string, line=1, pause=0.25):
        """Affiche une chaîne de caractères sur une ligne de l'afficheur.
        Ce texte défilera s'il est plus long que la largeur de l'afficheur.
        """

        if len(string) <= self.width:
            self.display_string(string, line)
        else:
            if line not in range(1, self.height):
                line = 1
            ticket = self.take_a_ticket()

            def run():
                norm_string = normalize('NFKD', string).encode('ascii', 'ignore').decode("utf-8")
                offset = 0
                while ticket == self.last_ticket and not self.interrupted():
                    substring = norm_string[offset:offset + self.width]
                    with self.lock:
                        if line == 1:
                            self.write(0x80)
                        elif line == 2:
                            self.write(0xC0)
                        elif line == 3:
                            self.write(0x94)
                        elif line == 4:
                            self.write(0xD4)
                        for char in substring:
                            self.write(ord(char), Rs)
                        offset += 1
                        offset %= len(norm_string) - self.width + 1
                    sleep(pause)

            thread = threading.Thread(target=run)
            thread.start()

    def clear(self):
        """Efface le contenu de l'afficheur et ramène le curseur à l'origine."""

        with self.lock:
            self.write(LCD_CLEARDISPLAY)
            self.write(LCD_RETURNHOME)

    def backlight_on(self, state=True):
        """Allume ou éteint le rétro-éclairage."""

        with self.lock:
            if state:
                self.device.write_cmd(LCD_BACKLIGHT)
            else:
                self.device.write_cmd(LCD_NOBACKLIGHT)

    def load_custom_chars(self, custom_charset):
        """Charge un jeu de caractères spéciaux dans la CGRAM du contrôleur."""

        with self.lock:
            self.write(LCD_SETCGRAMADDR)
            for char in custom_charset:
                self.write(char, Rs)