This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

TM4C1297NCZAD: Extra DMA words transmitted to LCD

Part Number: TM4C1297NCZAD

We have interfaced to a 4x20 character display that uses an Hitachi HD44780 controller.  Everything works, however when we use LCD DMA to transmit the an entire screens worth of data (4x20 = 80 16-bit words), the LCD DMA first transmits 16 words of 0x0000 data followed by our 80 16-bit words.  This causes the display to display blank data for the first 16-characters on the top row.  The very first time we use DMA to transmit a screen, everything works fine and 80 words are transmitted.  From that point forward, 96 words are transmitted even though we have told the driver to transmit only 80.  We are using the latest Tiva Peripheral Driver Library for the LCD.  Our frame buffer is on a 32-bit boundary.  

  • Hello SH14599,

    Can you provide the source code for how you are configuring the DMA and how you are loading/sending data with it?
  • It is as though the length of LCD DMA must be a multiple of 32 words.

  •    /* Enable our internal LCD Controller */
       SysCtlPeripheralEnable(SYSCTL_PERIPH_LCD0);
    
       LCDModeSet(LCD0_BASE, LCD_MODE_LIDD, (g_ui32SysClock / 240), g_ui32SysClock);  // MCLK=2MHz (500ns), was 254
       LCDDMAConfigSet(LCD0_BASE, LCD_DMA_BURST_16);
       LCDIDDConfigSet(LCD0_BASE, LIDD_CONFIG_ASYNC_HITACHI );
    
       /* Now configure RS, R/Wn, E as LCD controlled pin */
       GPIOPinConfigure(GPIO_PR1_LCDFP);
       GPIOPinConfigure(GPIO_PR2_LCDLP);
       GPIOPinConfigure(GPIO_PJ6_LCDAC);
       GPIOPinTypeLCD(GPIO_PORTJ_BASE, GPIO_PIN_6);
       GPIOPinTypeLCD(GPIO_PORTR_BASE,( GPIO_PIN_1 | GPIO_PIN_2 ));
    
       /* Set the timing limits to the maximumx to get the slowest settings possible */
       sTimings.ui8WSSetup     =  1;  // was 31, tAS=WRSU=100ns
       sTimings.ui8WSDuration  = 10;  // was 63, tPW=WRDUR=4800ns (500ns * 10)
       sTimings.ui8WSHold      = 15;  // was 15, tAH=WRHOLD=7500ns (500ns * 15) tC=12000ns
       sTimings.ui8RSSetup     =  1;  // was 31, tAS=RDSU=100ns
       sTimings.ui8RSDuration  = 10;  // was 63, tPW=RDDUR=4800ns (500ns * 10)
       sTimings.ui8RSHold      = 15;  // was 15, tAH=RDHOLD=7500ns (500ns * 15) tC=12000ns
       sTimings.ui8DelayCycles =  4;
    
       /* Provide the LCD controller with our defined timings */
       LCDIDDTimingSet(LCD0_BASE, 0, &sTimings);
    
       /* Determine the number LCD init commands */
       ulLimit = sizeof(ucLCDInitCmd) / sizeof(ucLCDInitCmd[0]);
       uint8_t  ucLCDInitCmd[] = { 0x38, 0x38, 0x38, 0x38, 0x10, 0x06, 0x0C, 0x01 };
    
       uint32_t ulDelayMsec = 25.0 * (CYCLES_FROM_TIME_US(g_ui32SysClock, 1000) / 3 );
    
       /* Initialize the LCD */
       for( ulIdx = 0; ulIdx < ulLimit; ulIdx++)
       {
          /* Write the command to the display */
          LCDIDDCommandWrite(LCD0_BASE, 0, (uint16_t)ucLCDInitCmd[ulIdx]);
    
          SysCtlDelay(ulDelayMsec);
       }
    
       if( ucRetVal == 0 )
       {
          /* Determine the number LCD init commands */
          ulLimit = sizeof(gs_pucCustomChars) / sizeof(gs_pucCustomChars[0]);
    
          /* set cgram address to 0 */
          LCDIDDCommandWrite(LCD0_BASE, 0, 0x40);
          
          /* Wait for BUSY flag to clear */
          while (LCDIDDStatusRead(LCD0_BASE, 0) & 0x80) { };
    
          /* Create our custom characters */
          for( ulIdx = 0; ulIdx < ulLimit; ulIdx++)
          {
             for( ucIdx = 0; ucIdx < 8; ucIdx++)
             {
                ucRetVal |= WriteData( gs_pucCustomChars[ulIdx][ucIdx] );
             }
          }
    
          /* set data ram address to 0 */
          LCDIDDCommandWrite(LCD0_BASE, 0, 0x80);
    
          /* Wait for BUSY flag to clear */
          while (LCDIDDStatusRead(LCD0_BASE, 0) & 0x80) { };
       }
    
       /* If the LCD commands were sent successfully */
       if( ucRetVal == 0 )
       {
          /* Delay to allow the change to the LCD take effect */
          osal_TaskDelay( osalMsToTicks(10) );
    
          /* Register our Interrupt Hanlder */
          LCDIntRegister(LCD0_BASE, LCDIntHandler);
    
          /* Finally, Enable the LCD DMA Done interrupt */
          LCDIntEnable(LCD0_BASE, LCD_INT_DMA_DONE);
    
          /* Set the Interrupt priority since we use RTOS calls in the ISR */
          IntPrioritySet(INT_LCD0, osalMaxIntPrio+1);
    
    	  /* Turn on the LCD backlight if required */
          BSP_LCD_BACKLIGHT( get_enable_backlight() );
       }
    
    
    

  • Note that we have a mutex/semaphore that protects us from using the LCD peripheral while a DMA operation is in progress.   To send data, we use:

         
       /* set cgram address to 0 */
       LCDIDDCommandWrite(LCD0_BASE, 0, 0x40);
       
       /* Wait for BUSY flag to clear */
       while (LCDIDDStatusRead(LCD0_BASE, 0) & 0x80) { };
          if( osal_TakeMutexSemaphore(g_xLCDBufMutex, osalMaxDelay, 0) == osalTRUE )
          {
             /* Write the display using DMA */
             LCDIDDDMAWrite(LCD0_BASE, 0, gs_ulLCDDMABuf, DISPLAY_LCD_MAX_LEN);
          }
          else
          {
             ucRetVal = 1;
          }

    Our ISR does the following:

    void LCDIntHandler(void)
    {
       uint32_t ulIntStatus;
       osal_EnterInterrupt();
       ulIntStatus = LCDIntStatus(LCD0_BASE, osalTRUE);
       if( ulIntStatus & (LCD_INT_DMA_DONE) )
       {
           if (ulIntStatus != 1)
           {
               ulIntStatus = 0;
           }
          /* Disable the DMA so that commands may be sent to the LCD */
          LCDIDDDMADisable(LCD0_BASE);
          /* Give back the semaphore */
          osal_GiveMutexSemaphore(g_xLCDBufMutex, &ulTaskWoken);
       }
       LCDIntClear(LCD0_BASE, ulIntStatus);
       osal_ExitInterrupt();
    }

  • Here is how we declare our frame buffer.  And, before we transmit, we copy the 80 bytes into that buffer (packed 16-bits).  I forgot that include that above...

    #pragma DATA_ALIGN (gs_ulLCDDMABuf, 4);
    static uint32_t gs_ulLCDDMABuf[DISPLAY_LCD_MAX_LEN / 2];
    
       /* Copy from the 8bit buffer provided into our 32bit buffer in 16bit chunks */
       for( ulIdx = ulJdx = 0; ulIdx < ucLen; ulIdx += 2, ulJdx++)
       {
          gs_ulLCDDMABuf[ulJdx] = ((((uint32_t)(pucBuf[ulIdx + 1])) << 16) + pucBuf[ulIdx]);
       }

  • Hello SH14599,

    Looking at DriverLib documentation "SW-TM4C-DRL-UG-2.1.4.178.pdf" for the description of LCDDMAConfigSet, it looks like you may not have configured it fully.

    Your code that is posted shows:

    LCDDMAConfigSet(LCD0_BASE, LCD_DMA_BURST_16);

    However DriverLib documentation says: "The ui32Config parameter is a logical OR of various flags. It must contain one value from each of the following groups."

    The groups are LCD_DMA_FIFORDY_* (absent), LCD_DMA_BURST_* (present), and LCD_DMA_BYTE_ORDER_* (absent).

    You should look into including the proper settings for your screen based on that for starters, as that could be part/all of the issue you face.


    Now then with all that said, while looking at what you are doing in further detail I am now also wondering if using the DMA is a bit overkill for this miniature screen! There have been many many posts for the LCD controller you cite on E2E, just put the name in the search bar and you will find many threads, but not one other person that I could see used the DMA for it! I have a feeling you are committing a massive overkill in complexity. In fact you may find it quicker to leverage information already on E2E for the HD44780 controller. For example this post cb1 has given much detail on: https://e2e.ti.com/support/microcontrollers/tiva_arm/f/908/t/279206

    As cb1 is still very much about and around, I suspect if you follow along with his lead rather than trying to use DMA, that he would be willing to help further guide you should you stumble while trying to follow with his advice.


    Also lastly, "It is as though the length of LCD DMA must be a multiple of 16 words." - 80 is a multiple of 16 as well? Perhaps you meant 32 bytes? I didn't see any notion that suggested that was the case either though. Just an FYI.

  • As (fortunately) still (somewhat) around - agreement is full w/Vendor Ralph's suggestion to employ (dare I say) "KISS!"

    The Lcd Controller takes substantial time to process each/every character - thus µDMA (usually chosen for speed) seems (as Ralph noted) miscast.

    Four 0x38 initialization bytes were listed - two work quite well - note that you must comply with the (relatively) long delay times demanded by the Lcd Controller (especially) during each byte of initialization.

    It should be noted that while, "Transferring 80 bytes of display data (completely filling the display) enables a, "repetitive & stylized data transfer" - it almost NEVER is absolutely required!    (far shorter transfers may be employed - further "defeating" the "Need for µDMA!" ... such are achieved via: "Cursor Direction - then selective (and limited) display writes - which (likely) "Quickly and more Efficiently Update just "small portions" of the screen.")

  • Thanks for your response. I am in complete agreement with you. Using DMA for this small of the display is overly complex. I would prefer to adjust the timing parameters of the LCD peripheral to match the timing of the LCD controller and then simply push the data out selectively, in a loop, polling the BUSY flag from the LCD controller between data bytes. However, the owner of this code is a little resistant to simplifying the code to not use DMA.
  • Ralph,

    Thank you for your quick response. Yes, I meant 32 bytes. It is as though the LCD DMA is requiring the frame buffer to be a size multiple of 32 bytes. Regarding the DMA config function, it turns out that the only non-zero setting in that register is the burst size. All of the other settings result in zero bits in the register. So, I don't think this is the issue. I tried various settings and it didn't fix the issue. As far as using LCD DMA for this small display, I completely agree with you that it is overly complex. My opinion is that we should push the data out in a loop, checking the BUSY flag from the LCD controller between writes. I will look into convincing the original author of the code to use that approach.

    In the meantime, would it be possible for you to verify this apparent multiple of 32-bytes requirement by the LCD DMA?

    Thanks!
  • Thank you - as you are likely (consultant) to this party - (some) diplomacy must be employed.    (i.e. "walk a tight-rope!")     Feel your pain!

    Polling of the Busy Flag (for all BUT initialization) is the preferred method of, "Optimizing data transfers" to such character-based displays.     However the polling may "overly engage the MCU" (you are, "sticking yourself w/in a polling loop") - and it is possible to (instead) allow "Busy's Toggle" to "interrupt the MCU!"     (yielding the fastest, most efficient data transfers ... albeit at some cost of added code complexity)

    It appears that your client has, "Hitched to the µDMA wagon" because, "It is there!"      Or because someone noted "Lcd" - yet that MCU's, "µDMA LCD reference" - always and only - targeted far larger GRAPHIC DISPLAYS - not the SO basic 4x20 character module(s)!

    Knowing, "When, Where, Why & How" to (properly) exploit, "Unique MCU peripheral capabilities" requires (je ne sais quoi) perhaps a higher "depth of understanding" - which you possess - client (it appears) not so much!     (yet ... it is hoped)

  • SH14599 said:

    In the meantime, would it be possible for you to verify this apparent multiple of 32-bytes requirement by the LCD DMA?

    Hello SH14599,

    The DriverLib User's Guide states clearly that the LCDIDDDMAWrite() function "enables the DMA engine in the LCD controller before transfering the required block of data to the display. The DMA engine transfers data 16 bits at a time so data must be padded if a display with an 8 bit interface is used." Please note it states *BITS* and not *BYTES*. So therefore, you'd have a minimum of 2 bytes per transfer.

    It also notes this is intended to transfer large block of data to the display... I am not so sure the situation you presented quite meets that criteria.

    I would certainly encourage this 'original author' to reconsider their approach, but in parallel read the details of the DriverLib User's Guide for LCD Interface Display Driver (LIDD) Mode (page 365) to better understand which APIs should be used and how.

  • Thank you Jacobi. We ultimately changed the LCD screen write function to write each character out in a loop (80 chars for a 4x20 display), waiting on the BUSY (D7) bit to go low to indicate the LCD has accepted the data. Optimizing the bus timing parameters in the LCD peripheral, we were able to write out all characters in about 3.5ms.
  • It is noted that, "80 chars. sent over a duration of 3,500µS", works out to just under 44µS/char. (which IS "reasonable.")

    Using a (likely) newer, contrast enhanced, 4x20 display - we achieved similar "screen fills" in ~3,200µS - simply via an 8 bit parallel bus. (no use of EPI)

    Note that, "issuing a display clear" (code 0x01) causes a "long delay" - yet if the "entire screen is rewritten" - there is no need for such "clear display."

    Much depends upon, "What, Where & Length" of New Data.    Screen "Write-Times" can be substantially reduced if only 20-30 char. positions are to be "over-written."

    A final caution - "over-writing existing data - especially when, "No change is indicated/required" - may yield a noticeable "flicker" - and may even produce a "beat-frequency" with a "PWM" modulated backlight.    Selective display update (rather than blind update) proves optimal...