nspsck / RM67162_Micropython_QSPI

This is a C driver as a user module for the T-AMOLED-S3 using QSPI-protocol.
MIT License
7 stars 4 forks source link

Backgroung color wor write function #8

Closed dobodu closed 6 months ago

dobodu commented 6 months ago

I think backgound color for the write function does not work

Here is the code I wrote to illustrate, coming for the hello.py example.

import random from utime import sleep_ms import rm67162 import tft_config import fonts.large as font_var import fonts.vga2_bold_16x32 as font_mono

tft = tft_config.config()

def main(): text = "Hello!" tft.reset() tft.init() tft.rotation(1) tft.fill(rm67162.RED) #Ok filled in red tft.write(font_var, text, (tft.width() - tft.write_len(font_var,text)) // 2, (tft.height() - font_var.HEIGHT ) // 2, rm67162.WHITE, rm67162.RED) #KO background is black sleep_ms(2000) tft.fill_rect(0,0,tft.width(),tft.height(),rm67162.BLUE) #Ok filled in blue tft.text(font_mono, text, (tft.width() - len(text) * font_mono.WIDTH) // 2, (tft.height() - font_mono.HEIGHT ) // 2, rm67162.WHITE, rm67162.BLUE) #OK background is blue sleep_ms(2000)

Can you try and confirm, I send below an appropriate variable bitmap font I used.

large.zip

nspsck commented 6 months ago

Hi, I can confirm this issue! It's now fixed. Thank you again for testing out!

dobodu commented 6 months ago

All right, I was wondering why the grey looked like grey and vice-versa. That seems better and background is working.

However, you suppressed the polygon and filled polygon function ?

nspsck commented 6 months ago

All right, I was wondering why the grey looked like grey and vice-versa. That seems better and background is working.

This is due to the color byte swapping. I forgot to take out of the code. Hence the color is different than what it should be.

However, you suppressed the polygon and filled polygon function ?

Ops.. was unintentional, sorry. Was editing the code on another PC and I hurried a little. Going to bring that back asap. Aka, a few minutes.

nspsck commented 6 months ago

Done.

dobodu commented 6 months ago

Thank you.

I'll dig a little bit more since I always face some trouble with the fill_rectangle. I definitely don't understand where it's comming from and I can't reproduce from a short piece of code...

By the way, I've been testing my program on two different hadwares / libs.

1st one is a Pi Pico, overclocked to 240Mhz, single thread, with an SPI ST7789 screen 2nd one is an ESP32S3, clocked to 240MHz, single thead with a QSPI RM67169 screen

As the program is gathering info from a bosh BNO085 (9axis) + BME280 (env sensor) but also BLE, acting like a display (i mean representing information gathered), I expected the QSPI setup to be much faster than the SPI one.

What I found :

Pico + ST7789 = 12 to 14 FPS ESP32S3 + RM67169 = 8 to 10 FPS

Assuming that the SPI display is 320x240 whereas the QSPI is 536x240 on one side, and on the other side QSPI = 4xSPI lines, I would have expected the QSPI setup to be twice faster as SPI one, instead of being 50% slower.

What's your opinion ?

nspsck commented 6 months ago

Hi,

I'll dig a little bit more since I always face some trouble with the fill_rectangle. I definitely don't understand where it's comming from and I can't reproduce from a short piece of code...

Well, some of the functions are not very safe since they parse a int to uint16_t, which is an unsigned int type and it's easy to make an overflow mistake. That's a design issue of mine. But feel free to contact if you got some reproduce-able codes, I'll try to solve these issues.

Assuming that the SPI display is 320x240 whereas the QSPI is 536x240 on one side, and on the other side QSPI = 4xSPI lines, I would have expected the QSPI setup to be twice faster as SPI one, instead of being 50% slower.

What's your opinion ?

The esp32 port differs from the rp2 ports. You can only compare an apple to an apple. Which normally, esp32 ports tends to be slower than rp2 and stm32 due to freeRTOS overheads. One way used to make the situation better is to allocate a huge chunk of heap RAM as buffer. Besides, your rm67169 display has actually 0.675 times more pixels, so the qspi actually works.

