0

I am trying to write a bare metal code to program PL111 LCD controller. I am using QEMU emulator set up for Realview ARM Cortex-A8. I earlier managed to print characters on the linux terminal window using QEMU's "-serial stdio" option.

I have gone through PL111 document and I can't figure out one thing. I set up PL111 controller's LCDUPBASE tovcontain the address of the frame buffer in memory. But can I simply write ascii values to the frame buffer and the LCD controller would pick my frame buffer and display the corresponding characters on the screen or do I need to perform some kind of conversion on ascii values( as per an already existing standard which I am not aware of) before writing them to the frame buffer?

In case of the former being true, is this conversion handled by the controller itself as per some conversion table in controller hardware? What about stuff such as background colour? The PL111 document doesn't say anything about this. This hurdle also made me think about the role of a GPU, if I were having a GPU as well, where would it fit it in this scheme and what exactly would be its role?

Are there any good resources, documents or books that can help to understand these concepts better. Pardon me if my questions sound stupid. I don't have much experience of embedded/peripheral programming, I am basically trying to learn/get acquainted with ARMv7 architecure and i thought it would be nice and interesting if I could get to print my assembly programming prints on the QEMU console rather than linux console. (using "-serial stdio" option)

I'd really appreciate it if people here can help me with this. Thanks

/* boot.s */

.section .data

.section .bss

.section .text

.globl _start

_start:

/*** Interrupt Vector Table Start**/

b _RESET_HANDLER                /* Reset Handler        */
b _UNDEF_HANDLER        /* Undef Instruction Handler    */
b _SWI_HANDLER          /* Software Interrupt Handler   */
b _PREFETCHABORT_HANDLER    /* Prefect Abort Handler    */
b _DATAABORT_HANDLER        /* Data Abort Handler       */
b _IRQ_HANDLER          /* IRQ Handler          */
b _FIQ_HANDLER          /* FIQ Handler          */

/*** Interrupt Vector Table End******/

_FIQ_HANDLER:

b .     /* Not implemented yet, so go in infinite loop */

_IRQ_HANDLER:

b .    /* _isr_irq   /* jump to interrupt service routine */

_DATAABORT_HANDLER:

b .     /* Not implemented yet, so go in infinite loop */

_PREFETCHABORT_HANDLER:

b .     /* Not implemented yet, so go in infinite loop */

_SWI_HANDLER:

b .     /* Not implemented yet, so go in infinite loop */

_UNDEF_HANDLER:

b .     /* Not implemented yet, so go in infinite loop */

_RESET_HANDLER:

b _initialize_cpu

cpuinitialize.s =>

.section .data

.section .bss

.section .text

.globl _initialize_cpu

_initialize_cpu:

/* LCD initialization code */

    .include "ColourLCDPL111.s"
    .set SYS_OSC4, 0x1000001C  /* Mapped register for OSCCLK4*/ 
    .set SYS_LOCK, 0x10000020  /* reference clock CLCDCLK for PL111*/

     movw r0, #:lower16:SYS_LOCK          /* Unlocking the register*/
     movt r0, #:upper16:SYS_LOCK
     movw r1, #0xA05F    
             str  r1, [r0]

         movw r2, #:lower16:SYS_OSC4   /* Setting the CLCDCLK frequency 36MHz*/
         movt r2, #:upper16:SYS_OSC4
         movw r1, #0x2CAC    
                 str  r1, [r2]

     str  r1, [r0]                /* Locking the register again*/


         movw r0, #:lower16:LCDTiming0_ADDR
         movt r0, #:upper16:LCDTiming0_ADDR
         movw r1, #:lower16:0x1313A4C4      /* PPL = 49 ; HSW = 3 TODO:change*/
         movt r1, #:upper16:0x1313A4C4      /* HBP = 5  ; HFP = 5 */
             str  r1, [r0]

             movw r0, #:lower16:LCDTiming1_ADDR
             movt r0, #:upper16:LCDTiming1_ADDR
             movw r1, #:lower16:0x0505F657        /* LPP = 600 ; VSW = 2 TODO:change*/
             movt r1, #:upper16:0x0505F657        /* VBP = 2   ; VFP = 2 */
             str  r1, [r0]


     movw r0, #:lower16:LCDTiming2_ADDR
     movt r0, #:upper16:LCDTiming2_ADDR
     movw r1, #:lower16:0x071F1800          /* CPL[25:16] = 799     ; BCD[26] =  1 (PCD Bypassed)   */
     movt r1, #:upper16:0x071F1800          /* PCD        = ignored */                  
             str  r1, [r0]


     movw r0, #:lower16:LCDUPBASE_ADDR   /* Setting up frame buffer address to 0x00000000*/
     movt r0, #:upper16:LCDUPBASE_ADDR
     mov  r1, #0x0  
     str  r1, [r0]

     movw r0, #:lower16:LCDControl_ADDR     
     movt r0, #:upper16:LCDControl_ADDR
     movw  r1, #0x082B          /* Setting up TFT 24Bit Mode */  
     str  r1, [r0]

     movw r0, #:lower16:LCDIMSC_ADDR    /* LCD interrupts: Disabled for now */
     movt r0, #:upper16:LCDIMSC_ADDR
     mov  r1, #0x00000000            
     str  r1, [r0]

     mov  r0, #40            /* lets try to print 'A' at frame buffer + 40 */
     mov  r1, #65   
     str  r1, [r0]

