Mega loop - sometimes slow

I'm building a velocity sensitive keyboard using a mega. Each key has two switches, which are engaged sequentially when the key is pressed. If there is a long interval between the switches then thats a slow key press. If the time interval is small, that means the key was pressed quickly.

The switches are pretty close together, so good timing is important. A very fast key press would be 150 microseconds, and a normal one would be 1000 microseconds.

I have a loop that scans for input changes, which takes about 16 microseconds if nothing is changing. With debug code its about 23 microseconds.

The problem comes in when some loops mysteriously take too long - about 550 microseconds. This means that the time interval between the two switches is computed incorrectly, because for some reason the arduino was busy doing something besides checking for switch activation. When it gets around to checking the pin state, both pins are already on, so the time difference is zero.

I've simplified my code to the point where almost nothing is taking place, so I'm pretty sure its nothing I'm doing. I'm thinking that maybe there is an interrupt routine that happens every so often in the arduino, that takes about 525 microseconds to run.

Is there an interrupt (or something) that runs periodically? If so, can it be deactivated without bad results, like bricking the arduino? How should I get around this problem?

PS:

mega 2560
arduino IDE 1.01
sketch is attached - debugphone.ino
sketch output is like this (avg, max is over 10000 loops)

avg time: 24
max time: 560
avg time: 24
max time: 560
avg time: 24
max time: 560
avg time: 24
max time: 548
avg time: 24
max time: 552
avg time: 24
max time: 552
avg time: 24
max time: 556
avg time: 24
max time: 556

debugphone.ino (9.22 KB)

Ansible:
I've simplified my code to the point where almost nothing is taking place, so I'm pretty sure its nothing I'm doing.

I'm pretty sure it is something you're doing. How can we possibly get past this impasse? Maybe something in Read this before posting a programming question will help. Section #6 looks promising.

I'm thinking that maybe there is an interrupt routine that happens every so often in the arduino, that takes about 525 microseconds to run.

525 microseconds * 16 clock cycles per microsecond = 8400 clock cycles. The millis interrupt service routine is not even close to that expensive.

Now I'm thinking that the problem is with Serial.print.

changing my debug messages from this

    Serial.print("avg time: ");
    Serial.println((newtime - last10000) / 10000);
    Serial.print("max time: ");
    Serial.println(maxcycle);

To this:

    //Serial.print("avg time: ");
    Serial.println((newtime - last10000) / 10000);
    //Serial.print("max time: ");
    Serial.println(maxcycle);

changes the max cycle time from ~550 to ~380 microseconds. So I guess there's the culprit. I can compact the output further by using binary. Hopefully that will get it to the point where its close to a very fast key press.

I expected changing the baud rate to do something, but strangely it doesn't seem to have much effect. I tried 115200 and 230400, but they are almost the same as 9600.

Ansible:
Now I'm thinking that the problem is with Serial.print.

Now I'm thinking that the problem is probably somewhere in your code.

Since you haven't shown it to us, it seems pointless speculating about where in the code it might be.

I'm pretty sure it is something you're doing. How can we possibly get past this impasse? Maybe something in Read this before posting a programming question will help. Section #6 looks promising.

How condescending! What specifically are you referring to?

Now I'm thinking that the problem is probably somewhere in your code.

Since you haven't shown it to us, it seems pointless speculating about where in the code it might be.

The code is right there in the first post, as an INO file attachment.

Ansible:
How condescending!

If you say so.

What specifically are you referring to?

I mistakenly believed you had not included your sketch. I see that you had. I apologize.

Ansible:
The code is right there in the first post, as an INO file attachment.

I didn't see the attachment, sorry.

My first suspicion is that the output is causing the delay. You could keep a count of the number of samples that are over and under (say) 50 usec to see how often these longer iterations occur, and if it's exactly one per 10,000 iterations then you know where to start looking for the problem..

My calculations show that your debugging output is around 37 characters. At 9600 baud you output one character per 1/960 seconds (0.0010416 S) so 37 would take 0.038541 S (38 mS).

