PWM on ATTiny841: Paging Dr. Azzy!

So I think I have tracked down the problem, but I don't know what the root cause is. The fact that Timer 2 was outputting on TOCC4 but not TOCC3 as assigned, I assigned OC1A to TOCC7, OC1B to TOCC6, and OC2A to TOCC5. This all works wonderfully, with the correct duty cycles and everything. Unfortunately, this routing doesn't work for my project/layout. But it appears that for some reason, TOCC0-3 do not output PWM with this core. I don't know if that is a core issue, a settings issue, or something else, but I have used the same code both times, just changing pin assignments, and the fact of the matter is that TOCC4-7 work just fine while TOCC0-3 does not. Dr. Azzy, I would love to hear your thoughts here. Should I create a bug ticket on gitHub, or am I just missing a small, crucial detail? Thanks!

I don't have any problem getting TOCC3 to output pwm, using analogWrite() or when I configure Timer 2 (OC2A) to use that pin. Have you tried analogWrite(), it's the default output for OC0A. Either pin 4 (cw pins) or pin 6 (ccw pins). Maybe you have a problem with your board or the chip.

Using the code you posted above I cannot reproduce your problem. I don't suppose you had the wrong pinout mapping selected? And TOCC1 is on the serial RX line- you didn't have a serial adapter plugged in by any chance did you? Depending on it's design, it's TX line might have been forceful enough to keep the tiny from driving the pin low (which would be potentialyl damaging to the tiny, as it would involve excessive current - most adapters have a 1K resistor in series with their TX pin, so if it finds itself fighting a low impedance pin trying to drive the line the other direction, they don't damage eachother, though...)

Code you posted on github is a mess, presumably from frantically changing things in hope of finding clues - but once I changed the TOCC* register writes to do what your comments and posts said you were trying to do, (and what you do here), that did not reproduce any bad behavior either.

Thank you so much for looking into this. I got some new chips in and just tested with a new one. I'm seeing the same issue still, even with switching to different breadboard location. My setup is literally just the chip with power, and my little 24 MHz logic analyzer connected to one pin at a time. I am using no serial communications at all. I have gone back to the code posted above and I am still seeing my problems.

Here is a question that might shed some light on things. I am using a nano to program the Tiny. I have nano pin 10 to reset (PB3), nano pin 11 to SDA/MOSI(PA6), nano pin 12 to TXD1/MISO (PA5), nano pin 13 to RXD1/SCK (PA4). Is this the correct pin mapping? I would love to understand what I'm doing wrong here, both for resolving the issue as well as just the learning of it. Any insight is, again, greatly appreciated.

Please post the exact code that is behaving in an unexpected manner, and explain what behavior you expect and what you see, and I will test if I can reproduce.

Below is the exact code that I am using and seeing my issue with. I don't mean to be a bother with this, I just don't understand, which is what gets me the most. Sure I can work around this by remapping my PWM outs, but that still leaves the mystery...

Expectation:
TOCC3 -> ~20% duty cycle
TOCC4 -> ~40% duty cycle
TOCC2 -> ~60% duty cycle
TOCC1 -> ~80% duty cycle

What I see:
TOCC3 -> +5V DC
TOCC4 -> ~40% duty cycle
TOCC2 -> +5V DC
TOCC1 -> +5V DC

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

// Assign pins of ATTiny841, make sure it runs at 8 MHz internal oscillator

//DigitalPin Assignments
const unsigned int pwm1 = 6;
const unsigned int pwm2 = 5;
const unsigned int pwm3 = 7;
const unsigned int pwm4 = 8;
void setup() {
  //Define what each pin is
  pinMode(pwm1, OUTPUT);
  pinMode(pwm2, OUTPUT);
  pinMode(pwm3, OUTPUT);
  pinMode(pwm4, OUTPUT);
  //Set up the timer outputs to the correct pins. Don't touch timer 0 so that millis() works correctly
  //Pin 8 -> PWM2 -> TOCC4 (make timer OC2B)
  //Pin 9 -> PWM1 -> TOCC3 (make timer OC2A)
  //Pin 10 -> PWM3 -> TOCC2 (make timer OC1B)
  //Pin 11 -> PWM4 -> TOCC1 (make timer OC1A)
  TOCPMSA0 = (1<<TOCC1S0) | (1<<TOCC2S0) | (1<<TOCC3S1); //This sets TOCC1 throught TOCC3
  TOCPMSA1 = (1<<TOCC4S1); //This sets TOCC4 to be OC2B
  TOCPMCOE = (1<<TOCC1OE) | (1<<TOCC2OE) | (1<<TOCC3OE) | (1<<TOCC4OE); //Enable the time/output compare on TOCC1-4
  //Disable interrupts on timers
  TIMSK1 = 0;
  TIMSK2 = 0;
  //Set up 16 bit timers so that we can use PWM
  //PWM is 10-bit fast, 0x03FF TOP, no prescaler
  //Set to Fast, 10-bit PWM, max value of 1024
  TCCR1A = (1<<COM1A1) | (1<<COM1B1) | (1<<WGM11) | (1<<WGM10); //TCCR1A = _BV(COM1A1) | _BV(COM1B1) | _BV(WGM10) | _BV(WGM11);
  TCCR1B = (1<<WGM12) | (1<<CS10); //No prescaler,  | _BV(WGM12)
  TCCR1C = 0b00000000; //Set to zero when in PWM mode
  TCCR2A = (1<<COM2A1) | (1<<COM2B1) | (1<<WGM21) | (1<<WGM20); //TCCR2A = _BV(COM2A1) | _BV(COM2B1) | _BV(WGM20) | _BV(WGM21); //Set to Fast, 10-bit PWM, max value of 1024
  TCCR2B = (1<<WGM22) | (1<<CS20); //No prescaler,  | _BV(WGM22)
  TCCR2C = 0b00000000; //Set to zero when in PWM mode
  uint16_t dutyCycle1 = 200;
  uint16_t dutyCycle2 = 400;
  uint16_t dutyCycle3 = 600;
  uint16_t dutyCycle4 = 800;
  writeDutyCycle(3,dutyCycle1); //PWM1 is on TOCC3
  writeDutyCycle(4,dutyCycle2); //PWM2 is on TOCC4
  writeDutyCycle(2,dutyCycle3); //PWM3 is on TOCC2
  writeDutyCycle(1,dutyCycle4); //PWM4 is on TOCC1
}
void loop() {
}
void writeDutyCycle(uint8_t tocc, uint16_t duty) {
  // Set duty cycle based on the TOCC pin being specified
  if(tocc == 1) {
    OCR1A = duty;
  }
  else if (tocc == 2) {
    OCR1B = duty;
  }
  else if (tocc == 3) {
    OCR2A = duty;
  }
  else if (tocc == 4) {
    OCR2B = duty;
  }
  TOCPMCOE |= 1<<tocc;
}

Also, attached is what my configuration settings are for programming.

config.png

So I just hooked up my brand new Espotek Labrador for troubleshooting purposes. I used it's oscilloscope function to see what the time waveform looks like instead of what the logic analyzer said. The story is interesting. On TOCC4, I get a nice, strong pulse train, peaking at or above 4.8V. On TOCC3, it is a weak pulse train centered at about 1.6V and clearly not a square wave. It gets worse for TOCC2, and by the time I measure TOCC1, it is almost a static 1.6V DC on the pin. Very weird. I have attached screen shots for each case.

ripthorn:
Below is the exact code that I am using and seeing my issue with. I don't mean to be a bother with this, I just don't understand, which is what gets me the most. Sure I can work around this by remapping my PWM outs, but that still leaves the mystery...

Expectation:
TOCC3 -> ~20% duty cycle
TOCC4 -> ~40% duty cycle
TOCC2 -> ~60% duty cycle
TOCC1 -> ~80% duty cycle

What I see:
TOCC3 -> +5V DC
TOCC4 -> ~40% duty cycle
TOCC2 -> +5V DC
TOCC1 -> +5V DC

#include <avr/io.h>

#include <avr/interrupt.h>
#include <util/atomic.h>

// Assign pins of ATTiny841, make sure it runs at 8 MHz internal oscillator

//DigitalPin Assignments
const unsigned int pwm1 = 6;
const unsigned int pwm2 = 5;
const unsigned int pwm3 = 7;
const unsigned int pwm4 = 8;
void setup() {
  //Define what each pin is
  pinMode(pwm1, OUTPUT);
  pinMode(pwm2, OUTPUT);
  pinMode(pwm3, OUTPUT);
  pinMode(pwm4, OUTPUT);
  //Set up the timer outputs to the correct pins. Don't touch timer 0 so that millis() works correctly
  //Pin 8 -> PWM2 -> TOCC4 (make timer OC2B)
  //Pin 9 -> PWM1 -> TOCC3 (make timer OC2A)
  //Pin 10 -> PWM3 -> TOCC2 (make timer OC1B)
  //Pin 11 -> PWM4 -> TOCC1 (make timer OC1A)
  TOCPMSA0 = (1<<TOCC1S0) | (1<<TOCC2S0) | (1<<TOCC3S1); //This sets TOCC1 throught TOCC3
  TOCPMSA1 = (1<<TOCC4S1); //This sets TOCC4 to be OC2B
  TOCPMCOE = (1<<TOCC1OE) | (1<<TOCC2OE) | (1<<TOCC3OE) | (1<<TOCC4OE); //Enable the time/output compare on TOCC1-4
  //Disable interrupts on timers
  TIMSK1 = 0;
  TIMSK2 = 0;
  //Set up 16 bit timers so that we can use PWM
  //PWM is 10-bit fast, 0x03FF TOP, no prescaler
  //Set to Fast, 10-bit PWM, max value of 1024
  TCCR1A = (1<<COM1A1) | (1<<COM1B1) | (1<<WGM11) | (1<<WGM10); //TCCR1A = _BV(COM1A1) | _BV(COM1B1) | _BV(WGM10) | _BV(WGM11);
  TCCR1B = (1<<WGM12) | (1<<CS10); //No prescaler,  | _BV(WGM12)
  TCCR1C = 0b00000000; //Set to zero when in PWM mode
  TCCR2A = (1<<COM2A1) | (1<<COM2B1) | (1<<WGM21) | (1<<WGM20); //TCCR2A = _BV(COM2A1) | _BV(COM2B1) | _BV(WGM20) | _BV(WGM21); //Set to Fast, 10-bit PWM, max value of 1024
  TCCR2B = (1<<WGM22) | (1<<CS20); //No prescaler,  | _BV(WGM22)
  TCCR2C = 0b00000000; //Set to zero when in PWM mode
  uint16_t dutyCycle1 = 200;
  uint16_t dutyCycle2 = 400;
  uint16_t dutyCycle3 = 600;
  uint16_t dutyCycle4 = 800;
  writeDutyCycle(3,dutyCycle1); //PWM1 is on TOCC3
  writeDutyCycle(4,dutyCycle2); //PWM2 is on TOCC4
  writeDutyCycle(2,dutyCycle3); //PWM3 is on TOCC2
  writeDutyCycle(1,dutyCycle4); //PWM4 is on TOCC1
}
void loop() {
}
void writeDutyCycle(uint8_t tocc, uint16_t duty) {
  // Set duty cycle based on the TOCC pin being specified
  if(tocc == 1) {
    OCR1A = duty;
  }
  else if (tocc == 2) {
    OCR1B = duty;
  }
  else if (tocc == 3) {
    OCR2A = duty;
  }
  else if (tocc == 4) {
    OCR2B = duty;
  }
  TOCPMCOE |= 1<<tocc;
}




Also, attached is what my configuration settings are for programming.

You picked pin numbers based on the counterclockwise pin mapping, but are using the clockwise pin mapping. Hence, they were not set output first - except on pin 5 (bit 5 in port A, PIN_PA5, TOCC4), which happens to b arduino pin 5 in both pin mappings) When you compile for an 841, the core even outputs a warning reminding you which pinout you have selected. Which I belive I asked you about a while earlier in the thread based on your initial description of the symptoms.

