3

I would like to use an inertial sensor to measure acceleration and send the results to an Android app using a serial communication with a bluetooth Mate Gold module. The sensor provides a data ready interrupt which notifies Arduino-Mega that a new value is stored in a register of the module. The output data rate can be modified during the configuration of the sensor. I want the information to be sent at a 10 Hz rate to the bluetooth module.

Is an interrupt at 400Hz capable of compromising the integrity of a 50 bytes long message sent at 10 Hz with a baudrate of 9600? If yes, which approach would you recommend to correctly send the data?

Edit: I'm using the following formula to compute the amount of time required to send N chars.

bytes x bits_per_character / bits_per_second
N x (8+2) / 9600 = N/960 secs

For N = 52:

52/960 = 0.054 secs = 54 ms

So theoretically, I can send a 54 char-long message at 18Hz with a baudrate of 9600.

Edit2 I can use the hardware serial or the Software Serial interface.

  • Have you done the math? – Ignacio Vazquez-Abrams Mar 13 '15 at 1:03
  • Hi Ignacio, is the math correct? – UserK Mar 13 '15 at 1:13
  • 1
    The arduino has hardware serial. So it sort of runs in the background, independent of the main processor. – Gerben Mar 13 '15 at 15:31
  • 1
    SoftwareSerial disables servicing interrupts during transmission of every byte, although interrupts are allowed between consecutive bytes. Looks like you should be safe, but you will have some latency in responding to the sensor interrupt. – Edgar Bonet Mar 13 '15 at 17:11
  • 1
    Actually I was wrong: at 9600 bps, interrupts are blocked during 9/9600 s ≈ 938 µs (plus a few cycles). They are enabled right after the stop bit has been written, which means they can be serviced during the stop bit period. – Edgar Bonet Mar 15 '15 at 17:14
5

If you are on the ardruino platform, and are using the hardware serial port, you basically don't have to do anything.

The Serial library interface already uses a outgoing data-buffer (it's varies between versions, 1.5.5 has it set to 64 bytes. The arduino people keep changing it's size for no reason), where the call to Serial.print() or Serial.write() only places the bytes in question into the outgoing buffer, and the buffer is then emptied asynchronously in the background by the TX-ready (UDRE) interrupt (which is then disabled when the buffer is emptied).

Basically, all you have to do is ensure that you're not placing data into the transmit buffer from both an interrupt and the main loop(), because it's then possible for the message from the interrupt to be inserted into the middle of the message from the main loop thread.

This can be easily enough done by either guarding the call to Serial.print() with cli() sei() interrupt guards in the main loop() section (or the useless arduino analog: noInterrupts() and interrupts(), which are just named proxies for cli() sei(), which exists for no good reason), or simply not printing from an interrupt (which is a good idea anyways, string formatting can be slow).

  • 1
    As of 1.5.5, the transmit buffer in HardwareSerial.h is 64 bytes so his 54 character messages will fit. – jdr5ca Mar 15 '15 at 4:00
  • 1
    @jdr5ca - Good to know. The only arduino package I have installed is 1.0.5, where it's 512 bytes(!). – Connor Wolf Mar 15 '15 at 4:01
  • That is fixed back to 64 in my 1.0.5-r2 install. Two buffers per serial port is 1k! With 3 ports on the Mega, that would suck up 3k of RAM = doh! – jdr5ca Mar 15 '15 at 4:21
  • @jdr5ca - Yeah, it's possible I was hacking one some bits, and that value is not original. I removed the text. – Connor Wolf Mar 15 '15 at 4:35
  • cli() and sei() disables/enables all interrupts, correct? I have got problems with servo controlling (jittering) because of that. A decoupling queue to avoid corrupted serial communication was the only solution in my case. – salocinx Jun 9 '17 at 9:58
2

You want to send a 50 byte (or 52 byte?) sequence every 100 ms (10 Hz).

For hardware serial, that will go into a buffer which is emptied via serial interrupts; it should be emptied well before the next 100 ms tic (as you have calculated).