The serial output buffer can hold 32 or 64 bytes (can't remember which) so it will eventually fill up and then the data gets clocked out at the above speed, holding up the main loop.

I would expect changing to a higher baud rate would help, but only to an extent. If the buffer fills up it will still block.

Thx for the replies, guys.

I put in a counter to total up the number of loops over a threshold - I set it to 200 microseconds. Only one loop is over that, presumably the one where the serial output takes place.

I'm on board with the idea that serial output could be the holdup. However, I was trying out the test key and I'm occasionally able to produce a '0' duration when serial output shouldn't be happening. The procedure:

  • wait 2-3 seconds or more, plenty of time for all the debug messages from previous key presses to be sent.
  • hit the key fairly sharply, enough to produce a duration of 500-800, probably.
  • occasionally get a '0' duration back.

If its really the serial port write that is the problem, how would that affect a key hit when no message is being sent? For an isolated key press, the key timing is supposed to take place before the message sending. On the other hand, what else would cause a delay like that?

Wow, the plot thickens!

I commented out all my serial writes, and instead turn on the LED when there is a duration of 0 for a key press. Sure enough, it only takes a few key hits to turn on the LED. So I guess the serial port isn't the problem, or at least not the only problem.

code is attached. Maybe I need to start over with an interrupt approach rather than a polling loop.

velociphone.ino (9.73 KB)

Is the end goal to handle exactly 24 keys (48 switches)?

That could possibly be spread across 10 I/O ports?

At any one moment in time, the human will simultaneously press how keys? One for each finger?

A better way to handle this problem is to use pin change interrupts - when the first switch is switched
you record the value of micros(), when the second switch goes, subtract the recorded value from the current
values of micros() and place the result in an appropriate volatile variable (an array would be good, indexed
by key).

If no other interrupts are firing this should give accuracy as good as micros(), which I think is either
2us or 4us...

There is at least one pin-change-interrupt library out there, or you can code it yourself - basically IO
pins are organised into ports (upto 8 pins per port), and you install an interrupt routine for each port
of interest.

The pin change interrupt routine would have to first figure out which pin has changed (XOR with the previous
value from that port).

Yep, that's it. 24 keys, arranged in a circle, so that there are multiple operators. I wouldn't expect more than 8 or so keys to be pressed at any one time, though I wouldn't want to limit that arbitrarily.

Here's a blog post re the project (non-velocity sensitive version!):

A better way to handle this problem is to use pin change interrupts

I was thinking along those same lines, MarkT. I have my eye on the PinChangeInt library. Maybe the whole thing will end up being simpler than it is now. I was wondering if you could call micros() from inside an interrupt handler, sounds like yes.

Ansible:
I was wondering if you could call micros() from inside an interrupt handler, sounds like yes.

Yes, that is commonly done.

You can do pin change interrupts yourself, it isn't that hard.

Not all pins on the Mega raise pin-change interrupts though.

3 x 16-bit port-expanders might help you. I have stuff on one here:

That particular one (MCP23017) can raise an interrupt when a pin changes. I'm not sure about the timing in your case, but surely velocity sensitive keyboards can't require really sophisticated hardware?

Ok so if I understand correctly, the 2560 has three pin-change interrupts, together with the 8 'external interrupts'.

Each pin-change interrupt can service one port, with (possibly) up to 8 input pins on it. The external interrupts only handle one pin each. That's a max of 24 + 8 = 32 pins, well short of the 48 pin goal. I'd expect the total to be even less, what with some ports having fewer than 8 pins, some external interrupts being used for whatever it is they are normally used for, etc.

So yeah to get the 48 interrupt driven pins I'll have to use a Due (which it looks like can assign ISRs to every pin independently) or something like the MCP23017.

I guess I can go ahead and write up a little interrupt driven test for just one key to see if it really does solve the timing issues.

Ansible:
Yep, that's it. 24 keys, arranged in a circle, so that there are multiple operators. I wouldn't expect more than 8 or so keys to be pressed at any one time, though I wouldn't want to limit that arbitrarily.

What else? Or is the Mega just managing the keyboard?

Bit of related stuff here: Velocity Sensitivity with non-MIDI Keyboard - Audio - Arduino Forum

[quote author=Coding Badly link=topic=175988.msg1307546#msg1307546 date=1373261589]
What else? Or is the Mega just managing the keyboard?[/quote]

Currently there are 3 analog knobs and one 5 position selector knob. In the non-velocity sensitive version everything was handled by the mega. I was figuring that for velocity sensitive operation I'd just get a separate arduino for the knob stuff and just use the mega for the keys. Especially since accessing the analog inputs was so slow - about 120 microsecs per analog knob. I wouldn't mind adding a few mode buttons too, such as for looper or effects toggle.

Sound synthesis is done in a laptop currently, but may end up being done by an odroid or cubox arm computer.

Interesting!