Serial bit banging MSB flipping

Hello!

I've written a piece of blocking code to watch a pin and read a char when available. The char is encoded in regular old ASCII over RS232 serial - nothing fancy here. By and large, it works - however, half the time, I get the wrong char saved. I send an 'e', I get an '%'. I send an 'f' I get a '&' about half the time. The other half of the time, I get the expected 'e' or 'f'.

Those familiar with your ASCII tables will notice that 'e' and '%' are identical save their MSB, which is broadcast last. Ditto for 'f' and '&'. So what's happening is the last bit in the serial stream isn't being picked up and is undefined.

  int currentChar = 0;
  int iInputArray[8] = {0,0,0,0,0,0,0,0};
  
  // Listen for rxPin to go high - that'll be the rise of the start bit
  while(!digitalRead(rxPin)){} 
  
  delayMicroseconds(iHalfCycleDuration); // Wait half a cycle (mid of start bit)
  delayMicroseconds(iCycleDuration);// Wait until mid of first bit
  for (int i = 7; i > 0; i-- )
  {  
     iInputArray[i] = !digitalRead(rxPin); // grab this bit
     
     delayMicroseconds(iCycleDuration); // Wait until mid of next bit
  }
   
  for (int i = 0; i < 8; i++ ) // Show me what I saved, raw!
  {  
    sendChar(' ');
    sendChar(iInputArray[i] + 48);  // 1 or 0, please
  }  
  sendChar(0x0A);
  
  for (int i = 1; i < 8; i++ ) // Show me what I saved, in character form!
  {  
    currentChar = currentChar + (iInputArray[i] * round(pow(2, (7 - i))));
  }  
  sendChar(currentChar);

From my experimentation, I find that the codes are recieved in reverse (LSB first) and inverted. This is why I do a count-down loop (to fill my bit array in the right order) and invert the digitalRead(). I only grab the first 7 bits (the last of which is the offending bit).

The output is as follows. The MSB is ignored as it's not part of the ASCII definition. The translation between bit array and char is correct.

 0 0 1 0 0 1 0 1
%
 0 1 1 0 0 1 0 1
e
 0 0 1 0 0 1 0 1
%
 0 1 1 0 0 1 0 1
e

Now, I'm pretty sure the problem is timing drift. Currently, iCycleDuration = 104, which I derived from the bit pulse width at 9k6 baud (1 / 9600 = 0.000104). So, I position myself in the middle of the start bit then wait until I'm in the middle of the data bits and repeat. I wouldn't envisage enough drift to put me 52uS out, necessary to start causing problems.

But, if I adjust my in-loop delay to

     delayMicroseconds(iCycleDuration - 3); // Wait 101 instead of 104uS

The detected values are always right. No randomness - 'e's and 'f's all the time. Which suggests that I've massively underestimated the time it takes for an ATMega168 to execute an assignment into an array.

Sound plausible? Anyone see where I'm going wrong?

Which suggests that I've massively underestimated the time it takes for an ATMega168 to execute an assignment into an array.

Actually, it wouldn't be this, it would be the digitalRead(). I'm not sure how long it takes, but I see that anagloRead() takes a whopping 100uS, so it seems likely the digitalRead() might take 5+ uS.

I'll do some timing tests when I get home and adjust my cycle times accordingly. Although, presumably, if I'm drifting by 52±3 uS over 7 iterations, my guess is that the digitalRead() is taking 7-8uS.

Which suggests that I've massively underestimated the time it takes for an ATMega168 to execute an assignment into an array.

If you look at the "read" method in SoftwareSerial, you'll see that the bit timing ("bitDelay") is the calculated bit time minus fifty microseconds, so, yes you've underestimated, but the time is probably lost in "digitalRead" (in "wiring_digital.c").
Also, your array access may be a little quicker if it were of type "byte" instead of "int".

A digital read takes 1 instruction cycle. I think the ATMEGA chip has a 1 to 1 mapping from clock speed to instruction cycle, so a digital read would take 1/16th of a microsecond.

I found this by Oracle on another thread, which suggests maybe the digitalRead() isn't the culprit, although a different user says this is only true for direct port manipulation, and that the digitalRead() abstraction may still take a long time.

Also, your array access may be a little quicker if it were of type "byte" instead of "int".

I'll try this. I've not used bytes before - I assumed they were pretty much identical to chars (and, by extension, ints). I'll have to research what bytes are best used for...

I assumed they were pretty much identical to chars (and, by extension, ints).

Bytes are half the size of ints on the Arduino.
"byte"s are identical to "unsigned char".
[edit]I'm curious - why aren't you using the soft serial library?[/edit]

I'm curious - why aren't you using the soft serial library?

If this is the Serial.begin()/Serial.print() etc: two reasons - one, I wanted to understand the RS-232 protocol better (as I'd like to do more bit-level serial communication in the future (notably fbus)). Two, I need to communicate with three devices, but only have the one USART on the AtMega168, which meant I needed to make my own implementation (or find one, but that's no fun). I realised a little later that, since these three connections wouldn't be simultaneous, I could have used some manner of hardware multiplexing - but I'd got be reading and writing largely working by then..!

If this is the Serial.begin()/Serial.print() etc

No, thats hardware serial - the stuff I'm talking about is here:
http://arduino.cc/en/Reference/SoftwareSerial

You've got the sources in your installation - why not pick them apart to learn / do what you want?
No fun maybe, but saves you re-inventing the wheel.

Huh. How about that.

I guess the reason I'm not using that is largely because I didn't know about it :slight_smile: Still, I've had some fun learning it from scratch - I'll treat these libs as a sheet sheet to finish and optimise it :slight_smile:

Although I've not looked at it in depth, I think I may be able to improve on it slightly. This lib says its read function - like mine - is blocking until a char arrives and that no isAvailable() is available. By hooking an interrupt to the rx line, we can trigger a listening event on the start bit. Thus, we can save a "last received" char into a buffer for asynchronous reading, making the read non-blocking and the isAvailable() meaningful.

But I may be getting ahead of myself there... ;D