Serial.write, interrupts and polling

I know there has been some existing forum activity in this area, and in fact, I even see the question I have has been asked before - but I have not seen or understood a clear answer.

I have a passive IR motion detector module. It takes an ATMEGA328's digital input high when it detects motion. My code tests this input and - if high - sends a single character via Serial.write() out through a bluetooth module to a listening mobile phone. However, my main loop code does a lot of things, and the time interval between polling this digital input has grown too long.

The obvious answer seems to be to connect the motion detector to an interrupt pin and have an ISR send the character to the BT module, except that Serial doesn't work in an ISR.

All the literature says use the ISR to set a flag and test that flag in the main loop. Now the question I've seen asked before but not answered:

If I have to test the ISR-set flag in the main loop,
then why don't I just do a digital read in the main loop and delete the ISR?

One reason is to ensure that short high transitions on the pin are captured which the main loop might otherwise miss. However, my motion detector holds the pin high for seconds - so this isn't an issue in this appplication.

Another possible reason is that doing repeated digital reads might be far slower than doing repeated boolean tests - can anyone say whether it is or not?

As it is, I'm thinking I'll just litter the main loop with far more digitalread-and-test IF statemnts.
It's ugly and offensive, and it gets worse because I'd need to sprinkle these inside some hefty functions, too.

However, the equivalent ISR-and-test-the-flag would look just as bad. It would just replace all my occurences of:

if (digitalread(mypin)=HIGH) Serial.write("M");

with

if (ISRflag) Serial.write("M")l

  • no better.

Am I missing a better way? Any help would be appreciated.

Thanks,
Chris

The obvious answer seems to be to connect the motion detector to an interrupt pin and have an ISR send the character to the BT module, except that Serial doesn't work in an ISR.

Serial.print(), Serial.println(), and Serial.write() work in ISRs. The problem is that if there is not room in the outgoing serial buffer, the functions block, waiting for there to be room. Room is made in the buffer by interrupts happening, and interrupts do not happen while your interrupt handler is running.

Now, if you could determine how much room was in the outgoing buffer, and you could send a message that fit in the buffer, you could safely use Serial.print()/write() in the ISR. But, there is no way to determine how much room is in the buffer.

However, my main loop code does a lot of things, and the time interval between polling this digital input has grown too long.

This is what you need to deal with.

Now the question I've seen asked before but not answered:

If I have to test the ISR-set flag in the main loop,
then why don't I just do a digital read in the main loop and delete the ISR?

Suppose that the PIR sets the pin HIGH for 5 milliseconds, and you check twice a minute to see if the pin is HIGH. What are the odds that the pin would still be HIGH when you looked?

Suppose, now, that the interrupt handler set a flag, which it can do because the interrupt handler is called as soon as the pin goes HIGH. What are the odds that the flag will still be set when you looked?

One reason is to ensure that short high transitions on the pin are captured which the main loop might otherwise miss. However, my motion detector holds the pin high for seconds - so this isn't an issue in this appplication.

The key is the last three words of that statement. For other applications, that is not the case.

Another possible reason is that doing repeated digital reads might be far slower than doing repeated boolean tests - can anyone say whether it is or not?

Orders of magnitude slower to read the pin than to read a variable.

Am I missing a better way?

Just like we are missing seeing your code.

Since you haven’t posted your code all I can do is guess.

Guesses
My first guess is that you have more than 10 lines of code in loop()

My second guess is that you have few if any functions in your code.

My third guess is that you are using the delay() function which means that loop() doesn’t repeat very fast.

My fourth guess is that if you have problems now you will have even more problems with an interrupt.

Comment
If your motion detector holds the signal high for longer than (say) 20 millisecs you don’t need to use interrupts.

Questions
Post your code
How many times per second does loop() repeat in your code?

Some reading material
Planning and implementing a Program

…R

Comment
If your motion detector holds the signal high for longer than (say) 20 millisecs the time it takes loop() to iterate you don’t need to use interrupts.

I fixed that for you. 8)

PaulS:

Comment
If your motion detector holds the signal high for longer than (say) 20 millisecs the time it takes loop() to iterate you don’t need to use interrupts.

I fixed that for you. 8)

You are too kind - but actually I intended it to be the way it was. I was (am) trying to get it into the OP’s mind that loop() should take a lot less than 20msecs to repeat. because I have the impression, from his post that it may well be taking much longer.

And also my comment was made in the context where he had said the detector “holds the pin high for seconds”

…R

but actually I intended it to be the way it was.

I understand that. The point I wanted to make, which goes along with your guesses, is that the time that loop() takes to iterate is the deciding factor. Clearly, OP needs to reduce that time. The 20 millisecond value is probably way higher than loop() should take.

Thanks for your comments gents.

Thank you Paul S. for telling me that Serial functions will work in an ISR. The buffer will never be full (he said confidently) so may try to use Serial.write() in the ISR to get good responivity and tidy code.

You both say the main loop should execute very fast. I had not known this and still don't know why. Why is this good practice? Just so that software polling is reasonably responsive?

Here it is:

void loop()
{ ScanSensors();
ProcessCommand();
ScanSensors();
checkTimers();
}; // loop

Scanning sensors polls the digital pin but also reads sensors like the DHT22 temp/humidty sensor - which requires delays to read it.
I also read battery voltage and in doing so I get the internal reference voltage, and do the sums on it, and I do it all five times and take an average. So there's a lot of work. I guess I'm missing a fundamental concept here - how do I distribute the work/time without the main loop taking the sum od the times of all the functions called within it?

Thanks for confirming digital read is far slowed than testing a boolean. Another good reason to go with interrupts.

I can see why my not posting the code would irritate. Apologies. It is large and I thought that posting all of the relevant facts (as I saw them) would be a more respectful way to ask for help from strangers who owe me nothing. I still have not posted the code. I appreciate your help, but there is a whiff off arrogant hostility which I am reluctant to drop my trousers in front of.

Robin, you can now see my loop code. As you can see, less than 10 lines and I do use functions (though neither of these will affect the loop execution time, presumably).

There are two delay()s in the code. One is in reading the DHT22. I power it up and wait 200mS for it to stabilise before reading it.
I suppose I should remove this delay by adding an event to the checkTimers() routine - or leave the DHT22 powered up and eat the power loss (this is a solar powered project). The second is a delay(2) in the code which reads the internal reference.

Your question "how long does the loop take to execute" is depressing because it's so obviously centrally relevant yet I don't appear to know the answer. Watching the solar panel click in and out tells me it takes about 5 seconds, which is how come the delay from detecting motion and taking action is unacceptable. But then - with no delays I am forced to wonder what takes so long, so I will look into that some more. If I can get the loop execution time down to less than half a second or so, then the problem goes away. If not, then I'll try interrupts.

Thanks

I’m sure I read somewhere that future versions of the system will add an
availableToWrite () or similar call to determine how much space is in the
TX buffer so that ISRs could write (after checking)…

Or maybe it was a nice dream…

Still you can go into HardwareSerial.h/cpp and add your own call if you
want to… The serial stream has a _tx_buffer slot used to buffer the chars.

Scanning sensors polls the digital pin but also reads sensors like the DHT22 temp/humidty sensor - which requires delays to read it.

No, it does not. The DHT22 can not be read too often, and does take time to produce a reading. But, you can use the blink without delay technique to start the reading going, and walk away. Check back later to get the result. It is not necessary to wait around.

I also read battery voltage and in doing so I get the internal reference voltage, and do the sums on it, and I do it all five times and take an average. So there's a lot of work.

In your mind, writing the code. The Arduino can do that rapidly.

So there's a lot of work. I guess I'm missing a fundamental concept here - how do I distribute the work/time without the main loop taking the sum od the times of all the functions called within it?

How often do you really need to read the temperature? Do you really need to wait for the reading to be taken?

How often do you really need to read the battery voltage?

Would it be possible to do one on one pass through loop() and the other on another pass?

Your question "how long does the loop take to execute" is depressing because it's so obviously centrally relevant yet I don't appear to know the answer.

One to three lines of code, to Serial.print(millis()), and you'd know how long it took.

ChrisXenon:
Robin, you can now see my loop code. As you can see, less than 10 lines and I do use functions

Seriously - full marks, go to the top of the class. So many people have about 2 pages of spaghetti code.

(though neither of these will affect the loop execution time, presumably).

The word "neither" implies 2 and there are in fact 4 function calls and 3 different functions (this is not a serious comment)

What is serious is your statement that the functions won't affect loop() execution time. Of course they will - they are the ONLY thing that affects loop() execution time.

I agree with @PaulS about the DHT device - start it going but don't hang around waiting for it. Come back to it later in a future iteration of loop().

If loop() iterates frequently then it can easily poll the motion detector several times during the period that the signal is HIGH. Obviously you are just interested in the first reading and must then make sure it has gone LOW before treating another HIGH as a new event.

...R

Thanks for your further comments. Actually, I mis-informed you - sorry about that - in that I read temperature once every 10 minutes - not every time around the loop.

So I added code to toggle a digital pin each time around the main loop, and looked at it on a scope. When it's "doing nothing", each loop takes 40uS - astonishingly fast to me.

When it is doing things like processing a command, the loop time goes up to 4mS - a thousand times slower, but still WAY fast enough.
So why was the system failing to take a photo of the thing causing the motion trigger? Well, this was progress in that it forced me to see that the problem was not where I thought it was.

It is in the Android phone component. The motion is detected and reported via Bluetooth within 2 mS, so the uC component if off the hook. But the phone hoos and haws around for 2 seconds before it takes the phone. All of that seems to be tied up with very repetitive Bluetooth debug code in logcat, but anway - I'm now off to catch another goose.

Thanks again for your help.

Chris

ChrisXenon:
But the phone hoos and haws around for 2 seconds before it takes the phone.

I presume you mean "takes a picture"

That delay is a real PITA. I don't know if there is any way round apart from taking a movie and starting it before the event you want to capture.

In the good ol' days of manual focus 35mm film cameras the shutter was pretty much instant.

...R