Continue to Site

Welcome to our site!

Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

  • Welcome to our site! Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

PIC Double Buffering

Status
Not open for further replies.

Mike - K8LH

Well-Known Member
LCD writes and their associated inter-write delays can eat up a lot of time when writing lots of characters to the display. One method I came up with to avoid some of this overhead in 'main' is to update one character at a time on the LCD from a 32 byte display buffer using an ISR. Writing to the LCD in main is simply a matter of placing characters in the display buffer (very fast) while the display is updated entirely in the background. Also, driving a switch and/or encoder matrix using the LCD RS and D4..D7 lines becomes a simple ISR task since the ISR has complete control of LCD write operations.

Regards, Mike

Code:
     char lcd[33] = { " lcd buffer demo        HH:MM:SS" };

     #define line1 0
     #define line2 16
Code:
   void interrupt()             // 500 usec (1000 cycle) interrupts
   { static char line = 0xC0;   //
     static char bndx = 0;      //
     pir1.TMR2IF = 0;           // clear TMR2 interrupt flag
     if(line & 0x80)            // if "new line"
       PutCMD(line ^= 0x40);    // toggle DDRAM address (0xC0 or 0x80)
     else                       // not "new line" so
       PutDAT(lcd[bndx++]);     // refresh display, bump index
     bndx &= 31;                // pseudo %32, 0..31 inclusive
     if((bndx & 15) == 0)       // if new line (0 or 16)
       line ^= 0x80;            // toggle b7 pseudo "new line" flag
   }


//
 
Last edited:
PIC Double Buffering Techniques

...
I posted a simple method for updating an LCD display from a 32 character display buffer in the background via an ISR so I'm a bit confused by your comment. Is your comment and concern about how your main program would write an eight character ADC value into the display buffer? If so, wouldn't you just write the new eight digit ADC value, buffered or not, all at once into the display buffer three or four times per second? Your LCD writes to the display buffer in 'main' would be perceived as instantaneous since the ISR refreshes the entire LCD display from the buffer every 17 msecs (when using 500-usec interrupts).

Sorry Mike I posted hurriedly with minimal and a poor explanation.

The problem is not just display, also in the example with the ADC the value is derived from the ADC in 2 bytes, which are then combined in a later math process to give a 10bit ADC value. If the display interrupt occurs between those 2 ADC bytes being updated the value may be written wrong and cause trash on the display.

Double buffering is generally used so the ADC read and math can be done first and placed in a buffer, then at some point guaranteed to not conflict with the interrupt the value is copied from buffer1 to buffer2, and buffer 2 is used within the int for display purposes.
 
Sorry Mike I posted hurriedly with minimal and a poor explanation.

The problem is not just display, also in the example with the ADC the value is derived from the ADC in 2 bytes, which are then combined in a later math process to give a 10bit ADC value. If the display interrupt occurs between those 2 ADC bytes being updated the value may be written wrong and cause trash on the display.
I'm sorry, Roman. I still don't see the relevance of your comment to my post. What exactly do you mean when you say "a display interrupt occuring between those two ADC bytes being updated"? Being updated in what way? Are you talking about a display interrupt occuring between reading the lo and hi bytes from the ADC result registers? Or, are you talking about a display interrupt that occurs while you're doing the math on those bytes? Or, are you talking about an interrupt occurring while converting the binary number into an ASCII string in preparation for display? None of these operations should be affected by a display interrupt or cause trash in the 32 byte display buffer or on the LCD screen.

Double buffering is generally used so the ADC read and math can be done first and placed in a buffer, then at some point guaranteed to not conflict with the interrupt the value is copied from buffer1 to buffer2, and buffer 2 is used within the int for display purposes.
Based on your comments, I wonder if you misunderstand how my driver works? This LCD driver method uses a single 32 byte display array (buffer). When you want to write a character onto the LCD display in your main program you simply stuff the ASCII value for that character into the array at index 0 through 15 for line 1 on the display or at index 16 through 31 for line 2 on the display. The ISR driver writes one character from the array (buffer) to the LCD during each interrupt and after 34 interrupts (17 msecs) the entire display has been updated from the array (buffer).

Updating the display with an ADC calculation could be as simple as;

Code:
  #define line1 0
  #define line2 16

  unsigned char lcd[32] = { "                                " };
