0

I was conducting some research on wireless communication between a simple transmitter and receiver module using an Arduino. Now, I tested the channel accuracy and i received 14% error for a delay of one micro second. For a delay of 2 microseconds, my error was 0%, and for three microseconds, my error rate was 30%. I am not quite sure why the data would result in this. I think it possibly has something to do with the gain on the receiver and the delay time. Some sweet spot perhaps? Can anyone offer me some ideas/explanations? Also, sorry for any confusion in this question. I am happy to clarify.

Part 2:

Hey Everyone,

I also had a question regarding what purpose each of the following delay serves in wireless communication.

// Write each of the 8 bits
for (uint8_t i = 8; i > 0; --i)
{
  if (b & 1) // choose bit
    *reg |= reg_mask; // send 1
  else
    *reg &= inv_mask; // send 0

  tunedDelay(delay);// line in question ---------------------
  b >>= 1;
}

and the delay used in the regular blink program.

The regular delay used in the blink programs was what set the 2 microsecond delays.

Lastly, I am looking at the read and recv methods in the software serial library and i don't see any checks for start bits, so what's the point of writing one in the write method?

5
  • Related to Basic Serial Transmission Protocol and How to find the deferenced value of a transmit Port Register?. The bit of code you are quoting is from SoftwareSerial, and the delay is what makes the bits come out at the intended baud rate. The regular delay used in the blink programs was what set the 2 microsecond delays. - what do you mean by that?
    – Nick Gammon
    Commented Oct 7, 2015 at 0:59
  • So, you can open up the Arduino IDE, and type in delay(x) without have to physically import any new libraries other than the ones automatically inherited. That delay is what i am referring to.
    – Jonathan
    Commented Oct 7, 2015 at 1:07
  • How do I link other posts? Is it something I have to do elsewhere? Or do i just link them on the post and they will automatically show up as linked posts?
    – Jonathan
    Commented Oct 7, 2015 at 1:09
  • "Lastly, I am looking at the read and recv methods in the software serial library and i don't see any checks for start bits, so what's the point of writing one in the write method?" ... So that you can communicate with hardware UARTs, which do require a start bit... Commented Oct 7, 2015 at 1:32
  • How do I link other posts? - if you mention another StackExchange post it is automatically added to the linked posts.
    – Nick Gammon
    Commented Oct 7, 2015 at 2:03

2 Answers 2

1

Now, I tested the channel accuracy and i received 14% error for a delay of one micro second. For a delay of 2 microseconds, my error was 0%, and for three microseconds, my error rate was 30%.

Microsecond resolution delays on an Arduino are tenuous at best. They aren't that accurate. Simply because you're hitting the limit of the speed of the CPU.

At 16MHz you have a clock period of 1/16000000 seconds. That's 63ns. That's the granularity of the delays you can achieve - and that can only be achieved by inserting a precise number of NOP assembly instructions.

So for the best 1µs delay possible you would need (0.000001 / 0.000000063) 15.87 NOP instructions. Of course, you can't have that many, so you have to round it either up or down. Normal integer truncation would result in 15. So that would actually be a delay of 945ns, which is an error of 5.5%.

2µs would be (0.000002 / 0.000000063) 31.74 NOP instructions, which would round to 31, so 1.953µs, or an error of 2.35%. As you can see increasing the length generally reduces the error.

HOWEVER

The delay function on the Arduino for microsecond accuracy (delayMicroseconds()) is maybe not as accurate as it could be. Simply because it's impossible to just have an exact number of NOP instructions for a delay since you can't know how long the delay should be until the function is called with a parameter detailing how long the delay should be. So it has to perform a number of calculations and make a number of assumptions. It tries to come close, but it was written for one specific version of the compiler and compilers change. Things may have been changed in how the function gets called, and how long parts of it take (what registers are pushed and popped may change with different compiler versions, for instance). So it's not a perfectly reliable delay.

For more detail you can look at the source code of it (it's in wiring.c) and you can see how for small delays it's very hard to get it right. For instance, this couple of lines highlights the problem nicely:

    // for a one-microsecond delay, simply return.  the overhead
    // of the function call yields a delay of approximately 1 1/8 us.
    if (--us == 0)
        return;

Note the use of the word approximately there...

Also you need to note that the delay functions are just that - a delay at that specific moment in time from the start of the call to its finish. It doesn't take into account anything else that's going on in your code, so a delay of 1µs in a loop of code that does other things will end up in a loop that takes X + 1µs where X is the amount of time that the rest of the code takes to execute.

And that's where the tunedDelay() function comes in. It has been specially written to be used in that specific circumstance to provide a delay that takes into account the time the rest of the loop takes to run. That is, the loop's time plus the tunedDelay time equate to the final period you want for the whole loop. For asynchronous serial communications the timing accuracy is important. Since there is no separate (or even embedded) clock to get the timing from both ends have to agree what speed the communication happens at (the baud rate), and any deviation from that can cause communication errors. So the timing has to be as close as possible. A normal delayMicroseconds() function call just wouldn't cut the mustard - they needed it more precise than that. Manually tuned to have specific delay values that are as precise as possible, and that was done for specific baud rates on specific speed chips. Each supported frequency has a table such as this:

static const DELAY_TABLE PROGMEM table[] =
{
  //  baud    rxcenter   rxintra    rxstop    tx
  { 115200,   1,         17,        17,       12,    },
  { 57600,    10,        37,        37,       33,    },
  { 38400,    25,        57,        57,       54,    },
  { 31250,    31,        70,        70,       68,    },
  { 28800,    34,        77,        77,       74,    },
  { 19200,    54,        117,       117,      114,   },
  { 14400,    74,        156,       156,      153,   },
  { 9600,     114,       236,       236,      233,   },
  { 4800,     233,       474,       474,      471,   },
  { 2400,     471,       950,       950,      947,   },
  { 1200,     947,       1902,      1902,     1899,  },
  { 600,      1902,      3804,      3804,     3800,  },
  { 300,      3804,      7617,      7617,     7614,  },
};

That's the value to send to the tunedDelay() function in the four different situations where its used for each of the baud rates at that specific clock frequency (16MHz in that example).

tunedDelay() itself is written in assembly code and is a very tight loop for the number of iterations specified in the parameter. The shortest possible piece of code with the minimum of overhead to get the maximum accuracy and finest granularity (least number of clock cycles for one iteration) out of it.

7
  • Thank you so much sir. That was an amazing answer. Thank you so much!
    – Jonathan
    Commented Oct 12, 2015 at 18:10
  • Can you please explain what are the purposes of rxcenter, rxintra, rxstop, and tx?
    – Jonathan
    Commented Oct 12, 2015 at 18:12
  • 1
    They are how long to wait before looking at the state of the line for a specific bit (rx...) or how long to wait between sending each bit (tx)
    – Majenko
    Commented Oct 12, 2015 at 18:40
  • So does rxcenter wait that many nanoseconds after receiving the startbit, the rxintra waits that many ns between each subsequent payload bit, and the rxstop waiting that long for the stop bit(even though there is no stop bit)? Am I understanding that right? ^^
    – Jonathan
    Commented Oct 12, 2015 at 18:52
  • 1
    It's not nanoseconds - its loop counts, but that is pretty irrelevant, it's just a period of time. rxcenter starts the reading at the center of a bit - give it time to arrive, but not too long - just the first bit after the leading edge of the start bit. rxintra is how long to wait between each successive bit, and rxstop is how long to wait to sample the stop bit. tx is like rxintra but for transmitting.
    – Majenko
    Commented Oct 12, 2015 at 19:00
1

I am looking at the read and recv methods in the software serial library and I don't see any checks for start bits, so what's the point of writing one in the write method?

The start bit is absolutely essential in serial comms. In the graphic below, the start bit is marked "B" - which distinguishes it from "idle" data which is "A". Once received, the receiver then waits 1.5 bit times and starts clocking in the remaining bits. (Starting at "C" in the diagram, and then moving on for a total of 8 bits).

One serial byte

Without a start bit, how would the receiver know when the byte started? Imagine you wanted to send 0xFF (all 1 bits). Without a start bit that would be indistinguishable from the idle state.

In the graphic, "D" is the stop bit. Why have a stop bit? We need one (which is a "1" bit), so that we can detect the transition from the stop bit in the previous byte (a "1") and the start bit in the next byte (a "0").

In the graphic, "E" is the start bit for the next byte.


I don't see any checks for start bits ...

SoftwareSerial has a pin-change interrupt set up to detect that the receiving pin has changed state (to a zero usually). That is your start bit. You don't see it in the loop, because it has already arrived, otherwise you wouldn't be in the receive loop in the first place.

3
  • Hey @NickGammon, i have a question. So let's say the receiver is high, and it reads a 0 not because of the write function but because of interference/noise, and so it says, ok time to read 1 byte. How come it doesn't take the next 8 bits, and covert it into a integer? However, when the write sends a 0, it quickly converts the next 8 bits into a integer. How does this get resolved? Or am I completely missing the ball park here?
    – Jonathan
    Commented Oct 12, 2015 at 18:49
  • Also, i believe this is the line you're referring to regarding reading the start bit: if (_inverse_logic ? rx_pin_read() : !rx_pin_read()) and uint8_t SoftwareSerial::rx_pin_read() { return *_receivePortRegister & _receiveBitMask; } So because the pin is on high, rx_pin_read will always return 1 in that register right? In which case, !rx_pin_read will evaluate to false won't it? never going into that if statement. Or am I totally off?
    – Jonathan
    Commented Oct 12, 2015 at 19:00
  • That is because it is a pin-change interrupt, which responds to a change in state. That line tests whether we are changing to the right state (normally from 1 to 0). The reason we are there in the first place is because of the interrupt.
    – Nick Gammon
    Commented Oct 12, 2015 at 19:39

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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