Ideas on a T4 parallel library using FlexIO

Some polling results

Been optimizing the parallel display here (polled, 8bit and 16bit) and I thought I would share my results:

Code:
8bit
ILI9341 Test!
Display Power Mode: 0x9C
MADCTL Mode: 0x48
Pixel Format: 0x5
Image Format: 0x0
Self Diagnostic: 0xC0
Benchmark                Time (microseconds)
Screen fill              40571
Text                     3860
Proportional Text        3518
Lines                    19925
Horiz/Vert Lines         3395
Rectangles (outline)     2191
Rectangles (filled)      83215
Circles (filled)         16088
Circles (outline)        15457
Triangles (outline)      4684
Triangles (filled)       29904
Rounded rects (outline)  4834
Rounded rects (filled)   92047
Done!


16bit
ILI9341 Test!
Display Power Mode: 0x9C
MADCTL Mode: 0x48
Pixel Format: 0x5
Image Format: 0x0
Self Diagnostic: 0xC0
Benchmark                Time (microseconds)
Screen fill              19223
Text                     3568
Proportional Text        3975
Lines                    18685
Horiz/Vert Lines         1689
Rectangles (outline)     1139
Rectangles (filled)      39451
Circles (filled)         11761
Circles (outline)        15072
Triangles (outline)      4263
Triangles (filled)       16499
Rounded rects (outline)  3893
Rounded rects (filled)   44945
Done!

16bit mode: read/write pixels in one 16bit operation. Both are 16bit 565.
8 bit does a write in 2 8bit operations, and reads in 3 8bit operations and then has to do a fix-up to get 565.

Either way, these numbers are quite good, but I think I can still squeeze more out of it.
 
Isn’t this on a Teensy 3.6 & writing to the port register?
Or did you port your library to a T4.1?
 
I figured out a way to combine FlexIO1, FlexIO2, and FlexIO3 together into a single non-blocking parallel interface. This is exciting because it means it should be possible in theory to have a library that enables parallel output on any arbitrary set of FlexIO pins - up to 26 pins on T4.0, 32 pins on MicroMod, and 38 pins on T4.1. Also, it appears that the clock pin can be any arbitrary FlexIO pin as well.

It's still necessary to use interrupts instead of DMA when FlexIO3 is involved (and if you wanted maximum speed you would want to stick to just one FlexIO). But otherwise I think there could be a lot of freedom to choose any convenient set of pins.

Here's a proof of concept that ties together all three FlexIOs to make an 8-bit parallel interface. It works fine up to the limit of my test equipment, although I could imagine there could be synchronization issues at really high speeds...
Code:
// proof of concept of using FlexIO1, FlexIO2, and FlexIO3 at the same time

#include <Arduino.h>
#include "FlexIO_t4.h"
#define SHIFTNUM 4 // number of shifters used (must be 1, 2, 4, or 8)
#define BYTES_PER_BEAT (sizeof(uint8_t))
#define BEATS_PER_SHIFTER (sizeof(uint32_t)/BYTES_PER_BEAT)
#define BYTES_PER_BURST (sizeof(uint32_t)*SHIFTNUM)
#define SHIFTER_IRQ (SHIFTNUM-1)
#define TIMER_IRQ 0
#define FLEXIO_BASE_CLOCK 120000000UL
#define SHIFT_CLOCK_DIVIDER 10 // shift clock is 120 MHz divided by 10 = 12 MHz
#define FLEXIO_SHIFT_CLOCK (FLEXIO_BASE_CLOCK/SHIFT_CLOCK_DIVIDER)
#define FLEXIO_ISR_PRIORITY 64 // interrupt is timing sensitive, so use relatively high priority (supersedes USB)

FlexIOHandler *pFlex1, *pFlex2, *pFlex3;
IMXRT_FLEXIO_t *p1, *p2, *p3;

uint8_t databuf[32] = {0x05, 0x0A, 0x14, 0x28, 0x50, 0xA0, 0x40, 0x80, 0x05, 0x0A, 0x14, 0x28, 0x50, 0xA0, 0x40, 0x80, 0x05, 0x0A, 0x14, 0x28, 0x50, 0xA0, 0x40, 0x80, 0x05, 0x0A, 0x14, 0x28, 0x50, 0xA0, 0x40, 0x80};

