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.
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...).
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.
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() {}
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.
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.
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() {
}