Attiny85 with Common Cathode RGB LED PWM,

Hello there!

I am working on a project that flashes a single common cathode RGB LED on a small PCB that is battery-powered. I am having an issue where my LED doesn't turn off fully when the R, G, and B values are set to 0. There remains a dim yellow light, so a little red and green.
When using the Arduino functions I do not get this behavior.
The current code example I show below doesn't contain any Arduino library functions.
I'm doing this because I want to save space on my chip for more features.
So my question is what could I be doing wrong that I am getting a signal from the Red and Green pins even when they are set to 0

The components I am using are as follows:

1 Attiny85
1 RGB LED
1 tact switch
2 CR1620 Coin Cell Batteries

Code Example with described behavior:

#define F_CPU 8000000

#define PIN_R 0
#define PIN_G 1
#define PIN_B 4
#define PIN_BUTTON 3

uint8_t lightsOn;
unsigned int previousClockTime;
unsigned int main_clock;
unsigned int timerDuration;
volatile uint8_t timer;
volatile uint16_t millis_2;

int num = 0;

uint8_t rainbow[8][3] =
{
  {255, 0, 0}, // Red
  {255, 165, 0}, // Orange
  {255, 255, 0}, // Yellow
  {0, 128, 0}, // Green
  {0, 0, 255}, // Blue
  {75, 0, 130}, // Indigo
  {238, 130, 238},// Violet
  {255, 255, 255} // White
};


ISR(TIMER0_OVF_vect) {
  timer++;
  if (timer == 32) {
    timer = 0;
    millis_2++;  // every 1ms
  }
}


int main(void) {

  DDRB |= (1 << PIN_R) | (1 << PIN_G) | (1 << PIN_B);
  TCCR0A |= (1 << COM0A1) | (1 << COM0B1) | (1 << WGM01) | (1 << WGM00);
  TCCR0B |= (1 << FOC0A) | (1 << FOC0B) | (1 << CS00);

  TIMSK |= (1 << TOIE0);

  TCCR1 |= (1 << CS10);
  GTCCR |= (1 << FOC1B) | (1 << PWM1B) | (1 << COM1B1);
  GIMSK &= ~(1 << PCIE);
  sei();

  PORTB |= (1 << PIN_BUTTON);

  while (1) {
    main_clock = millis_2;
    if (lightsOn) {
      OCR0A = rainbow[num][0];
      OCR0B = rainbow[num][1];
      OCR1B = rainbow[num][2];
      timerDuration = 1000;
    }
    else {
      OCR0A = 0;
      OCR0B = 0;
      OCR1B = 0;
      timerDuration = 1000;
    }
    if (main_clock - previousClockTime > timerDuration) {
      lightsOn = !lightsOn;
      num = (num + 1) % 8;
      previousClockTime = main_clock;
    }
  }
}

I appreciate the help! I've been trying to work through this problem for weeks now.

My head on the chopping block --
what if. . .

unsigned long previousClockTime;
unsigned long main_clock;
unsigned long timerDuration;

volatile unsigned long millis_2;   // ??

Thanks for the reply! Haha now realizing yes that does need to change. Still same behavior sadly..

Drat.

Disable PWM'ing if == 0 ? (i.e. rainbow[num])
I have a ESP32 with an on-board RGB, but analogWrite(0) doesn't turn it full off (it has to be digitalWritten LOW).

@DrAzzy or @jremington ?

Why are you setting the Force Output on Compare bits?

Without comments in the code, it is impossible to understand what you are trying to do with these settings, but they are not standard PWM settings.

I suggest that you get a simple PWM operation running properly with one LED, on a single timer channel, first on Timer0, then on Timer1, before trying to mix the two timers together in the final application.

Thanks for the reply, I apologize I took the comments out to make it less messy, here is the setup with comments:

DDRB |= (1 << PIN_R) | (1 << PIN_G) | (1 << PIN_B);  // Sets Pins to Output pinMode()
TCCR0A |= (1 << COM0A1) | (1 << COM0B1) | (1 << WGM01) | (1 << WGM00);  // Sets the PWM mode Pin 0 and Pin 1 -> Main Register
TCCR0B |= (1 << FOC0A) | (1 << FOC0B) | (1 << CS00);                    // Sets the PWM mode Pin 4 -> CS00 enabled -> no prescaler

TIMSK |= (1 << TOIE0);  // Enable Overflow Interrupt

TCCR1 |= (1 << CS10);                                  // Timer 1 Initalize Start
GTCCR |= (1 << FOC1B) | (1 << PWM1B) | (1 << COM1B1);  // Initialize PWM
                                                           // OCR1C = 255;                                           // Timer 1 ??
GIMSK &= ~(1 << PCIE); // I think needed for Sleep mode
sei();

PORTB |= (1 << PIN_BUTTON);  // Sets button pin to PB3

I am very new to Port Manipulation, but my goal is to use 3 PWM Pins to control my RGB LED.
For the piece of code you are referring to I believe I am using it set the Timer1 to handle the PWM for PB4.

Again, this is me trying to piece things together that I have found.

This is what I use (at 1 MHz)

  DDRB = 1 << DDB4 | 1 << DDB1 | 1 << DDB0;
  TCCR0A = 2 << COM0A0 | 2 << COM0B0 | 3 << WGM00;
  TCCR0B = 0 << WGM02 | 1 << CS01;
  TCCR1 = 0 << PWM1A | 0 << COM1A0 | 1 << CS12;
  GTCCR = 1 << PWM1B | 2 << COM1B0;

Recommend you read the datasheet chapter 11.7.3 about fast PWM mode.

It describes the special case when setting OCR0A to 0. You will still get a small spike, which is what you see.

If you wire your led the other way around (and inverse your code) so that OCR0A at MAX is "off", it will be really off. But with a common cathode that might be difficult, so then explore
to reverse the polarity of the PWM output via COM0A[1:0] bits.

Interesting, I'll try that out and see what needs to change for 8 MHz. Thanks for the suggestion!

Ooo that's very interesting about setting the Compare Registers to 0. That's exactly what is happening!

So when I first started this project I was using a Common Anode LED and It did not create the small spike behavior showing the dim yellow on the LED. I had all of the values inversed and everything worked great.
So when it came to setting up the sleep mode to save battery when the LED is off. I found out that when in sleep mode the LED values are all set to low automatically. But since it's a common anode Low was considered full brightness and turned the LED to full white, and I could not figure out any way to override that.
So I switched to Common Cathode to remedy that issue.
But now with a common cathode sleep mode works but now observe the dim yellow behavior when Compare Registers for LED Pins are set to 0.

So is this a Common Cathode vs Common Anode issue?
Or is there a way to make either piece of hardware work in the way I expect, and it's just about the way the Registers are initialized?

You would not have found anyone setting FOC bits. You really need to read the data sheet closely, many times!

You could try DDRB = 0b00000000; when lightsOn == 0 .

Yes, you're right, I need to get more comfortable with reading datasheets. So looking into the what you mentioned about the FOCs further it does say that the FOCs are to be active only when in a non-PWM mode, so that wasn't doing anything for me. So I removed the FOCs, and the PWM for PB4 still works!

Now my initialization looks like this, I also removed other pieces that aren't applicable to this example:

DDRB |= (1 << PIN_R) | (1 << PIN_G) | (1 << PIN_B);  // Sets Pins to Output pinMode()
TCCR0A |= (1 << COM0A1) | (1 << COM0B1) | (1 << WGM01) | (1 << WGM00);  // Sets the PWM mode Pin 0 and Pin 1 -> Main Register
TCCR0B |= (1 << CS00);                    // Sets the PWM mode Pin 4 -> CS00 enabled -> no prescaler

TIMSK |= (1 << TOIE0);  // Enable Overflow Interrupt

TCCR1 |= (1 << CS10);                                  // Timer 1 Initalize Start
GTCCR |= (1 << FOC1B) | (1 << PWM1B) | (1 << COM1B1);  // Initialize PWM
sei();

The behavior is still the same where the LED is producing a dim yellow light when comparing registers are set to 0.

I tried your suggestion and it caused the LED to remain a dim yellow color but not flash the other colors at all.

Please post a complete schematic diagram of the assembly (hand drawn, not Fritzing), with parts and pins clearly labeled.

I'm fairly sure that the phase correct PWM mode does not produce the narrow spike, so give that a try. I know that it does not on more advanced ATmega MCUs, and will test that with the ATtiny85 later today.

I will get that drawing to you later today. Thank you for the help!

When I take your initial code and change

TCCR0A |= (1 << COM0A1) | (1 << COM0B1) | (1 << WGM01) | (1 << WGM00) ;

to

TCCR0A |= (1 << COM0A1) | (1 << COM0B1) | (1 << WGM01) | (1 << WGM00) | (1 << COM0A0) | (1 << COM0B0 );

Your leds will switch off.

