4
\$\begingroup\$

In my sketch main loop I used SoftwareSerial in order to read data from gps.

void loop()  
{ 
  while (nss.available())
  {
    if (gps.encode(nss.read()))
    {
     ...

Also I have timer ISR, which is called every second or so, where I engage buzzer

ISR(TIMER1_COMPA_vect)
{
  tone(8, 1000, 200); 
  // digitalWrite(9, 1); delay(200); digitalWrite(9, 0);

}

The problem is, when I hear the tone and at the same time there is an incoming gps data, the tone is corrupted. When data is finished the tone is clear. So somehow serial input and tone are interfering with each other.

Even without the code, that is reading data (nss.available() and nss.read()), if I just comment out nss.begin(57600); then the problem is solved.

How can I solve this?

\$\endgroup\$
3
  • \$\begingroup\$ possible duplicate of How to organize tasks? \$\endgroup\$
    – Dave Tweed
    Nov 3 '12 at 13:17
  • \$\begingroup\$ @DaveTweed It seems that question got deleted \$\endgroup\$
    – m.Alin
    Nov 3 '12 at 14:04
  • \$\begingroup\$ It seems that both of these functions depend on interrupts which may end up blocking each other, and they may also want to use the same hardware timer. However, it may be possible to find a solution for tone generation by direct use of an atmega timer in a mode which does not require CPU interaction to reset. Essentially, you will either need to find someone's existing solution for software-serial compatible tone generation, or you will need to learn about what these libraries are doing behind the scenes and come up with a custom alternative or modification which does not interfere. \$\endgroup\$ Nov 3 '12 at 14:10
8
\$\begingroup\$

If you can settle for a tone of frequency 976.5625 Hz instead of the 1000 Hz in your code, here is the solution (assumption: Arduino Uno or Duemilanove, not sure about the Mega):

Connect your output to Pin 5 or 6 (the PWM pins) instead of pin 8. Create a volatile global variable:

volatile int_8 tone_tracker = 0;

Change your Timer1 ISR to be called every 200 ms.

In the ISR code, count up to 4 saving the value in tone_tracker, then:

analogWrite(SPKR_PIN, 127);

Next time the ISR is invoked, set tone_tracker to 0 and:

analogWrite(SPKR_PIN, 0);

The issue should be resolved.


Why this works:

The Timer0 ISR code now just switches on and off the tone at set times, leaving the actual tone generation to the microcontroller hardware.

The Arduino default PWM frequency on pins 5 and 6 is 976.5625 Hz unless you are modifying Timer0 somewhere else in your code (or doing something else with PWM where libraries make some "invisible" changes).

Writing analog 127 to the PWM pin generates a 50% duty cycle square wave at the default PWM frequency. That generates the tone required.

In this approach, the actual tone generation is by the PWM output of the microcontroller, not through interrupt driven variable-frequency code as used in the tone library.

The jugglery with tone_tracker gives you 200 x 4 = 800 mS silence, then 200 x 1 = 200 mS of tone output. The timer ISR and the serial ISR might well conflict, but that doesn't affect the PWM generation. Thus, at worst the tone might play on for a few microseconds longer, or may start a few microseconds later, if the MCU is busy dealing with serial comms at the time - but this would not be discernible by human ears.

Of course, this won't work as easily if you need any tone frequency except 976.5625 Hz (on pins 5 and 6) or 488.28125 Hz (on pins 3, 9, 10 and 11).

\$\endgroup\$
4
  • 1
    \$\begingroup\$ Yes, making it so that the CPU does not have to be involved in the actual tone generation is the key to compatibility. If you could move the explanation that the interrupt would now be used only to control the duration which the tone is played earlier in the answer that would make it clearer. \$\endgroup\$ Nov 3 '12 at 14:28
  • 1
    \$\begingroup\$ Very interesting and descriptive answer. Let me digest this and get back with question floating in my mind... \$\endgroup\$
    – Pablo
    Nov 3 '12 at 14:34
  • \$\begingroup\$ @ChrisStratton Thanks, suggestion incorporated. :-) \$\endgroup\$ Nov 3 '12 at 15:01
  • \$\begingroup\$ This works great of course. The alternative will be to have external tone generator if flexibility will not be enough. To conclude - I can't use delay() in ISR in any way. \$\endgroup\$
    – Pablo
    Nov 3 '12 at 15:36
4
\$\begingroup\$

Arduino makes simple things easy, but it makes complex things more difficult. You're getting into the realm in which Arduino simply falls over.

Both SoftwareSerial and Tone are very CPU-intensive tasks with very strict timing requirements. The Arduino setup() ... loop() approach is not capable of dealing with doing both of them simultaneously, although either one alone works fine. You need to use interrupts, but the interrupt service routines (ISRs) in the Arduino libraries are not designed to cooperate with each other.

Specifically, the SoftwareSerial module works by taking an interrupt at the beginning of each incoming character's start bit, and then tying up the CPU until the entire character has been received. The Tone module uses a hardware timer to generate the correct frequency, but (I'm guessing) uses an interrupt to update the selected output pin on each edge. The long serial ISR is preventing the timer ISR from running as regularly as it needs to. This is what causes the corruption you're hearing.

At this point, you pretty much have to abandon the Arduino libraries and start programming at the "bare metal" level, by either writing your own library modules, or by switching to another software development environment altogether (e.g., WinAVR).

\$\endgroup\$
4
  • 2
    \$\begingroup\$ Very poor answer. Both functions are already based on interrupts, not the main program loop. But the cpu can only do one thing at a given instant in time, interrupt driven or not. A heavyweight ISR thus blocks another time critical task from running when it is supposed to. \$\endgroup\$ Nov 3 '12 at 14:22
  • 1
    \$\begingroup\$ The point of the Arduino ecosystem is that you shouldn't have to deal with low level stuff... there are supposed to be libraries that are simple to use and your little gadget "just works." However, it's obviously not "just working" here, which is the other side of the coin for Arduinos... you can only do so much with the stock libraries before you hit a brick wall where doing something simple like generating a tone and reading serial data breaks functionality. I'm pretty sure that's what Dave is getting at here, and he's totally right. People need to know the limitations of the platform. \$\endgroup\$ Nov 3 '12 at 16:36
  • \$\begingroup\$ @TobyLawrence - there are some valid points about abstraction hiding critical implementation detail on a resource constrained platform, but the now removed claim that switching to an ISR would be how to solve the problem was both technically flawed and ignored the fact that the library components in question are already interrupt based. The proper solution is to get the CPU out of the tone generation business, as Anindo's answer at least partially accomplishes - and without needing to abandon the convenience of the Arduino software serial solution. \$\endgroup\$ Nov 3 '12 at 17:06
  • 1
    \$\begingroup\$ Technical flaws are inevitable, but I find the most beneficial way to point out flaws here is to parlay them into an actual answer... instead of just dumping them into comments amongst the question and answers. :P \$\endgroup\$ Nov 3 '12 at 20:53

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.