How to efficiently fill word array from flash table?

Started by trastikata, Jun 26, 2024, 03:23 PM

Previous topic - Next topic

trastikata

Hi,

For the purpose of pre-loading a font character in memory, currently I am looping through the elements of a word array and filling from a flash table for each element.

QuoteFor bCounter = 0 to 7
  MyWordArray[bCounter] = CRead16 MyFont[bCounter + Offset]
Next

Dim MyFont as Flash16 = ....

I am certain there's more efficient way to set the array but I can't think of it. 

John Lawton


trastikata


TimB


trastikata

Quote from: TimB on Jun 26, 2024, 04:50 PMIs this a 8bit or 16 bit device?

Hi TimB,

it's a 16b device, but would it make difference?

top204

Dim WordArray[10] As Word
Dim FlashTable As Flash16 = 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
 
WordArray = FlashTable

The elements in the word array will be filled with the contents of the flash memory table using a very efficient assembler mechanism.

trastikata

Thank you Les, however this is for a font, so I'll need to be able to offset the address in the table.

Currently I am trying to make it work this way, but it doesn't work as expected and I can't understand why:

Proc TftSearchChar(bChar As Byte)
    '0 converted to space
    If bChar = 0 Then bChar = $20
    'Small letters converted to capital letters
    If bChar >= $61 And bChar <= $7A Then bChar = bChar - $20
    'Offset to the start of the table
    bChar = bChar - 32
    'Load the table address
    wTftFontPosition = AddressOf(BoldFont) + bChar * 16
    'Fill the pixel array
    For bChar = 0 To 7
        waTftPrintArray[bChar] = cPtr16(wTftFontPosition++)
    Next
EndProc

Dim BoldFont As Flash16 = $0000, $0000, $0000, $0000, $0000, $0000, $0000, $0000,_ 'SP
$0000, $0000, $0000, $0000, $0000, $0000, $0000, $0000,_ '!
$0000, $0000, $0000, $0000, $0000, $0000, $0000, $0000,_ '"
$006C, $00FE, $00FE, $006C, $00FE, $00FE, $006C, $0000,_ '#
$0000, $0000, $0000, $0000, $0000, $0000, $0000, $0000,_ '$
$00CE, $006A, $002E, $0010, $00E8, $00AC, $00E6, $0000,_ '%
$0000, $0000, $0000, $0000, $0000, $0000, $0000, $0000,_ '&
$0000, $0000, $0000, $0000, $0000, $0000, $0000, $0000,_ ''
$01C0, $0060, $0030, $0018, $0018, $0030, $0060, $01C0,_ '(
$000E, $0018, $0030, $0060, $0060, $0030, $0018, $000E,_ ')
.....

TimB

This is a bit of code that does the same thing on an 8 bit device
(witten by Les)

I can send you the whole inc if you want to get an idea on the way he wrote this display driver?


'-------------------------------------------------------------------------------------------------
' Draw a full image to the LCD's display buffer RAM
' Input    : pImageAddr holds the address in flash memory that stores the image
' Output    : None
' Notes    : None
'
Proc ST_Shadow_FullImage(BycRef pImageAddr As Global_ST_lTblPtr)
    Dim bXpos As Global_ST_bTemp2                                  ' An alias to hold the X position

    Global_ST_wFSR0 = AddressOf(Global_ST_bBuffer)                  ' Load FSR0L\H with the address of the display buffer array
    Global_ST_bPage = 0                                            ' \ Create a loop for the amount of pages (lines) in the display
    Repeat                                                          ' /
        bXpos = 0                                                  ' \ Create a loop to move across the X position
        Repeat                                                      ' /
            Tblrd*+                                                ' Read from Flash memory with auto increment
            POSTINC0 = TABLAT                                      ' Transfer the image to the display RAM buffer
            Inc bXpos                                              ' \
        Until bXpos >= ST_cWidth                                    ' / Close the X position scan loop
        Inc Global_ST_bPage                                        ' \
    Until Global_ST_bPage.3 = 1                                    ' / Close the page loop