Code:
 /*
  *  pseudo ADC code
  */
  unsigned int result;           //
  unsigned char string[4];       //

  result = readADC(0);           // read ADC channel 0
  result *= mymultconst;         // result = 0..8191
  string = bin2ascii(result);    // 0..8191 in, "0000".."8191" out
  lcd[line1+4] = string[0];      // huns
  lcd[line1+5] = string[1];      // tens
  lcd[line1+6] = string[2];      // ones
  lcd[line1+7] = '.';            // manually insert decimal pt.
  lcd[line1+8] = string[3];      // tenths


//
I'm sure you'll agree from this example that the method offers some significant savings in terms of memory and speed, but that's not the only advantage. Having direct access to the LCD display array (buffer) can make many other difficult tasks much easier too. For example, flashing a single character or group of characters on the display at some periodic interval becomes a relatively easy task;

Code:
 /*
  *  flash "standby" at 2 Hz rate
  */
  if(msctr % 250 == 0)           // if 250 msec interval
  { lcd[line2+2] ^= ('s'^' ');   // toggle between 's' and ' '
    lcd[line2+3] ^= ('t'^' ');   //
    lcd[line2+4] ^= ('a'^' ');   //
    lcd[line2+5] ^= ('n'^' ');   //
    lcd[line2+6] ^= ('d'^' ');   //
    lcd[line2+7] ^= ('b'^' ');   //
    lcd[line2+8] ^= ('y'^' ');   //
  }


//
Code:
 /*
  *  flash RTC colon chars (HH:MM:SS)
  */
  if(msctr % 500 == 0)            // if 500 msec interval
  { lcd[line1+10] ^= (':'^' ');   // flash the colon char
    lcd[line1+13] ^= (':'^' ');   //  
  }                               //
I apologize if I'm missing your point. I don't understand your concern or how it relates to the LCD method I posted. Perhaps you can better explain how interrupts from the method can mess up ADC values on the LCD display?

Thanks...

Cheerful regards, Mike
 
Last edited:
... What exactly do you mean when you say "a display interrupt occuring between those two ADC bytes being updated"? Being updated in what way? Are you talking about a display interrupt occuring between reading the lo and hi bytes from the ADC result registers? Or, are you talking about a display interrupt that occurs while you're doing the math on those bytes? Or, are you talking about an interrupt occurring while converting the binary number into an ASCII string in preparation for display? None of these operations should be affected by a display interrupt or cause trash in the 32 byte display buffer or on the LCD screen.

Yep thats the point. ALL of those operations would cause a trashed display if you don't double buffer.

Take a simple example where a var byte is converted into 3 digit decimal number 0-255 and displayed on the LCD once a second. And let's say the var value changed from 199 to 200, and was being processed into 3 decimal digits, and the hundreds digit was processed (changed from 1 to 2) then the int occurs and the digits are written to the display it will write 299 instead of the orig value 199 or the next value 200.

Or if the int just writes one digit (at that instant) and it is the second digit it may write 290 instead of the 200.

I'm sorry, Roman. I still don't see the relevance of your comment to my post.
...

I apologise if my original post "No double buffering?" gave the impression of being overly critical as that was not the intent it was more a surpised (and low effort, sorry) response that you had not used one step in the cyclic sequence for a buffer copy.
 
Yep thats the point. ALL of those operations would cause a trashed display if you don't double buffer.
But, double buffer what, Roman? You do realize that the display array is being used as a buffer in this method, right? A display interrupt during any of the ADC operations mentioned will NOT trash the display.

Take a simple example where a var byte is converted into 3 digit decimal number 0-255 and displayed on the LCD once a second. And let's say the var value changed from 199 to 200, and was being processed into 3 decimal digits, and the hundreds digit was processed (changed from 1 to 2) then the int occurs and the digits are written to the display it will write 299 instead of the orig value 199 or the next value 200.
After you write the ASCII "200" characters into the display array (overwriting the old "199" characters), which shouldn't take more than a few microseconds at most, you'll see a nice smooth glitch free transition from "199" to "200" on the display. With a 17 msec refresh interval, ANY display update appears to be instantaneous. You'd actually have to work pretty hard to force the display to glitch as you describe above, like deliberately using long delays between writing the '2', '0', and '0' characters into the display array.

