Skip to content

feat: implement SPIBusFast driver with 90° rotation for AXS15231B QSPI#451

Open
straga wants to merge 3 commits intolvgl-micropython:mainfrom
straga:feature/spi_bus_fast
Open

feat: implement SPIBusFast driver with 90° rotation for AXS15231B QSPI#451
straga wants to merge 3 commits intolvgl-micropython:mainfrom
straga:feature/spi_bus_fast

Conversation

@straga
Copy link
Collaborator

@straga straga commented Sep 9, 2025

  • Add SPIBusFast bus driver with partial updates support
  • Enable LVGL partial mode instead of full frame updates
  • Implement 90° rotation with pixel coordinate transformation
  • Add RTOS task for double buffering
  • Support QSPI 4-wire interface for AXS15231B controller

New SPI python driver:
https://github.com/straga/micropython_lcd/tree/master/device/JC3248W535/new_SPI

  - Add SPIBusFast bus driver with partial updates support
  - Enable LVGL partial mode instead of full frame updates
  - Implement 90° rotation with pixel coordinate transformation
  - Add RTOS task for double buffering
  - Support QSPI 4-wire interface for AXS15231B controller
@kdschlosser
Copy link
Collaborator

I really like that you took the initiative with this.. I am sure it has given you a much better understanding of how the software rotation works and how to manage keeping the tasks in synchronization with each other to keep from corrupting the buffer data...

There is always a but... so here it is....

I would really like to provide the software rotation across all of the different available busses. The hitch is adding it without adding new bus classes to facilitate it and to only use a single set of rotation functions and task codes for all of the busses.

would you be willing to assist me with doing this? I have started doing on it several different occasions but I ended up getting interrupted with something else and didn't finish or I thought of a better way to go about it...

an additional argument would need to be passed to the init function of the bus drivers that would signal that software rotation needs to be used. that would then cause the bus driver to do the rotation. I want to move all IO related operations over to the other core so all bus types would need to run a task where the buffer gets passed over to that task to be sent to the display. that flag that gets passed to the init function would be for determining whether or not the buffer needs to get rotated while being copied to an intermediate buffer that is used for transmit. We would need something that would instruct the bus driver on how large to allocate that destination frame buffer.

@straga
Copy link
Collaborator Author

straga commented Sep 10, 2025

The approach you outlined makes perfect sense - using a single set of rotation functions and moving all IO operations to the other core will be much cleaner than having separate bus classes.

@kdschlosser
Copy link
Collaborator

It's a big overhaul of the code to make it work. If I had some help with it that would help out greatly. I started to do it on more than one occasion and ended up scraping it because I ended up making it overly complicated. I want to keep the code as simple as possible and that's the hard part. Maintainability needs to be high on the priority list.

@kdschlosser
Copy link
Collaborator

I didn't dive too deep into the code you wrote in this PR. Does it work like the RGB driver where you now have the ability to set partial buffers with LVGL or does it still need to render full framebuffers?

@straga
Copy link
Collaborator Author

straga commented Sep 14, 2025

@kdschlosser: yes, I use RGB as reference.

@de-dh
Copy link

de-dh commented Sep 25, 2025

I was able to compile a firmware with the new SPI driver and the 90° rotation works! Thank you both for your work.
I case anyone needs the firmware: ESP32-JC3248W535-Micropython-LVGL

Copy link
Collaborator

@kdschlosser kdschlosser left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check out the couple of comments I made.

}
// Do NOT initialize to zero - leave uninitialized for performance

self->panel_io_config.trans_queue_depth = 10;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This may not be a large enough queue size. You need to double check to see how many transactions are getting created when you iterate over the full frame buffer

self,
ESP_TASK_PRIO_MAX - 1,
&self->copy_task_handle,
tskNO_AFFINITY // Do not pin to specific CPU core
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't want to use tskno_AFFINITY here because of how FreeRTOS handles assigning the core the task runs on. Once this decision gets made the task never changes cores because it never stop running. If FreeRTOS determines that the main core has less load then it will assign the task to the same core that MicroPython and LVGL is running on which defeats the whole purpose of having the rotation task running. If the task is running on the same core as LVGL the rotation and transmit end up being blocking calls.

// Allocate two full frame buffers for double buffering
uint32_t fb_size = width * height * self->bytes_per_pixel;

self->active_fb = (uint8_t*)heap_caps_malloc(fb_size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The full buffers need to be allocated in DMA memory otherwise the transmit of the buffer ends up being a blocking call. We need it to not be blocking this way the CPU is able to copy the data from the transmitting buffer to the idle buffer to keep them in sync properly. This allows 2 things to happen at once. Without using DMA memory not only does the task have to wait until the transmit finishes it then has to copy the buffer data afterwards. This length of time will end up being over 20 milliseconds which can cause the main core to have to sit and wat because the likely hood of LVGL rendering to both partial buffers in that time is highly probable.

When you look at the RGB bus code it's a bit confusing with respect to this. When that driver sits and waits at the event that is signaled from the vsync callback it is not waiting for the buffer that was just passed to be transmitted. It is waiting to know when the previous buffer has finished transmitting so that way it knows it is able to write data to it without causing corruption.

offset += current_chunk;
remaining -= current_chunk;
chunk_count++;
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code above is the reason why I did not merge this PR. The code is specific to a display IC and it would not be able to be used with any other display IC.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants