precise timing issues with LCD libraries

I've been working on a synth related project for a few months and had a general question about using the LiquidCrystal libraries. I'm currently using an Adafruit RGB LCD Shield (with buttons) that uses I2C interfacing.

My main problem is that the app I'm doing requires pretty accurate timing that involves synchronizing multiple metronomes/gates (using the Metro() library ) that are being used to control external analog synth hardware.

From what I can see, the LCD.cpp library uses a number of delayMicro() functions and I suspect this is what is causing my gates to go out of sync after a few seconds.

When I disable all of the code that runs the display, the timing is fine, so I'm pretty confident that is where the problem lies.

Does anyone have some advice on a way to use the LCD display that will get around this issue ?

Otherwise, the only solution I can think of is to do all of the timing with external circuits, like a 555 timer.

Thanks in advance for any suggestions

How tight is your timing?
How much delay and/or jitter can you tolerate?
i.e. how much time can your code tolerate being delayed while writing output to the LCD?

Depending on how much overhead time your code can tolerate it will determine what direction you can go and/or what you need to do.
Until you know that, it will be trial and error, which can waste lots of time and IMO is a waste of time.

So the first thing you need to know is how much overhead time can you tolerate.

--- bill


Some additional information:

I2C usually has more overhead than direct pin control.
The shield that you are using uses I2C and it uses an MCP23017 16 pin i/o expander to control the LCD.

The adafruit LCD libraries for their products that use I2C expanders are REALLY SLOW.
They try to emulate the Arduino pin control API so there are lots of i2c bytes being sent to control each pin and their code controls each pin separately.
A better way to control an i/o expander chip controlling an LCD is to control multiple pins at once which can dramatically reduce the overhead and speed things up significantly.
In other words the way their libraries are designed and written they are slow with lots of overhead.

Also, the way the microchip i/o expanders like the MCP23017 or MCP2308 work they have more i2c overhead than a PCF8574. In other words for a given i2c clock rate, the PCF8574 can control its output pins faster than the microchip expanders.

On the positive side, the MCP23017 can run with a much faster i2c clock than the PCF8574.

The default clock on the Arduino is 100Khz. The MCP23017 can run up to 1.7Mhz assuming a good h/w design with proper pullups.
The AVR can support an i2c master clock of 800Khz

Here are some actual timing numbers that show the actual amount of overhead in us for transferring a single byte to the display.

The first set of numbers is for a backpack that adafruit makes that uses a MCP23008.
This chips works similar to the one on the shield you have but the shield will have a little more overhead given it uses a MCP23016 chip and given the way Adafruit designed the shield.
There are two sets of numbers.
The top set is when the Adafruit LiquidCrystal library is used.
The bottom set is my hd44780 library which is much more efficient.

3882 us Adafruit library with MCP23008 @ 100kHz with #292 backpack
1492 us Adafruit library with MCP23008 @ 400kHz with #292 backpack
1131 us Adafruit library with MCP23008 @ 800kHz with #292 backpack


649 us hd44780 hd44780_I2Cexp with MCP23008 @ 100Khz with #292 backpack
230 us hd44780 hd44780_I2Cexp with MCP23008 @ 400Khz with #292 backpack
168 us hd44780 hd44780_I2Cexp with MCP23008 @ 800Khz with #292 backpack

You can see how the times get shorter when the i2c clock is bumped up but that the Adafruit library is much slower than my hd44780 library.

The next set of numbers is for using a typical LCD keypad shield that uses direct pin control and an analog pin for reading the buttons.

285 us LiquidCrystal Library with typical LCD keypad Shield
 92 us hd44780 hd44780_pinIO with typical LCD keypad Shield

You can see that it is quite a bit faster than the Adafruit I2C LCD library even when the i2c clock is bumped up to 800kHz.
My hd44780 library using pin control it is about 42 times faster than the Adafruit I2C LCD library.
While these i2c tests were done on the Adafruit #292 backpack, the timing on your shield should be similar but a bit slower than the #292 backpack due to the shield you have using a MCP23016 and the way Adafruit has wired it up to the LCD and buttons.

Once you know the maximum timing overhead you can tolerate in your other code you decide what direction you need to take.
Keep in mind that these timings are for an individual character.
So if you sent more than one character with a lcd.print() etc... each character will take the amount of time shown.

