ATTiny85 recieving garbage from Serial Comms

Greetings!

I've been making a project for my masters thesis involving controlling a rather simple device of my own making with an ATTiny85, and I've been having problems with one part of the serial communication:

I successfully managed to send data from the ATTiny85 to my computer through an FTDI cable and even through an Arduino Uno and Leonardo serving as USB-serial converters. However I can't for the life of me get the ATTiny to read anything but garbage with the actual data scattered through that noise signal.

I checked to see if it was a problem with the FTDI cable by using the Uno and Leonardo to recieve information from my computer and echo it back, and they send it back fine.

The ATTiny also sends information of its own fine, the problem is really on what it's reading from the RX line.

I'm using SoftwareSerial and I've tried several BAUD rates so far: 2400 through to 19200. I'm also using the ArduinoISP using the Uno as an ATTiny programmer.

Looking around the web gave me no answers, most of the problems were related with the transmission from the ATTiny, not the ATTiny itself recieving.

Here's the ATTiny code (excuse the mess, I've been commenting lines in and out):

#include <SoftwareSerial.h>

#include <avr/io.h>
#include <avr/delay.h>

#define RX 3
#define TX 2

int a=0;
char error[]="ERROR";

SoftwareSerial mSerial(RX,TX);

void setup(){

  pinMode(RX,INPUT);
  pinMode(TX,OUTPUT);

  
  mSerial.begin(9600);


}


void loop(){
  

  
    if(mSerial.available()>0){
      
 
      a=mSerial.read();
      if(a<0){
        mSerial.write(error);
      }
      else{
        mSerial.write(a);
      }
      
    }

    
  
}

The circuit is just the normal Vcc + Gnd from the cable/Arduino powering the ATTiny, and the RX/TX lines of one connected to the TX/RX lines of the other.

Anyone has any ideas why I'm having so much noise in the RX input of the ATTiny?

I'll try to get this hooked up to an oscilloscope when I can get access to one here in the university and see what's going on with the signal waveform itself, it might help pinpoint the problem.

Thank you for your time!

What is the clock source and frequency of your ATTiny85?

Yeah, I'm sorry, forgot to specify that:
It's the internal clock at 8MHz

It's the internal clock at 8MHz

I suspected that. The most likely reason would be timing errors due to the tolerance of the RC clock. You probably need to use a crystal.

If you have access to a scope or frequency counter, you can set the CKOUT fuse bit and then measure the actual clock frequency on pin 3.

Indeed that was one of my suspicions, but I found it weird that I was recieving data properly from the Tiny, no garbage at all, which is, I suppose, strange, because shouldn't both the RX and TX be having problems if the culprit is the internal clock?

You are correct that both RX and TX use the same clock source and they use the same delay routine in the softwareSerial library. But, the timings are ever so slightly different for RX and TX.

One other thought is to try moving RX/TX to pins 5/6 (PB0/PB1) - as these pins support interrupts on the '85 but I'm not sure if that will change the outcome. Worth a try though.

Hopefully one of our resident Attiny experts will weigh in...

Will do! Thank you for the help!

As suspected swapping the pins did nothing. I'm still only able to cleanly recieve from the ATTiny. Perhaps lowering the BAUD rate even further might at least make things more readable to the ATTiny. If that doesn't work, I'll just try tuning the clock.

You might try NeoSWSerial... The way it calculates bit times is different, so it might work. It's available from the Arduino IDE Library Manager, under the menu Sketch -> Include Library -> Manage Libraries.

NeoSWSerial is also much more efficient. SoftwareSerial disables interrupts for long periods of time, when a character is sent or received. Disabling interrupts for 1ms is an eternity for a 8MHz processor. It can't do anything else but wait for the character to finish. It could have executed 5,000 instructions during that time.

You might look at AltSoftSerial, too. It would be even better, but I'm not sure it supports the ATtiny85. You would have to use INT0 (PB2) for the RX.

Are you trying to output serial while also receiving serial input? You can't do both at the same time with software serial - it's half-duplex.

That means a sketch that echoed every character received back wouldn't work if it received two characters, because it would be receiving the second character while trying to echo the first.

Hey!

Back again,

It's not the internal oscillator. I've been using an oscilloscope and it's giving me a clock deviation of about 1-2% which is more than acceptable for this.