/* variables used by ISR */
volatile unsigned int bytes_remaining;
volatile unsigned int bursts_to_complete;
volatile uint32_t *readPtr1, *readPtr2, *readPtr3;
uint32_t finalBurstBuffer[SHIFTNUM];
volatile bool pendingTransfer;

/* 
 * Output  FlexIO  Teensy Pin
 *   D0     2:0     10
 *   D1     2:1     12
 *   D2     2:2     11
 *   D3     1:4     2
 *   D4     1:5     3
 *   D5     1:6     4
 *   D6     3:16    8
 *   D7     3:17    7
 *   CLK    3:2     14
 *   
 *   FlexIO1 outputs 8 bits starting on FlexIO1:1 with no clock output.
 *   FlexIO2 outputs 8 bits starting on FlexIO2:0 with no clock output.
 *   FlexIO3 outputs 8 bits starting on FlexIO3:10 with clock output on FlexIO3:2.
 */

void setup() {
    Serial.begin(115200);
    Serial.print(CrashReport);
    Serial.println("Start setup");

    pinMode(15, OUTPUT);
    digitalWriteFast(15, LOW);

    FlexIO_Init();
}

void loop() {
    transmitAsync(databuf, 32);

    delay(1000);
}

void transmitAsync(void *src, uint32_t bytes) {


    while (pendingTransfer) {
      yield(); // wait until previous transfer is complete
    }
    pendingTransfer = true;

    bursts_to_complete = bytes / BYTES_PER_BURST;

    int remainder = bytes % BYTES_PER_BURST;
    if (remainder != 0) {
        memset(finalBurstBuffer, 0, sizeof(finalBurstBuffer));
        memcpy(finalBurstBuffer, (uint8_t*)src + bytes - remainder, remainder);
        bursts_to_complete++;
    }
    bytes_remaining = bytes;
  
    /*   Synchronize FlexIO timers by using precise timing.
     *   FlexIO1, FlexIO2, and FlexIO3 are triggered in sequence with precise delay time equal to the burst time.  
     *   The first two bursts (FlexIO1 and FlexIO2 only) are "dummy" bursts of all zeros with no clock signal. 
     *   Then all three FlexIOs output data simultaneously starting with the third burst.
     *   Triggering occurs when data is written to the last shifter SHIFTBUF[SHIFTNUM-1].
     */

    uint8_t beats = SHIFTNUM * BEATS_PER_SHIFTER;
    p1->TIMCMP[0] = ((beats * 2U - 1) << 8) | (SHIFT_CLOCK_DIVIDER / 2U - 1U);
    p2->TIMCMP[0] = ((beats * 2U - 1) << 8) | (SHIFT_CLOCK_DIVIDER / 2U - 1U);
    p3->TIMCMP[0] = ((beats * 2U - 1) << 8) | (SHIFT_CLOCK_DIVIDER / 2U - 1U);

    uint32_t burst_cycles = (F_CPU_ACTUAL) * ((SHIFTNUM * BEATS_PER_SHIFTER) / ((float) FLEXIO_SHIFT_CLOCK));
    uint32_t cycleStart = ARM_DWT_CYCCNT;
    uint32_t cycleDelay;

    // load zeros for dummy burst without triggering yet
    for (int i = 0; i < SHIFTNUM - 1; i++) {
        p1->SHIFTBUF[i] = 0;
    }

    // trigger dummy burst
    cycleDelay = burst_cycles;
    __disable_irq();
    while (ARM_DWT_CYCCNT - cycleStart < cycleDelay) ;
    p1->SHIFTBUF[SHIFTNUM - 1] = 0;
    __enable_irq();
    while (0 == (p1->SHIFTSTAT & (1 << (SHIFTNUM - 1)))) ; // wait for dummy burst to start

    // load zeros for dummy burst without triggering yet
    for (int i = 0; i < SHIFTNUM - 1; i++) {
        p1->SHIFTBUF[i] = 0;
        p2->SHIFTBUF[i] = 0;
    }

    // trigger dummy burst
    cycleDelay += burst_cycles;
    __disable_irq();
    p1->SHIFTBUF[SHIFTNUM - 1] = 0;
    while (ARM_DWT_CYCCNT - cycleStart < cycleDelay) ;
    p2->SHIFTBUF[SHIFTNUM - 1] = 0;
    __enable_irq();
    while (0 == (p2->SHIFTSTAT & (1 << (SHIFTNUM - 1)))) ; // wait for dummy burst to start

    // load data for first data burst without triggering yet
    if (bytes_remaining < BYTES_PER_BURST) {
        beats = bytes_remaining / BYTES_PER_BEAT;
        p1->TIMCMP[0] = ((beats * 2U - 1) << 8) | (SHIFT_CLOCK_DIVIDER / 2U - 1U);
        p2->TIMCMP[0] = ((beats * 2U - 1) << 8) | (SHIFT_CLOCK_DIVIDER / 2U - 1U);
        p3->TIMCMP[0] = ((beats * 2U - 1) << 8) | (SHIFT_CLOCK_DIVIDER / 2U - 1U);
        readPtr1 = finalBurstBuffer;
        readPtr2 = finalBurstBuffer;
        readPtr3 = finalBurstBuffer;
        bytes_remaining = 0;
    } else {
        readPtr1 = (uint32_t*)src;
        readPtr2 = (uint32_t*)src;
        readPtr3 = (uint32_t*)src;
        bytes_remaining -= BYTES_PER_BURST;
    }
    for (int i = 0; i < SHIFTNUM - 1; i++) {
        p1->SHIFTBUF[i] = *readPtr1++;
        p2->SHIFTBUF[i] = *readPtr2++;
        p3->SHIFTBUF[i] = *readPtr3++;
    }

    // trigger first data burst
    cycleDelay += burst_cycles;
    __disable_irq();
    p1->SHIFTBUF[SHIFTNUM - 1] = *readPtr1++;
    p2->SHIFTBUF[SHIFTNUM - 1] = *readPtr2++;
    while (ARM_DWT_CYCCNT - cycleStart < cycleDelay) ;
    p3->SHIFTBUF[SHIFTNUM - 1] = *readPtr3++;
    __enable_irq();

    asm("dsb");

    // now that clocks are synchronized, use an interrupt to trigger following bursts
    p3->TIMSTAT = (1 << TIMER_IRQ); // clear timer interrupt signal
    p3->TIMIEN |= (1 << TIMER_IRQ);
    p3->SHIFTSIEN |= (1 << SHIFTER_IRQ);

}