--- bill

Some thoughts... I run a lot of timing related stuff, although none too critical. I turn off the lcd... don’t even call the display function.. unless a user pushes a button. So even loop.. all display calls don’t run. I push button.. lcd comes on.. shows values.. then automatically times out in 60 seconds (as function won’t run)

If you need constant display.. then I’d suggest two Arduino. One doing time critical functions.. then sending main vars to display say every 5 sec via ic2 to another Arduino. Then second Arduino just drives the display. Takes in key vars via ic2.. and displayed them using all needed delays.. none of which will interfere with other Arduino

Thanks for the very detailed explanation Bill Your feedback gives me a deeper understanding of why I'm having this issue, as well as making me aware of how little I understand the lower levels of software/hardware interaction. I'm still a 'noob' when it comes to the hardware/ digital electronics layer of my project. I would like to come back to your suggestions when I have a better understanding of the trade offs between IC2 and direct pin control. I forgot to mention that I'm using an Arduino UNO, and I've run out of available pins due to other functions. I'm also running a 12 bit DAC (MCP4725) on the IC2 bus at the same time and hope to add 2 more in a future version.

To answer your question regarding timing accuracy, I think it would be around 1 msec. Using the Metro library gives a minimum 'tick' value of 1msec, so that's what I'm basing this on. Since all of the LCD libraries I've seen rely on a delayMicro() based approach ( even if it is micro seconds ), I think I would still have the same problem, but it may occur less often, eg. every 256 metro clicks rather than 16. I'm making an assumption based on my incomplete understanding, so that may not be the case.

Also, thanks for your suggestion 'FullOfBadIdeas'. I discovered that my timing did work when I disabled the LCD menu system, which is how I realized that it was causing my problem. Unfortunately, I do need stable timing because it's a music app and dropping beats every so often isn't gonna work. The UI involves being able to change parameters, while the system is running, so shutting off the display won't work.

Using 2 Arduino's would solve my issue, but in this case, I'm eventually trying to make this a standalone Eurorack synth module and have to consider the form factor and cost.

I think the approach I'm going to take is to use external timers (555 chips ) to generate all of the clocks I need and have the Arduino deal with everything else. I came across a digital potentiometer chip ( to set 555 clock speed and duty cycles ) which can be precisely controlled via the Arduino, so that there won't be any need to change the UI.

Now I have some insight as to why most of the synth modules I've seen rely on 7 seg displays and rotary encoders if they are based around an internal micro processor.

Thanks again for the suggestions!

The 1ms tick value and your max tolerable jitter/delay are two different things.
Interrupts are not masked when you call the lcd output routines. So metro will still know that time has passed.
However, it is when too much time passes (more than you wanted/needed) before metro can get control again is what is causing problems.

What is your time period you using?
That will give a ball park idea. The actual will be less but that is a start.

---b ill

If I understand the question correctly, I think it would be 250 msec. Here's a description that might help illustrate the problem:

I have 3 instances of Metro objects that have a 'tick' time of 1 msec when I create the instance.
Each Metro object also has it's interval dynamically changed by a duty cycle value to control it's duration.
ie. If the duty cycle is 50% and the Metro update is 50%, then the pin that Metro is controlling goes 'HIGH' for 125 msec and 'LOW' for 125 msec.

When I start them in sync they work for about 3 bars, then they drop a beat on the 4th bar..

here's what the output of the Serial Monitor look like if I associate a 1 when pin goes HIGH and 0 for LOW

1010101010101010
1010101010101010
1010101010101010
101010101010101
0101010101010101
0101010101010101 . etc.

There's another Metro that updates every 2000 msec ( 250 * 8 ) that does Serial.println()

When I disable the menu system, the output is consistent and doesn't drop at all.

I spent the last couple of days playing with a 555 chip and got something working that can replace the Metro code , so I think it makes sense for this project to offload timing to an external clock and still be able to use the 'slower' Adafruit library.

garyborg:
I have 3 instances of Metro objects that have a 'tick' time of 1 msec when I create the instance.
Each Metro object also has it's interval dynamically changed by a duty cycle value to control it's duration.
ie. If the duty cycle is 50% and the Metro update is 50%, then the pin that Metro is controlling goes 'HIGH' for 125 msec and 'LOW' for 125 msec.