EndProc

trastikata

Thank you TimB, but this particular to 8-bit devices. I am looking for a more generic method.

I am still trying to debug why the code above that I posted is not working as expected.

top204

Why are you using a RAM array for the font character bitmaps trastikata? They are very efficient when read directly from flash memory, especially on the 16-bit devices.

Take a look at the "ILI9320.inc" file in the "C:\Users\Les\PDS\Includes\" folder. This is for the 16-bit devices and uses the memory directly from flash. Take a look at the "__Print:" subroutine.

You will also notice that I used the device's Wx registers a lot in the routines because the compiler recognises when these are being used and can optimise its assembler code to make things smaller and faster. The compiler alias' the Wx registers to WREGx register names in the high level language to make things a bit more clearer:

' Display an ASCII character
' Input     : WREG0 holds the character to display
' Output    : WREG0 still contains the character that was displayed
' Notes     : _Glcd_Ypos holds the Y position (in pixels)
'           : _Glcd_Xpos holds the X position (in pixels)
'           : WREG5 holds the character image width
'           : WREG7 is used for the character height loop
'           : WREG8 is used for the character X pixel loop
'           : WREG9 is used for the amount of bytes to read loop
'
#ifSym __SYSCOM_GLCD_PRINT_REQ_
__Print:
'
' Locate the character's image data from the code memory table
'
    Push.d WREG0                                            ' Save WREG0 and WREG1
    Push.d WREG8                                            ' Save WREG8 and WREG9
    Push.d WREG12                                           ' Save WREG12 and WREG13
    WREG0 = WREG0.Byte0                                     ' Clear the top byte of WREG0
    WREG0 = WREG0 - 32                                      ' Remove the ASCII offset
    WREG0 = WREG0 * 2                                       ' Offsets are 2 bytes each (words)
    WREG12 = Glcd_wOffsetAddress + WREG0                    ' Load WREG12 with the font image offset within the the table
    WREG12 = cPtr16(WREG12++)                               ' Read and store the 16-bit font character image address
    WREG12 = WREG12 + Glcd_wOffsetAddress                   ' Point to the font image within the table
    WREG5 = cPtr8(WREG12++)                                 ' Read the character's image width
'
' Place the character on the LCD
'
    Glcd_WriteReg($03,%0001000000111000)                    ' Set scanning mode for Horizontal writes, Horizontal increment, Vertical increment
'
' Create a window for the size of the character
'
    Glcd_SetWindow(_GLCD_XPOS, _GLCD_YPOS, (_GLCD_XPOS + WREG5) - 1, (_GLCD_YPOS + Glcd_wFontHeight) - 1)
    Glcd_hEnable()                                          ' Enable the LCD again
    Glcd_hSetCGRAM(_GLCD_XPOS, _GLCD_YPOS)                  ' Point to CGRAM
    _GLCD_XPOS = _GLCD_XPOS + WREG5                         ' Update the cursor position ready for the next character
    Repeat
        WREG7 = Glcd_wFontHeight                            ' Set the character height counter
        WREG9 = Glcd_bFontAmountReads                       ' Store the amount of bytes to read for this segment
        Repeat
            WREG13.Byte0 = cPtr8(WREG12++)                  ' Read the byte from code memory
            WREG8 = 8                                       ' Create a loop for the bits to plot
            Repeat
                If WREG13.0 = 1 Then                        ' Do we need to set a pixel?
                    Glcd_hWriteDataWord(_GLCD_Ink_)         ' Place a pixel on the LCD
                Else                                        ' Otherwise...
                    Glcd_hWriteDataWord(_GLCD_Paper_)       ' Clear a pixel on the LCD
                EndIf
                WREG13 = WREG13 >> 1                        ' Move each pixel into bit-0
                Dec WREG7                                   ' Decrement the character font height counter
                If SRbits_Z = 1 Then Break                  ' Exit the loop when we reach the correct size of the character
                Dec WREG8                                   ' \
            Until SRbits_Z = 1                              ' /Close the loop when all pixels plotted
            Dec WREG9                                       ' \
        Until SRbits_Z = 1                                  ' / Close the loop when all bytes read
        Dec WREG5                                           ' \
    Until SRbits_Z = 1                                      ' / Close the loop when all width pixels are done
    Glcd_WriteReg($03,%0001000000110000)                    ' Set Entry mode for horizontal left to right increments
    Glcd_ResetWindow()                                      ' Reset the window
    Pop.d WREG12                                            ' Restore WREG12 and WREG13
    Pop.d WREG8                                             ' Restore WREG8 and WREG9
    Pop.d WREG0                                             ' Restore WREG0 and WREG1
    Return
