Changing ATtiny85 PWM frequency

Hi all,

I have got the code below working on a ATtiny85 micro (Datasheet) . It outputs a PWM signal of 500Hz, 0-100% duty cycle using an analogue voltage (potentiometer) to vary the duty cycle. I need to alter the frequency to 2Khz instead of 500Hz. My programming knowledge is very basic and would appreciate if anybody could help me achieve this? Thanks!

//Constants:
const int ledPin = 0;  //pin 0 has PWM funtion on attiny85 pin 5
const int potPin = A1; //pin A1 to read analog input pin 7. A0-A5 are labeles on the UNO for analogue input.

//Variables:
int value; //save analog value

void setup(){
  //Input or output?
  pinMode(ledPin, OUTPUT); 
  pinMode(potPin, INPUT); //Optional 
}

void loop(){
  value = analogRead(potPin);          //Read and save analog value from potentiometer
  value = map(value, 0, 1023, 0, 255); //Map value 0-1023 to 0-255 (PWM)
  analogWrite(ledPin, value);          //Send PWM value to led
  delay(100);                          //Small delay
}

What is this for and how are you going to verify the results ?
I don't know of a ready made Arduino library solution. You'll probably have to directly manipulate the timer registers. Since you are using delay() you want to avoid using timer0 so you should look at timer1.

It's to test vehicle damper soft/hardness. 500Hz, 1Khz & 2Khz frequency is the requirement, 0-100% duty cycle.

I've found something similar to what I require (code below), except it runs at 25Khz but only goes down to around 2% duty cycle instead of zero.

Can anyone see a way of modifying it so I can get the 500Hz, 1Khz & 2Khz frequencies?

Any help would be most appreciated!

/*
 *                         ATtiny85
 *                      -------u-------
 *  RST - A0 - (D 5) --| 1 PB5   VCC 8 |-- +5V
 *                     |               |
 *        A3 - (D 3) --| 2 PB3   PB2 7 |-- (D 2) - A1  --> 10K Potentiometer
 *                     |               | 
 *        A2 - (D 4) --| 3 PB4   PB1 6 |-- (D 1) - PWM --> Fan Blue wire
 *                     |               |      
 *              Gnd ---| 4 GND   PB0 5 |-- (D 0) - PWM --> Disabled
 *                     -----------------
 */
 
// normal delay() won't work anymore because we are changing Timer1 behavior
// Adds delay_ms and delay_us functions
#include <util/delay.h>

// Clock at 8mHz
#define F_CPU 8000000  //F_CPU 8000000. This is used by delay.h library

const int PWMPin = 1;  // Only works with Pin 1(PB1)
const int PotPin = A1;

void setup()
{
  pinMode(PWMPin, OUTPUT);
  // Fast PWM Mode, Prescaler = /8
  // PWM on Pin 1(PB1), Pin 0(PB0) disabled
  // 8Mhz / 8 / (39 + 1) = 25Khz
  TCCR0A = _BV(COM0B1) | _BV(WGM01) | _BV(WGM00);
  TCCR0B = _BV(WGM02) | _BV(CS01);
  // Set TOP and initialize duty cycle to zero(0)
  OCR0A = 39;    // 39 TOP - DO NOT CHANGE, SETS PWM PULSE RATE
  OCR0B = 0;     // duty cycle for Pin 1(PB1) - generates 1 500nS pulse even when 0
}

void loop()
{
  int in, out;

  in = analogRead(PotPin);
  out = map(in, 0, 1023, 0, 39);//(in, 0, 1023, 0, 39)
  OCR0B = out;
  _delay_ms(200);
}

You have to find the right combination of prescaler and OCR0A which yields 2kHz (or your desired frequency) for "fast" PWM.

Try this: AVR Timer Calculator | Eleccelerator

Check first it works for the parameters for 25kHz that you have in the sample sketch.

Then, from the datasheet, work out how to change the prescaler using the timer registers
TCCR0A and/or TCCR0B

Edit
If you want 0% duty cycle, one way is to change the pinMode() to INPUT.

Have you a logic analyser or oscilloscope ?

Just trying to pop in the parameters into the calculator for my sample code to get 25Khz. Not sure what to enter for some of the fields?

System Clock Frequency (Hz):8,000,000
Timer Resolution:8 bit
Prescaler: clk/8
Total Timer Ticks:?
Overflow Count:?
Remainder Timer Ticks:?
Real Time (sec):?

Like you said, if I change the pinMode() to INPUT there will be zero output. I would ideally want 0% duty cycle when the potentiometer reached 0V and 100% when it reaches 5V.

I put in the required frequency and the calculator gave me the total timer ticks. I can get down to 4Khz and no lower, when I change the OCR0A. Code below.

Yes, I'm using a scope to check the signals.

System Clock Frequency (Hz): 8000000
Timer Resolution: 8 bit
Prescaler: (2) Clk/8
Total Timer Ticks: 250.6265664160401
Overflow Count: 0
Remainder Timer Ticks: 250.6265664160401
Real Time (sec): 0.0002506265664160401
New Freq (Hz): 3990

/*
 *                         ATtiny85
 *                      -------u-------
 *  RST - A0 - (D 5) --| 1 PB5   VCC 8 |-- +5V
 *                     |               |
 *        A3 - (D 3) --| 2 PB3   PB2 7 |-- (D 2) - A1  --> 10K Potentiometer
 *                     |               | 
 *        A2 - (D 4) --| 3 PB4   PB1 6 |-- (D 1) - PWM --> Fan Blue wire
 *                     |               |      
 *              Gnd ---| 4 GND   PB0 5 |-- (D 0) - PWM --> Disabled
 *                     -----------------
 */
 
// normal delay() won't work anymore because we are changing Timer1 behavior
// Adds delay_ms and delay_us functions
#include <util/delay.h>

// Clock at 8mHz
#define F_CPU 8000000  //F_CPU 8000000. This is used by delay.h library

const int PWMPin = 1;  // Only works with Pin 1(PB1)
const int PotPin = A1;

void setup()
{
  pinMode(PWMPin, OUTPUT);
  // Fast PWM Mode, Prescaler = /8
  // PWM on Pin 1(PB1), Pin 0(PB0) disabled
  // 8Mhz / 8 / (39 + 1) = 25Khz
  TCCR0A = _BV(COM0B1) | _BV(WGM01) | _BV(WGM00);
  TCCR0B = _BV(WGM02) | _BV(CS01);
  // Set TOP and initialize duty cycle to zero(0)
  OCR0A = 250;    // 39 TOP - DO NOT CHANGE, SETS PWM PULSE RATE
  OCR0B = 0;     // duty cycle for Pin 1(PB1) - generates 1 500nS pulse even when 0
}

void loop()
{
  int in, out;

  in = analogRead(PotPin);
  out = map(in, 0, 1023, 0, 255);//(in, 0, 1023, 0, 39)
  OCR0B = out;
  _delay_ms(200);
}

You have to look at a more comprehensive data sheet. Your link it to a summary only.

Try setting the pre-scaler to /64 to get closer to your target frequency range.

edit

You could try something like this to get the potentiometer to force a 0% duty cycle

in = analogRead(PotPin);
if (in < 10 ) in = 0 ;   // initially 10. experiment here
. . .

This should give you ~2 kHz

8 000 000 / 64 = 125 kHz
64 - pre-scaler
125 kHz / 63 ~ 2000 Hz
63 - OCR0A

	// use fast PWM mode
	TCCR0A = 1<<COM0B1 | 0<<COM0B0 | 1<<WGM01 | 1<<WGM00;
	TCCR0B = 1<<WGM02;

	// disable interrupts
	TIMSK = 0;
	
	// set TOP to generate ~2 kHz
	OCR0A = 63;

    // 50% duty cycle
    OCR0B = 31;
	
	// start timer at 125 kHz (8 MHz / 64)
	TCCR0B = 1<<WGM02 | 0<<CS02 | 1<<CS01 | 1<<CS00;

@hzrnbgy, I've tried the code and it works perfectly for the 500Hz, 1Khz & 2Khz that I require! Thanks

@6v6gt, I tried the:

  if(in<10) in=0; //initially 10

I think the issue is that the potentiometer is actually at 0V and my scope is showing I have a duty cycle of 0.5% still present. Even if I force 'in' to zero it results in no change. Is there something else I could try?

/*
 *                         ATtiny85
 *                      -------u-------
 *  RST - A0 - (D 5) --| 1 PB5   VCC 8 |-- +5V
 *                     |               |
 *        A3 - (D 3) --| 2 PB3   PB2 7 |-- (D 2) - A1  --> 10K Potentiometer
 *                     |               | 
 *        A2 - (D 4) --| 3 PB4   PB1 6 |-- (D 1) - PWM --> Fan Blue wire
 *                     |               |      
 *              Gnd ---| 4 GND   PB0 5 |-- (D 0) - PWM --> Disabled
 *                     -----------------
 */
 
// normal delay() won't work anymore because we are changing Timer1 behavior
// Adds delay_ms and delay_us functions
#include <util/delay.h>

// Clock at 8mHz
#define F_CPU 8000000  //F_CPU 8000000. This is used by delay.h library

const int PWMPin = 1;  // Only works with Pin 1(PB1)
const int PotPin = A1;

void setup()
{
  pinMode(PWMPin, OUTPUT);
TCCR0A = 1<<COM0B1 | 0<<COM0B0 | 1<<WGM01 | 1<<WGM00;
  TCCR0B = 1<<WGM02;

  // disable interrupts
  TIMSK = 0;
  
  // set TOP to generate ~2 kHz
  OCR0A = 63;// 63=2Khz, 127=1Khz, 500Khz=255

    // 50% duty cycle
    OCR0B = 31;//31
  
  // start timer at 125 kHz (8 MHz / 64)
  TCCR0B = 1<<WGM02 | 0<<CS02 | 1<<CS01 | 1<<CS00;

}

void loop()
{
  int in, out;

  in = analogRead(PotPin);
  if(in<10) in=0; //initially 10
  out = map(in, 0, 1023, 0, 255);//(in, 0, 1023, 0, 39)
  OCR0B = out;
  _delay_ms(200);
}

Thanks again!

Also, on the potentiometer I have 0.00V, this represents ~1.6% duty cycle.
~1.25V represents 100% duty cycle. Is there a way I can use the entire potentiometer range (0-5V) to represent the 0-100% duty cycle?

Your help is most appreciated!

Since the TOP value is OCR0A = 63, you can have the duty cycle by limiting OCR0B from 0 to 63

I think this should be

out = map(in, 0, 1023, **0, 63**);
OCR0B = out;

Try this:

in = analogRead(PotPin);
out = map(in, 0, 1023, 0, 255);//(in, 0, 1023, 0, 39)
if ( out < 2 )  pinMode(PWMPin, INPUT);   // was 2. Experiment here.
else pinMode(PWMPin, OUTPUT);

@hzrnbgy ,@6v6gt,

Both of your suggestions have worked perfectly, thanks you so much. I may come back to pick your brains again so I can get a deeper understanding of how the code functions.