tiistai 17. helmikuuta 2015

Playing with Chrom-Art accelerator of STM32F429I Discovery


Previously we got something to show on screen, but writing data to screen pixel-by-pixel in software isn't very efficient. Fortunately STM32F429 has Chrom-Art accelerator build it that we can offload some operations to.

When thinking of graphics accelerator one easily thinks current high-end PC graphics board and their embedded alternatives, with massive binary-only drivers and inaccessible datasheets the manufacturer might quickly flash for you, assuming you visit their office, pass background check and lie detector test and sign an NDA.

Fortunately this accelerator isn't one of those and everything is in datasheets (well, I hope, unless they've kept something hidden). On the flipside, unfortunately it isn't very powerful either, being limited to simple pixel-by-pixel (no scaling) copy- and blend operations.

The accelerator is quite clearly intended to be used in RGB (16-bit and above) modes, as it does not officially even support 8-bit output modes (it can however read 8-bit data as input and convert it to RGB data for blending/output). So, since I am now using 8-bit output mode for now, I won't be getting a lot out of it. By lying to it about source- and target modes we can however  use it for faster bitmap copies for drawing, without blending though.

But the code. Unlike most peripheral devices DMA2D (two dimensional DMA transfer, get it?) doesn't require other initialization than enabling the clock, which we already did during display initialization previously, so we can proceed directly to operations;


First simple helper function, this does nothing but waits until previous operation is done so we can start next.
/* -------------------------------------------------------------
 * Wait until DMA2D operation is done.
 */
void dispWaitDMA()
{
  unsigned int n = 0;
  while (DMA2D->CR & 1) // wait until transfer is finished
    { if (++n >= 2000000)
        { serialWrite((unsigned char*)"DMA2D fail\r");
          return;
        }
    }
}


// very simple test bitmap; 16x16, 8-bit data.
static const char bitmap1[] = {
     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     0, 0, 0, 0,15,15,15,15,15,15,15, 0, 0, 0, 0, 0,
     0, 0,15,15,15,15,15,15,15,15,15, 0, 0, 0, 0, 0,
     0, 0,15,15,15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     0, 0,15,15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     0, 0,15,15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     0, 0,15,15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     0, 0,15,15,15,15,15,15, 0, 0, 0, 0, 0, 0, 0, 0,
     0, 0,15,15,15,15,15,15, 0, 0, 0, 0, 0, 0, 0, 0,
     0, 0,15,15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     0, 0,15,15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     0, 0,15,15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     0, 0,15,15, 0, 0, 0, 0,20,26,31,31,28,26,23, 0,
     0, 0,15,15, 0, 0, 0, 0,20,26,31,31,28,26,23, 0,
     0, 0, 0, 0, 0, 0, 0, 0,20,26,31,31,28,26,23, 0,
     0, 0, 0, 0, 0, 0, 0, 0,20,26,31,31,28,26,23, 0 };



Then drawing a box. I'm using L8 display mode which controller does not officially support, so we lie to it and claim that we are writing ARGB4444 data. Since controller does nothing but memory writes without conversions this works, albeit at same time limiting us to operations with only even number of pixels.
/* -------------------------------------------------------------
 * Draw box filled with single color.
 */
void dispDrawBox(unsigned int x, unsigned int y, unsigned int w, unsigned int h, unsigned int color)
{

   // Controller does not officially support indexed mode for register-memory transfer,
   // so we cheat a bit - we claim to be using ARGB4444 (16bit) mode instead, but fill
   // it with L8L8 data. Since color space conversions are not done, this will go through
   // with no problems.
   // As a side effect this limits us to even addresses and widths, so x and w *must* be even.
  DMA2D->OPFCCR = 4; // ARGB4444. We're lying - we use it as L8L8 instead.
   // Color is 8-bit index value; since register expects 16-bits we duplicate it.
  DMA2D->OCOLR = color + (color << 8);

   // Target address. LSB of X can be cleared to force even address.
  DMA2D->OMAR = (unsigned int)(dispFrameBuff + x + y*DISP_WIDTH); 
   // Controller thinks it's drawing 16-bit data, so /2's are here to correct that.
  DMA2D->OOR = ((DISP_WIDTH-w)/2); // Display pitch, in 16-bit units
  DMA2D->NLR = ((w/2) << 16) | (h); // Width and height

  DMA2D->CR = (3 << 16) | 1; // 3=register-to-memory, start.
  
  dispWaitDMA();
}
Then the bitmap operation. Again, 8-bit output mode limits us to full bitmap copy operations with no transparency (alpha channel), but even that can be helpful. Now we have to lie about source format too; if source and output formats aren't same, DMA2D tries to do format conversions which will cause problems as data isn't what it expects.
/* -------------------------------------------------------------------
 * Copy bitmap from memory (sram or flash) to display buffer.
 * This assumes that source buffer is exactly (w*h) bytes in size and has L8 data in it.
 */
void dispBlit(unsigned int x, unsigned int y, unsigned int w, unsigned int h, const void *adx)
{
    // Memory-memory transfer.
    //  PFC can only be used with RGB output modes so that is unavailable,
    //  restricting this to full square (no transparency) transfers.
    //  Same limitations with width as dispDrawBox also apply.
  
   // Output as rectangle.
  DMA2D->OMAR = (unsigned int)(dispFrameBuff + x + y*DISP_WIDTH);
  DMA2D->OOR = ((DISP_WIDTH-w)/2); 
  DMA2D->NLR = ((w/2) << 16) | (h); 
  
  DMA2D->FGMAR = (unsigned int)(adx); // foreground (source buffer) address.
  DMA2D->FGOR = 0; // zero row offset; pixels rows are directly after each other in source buffer.
  
  DMA2D->OPFCCR = 4; // Output format; ARGB4444. We're lying - we use it as L8L8 instead (output stage doesn't support L8 mode)
  DMA2D->FGPFCCR = 4; // Source format ARGB4444. Lying again due to output format limitations; using L8 (although that is our data) will not work here.
  
  DMA2D->CR = 1; // mem-mem transfer (fg only), start transfer.

  dispWaitDMA();
}
So there, simple bitmap operations. RGB modes would offer more freedom here, including blending, but for now I'm happy to work with lesser memory footprint. After all, I'm planning more informative display (think something like smart display for home heating and individual device tracking/control) over flashy graphics.

Ei kommentteja:

Lähetä kommentti