2025-11-18 1:24 PM
I'm trying to drive a display controller and have found it trickier than I anticipated, and I hope you can kindly help.
My microcontroller is the STM32U545. My display controller is the UltraChip UC1628c_A, as part of a custom 160x160 monochrome LCD. Our display's only data interface is the S8 4-wire interface described on page 32 of that link. It's half-duplex SPI with an additional pin called Control/Data (CD), or sometimes Command/Data. Each byte of the message that we send is either Control or Data. We tell the controller which by driving the CD pin low for Control or high for Data during that byte (the controller reads that pin in the middle of the final bit).
Each message starts with one or more Command bytes, and most messages are followed by one or more Data bytes (up to 160 bytes in my product). I expect that the display controller requires these bytes be sent as one continuous SPI message, without stopping the clock or changing the chip-select (slave-select) line. And that is what I'm finding surprisingly difficult to do without resorting to blocking code, which would degrade the performance of other features I'm going to run on this MCU.
Earlier, I tried to achieve this by using the STM32U5’s linked-list DMA, just because its 1-step mode can generate an interrupt after every linked list item (LLI). I broke up my message so that each LLI was entirely either Control or Data. Then, in the interrupt, I would set the C/D pin to suit the next LLI before re-enabling the transmission. But being new to the STM32, I’m struggling to understand and control these rich but complex peripherals correctly, and I haven’t gotten my code to work after the first LLI. More importantly, the apparently necessary steps in the interrupt are more numerous than I expected, which makes me think that the interrupt routine won’t keep up with the SPI transmissions. Slowing the SPI baud rate could mitigate that, but it wouldn't be a good tradeoff to have to make.
My new idea is to treat the C/D pin as another data stream. Why not just prepare a buffer of Command/Data bytes to match the display-data buffer and send the two buffers in parallel? The tricky part is again synchronization, if for instance I put the C/D data on a UART. But it appears that the STM32U5’s Octo-SPI feature (intended for parallel SPI interfaces to high-speed memory chips) could do this job. I would set it for dual-SPI: two synchronized SPI MOSI lines. Octo-SPI's limitation of half-duplex is perfectly fine.
This seems good, though preparation of the data buffer would be required. It seems I can’t give the Octo-SPI peripheral separate buffers for each of its outputs; for my dual-SPI configuration, it will process one transmit buffer, sending all the even-numbered bits out one pin and the odd-numbered bits out the other. So I would have to interleave the buffer’s bits for C/D and display data. That seems worth it for the benefit of avoiding interrupts and race conditions… but I also thought my linked-list approach was going to be good at first, so I’d appreciate your advice.
Thanks!
2025-12-03 12:22 AM
Hello @wallwhit ;
How are the OCTOSPI pins connected to the device? Could you please share your hardware?
In order to start with OCTOSPI interface, you must verify if these interfaces are supported by the device. The command format and order shared in the device datasheet must be aligned with the command format and order mentioned in the STM32 reference. Note that, the controllers interfaces work only as masters and support the Single-SPI (legacy-SPI). The interfaces do not support full duplex. Therefore, it cannot send and receive data simultaneously.
Check the STM32U545 errata sheet for any known limitations and workarounds.
I recommend you to look at Getting started with Octo-SPI, Hexadeca-SPI, and XSPI interfaces on STM32 MCUs - Application note may help you to start with OCTOSPI interface.
Thank you.
Kaouthar
To give better visibility on the answered topics, please click on Accept as Solution on the reply which solved your issue or answered your question.
2025-12-03 6:52 AM
Hi Kaouthar,
Thank you. The half-duplex constraint of OCTOSPI would be fine for my job. But I wasn't thrilled with the memory requirements of my proposed C/D buffer, and I've solved this problem without OCTOSPI.
I went back to my earlier approach of controlling the C/D pin as GPIO during interrupts. Instead of using linked list DMA as I had been trying, I'm using ordinary DMA, and my interrupt is the SPI end of transfer. In that interrupt handler, I point the DMA to the next message segment.
It's working fine, though the interrupt handler is a bit slow because it disables and re-enables SPI and DMA. I think this is necessary, but perhaps you see a safe way to speed this up? Here's the relevant part of my interrupt handler. Note that `driverP` uses my typedef for a pointer to this SPI driver instance, and `srcAddr` and `size` are for the next SPI message segment.
if (LL_SPI_IsEnabled(driverP->mSpiInstanceP))
{
LL_SPI_Disable(driverP->mSpiInstanceP);
}
LL_SPI_SetTransferSize(driverP->mSpiInstanceP, size);
// Re-enable SPI and DMA Request
LL_SPI_Enable(driverP->mSpiInstanceP);
LL_SPI_EnableDMAReq_TX(driverP->mSpiInstanceP);
// Reconfigure GPDMA for next message segment
// Note: We assume Destination Address (SPI TXDR) was set in Init and doesn't change.
LL_DMA_DisableChannel(driverP->mDmaInstanceP, driverP->mDmaChannel);
LL_DMA_SetSrcAddress(driverP->mDmaInstanceP, driverP->mDmaChannel, srcAddr);
LL_DMA_SetBlkDataLength(driverP->mDmaInstanceP, driverP->mDmaChannel, size);
// Clear all pending DMA flags (we could probably skip most of these, but they're quick)
LL_DMA_ClearFlag_TC(driverP->mDmaInstanceP, driverP->mDmaChannel);
LL_DMA_ClearFlag_HT(driverP->mDmaInstanceP, driverP->mDmaChannel);
LL_DMA_ClearFlag_DTE(driverP->mDmaInstanceP, driverP->mDmaChannel);
LL_DMA_ClearFlag_ULE(driverP->mDmaInstanceP, driverP->mDmaChannel);
LL_DMA_ClearFlag_USE(driverP->mDmaInstanceP, driverP->mDmaChannel);
LL_DMA_ClearFlag_SUSP(driverP->mDmaInstanceP, driverP->mDmaChannel);
// Enable GPDMA Channel
LL_DMA_EnableChannel(driverP->mDmaInstanceP, driverP->mDmaChannel);
// Start Master Transfer
LL_SPI_StartMasterTransfer(driverP->mSpiInstanceP);
Thank you.
2025-12-09 12:42 AM - edited 2025-12-09 12:43 AM
Hello @wallwhit ;
Glad to know that the problem is solved.
Thank you for updating post and sharing this approach.
Make sure that you put the highest SPI priority interrupt.
Thank you.
Kaouthar
To give better visibility on the answered topics, please click on Accept as Solution on the reply which solved your issue or answered your question.