Code snippet for filling the frame buffer that actually turned the whole screen white. I took a randomly large value 10000 when simply using i=800 and j=600x4 didn't work

  void PopulateFrameBuffer(void)
  {
  unsigned int i,j;
  unsigned char *ptr = (unsigned char *)0x0;
  for(i=0; i<800;i++)
    {
    for (j=0;j<(600*10000);j++)
    {
      *ptr++=0xFF;

    }
  }
 }

I called this function from assembly after initialization code. Frame buffer start address is 0x00000000

san216
  • 85
  • 1
  • 11
  • Have you tried just writing ASCII values to the frame buffer? What happens? – JeremyP Apr 10 '12 at 10:37
  • @JeremyP I haven't tried that yet, I have only written some initialization code as of now. I'll use 24bit RGB format for TFT mode. I am thinking if my frame buffer contains 24bit RGB values, then how are they not going to be interpreted as colour values but as ascii characters and what would decide the background colour if it does get interpreted as ascii characters. I have flipped through a few other LCD controller specs but none of them talked about this. Also I also read that in VGA programming, in pixel data, you have bit fields for encoding background,forground colour and ascii characters – san216 Apr 11 '12 at 05:00
  • Well I would certainly experiment with the controller to see what happens. As it's QEMU, you can't do any damage. – JeremyP Apr 11 '12 at 07:40
  • @JeremyP I tried writing ascii values to the frame buffer as suggested – san216 Apr 11 '12 at 15:58
  • @JeremyP I tried writing ascii values to the frame buffer as suggested by you but it didn't get printed on the console. I have put my code in my post's edit. QEMU console window does widen up from its default size though and i see a tiny bit of smudge of colours at the top left corner but that's about it. I have searched quite a bit on this but haven't managed to find anything substantial as yet. 24Bit values in frame buffer are just RGB values for a pixel. Several such pixels would form a character. There has to be some sort of standard method/algorithm to encode pixels in such a manner – san216 Apr 11 '12 at 16:09
  • @JeremyP By the way, thanks for reformatting my post : ). I apologize for the clumsiness. – san216 Apr 11 '12 at 16:13
  • @JeremyP I managed to write a Printf like function that formats, takes input arguements and puts the corresponding ascii characters on the LCD screen but it supports only "%x" format string as of now. And there is a little bug in line feed (\n) that i've to work on, otherwise works just like the regular printf. It takes variable number of arguements and I have implemented it purely in assembly, it can be called from C code using the usual printf("Value of a : %x", a) ;) – san216 Apr 20 '12 at 06:28

1 Answers1

2

This isn't really an answer, it's more of a comment, but the comment box only allows limited space and formatting.

According to this knowledge base article, the PL111 supports only pixel mapped modes. i.e. you have to turn on and off each pixel. So, if you have chosen 24 bit true colour, 24 bits from the frame buffer are used to control the colour of each pixel, 8 for red, 8 for green and 8 for blue. how those bits are organised is anybody's guess. For instance:

  • it might use a contiguous sequence of three bytes,
  • or it might use a contiguous sequence of four bytes but not using the most significant byte
  • or it might organise the bitmap into planes, for instance, all the red values, followed by all the green values, followed by all the blue values.
  • as an extreme option, you could have up to 24 planes, so the first bit of all pixels come first followed by the second bit of all pixels and so on.

My guess is that the first option is the one used (24 contiguous bits per pixel). You can test this by putting 0xFF in the first six bytes of the frame buffer. That should turn the left hand two pixels on the top row white. If it turns the first white and the second pixel cyan, you know that you have 32 bits per pixel ignoring the most significant byte (and the bytes are little endian). If the first six pixels turn red or another colour, you have a frame buffer organised into planes.

In order to make actual characters come out, you'll need a bit map for each possible character and you'll need to blit them into the frame buffer. You can do this in software, but there should be some sort of hardware support available.

Edit

OK, let's set how to put a character into the frame buffer. I'll use C because my ARM assembly skills aren't fantastic. Also, this is totally untested or even compiled

Assume 8 x 8 characters packed 8 pixels to the byte and a 24 bit colour frame buffer.

 #define PIXEL_WIDTH 3   // width in bytes of a pixel
 #define CHAR_WIDTH  8
 #define CHAR_HEIGHT 8

 #define RED_BYTE    2   // Index of the RGB components in the pixel in the framebuffer (little endian)
 #define GREEN_BYTE  1
 #define BLUE_BYTE   0

 struct FrameBufferDescriptor
 {
     uint8_t* start;           // Address of first byte in the buffer
     size_t rasterLineWidth;   // width in bytes of each line of pixels in the display.
 };

