We have upgraded the community system as part of the upgrade a password reset is required for all users before login in.

FB_ILI9341 80fps up to 100fps | memcpy(); 387 fps minimal

  • Yep, this works like a charm! šŸ™‚

    [  254.231106] m25p80 spi32766.0: spi-nor spi32766.0 40000kHz 8 bits mode=0x00
    [  254.238192] spidev spi32766.1: spidev spi32766.1 40000kHz 8 bits mode=0x00
    [  254.245322] spidev spi32766.1: Deleting spi32766.1
    [  254.274384] fb_ili9341 spi32766.1: fbtft_request_gpios: 'reset' = GPIO2
    [  254.281148] fb_ili9341 spi32766.1: fbtft_request_gpios: 'dc' = GPIO3
    [  254.581036] fb_ili9341 spi32766.1: Display update: 3100 kB/s, fps=0
    [  254.587786] graphics fb0: fb_ili9341 frame buffer, 320x240, 150 KiB video memory, 3072 KiB DMA buffer memory, fps=100, spi             32766.1 at 96 MHz
    [  254.600541] fbtft_device: GPIOS used by 'fb_ili9341':
    [  254.605665] fbtft_device: 'reset' = GPIO2
    [  254.609742] fbtft_device: 'dc' = GPIO3
    [  254.613560] m25p80 spi32766.0: spi-nor spi32766.0 40000kHz 8 bits mode=0x00
    [  254.620643] fb_ili9341 spi32766.1: fb_ili9341 spi32766.1 96666kHz 8 bits mode=0x06


    I modified this program to work with the 16 bit framebuffer and elimate some checks

    /* fbgrad: draw gradient using framebuffer.
       run in console, X11 would overwrite everything immediatelly.
       (c) Lev, 2018, MIT licence
    #include <stdint.h>
    #include <stdlib.h>
    typedef uint_fast16_t uint;
    typedef struct {
        char    *buffer;
        size_t  size,
                bytes_per_pixel, bytes_per_line,
                width, height;
        uint    red, green, blue;
    } Screen;
    #include <stdio.h>
    #include <fcntl.h>
    #include <linux/fb.h>
    #include <linux/kd.h>
    #include <sys/mman.h>
    #include <sys/ioctl.h>
    #include <unistd.h>
    #include <time.h>
    #define fbdev "/dev/fb0"
    #define ttydev "/dev/tty"
    typedef struct {
        uint_fast8_t    r, g, b, a;
    } Color;
    #define Die(Msg, ...) { \
        fprintf (stderr, "fbgrad: " Msg ".\n", __VA_ARGS__); \
        exit(1); \
    #define Assumption(Cond, Msg) \
        if (!(Cond)) { \
            fprintf (stderr, "fbgrad: failed assumption: %s\n", Msg);\
    int main (int argc, char **argv) {
        //int ttyfd = open (ttydev, O_RDWR);
        //if (ttyfd < 0)
        //    Die ("cannot open \"%s\"", ttydev);
        //if (ioctl (ttyfd, KDSETMODE, KD_GRAPHICS) == -1)
        //    Die ("cannot set tty into graphics mode on \"%s\"", ttydev);
        int fbfd = open (fbdev, O_RDWR);
        if (fbfd < 0)
            Die ("cannot open \"%s\"", fbdev);
        struct fb_var_screeninfo vinf;
        struct fb_fix_screeninfo finf;
        if (ioctl (fbfd, FBIOGET_FSCREENINFO, &finf) == -1)
            Die ("cannot open fixed screen info for \"%s\"", fbdev);
        if (ioctl (fbfd, FBIOGET_VSCREENINFO, &vinf) == -1)
            Die ("cannot open variable screen info for \"%s\"", fbdev);
        /*Assumption ((vinf.red.offset%8) == 0 && (vinf.red.length == 8) &&
                    (vinf.green.offset%8) == 0 && (vinf.green.length == 8) &&
                    (vinf.blue.offset%8) == 0 && (vinf.blue.length == 8) &&
                    (vinf.transp.offset) == 0 && (vinf.transp.length == 0) &&
                    vinf.xoffset == 0 && vinf.yoffset == 0 &&
                    vinf.red.msb_right == 0 &&
                    vinf.green.msb_right == 0 &&
                    vinf.blue.msb_right == 0,
                    "Color masks are 8bit, byte aligned, little endian, no transparency."
        Screen s = {
            .size            = finf.line_length * vinf.yres,
            .bytes_per_pixel = vinf.bits_per_pixel / 8,
            .bytes_per_line  = finf.line_length,
            .red             = vinf.red.offset/8,
            .green           = vinf.green.offset/8,
            .blue            = vinf.blue.offset/8,
            .width           = vinf.xres,
            .height          = vinf.yres
        s.buffer = mmap (0, s.size, PROT_READ | PROT_WRITE, MAP_SHARED, fbfd, 0);
        if (s.buffer == MAP_FAILED)
            Die ("cannot map frame buffer \"%s\"", fbdev);
        int time_start = time (NULL);
        uint border = vinf.xres/2;
        for (uint t = 0; t < 255*1; t++) {
            for (uint y = 0; y < vinf.yres; y++) {
                for (uint x = 0; x < vinf.xres; x++) {
                    uint pix_offset = x * s.bytes_per_pixel + y * s.bytes_per_line;
                    // PIXEL FORMAT IS  RGB565, in BGR format
                    if(/*t < 255*10/2*/ x < border) {
    					s.buffer[pix_offset] = 0B00000000;
    					s.buffer[pix_offset+ 1] = 0B11111000;
                    } else {
                        s.buffer[pix_offset+ 1] = 0B00000000;
                        s.buffer[pix_offset] = 0B11111000;
                    //s.buffer[pix_offset + s.red] = x * 255 / s.width;
                    //s.buffer[pix_offset + s.green] = y * 255 / s.height;
                    //s.buffer[pix_offset + s.blue] = t;
        int time_end = time(NULL);
        munmap (s.buffer, s.size);
        /*if (ioctl (ttyfd, KDSETMODE, KD_TEXT) == -1)
            Die ("cannot set tty into text mode on \"%s\"", ttydev);
        close (fbfd);
        //close (ttyfd);
        printf ("FPS: %.2f.\n", 255.0 / (time_end - time_start));
        return EXIT_SUCCESS;

    This access method (memory-mapped buffer, byte-wise writes) gives circa 255FPS [TIMING NOT ACCURATE, USES time() in seconds). There are probably fast access ways than this (write whole image in one go). I need to look into the DirectFB layer for that maybe.

    But this is extremely cool. /dev/fb0 works on a custom firmware image. Wondering what prevented it from running on the stock image.

    For anyone wondering what the pin connections are:

    • VCC -> 3.3V
    • GND -> GND
    • CS -> CS1 (GPIO pin 6)
    • RESET -> GPIO 2
    • DC -> GPIO 3
    • MOSI -> GPIO 8
    • SCLK -> GPIO 7
    • LED -> to 5V over 50 Ohms resistor or over 10 Ohms to 3.3V (power LED backlight, needs small current limiting)
    • MISO -> GPIO 9

    When you're back, could you tell us how you converted the BMP images (e.g. logo.bmp) to the needed 16-bit format so that they are displayed correctly? I could not get this to work directly, I can only save it in 8 or 24 bit format, and the image looks garbeled on the display.

    EDIT: Tool reports 85 FPS when using different pixel computation.

    unsigned short ConvertRGB888toRGB565(unsigned int sourceColor)
    	unsigned int red = (sourceColor & 0x00FF0000) >> 16;
    	unsigned int green = (sourceColor & 0x0000FF00) >> 8;
    	unsigned int blue =  sourceColor & 0x000000FF;
    	return (red >> 3 << 11) + (green >> 2 << 5) + (blue >> 3);
    				unsigned int targetCol = ((uint8_t)( x * 255 / s.width) << 16)
    						| ((uint8_t)(y * 255 / s.height) << 8)
    						| (uint8_t) t;
    				unsigned short targetColor16 = ConvertRGB888toRGB565(targetCol);
    				s.buffer[pix_offset] = targetColor16 & 0xff;
    				s.buffer[pix_offset + 1] = targetColor16 >> 8;

  • @Maximilian-Gerhardt In this topic ffmpeg side you can find BMP image conversion, i am using ffmpeg, gimp, imagemagic(convert) all the image job, quick tip:

    mkdir ~/sequence
    # basically we have 2 steps for conversion in ffmpeg
    # 1. convert the video to resolotion of display, in my case 320x240(4:3 ratio)
    ffmpeg -i the-prey-video.xyz -vf scale=320:240 scaled.mp4 
    # 2. create the sequence files from given video
    ffmpeg -i scaled.mp4 -pix_fmt rgb565 -r 25/1 ~/sequence/seq%0001d.bmp
    # if you want to convert one image file(png,jpeg,bmp) to rgb565 bmp
    ffmpeg -i some-image-file.png  -vf scale=320:240  -pix_fmt rgb565 exported-image.bmp

    thats it.

    Tested right now, when the files located in ram (/tmp) on omega it gives stable 100fps. In here 100fps notro's wiki says "Thus above fps=20 we only have 4 distinct values: 100/25=4, 100/26=3, 100/34=2, 100/51=1 (integer division)" in here so i think it can go further. Simple math says we have 96Mhz spi frequency hardware limit.

    on the other hand -> mt7688 datasheet says:

    27:16 rs_clk_sel Register Space SPI clock frequency select.
    0: SPI clock frequency is hclk/2. (50% duty cycle, duty cycle is the ratio of the output high time to the total cycle time)
    1: SPI clock frequency is hclk/3. (33.33% or 66.67% duty cycle)
    2: SPI clock frequency is hclk/4. (50% duty cycle)
    3: SPI clock frequency is hclk/5. (40% or 60% duty cycle)
    4095: SPI clock frequency is hclk/4097
    page 143

    I think we could'nt get the max freq... i do'nt know that, ok then i will prepare a document "how to fbtft supported image" today, see you soon.

  • @Maximilian-Gerhardt I think activated kernel_menuconfig items are triggers some packages in the image, Build process step by step it works!

  • Nice work!
    Can we run some number just to validate my understanding is correct...

    If it's 320x240 ( @ 16 bit/pixel) per image frame
    and such individual (different) frames are transfer 80 times per second, thus
    320x240x16x80 = 98.304 Megabits per second, not considering SPI overhead (and I am thinking the SPI protocol engine is hardware based fetching directly from RAM space.)

    Later post stated 12 fps, which will be
    320x240x16x12 = 14.8 MHz pixel clock (not padded with SPI and frame fetching overhead), while SPI is clocked at 48MHz.
    <-- here I am assuming MT7688 SoC is doing something else (such as running other code, e.g., build new frames, housekeeping, application code, etc.)

    In my view, the usability would be how much of the CPU time is left over for Omega2+ to perform its regular job (other than screen refreshing)? Do you have a guesstimate?


  • @ccs-hello said in FB_ILI9341 80fps up to 100fps:

    I am thinking the SPI protocol engine is hardware based fetching directly from RAM space

    Yes, that's exactly what it is. The other question " how much of the CPU time is left over for Omega2+ to perform its regular job " i2ve just test it, it seems to be ok, but i did'nt test it for durability, just push the work load to omega2 and its just fine;

    1. 25fps ram loaded bmp sequence push to /dev/fb0 using sleep microseconds
    2. 3 ssh connection + serial console,
    3. working top application,
    4. shiftOut application that drives 7 segment lcd using 8bit shiftregister on omega
    5. web site download using wget in to omega2



    bmp push to /dev/fb0

    #!/usr/bin/env sh
    echo "mkdir /tmp/kizu"
    echo "cp -v /tmp/mounts/USB-A1/kizuwa/* /tmp/kizu"
    if [ -d /tmp/kizu ]; then
    while : # infinite loop
                    for i in `ls -v /tmp/kizu/*.bmp`
                    $(cat $i > /dev/fb0) > /dev/null
                            # opkg install coreutils-sleep (microsecond sleep)
                            # this way this script will work 25 times in a second 
                            # human eyes can't grab above this value so this is ok.
                    echo $i
                    sleep 0.04

    website download

    wget -mkEpnp --no-check-certificate https://docs.onion.io/omega2-docs/

    workload test video : https://www.youtube.com/watch?v=IFDwKkcWwDg

    screen record video : https://www.youtube.com/watch?v=RJgqwcGm7vQ

  • I am currently working on getting DirectFB (https://github.com/deniskropp/DirectFB) to work with the Omega2. DirectFB is an abstraction layer above fbdev (which provides /dev/fb0).

    Based on DirectFB, one can build SDL, with which games can be built. DirectFB also includes a C++ wrapper and example programs which implement a image viewer and video player (dfbplay) with lots of formats (MPEG, AVI, ...)

    I am so close to getting DirectFB running on this thing. So close.

    Basically I get DirectFB to compile. But during startup, it just hangs. I have debug output available, but since I haven't yet worked with DirectFB (or have a working reference system), it's hard for me to find the error. Especially since I had to patch DirectFB to get it compile under the gcc-musl toolchain at all..

    If anyone is willing to help or test this, I have more info at the original OpenWRT/LEDE forum post at https://forum.openwrt.org/t/need-debugging-help-with-directfb-lede/19985. The responses there: Zero.

    This is a stepping stone to getting a multimedia experience running directly on the Omega2, and possible more complicated games / emulators.

  • Thank you for your hard work, @Maximilian-Gerhardt . I am willing to help or test this, actually i cloned directfb, and lvgl_project, but i couldn't create time for the full scale display manager because of my project.
    I've nearly finished a prototype device for education purpose using fbtft on omega, plus atmel328 that drives touch screen and rc522 rfid R/W, display on/off, some digital controls ...
    After this project i will take my time, firstly chrooted debian i am thinking of, this way i can find a way out or might be see a workaround. After than that, not for just prototype, from top to toe openwrt lede specialist, I am aiming for. I am lack of knowledge for the full scale solution but all of us together is another thing. If this thing works Omega2 will be a fullscale computer šŸ™‚
    In a month or so i will be dive deeper i think.



  • Any updates on this? šŸ™‚

  • @brolly759 Sorry for the late reply, i saw the post now.

    Actually there are but that is not a finished code(even beta).

    My work partner and I parted ways, and the project was canceled so i don't code it further.

    In the code there are lots of comment lines with Turkish language, when i create a free time i can change the comment lines and push the "unfinished code" to github. If it'll work for someone i will be glad.

    Some of the functions.

    /* Color conversion */
    	typedef struct
    		unsigned int
    		unsigned short int
    			r, g, b;
    	} hex2rgb;
    		parseRgb888( hex2rgb  hexValue );
    		parseRgb565( hex2rgb  hexValue );
    		color565( char *hexVal );
    		color888( char *hexVal );
    /* Color conversion */
    	parseRgb565 ( hex2rgb hexParse )
    		hex2rgb hexParsed888, parsed;
    		hexParsed888 = parseRgb888( hexParse );
    		parsed.hexValue = ( ( hexParsed888.r >> 3 ) << 11 ) | ( ( hexParsed888.g >> 2 ) << 5 ) | ( hexParsed888.b >> 3 );
    		parsed.r = ( ( parsed.hexValue >> 11 ) & 0x1F );
    		parsed.g = ( ( parsed.hexValue >> 5 ) & 0x3F );
    		parsed.b = ( parsed.hexValue & 0x1F );
    		//printf( "Output\nHex 565: #%04X\n", parsed.hexValue );
    	return parsed;
    	parseRgb888( hex2rgb hexParse )
    		hex2rgb parsed;
    		parsed.r = ( ( hexParse.hexValue >> 16 ) & 0xff );
    		parsed.g = ( ( hexParse.hexValue >> 8 ) & 0xff );
    		parsed.b = ( ( hexParse.hexValue >> 0 ) & 0xff );
    		//printf( "Output\nHex 888: #%06X\n", hexParse.hexValue );
    		//printf( "RGB (%d, %d, %d)\n", parsed.r, parsed.g, parsed.b );
    	return parsed;
    	color565( char *hexVal )
    		hex2rgb hex24, hexParsed565;
    		sscanf( hexVal , "%06x", &hex24.hexValue );
    		//~ hexParsed888 = parseRgb888( hex24 );
    		hexParsed565 = parseRgb565( hex24 );
    		//~ printf ( "Hex24\t#%06X\nRGB888 (%d, %d, %d)\n\n", hex24.hexValue ,hexParsed888.r, hexParsed888.g, hexParsed888.b );
    		//~ printf ( "Hex16\t#%04X\nRGB565 (%d, %d, %d)\n", hexParsed565.hexValue, hexParsed565.r, hexParsed565.g, hexParsed565.b );
    		return hexParsed565;
    	color888( char *hexVal )
    		hex2rgb hex24, hexParsed888;
    		sscanf( hexVal , "%06x", &hex24.hexValue );
    		hexParsed888 = parseRgb888( hex24 );
    		//~ hexParsed565 = parseRgb565( hex24 );
    		//~ printf ( "Hex24\t#%06X\nRGB888 (%d, %d, %d)\n\n", hex24.hexValue ,hexParsed888.r, hexParsed888.g, hexParsed888.b );
    		//~ printf ( "Hex16\t#%04X\nRGB565 (%d, %d, %d)\n", hexParsed565.hexValue, hexParsed565.r, hexParsed565.g, hexParsed565.b );
    		return hexParsed888;
    	drawEmbedBmp ( struct fb_info *fb_info,
    				   char *bmpFile, unsigned int bmpFile_len )
    		memcpy( fb_info->ptr, &bmpFile[0], bmpFile_len );


  • Well fingers crossed you get this working šŸ˜‰

  • Hello, great work you can, it would help us a lot to have a vagrant file. Do you have the firmware compiled? Could you provide it?

Log in to reply

Looks like your connection to Community was lost, please wait while we try to reconnect.