FASTRUN void isr() {

    if (p3->TIMSTAT & (1 << TIMER_IRQ)) { // interrupt from end of burst
        p3->TIMSTAT = (1 << TIMER_IRQ); // clear timer interrupt signal
        bursts_to_complete--;
        if (bursts_to_complete == 0) {
            p3->TIMIEN &= ~(1 << TIMER_IRQ); // disable timer interrupt
            pendingTransfer = false;
            asm("dsb");
            transferCompleteCallback();
            return;
        }
    }

    if (p3->SHIFTSTAT & (1 << SHIFTER_IRQ)) { // interrupt from empty shifter buffer
        if (bytes_remaining == 0) { // just started final burst, no data to load
            p3->SHIFTSIEN &= ~(1 << SHIFTER_IRQ); // disable shifter interrupt
        } else if (bytes_remaining < BYTES_PER_BURST) { // just started second-to-last burst, load data for final burst
            uint8_t beats = bytes_remaining / BYTES_PER_BEAT;
            p1->TIMCMP[0] = ((beats * 2U - 1) << 8) | (SHIFT_CLOCK_DIVIDER / 2U - 1U);
            p2->TIMCMP[0] = ((beats * 2U - 1) << 8) | (SHIFT_CLOCK_DIVIDER / 2U - 1U);
            p3->TIMCMP[0] = ((beats * 2U - 1) << 8) | (SHIFT_CLOCK_DIVIDER / 2U - 1U);
            readPtr1 = finalBurstBuffer;
            readPtr2 = finalBurstBuffer;
            readPtr3 = finalBurstBuffer;
            bytes_remaining = 0;
            for (int i = 0; i < SHIFTNUM; i++) {
                p1->SHIFTBUF[i] = *readPtr1++;
                p2->SHIFTBUF[i] = *readPtr2++;
                p3->SHIFTBUF[i] = *readPtr3++;
            }
        } else {
            bytes_remaining -= BYTES_PER_BURST;
            for (int i = 0; i < SHIFTNUM; i++) {
                p1->SHIFTBUF[i] = *readPtr1++;
                p2->SHIFTBUF[i] = *readPtr2++;
                p3->SHIFTBUF[i] = *readPtr3++;
            }
        }
    }

    asm("dsb");
}