You will get about 40 sensor interrupts during that 100 ms. With hardware serial, that need not be a problem - so long as you don't send bytes from within a sensor interrupt. If your sensor interrupt just accumulates results and the main loop (non-interrupt) uses those results to periodically send the 50 byte message, you should be good.

Suppose you were averaging each of the sensor readings. In the sensor interrupt you might add 1 to the count and add the reading to a sum value. In the main loop, test if it has it has been 100 msec since the last send, and if so turn off interrupts, divide the sum by the count and store that average in a new variable, zero the sum and count, turn on interrupts. Then use the computed average to format and send the 50 byte package.

That's just an example. The main thing is to make the sensor interrupt quick and simple; and to format and send the packet in the main loop (inhibiting interrupts temporarily while fetching and clearing the variables which get updated during the sensor interrupt).

Be sure to use "volatile" for the variables used by both the sensor interrupt and the main loop.

The serial should take care of itself if you use hardware serial, and might even be OK for software serial. In the latter case, a sensor interrupt in the middle of sending the bits of a single character/byte would mess up the bit timing, but software serial should inhibit interrupts during that time - so your sensor interrupt might be delayed up to a millisecond.

1

What you are looking for is a way to safely interleave transmitting characters while at same time responding to the interrupts. You can wrap the sending of bytes with

noInterrupts();
swSerial.write("somethinglongyaddayadda"); // takes 50-60 ms
interrupts();

That will protect the write() operation from being interrupted and ruined by you sensor interrupt handler. But as you note, that will block the ability to process the the sensor every 2.5 ms. During the noInterrupts() period you would miss readings.

To achieve the interleave of both operations, you should transmit the message one character at a time. Implement a transmit queue to hold the outbound string. When it has characters, send them 1 at a time, wrapping each interrupt protection:

...inside loop...
if(txQueue is not empty) {
  // do not need noInterrupts() or cli() as these are inside write();
  swSerial.write(one character from Q); // takes ~1 ms
  txPointerHead += 1;
}
// at this point the interrupts are enabled 
// and the sensor will be processed if available
// if the interrupt occurred in the middle of the above, 
// the level is still set and the interrupt handler will be invoked

The 1 ms is ideal and not accounting for overhead, but it is unlikely the 1 character write time could stretch out to 2.5 ms. As long as the write time stays under 2.5 ms, you will never drop a sensor reading.

Note that this interleave means that the characters will have larger inter-character delay. The serial ports will not care about that. But if the processing of the sensor data takes too much time, you will not be able to keep up with your 10 messages per second goal.

Some options I see to improve:

  1. Use a higher baud rate. The baudrate of the serial port is not related to the bluetooth data rate, so you might as well use the 115.2k. That will reduce the blocking time. The baud rate doesn't even need to be the same on both ends. The BT module isn't a simple modem; it is packetizing characters wrapping them in encryption and transmitting. The receiver unbundles the packets and pushes out its serial port at the receivers baud rate.

  2. Use hardware serial. Using a hardware serial port greatly reduces the time because all write() does is put one character into a register. The time for that is microsecond domain. After the byte is put in the transmit register, the UART handles the clocking of the bits so the microcontroller is free to do other tasks.

  • 1
    Assuming you're on the arduino platform, the Serial library already does all this for you. It uses an internal 128-byte outgoing ring buffer, and the buffer is emptied by the transmit-ready interrupt of the USART. – Connor Wolf Mar 15 '15 at 3:09
  • True point that SoftwareSerial turns off interrupts character by character, so the calls are redundant. SoftwareSerial uses the Print::write(uint8_t*,size_t) which is a synchronous call that does not return until all the characters are written. There is no transmit queue which would allow an asynchronous call (at least in my 1.5.5). So if he does not want the main loop blocked for the 50 ms periods during the transmit of the entire string, this was my approach to keep the loop() running. – jdr5ca Mar 17 '15 at 6:20

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.