This is precisely why I now use the PIN_Pxn notation (introduced on ATTinyCore in 1.4.0 - to my embarassment, I thought I'd added them last year until a month or so ago as I was getting ready to release 1.4.0, and tried to use them in a test sketch)) basically exclusively to refer to pins, and avoid "digital pin numbers" or "arduino pin numbers" whenever possible. The PIN_Pxn macros are all just #defined to the Arduino pin numbers... but using those, when you change the pin numbering, the values that those constants are #defined as changes transparently and your sketch continues unfazed by it. And there is not even a shadow of ambiguity about which pin you intend to refer to. The fact that there are "physical pin numbers" and ":arduino pin numbers" alone causes a great deal of confusion among new Arduino users.

The noise you're picking up on the scope is just that - electrical noise on a tristated input pin, probably most of it capacitively coupled in from the one pin that is PWMing;

DrAzzy:
You picked pin numbers based on the counterclockwise pin mapping, but are using the clockwise pin mapping. Hence, they were not set output first - except on pin 5 (bit 5 in port A, PIN_PA5, TOCC4), which happens to b arduino pin 5 in both pin mappings) When you compile for an 841, the core even outputs a warning reminding you which pinout you have selected. Which I belive I asked you about a while earlier in the thread based on your initial description of the symptoms.