void transferCompleteCallback() {
    digitalWriteFast(15, HIGH);
    delayNanoseconds(80);
    digitalWriteFast(15, LOW);
    //  Serial.println("d");
}


void FlexIO_Init() {
  
    /* Get FlexIO channels */
    pFlex1 = FlexIOHandler::flexIOHandler_list[0]; // use FlexIO1
    pFlex2 = FlexIOHandler::flexIOHandler_list[1]; // use FlexIO2
    pFlex3 = FlexIOHandler::flexIOHandler_list[2]; // use FlexIO3

    /* Pointer to the port structures in the FlexIO channels */
    p1 = &pFlex1->port();
    p2 = &pFlex2->port();
    p3 = &pFlex3->port();

    /* Pointer to the hardware structures in the FlexIO channels */
    const FlexIOHandler::FLEXIO_Hardware_t *hw1, *hw2, *hw3;
    hw1 = &pFlex1->hardware();
    hw2 = &pFlex2->hardware();
    hw3 = &pFlex3->hardware();

    /* Basic pin setup */
    pinMode(10, OUTPUT); // FlexIO2:0
    pinMode(12, OUTPUT); // FlexIO2:1
    pinMode(11, OUTPUT); // FlexIO2:2
    pinMode(2, OUTPUT);  // FlexIO1:4
    pinMode(3, OUTPUT);  // FlexIO1:5
    pinMode(4, OUTPUT);  // FlexIO1:6
    pinMode(8, OUTPUT);  // FlexIO3:16
    pinMode(7, OUTPUT);  // FlexIO3:17
    pinMode(14, OUTPUT); // FlexIO3:2
    
    /* High speed and drive strength configuration */
    *(portControlRegister(10)) = 0xFF;
    *(portControlRegister(12)) = 0xFF;
    *(portControlRegister(11)) = 0xFF;
    *(portControlRegister(2)) = 0xFF;
    *(portControlRegister(3)) = 0xFF;
    *(portControlRegister(4)) = 0xFF;
    *(portControlRegister(8)) = 0xFF;
    *(portControlRegister(7)) = 0xFF;
    *(portControlRegister(14)) = 0xFF;

    /* Set clock */
    switch (FLEXIO_BASE_CLOCK) {
        case 480000000UL:
            pFlex1->setClockSettings(3, 0, 0); // (480 MHz PLL3_SW_CLK clock, CLK_PRED=1, CLK_PODF=1)
            pFlex2->setClockSettings(3, 0, 0); // (480 MHz PLL3_SW_CLK clock, CLK_PRED=1, CLK_PODF=1)
            pFlex3->setClockSettings(3, 0, 0); // (480 MHz PLL3_SW_CLK clock, CLK_PRED=1, CLK_PODF=1)
            break;
        case 240000000UL:
            pFlex1->setClockSettings(3, 1, 0); // (480 MHz PLL3_SW_CLK clock, CLK_PRED=2, CLK_PODF=1)
            pFlex2->setClockSettings(3, 1, 0); // (480 MHz PLL3_SW_CLK clock, CLK_PRED=2, CLK_PODF=1)
            pFlex3->setClockSettings(3, 1, 0); // (480 MHz PLL3_SW_CLK clock, CLK_PRED=2, CLK_PODF=1)
            break;
        case 120000000UL:
        default:
            pFlex1->setClockSettings(3, 1, 1); // (480 MHz PLL3_SW_CLK clock, CLK_PRED=2, CLK_PODF=2)
            pFlex2->setClockSettings(3, 1, 1); // (480 MHz PLL3_SW_CLK clock, CLK_PRED=2, CLK_PODF=2)
            pFlex3->setClockSettings(3, 1, 1); // (480 MHz PLL3_SW_CLK clock, CLK_PRED=2, CLK_PODF=2)
            break;
    }
    // Remark: 120 MHz is the maximum FlexIO frequency shown in the reference manual, but testing shows that 240 MHz works.
    // 480 MHz has also been shown to work but some bugs may occur if CPU speed is less than 600 MHz...

    /* Set up pin mux */
    pFlex2->setIOPinToFlexMode(10);
    pFlex2->setIOPinToFlexMode(12);
    pFlex2->setIOPinToFlexMode(11);
    pFlex1->setIOPinToFlexMode(2);
    pFlex1->setIOPinToFlexMode(3);
    pFlex1->setIOPinToFlexMode(4);
    pFlex3->setIOPinToFlexMode(8);
    pFlex3->setIOPinToFlexMode(7);
    pFlex3->setIOPinToFlexMode(14);

    /* Enable the clocks */
    hw1->clock_gate_register |= hw1->clock_gate_mask  ;
    hw2->clock_gate_register |= hw2->clock_gate_mask  ;
    hw3->clock_gate_register |= hw3->clock_gate_mask  ;

    /* configure FlexIO registers */
    FlexIO_Config(p1, 1, 0xFF);
    FlexIO_Config(p2, 0, 0xFF);
    FlexIO_Config(p3, 10, 2);

    /* configure interrupts on FlexIO3 */
    attachInterruptVector(hw3->flex_irq, isr);
    NVIC_ENABLE_IRQ(hw3->flex_irq);
    NVIC_SET_PRIORITY(hw3->flex_irq, FLEXIO_ISR_PRIORITY);

    // disable interrupts until later
    p3->SHIFTSIEN &= ~(1 << SHIFTER_IRQ);
    p3->TIMIEN &= ~(1 << TIMER_IRQ);
}