/*
 *  Draw a character at the (x, y) coordinates (0, 0) is top left.
 *  frameBuffer: describes position and width of the frame buffer
 *  x, y: coordinates of top left corner of the character
 *  characterMap: monochrome bitmap of 1 x 8 x 8 character.  Pixel packed 8 per byte.  
 *                If a bit is set, it means foreground colour, if cleared, it means 
 *                background colour
 *  foreground, background: RGB colours for foreground and background.
 */
void drawCharacter(struct FramBufferDescriptor* frameBuffer, size_t x, size_t y, uint8_t* characterMap, uint32_t foreground, uint32_t background)
{
    // first where to start drawing in the frame buffer
    uint8_t* destination =  frameBuffer->start + (y * rasterLineWidth) + (x * PIXEL_WIDTH);
    // char is 8 x 8
    for (int row = 0 ; row < CHAR_HEIGHT ; ++row)  // iterate for each row
    {
        for (pixel = 0 ; pixel < CHAR_WIDTH ; ++pixel)   // iterate for each picxel on the row
        {
            // determine the coloutr of this pixel
            uint32_t colour = background;
            if ((characterMap[row] & (1 << (CHAR_WIDTH - pixel - 1))) != 0) // Most sig bit will be on the left
            {
                colour = foreground;
            }
            // Put the RGB values in the framebuffer
            destination[pixel * PIXEL_WIDTH + RED_BYTE] = colour >> (RED_BYTE * 8);
            destination[pixel * PIXEL_WIDTH + GREEN_BYTE] = colour >> (GREEN_BYTE * 8);
            destination[pixel * PIXEL_WIDTH + BLUE_BYTE] = colour >> (BLUE_BYTE * 8);
        }
        destination += frameBuffer->rasterLineWidth;  // Go to next line of the frame buffer
    }
}
JeremyP
  • 84,577
  • 15
  • 123
  • 161
  • Whatever you said in the first part of your answer is given in the PL111 document, the stuff about how bits are packed for a given bpp. Its the last paragraph of your answer that I was looking for. "A bit map for each possible character" actually answered me to some extent. I did an internet search on this and in my search I found people talking about 8x8 ascii bit maps,fonts like in this link http://forum.osdev.org/viewtopic.php?f=2&t=22033 . While I have understood the idea, I am still unclear about how to actually implement it – san216 Apr 12 '12 at 15:40
  • In this link http://forum.osdev.org/viewtopic.php?f=2&t=22033, this fellow has given a header file with a 2D array of 128x8 which he calls 8x8 Bitmap font. Firstly how is it 8x8? and secondly can bitmap and font be used interchangeably to express the same meaning? And how do i use this bitmap to populate my framebuffer, do I use the ascii value as a row index into the above 2D bitmap array and then use those 8 row values and arrange them in my frame buffer in a rectangular block of 4x2 pixel data or something like that? (It should be 8x8 as he called it but 8x8 is impossible with only 8 vals – san216 Apr 12 '12 at 15:58
  • I have been grappling with this for quite a few days now and my implementation has got kind of stuck and I can't seem to bring myself to skipping this part and implementing other things. I have only managed to find scattered information on this whole thing. What I am looking for is a simple but concrete tutorial/document that explains things from the scratch and I would build upon it from there and adapt it to my requirement. And I'd really like to thank you for devoting your time trying to help me. – san216 Apr 12 '12 at 16:06
  • @san216: The whole font is in one two dimensional array. He has 128 8 x 8 characters. Don't forget that to represent 1 8 pixel row takes only 1 byte. – JeremyP Apr 13 '12 at 07:52
  • Yes, I got that later that he was actually using each bit of the byte to represent a pixel. Thus each pixel can be just 1 or 0. I just wrote a code to map this 1bbp bitmap to an 24bbp rgb bitmap for each pixel where 1 would be represented by 24Bit (0,0,255) i.e red colour and zero would be (0,0,0). I am yet to test it. – san216 Apr 13 '12 at 08:20
  • @san216: added an example of how to draw a bitmap on the screen. – JeremyP Apr 13 '12 at 08:28
  • Thanks for the code you provided. I already tried to do something similar but this time the problem was something else. Nothing was visible on the screen except for the smudge on the top left corner of the screen, I tried changing framebuffer address, even chaning the mode to 1bpp from 24bpp but nothing happened. So eventually I thought I'll fill the whole frame buffer with 0xFF and see if I get a white screen. I programmed the display at 800x600 resolution hard coding the values at this link http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0224i/Cachedgd.html – san216 Apr 14 '12 at 02:36
  • Calculating the values could be error prone so I thought it would be better to use the values from link above directly. Now when I tried to fill the 800x600x4(4bytes/pixel of which pixel serializer would take first 24bits of each byte) only half of the line at the top of the screen turned white. I had to fill much larger area of the frame buffer(800x600x10000) to get the whole screen white. Is there another scaling factor I am not taking into account? I believe because of this my characters didn't appear on the screen. I have provided the framebuffer filling code snippet in the edit. – san216 Apr 14 '12 at 02:42