#endIfSym   ' __SYSCOM_GLCD_PRINT_REQ_

The #ifSym/#endIfSym directives are a simple pre-processor mechanism I created into the compiler itself to only use code if the command itself has been used in the program. I created it many years ago so I could add libraries to the compilers that replaced its built-in commands.

trastikata

Quote from: top204 on Jun 27, 2024, 06:50 AMWhy are you using a RAM array for the font character bitmaps trastikata? They are very efficient when read directly from flash memory, especially on the 16-bit devices.

Thank you Les. I need the array for anti-aliasing calculations which are done in blocks. Especially with font resizing and anti-aliasing I need the entire array to look forward around the current pixel.

top204

So the font bitmap data is read from flash memory into a RAM array, then processed and written to the LCD?

If that is the case, then a simple block read/write should be enough, once the correct address in flash memory has been calculated and the amount of data has been established:

The code below is extremely efficient because it uses the device's Wx registers for parameters, so the assembler code is very tight indeed:

    Dim WordArray[10] As Word
    Dim FlashTable As Flash16 = 1,2,3,4,5,6,7,8,9   

    BlitFlashToRAM(AddressOf(FlashTable), WordArray, 3)
   
'-------------------------------------------------------------------------------------------------------------
' Load a section of 16-bit flash memory data into a 16-bit RAM array
' Input     : pFlashAddress holds the address in flash memory to start reading from
'           : pArrAddress holds the address of the RAM array to write too
'           : pAmount holds the amount of words to read from flash memory
' Output    : The RAM array passed in pArrAddress will be written too
' Notes     : Uses the device's Wx registers for more efficient assembler code
'
Proc BlitFlashToRAM(pFlashAddress As WREG0, ByRef pArrAddress As WREG12, pAmount As WREG2)
    Repeat                              ' Create a loop
        TblrdL.w [W0++],[W12++]         ' Read flash memory from the address held in W0 and write it to the address held in W12, with auto increments 
        Dec pAmount                     ' Decrement the amount of read/writes to perform
    Until SRbits_Z = 1                  ' Exit the loop when the amount reaches 0
EndProc


top204

If you are not sure if your flash memory address is sitting on a 16-bit address boundary, you will be better off reading/writing 8-bit data instead of 16-bit data, because the 16-bit devices will give an address exception if the memory is not a 16-bit boundary (one of the querks of the 16-bit devices). So the procedure below does not care where the flash memory is sitting:

'-------------------------------------------------------------------------------------------------------------
' Load a section of flash memory into a RAM array
' Input     : pFlashAddress holds the address in flash memory to start reading from
'           : pArrAddress holds the address of the 16-bit RAM array to write too
'           : pAmount holds the amount of words to read from flash memory
' Output    : The 16-bit RAM array passed in pArrAddress will be written too
' Notes     : Uses the device's Wx registers for more efficient assembler code
'
Proc BlitFlashToRAM(pFlashAddress As WREG0, ByRef pArrAddress As WREG12, pAmount As WREG2)
    pAmount = pAmount * 2               ' Double the amount because the loop is for 16-bit data
    Repeat                              ' Create a loop
        TblrdL.b [W0++],[W12++]         ' Read 8-bit flash memory from the address held in W0 and write it to the address held in W12, with auto increments 
        Dec pAmount                     ' Decrement the amount of read/writes to perform
    Until SRbits_Z = 1                  ' Exit the loop when the amount reaches 0