So are you creating the objects with an interval of 1ms?
Is that short of an interval really needed?
If so that will be problematic as the actual system timer is 1ms and since metro runs from loop() that means that everything done inside loop() between the calls to the metro check() and including the code done when check() is true must all be less than 1ms.
If the total amount of time between loop() iterations is ever longer than 1ms you would loose/miss metro events if you are using a 1ms metro interval.

That is often very tough to do.
For sure it isn't possible when using the AdaFruit keypad LCD and their library as it takes
over 3ms just to send a single character to the LCD.
Longer metro intervals could eliminate missed metro events.

Can you move the math around to bump the interval to something like 10ms or longer?
That buys lots of breathing room but the Adafruit LCD library would likely still causes issue since it is so slow.
But could still be usable if you never printed more than about 2 characters a time.

Or you may have to change how you are doing things a just a bit.
For example, maybe you could use the millis() timer tick value to track elapsed time rather than trying to use metro events every 1ms to track time.

All that said, even if the code is fast enough or the metro interval is long enough to ensure you won't miss any events, you will still experience jitter.
For example, suppose you using a 125ms metro interval.
The tick counter inside metro is looking for its 125ms countdown to expire,
you loop around and the internal counter was 124 so check() returned false.
But then you do something on the LCD like print 8 characters. 8 characters with the Adafruit shield and library is 8 * 3.882 ms or 31ms.
loop() comes around and now check() says its is time; however, because of the LCD output, you are executing your code 30ms late. That is jitter.
You didn't miss the event, it was just being processed a bit later than you actually wanted it to happen.
While that is an extreme example, anything done inside loop before the next call to event() will cause jitter in the event() processing.

Depending on your timing requirements, you may need a low jitter.
If so, you will have to use interrupts which would guarantee you get the CPU when you want/need it.
There are other libraries similar to metro that just as easy to use, but use interrupts. There are certain things you can't do in the ISR function (like doing output using Serial, I2C, or call delay()) but you will be guaranteed to get your events on time at the exact time you wanted as it will interrupt whatever else is going on including other libraries like the Adafruit LCD library.
The other libraries are TimerOne, MsTimer2, FlexiTimer2, TimerThree
You can find them from this page: Teensyduino: Using Arduino Libraries with Teensy USB development board

--- bill

Actually, my description of "jitter" above is incorrect. I should have used the term latency which is the amount of delay from the point in time you wanted. "jitter" is variation in latency across multiple events.

--- bill

This is all very interesting but rather academic. We have not seen grayborg's code yet. We might take one look at that and say "no wonder you have timing problems"! No offence, greyborg, but your sketch would not be the first we have seen that updates the display hundreds of times per second even when nothing has actually changed.

If I'm doing you an injustice, there are faster ways of updating an LCD display, compared to the AdaFruit library, as Bill had pointed out by comparing it to his own library. For example, if you can dedicate just one Arduino pin to the display (plus shared use of the i2c pins), you can significantly speed things up. The display has an "E" pin, and this pin gets manipulated twice as often, maybe three times as often, as any other pin. Dedicate an Arduino pin to that and the difference can be dramatic.

Thanks bill, your description of the jitter/latency issue makes perfect sense. Also, thanks for the links to the timing libraries as well. In an earlier version of the project, I used the elapsedMillis library and had even more issues, that Metro generally cleared up. I did try various settings other than 1 msec when making Metro instances ( 10 , 100 ) but that didn't make a difference.

PaulRB, thanks for the tip on the E pin concept, when I gain a better understanding of the lower level hardware/software interactions of the Arduino, I'll revisit these posts and see if I can really grasp the details. My main programming background is cycling74 Max, so I've been pretty insulated from the kinds of things I'm currently encountering.

No offence taken on the point about not seeing the actual code.. There's a lot of other things going on that aren't related to timing, so I'll strip out that stuff and post a version that directly illustrates the problem in the next couple of days.

In the meantime, I've got and external 555 circuit that is working perfectly and generating pulses with a variable duty cycle that I can use as an external clock, which means the LCD display works as is.

I've attached a stripped out version of the code. It still exhibits the same issue. Turning on the Serial Monitor will show the output of the gate labeled SGate.

Disabling the menuSelect() function in the main loop fixes the problem.

For future reference, I'm not sure how to include the code by embedding it in the post rather than attachment. Is that method preferable ?

Thanks

Gary

ECA_TimerDemo.ino (16.1 KB)

I wouldn't get too excited about using a dedicated Arduino pin for E especially if still using the other low level code from the current Adafruit_LiquidCrystal LCDkeypad library.

From a h/w perspective it would require doing some cut(s) and adding wire(s) to the shield and then it would no longer be compatible with the Adafruit library.

From a s/w perspective to do more than just updating the code that strobes E in their library would be replacing quite a bit if not most of their current low level code which would be quite an effort, almost like writing a new library.
(I think it would be easier and quick to write a new i/o class for the hd44780 library - which would also pick up some additional capabilities over the adafruit library)

From looking at their library code and knowing the way the MCP23017 chip works and what has to be done over the i2c bus to make it work, while using a dedicated Arduino pin for E will remove the two byte transfers to the i/o port per nibble currently used for strobing E, the bulk of the timing overhead is in other places and there are other s/w optimizations that could be done that could reduce the timing by more than using a dedicated E signal given their current library code and the way they are using the chip. There are multiple modes available in the chip and they are not taking advantage of a more efficient mode to better utilize the i2c bus which can really help lowering the overall timing overhead.

Also, consider this, on an AVR it takes roughly 4-6us (depending on IDE version) to set a pin HIGH or LOW using digitalWrite() on an AVR processor running at 16Mhz.
A byte transfer time at 100kHz is about 10us, or about twice as long.
If the s/w is re-written to take advantage of byte mode, the s/w can do back to back writes to the MCP23017 i/o port in the same i2c message stream. This is a HUGE win since the i2c START+address+MCP register# and END/status portions of the transfer are much longer than a single byte transfer.
i.e. in byte mode the host can send two back to back bytes to the output port to do things like wiggle E without having to re-negotiate the i2c bus or let go. Plus when in not in byte mode, you have to send the MCP23017 register number each time you want to talk to it.
By using byte mode and sending back to back bytes to wiggle E, you avoid a START+address + end/status overhead. which is about 4 byte times on the bus.
So to put it another way, using MCP23017 byte mode, an i2c clock of 400Khz and using back to back writes over i2c to toggle the E signal can be faster than using a dedicated Arduino pin when using digitalWrite().
Yes you could use direct pin i/o to control the pin that controls E but then you are stepping outside of Arduino. While possible and not too difficult, it does make the code non portable and more difficult to maintain.
And even if you did do the direct port i/o for the E pin, if you didn't also do the other adafruit library re-write optimizations it will still be slower than toggling E over i2c.
Then there are also optimizations that can allow the Arduino processor to continue to run while a previous LCD instruction or command is still running. This can be used to hide much of the overhead of getting things setup and transfered to the LCD.

hd44780 does all of these things.
I had a close look at the differences between the MCP23008 based Adafruit #292 backpack and the Adafruit rgb lcd-keypad shield and their libraries.
There are many things that I'd do differently that are really costing performance.
Like they read the two 8 bit ports on the chip before updating any bits in the 8 bit output port that controls the LCD rather than storing the most recent written value in RAM.

I had thought that there might be a way to configure the existing hd44780 library to work with that keypad shield even though it doesn't have explicit support for the MCP23017 (hd44780 has support for the MCP23008)
However, do to the way that Microchip defined the MCP23017 you can't talk to the MCP23017 as if it were two MCP23008 chips, even though they have a bank mode that separates it into two sets of registers that to make it look like and work like a MCP23008.
Byte/non-sequential mode works differently and Microchip also made some unfortunate choices in their default register layout and power up mode settings that make working with as if it were two 8 bit ports difficult as well as creates more overhead.
They essentially want to treat the two ports like a 16 bit output port rather than two separate 8 bit ports - at least for byte mode. Kind of ironic since it is called "byte mode".

I do remember looking at this about a year ago as I did want to support that shield, and it is all coming back to me, how irritated I was when looking at the details of the MCP23017 vs the MCP23008.
Auto detecting and supporting both chips for an application like this is a total pain and would add overhead and slow down MCP23008 based devices and that is why I left out MCP23017 support in the hd44780 code.