static void FlexIO_Config(IMXRT_FLEXIO_t *p, uint8_t shifterPin, uint8_t timerPin) {
    // shifterPin and timerPin are FlexIO pin numbers, not Teensy pin numbers

    uint8_t beats = SHIFTNUM * BEATS_PER_SHIFTER;
    Serial.printf("Multi Beat Quantity: %d \n", beats);

    const uint8_t shiftWidth = 8; // 8 bits
    const uint8_t timerIndex = TIMER_IRQ;
    const uint8_t triggerShifterIndex = SHIFTER_IRQ;

    /* Disable and reset FlexIO */
    p->CTRL &= ~FLEXIO_CTRL_FLEXEN;

    /* Configure the shifters */
    for (int i = 0; i <= SHIFTNUM - 1; i++)
    {
        p->SHIFTCFG[i] =
            FLEXIO_SHIFTCFG_INSRC * (1U)                                             /* Shifter input from next shifter's output */
            | FLEXIO_SHIFTCFG_SSTOP(0U)                                               /* Shifter stop bit disabled */
            | FLEXIO_SHIFTCFG_SSTART(0U)                                              /* Shifter start bit disabled and loading data on enabled */
            | FLEXIO_SHIFTCFG_PWIDTH(shiftWidth - 1U);            /* Shifter shift width */
    }

    p->SHIFTCTL[0] =
        FLEXIO_SHIFTCTL_TIMSEL(timerIndex)                         /* Shifter's assigned timer index */
        | FLEXIO_SHIFTCTL_TIMPOL * (0U)                                            /* Shift on posedge of shift clock */
        | FLEXIO_SHIFTCTL_PINCFG(3U)                                              /* Shifter's pin configured as output */
        | FLEXIO_SHIFTCTL_PINSEL(shifterPin)                    /* Shifter's pin start index */
        | FLEXIO_SHIFTCTL_PINPOL * (0U)                                            /* Shifter's pin active high */
        | FLEXIO_SHIFTCTL_SMOD(2U);               /* shifter mode transmit */

    for (int i = 1; i <= SHIFTNUM - 1; i++)
    {
        p->SHIFTCTL[i] =
            FLEXIO_SHIFTCTL_TIMSEL(timerIndex)                         /* Shifter's assigned timer index */
            | FLEXIO_SHIFTCTL_TIMPOL * (0U)                                            /* Shift on posedge of shift clock */
            | FLEXIO_SHIFTCTL_PINCFG(0U)                                              /* Shifter's pin configured as output disabled */
            | FLEXIO_SHIFTCTL_SMOD(2U);               /* shifter mode transmit */
    }

    /* Configure the timer for shift clock */
    p->TIMCMP[timerIndex] =
        ((beats * 2U - 1) << 8)                                     /* TIMCMP[15:8] = number of beats x 2 – 1 */
        | (SHIFT_CLOCK_DIVIDER / 2U - 1U);                          /* TIMCMP[7:0] = shift clock divide ratio / 2 - 1 */

    p->TIMCFG[timerIndex] =   FLEXIO_TIMCFG_TIMOUT(0U)                                                /* Timer output logic one when enabled and not affected by reset */
                              | FLEXIO_TIMCFG_TIMDEC(0U)                                                /* Timer decrement on FlexIO clock, shift clock equals timer output */
                              | FLEXIO_TIMCFG_TIMRST(0U)                                                /* Timer never reset */
                              | FLEXIO_TIMCFG_TIMDIS(2U)                                                /* Timer disabled on timer compare */
                              | FLEXIO_TIMCFG_TIMENA(2U)                                                /* Timer enabled on trigger high */
                              | FLEXIO_TIMCFG_TSTOP(0U)                                                 /* Timer stop bit disabled */
                              | FLEXIO_TIMCFG_TSTART * (0U);                                            /* Timer start bit disabled */

    if (timerPin != 0xFF) { // output clock signal

        p->TIMCTL[timerIndex] =
            FLEXIO_TIMCTL_TRGSEL((triggerShifterIndex << 2) | 1U)                             /* Timer trigger selected as highest shifter's status flag */
            | FLEXIO_TIMCTL_TRGPOL * (1U)                                              /* Timer trigger polarity as active low */
            | FLEXIO_TIMCTL_TRGSRC * (1U)                                              /* Timer trigger source as internal */
            | FLEXIO_TIMCTL_PINCFG(3U)                                                /* Timer' pin configured as output */
            | FLEXIO_TIMCTL_PINSEL(timerPin)                         /* Timer' pin index */
            | FLEXIO_TIMCTL_PINPOL * (1U)                                              /* Timer' pin active low */
            | FLEXIO_TIMCTL_TIMOD(1U);                                                 /* Timer mode 8-bit baud counter */

    } else { // no clock output

        p->TIMCTL[timerIndex] =
            FLEXIO_TIMCTL_TRGSEL((triggerShifterIndex << 2) | 1U)                             /* Timer trigger selected as highest shifter's status flag */
            | FLEXIO_TIMCTL_TRGPOL * (1U)                                              /* Timer trigger polarity as active low */
            | FLEXIO_TIMCTL_TRGSRC * (1U)                                              /* Timer trigger source as internal */
            | FLEXIO_TIMCTL_PINCFG(0U)                                                /* Timer' pin output disabled */
            | FLEXIO_TIMCTL_TIMOD(1U);                                                 /* Timer mode 8-bit baud counter */
    }

    /* Enable FlexIO with fast access */

    p->CTRL |= FLEXIO_CTRL_FLEXEN | FLEXIO_CTRL_FASTACC;
}

