ATTiny85 108kHz, square wave, 50% duty cycle

Hey,

I want a 108kHz square wave output with a 50% duty cycle on an ATTiny85 with code.
I read very many posts, searched the internet, asked ChatGPT, .... but I still don't have any idea how that works, if the ATTiny85 is capable to do that, if I need an external crystal, etc. etc. - some source say "no problem", other sources say "isn't possible".

Maybe you can bring some light into the darkness! Thank you very much!

Hi @D3221,

using an ATtiny 85 Board this was the closest I could get to 108 kHz without intensive further trial and error ... :wink:

About 108,8 kHz with the following dead simple sketch:

constexpr byte outPin = PB0;
byte state = LOW;


void setup() {
    // put your setup code here, to run once:
    pinMode(outPin, OUTPUT);
}

void loop() {
  digitalWrite(outPin,state);
  delayMicroseconds(5);
 asm volatile("nop\n\t");
  state = !state;
}

Give it a try ... :wink:

First: Thank you very much for your effort.

Second: Could you explain to me how (or why) this spits out 108kHz without big config or something? I'm very confused :smiley:

Give me some minutes , please ... :wink:

Ok. Here a version of the code with comments

/*
  Program to generate (about) 108 kHz rectangle signal with ATTiny 85
  
  2023-08-21
  ec2021
  
  Forum: https://forum.arduino.cc/t/attiny85-108khz-square-wave-50-duty-cycle/1160510/4
  
*/


// outPin defines the output pin of the signal
constexpr byte outPin = PB0;
// state holds the actual state the output shall have (HIGH or LOW)
// and it starts with LOW
byte state = LOW;


void setup() {
// set outPin to OUTPUT mode
    pinMode(outPin, OUTPUT);
}

void loop() {
  // write state (which is toggling between LOW and HIGH) to the output pin
  digitalWrite(outPin,state);
  // keep the state for 5 microseconds and the time it takes
  // for one single "no operation" microcode of the ATtiny 85
  delayMicroseconds(5);
  asm volatile("nop\n\t");
  // invert state (if it was LOW -> HIGH, if HIGH -> LOW)
  state = !state;
}

So the code is only switching the state of the output pin to HIGH (or LOW), waits a given time and then inverts the signal.

A signal of 108 kHz has a periode of 1/108000 s = 9.259 microseconds. The time from digitalWrite() to digitalWrite() is therefore about 4.6 microseconds.

I played around with the delayMicroseconds() until I was close to it (5 µs -> 110 kHz) .Adding a variable (like int count;) and doing a count++; in loop() took too long. So I finally added a "no operation" to finetune the result.

So it was trial and error (which is not bad if done systematically).

Hope you have an oscilloscope at hand: It might be that you have to finetune also depending on the brand/oscillator your device has.

Not sure what clock frequency your ATtiny was running on, but a 16Mhz Arduino (Uno) has AFAIK a delayMicrosecond resolution (steps) of 4us. So no difference if you use 3us or 5us.
Leo..

@Wawa,
That is not the case.

To demonstrate that I've used Direct Port Manipulation and delayMicroseconds() to generate pulses of 2µs, 3µs, 4µs, 5µs, 6µs and 7µs.

int outPin = 12;

void setup() {
  pinMode(outPin, OUTPUT);
}

void loop() {
  PORTB = B00010000;
  delayMicroseconds(2);
  PORTB = B00000000;
  delayMicroseconds(2);
  PORTB = B00010000;
  delayMicroseconds(3);
  PORTB = B00000000;
  delayMicroseconds(2);
  PORTB = B00010000;
  delayMicroseconds(4);
  PORTB = B00000000;
  delayMicroseconds(2);
  PORTB = B00010000;
  delayMicroseconds(5);
  PORTB = B00000000;
  delayMicroseconds(2);
  PORTB = B00010000;
  delayMicroseconds(6);
  PORTB = B00000000;
  delayMicroseconds(2);
  PORTB = B00010000;
  delayMicroseconds(7);
  PORTB = B00000000;
  delayMicroseconds(2);
  delay(10);
}

and the result is:

Clearly the pulses are all different widths.

They are all shorter than expected- the last one (7µs) is only about 5.8µs wide, but they do go up in steps of 1µs.

HI @JohnLincoln,

thanks for your interesting test!

I went into this because I was curious what an ATtiny85 would be able to do just with some lines of code and simple digitalWrite() and was surprised ...

With my ATtiny board (which a colleague donated me years ago without documentation just to test it :wink: ) I checked the sketch from above with

  • 4 µs delay -> 138 kHz
  • 5 µs delay -> 109 kHz
  • 6 µs delay -> 90 kHz

(Frequencies rounded)

So it makes a difference (though I would not have expected it when I started).

There is an interesting thread on stackoverflow

https://stackoverflow.com/questions/62620994/attiny85-microsecond-timer-implemented-on-timer0-does-not-count-the-correct-tim

If you scroll down to the last post there is a sketch using Timer0 and claiming that (theoretically) a resolution of 1µs can be achieved.

[edit] I think my ATtiny85 is a 16 MHz Digispark board 8 MHz noname.

Not sure if you are still interested in a different solution but this one is based on this tutorial

https://www.gadgetronicx.com/attiny85-compare-match-tutorial-interrupts/

It uses the compare match function of the Timer0.

PB0 is toggled in an interrupt so that it is easy to change the sketch to PB1, PB2, PB3, ...

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

void comp_match() {
  DDRB |= (1 << PB0);  // set PB0 as output
  TCCR0A = 0x00;
  TCCR0B = 0x00;
  TCCR0B |= (1 << CS00);   // No prescaling
  TCCR0A |= (1 << WGM01);  // Toggle mode and compare match  mode
  OCR0A = 75;              // Compare value can be calculated as frequency = Clock / OCR0A
                           // A value of 75 leads to about 108,6 kHz at a clock rate of 8150000 Hz
  TCNT0 = 0;
  sei();  // Enabling global interrupt
  TIMSK |= (1 << OCIE0A);
}

ISR(TIMER0_COMPA_vect) {
  PORTB ^= (1 << PB0);  //Toggling port PB0
}

void setup() {
  comp_match();
};

void loop() {
}

/*
    
    The expected frequency can be calculated as freq = clock rate  / compare value
    In this specific case the clock rate is around 8150000 Hz. 
    (See frequency 81 kHz for compare value 100)
    Empirical data measured 2023-08-23 with a noname ATtiny 85 board
    compare value    Frequency
            1..37 -> 217.3 kHz  
               38 -> 211,5kHz  
               40 -> 201 kHz 
               50 -> 162 kHz
               60 -> 135 kHz 
               70 -> 116,3 kHz  
               75 -> 108,6 kHz   
               76 -> 107,3 kHz   
               80 -> 102 kHz  
              100 -> 81,8 kHz   
              200 -> 41,1 kHz 
              255 -> 32,3 kHz
*/

The functionality of the timer registers are explained in detail in the linked source.

The sketch compiles with Arduino IDE 2.1.1.

Again the results may/will differ depending on the specific brand/board/clock rate.

Thank you very much! In a few days my PCBs arrive and I can test it :slight_smile: sadly I don't have an oscilloscope. So I just have to test it blindfolded :smiley:

An oscilloscope is a nice device when working with electronics ... :wink:

If you do not have one there is still a chance not to work "blindfolded".

If you have an Arduino UNO, NANO or the like you can use them to count the frequency of your ATtiny85.

const byte interruptPin = 3;
volatile unsigned long count = 0;
unsigned long lastMeasurement = 0;
unsigned long value;

void setup() {
  Serial.begin(115200);
  Serial.println("Start ...");
  pinMode(interruptPin, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(interruptPin), doCount, RISING);
  lastMeasurement = millis();
}

void loop() {
  if (millis()-lastMeasurement >= 1000){
    lastMeasurement = millis();
    noInterrupts();
    value = count;
    count = 0;
    interrupts();
    Serial.println(value);
  }
}

void doCount() {
  count++;  
}

Output when

  • Pin 3 (UNO) connected to PB0 (ATtiny)
  • GND (UNO) to GND (ATtiny)

looks like this

108003
108123
107968
108082
108087
107979
108060
107966
108084
107960

It's the frequency in Hz measured every second.

Feel free to have a look here

https://www.arduino.cc/reference/en/language/functions/external-interrupts/attachinterrupt/

for different boards.

My god. What kind of awesome dude you are! Thank you! I'll definitely try this the days! You can't imagine how far you push my project with all of this!

Sadly my output is just:

52980
53045
53023
52993
53068
52987
52998
52956
53047
52963
52988
52974
52896
52971
52920
52991
52959
52992

I just uploaded the code from #8 on my ATTiny via Arduino UNO as ISP (also burned 8MHz internal clock bootloader on my ATTiny85 before).

Any ideas why on my watch it's not 108kHz?

P.S.: With #2 I just get ~88285

The timing depends on the clock rate of your board/controller...

In the sketch from post #8 you can influence the timing by changing the value of OCR0A:

OCR0A = 75;              // Compare value can be calculated as frequency = Clock / OCR0A
                           // A value of 75 leads to about 108,6 kHz at a clock rate of 8150000 Hz
 

With the results you got try 37 or 38 ...

Good luck!

P.S.: In the code from post #2 you can play with

delayMicroseconds(5);

and the line

asm volatile("nop\n\t");

E.g. set the delay to 2 or 3 micros and measure the results. If it is close but still too quick copy and paste the "asm" line. It adds a "No Operation" assembler command. Each "nop" delays the controller a little bit.

Removing the asm line speeds up again.

107.7 kHz with

void loop() {
  digitalWrite(outPin,state);
  delayMicroseconds(4);  
  asm volatile("nop\n\t");
  state = !state;
}

should be unbeatable to get near 108 kHz huh? :slight_smile: But I guess it shouldn't make a huge difference the - the .3?

P.S.: Did a little test line:

delay == 1ms + 1 asm Line >> 148 kHz
delay == 1ms + 2 asm Line >> 151 kHz

delay == 2ms + 1 asm Line >> 148 kHz
delay == 2ms + 2 asm Line >> 150 kHz

delay == 3ms + 1 asm Line >>
delay == 3ms + 2 asm Line >>
delay == 3ms + 3 asm Line >> 128 kHz

delay == 4ms + 0 asm Line >> 110 kHz
!!!! delay == 4ms + 1 asm Line >> 107.7 kHz !!!!
delay == 4ms + 2 asm Line >> 104 kHz

It finally depends on what you are doing with the 108 kHz. Signal... :wink:

I think it is quite close if we consider how we create it.

What happens if you comment the asm line out? It will become quicker, but how much?

Another try can be to go to 3 micros delay and add further lines with the nop command until you come close to your target. That's "empirical engineering" also called trial and error...

Sorry, just now carefully read your well documented tests ... However you could add lots of nop lines to come closer to 108. No guarantee.

(Take care with ms = milliseconds, we are talking microseconds here!).

To reveal the mystery what this is for:

I want to build a custom PCB for a water atomizer. I need this to put my project on ONE PCB without smashing a chinese and mine in the housing :slight_smile:

You think the 107.7kHz should be enough for this?

Link is just an example.

And ofc you are right with the ms and µs :slight_smile:

No worries. That should be pretty good.

The spec on that page says

Resonant Frequency fr = 108 kHz +/- 3 kHz