Non-interrupt driven Software Serial

Hey!

I recently began a project that has to track sliding drawers, because the project looked simple i decided to go with the ATtiny85.

I'm tracking the drawers position using a magnetic encoder and its accuracy is a MUST.In my head I sketched that i will be able to communicate with the ATiny through serial communication (SoftwareSerial).

Since i'm using interrupts for my encoder, the software Serial Library won't work(it's also interrupt-driven) and turns out the Attiny only has one Interrupt vector so it can only have one ISR.....
(the IDE refuses to compile my code)

Keep in my mind i'm willing to sacrifice communication reliability(if the drawer moves while its sending bit etc.) for accurate tracking.

So, is there any way i can find non interrupt driven library for the Attiny? what are the possible solutions to this?

Thanks in advance!
I'm writing this at 3am so please forgive any mistakes i've made!

Here's a library for using the USI as a serial port. Perhaps you can change the ISR(PCINT0) to a regular function (like void checkUSI()), then call it from your encoder ISR and/or loop. Of course, you'll have to extern it in your ISR file...

Cheers,
/dev

For the record, included Reset the ATtiny85 has 15 interrupt vectors.

I don't see how you can send or receive serial data without using a Timer interrupt to manage the timing because serial data is all about timing.

I have written some serial code that does not use a Timer interrupt but it would be completely unsuitable for your application because it completely blocks the Attiny and allows no interrupts while it is sending a byte. It must do that so that the timing loop operates properly. I can't remember why I did not use a Timer - maybe it was to make the code portable.

If you are sending data to or receiving data from another Arduino you could use clock and data lines so that the process is completely independent of time.

What is the maximum number of outputs per second that your magnetic encoder will produce in your application?

...R

I don't see how you can send or receive serial data without using a Timer interrupt to manage the timing because serial data is all about timing.

The USI has its own timer counter. Its overflow interrupt (vector 15 in Jiggy-Ninja's table) is used to send or receive data (no simultaneous RX and TX).

With a polling approach, you could constantly compare a counter value to sample the bits, instead of interrupting on Pin Change or Timer Compare. Definitely not as reliable, but the OP is willing to accept that.

I think he was stymied by two things that need the PCINT0 ISR: the encoder and SoftwareSerial. Because there is only one Pin Change Interrupt, he wanted to try a polled version of a serial library. Merging those two ISRs is also a possibility. Using the USI would be the most efficient, but it's only available on pins 0 and 1.

Cheers,
/dev

/dev:
The USI has its own timer counter.

I probably should just have said "I don't see how you can send or receive serial data without using an interrupt to manage the timing"

The OP seems anxious not to use any interrupts for his serial data.

...R

If 9600 baud is OK I'd have though you could write a polling serial driver - or is the point the ATtiny
doesn't maintain a clock without this interrupt at all?

Robin2:
I probably should just have said "I don't see how you can send or receive serial data without using an interrupt to manage the timing"

The OP seems anxious not to use any interrupts for his serial data.

...R

Not anxious I would say. It looks like what happened is that the OP is using the pin change interrupt to track the magnetic encoder, which is a perfectly reasonable use of that feature. There is only one pin change interrupt to service all the PCINT pins on the ATtiny85, but it wouldn't matter even if there was more.

SoftwareSerial, unfortunately, hogs ALL the PCINT vectors for itself:

#if defined(PCINT0_vect)
ISR(PCINT0_vect)
{
  SoftwareSerial::handle_interrupt();
}
#endif

#if defined(PCINT1_vect)
ISR(PCINT1_vect, ISR_ALIASOF(PCINT0_vect));
#endif

#if defined(PCINT2_vect)
ISR(PCINT2_vect, ISR_ALIASOF(PCINT0_vect));
#endif

#if defined(PCINT3_vect)
ISR(PCINT3_vect, ISR_ALIASOF(PCINT0_vect));
#endif

Adding SoftwareSerial to the project caused there to be two ISRs declared for PCINT0, which is a no-no and the compiler complains.

OP apparently didn't know about the USI module, so thought the only option is to implement Serial comms completely with software bit-banging.

A thought - with more than one wire there are protocols that can send data fully asynchrously by only using
transistions, so a transition on one wire for a zero, on the other for a one, timing becomes unnecessary so
long as the reading end isn't too slow.

We need to hear from the OP.

...R

Hi,

Another thought. IF these are the kind of drawers that humans pull in and out at speeds measured in tenths of a second, polling the sensors should be plenty responsive enough, eliminating the need for those pesky interrupts.

Best,
Michael

Hey!

Sorry for not replying and keeping you hanging :confused:

Jiggy-Ninja:
For the record, included Reset the ATtiny85 has 15 interrupt vectors.

I meant PCINT vector but thanks for pointing that out!

Robin2:
What is the maximum number of outputs per second that your magnetic encoder will produce in your application?

...R

According to the datasheet its max output can reach upto 200,000/s (though I think I miscalculated..)

@/dev thanks for the suggestion, I'll try it out in a bit and keep you guys updated(that is if i was able to get it working :sweat_smile:)

MarkT:
If 9600 baud is OK I'd have though you could write a polling serial driver - or is the point the ATtiny
doesn't maintain a clock without this interrupt at all?

I prefer it doesnt use interrupts because i dont want to block my encoder reading.(hence Non-interrupt driven SS). Running at 9600 isnt big of an issue,and polling seems like the way to go (isnt that what the suggested library does?)

MarkT:
A thought - with more than one wire there are protocols that can send data fully asynchrously by only using
transistions, so a transition on one wire for a zero, on the other for a one, timing becomes unnecessary so
long as the reading end isn't too slow.

I only have two wires for communication so that sounds good, you mind sharing name of such protocols (or libraries :p)

mjward:
Hi,

Another thought. IF these are the kind of drawers that humans pull in and out at speeds measured in tenths of a second, polling the sensors should be plenty responsive enough, eliminating the need for those pesky interrupts.

Best,
Michael

Well the person I'm doing this for didnt tell me exactly what he needs it for (though i'm almost sure its humans) but to stay on the safe side i want to make sure its ideal...

Btw this is the magnetic encoder im using : AS5306

Now i didn't find tutorials/guides using this specific encoder but i did find ppl using other types and apparently they can use on one external interrupt pin, but it didn't make sense to me. Will i lose functionality if i use that method? what are it down sides?
It got me thinking that since INT0 has a higher priority than PCINT0 wouldnt it simply prioritize my encoder over communication?

Thanks in advance!
Sorry for long post :confused: here's a cookie gives you cookie

mero55:
According to the datasheet its max output can reach upto 200,000/s (though I think I miscalculated..)

It certainly seems like you misunderstood.

They weren't asking about the maximum rate in the datasheet. In your project, with your hardware, what is the maximum frequency the encoder would change at if the drawer is pulled out at the fastest expected speed?

Well the person I'm doing this for didnt tell me exactly what he needs it for (though i'm almost sure its humans) but to stay on the safe side i want to make sure its ideal...