I decided to try and make the Tiny only read from Serial rather than read/write and it works perfectly fine (it should blink an LED if it recieves an 'A', and it never seems to fail. The oscilloscope also confirms the RX pin to be noise free, so yes you were right in saying this is SoftwareSerial's inability to send/recieve data simultaneously.

I'll try that other SoftSerial library you mentioned, but do you think SoftwareSerial could read/write if I added a reasonable delay between the functions? How would that work? Processing speed isn't really a central point in this project as the machine this is going to control will be basically static for upwards of 5 minutes at a time without anything changing.

EDIT: Okay apparently a 1 second delay between the read and write operations is enough to make the read function actually read stuff properly. I still get a random 98 or 2 value on the byte I'm echoing back but in very low frequency, like once every 10-15 seconds. Gonna give NeoSWSerial a try.

EDIT2: Ok something weird is happenning (still SoftwareSerial): This code (the setup is identical to my first post):

void loop(){
  
    if(mSerial.available()>0){
  
      a=mSerial.read();
      
    }
    delay(1000);
    
    mSerial.write('d');mSerial.write('a');
    mSerial.write(a);
    mSerial.write('d');mSerial.write('b');
    mSerial.write(b);
    mSerial.write(b>>8);

    if(a==66){
          for(i=0;i<5;i++){
            digitalWrite(0,HIGH);
            delay(100);
            digitalWrite(0,LOW);
            delay(100);
          }
    }

    a=0;

}

For some reason "a" is equating to whatever value is in the if condition. If I put 65 there it jumps to 65 for most of the time, if I put 66 it stays mostly on 66, if I put any other value, same thing happens. I don't even send anything through Serial, it just does it on its own.

I'll try that other SoftSerial library you mentioned

"SoftSerial" is an old name for "SoftwareSerial", so I hope you meant AltSoftSerial or NeoSWSerial. They are both drop-in replacements for SoftwareSerial, so I don't understand why you are trying to make SoftwareSerial work.

, but do you think SoftwareSerial could read/write if I added a reasonable delay between the functions?

If you cannot reliably predict when you might receive information, you cannot use SoftwareSerial to transmit. Transmitting would prevent receiving anything.

Adding delay is rarely necessary. It keeps your sketch from doing other productive things, like reading characters before an overflow happens. Using delay with SoftwareSerial turns your 8MHz Arduino into a thumb-twiddler.

Ok something weird is happenning (still SoftwareSerial):

That sketch needs serious work, but we still don't know what you're trying to do. Why would you compare "a" everytime through loop if it hasn't changed? It won't change unless you read a character. You won't read a character unless one is available.

Yeah I said softserial because they're all software serial libraries, I was referring to the NeoSWSerial, sorry.

The reason for that code is because I am constantly sending a byte (through software on PC that continuously sends a specific byte (in this case it holds the value 66)) to the ATTiny (so Serial.available is always active), which should be saved in "a".

What I'm doing is just checking if the value saved in "a" is indeed "66" and then blink an LED 5 times.

What is happenning though, is when I don't send anything from the computer, "a" should be 0, as specified in the code, as "a" is reset to 0 everytime the loop goes around, but rather than doing that it just goes to 66 or whatever value I have on that if() condition (and the LED blinks), even if I reset the Tiny and never send any data. It's not noise as 90% of the time that's the value that's held in "a" (again without me sending anything).

But since this is using SoftwareSerial, it might be the same problem with reading and writing to serial too quickly, I just find it weird that the value "a" stays at is the same value in the if() condition, when it should be 0 as there's no serial data incoming (theoretically).

Also yeah, I don't think it's worth dwelling too much into this problem, since there are better alternatives, as you say.

Again thank you so much for your time.

I am constantly sending a byte... to the ATTiny

You should rethink that. Just send a byte when the value changes. Why? Because of this code:

   mSerial.write('d');mSerial.write('a');
    mSerial.write(a);
    mSerial.write('d');mSerial.write('b');
    mSerial.write(b);
    mSerial.write(b>>8);

That writes 7 bytes. So if the ATtiny receives more than 1 byte in that transmit time, the input buffer will eventually overflow and some received bytes will be lost. The ATtiny will have to write 7 * 64 bytes before a changed byte is detected. At 9600, that takes (7 * 64 chars) * (10 bits sent/char) / (9600 bits/second), or 0.47s.

Is that kind of lag acceptable? Can the bytes sent to the ATtiny be something like

    BBBBBBBBBBBBBBBBABBBBBB

If so, the lone "A" character may get lost. Is it ok to lose specific characters?

It might make things clear if I tell you what the device in which the ATTiny is installed is supposed to do:

It should receive a byte from the computer which is an angle (0-180 deg) value for a servomotor (microservo in this case, which I can already control through the ATTiny PWM).
It should send two bytes (a short int) which is the ADC value from a Hall sensor (which I can already do) and the current angle, single byte (optional).

The protocol I have there is it sending "da" to warn the PC software of incoming angle data (single byte) and sending "db" to warn of incoming Hall sensor data. This works pretty well even though it's probably a convoluted solution, but this is still very early in development anyway, so plenty of room for improvement.

I am going to have the program on the computer to just send the angle data on request, when the user inputs a new angle for the servomotor, but it will constantly read what the Hall sensor is measuring.

The only reason I was sending the same byte constantly was just for testing purposes, so I knew for sure that it was sending data to the ATTiny, but like you said, not the best thing to do.

It's sort of crucial that the ATTiny doesn't lose the angle data for the servomotor or mainly that it doesn't read junk data, as I don't want the servo to jitter or move to a random angle out of the blue, but speed itself is no issue as the device will change the angle of the servo and will not read anything else from serial until operations external to it (in this case a spectrometer measuring a Raman spectrum) are complete, and the user inputs a new angle value, which can take at least 1 or 2 minutes, but will keep on reading the Hall sensor and sending that data to the computer continuously during that time.

The protocol I have there is it sending "da" to warn the PC software of incoming angle data (single byte) and sending "db" to warn of incoming Hall sensor data.

Ok, that's the message "header".

It's sort of crucial that the ATTiny doesn't lose the angle data for the servomotor or mainly that it doesn't read junk data

Sure, typical communications problem. Add better error detection, because the NMEA xor is not very good. Actually, it's terrible. I highly recommend the Fletcher Checksum. Easy to understand and implement, decent detection.

You could also add an acknowledgement from the ATtiny to the PC, so the PC knows the ATtiny has received the message correctly. Yes, the ACK packet should have a checksum on it. And the PC should ACK the ACK, so the ATtiny knows the PC got the ACK... :wink:

Hey!

NeoSWSerial is working like a charm! Barely any data loss except for very few times where the Tiny just doesn't read anything so it sends back a default value, which is very very rare since I left this running for about 15 minutes while logging the results and there were very little meaningful spikes, less than a handful, when I plotted the data.

I will now look into error detection methods, like you suggested.

Thanks a lot for your help!

EDIT: I'm a bit stumped by this:

So I had Serial comms working, along with the ADC. I implemented the servo control using the Servo8Bit2 library, which I knew worked on the ATTiny85 from previous tests, and neither Serial or Servo works when they're both active in the code. I assumed they were using the same Timer, which is true. Servo8Bit2 has an option to enable Timer0 while NeoSWSerial uses Timer1, but simply changing that in Servo8Bit2 produces no results, ie, the chip still hangs.
Does the ATTiny85 really have two independent timers or is this due to something other than Timers? As far as I can tell, Servo8Bit2 doesn't use delays or anything that could mess with Serial interrupts.

EDIT2: Okay apparently both NeoSWSerial and Servo8Bit2 use the same interrupt table. I'm trying to solve this by making them function in alternate program cycles (literally using a if(counter%2==0) condition), which sorta works, but the Serial interrupts still creep into the Servo8Bit2 PWM control which makes the servo jitter sometimes.

I'm going to try to disable serial while the servo is being controlled; Do you happen to have any suggestions on how I can separate them further?

I should probably start a new thread...

Servo8Bit2 has an option to enable Timer0 while NeoSWSerial uses Timer1

NeoSWSerial *re*uses the same timer that is used for micros() and millis(). That's TIMER0 on most Arduinos, but it's TIMER1 on the ATtiny85.

If you look at the spec sheet for the ATtiny85, it has two timers: TIMER0 and TIMER1. I'm not sure why micros() uses TIMER1, but it may be because TIMER1 does not have PWM capability. That leaves TIMER0 for you to use for PWM... like you need for a servo. TIMER0 PWM can appear on PB0 (OC0A) and PB1 (OC0B).

both NeoSWSerial and Servo8Bit2 use the same interrupt table.

NeoSWSerial can use any of the pins PCINT0..PCINT5 for receiving (pins PB0..PB5). There is one vector for all of those PCINTs, PCINT0_vect.

The only vectors used by Servo8bit are TIM0_COMPA_vect and TIM1_COMPA_vect, as I expected. There is no reason for Servo8bit (an output library) to be handling Pin Change Interrupts (an input). There should not be any ISR conflict.

I'm trying to solve this by making them function in alternate program cycles (literally using a if(counter%2==0) condition), which sorta works, but the Serial interrupts still creep into the Servo8Bit2 PWM control which makes the servo jitter sometimes.

I'm going to try to disable serial while the servo is being controlled; Do you happen to have any suggestions on how I can separate them further?

I don't understand why you think they are "using the same interrupt table".

NeoSWSerial disables interrupts while it is transmitting. Is that what's interfering with Servo8bit?

I don't know what your servo requires, but if it could driven by the ATtiny85 PWM peripheral, without interrupts, that's the way to go. It looks like the Servo8bit library allows loooong PWM periods. Maybe you don't need that.

Look at the available clock frequencies for TIMER0 and Output Compare values, Section 11. analogWrite might be an option for this.

Hmm maybe I'm just mistaking the fact that NeoSWSerial disables interrupts, like you said, for using the same interrupt table.

My servo is a pretty standard RC microservo that uses the usual 20 ms PWM period. But no PWM timer prescaler will get me to that period (the closest is a prescaler of 1024 which will get me to 30 ms, but that's already a bit far from optimal), so I'm guessing I have to calculate how many times the timer counter overflows, and its remainder, after 20ms, and then send the 0.5-2.5ms pulse to tell the angle to the servo?

For example, I have my CPU clock at 8MHz, using a prescaler of 256 I get a total of 625 timer ticks which means: 2 overflows and a remainder of 113, in 20 ms. When that happened I could turn on a pin during 0.5 to 2.5 ms (depending on the angle), again by counting how many timer ticks happen in 0.5-2.5 ms. Is my reasoning correct?

I don't think I can just use analogWrite directly, because of its default PWM frequency being around 500Hz if I'm not mistaken, at least in the Arduino boards, which is ten times the frequency defined for a hobby servo and may cause overheating.

Anyway, what I did try was staggering the execution of Serial code and Servo code during several cycles of the program's main loop:

short counter=0;
while(1){
  
  counter++;
  if(counter%2==0){
     //Do Serial stuff
  }
  else{
    //Do Servo stuff
  }
}

which did work, but for some reason the serial code still interferes with the servo making it jitter a little bit. However, since the servo holds its position when it has no signal, maybe I can disable/enable the servo signal while the serial code does its thing, to avoid interference.

Thanks again!

My servo is a pretty standard RC microservo that uses the usual 20 ms PWM period. But no PWM timer prescaler will get me to that period

:frowning:

There is a "Phase Correct PWM mode" that would give a period of 15.3ms. Would that work?

If not, it looks like Servo8bit is doing exactly what you were suggesting.

maybe I can disable/enable the servo signal while the serial code does its thing,

By "thing", I assume you mean "transmit". That's the only time that NeoSWSerial disables interrupts. The receive process is very fast, and should not disturb the Servo library. Disabling the PWM signal while you transmit on NeoSWSerial should work, if the servo holds its position.

I've been working on this for the whole day and here's what I managed to get:

I completely wrote off the Servo8Bit library and hand crafted my own "PWM" signal using the timer counter to check when 20ms have passed and then send the control signal to the servo (by manually turning on/off a pin).

However, here's the catch: I only turn the servo when the current servo angle and the target angle (user input) differ, and while the servo rotates I completely block the serial communications. After the rotation is done I deactivate the servo signal and let the serial comms back on again.

mainLoop{
   //ADC measurement/conversion here
 
   //Serial communications here (also get newangle from serial)

   if(angle!=newangle){
       angle=newangle;
       for(i=0;i<50;i++)
           servoCall(); //this sends the control signal to the servo 50 times, for good measure
   }
}

The servoCall() function is basically this:

delay(20ms - control_Pulse_Length_ms); //The total signal has to repeat each 20ms so we need to subtract the time of the control pulse
set(Servo Pin, High);
delay(control_Pulse_Length_ms);
set(Servo Pin, Low);

So far this is working reasonably well, and serial data isn't lost. The communication lag when the servo rotates is not serious and does not interfere with data being transmitted, as far as I can tell.
The difference between this and Servo8Bit is that Servo8Bit uses interrupts every 20ms regardless of where the program is at, while I stop the program flow to send the servo signal several times before continuing and only doing that again after all the ADC and serial comms are complete.

The servo won't be subject to torque forces large enough to turn it while the signal is off, but I can always send a "maintenance" signal every 'n' seconds, just to be sure.