Plus, if you were actually trying to bench the driver/displays fastest refreshing rate, you should be only running graphical tasks. As there also could be some overhead lying in the sensors. Also, just for reference, if you are using this driver, the fill() function can be executed over 50 times per seconds, which means, the max FPS is over 50. [Edit] (for full display re-draw)

And btw, if you do not overlock the rp2, that is run it at 125MHz, you can get 62.5MHz for the spi line, so, faster than clocks above any cpu clock over 125MHz.

dobodu commented 6 months ago

Well, Thank you for your answer.

My main concern wasn't to benchmark the two solutions but from scratch, I expecter the EPS32 to go ahead the Pico.

I like this small board I bought in order to give a try. AMOLED is perfect for outside purposes. Of course le display is very small compared to the Washare 2.8" but the quality has nothing to compare.

I'm working on an app since last year in order to put on a motorbike. I've been improving it a lot (it can discuss with calculator, by wire or by BLE, get roll/pan/tilt, acceleration and what ever, store datas, be contacted by wifi...). I'm having much fun doing this even if lack the years I was student since I have so less time to developp.

For now, everything works on Pico, but I'd like to improve a bit performance, i think I'll focus on dual threading Sensors calculation vs display. It runs at 15 FPS, let's try to gain +25%

On the ESP32, everything works too, but I still face this rect_fill bug I'm working on. I also miss the JPG functions (I've tried to import the slow JPG that's farely sufficent for my purpose : Displaying the bike picture during startup...) and the bad point is that is reaches hardly more than 10 FPS. Maybe the second thread could also increase a bit.

Well, I'd like once more to say Thank you, You've been a nice help and reactive as a Cobra to correct small bugs. If you find the time, don't forget about JPG :-)

dobodu commented 6 months ago

DSC_1080.JPG

nspsck commented 6 months ago

Hi,

If the jpeg file is small, you can just convert all the pixel data into color565 raw data first, then use the bitmap function to display it. It should be very fast.

If all you need is the 25% increase over the pi pico. Please try my suggestions above. Do not - overclock the pico and set the spi baudrate to 62500000.

dobodu commented 6 months ago

The new version 20240505_005925.JPG

dobodu commented 6 months ago

As you can see, even if a wrote a rect_fill on parts of the previous areas, a still have background glitches from the first screen displayed (see below) 20240505_005846.JPG

nspsck commented 6 months ago

:( actually, I can give two days on the jpeg.

But it will be slower than raw color565 data, and uses more RAM than raw data. The only advantage is, that you save some flash space. That's something you should keep in mind. And that is also the only reason why I have not prioritized it.

nspsck commented 6 months ago

As you can see, even if a wrote a rect_fill on parts of the previous areas, a still have background glitches from the first screen displayed (see below)

That's strange, if you meant the text "sprint RS 955i". Have you tried to write the area with a fill_rect() function? Are you certain, that the function which will write the text is never called somewhere unexpected?

To simply test the fill_rect function, you can just fill an area with color 1, delay 2 seconds, and fill the area with color 2. It passed this test on my end.

Btw: really good looking project! That's something you definitely should be proud of!

dobodu commented 6 months ago

Thank you for the feedback concernant my project ;-)

How stupid am I. you're perfectly right with the raw bitmap data... Jpeg was more convinient for the ease of use (common format everybody knows) but that must make it !

Concerning the background, of course I've been investigation my code but that's impossible I get a write function of the text in the main loop, it's called only once.

My hypothesis were :

1/As I use subroutines, I transmit display as a Global variable, So I checked I didn't forget anything (OK) 2/I "clean the display" every time I change the type of screen, maybe I forgot smthg => I checked, it's impossible since the tittle (on top) comes from the same subroutine and is working 3/hardaware related issues : The one I believe on the most :