Probably you need to get the PWM table from your common anode sketch to get the brightness (colors) working again, as this inverts the PWM pin output.

[edit] or the quick and dirty fix setting the compare match this way

      OCR0A = 255 - rainbow[num][0];
      OCR0B = 255 - rainbow[num][1];

This sets up Timer0 for phase correct PWM, and there is no spike on the output when OCRnx = 0. For inverted output operation (output HIGH when OCR0x = 0), set COM0x bits to 3

// ATtiny85, ATtinyCore, 8 MHz, Timer1 = CPU clock
//Sketch uses 314 bytes (3%) of program storage space. Maximum is 8192 bytes.
//Global variables use 9 bytes (1%) of dynamic memory, leaving 503 bytes for local variables. Maximum is 512 bytes.

void setup() {
  // Timer0 phase correct PWM mode PB0 = OC0A, PB1 = OC0B  cycle time on scope: 4.2 ms
  DDRB = 1 << DDB4 | 1 << DDB1 | 1 << DDB0;  //set PWM outputs
  TCCR0A = 2 << COM0A0 | 2 << COM0B0 | 1 << WGM00;  //clear OC0x on compare match up counting, set on down, phase correct PWM 
  TCCR0B = 0 << WGM02 | 3 << CS00; //clk/64, 125 kHz, 244 Hz PWM
  OCR0A = 64;  //25% PWM HIGH
  OCR0B = 0;  //no spike, output LOW
  TIMSK = 0;
//  TCCR1 = 0 << PWM1A | 0 << COM1A0 | 7 << CS10; //clk/64, 125 kHz
//  GTCCR = 1 << PWM1B | 2 << COM1B0;  //clear OC1B on compare match
//  OCR1B = 0;
}

void loop() {}

Oh my god that worked!

I switched this line

TCCR0A |= (1 << COM0A1) | (1 << COM0B1) | (1 << WGM01) | (1 << WGM00);  

with this line

TCCR0A = 2 << COM0A0 | 2 << COM0B0 | 1 << WGM00;

And no more spike!

I can tell my timer is a little slower than before, is that to be expected?
I can easily update my custom millis function to accommodate that.
Super thankful for all of the help!

Here is the full working code snippet for anyone looking!

#define F_CPU 8000000

#define PIN_R 0
#define PIN_G 1
#define PIN_B 4
#define PIN_BUTTON 3

uint8_t lightsOn;
unsigned long previousClockTime;
unsigned long main_clock;
unsigned long timerDuration;
volatile uint8_t timer;
volatile unsigned long millis_2;

int num = 0;

uint8_t rainbow[8][3] =
{
  {255, 0, 0}, // Red
  {255, 165, 0}, // Orange
  {255, 255, 0}, // Yellow
  {0, 128, 0}, // Green
  {0, 0, 10}, // Blue
  {255, 255, 255}, // White
  {75, 0, 130}, // Indigo
  {238, 130, 238},// Violet

};


ISR(TIMER0_OVF_vect) {
  timer++;
  if (timer == 16) {
    timer = 0;
    millis_2++;  // every 1ms
  }
}


int main(void) {
  DDRB |= (1 << PIN_R) | (1 << PIN_G) | (1 << PIN_B);   // Sets Pins to Output pinMode()
  TCCR0A = 2 << COM0A0 | 2 << COM0B0 | 1 << WGM00;      // clear OC0x on compare match up counting, set on down, phase correct PWM
  TCCR0B |= (1 << CS00);                               // Start timer/counter 0, no prescaling
  TIMSK |= (1 << TOIE0);                               // Enable timer/counter 0 overflow interrupt
  TCCR1 |= (1 << CS10);                                // Start timer/counter 1, no prescaling
  GTCCR |= (1 << PWM1B) | (1 << COM1B1);               // Enable PWM mode on timer/counter 1 output B
  sei();                                               // Enable global interrupt

  while (1) {
    main_clock = millis_2;
    if (lightsOn) {
      OCR0A = rainbow[num][0];
      OCR0B = rainbow[num][1];
      OCR1B = rainbow[num][2];
      timerDuration = 1000;
    }
    else {
      OCR0A = 0;
      OCR0B = 0;
      OCR1B = 0;
      timerDuration = 1000;
    }
    if (main_clock - previousClockTime > timerDuration) {
      if (lightsOn) {
        num = (num + 1) % 8;
      }
      lightsOn = !lightsOn;

      previousClockTime = main_clock;
    }
  }
}

Thanks again for the help! :pray: