@@ -217,7 +217,7 @@ static void _send_pixels(busdisplay_busdisplay_obj_t *self, uint8_t *pixels, uin
217217}
218218
219219static bool _refresh_area (busdisplay_busdisplay_obj_t * self , const displayio_area_t * area ) {
220- uint16_t buffer_size = 128 ; // In uint32_ts
220+ uint32_t buffer_size = 128 ; // In uint32_ts
221221
222222 displayio_area_t clipped ;
223223 // Clip the area to the display by overlapping the areas. If there is no overlap then we're done.
@@ -226,7 +226,7 @@ static bool _refresh_area(busdisplay_busdisplay_obj_t *self, const displayio_are
226226 }
227227 uint16_t rows_per_buffer = displayio_area_height (& clipped );
228228 uint8_t pixels_per_word = (sizeof (uint32_t ) * 8 ) / self -> core .colorspace .depth ;
229- uint16_t pixels_per_buffer = displayio_area_size (& clipped );
229+ uint32_t pixels_per_buffer = displayio_area_size (& clipped );
230230
231231 uint16_t subrectangles = 1 ;
232232 // for SH1107 and other boundary constrained controllers
@@ -265,7 +265,8 @@ static bool _refresh_area(busdisplay_busdisplay_obj_t *self, const displayio_are
265265 // upper bound for ESP32-S3's 24KB main task stack.
266266 _Static_assert (CIRCUITPY_QSPI_DISPLAY_AREA_BUFFER_SIZE <= 2048 ,
267267 "CIRCUITPY_QSPI_DISPLAY_AREA_BUFFER_SIZE exceeds safe stack limit (8KB)" );
268- if (mp_obj_is_type (self -> bus .bus , & qspibus_qspibus_type ) &&
268+ bool is_qspi_bus = mp_obj_is_type (self -> bus .bus , & qspibus_qspibus_type );
269+ if (is_qspi_bus &&
269270 self -> core .colorspace .depth == 16 &&
270271 !self -> bus .data_as_commands &&
271272 !self -> bus .SH1107_addressing &&
@@ -275,6 +276,10 @@ static bool _refresh_area(busdisplay_busdisplay_obj_t *self, const displayio_are
275276 if (rows_per_buffer == 0 ) {
276277 rows_per_buffer = 1 ;
277278 }
279+ // Clamp to actual display height.
280+ if (rows_per_buffer > displayio_area_height (& clipped )) {
281+ rows_per_buffer = displayio_area_height (& clipped );
282+ }
278283 subrectangles = displayio_area_height (& clipped ) / rows_per_buffer ;
279284 if (displayio_area_height (& clipped ) % rows_per_buffer != 0 ) {
280285 subrectangles ++ ;
@@ -284,32 +289,31 @@ static bool _refresh_area(busdisplay_busdisplay_obj_t *self, const displayio_are
284289 if (pixels_per_buffer % pixels_per_word ) {
285290 buffer_size += 1 ;
286291 }
287- }
288292
289- if (mp_obj_is_type (self -> bus .bus , & qspibus_qspibus_type ) &&
290- self -> core .colorspace .depth == 16 &&
291- !self -> bus .data_as_commands &&
292- displayio_area_height (& clipped ) > 1 &&
293- rows_per_buffer < 2 &&
294- (2 * displayio_area_width (& clipped ) + pixels_per_word - 1 ) / pixels_per_word <= CIRCUITPY_QSPI_DISPLAY_AREA_BUFFER_SIZE ) {
295- rows_per_buffer = 2 ;
296- subrectangles = displayio_area_height (& clipped ) / rows_per_buffer ;
297- if (displayio_area_height (& clipped ) % rows_per_buffer != 0 ) {
298- subrectangles ++ ;
299- }
300- pixels_per_buffer = rows_per_buffer * displayio_area_width (& clipped );
301- buffer_size = pixels_per_buffer / pixels_per_word ;
302- if (pixels_per_buffer % pixels_per_word ) {
303- buffer_size += 1 ;
293+ // Ensure at least 2 rows per buffer when possible.
294+ if (rows_per_buffer < 2 &&
295+ displayio_area_height (& clipped ) > 1 &&
296+ (uint32_t )((2 * displayio_area_width (& clipped ) + pixels_per_word - 1 ) / pixels_per_word ) <= (uint32_t )CIRCUITPY_QSPI_DISPLAY_AREA_BUFFER_SIZE ) {
297+ rows_per_buffer = 2 ;
298+ subrectangles = displayio_area_height (& clipped ) / rows_per_buffer ;
299+ if (displayio_area_height (& clipped ) % rows_per_buffer != 0 ) {
300+ subrectangles ++ ;
301+ }
302+ pixels_per_buffer = rows_per_buffer * displayio_area_width (& clipped );
303+ buffer_size = pixels_per_buffer / pixels_per_word ;
304+ if (pixels_per_buffer % pixels_per_word ) {
305+ buffer_size += 1 ;
306+ }
304307 }
305308 }
306309 #endif
307310
308311 // Allocated and shared as a uint32_t array so the compiler knows the
309312 // alignment everywhere.
310- uint32_t buffer [buffer_size ];
311313 uint32_t mask_length = (pixels_per_buffer / 32 ) + 1 ;
314+ uint32_t buffer [buffer_size ];
312315 uint32_t mask [mask_length ];
316+
313317 uint16_t remaining_rows = displayio_area_height (& clipped );
314318
315319 for (uint16_t j = 0 ; j < subrectangles ; j ++ ) {
@@ -324,28 +328,55 @@ static bool _refresh_area(busdisplay_busdisplay_obj_t *self, const displayio_are
324328 }
325329 remaining_rows -= rows_per_buffer ;
326330
327- displayio_display_bus_set_region_to_update (& self -> bus , & self -> core , & subrectangle );
331+ #if CIRCUITPY_QSPIBUS
332+ if (is_qspi_bus ) {
333+ // QSPI path: fill_area first (overlaps with previous DMA),
334+ // then single-transaction set_region + RAMWR + pixels.
335+ // depth is always 16 here (guarded by is_qspi_bus check above).
336+ uint32_t subrectangle_size_bytes = (uint32_t )displayio_area_size (& subrectangle ) * (self -> core .colorspace .depth / 8 );
328337
329- uint16_t subrectangle_size_bytes ;
330- if (self -> core .colorspace .depth >= 8 ) {
331- subrectangle_size_bytes = displayio_area_size (& subrectangle ) * (self -> core .colorspace .depth / 8 );
332- } else {
333- subrectangle_size_bytes = displayio_area_size (& subrectangle ) / (8 / self -> core .colorspace .depth );
334- }
338+ memset (mask , 0 , mask_length * sizeof (mask [0 ]));
339+ memset (buffer , 0 , buffer_size * sizeof (buffer [0 ]));
340+
341+ displayio_display_core_fill_area (& self -> core , & subrectangle , mask , buffer );
335342
336- memset (mask , 0 , mask_length * sizeof (mask [0 ]));
337- memset (buffer , 0 , buffer_size * sizeof (buffer [0 ]));
343+ // begin_transaction waits for any prior async DMA to finish,
344+ // so fill_area above overlaps with previous DMA.
345+ if (!displayio_display_bus_begin_transaction (& self -> bus )) {
346+ // Transaction failed (bus deinitialized, timeout, etc.).
347+ // Bail out to prevent calling send() outside a transaction.
348+ return false;
349+ }
350+ displayio_display_bus_send_region_commands (& self -> bus , & self -> core , & subrectangle );
351+ _send_pixels (self , (uint8_t * )buffer , subrectangle_size_bytes );
352+ displayio_display_bus_end_transaction (& self -> bus );
353+ } else
354+ #endif
355+ {
356+ // Non-QSPI path: original ordering preserved exactly.
357+ displayio_display_bus_set_region_to_update (& self -> bus , & self -> core , & subrectangle );
358+
359+ uint16_t subrectangle_size_bytes ;
360+ if (self -> core .colorspace .depth >= 8 ) {
361+ subrectangle_size_bytes = displayio_area_size (& subrectangle ) * (self -> core .colorspace .depth / 8 );
362+ } else {
363+ subrectangle_size_bytes = displayio_area_size (& subrectangle ) / (8 / self -> core .colorspace .depth );
364+ }
338365
339- displayio_display_core_fill_area (& self -> core , & subrectangle , mask , buffer );
366+ memset (mask , 0 , mask_length * sizeof (mask [0 ]));
367+ memset (buffer , 0 , buffer_size * sizeof (buffer [0 ]));
340368
341- // Can't acquire display bus; skip the rest of the data.
342- if (!displayio_display_bus_is_free (& self -> bus )) {
343- return false;
344- }
369+ displayio_display_core_fill_area (& self -> core , & subrectangle , mask , buffer );
345370
346- displayio_display_bus_begin_transaction (& self -> bus );
347- _send_pixels (self , (uint8_t * )buffer , subrectangle_size_bytes );
348- displayio_display_bus_end_transaction (& self -> bus );
371+ // Can't acquire display bus; skip the rest of the data.
372+ if (!displayio_display_bus_is_free (& self -> bus )) {
373+ return false;
374+ }
375+
376+ displayio_display_bus_begin_transaction (& self -> bus );
377+ _send_pixels (self , (uint8_t * )buffer , subrectangle_size_bytes );
378+ displayio_display_bus_end_transaction (& self -> bus );
379+ }
349380
350381 // Run background tasks so they can run during an explicit refresh.
351382 // Auto-refresh won't run background tasks here because it is a background task itself.
@@ -356,6 +387,21 @@ static bool _refresh_area(busdisplay_busdisplay_obj_t *self, const displayio_are
356387 usb_background ();
357388 #endif
358389 }
390+
391+ #if CIRCUITPY_QSPIBUS
392+ if (is_qspi_bus ) {
393+ // Drain the last async DMA transfer before returning.
394+ // Within the loop, begin_transaction() waits for the PREVIOUS
395+ // subrectangle's DMA, enabling fill_area/DMA overlap. But the
396+ // LAST subrectangle's DMA is still in-flight when the loop ends.
397+ // Without this drain, bus_free() returns false on the next
398+ // refresh() call, causing it to be silently skipped.
399+ if (displayio_display_bus_begin_transaction (& self -> bus )) {
400+ displayio_display_bus_end_transaction (& self -> bus );
401+ }
402+ }
403+ #endif
404+
359405 return true;
360406}
361407
0 commit comments