On Pico version, screen is handled via I2C On ESP32, screen is handle via QSPI (the main difference) On both Touch screen, Gyro and Env are handled on I2C Touch screen is handled via interrupt gesture

Normally, there should't be any relation (the bug appears before any screen touch) but I suspect some kind of interraction. The Gyro sensor requires I/O operation to handles chips reports every 50ms.

nspsck commented 6 months ago

1/As I use subroutines, I transmit display as a Global variable, So I checked I didn't forget anything (OK) 2/I "clean the display" every time I change the type of screen, maybe I forgot smthg => I checked, it's impossible since the tittle (on top) comes from the same subroutine and is working 3/hardaware related issues : The one I believe on the most :

On Pico version, screen is handled via I2C On ESP32, screen is handle via QSPI (the main difference) On both Touch screen, Gyro and Env are handled on I2C Touch screen is handled via interrupt gesture

Also, maybe 4, but I can not think how it could happen. Both write() and fill_rect() functions use the same buffer, which is a 240x536x2 bytes buffer. Both functions use the same RAM area if their size is the same. However, when you call fill_rect(), a "refilling" of that area should occur and the font data should be overwritten. But if it somehow (which I do not get why) could not, the same font data will be displayed again.

dobodu commented 6 months ago

Well,

Both case (fill or rect_fill) give the same result... confirming that same buffer = same result.

It's annoying I can't reproduce this on a small part of code.

nspsck commented 6 months ago

Actually.. ugh... If you do not mind, you can add me on [censored] and send me the code privately. I kinda want to know how this could happen.

dobodu commented 6 months ago

Well, I'll appreciate some help, this thing is driving me crazy... I will look stupid if the bug comes from a stupid mistakes, but I prefer to be stupid than being stopped :-)

You've been censored, how can I reach you directly ?

nspsck commented 6 months ago

Ugh, hi,

Since I am not comfortable having my personal information exposed to the internet for a long period of time and we could not establish a connection during that time period, I suggest you to open a private github repo and add me to the contributor, later you can delete that repo whenever we are done.

best regards.

dobodu commented 6 months ago

Hi, sorry we were outside while it was not raining. You are right, keep our emails away from exposition.

I've made some progression :-)

I join you the bug_tracking .PY file I made, from far it's simpliest than digging in my code (I should reorganize a bit, definitely)

bug_track.zip

So, it seem to be a 3 player affair, Wifi and Bluetooth on one side, my own library on the other side and the display driver in the middle. More comments are in the main file I send you.

The ODBBLE library I made is public access on my github so no matter who can see it !

By the way, I'm french (maybe the reason my english is too bad), glad to have a friend in China (am I right ?)

nspsck commented 6 months ago

image

Ugh, what I see in your code is.. Or the expected behavior should be: You see "You should see me alone" always displayed on the display, flickering once every seconds.

Here is how I see it is being executed:

If the display refreshes too fast, which it does, you won't see any difference in displayed text with this piece of code.

To me, I would change the function as follow to exam whether the texts are "erased" correctly:

change:

text = "You should see me alone"

to something like

text1 = "First text"
text2 = "Second text"

Then call them respectively in the while True loop and clear_display() function like so: image

(Sorry if I were providing too many details, maybe this is just a mistake caused by carelessness, we all make these :D)

By the way, I'm french (maybe the reason my english is too bad), glad to have a friend in China (am I right ?)

Partially :D, am actually living in EU as well. But I do speak Chinese and definitively looks Asian.

dobodu commented 6 months ago

For sure I could have made 2 different texts, But that looked funny to have two "you should see me alone" at the same time when the bug appears.

The code structure may seem strangle but I firstly wanted to keep my main program structure (clear display is called by an interruption handler).

PS : so we share the same time zone 😁

nspsck commented 6 months ago

Oh, sorry, I was not care enough to note the starting point's different. (80 and 120). My bad!

PS: yes, exactly.

nspsck commented 6 months ago

https://github.com/nspsck/RM67162_Micropython_QSPI/assets/86347287/1558d7ee-94a3-418e-8a8e-77fac8c78e7f

