Attiny85 SoftwareSerial and Pin Change Interrupt Not Working

Hello,

I’ve been having issues programming an Attiny85, on a breadboard using an Arduino Uno as ISP. This is my first attempt at programming an Attiny.

Basically, I want the Attiny85 to count the elapsed time between two pin change rising events of a square wave (a crankshaft sensor that gives out a fast square wave with intervals between rising edges of 1400 to 200 microseconds).

The Attiny is connected to the signal using an optocoupler, and the optocoupler is connected to pin PB1. The optocoupler is working, I’ve connected a scope to it at the Attiny’s intended PB1 input pin, and it looks fine.

And I would like to see what the Attiny is doing, by having a calculated average of the rising pin change intervals printed out, for now, via serial monitor.

RX and TX are connected to pins 3 and 4 on the Attiny, as in my code.

So far, neither of these two things is working. Neither does the Attiny seem to bother wanting to execute the pin change ISR, nor does it give out anything on the serial monitor.

All I get when I call up the Serial Monitor is one character, a “ÿ”, and only once. I’ve made sure that the baud rate is the same as in my code, so that can’t be the problem. I’ve connected a 220 ohm resistor between the Attiny’s designated TX pin no.4 and the Arduino board’s TX pin. The Attiny’s speed is set to 8 MHz internal.

Sketches upload fine though with the Arduino IDE, with no error messages.

As far as I understand, I can’t just use the external interrupt on pin 2 and listen for RISING edges, because that pin is needed to program the Attiny with my Arduino Uno, right?

Here’s my code:

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


#define crankshaft PB1
#define led 0



// Defining mySerial Rx and Tx Pins

const int Rx = 3;
const int Tx = 4;

SoftwareSerial mySerial(Rx, Tx);


// Crankshaft Interval Calculation Variables

volatile unsigned long crankRisingTimestamp = micros();
volatile unsigned long crankTimestampCurrent = micros();
int crankTimestampCounter;

volatile unsigned long crank_cumul;
volatile unsigned long crankAverage;

volatile unsigned long microsCrank;

volatile unsigned long microsDifference;
volatile unsigned long microsNow;
volatile unsigned long microsStartCount;


void setup() {

  pinMode(crankshaft, INPUT);
  pinMode(led, OUTPUT);

  digitalWrite(led, LOW);
  digitalWrite(crankshaft, LOW);

  pinMode(Rx, INPUT);
  pinMode(Tx, OUTPUT);

  mySerial.begin(9600);


  // Setting Interrupt Registers

  GIMSK = 0b00100000;    // activating pin change interrupts
  PCMSK = 0b00000010;    // activating interrupt on pin PB1
  sei();                 // Enable Interrupts


}

void loop() {


// Calculating an arithmetic average of all crankshaft sensor interval readings during every 250 ms


  microsDifference = 0;
  crankTimestampCounter = 0;
  crank_cumul = 0;
  crankAverage = 0;

  microsStartCount = micros();

  while (microsDifference <= 250000) {

    microsNow = micros();

    microsDifference = microsNow - microsStartCount;


    if (crankRisingTimestamp != crankTimestampCurrent) {


      microsCrank = crankRisingTimestamp - crankTimestampCurrent;

      crank_cumul += microsCrank;

      crankTimestampCounter += 1;

      crankTimestampCurrent = crankRisingTimestamp;


    }

  }

  crankAverage = crank_cumul / crankTimestampCounter;

  mySerial.println(crankAverage);


}


ISR(PCINT1_vect)
{
  if (digitalRead(crankshaft))  crankRisingTimestamp = micros();
  mySerial.println("ISR OK");

}

What have I been doing wrong? :confused:

carguy: This is my first attempt at programming an Attiny.

Have you been able to verify that the ATtiny85 is actually being programmed?

tmd3: Have you been able to verify that the ATtiny85 is actually being programmed?

Yes, I can burn a bootloader onto the Attiny and I can make an LED blink.

Have you tried a sketch with JUST software serial sending "Hello World" or similar?

I remember once playing with SoftwareSerial on the ATtiny85...I think I had a similar issue and changing to 1Mhz fixed the issue...tried that?

Johnny010: Have you tried a sketch with JUST software serial sending "Hello World" or similar?

Yes, I just uploaded this simple sketch:

#include "SoftwareSerial.h"
 const int Rx =3;
 const int Tx = 4;
 SoftwareSerial mySerial(Rx,Tx);

void setup()
{
 pinMode(Rx,INPUT);
 pinMode(Tx,OUTPUT);
 mySerial.begin(9600);
}
void loop()
{

while(1){
 mySerial.print("hello");

 delay( 200 );
}
}

