3636SSD1306_SETCOMPINS = 0xDA
3737SSD1306_SETVCOMDETECT = 0xDB
3838
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+
3993class SSD1306 (Elaboratable ):
4094 """ Driver for SSD1306 based LCD screen.
4195
@@ -48,19 +102,14 @@ class SSD1306(Elaboratable):
48102 The screen width in pixels.
49103 height : int
50104 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.
55105 por_init : bool
56106 When True, the screen is automatically initialized upon power on reset.
57107 when False, the user need to assert `reset` for one clock cycle.
58108 """
59109
60- def __init__ (self , width , height , burst_len = 0 , por_init = True ):
110+ def __init__ (self , width , height , por_init = True ):
61111 self .width = width
62112 self .height = height
63- self .burst_len = burst_len
64113 self .por_init = por_init
65114
66115 # 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):
80129 self ._colstart = settings ["colstart" ]
81130 self ._colend = self ._colstart + width
82131
83- if self .burst_len == 0 :
84- self .burst_len = self ._size
85-
86132 self .reset = Signal ()
87133 self .ready = Signal ()
88134
@@ -96,16 +142,6 @@ def __init__(self, width, height, burst_len=0, por_init=True):
96142 ])
97143 self .error = Signal ()
98144
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-
109145 def elaborate (self , platform ):
110146 sink = self .sink
111147 source = self .source
@@ -140,10 +176,7 @@ def elaborate(self, platform):
140176 SSD1306_NORMALDISPLAY ,
141177 SSD1306_DISPLAYON ,
142178 ]
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 )
147180
148181 # Recipe for sending a framebuffer
149182 cmds = [
@@ -154,12 +187,13 @@ def elaborate(self, platform):
154187 0 , # Page start address. (0 = reset)
155188 self ._pages - 1 , # Page end address.
156189 ]
157- blob = self . cmds_to_mem ( cmds )
190+ m . submodules . display = display = MemoryStreamReader ( 8 , cmds )
158191
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 )
161195
162- cnt = Signal (range (self .burst_len + 2 ))
196+ cnt = Signal (range (self ._size ))
163197
164198 with m .FSM ():
165199 with m .State ("UNKNOWN" ):
@@ -178,10 +212,11 @@ def elaborate(self, platform):
178212 # Send the appropriate sequence to power on
179213 # and initialize the display.
180214 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 ),
183218 ]
184- with m .If (init .done & ~ source .valid ):
219+ with m .If (init .done & ~ init . source .valid ):
185220 m .next = "DISPLAY"
186221
187222 with m .State ("DISPLAY" ):
@@ -190,56 +225,44 @@ def elaborate(self, platform):
190225
191226 # Send the appropriate sequence to prepare
192227 # for a frame buffer write.
193- with m .Elif (sink . valid ): # ~ sink.ready
228+ with m .Elif (~ self . ready | sink .valid ):
194229 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 ),
197233 ]
198- with m .If (display .done & ~ source .valid ):
234+ with m .If (display .done & ~ display . source .valid ):
199235 # On the first time after initialization
200236 # we want to clear the frame buffer to make
201237 # 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"
206242
207- with m .State ("FRAMEBUFFER " ):
243+ with m .State ("CLEAR " ):
208244 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
211249 ]
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 ):
238252 m .d .sync += cnt .eq (cnt + 1 )
239253 with m .Else ():
240254 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"
244267
245268 return m
0 commit comments