36
36
SSD1306_SETCOMPINS = 0xDA
37
37
SSD1306_SETVCOMDETECT = 0xDB
38
38
39
+
40
+ class SSD1306_Wrapper (Elaboratable ):
41
+ def __init__ (self ):
42
+ self .sink = stream .Endpoint ([
43
+ ("d_cn" , 1 ),
44
+ ("data" , 8 ),
45
+ ])
46
+ self .source = stream .Endpoint ([
47
+ ("r_wn" , 1 ),
48
+ ("data" , 8 ),
49
+ ])
50
+
51
+ def elaborate (self , platform ):
52
+ sink = self .sink
53
+ source = self .source
54
+
55
+ m = Module ()
56
+
57
+ m .d .comb += source .r_wn .eq (0 ) # Write only
58
+
59
+ with m .FSM ():
60
+ # Send the I2C address and control byte
61
+ with m .State ("ADDR" ):
62
+ m .d .comb += [
63
+ source .data .eq (SSD1306_I2C_ADDRESS << 1 ),
64
+ source .valid .eq (sink .valid ),
65
+ ]
66
+ with m .If (source .valid & source .ready ):
67
+ m .next = "CONTROL"
68
+
69
+ with m .State ("CONTROL" ):
70
+ m .d .comb += [
71
+ # Control byte:
72
+ # Command: 0x00: Co = 0, D/C# = 0
73
+ # Data: 0x40: Co = 0, D/C# = 1
74
+ source .data .eq (Mux (sink .d_cn , 0x40 , 0x00 )),
75
+ source .valid .eq (sink .valid ),
76
+ ]
77
+ with m .If (source .valid & source .ready ):
78
+ m .next = "DATA"
79
+
80
+ with m .State ("DATA" ):
81
+ m .d .comb += [
82
+ source .data . eq (sink .data ),
83
+ source .valid .eq (sink .valid ),
84
+ source .last .eq (sink .last ),
85
+ sink .ready .eq (source .ready ),
86
+ ]
87
+ with m .If (source .valid & source .ready & source .last ):
88
+ m .next = "ADDR"
89
+
90
+ return m
91
+
92
+
39
93
class SSD1306 (Elaboratable ):
40
94
""" Driver for SSD1306 based LCD screen.
41
95
@@ -48,19 +102,14 @@ class SSD1306(Elaboratable):
48
102
The screen width in pixels.
49
103
height : int
50
104
The screen height in pixels.
51
- burst_len : int
52
- Specify the maximum amount of framebuffer bytes that can be
53
- sent at a time before closing the I2C transaction.
54
- 0 means unlimited.
55
105
por_init : bool
56
106
When True, the screen is automatically initialized upon power on reset.
57
107
when False, the user need to assert `reset` for one clock cycle.
58
108
"""
59
109
60
- def __init__ (self , width , height , burst_len = 0 , por_init = True ):
110
+ def __init__ (self , width , height , por_init = True ):
61
111
self .width = width
62
112
self .height = height
63
- self .burst_len = burst_len
64
113
self .por_init = por_init
65
114
66
115
# Table from https://github.com/rm-hull/luma.oled/blob/main/luma/oled/device/__init__.py
@@ -80,9 +129,6 @@ def __init__(self, width, height, burst_len=0, por_init=True):
80
129
self ._colstart = settings ["colstart" ]
81
130
self ._colend = self ._colstart + width
82
131
83
- if self .burst_len == 0 :
84
- self .burst_len = self ._size
85
-
86
132
self .reset = Signal ()
87
133
self .ready = Signal ()
88
134
@@ -96,16 +142,6 @@ def __init__(self, width, height, burst_len=0, por_init=True):
96
142
])
97
143
self .error = Signal ()
98
144
99
- def cmds_to_mem (self , cmds ):
100
- mem = []
101
-
102
- for cmd in cmds :
103
- mem .append (SSD1306_I2C_ADDRESS << 1 ) # Write
104
- mem .append (0x00 ) # Co = 0, D/C# = 0
105
- mem .append (cmd )
106
-
107
- return mem
108
-
109
145
def elaborate (self , platform ):
110
146
sink = self .sink
111
147
source = self .source
@@ -140,10 +176,7 @@ def elaborate(self, platform):
140
176
SSD1306_NORMALDISPLAY ,
141
177
SSD1306_DISPLAYON ,
142
178
]
143
- blob = self .cmds_to_mem (cmds )
144
-
145
- m .submodules .init = init = \
146
- LastInserter (3 )(MemoryStreamReader (8 , blob ))
179
+ m .submodules .init = init = MemoryStreamReader (8 , cmds )
147
180
148
181
# Recipe for sending a framebuffer
149
182
cmds = [
@@ -154,12 +187,13 @@ def elaborate(self, platform):
154
187
0 , # Page start address. (0 = reset)
155
188
self ._pages - 1 , # Page end address.
156
189
]
157
- blob = self . cmds_to_mem ( cmds )
190
+ m . submodules . display = display = MemoryStreamReader ( 8 , cmds )
158
191
159
- m .submodules .display = display = \
160
- LastInserter (3 )(MemoryStreamReader (8 , blob ))
192
+ # Instanciate the I2C address and control byte wrapper
193
+ m .submodules .wrapper = wrapper = SSD1306_Wrapper ()
194
+ m .d .comb += wrapper .source .connect (source )
161
195
162
- cnt = Signal (range (self .burst_len + 2 ))
196
+ cnt = Signal (range (self ._size ))
163
197
164
198
with m .FSM ():
165
199
with m .State ("UNKNOWN" ):
@@ -178,10 +212,11 @@ def elaborate(self, platform):
178
212
# Send the appropriate sequence to power on
179
213
# and initialize the display.
180
214
m .d .comb += [
181
- init .source .connect (source ),
182
- source .r_wn .eq (0 ), # Write only
215
+ init .source .connect (wrapper .sink , exclude = {"last" }),
216
+ wrapper .sink .d_cn .eq (0 ), # Commands
217
+ wrapper .sink .last .eq (1 ),
183
218
]
184
- with m .If (init .done & ~ source .valid ):
219
+ with m .If (init .done & ~ init . source .valid ):
185
220
m .next = "DISPLAY"
186
221
187
222
with m .State ("DISPLAY" ):
@@ -190,56 +225,44 @@ def elaborate(self, platform):
190
225
191
226
# Send the appropriate sequence to prepare
192
227
# for a frame buffer write.
193
- with m .Elif (sink . valid ): # ~ sink.ready
228
+ with m .Elif (~ self . ready | sink .valid ):
194
229
m .d .comb += [
195
- display .source .connect (source ),
196
- source .r_wn .eq (0 ), # Write only
230
+ display .source .connect (wrapper .sink , exclude = {"last" }),
231
+ wrapper .sink .d_cn .eq (0 ), # Commands
232
+ wrapper .sink .last .eq (1 ),
197
233
]
198
- with m .If (display .done & ~ source .valid ):
234
+ with m .If (display .done & ~ display . source .valid ):
199
235
# On the first time after initialization
200
236
# we want to clear the frame buffer to make
201
237
# sure we do not display crap.
202
- # with m.If(~self.ready):
203
- # m.next = "CLEAR"
204
- # with m.Else():
205
- m .next = "FRAMEBUFFER"
238
+ with m .If (~ self .ready ):
239
+ m .next = "CLEAR"
240
+ with m .Else ():
241
+ m .next = "FRAMEBUFFER"
206
242
207
- with m .State ("FRAMEBUFFER " ):
243
+ with m .State ("CLEAR " ):
208
244
m .d .comb += [
209
- source .r_wn .eq (0 ),
210
- source .last .eq ((cnt == self .burst_len + 2 - 1 ) | sink .last ),
245
+ wrapper .sink .valid .eq (1 ),
246
+ wrapper .sink .data .eq (0 ), # Black pixels
247
+ wrapper .sink .last .eq (cnt == self ._size - 1 ),
248
+ wrapper .sink .d_cn .eq (1 ), # Framebuffer data
211
249
]
212
-
213
- # Send the I2C address and control byte,
214
- # then send the frame buffer data up to burst length.
215
- with m .If (cnt == 0 ):
216
- m .d .comb += [
217
- source .data .eq (SSD1306_I2C_ADDRESS << 1 ),
218
- source .valid .eq (1 ),
219
- ]
220
- with m .Elif (cnt == 1 ):
221
- m .d .comb += [
222
- source .data .eq (0x40 ), # Control byte: Co = 0, D/C# = 1
223
- source .valid .eq (1 ),
224
- ]
225
- with m .Else ():
226
- m .d .comb += [
227
- source .data .eq (sink .data ),
228
- source .valid .eq (sink .valid ),
229
- sink .ready .eq (source .ready ),
230
- ]
231
-
232
- # End of burst detection
233
- # Reset the counter and stay in this state
234
- # to send the next burst, or go back to the
235
- # DISPLAY state when the end of the framebuffer is reached.
236
- with m .If (source .valid & source .ready ):
237
- with m .If (~ source .last ):
250
+ with m .If (wrapper .sink .ready ):
251
+ with m .If (~ wrapper .sink .last ):
238
252
m .d .sync += cnt .eq (cnt + 1 )
239
253
with m .Else ():
240
254
m .d .sync += cnt .eq (0 )
241
- with m .If (sink .last ):
242
- m .d .comb += display .rewind .eq (1 )
243
- m .next = "DISPLAY"
255
+ m .d .sync += self .ready .eq (1 )
256
+ m .d .comb += display .rewind .eq (1 )
257
+ m .next = "DISPLAY"
258
+
259
+ with m .State ("FRAMEBUFFER" ):
260
+ m .d .comb += [
261
+ sink .connect (wrapper .sink ),
262
+ wrapper .sink .d_cn .eq (1 ),
263
+ ]
264
+ with m .If (sink .valid & sink .ready & sink .last ):
265
+ m .d .comb += display .rewind .eq (1 )
266
+ m .next = "DISPLAY"
244
267
245
268
return m
0 commit comments