Screenshot of logic analyzer sampling at 24 MHz, showing good synchronization. D7 not shown, since I only have an 8 channel logic analyzer and CLK is connected to D7.
multiFlexIO.jpg
 
Very cool. Note I do not mind if data sent out is polled, I can imagine that DMA could have an advantage in many cases. Keep up the haxxoring!
 
@easone - amazing work!
It would be worth hooking this up to one of the ILI displays and trying to send commands/data and see if it can accept anything.
Now I see how this would work for writing data out from the Teensy, but can we flip it around and read data in from the parallel port?
 
What I'd love to see is a 4:3 TFT that's 640x480, seems that they're no where to be found, because everyone thinks 16:9 is all cool and stuff, bleh.
Why? well, I'm not rendering fancy graphics, I just want a terminal I can stuff in my pocket :)

A little late, but what about this 800*480 display, and just display sidebars to make it 640*480 :) It's NT35510 based, but it's a very similar controller to the ILI9488 command set, and affordable. I've used these in a project, and really like them!

https://www.aliexpress.com/item/32948485780.html?spm=a2g0o.productlist.0.0.4cea6976r7xU0Z
 
A little late, but what about this 800*480 display, and just display sidebars to make it 640*480 :) It's NT35510 based, but it's a very similar controller to the ILI9488 command set, and affordable. I've used these in a project, and really like them!

https://www.aliexpress.com/item/32948485780.html?spm=a2g0o.productlist.0.0.4cea6976r7xU0Z

It's never too late.
Pixels might be a bit small, but that's actually a pretty good find. I guess I could even use the unused screen real estate for other data.
I'm mostly interested in running https://github.com/xxxajk/fbcon which if you try out, will show you the reason why I'd like to have exacting dimensions. :cool:
 