EndProc

Notice the TblrdL.b instead of the original TblrdL.w, so the mnemonic reads and writes 8-bit data instead of 16-bit data, but does not care where the address' are.

The assembler code produced by the above procedure is:

BlitFlashToRAM:
; rd_i000010_f001_000058_p000016,0 mkr$ in [test_24fj64ga002.bas] pAmount = pAmount * 2
    sl.w WREG2
; rd_i000011_f001_000059_p000016,0 mkr$ in [test_24fj64ga002.bas] repeat
_lbl__2:
; rd_i000012_f001_000060_p000016,0 mkr$ in [test_24fj64ga002.bas] tblrdl.b [W0++],[W12++]
    tblrdl.b [W0++],[W12++]
; rd_i000013_f001_000061_p000016,0 mkr$ in [test_24fj64ga002.bas] dec pAmount
    dec.w WREG2
_lbl__4:
; rd_i000014_f001_000062_p000016,0 mkr$ in [test_24fj64ga002.bas] Until SR.1 = 1
    bra nz,_lbl__2
_lbl__3:
; rd_i000015_f001_000063_p000016,0 mkr$ in [test_24fj64ga002.bas] EndProc
    return ; EndProc

And the passing of the data to the procedure is:

; rd_i000006_f001_000047_p000016,0 mkr$ in [test_24fj64ga002.bas] BlitFlashToRAM(AddressOf(FlashTable), WordArray, 3)
    mov.w #tbloffset(FlashTable),W0
    mov.w #WordArray,W12
    mov.w #3,W2
    call BlitFlashToRAM

Which is extremely efficient because of the use of the device's Wx registers. :-)

But you sometimes need to be careful that the Wx registers are not used elsewhere in the mechanism that is using it, in which case Push the Wx registers then Pop them, so they stay safe. This is also very efficient because the 16-bit devices have a true RAM stack and the Push/Pop mnemonics are very, very efficient with the Wx registers.

trastikata

Indeed Les, now the table read works, much appreciate your counsel.

P.s. Please check your PM inbox :)

top204

For a very, very efficient method of reading flash meory and writing it to RAM, the 16-bit device's Repeat mnemonic can be used, and because the code is quite small, it can be used inline for even more speed.

So that the device's Repeat mnemonic can be used in the high level language, it is named mRepeat, so it does not get mixed up with the compiler's Repeat directive:

'-------------------------------------------------------------------------------------------------------------
' Load a section of flash memory into a RAM array inline using the Repeat mnemonic
' Input     : pFlashAddr holds the address in flash memory to start reading from
'           : pArrayAddr holds the address of the RAM array to write too
'           : pAmount holds the amount of words to read from flash memory
' Output    : The RAM array passed in pArrayAddr will be written too
' Notes     : Uses the device's Wx registers and Repeat mnemonic for more efficient assembler code
'
$define BlitFlashToRAM(pFlashAddr, pArrayAddr, pAmount) '
    WREG0 = pFlashAddr                                  '
    WREG1 = pArrayAddr                                  '
    WREG2 = pAmount * 2                                 '
    WREG2 = WREG2 - 1                                   '
    mRepeat W2                                          '
    TblrdL.b [W0++],[W1++]

Then to use the BlitFlashToRAM meta-macro, use something like:

    BlitFlashToRAM(AddressOf(FlashTable), AddressOf(WordArray), 6)  ' Read 6 words from flash memory and write them to the WordArray

And the assembler code produced is:

    mov.w #tbloffset(FlashTable),W0
    mov.w #2176,W1
    mov.w #12,W2
    dec.w WREG2
    repeat W2
    tblrdl.b [W0++],[W1++]