Ugh, so, it basically works fine on my device.

I removed the cst8x module, because I don't have it. Other than that, very thing runs as expected.

nspsck commented 6 months ago

I have also changed the WIFI accordingly. And also, I removed the line which set IO38 to high, because mine works fine without that.

dobodu commented 6 months ago

Right, IO38 is only for the T-display S3. I forgot to remove the cst38 import line, at the beginning I suspected to interfer but I can reproduce the bug without the library (I wrote TS support driver myself). Here it comes if you want.

cst8x.zip

The bug appears when Wifi + Bluetooth + ODB are working together. Also when only Wifi + BT are setup, it works but as soon as you reboot I got an OSError...

dobodu commented 6 months ago

https://github.com/nspsck/RM67162_Micropython_QSPI/assets/8106589/c188f763-edaa-48cb-b849-51cb63fcbad6

nspsck commented 6 months ago

I spent some time on the schematics on v1 and v2. These looks almost identical in terms of IOs. (besides IO38 is called OL_EN, and enables the AMOLED display).

Could you please make the text print a changing number indicating the display is still being updated and it is not hanging?

dobodu commented 6 months ago

https://github.com/nspsck/RM67162_Micropython_QSPI/assets/8106589/b5bedeb2-0e28-426c-ba81-2448bdfb833b

No, it's not handling. On my program, I can even change the screen that is displayed but the display clean-up (like clear display) does the same bug.

On my side I've add and interrupt flag within the obdble library, without real expectation... Well as expected doesn't change anything.

On your side do you activate WiFi & Bluetooth?

nspsck commented 6 months ago

Hi

Ye, kinda, not so sure if they were running tho.

Btw. I meant to actually print some numbers on the display like:

count += 1
text = ('hello world %d', count)
tft.text(text, x, y, fg, bg)

See what happens on my prog. Everything works, I can change the screen but no clean-up occurs...

Have you uploaded the wrong video?

dobodu commented 6 months ago

See what happens on my prog. Everything works, I can change the screen but no clean-up occurs...

20240506_003503.JPG

nspsck commented 6 months ago

Oh, wait, sorry, are you using the latest firmware? Because for the older version, if you were using the fill_rect() function, it only goes up to 239x535, anything over that will do nothing... That must be it..

nspsck commented 6 months ago

As, the pixels goes from 0 to 239, total 240 pixels.. Then I uniformed this in #7 . To check if that was the cause, please try using the fill(rm67162.BLACK) function instead.

dobodu commented 6 months ago

On the video the console is outputting a tick_ms output from the program.

The second file (image) show what is happening when I change the display calling the cleaning procedure. It's working, I mean values that are shown get updated on the display. Only the fill of the entire area (fill_rect or fill function) doesn't seem to do her job.

I've shut down the computer for tonight. See if I can dream of a solution. I might try to print a buffer filled with 0 in order to see if bitmap function has more luck.

PS : the fact that it is happening only when WiFi is activated frighten me to be something out of your library behaviour.

nspsck commented 6 months ago

Only the fill of the entire area (fill_rect or fill function) doesn't seem to do her job.

Hmmm, if both functions can not do the job, and the bus is not broken..... Then the next thing in my head is when will the sun explode.... (like for real, this is just a big HOW for me) The fill function is a pointer-based filling function and the freeRTOS does not necessarily ensure memory safety, which means, that fill function will indeed fill whatever there are (from the device memory) into the display memory. So, even if the buffer is corrupted, it will show random color pixels to the display (like an old TV without signals).

Maybe try to initialize the WIFI, BLE, and other stuff first, and see if the display can be created?

Oh, wait, sorry, are you using the latest firmware? Because for the older version, if you were using the fill_rect() function, it only goes up to 239x535, anything over that will do nothing... That must be it...

Have you tried this already? Like, this is the last logical reason that I can think of.

Otherwise, maybe just do repetitive small "erase" to workaround this problem. If that is possible.

