Dead-Time PWM ATTINY85

Hi,

I am working on a solar panel power optimiser and i need to be able to drive 2 logic level mosfets with PWM from an Attiny85. Just wondering how I can program Arduino IDE to send 2 PWM signals from 2 seperate output pins so they are 180 degrees out of phase of each other.

Thanks.

Why separate pins ?
Use an N channel and P channel mostfet (or similar inverting configuration) on the same pin to achieve the same effect.

I am programming a pre-existing PCB so trying to do the phase shift in software.

Let's see the hardware and software design that you are already working with.

1 Like

What do you mean by 180* out of phase? Do you mean that one is high whilst the other low? If so you just need to digital write opposite values to the two pins, ie one high the other low and vice versa. There will be a small time skew between the signals. If this matters then an external hardware inverter would be better.

I am wanting one PWM output to be high, while the other is low, how major would skew time effect the output? The PWM outputs are on PBO & PB1 on the Attiny85.

I would guess the skew would be a few CPU cycles, for a standard UNO I think a cycle is 4 microseconds. How significant this is depends on just what the MOSFETs are doing. The problem could be that having both on at the same time might be dangerous to the product. If it matters then you could do slightly more complex logic, where you make sure that both outputs are low before turning one on - basically vary the sequence depending on the state. This might slightly slow things down. Could you say more about what the devices are switching?

If it has a "phase and frequency correct" PWM mode then use it. Set the control bits for the channels to normal and inverse output. Adding a gap between signal transitions is possible in this mode as well. This is how it works on an Uno (ATmega328...).

I am switching two logic level Mosfets which make up a syncronous Buck converter for stepdown dc to dc conversion

PB0 is the Inverted OC1A output. It is connected to the Dead Time Generator but I can't find in the datasheet if there is something you need to do to enable that function.

PB1 (MISO/DO/AIN1/OC0B/OC1A/PCINT1)
PB0 (MOSI/DI/SDA/AIN0/OC0A/not-OC1A/AREF/PCINT0)

You configure the Dead Time Generator with the prescale register (DTPS1) and the count register (DT1A).

https://www.hilltop-cottage.info/blogs/adam/avr-timer1-dead-time-generator-example-attiny85/

Good reference. Here is the code from there converted to Arduino Sketch form:

//set the top count give whole number percentage duty cycles
const unsigned char top = 99;
//40% duty cycle if top=99
const unsigned char compare = 39;
//prescale CLK/8, 8Mz clock and div8 prescale -> 1MHz tick -> appropx 10kHz output with top=99
const unsigned char prescaleTimer = (1 << CS12);
//prescale CLK/4.
const unsigned char prescaleDead = (1 << DTPS11); // div 8 = (1<<DTPS11) | (1<<DTPS10)
// with CLK/4 prescale and 8MHz clock the dead time is 0.5uS per LSB.
// Dead time is delay to rising edge of signal
const unsigned char deadHigh = 0x0F; //8uS dead time for OCR1B. Max 0x0F
const unsigned char deadLow = 0x08; //4uS dead time for /OCR1B

void setup()
{
  //set data direction for output compare A and B, incl complements
  DDRB = (1 << PB4) | (1 << PB3) | (1 << PB1) | (1 << PB0);

  //setup timer1 with PWM. Will be using both A and B compare outputs.
  // both compares will be the same but only B will have dead time applied
  OCR1A = compare;
  OCR1B = compare;
  TCCR1 = (1 << PWM1A) | (1 << COM1A0); //Compare A PWM mode with complement outputs
  GTCCR = (1 << PWM1B) | (1 << COM1B0); //Compare B PWM mode with complement outputs

  //PLLCSR is not set so the PLL will not be used (are using system clock directly - "synchonous mode")
  //OCR1C determines the "top" counter value if CTC1 in TCCR1 is set. Otherwise "top" is normal: 0xFF
  OCR1C = top;
  TCCR1 |= (1 << CTC1);
  TCCR1 |= prescaleTimer;

  //setup dead time for compare B. Note the prescaler is independent of timer1 prescaler (both receive the same clk feed)
  DTPS1 = prescaleDead;
  //DT1A is unset - output A has no dead time
  DT1B = (deadHigh << 4) | deadLow;
}

void loop() {}

What is the frequency of the PWM signal?

Fig-1 and data sheets indicate that ATtiny85 is capable to generate simultaneous and synchronous "non-inverted (OC0A)" and "inverted (OC0B)" PWM signals at Pin-5 and Pin-6 respectively.


Figure-1:

The PWM frequency for switching the Mosfets will be around 60Khz

Decide if you are going to use TC0 or TC1 of the ATtinyy85 for PWM generation. If using TC1, then you have the codes in post #12.

Hi, i have modified the code from post #12 to try and output PWM with dead time on PB0 & PB1, however i can only seem to get the PWM signal on PB0. Attached below is my code, just wondering how i can get PWM working on PB1.

 * Dead_Time_ATtiny85.cpp
 *
 * Created: 29/12/2012 13:13:20
 *  Author: Adam
 *
 * FUSES: CLKOUT should not be blown. 8MHz internal osc assumed
 */ 
 
/*
* ***Made available using the The MIT License (MIT)***
* Copyright (c) 2012, Adam Cooper
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
* associated documentation files (the "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
* following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or
* substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
* AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
* OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
* ************ end licence ***************
*/


#include <avr/io.h>

/*
* Configuration
*/
//set the top count give whole number percentage duty cycles
const unsigned char top = 99;
//40% duty cycle if top=99
const unsigned char compare = 59;
//prescale CLK/8, 8Mz clock and div8 prescale -> 1MHz tick -> appropx 10kHz output with top=99
const unsigned char prescaleTimer = (1<<CS12)|(1<<CS13)|(1<<CS11)|(1<<CS10);
//prescale CLK/4.
const unsigned char prescaleDead = (1<<DTPS11);// div 8 = (1<<DTPS11) | (1<<DTPS10)
// with CLK/4 prescale and 8MHz clock the dead time is 0.5uS per LSB.
//Dead time is delay to rising edge of signal
const unsigned char deadHigh = 0x0F; //8uS dead time for OCR1B. Max 0x0F
const unsigned char deadLow = 0x08; //4uS dead time for /OCR1B

int main(void)
{
  //set data direction for output compare A and B, incl complements
  DDRB = (1<<PB1) | (1<<PB0);
  
  //setup timer1 with PWM. Will be using both A and B compare outputs.
  // both compares will be the same but only B will have dead time applied
  OCR1A = compare;
  TCCR1 = (1<<PWM1A) | (1<<COM1A0); //Compare A PWM mode with complement outputs

  //PLLCSR is not set so the PLL will not be used (are using system clock directly - "synchonous mode")
  //OCR1C determines the "top" counter value if CTC1 in TCCR1 is set. Otherwise "top" is normal: 0xFF
  OCR1C = top;
  TCCR1 |= (1<<CTC1);
  TCCR1 |= prescaleTimer; 
  
  //setup dead time for compare B. Note the prescaler is independent of timer1 prescaler (both receive the same clk feed)
  DTPS1 = prescaleDead;
  //DT1A is unset - output A has no dead time
  DT1B = (deadHigh<<4) | deadLow;
  
    while(1)
    {
        //do nothing
    }
} 

I'm not doing exactly what you are, but this is the code I use for pb0 and pb1 complementary outputs. I'm using pwm and not dealing with the dead time generators.

Maybe it will help... it's probably how you set it up in the control registers... I use the common _BV macro ('bit value') that is in the .h files for the tiny85. You can ignore the WDT (Watch Dog Timer) I'm using.

#define _BV(bit) (1 << (bit)) // this macro is available in the Atmel supplied header files

void setup() {

  /*

     PB0 ^OC0B  pin 5  DDB0   PWM_NOT
     PB1  OC0B  pin 6  DDB1   PWM
     PB3  LED   pin 2  DDR3   LED

     PB4 is used for the pushbutton - input

  */

  DDRB |= _BV(DDB0) | _BV(DDB1) | _BV(DDB3);

  /*
     PB4  PB    pin 3 DDR4    PB

     Enable pullup for pushbutton input
  */

  PORTB |= _BV(PB);

  /*
       PWM1A - pulse width modulation mode A
       COMA0 - toggle the OC1A output line
       CS10  - divide by timer by 1
  */

  TCCR1 |= _BV(PWM1A) | _BV(COM1A0) | _BV(CS10);

  /*
     duty cycle is OCR1A/OCR1C
  */

  OCR1A = 85; // startup at 33% pwm
  OCR1C = 255;

  /*
     watchdog timer for switch debounce.

     WDIE enable WD interrupts

     WDP2 WDP1 WDP0
       0    0    0         16mS
       0    0    1         32mS
       0    1    0         64mS
       0    1    1         0.125 S
       1    0    0         0.25  S
       1    0    1         0.5   S
       1    1    0         1.0   S
       1    1    1         2.0   S

  */

  WDTCR |= _BV(WDIE) | _BV(WDP0);
}

It's always nice to use the built in names of things... your coding forces me to count bits... and for an easy mistake entry.

Would be more clear as DTPS1 = (1<<DTPS11) instead of assigning it to a constant then the register...

Is this doing what you think? I don't know what you're doing here by the way it's coded...

const unsigned char deadHigh = 0x0F; //8uS dead time for OCR1B. Max 0x0F
const unsigned char deadLow = 0x08; //4uS dead time for /OCR1B
.
.
.
DT1B = (deadHigh<<4) | deadLow;

Use the nomenclature in the documentation.... such as DT1BL2 or whatever...


There are reasons for this, mainly if you move to another Atmel micro, the locations and bit positions may change but the proper identifiers will make the code port properly. When you add 'computed', such as in your 'const' values, it introduces bugs into the ported code...

Setting these up can be very tedious and easy to forget... especially if it's not a daily thing.

Good luck

:smiley_cat:

Hi, Thanks i have managed to get the PB1 & PB0 inverted outputs with dead-time with the following code, from timer 1. The code in my previous post was not mine it was from:
AVR Timer1 Dead Time Generator Example (ATtiny85) – Adam @ Hilltop Cottage
I just was trying to use it as reference code.


void setup() {
DDRB = (1<<PB1) | (1<<PB0); //Set output for Pin PB0 & Pin PB1
  
OCR1A = 105; // Sets the compare value for OCR1, This value sets the duty cycle Eg. (105+1)/(205+1) = ~50% Duty cycle

TCCR1 = (1<<PWM1A)|(1<<COM1A0)|(1<<CS12)|(1<<CS13)|(1<<CS11)|(1<<CS10)|(1<<CTC1);
//Enables PWM on timer 1 & toggles the OC1A Output 
//& Resets Timer/Counter 1 in CPU CLK cycle after comapre match with OCR1A 
//& Sets the Clk prescaler to determine the frequency of PWM
  
OCR1C = 205; //Sets the top value for Duty Cycle Eg. (OCR1A+1)/(OR1C+1) =Duty Cycle
  
DTPS1 =  (1<<DTPS10); //Sets the dision factors for Dead Time prescaler
  
DT1A = (1<<DT1AH1) | (1<<DT1AL1); // DT1AH 0-3 Controls the value of Dead timer prescaler for DTAH OC1B in 4 bit binary
}

void loop() {
  
}

Looks much better... did my suggestions make sense?

Have fun, take care...

:smiley_cat:

1 Like

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.