Using STM32 DMA to speed up hardware SPI and U8G2 - Attempt 1 - Simple DMA
Using STM32 DMA to speed up hardware SPI and U8G2 - Attempt 1 - Simple DMA
So we have finished all the basic hardware connection, setup and using hardware SPI to let u8g2 to communicate to LCD12864. Please check this post for detail:
STM32 and U8G2 to draw on LCD12864 Using hardware SPI
It works well, however there is a tiny problem, it's too slow.
The speed problem of SPI of LCD12864(ST7920)
Every time we call u8g2.sendBuffer(), this operation takes 43milliseconds.
How to measure sendbuffer() 's time usage in stm32cubeIDE:uint32_t start_ts = HAL_GetTick();
u8g2.sendBuffer();
uint32_t end_ts = HAL_GetTick();
unsigned int delta_msec = end_ts - start_ts;
printf("sendbuffer used %u ms\r\n", delta_msec);
Using stm32F103C8T6 bluepill, system speed 72Mhz, spi speed 572kbit/s
Without doing much (just write some text) in the main loop, the LCD screen just have a FPS about 16FPS ( with some printf() to output debug info ).
If we let the MCU work on some real work like ADC, calculation for PWM, PID algorithm, then the speed /FPS should drop even more! To simulate some "real work", I added a modest sleep(20ms) to slow down the main loop
HAL_Delay(20);
With this sleep 20ms, the FPS dropped to 12, I guess we can't run DOOM on it then ??
So this is bad.
Using STM32's DMA to Speed it up
Is there something we can do to improve the speed ?
Yes, STM32 can do DMA, Direct memory access, which doesn't use CPU time, to help to transfer data from memory to memory, or memory to SPI bus.
Not working solution - simple DMA:
So the first attempt would be, you may have already thought of, is to replace the HAL_SPI_Transmit() function for the message U8X8_MSG_BYTE_SEND inside the hw_spi call back function we defined for u8g2 to use, to replace it with a DMA version of the SPI transmit function: HAL_SPI_Transmit_DMA
inline uint8_t u8x8_byte_3wire_hw_spi(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr) {
switch (msg) {
case U8X8_MSG_BYTE_SEND:
// HAL_SPI_Transmit(&LCD_SPI, (uint8_t*) arg_ptr, arg_int, 10000);
HAL_SPI_Transmit_DMA ( );
break;
So this change is simple, does it work? unfortunately no, this DMA call will instantly return, and u8g2 will calls it repeatedly again and again, so it messes up the DMA mechanism. We also need to add while() loop to block it and wait until the DMA is done like this:
while (HAL_DMA_GetState( LCD_SPI.hdmatx) != HAL_DMA_STATE_READY){};
HAL_SPI_Transmit_DMA ( );
How does it work? Unfortunately It not only couldn't make the screen correct and clean, but also the time is the same slow.
After some debug , break point, show variables work, it turned out that u8g2 keep calling the U8X8_MSG_BYTE_SEND function to send 16 bytes each time (one line of dots, 16X8=128), so each time we send 16bytes, instantly we need to call the DMA to send again, but DMA is still sending it, so we have to wait, blocking, and send again, then wait again, that explains why the DMA way of sending buffer uses pretty much the same amount of time!
Also, 16 bytes is really not a lot to send, seems wasting DMA's ability to send large amount of data without using DMA's time.
At last, to fill up the whole 12864 screen, this DMA call need to be called for at least 64times, and we need to wait for 64 blocking times, this totally make the simple DMA method useless.
And last last bad news, after using this simple DMA method, the FPS dropped even more, it's even slightly slower than the regular non-DMA SPI transmit speed.
Test shows FPS is only 11 and sendbuffer() takes 45ms(was 42ms in the regular SPI send)...not to mention the screen content is totally broken.
Comments
Post a Comment