I apologise if my original post "No double buffering?" gave the impression of being overly critical as that was not the intent it was more a surpised (and low effort, sorry) response that you had not used one step in the cyclic sequence for a buffer copy.
No apology necessary. Your original comment WAS a little cryptic and vague but it's been an interesting discussion.

I can assure you that this method works extremely well in practice. LCD display updates in the ISR are buffered through the display array. There's no way for a display interrupt to trash the display array, the display, or any other variable in your program because the display interrupt only reads characters from the display array. And while using the display array as a buffer is the preferred method for painting characters onto the LCD display, you could, if you're very careful, modify the contents of the display array directly as depicted in the flashing "standby" message or the flashing RTC colon character examples in a previous post.

Cheerful regards, Mike
 
Last edited:
I'm beginning to think we are talking about different things?

...
After you write the ASCII "200" characters into the display array (overwriting the old "199" characters), which shouldn't take more than a few microseconds at most, you'll see a nice smooth glitch free transition from "199" to "200" on the display.
...

If the 3 chars are written to the buffer outside the interrupt, it is totally possible for the interrupt to occur between writing the 0 and 1 digits or between the 1 and 2 digits, unless you disable interrupts while writing or ensure the interrupt will not occur in some other way.

From what I understand so far, you have no double buffering, and rely on the fact the trashed display will be cleaned up 17mS later (or 3*17mS later with a 3 digit display), which is probably too fast to have seen.

But since the thread is about displaying four multi-digit variables, and each may only be updated infrequently (say once per second) I think it's advisible to include double buffering from the start so there will never be a case of a trashed display.

Code:
(outside interrupt)
* read ADC
* convert 2 ADC bytes to a 16bit var
* convert 16bit var to 5 decimal digits, in array blah[]
* (at a time when interrupt can not occur!) copy blah[] to blah_buffer1[]
 
(inside interrupt)
* on count0-4 display one digit from blah_buffer2[] to LCD 
* on count5 copy blah_buffer1[] to blah_buffer2[]

The double buffering allows the ADC task to be performed at any time and any frequency (completely independently) without ever getting a trashed digit on the LCD.

If you will only perform the ADC task after every full set of displaying 5 digits you can single buffer instead.
 
Last edited:
I'm beginning to think we are talking about different things?
I've been thinking that all along (lol). I suspect you simply didn't take time to understand some basic concepts behind my buffered LCD method which is why your original and subsequent comments about double buffering something (you haven't said exactly what) and mysterious "ADC trashing display interrupts" have been so puzzling.

Mike said:
... After you write the ASCII "200" characters into the display array (overwriting the old "199" characters), which shouldn't take more than a few microseconds at most, you'll see a nice smooth glitch free transition from "199" to "200" on the display. ...
If the 3 chars are written to the [display] buffer outside the interrupt, it is totally possible for the interrupt to occur between writing the 0 and 1 digits or between the 1 and 2 digits, unless you disable interrupts while writing or ensure the interrupt will not occur in some other way.

While it is certainly possible for a display interrupt to occur between the time you write the first and second digits into the display array buffer, or between the time you write the second and the third digits into the display array buffer, the interrupt does not adversely affect the LCD display. Again, the display ISR writes one new character from the display array buffer to the LCD during each interrupt and the entire LCD is refreshed from the thirty two byte buffer over a span of 17 msecs (when using 500 usec interrupts). Interrupted or not, you should certainly be able to update the display array with your new values within the span of one interrupt interval where only a single character from the display array is being written to the LCD. There's simply no need to disable interrupts while you're writing new characters into the display array.

From what I understand so far, you have no double buffering, and rely on the fact the trashed display will be cleaned up 17mS later (or 3*17mS later with a 3 digit display), which is probably too fast to have seen.

Let's take a moment to talk about what happens when you use traditional LCD write operations to overwrite a value of "199" displayed on an LCD to a new value of "200". First you write the "2" digit and the LCD will display "299". Then you write the first "0" and the LCD will display "209". Finally, when you write the last "0", the LCD will display the new "200" value you wanted. So the LCD goes from displaying "199" to "299" to "209" to "200". This would seem to fit your description of a trashed display but I doubt anyone reading this would agree that's the case. From experience we know that the three "200" characters are being written to the display fast enough that we perceive a smooth transition from the old "199" value to the new "200" value.

With my buffered LCD method, there is a chance that the ISR display array buffer index is right in the middle of those three new "200" characters that you just wrote into the display array. So it is possible that the LCD will display a "190" or a "100" during the span of one 17 msec LCD refresh cycle. But guess what? That's still fast enough to perceive a smooth transition from the original "199" value to the new "200" value, without any unusual visual artifacts, just like you get with traditional LCD write operations. As I mentioned before, anything you write into the display array buffer appears to show up instantaneously on the LCD display.

There isn't any method that won't produce those transitional values you call trash when overwriting multiple LCD characters. So what it really comes down to is, if you can't see it, it ain't trash.

But since the thread is about displaying four multi-digit variables ...

We're all aware that this thread is about displaying four multi-digit variables on an LCD and I was happy to suggest a novel interrupt driven buffered LCD method that's pretty darned versatile and efficient. Before moving on to something else, may I ask if I've adequately addressed your concerns about my interrupt driven buffered LCD method?

Cheerful regards, Mike
 
Last edited:
I wasn't criticising the concept of writing one digit to the display each interrupt. It's I system I like and have used for years. :)