dobodu commented 6 months ago

On the video the console is outputting a tick_ms output from the program.

The second file (image) show what is happening when I change the display calling the cleaning procedure. It's working, I mean values that are shown get updated on the display. Only the fill of the entire area (fill_rect or fill function) doesn't seem to do her job.

I've shut down the computer for tonight. See if I can dream of a solution. I might try to print a buffer filled with 0 in order to see if bitmap function has more luck.

Added : I missed your post concerning 239x535 limitations. I've pulled github 2 or 3 days ago. I'll upgrade once more tonight. (that might be it, as my display clean-up was 536x190). The one I use, is the one you put after I requested you not to forget polygons...

PS : the fact that it is happening only when WiFi is activated frighten me to be something out of your library behaviour.

dobodu commented 6 months ago

Well I checked I use the latest firmware I checked both 239x535 and 236x536 I added a number to the main loop...

https://github.com/nspsck/RM67162_Micropython_QSPI/assets/8106589/18d3cbaf-b857-419a-906e-d0d872e7d074

I got a WiFi error everytime I try te run a second time the program...

nspsck commented 6 months ago

This might just be a RAM issue then. Maybe allocating 250kb RAM as a framebuffer is too expensive. Maybe wifi cannot run on psram.

dobodu commented 6 months ago

I've uploaded everything to the last version (Micropython code, libraries....), reorganized my code for readability (nothing to do with our trouble) rebuild everything and guess what... ?