You could try asking. You never know.

It got me thinking that since INT0 has a higher priority than PCINT0 wouldnt it simply prioritize my encoder over communication?

That's not how priority works. All it means is that if there are two interrupts that need to be serviced at the same time, the higher priority interrupt will be serviced first. Interrupts are disabled by default in an ISR, so if an interrupt happens while you're in another ISR it will need to wait until the current one is finished. You can enable interrupts in an ISR, but then any interrupt, even lower priority ones, can preempt the current ISR.

Other architectures do it differently. I know that PIC can have certain interrupts assigned high priority in software. I high priority interrupt can preempt a low priority one, but a low priority can't preempt a high priority.

Hey!!

I'm really getting frustrated....

@jiggy-Ninja I asked the guy and waiting for his response...
Thanks for the clarification as well :slight_smile:

I tried changing PCINT to regular function and poll that in my loop but it still didnt work (apparently not fast enough). tried using Timer Interrupt but couldnt know how to do it... any pointers??

Thanks in advance!!

Post code?

Hey!!
I'm following the code from this blog.

Here's the original code - i have several variations....

#include "avr/interrupt.h"; 
#include <SoftwareSerial.h>

volatile int value = 0;
volatile int lastEncoded = 0;
String ID = "A1"; 
String content = "";
char character;

SoftwareSerial mySerial(1,2);

void setup()
{
 
  // set pins 3 and 4 to input
  // and enable pullup resisters
  pinMode(3, INPUT);
  pinMode(4, INPUT);
  digitalWrite(3, HIGH);
  digitalWrite(4, HIGH);
 
  GIMSK = 0b00100000;       // Enable pin change interrupts
  PCMSK = 0b00011000;       // Enable pin change interrupt for PB3 and PB4
  sei();                    // Turn on interrupts
}

 
void loop()
  while(mySerial.available()) {
      character = mySerial.read();
      content.concat(character);
      
  if (content == ID) {
  mySerial.println(encoderValue);
}
      }

content = "";
character = "";
}


ISR(PCINT0_vect)
{
  int MSB = digitalRead(3); //MSB = most significant bit
  int LSB = digitalRead(4); //LSB = least significant bit
 
  int encoded = (MSB << 1) |LSB; //converting the 2 pin value to single number
  int sum  = (lastEncoded << 2) | encoded; //adding it to the previous encoded value
 
  if(sum == 0b1101 || sum == 0b0100 || sum == 0b0010 || sum == 0b1011)
    value++;
  if(sum == 0b1110 || sum == 0b0111 || sum == 0b0001 || sum == 0b1000)
    value--;
 
  lastEncoded = encoded; //store this value for next time
 

}

sorry if it isnt the best structure :confused:

So the theory is: I hv several drawers connected together on the same 4 lines (Vcc,GND ,Tx, Rx)
I have a MCU (Arduino Uno) polling through each drawer.So it sends an ID of each drawer and waits for the response containing the information. Then it sends that to my computer ....

Hmm... do u think changing from PCINT to INT and setting it to Falling will work?

Thanks in advance!!

You need to explain this much more clearly.