What I was surprised at is that someone of your coding skill did not double buffer! You can live with trashed display data if the display is constantly being updated very fast from the original variable, but living with a problem because it's "usually too fast to see" is not an optimal procedure.

In the event that the code is changed, and the display may be updated once (your 32 chars over your 17mS) then the micro goes into sleep for a second, or does any task where interrupts are turned off for a second, you can easily get a trashed display which would remain for the whole second or for the entire sleep period.

Double buffering is professional and ensures that after writing all 32 chars, they will ALWAYS be correct, fully independent of any timing issues between when the ADC task is done compared to when the interrupt is done.
 
I wasn't criticising the concept of writing one digit to the display each interrupt.
Hi Roman,

I believe your original comment was about the lack of double buffering, which was then extended to include phantom "ADC trashing interrupts", yes?

What I was surprised at is that someone of your coding skill did not double buffer!
I was a little surprised by your original comment. In your subsequent posts you haven't really told me what it is you think I should double buffer, nor have you come up with any compelling premises to support a conclusion that I should be double buffering something(?). I've explained as best I can that neither the lack of double buffering nor the display interrupts will cause the trashed ADC values or the trashed displays you've mentioned.

You can live with trashed display data if the display is constantly being updated very fast from the original variable, but living with a problem because it's "usually too fast to see" is not an optimal procedure.
Wow, you've contrived an "optimal procedure" complaint based on a problem and solution built into the HD44780 design. That's impressive.

Again, no driver method can avoid those transitional values you call "trash" when overwriting multiple characters on the LCD and that's why every method relies on the persistence of the display to avoid seeing those transitional values. I can live with that. We all live with that. We just didn't know that, no matter what method is used, it's "not an optimal procedure", whatever that's supposed to mean, if it updates the display fast enough to avoid seeing those transitional values (lol).

In the event that the code is changed, and the display may be updated once (your 32 chars over your 17mS) then the micro goes into sleep for a second, or does any task where interrupts are turned off for a second, you can easily get a trashed display which would remain for the whole second or for the entire sleep period.
This method does rely on interrupts and so I'm sure everyone realizes it's not the end-all solution for all your LCD applications. However, if you do want to use this method in these situations, then you can just as easily avoid a trashed display simply by waiting for the end of a full display refresh cycle before suspending interrupts or going to sleep. Possible solutions, which don't require double-buffering (sorry), might be;

Code:
  while(bndx);           // wait for end of refresh cycle
  sleep();               // then sleep
Code:
  while(bndx);           // wait for end of refresh cycle
  intcon.GIE = 0;        // then suspend interrupts
The solutions are simple, intuitive, and obvious to anyone willing to take time to understand the method.

Double buffering is professional and ensures that after writing all 32 chars, they will ALWAYS be correct, fully independent of any timing issues between when the ADC task is done compared to when the interrupt is done.

Nonsense! Double buffering is just another tool. It doesn't make you "professional". That's an indefensible and hilarious conclusion (lol).

There are several simple ways to synchronize display array writes to the display cycle short of using double buffering, but in most cases synchronization shouldn't be necessary. The method I proposed uses the 32 byte display array much like the dual-ported RAM on a video card. You place characters into the array in your main program when you want to display them, and, just like magic, they show up on the display. It's really that simple. The method doesn't care how or when you run your ADC task or when you write characters into the display array and that's about as "fully independent of any timing issues" as you can get (lol).