It's never too late.
Pixels might be a bit small, but that's actually a pretty good find. I guess I could even use the unused screen real estate for other data.
I'm mostly interested in running https://github.com/xxxajk/fbcon which if you try out, will show you the reason why I'd like to have exacting dimensions. :cool:
The pixels are a little small :) In my project that uses this, I display log files from my app, in a small but legible font - not something I'd want to read for a long period, though!

20220307_081400_888.jpg

I'm about to try a 7.0" 800*480 SSD1963 based display, once it arrives and I modify my parallel driver to handle that screen. Found for $35 on AliExpress, which wasn't too bad. Will see how that looks.
 
The pixels are a little small :) In my project that uses this, I display log files from my app, in a small but legible font - not something I'd want to read for a long period, though!

View attachment 27763

I'm about to try a 7.0" 800*480 SSD1963 based display, once it arrives and I modify my parallel driver to handle that screen. Found for $35 on AliExpress, which wasn't too bad. Will see how that looks.

How are you exporting the screenshot? Are you writing it directly to the SD card or through a buffer of some sort?
In LVGL v8 there is a screen-capture API that will export a C array of the current screen - this might be faster than reading the GRAM from the display that you had mentioned to me in PM
 
The console program that I have designed does 8x8 characters, none of that silly anti-aliasing nonsense, and no possibility yet to scale up the size.
Why 8x8 pixels fixed? Because I like my pixels to look like pixels, not like I need a new set of glasses. Anti-aliasing drives me nuts when applied to text, and makes it look blurry.
 
How are you exporting the screenshot? Are you writing it directly to the SD card or through a buffer of some sort?
In LVGL v8 there is a screen-capture API that will export a C array of the current screen - this might be faster than reading the GRAM from the display that you had mentioned to me in PM
Yes, I currently do this by reading the TFT GRAM directly, either into a full screen buffer into PSRAM (double slow :) ) or in chunks into a buffer in TCM, and then write to the SD card as a .BMP, configurable to be RGB555, 565 or 888. I have some screens in this project where I write to part of the TFT directly, outside of LVGL, for some high speed use cases which LVGL can't handle, such as high-speed scrolling graphs or video playing, so LVGL doesn't have the entire screen "known" to it. I am converting the graphs to LV_CANVAS though, with decent performance - you just define a memory buffer for the LV_CANVAS and write directly to that buffer, and let LVGL handle the rendering. It isn't quite as fast, but it's more flexible for letting me mix LVGL elements into that part of the screen and given I use only a handful of colours for that, can define the canvas as 4 or 8 BIT indexed palette, so the memory required is lower.

Once that conversion is done, I'll check out the LVGL screen capture API, haven't tried it before, it could be handy! But it's still nice to have a screen capture that's independent of how you draw to the screen and even using a partial buffer, and it's not awful performance for a "screenshot" - a full read of 800*480 over 8 bit parallel via FlexIO takes 579ms and via GPIO 251ms, including the writes to SD card, and I haven't obsessed on optimizing the read part like I have the write part :)
 
The console program that I have designed does 8x8 characters, none of that silly anti-aliasing nonsense, and no possibility yet to scale up the size.
Why 8x8 pixels fixed? Because I like my pixels to look like pixels, not like I need a new set of glasses. Anti-aliasing drives me nuts when applied to text, and makes it look blurry.

20220307_150121_888.jpg

There you go :)
 
What are you measuring up top left? (49c and 68Hz)?
It's just a rotating display of some key info as I develop...the first screenshot showed it when it had power (current) usage and screen bit depth displayed, the second showed CPU temp and TFT refresh rate.
 
Not had a chance to look at it yet; I just changed the font I used in my app to be more like one you prefer :)

Well, it may interest you, or perhaps not, but... with my (unfinished) console lib, you can even have flashing text. :-D
Should work if your library is fairly normal-ish. Doesn't depend too much on what is under the hood. Even works on monochrome displays.
Also \007 can output a beep to a buzzer via pwm.
 
The actual text and attributes are stored in RAM.
The display is updated one when:
  1. A character changes
  2. A character with the blink attribute is set and it needs to invert the character
  3. The cursor flashes
A full display refresh and blink is set for 0.5 Seconds, however refresh can be forced at any time.

I don't understand where DMA would have much play, since it's all in RAM anyway. If the *display driver* does DMA, this of course will just work.
 
Back
Top