Still not working :-(

The only thing I am shure :

When I activate Wifi + BLE + OBD ==> The bug appears. When I do not activate WiFi but I activate BLE + OBD ==> The bug isn't there When I activate WiFi + BLE but not OBD ==> The bug isn't there but Stop/Start fails

I guess there might be two problem, the first one is related to BT/WiFi The other one might be with my OBD Library. So i'll investigate more.

The pitty is that's all working with I2C based ST7789...

nspsck commented 6 months ago

Funny enough, I finnally was able to reproduce the bug after I reduced the framebuffer size... like, after dropping the framebuffer completely I was able to reproduce the bug... WTF?

And after reenbled the framebuffer, the problem went away...

dobodu commented 6 months ago

Strange, very strange M. Holmes !

Do you think I should to use a specific buffer in order to get around that bug ? After all, we got sufficient memory on the ESP32 to create a display buffer. Maybe I could make screen opération directly in this buffer (I don't know if I can use your library graphic function on this one) and copy the entire buffer to the screen at one time ?

dobodu commented 6 months ago

something like

import framebuf buffer = bytearray(TFT_WIDTH TFT_HEIGHT 2) fbuf = framebuf.FrameBuffer(buffer, TFT_WIDTH, TFT_HEIGHT, framebuf.RGB565)

nspsck commented 6 months ago

then you call

tft.bitmap(x_start, y_start, x_end, y_end, fbuf)

This could work

dobodu commented 6 months ago

Well, I replaced

tft.fill_rect(0,0,tft.width(),tft.height(),rm67162.BLACK) by tft.bitmap(0,0,tft.width(),tft.height(),fbuf)

with the parameter to create the frame buffer

buffer = bytearray(TFT_WIDTH TFT_HEIGHT 2) fbuf = framebuf.FrameBuffer(buffer, TFT_WIDTH, TFT_HEIGHT, 0)

and it gives me another strange behaviour

dobodu commented 6 months ago

https://github.com/nspsck/RM67162_Micropython_QSPI/assets/8106589/7c1b3191-e99d-41e8-b516-bc4c973f01fb

dobodu commented 6 months ago

I give up for tonight. I only changed things in obdble library to change BLE.scan parameters (and I really can't understand how it could impact a memory copy of a buffer....)

However thank you for help !

Here's the code

WF_SUPPORT = True BT_SUPPORT = True OBD_SUPPORT = True

from machine import Pin, I2C from utime import sleep_ms, ticks_ms import rm67162 import tft_config import fonts.vga2_bold_16x32 as font import framebuf

import network import socket import bluetooth import obdble

TFT_WIDTH = 536 TFT_HEIGHT = 240

buffer = bytearray(TFT_WIDTH TFT_HEIGHT 2) fbuf = framebuf.FrameBuffer(buffer, TFT_WIDTH, TFT_HEIGHT, 0)

def clear_display() : tft.bitmap(0,0,tft.width(),tft.height(),fbuf) tft.text(font, text , (tft.width() - len(text)*font.WIDTH) // 2, 80, rm67162.WHITE, rm67162.BLACK) sleep_ms(1000) tft.bitmap(0,0,tft.width(),tft.height(),fbuf)

def main(): global text i=1 while True : clear_display() tft.text(font, text+str(i), (tft.width() - len(text)*font.WIDTH) // 2, 120, rm67162.WHITE, rm67162.BLACK) i = i+1 sleep_ms(1000)

if name=='main':

tft = tft_config.config()
tft_config.TFT_CDE.value(1)

if WF_SUPPORT :
    wlan = network.WLAN(network.STA_IF)
    wlan.active(True)
    wlan.connect("test", "test")
    addr = socket.getaddrinfo('0.0.0.0', 80)[0][-1]

if BT_SUPPORT :
    ble = bluetooth.BLE()

if BT_SUPPORT & OBD_SUPPORT :
    central = obdble.OBD(ble,debug=1)
    central.scan()

text = "You should see me alone"
tft.reset()
tft.init()
tft.rotation(1)
main()
nspsck commented 6 months ago

Ok, the problem I had was a different bug. Also a weird one but should not be related to yours.

So, I tried to implement a RAM-saving thing for you. All functions that do not require tons RAM work just fine. *The fill_rect only works for a maximum of 5368 pixels area**. So you need to do something like this to refresh the screen:

# assuming you have rotation = 1
for i in range(30):
    tft.fill_rect(0, i * 8, 536, 8, rm67162.BLACK)

This is purely for testing and for a long run time, it surely crushes because the driver can not DMA from PSRAM and the temporary buffer is not garbage collected. If this works for you, I'll then add gc, or optimize the code.

If you want the old behavior, (i.e. the massive buffer) add use_frame_buffer=True to the RM67162constructor. Not the panel constructor.

dobodu commented 6 months ago

Just a question,

Why do you use a frame buffer for the fill purpose.? Can't you write straight the two color bytes directly to the screen buffer like the fill_slow C function in your library?

nspsck commented 6 months ago

Hi

I just did it with the bias that esp32s are filling faster if you give them a full chunk of buffer. (10-20% back then) And it's for the simplicity on the development side.

This driver uses DMA, and the way DMA works: It requires a internal DMA-able RAM to pull data from and some time to initialize, and the CPU must set the pointer and parameters for the DMA. So if you were just using 2 color bytes (if I understood your suggestion correctly), you are totally defeating the purpose of using DMA, that is taking load off of the CPU. The CPU must initialize the DMA over and over and over again.

But it makes sense to not use the whole buffer. But that requires me to manage the memory from every individual function that I create. And in case I forgot, I'd be having a bug that hunts me badly. rust is so much better than C just for this purpose only, btw. But the C compiler won't warn me at all.

Also, the current solution requires the temporary buffer to be freeed immediately after the filling is done, otherwise it will go into PSRAM, which is not DMA-able, causing a system reboot.

Also, I had some ideas back then, that is creating a mode which the DMA just polls whatever is available in the RAM and transmit these into the display. This causes a little tearing but it allow the CPU to run freely, which is great for game development, which is also another dead project of mine, which I was horrified by the behavior on each port then I killed that project.

PS. Did this change solver your problem?

dobodu commented 6 months ago

Hi,

Well, I digged a little bit more modifying the bugtracker.py to elimate my library relation. The bug is still present, meaning that it's a deal between bluetooth library and the display driver. I believe the bluetooth driver might be something wrong... or the library itself...

All test are commented inside the next file

bug_track.zip