I'd be happy to continue the discussion if you'd like but it seems pretty obvious at this point that you are unable or unwilling to back up your claims with anything more substantive than opinion or deflection, and, you're simply inventing new problems, that don't really exist, without any premises to support your conclusions or claims.

Cheerful regards, Mike

===================================

For those curious souls... I thought a quick n' dirty demo might be in order so I threw an 8 pin 12F1822 onto a K8LH Serial LCD Interface board that was handy. Nothing fancy. No tasking or scheduling, just simple delays in a main loop. Circuit drawing and BoostC program listing attached. I hope someone finds it useful...

[video=youtube_share;z0t3_CGqQqU]http://youtu.be/z0t3_CGqQqU[/video]
 
Last edited:
...
I'd be happy to continue the discussion if you'd like but it seems pretty obvious at this point that you are unable or unwilling to back up your claims with anything more substantive than opinion or deflection, and, you're simply inventing new problems, that don't really exist, without any premises to support your conclusions or claims.

I have already backed up my "claims";

Mr_RB said:
...
In the event that the code is changed, and the display may be updated once (your 32 chars over your 17mS) then the micro goes into sleep for a second, or does any task where interrupts are turned off for a second, you can easily get a trashed display which would remain for the whole second or for the entire sleep period.
...

I think you should have a look through the Microchip appnotes on double buffering since you seem to be acting like this is some problem I just "made up".

Anyway this is getting argumentative so I'm done here. :)
 
Mr RB said:
Mike said:
I'd be happy to continue the discussion if you'd like but it seems pretty obvious at this point that you are unable or unwilling to back up your claims with anything more substantive than opinion or deflection, and, you're simply inventing new problems, that don't really exist, without any premises to support your conclusions or claims.
I have already backed up my "claims";
Actually, you haven't. One only need read the thread to see a pattern emerge.

Mike said:
Mr RB said:
In the event that the code is changed, and the display may be updated once (your 32 chars over your 17mS) then the micro goes into sleep for a second, or does any task where interrupts are turned off for a second, you can easily get a trashed display which would remain for the whole second or for the entire sleep period.
This method does rely on interrupts and so I'm sure everyone realizes it's not the end-all solution for all your LCD applications. However, if you do want to use this method in these situations, then you can just as easily avoid a trashed display simply by waiting for the end of a full display refresh cycle before suspending interrupts or going to sleep. Possible solutions, which don't require double-buffering (sorry), might be;
Code:
  while(bndx);           // wait for end of refresh cycle
  sleep();               // then sleep
Code:
  while(bndx);           // wait for end of refresh cycle
  intcon.GIE = 0;        // then suspend interrupts
The solutions are simple, intuitive, and obvious to anyone willing to take time to understand the method.
I think you should have a look through the Microchip appnotes on double buffering since you seem to be acting like this is some problem I just "made up".

When you replace the missing context, it's clear that I both acknowledged the problem and provided a simple solution and so your response simply doesn't make sense. This is however a good example of your pattern of deflection.

Anyway this is getting argumentative so I'm done here.

Based on the pattern and nature of your comments, I think that's a good idea.

Kind regards, Mike
 
Last edited:
hi,
As a reader of this Thread, I don't see how the later posts are helping the OP.?

I would suggest that either Mike or Roman start a new Thread in order to discuss their differences.

Eric
 
Hi Eric,

I'm sorry. I just wanted to contribute something useful. I'm not sure what the other member was trying to do.

Kind regards, Mike
 
Mike... RB... When you're outside looking in... as it were... We can see both sides.. I think your both right... Mike! I think your ISR LCD writes are a really good idea.. I can also see RB's concerns, however!! in practice either / or both ways of "double buffering" are still better than direct screen writing... At least the OP can make up his own mind.....
 
Last edited:
Hi Eric,

I'm sorry. I just wanted to contribute something useful. I'm not sure what the other member was trying to do.

Kind regards, Mike

Hi Mike and Roman,
So that others can follow and contribute to your idea's, I have moved the last few posts to this Thread 'PIC Double Buffering Techniques', hope this helps.

Eric
 
Last edited:
Status
Not open for further replies.

New Articles From Microcontroller Tips

Back
Top