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.

Hang updating Sharp LCD: SPI, Timer_A1 conflict

Other Parts Discussed in Thread: MSP-EXP430FR5969, MSPWARE

Hardware: 430BOOST-SHARP96 BoosterPack
                MSP‐EXP432P401R LaunchPad
Software: 430BOOST-SHARP96_GrlibExample_MSP432P401R

In the process of modifying the MSP432 Grlib example code to draw a bar graph on the Sharp 96x96 LCD screen last week I ran into a problem: LCD updates didn't always work as expected. Often the Graphics_flushBuffer() routine would draw what I expected, but occasionally it would draw part of the screen and then stop. Here's what I expected to see at one point, for example -- text and a "black" (reflective) rectangle on a white background:



and here are some snapshot of what actually appeared:


Over the weekend I managed -- with a great deal of assistance from my brother Bruce -- to isolate what was causing all this ugly output. It turned out to be a problem with the Timer_A1 interrupt handler code which was (properly) attempting keep reversing the LCD screen voltage by issuing an SPI NOP+VCOM_Invert command to the Sharp LS013B4DN04 LCD chip. Unfortunately, when it did so, it was (very improperly) forgetting to check whether anyone else was using the SPI bus.

Every time this occurred, the SPI screen update initiated by Graphics_flushBuffer() got cut short, with results like those above. ( At this point all the test runs I made over the weekend to try to isolate the problem are all running together, but I believe that in some cases the problem persisted, that is, subsequent Graphics_flushBuffer() calls had no further effect on the display. I don't know whether this was an SPI bus lockup, an LCD lockup, or the proverbial Something Completely Different. )

Here's the code in that project's main.c file:

//------------------------------------------------------------------------------
// Timer A0 Interrupt Service Routine
//------------------------------------------------------------------------------
  void TimerA1_0IsrHandler(void)
  {
      Sharp96x96_SendToggleVCOMCommand();

      ...

The problem with this -- as Bruce finally managed to pound into my thick sk... er, politely pointed out to me -- isn't so much that a few screen update bytes get overwritten by the VCOM_Invert command, it's that after the SPI write code invoked by Sharp96x96_SendToggleVCOMCommand() sends its two bytes, it then unilaterally sets CS LOW, which tells the LCD it's no longer needed. Then the interrupt handler exits, at which point the suspended SPI-write-in-progress tries to continue, but the LCD has just been told to cover its ears and stop listening. With -- as they say -- "unpredictable results". <grin>

Here's the latest iteration of my workaround code:

//------------------------------------------------------------------------------
// Timer A0 Interrupt Service Routine

//------------------------------------------------------------------------------

  // Must match Sharp96x96.c pins
  #define LCD_SPI_CS_PIN_PORT    P4OUT      // GPIO_PORT_P4
  #define LCD_SPI_CS_PIN         (1<<3)     // GPIO_PIN3
  volatile uint32_t spi_collision_count = 0;
  void TimerA1_0IsrHandler(void)
  {
    // See if bus is active so we don't mash on activity in progress
    // Note: Testing UCBUSY is NOT sufficient.
    if ( !(LCD_SPI_CS_PIN_PORT & LCD_SPI_CS_PIN) ) {  // If SPI bus is free
      Sharp96x96_SendToggleVCOMCommand();             //    issue VCOM_Invert cmd
    } else {                                          // else
      spi_collision_count++;                          //    log and avoid
    }

There is also something a bit odd in the way the Timer_A1 count register gets updated inside the handler -- it feels more appropriate for Continuous Mode rather than Up Mode -- but fixing that doesn't seem to be required to get clean screen updates; it only means that VCOM_Invert gets issued a lot more than necessary.

FYI, this code appears to be a direct port of project 430BOOST-SHARP96_GrlibExample_G2. Based on a quick glance at that project's main.c file, it appears that the G2 version has the same problem.

Oh, and if anyone is curious about why Sharp96x96_SendToggleVCOMCommand() is needed, there is a detailed description of this in the "VCOM Inversion" section of the Sharp LS013B4DN04 Memory LCD Application Note.

I hope this post saves someone else a few hours of head-scratching. <grin>

Now, what was I doing when... Oh. Right. I was trying to draw a bar graph...


Frank McKenney

  • Hi Frank,

    Thanks so much for sharing and with such detail! I'm glad you and your brother were able to work through it, and this should be a helpful reference for anyone trying to do the same thing in the future.
    As for this being a potential issue lurking in the G2 code example for the Sharp LCD as well, I'll report this to make sure someone checks it out.

    Regards,
    Katie
  • Hi Frank,

    Thank you for the detailed information regarding this problem.

    In the latest release of graphics library we decided to give the user total control over interrupts in their application (in older versions we disabled global interrupts in Graphics_flushBuffer() API and then re-enabled global interrupt). I believe that the problem you are facing is due to this modification.

    What this means is that in your application you will need to disable interrupts before calling Graphics_flushBuffer() API to avoid race conditions like the one you are currently facing.

    For MSP430 you can take a look at the MSP-EXP430G2 and MSP-EXP430FR5969 Grlib example projects for the recommended way to call this API.

    For MSP432 you could probably call the function in the following way.


      __disable_interrupts();
     // Flush Buffer to LCD
     Graphics_flushBuffer(&g_sContext);    
     // Restore original GIE state
     __enable_interrupts();


    Hope this helps,
    Ivan

  • Ivan Calzada said:
    In the latest release of graphics library we decided to give the user total control over interrupts in their application (in older versions we disabled global interrupts in Graphics_flushBuffer() API and then re-enabled global interrupt). I believe that the problem you are facing is due to this modification.

    Hello, Ivan. Thank you for your prompt reply.

    Given your use of "we", I can say "thank you" directly to you and/or your group for making this change, since "long" periods of uninterruptibility -- especially ones hidden inside library code -- can cause their own problems. Bruce estimated the Graphics_flushBuffer() call at 27msec (several aeons of solipsism at 12MHz <grin> ).

    Ivan Calzada said:
    What this means is that in your application you will need to disable interrupts before calling Graphics_flushBuffer() API to avoid race conditions like the one you are currently facing.

    For MSP430 you can take a look at the MSP-EXP430G2 and MSP-EXP430FR5969 Grlib example projects for the recommended way to call this API.

    For MSP432 you could probably call the function in the following way.

      __disable_interrupts();
     // Flush Buffer to LCD
     Graphics_flushBuffer(&g_sContext);    
     // Restore original GIE state
     __enable_interrupts();

    If this approach is required, or even "strongly recommended", it would be helpful if this coding style were present in the Example code. Even a comment in the code to the effect that "Here there be dragons" helps anyone using the code to distinguish between the Known World and Unknown Territory.

    I looked at my copy of the "G2" code, and those calls (a) use different names (e.g. GrFlush() vs. Graphics_flushBuffer() ) and (b) are not bracketed by interrupt disable/enable calls. I believe my code came from the MSPWare_2_00_00_41; is that out of date?

    But I'm wandering a bit. From my point of view ( not being tasked with rewriting the code <grin> ) the problem isn't in the example's interrupt handler code, it's in the SPI code. Let me back up a bit so you can check my assumptions...

    As I read the Sharp LS013B4DN04 Application Note, some SPI command must arrive at the LCD at a rate of roughly 60 times a second (less often in "Static" mode) with the M1 bit of the command (-x------) alternating between 0 1nd 1 on a roughly equal basis. Requiring the user to code At Least One Grlib Call Every 1/60th Of A Second is probably not a viable option<grin>, so the only reasonable way I can see of guaranteeing this timing -- or anything close to it -- is to use a timer interrupt (which the current Example code does). It will require that the interrupt handler be able to either (1) issue an SPI write (NOP+VCOM) itself or (2) queue an equivalent SPI write to occur after the interrupt handler exits.

    I don't see any simple way of accomplishing (2), given that user code might not do any further Grlib calls for an extended period: seconds, minutes, hours, perhaps even days. That seems to leaves us with the not-normally-recommended option of executing "lengthy code" (performing an SPI write) inside an interrupt handler.

    The heart of problem seems -- to me -- to be that the SPI  code being used is non-reentrant. To my way of thinking, if any SPI-bus-using routine is invoked when the SPI bus is already in use, the second routine should return to its caller with some sort of error indication (e.g. ERROR_ALL_CIRCUITS_ARE_BUSY_PLEASE_TRY_AGAIN_LATER). The interrupt handler would flag the need to try again the next time it was entered, and would then exit.

    If the SPI bus is busy when the Timer_A1 interrupt occurs, that means that the Sharp LCD is receiving commands, and no immediate SPI write is required; all we have to worry about is alternating the M1 bit those SPI commands issued by user code (e.g. Graphics_flushBuffer() calls). If the SPI bus is idle when the interrupt occurs, issuing an SPI write won't affect the other graphics code. ( As for interrupt-driven SPI code mixed with this, ... well, I don't want to think about that this morning. Let's put that off for Version 3.0. <grin> )

    This is the kind of thing that MUTEXes and SEMAPHOREs and their ilk were created for.

    ( I realize that I'm being pedantic here. I really do understand that you and your group may not be the ones responsible for the SPI code, and that if that's the case, your group has to live with what the other group provides, and that both of you have your own priorities. I'm just vacationing a bit in Dr. Pangloss's Best Of All Possible Worlds. <grin> )

    Oh, before I forget: I was curious about the The Timer_A1 setup/handling. The way it's written, it looks as though it would generate oddly-staggered intervals. Is there some peculiarity of the LS013B4DN04 that requires this? Or am I missing something more subtle at work here?

    In any case, thank you for your time. And for the Sharp96 code. <grin>

    Frank McKenney

  • HI Frank,

    Thank you again for your feedback! With respect to MSP GrLib I just checked to make sure that MSPWare 2_00_00_41 contains the lates version of MSP GRlib and it does. The examples in MSP Grlib 2_00_00_17 contains the desabling and re-enabling of global interrupts during flush operation. If your installation does not have this version would you mind giving more information on how MSPWare was installed in your system (using CCS App Center or using installer from  http://www.ti.com/tool/mspware).

    Frank McKenney said:

    The heart of problem seems -- to me -- to be that the SPI  code being used is non-reentrant. To my way of thinking, if any SPI-bus-using routine is invoked when the SPI bus is already in use, the second routine should return to its caller with some sort of error indication (e.g. ERROR_ALL_CIRCUITS_ARE_BUSY_PLEASE_TRY_AGAIN_LATER). The interrupt handler would flag the need to try again the next time it was entered, and would then exit.

    If the SPI bus is busy when the Timer_A1 interrupt occurs, that means that the Sharp LCD is receiving commands, and no immediate SPI write is required; all we have to worry about is alternating the M1 bit those SPI commands issued by user code (e.g. Graphics_flushBuffer() calls). If the SPI bus is idle when the interrupt occurs, issuing an SPI write won't affect the other graphics code. ( As for interrupt-driven SPI code mixed with this, ... well, I don't want to think about that this morning. Let's put that off for Version 3.0. <grin> )

    This is the kind of thing that MUTEXes and SEMAPHOREs and their ilk were created for.

    You are correct, using mutexes and semaphores in your application is another good option to meet the requirement (keeping maximum time between VCOM toggles to no more than one second).

     

    Frank McKenney said:

    Oh, before I forget: I was curious about the The Timer_A1 setup/handling. The way it's written, it looks as though it would generate oddly-staggered intervals. Is there some peculiarity of the LS013B4DN04 that requires this? Or am I missing something more subtle at work here?

    Actually, there is a problem on the way the Timer ISR is setup in the example code. The ISR should be:

    #pragma vector=TIMER0_A0_VECTOR
    __interrupt void TIMER0_A0_ISR(void)
    {

         Sharp96x96_SendToggleVCOMCommand();
        TA0CTL |= TACLR;
     // TA0CCR0 += 32000 - 1;
        TA0CTL &= ~TAIFG;
    }

    This will make sure that positive and negative inversions intervals be as equal as possible and intervals does not exceed one second periods in static mode. I have submitted a bug and should be fixed in our new release of MSP GrLib.

    Regard,
    Ivan

  • Hi, Ivan.


    I looked back over this thread and realized that the reason parts of your replies hadn't sense to me was that we were talking about different pieces of code. Very similar code -- otherwise I'm sure we would have realized it sooner -- but still different. I took a long look at MSPWare 2.0.0.41 and found these Example Projects:

    ./MSPWare_2_00_00_41/examples/boards/MSP-EXP430FR5969/MSP-EXP430FR5969 Software Examples/Source/430BOOST-SHARP96_GrlibExample_FR5969
    ./MSPWare_2_00_00_41/examples/boards/MSP-EXP430FR5969/MSP-EXP430FR5969 Software Examples/Source/430BOOST-SHARP96_GrlibExample_FR5969/GrLib
    ./MSPWare_2_00_00_41/examples/boards/MSP-EXP430FR5969/MSP-EXP430FR5969 Software Examples/Source/430BOOST-SHARP96_ULP_FRAM/GrLib
    ./MSPWare_2_00_00_41/examples/boards/MSP-EXP430G2/MSP-EXP430G2 Software Examples/Source/430BOOST-SHARP96/430BOOST-SHARP96_GrlibExample_G2
    ./MSPWare_2_00_00_41/examples/boards/MSP-EXP432P401R/MSP-EXP432P401R Software Examples/Source/BOOSTXL-K350SVG-S1_GrlibExample_MSP432P401R
    ./MSPWare_2_00_00_41/examples/boards/MSP-EXP432P401R/MSP-EXP432P401R Software Examples/Source/BOOSTXL-K350SVG-S1_GrlibExample_MSP432P401R/GrLib
    ./MSPWare_2_00_00_41/examples/boards/MSP-EXP432P401R/MSP-EXP432P401R Software Examples/Source/430BOOST-SHARP96_GrlibExample_MSP432P401R
    ./MSPWare_2_00_00_41/grlib/examples/MSP-EXP430F5529_Grlib_Example
    ./MSPWare_2_00_00_41/grlib/examples/MSP-EXP430FR4133_Grlib_Example
    ./MSPWare_2_00_00_41/grlib/examples/MSP-EXP430FR5969_Grlib_Example
    ./MSPWare_2_00_00_41/grlib/examples/MSP-EXP430F5438_Grlib_Example
    ./MSPWare_2_00_00_41/grlib/examples/MSP-EXP432P401R_GrlibExample
    ./MSPWare_2_00_00_41/grlib/examples/MSP-EXP430F5529LP_GrLibExample
    ./MSPWare_2_00_00_41/grlib/examples/MSP-EXP430G2_Grlib_Example
    

    Since I had a 430BOOST-SHARP96 board, it seemed natural to base my work on a project specifically labelled for use with my two pieces of hardware, e.g. 430BOOST-SHARP96_GrlibExample_MSP432P401R, and all my replies in this thread were done assuming that you were talking about the same piece of code. Likewise, when I mentioned the "G2" version of the code, I was referring to project 430BOOST-SHARP96_GrlibExample_G2. This project lacks the interrupt enable/disable code you were referring to.

    Your references to "G2" code, on the other hand, appear to have been to the project  MSP-EXP430G2_Grlib_Example, which seems to be of more recent origin.

    And, of course, there is no Sharp96 'Grlib' example for the MSP 432P401R. The project named MSP-EXP432P401R_GrlibExample, assumes the use of a different LCD panel (a much nicer one, I might add <grin>).

    Given that we were working from different source code, I'm impressed that we were able to communicate as well as we did. "Left hand, meet right hand".<grin>

    If the 430BOOST-SHARP96 Example code projects are your groups responsibility, I'm sure that getting all these different projects in step is already in your queue. If not, could you pass your improvements along to that group?


    Thanks.

    Frank

  • Let me add two suggestions to the already said:

    1) any function that requires along time should be made interruptible. In case of the flush function, the flushing should be split in small parts between which interrupts are temporarily enabled.
    I faced a similar problem when writing config data to flash. During flash write, interrupts have to be disabled. So I disable them, do a write operation, then enable and again disable them, knowing that all pending interrupts are executed when it is safe. (the MSP does not advance main execution between two interrupts, unlike the Atmel, so ALL pending interrupts are served between an EINT/DINT pair)

    2) the problem of required command sends in regular intervals can be handled by using global flags. All library functions use a global flag to determine the state of the M1 bit in the next command. This flag is inverted by a timer ISR (in case it shall not be inverted too often) or on each use. Also, each time a command it sent, a global counter is reset to zero. it is incremented by the same ISR and when it reaches a threshold, a dummy command is sent. The ISR needs to be executed at least twice per required update period, to ensure that even worst case (library sent a command right after the last ISR execution), the maximum time is not exceeded. Still the user has to ensure that he doesn't block interrupts for a longer time, preventing the timer ISR to be called in time.

**Attention** This is a public forum