maybe this helps identify the problem: the only way I can get the Attiny to return that "hello", and only once, is by resetting the Arduino Uno which acts as the ISP. When I press the reset button, it gives out the "hello" one time.

Please tell us how the various parts are connected. Right now, I'm more concerned with the path that the serial data takes than with the input sensors.

ok here's how I've got it wired up:

Arduino - Attiny

Pin 10 - Reset Pin 11 - PB 0 Pin 12 - PB 1 Pin 13 - PB2 5V - Vcc GND - GND RX - PB3 TX - PB4

Also, I've got an ILD74 optocoupler connected to PB1, for the pin change interrupt. The collector gets 5V straight from the Arduino, and the emitter is connected to GND with a 10K pulldown and to the Attiny's PB1 pin.

I originally had two 150 Ohm resistors inline between PB4 and TX, mainly because I couldn't find a 220 Ohm resistor that is mentioned in some tutorials. But I've now also tried a 220 Ohm resistor, and it makes no difference.

Hi,

This jumps out at me:

the emitter is connected to GND with a 10K pulldown

Are you sure that shouldn't be a pull up?

--Michael

edit: sorry, think I just had a brainfart...

mjward: Hi,

This jumps out at me:

Are you sure that shouldn't be a pull up?

--Michael

edit: sorry, think I just had a brainfart...

I've got the exact same setup on a standalone Atmega328P-PU on another breadboard, also to trip an (external) interrupt, and it's working perfectly.

Yeah, read your post wrong. Looked for a delusional second like you meant connected to ground AND pulldown. My bad.

--Michael

OK, reading the connections back to you, with pin numbers where they’re not defined already, this is what I think you have:

       Arduino - Attiny

        Pin 10 - Reset [pin 1]
        Pin 11 - PB0   [pin 5] 
        Pin 12 - PB1   [pin 6]
        Pin 13 - PB2   [pin 7]
            5V - Vcc   [pin 8]
           GND - GND   [pin 4]
[pin 1]     RX - PB3   [pin 2]
[pin 0]     TX - PB4   [pin 3] <there's a 220 ohm resistor between these two>

ATtiny85 pins are shown as the IC’s pin numbers, while Arduino pins are shown as they’re labeled on an Uno board.

You also have the transistor side of an optocoupler connected with VCC on the collector and a 10K to GND on the emitter, and the emitter connected directly to the ATtiny85’s PB1, and thus also to the Arduino’s pin 12.

Have I got that right?

Here are a couple more questions:

  1. What’s running on the Arduino?
  2. What’s the purpose of the 220R between ATtiny85 PB4 and Arduino TX, pin 1?
  3. How are you reading the output from the ATtiny?

I suspect that you’re looking at the serial monitor, via the Arduino, and hoping that you’re getting the Tx message from the ATtiny85. The 220R between PB4 and Arduino Tx makes me think that Serial is enabled on the Arduino, that the 220R is to protect the ATtiny85 and Arduino transmit pins from damage when they both drive their respective Tx pins to different levels.

I think that the Arduino’s Tx pin dominates the status of the transmit data to the serial monitor. When you get a bit of a transmission from the ATtiny85, I think that it’s because the Arduino is running its bootloader, waiting for data, and its Tx pin isn’t yet an output, so the ATtiny85’s signal gets through unmolested. Then, when the Arduino starts in earnest, it executes Serial.begin(), and turns on the Tx output. That prevents anything from the ATtiny from getting through.

That’s mostly conjecture, though, and I haven’t looked at the code to see if that’s the way Tx behaves while the bootloader waits for input. We’ll await answers to those three questions above before delving further.

tmd3: Have I got that right?

Yes, you have.

tmd3: 1) What's running on the Arduino?

The standard "ArduinoISP" sketch.

tmd3: 2) What's the purpose of the 220R between ATtiny85 PB4 and Arduino TX, pin 1?

To tell you the truth, I simply did this because one of the tutorials I found online said to do so. shrug

tmd3: 3) How are you reading the output from the ATtiny?

By opening the serial monitor in the Arduino IDE.

OK. The first thing that the Arduino ISP sketch does is initiate Serial. I suspect that Tx becomes an output for the Arduino at that point, no matter what else the sketch might try to do. Once the Arduino starts driving Tx, the serial monitor will never hear from the ATtiny again.

Here's what I recommend: try loading the repeating "Hello, world" sketch in the ATtiny85, and then loading an empty sketch into the Arduino. The Arduino won't try to initiate Serial, and it will leave Tx in its default state, as an input. If my conjecture is correct, that should give the ATtiny85 control of the Tx line, and you should see "Hello, world" repetitively.

carguy: To tell you the truth, I simply did this because one of the tutorials I found online said to do so. shrug

Can you provide a link to that tutorial?

tmd3: Here's what I recommend: try loading the repeating "Hello, world" sketch in the ATtiny85, and then loading an empty sketch into the Arduino. The Arduino won't try to initiate Serial, and it will leave Tx in its default state, as an input. If my conjecture is correct, that should give the ATtiny85 control of the Tx line, and you should see "Hello, world" repetitively.

It works.

I tried something slightly different first, and it did the trick. Although you gave me the basic idea.

I removed the Atmega from the Arduino Uno board once the upload to the Attiny was complete. And now when I call up the serial monitor, the Attiny comes back nicely with a looped "Hello".

The same result when I upload a blank sketch to the Atmega.

I did have to remove the 10 uF capacitor between Reset and GND though on the Uno board. But the Attiny doesn't seem to mind. It did not go up in smoke, and the Universe didn't turn inward and disappear because I upload sketches to the Attiny with that capacitor missing.

tmd3: Can you provide a link to that tutorial?

I'm going to have to get back to you on that, sadly, I can't find it right now.

Either way, the Arduino doesn't take command of the serial port's transmit line, leaving it available for use by the ATtiny85.

Now that the ATtiny85 can talk to the serial monitor, how does your original sketch perform?

tmd3: Now that the ATtiny85 can talk to the serial monitor, how does your original sketch perform?

It is indeed doing better.

Still no luck though with the ISR routine to watch for rising square waves via the optocoupler.

When, after reading up a bit on interrupts, I tried changing "ISR(PCINT1_vect)" at the bottom of my sketch to "ISR(PCINT0_vect)", the IDE returned this:

libraries\SoftwareSerial\SoftwareSerial.cpp.o (symbol from plugin): In function `SoftwareSerial::read()':

(.text+0x0): multiple definition of `__vector_2'

sketch\attiny-rpm.ino.cpp.o (symbol from plugin):(.text+0x0): first defined here

collect2.exe: error: ld returned 1 exit status

Not sure what to make of it.

carguy: Still no luck though with the ISR routine to watch for rising square waves via the optocoupler.

It looks like you've noticed that there's no PCINT1 on the ATtiny85, so I won't belabor it.

As far as I understand, I can't just use the external interrupt on pin 2 ...

I'll recommend that you rethink that assessment. What's the purpose of the connection to PB2?

tmd3: I'll recommend that you rethink that assessment. What's the purpose of the connection to PB2?

Well apparently, PB2 is needed to program the Attiny via the Arduino Uno board, isn't it?

Sorry, I'm not really good with Q&A on the Attiny yet... my knowledge of all things Attiny is really quite limited so far.

carguy: Well apparently, PB2 is needed to program the Attiny via the Arduino Uno board, isn't it?

So it would appear. Is that connection to the Arduino necessary for the ATtiny85 to run, once it's been programmed?

You were able to get the "Hello" program to work without the ArduinoISP sketch in the Arduino, and without the ATmega328 installed at all:

carguy: It works. I tried something slightly different first, and it did the trick. I removed the Atmega from the Arduino Uno board once the upload to the Attiny was complete. And now when I call up the serial monitor, the Attiny comes back nicely with a looped "Hello". The same result when I upload a blank sketch to the Atmega.

I'd presume that the Arduino, and all its connections to the ATtiny85, aren't necessary once the ATtiny85 has been programmed. The only thing you need is the connection to the Arduino's Tx pin, so that the serial monitor can hear from the ATtiny85.

The problem you describe is due to the fact that SoftwareSerial uses PCINT0. If you want to use it, too, you'll have to modify the library to add code to the ISR to decide which pin interrupted, and decide what to do in response. Sounds tricky.

An alternative would be to use PB2 as an interrupt pin, and avoid using PCINT0 at all. To do that, you'll have to program the ATtiny85 with all the connections to your Arduino, and then disconnect PB2 from the Arduino and connect it to the optocoupler. Annoying, maybe, but doable.

You may also have to change your sketch to avoid trying to print from an ISR. If SoftwareSerial uses interrupts to print, then it will eventually fail to do so from inside an ISR. I haven't pored over the SoftwareSerial library - just enough to see that it defines a PCINT0 ISR. When you define it, too, the compiler complains, and stops.

The purpose of asking questions, rather than directly answering yours, is to avoid stealing from you the intellectual thrill of figuring it out yourself.