mero55:
So the theory is: I hv several drawers connected together on the same 4 lines (Vcc,GND ,Tx, Rx)
I have a MCU (Arduino Uno) polling through each drawer. So it sends an ID of each drawer and waits for the response containing the information.

Do you mean that you have a separate Arduino for each drawer?

What, exactly, is connected to what when you say "connected together on the same 4 lines (Vcc,GND ,Tx, Rx)"

Then it sends that to my computer ....

What sends it to your computer?

The code in Reply #15 seems to be able to get data from one drawer but it does not try to send that data anywhere.

And it seems to expect data (an ID?) from something but it seems to have no means to relate the ID to the drawer. And if the ID matches it sends data from a variable that has not been defined.

I think it would be a big help if you describe what you want to achieve rather than how you think it should be implemented.

Separately from all of that it is not a good idea to use the String (capital S) class as it can cause memory corruption in the small memory on an Arduino. Just use cstrings - char arrays terminated with 0.

Also, have a look at the examples in Serial Input Basics - simple reliable ways to receive data.

...R

Hey!

Robin2:
You need to explain this much more clearly.
Do you mean that you have a separate Arduino for each drawer?

No, I have an Attiny for each drawer along with an encoder.
The arduino is there to collect data from several drawers with the same setup (ATtiny85 + encoder).

Robin2:
What, exactly, is connected to what when you say "connected together on the same 4 lines (Vcc,GND ,Tx, Rx)"
What sends it to your computer?

The code in Reply #15 seems to be able to get data from one drawer but it does not try to send that data anywhere.

And it seems to expect data (an ID?) from something but it seems to have no means to relate the ID to the drawer. And if the ID matches it sends data from a variable that has not been defined.

I think it would be a big help if you describe what you want to achieve rather than how you think it should be implemented.

So the way i hv the setup is that all the ATtinys will share the same power lines (like a breadboard) and they will also share the same USB-to-Serial adapter. I mentioned the arduino because I was thinking it could replace my USB-to-Serial adapter.

The Attinys will be constantly listening for messages and when it receives a message with its ID (A1 in this example) it will reply with the encoder value. Each Attiny will have a unique ID.

Robin2:
a variable that has not been defined.

The var Encodervalue is a typo(sorry) and should be replaced with 'value' instead (it happens when i'm trying to merge different sketches or improving one)

Robin2:
I think it would be a big help if you describe what you want to achieve rather than how you think it should be implemented.

I simply want to track how far all my drawers are open in a fast and yet simple way, with the fastest update rate possible.

Robin2:
Separately from all of that it is not a good idea to use the String (capital S) class as it can cause memory corruption in the small memory on an Arduino. Just use cstrings - char arrays terminated with 0.

Also, have a look at the examples in Serial Input Basics - simple reliable ways to receive data.

...R

I'll sure have a look , and thank you for helping improve my code.

Thanks in advance!

Now that you have mentioned the Attinys the overall picture is somewhat clearer. I think of them as Arduino's also because I program them with the Arduino IDE.

If you want to send serial data from several Attinys to one Uno (a Mega or Micro would make life easier because they have extra hardware serial ports) you cannot connect the TX's from the Attinys together. The Serial system holds the TX line high when it is idle and several HIGH lines would just confuse each other.

You can connect multiple TX lines if the RX pin on the Uno is held high by a resistor (4k7 should be OK) and each TX line should connect to the RX pin through a diode arranged so that the HIGH on the TX cannot get through. That will allow the active TX line to pull the RX pin LOW as needed and the resistor will provide the HIGHs.

You will also need some system to ensure that two Attinys do not talk at the same time. That could be achieved by using another of the Uno's other I/O pins (one for each Attiny) to signal when the Attiny is allowed to send.

If you are using an Uno use SoftwareSerial to create an extra serial port for receiving data from the Attinys. Note that it is not practical to create multiple copies of SoftwareSerial.

I recommend you design the serial communication to work like the 3rd example in Serial Input Basics

...R

Hey!

Thanks for the quick reply and sorry for my prior vagueness...

Robin2:
You can connect multiple TX lines if the RX pin on the Uno is held high by a resistor (4k7 should be OK) and each TX line should connect to the RX pin through a diode arranged so that the HIGH on the TX cannot get through. That will allow the active TX line to pull the RX pin LOW as needed and the resistor will provide the HIGHs.

This looked simpler in my head....
Wouldnt the diode prevent it from getting the high through when it has to, or is it taken care of with the resistor you added? and would this affect the other devices in an unwanted manner?(damage them)
Note: they will not be transmitting at the same time.

Can you please elaborate or send me in the right direction please.

Robin2:
You will also need some system to ensure that two Attinys do not talk at the same time. That could be achieved by using another of the Uno's other I/O pins (one for each Attiny) to signal when the Attiny is allowed to send.

Thats taken care of, they are all connected to a common Tx pin that tells each ATiny when to 'talk'.

Robin2:
I recommend you design the serial communication to work like the 3rd example in Serial Input Basics

Done. Thank you for the reference.

The more I think about my sketch/design the worse it looks .....

Thanks in advance!