This is precisely why I now use the PIN_Pxn notation (introduced on ATTinyCore in 1.4.0 - to my embarassment, I thought I'd added them last year until a month or so ago as I was getting ready to release 1.4.0, and tried to use them in a test sketch)) basically exclusively to refer to pins, and avoid "digital pin numbers" or "arduino pin numbers" whenever possible. The PIN_Pxn macros are all just #defined to the Arduino pin numbers... but using those, when you change the pin numbering, the values that those constants are #defined as changes transparently and your sketch continues unfazed by it. And there is not even a shadow of ambiguity about which pin you intend to refer to. The fact that there are "physical pin numbers" and ":arduino pin numbers" alone causes a great deal of confusion among new Arduino users.

The noise you're picking up on the scope is just that - electrical noise on a tristated input pin, probably most of it capacitively coupled in from the one pin that is PWMing;

Well, that was the issue. In your first response you did mention pin mapping, but I guess it just didn't sink in. You are exactly right and changing my pin mapping to the following totally fixed the problem:

const unsigned int pwm1 = PIN_A4;
const unsigned int pwm2 = PIN_A5;
const unsigned int pwm3 = PIN_A3;
const unsigned int pwm4 = PIN_A2;

I tried to set them as PIN_PAn, but it didn't recognize it, turns out it doesn't like that second P, just PIN_An in my case.

Thank you so much for helping me through this. I know it must have been frustrating, but I really do appreciate it!

ripthorn:
Well, that was the issue. In your first response you did mention pin mapping, but I guess it just didn't sink in. You are exactly right and changing my pin mapping to the following totally fixed the problem:

const unsigned int pwm1 = PIN_A4;
const unsigned int pwm2 = PIN_A5;
const unsigned int pwm3 = PIN_A3;
const unsigned int pwm4 = PIN_A2;

I tried to set them as PIN_PAn, but it didn't recognize it, turns out it doesn't like that second P, just PIN_An in my case.

Thank you so much for helping me through this. I know it must have been frustrating, but I really do appreciate it!

Are you using the latest version of the core? 1.4.1 is latest released version.

PIN_xn constants are deprecated because (as I discovered after adding them) the official core uses PIN_An to represent the digital pin number of the analog pins; ATTinyCore still has those for now for backwards compatibility with existing sketches. Probably if/when I ever get to where I feel like pushing out a 2.0.0 and rolling in all the breaking changes I want to make to it...

Very good call on the core version. Apparently I have 1.3.3. I'll update and go from there. Thanks again!

EDIT: Works beautifully!

Alright, so I integrated all of my other code into my previous example code above, but now I don't get any PWM output on TOCC4. Without changing any wiring or anything, I loaded my example code above, looked at the four outputs, and saw the correct PWM on each. I copied and pasted that code into my other file, added all my other functionality, loaded that on to the chip, but I don't get the TOCC4 PWM output. I have attached both files below, with my example code being the _barebones designated one. The other is over 500 lines long, so I apologize for that. I am just wondering if there is anything that would mess up the operation of the code. I don't think there is, but I could just be missing something. Some thoughts I've had about what could be an issue or what might use a timer:

  • delayMicroseconds
  • delay
  • millis()
  • interrupt service routines on PCIE0 and PCIE1 (PCMSK0 = 0b00000011, PCMSK1 = 0b00000011 for enabling interrupts on PCInt0/PCInt1/PCInt8/PCInt9)

I'm so close! If I can figure out why this issue exists and fix it, I will have everything working (I've spent the weekend writing and debugging every other function I'm using). Any help would be greatly appreciated!

Test_Tiny841.ino (18.3 KB)

Test_Tiny841_barebones.ino (2.41 KB)

Alright, some really good progress has been made. I have tracked down the PWM issues thusly:

  • When I write to pin PA7 after the setup function, whether high or low, the PWM on TOCC4 (configured as OC2B) stops. Completely stops.
  • When I write to pin PA6 after the setup function, whether high or low, the PWM on TOCC1 (configured as OC1A) stops completely

Is there some kind of relationship between these pins that I am unaware of? This seems really quite strange. In practice, I won't write to PA6, as it is an unused pin, but I write to PA7 several times a second as an LED blink rate indicator.

As a point of comparison, OC1B on TOCC2 and OC2A on TOCC3 are unaffected by any of the actions I take on my pins. Anyone have any ideas why I might be seeing this pin interdependency?

That is not unexpected behavior, though I don't deny that it could be handled more gracefully. When you manually manipulate registers associated with a given peripheral, that breaks the assumptions of the Arduino abstractions that work on the same peripheral, so you can no longer expect them to behave correctly.

A car isn't supposed to fill with water, but if you try to drive it on a river, it will! And that's considered acceptable - you're violating the assumptions made by the car design, namely there being a solid surface underneath it.

It is turning off PWM on the timer channel that normally drives PWM on that pin. It doesn't know that you've configured that timer to drive PWM on a different pin (and checking for that at runtime is not a remotely sane use of precious flash on an 8k part with an array of peripherals that would make an atmega328p turn green with envy)

I think what I probably should do in turnOffPWM() is have it just clear the corresponding TOCPMCOE bit while leaving the timer unmolested... but that's not how it's done
I will fix in next release - would fix in github now, except that ATTinyCore is getting major work done on the 841, 1634, and 828 (the whole "modern" core, which was an unspeakable abomination under the hood is getting ripped out and killed with fire, and replaced with the other core and "variant" files. So it's kind of a waste to work on it a just a few weeks before the file that issue resides in gets trashed,

Actually, the other reason I'm not going to do it now is that the horrible decisions made during the design of that core make it really hard to drill down to where the code underlying a given sort of bad behavior is...

The quick fix is to comment out the call to turnOffPWM in digitalWrite() in wiring_digital.c within the tinymodern core

Thanks for that answer! Do I need to recompile anything if I go in and make your suggested modification to digitalWrite? Or is there a lower level AVR command I can send to set a pin high or low that wouldn't require modification of your fantastic core? I'm happy to do either, I just want to make sure that I implement it correctly. Thanks again!

EDIT: Also, depending on what the timeline is for the update, it might not even be a big deal for me. I'm trying to make sure that everything is good to go, but I have a different part of my circuit that I am wrestling with right now that will keep me occupied for a little while...

Recompile? Just your sketch, as normal.

Can also use direct port writes instead of digitalWrite()/etc

PORTA&=~(1<<6); //sets PA6 low
PORTA|=(1<<6); //sets PA6 high

Bits in DDRA control input or output
DDRA|=(1<<7); //sets PA7 as output

On every classic AVR except the 841, 441, 1634, and 828, setting a pin HIGH while it's not set output turns on the pullups.

But the 841 has a separate PUEA and PUEB register.

Note that everywhere except 841/441, all of the aforementioned registers are in the I/O space so that (register)|=(compile-time-known byte with 1 bit set), and (register)&=(compile-time-known byte with all but one bit set) compile to the atomic SBI/CBI instructions, so they're both lightning fast (2 clock cycles) and safe even if you have interrupts writing the same registers. On the 841/441, the PUEx registers aren't in the IO space.... even though there was room for them there! (wacky parts those last three classic tinies are.... You've got the 841, with a peripheral loadout more typical of an atmega - but sadly constrained on flash/ram, the 1634 with plenty of ram, flash, and the dual uarts, but nothing much else of note, and the 828, with a billion ADC channels, but again, otherwise very boring - weird that they gave the 841 the awesome ADC, but the 828 was the one that got all the channels!.... I sort of feel like they might have seen the writing on the wall and been scrambling to do the last things they'd always wanted to make available on classic AVRs).

Hi,

First off, DrAzzy thanks for all that you do, between the core and documentation, and your helpful responses in forums, I have learned an incredible amount about implementing ATTiny's over the past few weeks.

I have an application similar to ripthorns, and I am noticing that after calling digitalWrite(pinx,0), it is necessary to rewrite the COMnx0:1 bits (along with the appropriate TOCPMCOE bit) before fast PWM is enabled again.
For example, my PWM function is as follows -

void motor_PWM(int duty) {                              // set motor PWM
  if (duty == 0) {
    digitalWrite(MOTOR_PIN, 0);
  } else {
    TCCR1A |= (2 << COM1B0);                            // enable OC1B pins for non-inverting PWM
    OCR1B = duty;
    TOCPMCOE |= (1 << TOCC2OE);
  }
}

Just wondering if this is expected behavior? And figured it might help some other folks..

ATTiny841, 8MHz Internal
Core v1.4.1

daanstevenson:
Hi,

First off, DrAzzy thanks for all that you do, between the core and documentation, and your helpful responses in forums, I have learned an incredible amount about implementing ATTiny's over the past few weeks.

I have an application similar to ripthorns, and I am noticing that after calling digitalWrite(pinx,0), it is necessary to rewrite the COMnx0:1 bits (along with the appropriate TOCPMCOE bit) before fast PWM is enabled again.
For example, my PWM function is as follows -

void motor_PWM(int duty) {                              // set motor PWM

if (duty == 0) {
    digitalWrite(MOTOR_PIN, 0);
  } else {
    TCCR1A |= (2 << COM1B0);                            // enable OC1B pins for non-inverting PWM
    OCR1B = duty;
    TOCPMCOE |= (1 << TOCC2OE);
  }
}




Just wondering if this is expected behavior? And figured it might help some other folks..


ATTiny841, 8MHz Internal
Core v1.4.1

Yes, that is correct behavior. You have to do one or the other, or the timer will still have control of the pin; IIRC you can get 100% on time, or 100% off time (forget which) without taking the pin off timer control.... Yeah, it's 100% on time... if you set the compare match to >= TOP, it will never match and hence never turn off, but if you set it to 0, you get a single cycle before it turns off (yes, this also means that analogWrite doesn't behave quite as advertised.... I think to make it actually work exactly as advertised, TOP needs to be 254, and the compare value should be set to 1 less than the value passed to analogWrite)? I think megaTinyCore does this. One time early in the quarentimes I threw several weeks of my life into that. There are much better parts of my cores I could have spent that time on, imo...
I created an issue to remind me to alter it so that it only twiddles the TOCPMCOE register bits.

Current behavior is stupid, and it makes life harder on people who take over the timers to generate advanced PWM

Don't be so hard on yourself! :wink:
And thanks for the verification of behavior.