I'm not really a fan of using the MCP23008 or MCP23017 for controlling a hd4480 LCD as they have more overhead, particularly the MCP23017, vs the PCF8574 and they don't offer any advantages when being used to control a hd44780 LCD.
(ok maybe if you control of more colors for the backlight, but other than that I don't see that the added overhead is worth it)

--- bill

I took a quick look at your sketch. I'll admit I didn't fully grasp it as it is a bit complicated and would take a fair amount of time to really go through it in detail.
However, it appears to me that you probably want to use an interrupt based timer library rather than metro for your real time needs.
And then use the foreground (which is everything that is called from loop() ) for your user interface to the LCD keypad shield which sets the parameters for your timer functions - which are operating in the background using interrupts.
That will allow you to get your time critical functions called you really want and will interrupt any foreground code to do it.
If you switch over to using timer based functions instead of metro based functions, you must ensure that the timer functions do not use or call anything that needs to use interrupts.
And you must also declare any variables that are shared between your foreground code (anything called from loop()) and your timer functions as volatile to ensure that the compiler does attempt to keep them cached in local registers in the foreground code which would make them invisible to the timer functions.

Debugging can be a bit more challenging as you can't always just toss in debug statements into your timer functions since they are called from the actual ISR.
You can do some simple things including using the h/w serial port if you keep your output message smaller than 32 bytes and ensure that all characters printed will be fully transmitted before the next time you attempt to send more characters to the serial port.
Given your short timing intervals I'd also recommend bumping the baud rate up.

From what I've seen it definitely looks possible to switch over to using a timer library with interrupt based timer functions in place of metro based functions.

If you can switch to using timer/interrupt based functions, all the overhead for the lcd keypad library becomes a non issue and you won't ever have to worry about anything in the foreground impacting your time critical timer functions.

I would recommend taking a serious look at switching over to one of the timer libraries.

--- bill

I went back an reviewed the various timer libraries.
I had remembered them as being able to create multiple timer objects; however, that is not the case.
They only allow the creation of a single timer function. You can set the timeout period to anything you want, start/stop and change it, but there is only one.
This will make using them with your existing design difficult as it looks you are using multiple metro objects.

So while it could be done by layering your own internal time counting functions on top of that, it will add complexity since it all has to be driven off of single timer function vs being able to create/setup a timer function to replace of each of your existing metro objects/functions.

--- bill

For future reference, I'm not sure how to include the code by embedding it in the post rather than attachment. Is that method preferable ?

Definitely preferable. Can't open a .ino on my phone...

There must be some instructions on how to embed code somewhere. If it were your forum, where would you put such instructions?

Another option to consider: these should be far superior to the standard 16x2 LCD displays, being OLED rather than LCD and having a native i2c interface (no adaptor needed). Considerably more expensive, but perhaps worth it?

Why not write all your time critical stuff fired by a timer interrupt - then eg the LCD is in the background and doesn't hold it up....

Alan

allanhurst:
Why not write all your time critical stuff fired by a timer interrupt - then eg the LCD is in the background and doesn't hold it up....

That's basically what Bill suggested in a previous post. The OP has not responded yet.

A couple of hints....

The loop() has about a 7us overhead - embed everything inside a while(1) to avoid this. But then serial? doesn't work.

serialWrite etc are blocking for long strings - if you use them send as little as possible at as high a baudrate as possible.

The millis() function uses about 7uS every millisecond to correct it's timing. If you're desperate modify the source code to disable it.

diditalRead(), dgitalwrite() etc are very slow - use direct instructions eg PORTB |= 00010000b;

Allan

First, I support what other say about moving your timing from Metro for to the hardware timers.

However, there my be some improvement with what you have.

if (currentMillis - lastButton >= 500){  
       menuSelect();   // if you comment this line out, timing issue disappears
   }

How deep do you have to get into the menu and what is printed before the timing is affected?

When I take a look at menuSelect(), my question is whether or not the problem disappears when no buttons are pressed?

What about when a button is pressed, but then there is no further printing but lcd.clear() is called?

What about without BUTTONSELECT?

Does it take one of the gate timing functions where there is further printing and calls to lcd.clear()?

I'd be concerned with all the use of lcd.clear() which is relatively slow.