Go Down

Topic: modifying SoftPWM for attiny (Read 6700 times) previous topic - next topic

vapor20

Never mind - got it.  I changed the timers used in .cpp

Marvin Martian

Sorry for not getting back -  I need to pick this all up again soon as I'm now trying to modify it for the Tiny85.

Glad you got it working but keep an eye out if you are trying to control a lot of outputs as the 2313 runs out of RAM before you run out of pins!

Vicne

#17
Mar 15, 2015, 01:29 am Last Edit: Mar 15, 2015, 01:33 am by Vicne
Hi,

Sorry if it's wrong to dig out an old thread, but I'm facing the exact same problem and although several people seem to have succeeded in the end, no one posted a clear way to run softpwm on ATTiny2313

So based on this topic, and particularly this advice:
Timer 0 is used for millis (delay).  The 2313 has a second timer (timer 1) that is free for you to use.
I thought the best was to modify SoftPWM_timer.h as follows to use Timer1 for ATtinyX313. Basically, I defined USE_TIMER1 for X313 chips, and added corresponding macros in that case:

Code: [Select]
#include <avr/io.h>
#include <avr/interrupt.h>

// allow certain chips to use different timers
#if defined(__AVR_ATmega32U4__)
#define USE_TIMER4_HS // Teensy 2.0 lacks timer2, but has high speed timer4 :-)
#elif defined(__AVR_ATtinyX313__)
#define USE_TIMER1
#else
#define USE_TIMER2
#endif

// for each timer, these macros define how to actually use it
#if defined(USE_TIMER2)
#define SOFTPWM_TIMER_INTERRUPT    TIMER2_COMPA_vect
#define SOFTPWM_TIMER_SET(val)     (TCNT2 = (val))
#define SOFTPWM_TIMER_INIT(ocr) ({\
  TIFR2 = (1 << TOV2);    /* clear interrupt flag */ \
  TCCR2B = (1 << CS21);   /* start timer (ck/8 prescalar) */ \
  TCCR2A = (1 << WGM21);  /* CTC mode */ \
  OCR2A = (ocr);          /* We want to have at least 30Hz or else it gets choppy */ \
  TIMSK2 = (1 << OCIE2A); /* enable timer2 output compare match interrupt */ \
})
#elif defined(USE_TIMER1)
#define SOFTPWM_TIMER_INTERRUPT    TIMER1_COMPA_vect
#define SOFTPWM_TIMER_SET(val)     (TCNT1 = (val))
#define SOFTPWM_TIMER_INIT(ocr) ({\
  TIFR = (1 << TOV1);     /* clear interrupt flag */ \
  TCCR1B = (1 << CS11);   /* start timer (ck/8 prescalar) */ \
  TCCR1A = (1 << WGM11);  /* CTC mode */ \
  OCR1A = (ocr);          /* We want to have at least 30Hz or else it gets choppy */ \
  TIMSK = (1 << OCIE1A);  /* enable timer1 output compare match interrupt */ \
})
#elif defined(USE_TIMER4_HS)
#define SOFTPWM_TIMER_INTERRUPT    TIMER4_COMPA_vect
#define SOFTPWM_TIMER_SET(val)     (TCNT4 = (val))
#define SOFTPWM_TIMER_INIT(ocr) ({\
  TCCR4A = 0; \
  TCCR4B = 0x04; /* CTC Mode */\
  TCCR4C = 0; \
  TCCR4D  = 0; \
  TCCR4E  = 0; \
  OCR4C  = 0; \
  OCR4C  = (ocr); \
  TIMSK4  = (1 << OCIE4A); \
})
#endif


I also defined SOFTPWM_MAXCHANNELS to be 6 for the moment, because 20 requires too much memory.

However, my samples seem to have all timings wrong.

Here is my current test:
Code: [Select]
#include "SoftPWM.h"

void setup() {
  SoftPWMBegin(SOFTPWM_INVERTED);
  for (int i = 8; i < 14; i++) {
    SoftPWMSet(i, 0);
    SoftPWMSetFadeTime(i, 300, 300);
  }
}

void loop() {
  delay(1000);
  for (int i = 8; i < 14; i++) {
    SoftPWMSet(i, 85);
    delay(1000);
    SoftPWMSet(i, 0);
    delay(1000);
  }
}


I see a flash through all the leds with a small pause between each loop().
The scope indicates I'm getting 6ms pulses, but there's some randomness in the delays between them (see attached captures).

I have the feeling something messes with Timer0, but I fail to see what...

Any idea what could be wrong ?

All comments are welcome of course.


Vicne

Vicne

Hi,


Here are more tests, but they leave me even more puzzled.

What I did was first to focus on one single led, and triggering it on and off with a 1 second delay (in theory) in between. So the code is:
Code: [Select]

#include "SoftPWM.h"

void setup() {
  SoftPWMBegin(SOFTPWM_INVERTED);
  SoftPWMSetFadeTime(8, 0, 0);
}

void loop() {
  delay(1000);
  SoftPWMSet(8, 255);
  delay(1000);
  SoftPWMSet(8, 0);
}


What I observe is that the led goes on and off with a 32 second period instead of 2 (so delay takes 16 times more time than it should.

Then I thought I might debug when the interrupt handler is triggered, so I've added "pinMode(14, OUTPUT);" to the setup and "digitalWrite(14, 1-digitalRead(14));" as the first line of the ISR implementation.

The scope shows that pin 14 has a pulse every ms (meaning the ISR gets triggered twice in a row), while the pmw output in active state is almost always ON, except a small glitch every 125ms (See captures).

I really don't understand what's going on, because when I triggered all the leds yesterday, I had the feeling that delay() was passing way too fast (and that was also the observation by Marvin Martian before he found the issue), while now time passes 16 times too slowly...

I also checked the ATTiny core source declaration and for the 2313, it confirms what Coding Badly said:

#define TIMER_TO_USE_FOR_MILLIS                   0

So I don't see where the conflict comes from...

(oh, and yes, it's an actual conflict, because I also checked that fuses were properly burnt by removing calls to softPWM and using digitalWrite(), and in that case, the 1000ms delay is OK.)


@vapor20, I know it was 18 months, but if you're still around, do you remember the way you "changed the timers used in .cpp" ? The only static references to timers I find are in the "#ifdef WIRING" sections, which are not active here (I checked). It seems all the rest points to macros in SoftPWM_timer.h...

Kind regards,


Vicne

Vicne

Update:

By dichotomic trial and error :-), I could determine that the line that messes up is the last one of the SOFTPWM_TIMER_INIT macro:

TIMSK = (1 << OCIE1A);  /* enable timer1 output compare match interrupt */

In other words, reconfiguring Timer 1 did not disturb the delay() behaviour. So there is no timer conflict as I thought.

My guess now is that the issue is that both timers (0 for millis() and 1 for pwm) call the same interrupt code, or that the timer 0 interrupt service routine gets called when timer 1 interrupt triggers, causing chaos.

Now I admit it's still vague, but at least that's a small progress...

Of course, any idea or comment is welcome.

Kind regards,


Vicne

Coding Badly


Which SoftPWM library are you using?


Vicne

#21
Mar 16, 2015, 02:28 pm Last Edit: Mar 16, 2015, 02:30 pm by Vicne
Hi, Coding, and thanks for your reply.

I should have given a bit of context indeed:
- SoftPWM V0005 from https://code.google.com/p/rogue-code/wiki/SoftPWMLibraryDocumentation
- ATtiny cores 1.5 from https://code.google.com/p/arduino-tiny/
- Android IDE v1.6.0 on Windows 7 32b

The file I modified as described in post #17 is https://code.google.com/p/rogue-code/source/browse/Arduino/libraries/SoftPWM/trunk/SoftPWM_timer.h

Kind regards,

Vicne

Coding Badly


Vicne

Hi, Coding,


In fact, the version you pointed me to is a fork of the one I used, as indicated at the bottom of the page.

I diff'ed both versions and the only change Paul Stoffregren made is in the SoftPWM_timer.h where he added a "#if defined(__arm__)" test for the Teensy, in which case he uses a special IntervalTimer class instead of editing registers.
The comment is "// Teensy 3.0 has special interval timers :-)" ...

So basically, for a 2313, the compiled code is the same.

Any idea where I should search next ?

Thanks for your help,


Vicne

Vicne

#24
Mar 18, 2015, 12:56 am Last Edit: Mar 18, 2015, 02:40 pm by Vicne
Hi,

I spent more hours digging in the cores library, to no avail.

I was starting to doubt the ATtiny2313 was using Timer 0 for millis, delays, etc. as it's using Timer1 on ATtiny85, but in the end, I placed the following code in my sketch:

#if TIMER_TO_USE_FOR_MILLIS == 0
#error
#endif

and indeed the compilation stopped, so playing with Timer1 should not interfere with delays.


I also checked the ISR definition in SoftPWM.cpp, which is ISR(SOFTPWM_TIMER_INTERRUPT).

Now I wonder : is it normal that SOFTPWM_TIMER_INTERRUPT is 0 ?

SOFTPWM_TIMER_INTERRUPT is defined as TIMER1_COMPA_vect in the section I added to SoftPWM_timer.h, and TIMER1_COMPA_vect is defined as _VECTOR(4) in avr\iotn2313.h, included by avr\io.h (because __AVR_ATtiny2313__ is defined).

I admit I don't get what _VECTOR(4) means, but its value is 0.
For Timer0, TIMER0_COMPA_vect is defined as _VECTOR(13) which also is 0...

Does that make any sense ?


Thanks for reading...


Vicne

Vicne

Hi,

I managed to produce a preprocessed output, and the PWM code for the timer1 interrupt handler is declared as follows :

Code: [Select]
extern "C" void __vector_4 (void) __attribute__ ((signal,used, externally_visible)) ; void __vector_4 (void)

{
  uint8_t i;
  int16_t newvalue;
  int16_t direction;

  (...)

}


However, I could not find preprocessed code for millis() or delay()


Does that sound correct ?

Kind regards,


Vicne

Vicne

Speaking with colleagues, I wondered what would be the result of swapping timers : trying to use Timer1 for millis and Timer0 for softPWM.

So I changed the TIMER_TO_USE_FOR_MILLIS macro to 1 in core_build_options.h and rebuilt a new project with just a led flashing based on a one second delay. That worked OK.
I then came back to fiddling with PWM by setting registers of Timer0 this time.
But again, the delay started going crazy as soon as I enabled the Timer0's comparator interrupt.
So the situation seems symmetric. I then reverted my changes.

I also checked the ISR implementation in softPWM wondering if it was the messing with the other Timer's registers but didn't see anything. In doubt, I finally removed the ISR altogether so that no code is hooked to the Timer1's comparatorA interrupt vector, but still, the delay got crazy as soon as I enabled the comparator interrupts by setting OCIE1A to 1.

I also wondered whether I was running out of RAM, so I even reduced the number of PWM to 1 to rule out that cause, but the result was still the same.

Finally, I also checked that OCIE1A is indeed 6 and that TIFR is indeed 0x39 + 0x20. That seems correct also.

I really really wonder where the issue lies. That must be something obvious, but I still fail to get it.

If you want to help a poor lonesome soul :-), feel free to step in and challenge all this, of course.

Kind regards,


Vicne

Vicne

#27
Mar 21, 2015, 10:55 am Last Edit: Mar 21, 2015, 12:10 pm by Vicne
Hi all,

Hooray :-)

I finally succeeded in making SoftPWM run on the ATtiny 2313.

First, my mistakes, for those interested:

1) The code I had blatantly copied and modified in SoftPWM_timer.h seemed to just set a few specific bits and that's it, but it's not the case. A line in the original "Timer2" initialization like this:

TCCR2B = (1 << CS21);   /* start timer (ck/8 prescalar) */

Looks like it just sets bit CS21 to 1.
But the operator is not "|=", it's an "=".
In other words, it sets CS21 to 1 but also clears bits FOC2A, FOC2B, WGM22, CS22 and CS20 that are part of the same register.
While this is a good thing, it's not clear from the comment, and made me completely overlook that when dealing with Timers on the ATtiny2313, the line :

TIMSK = (1 << OCIE1A); /* enable timer0 output compare match interrupt */

also disabled TOIE1, OCIE1B, ICIE1 bits for Timer 1, but more importantly, it disabled interrupts on Timer 0 by clearing bits OCIE0B, TOIE0, OCIE0A, as this register acts on both timers.

That explains the "PWM messes with delay()" issue I was stuck on for a few days.

2) I had wrongly assumed that the same bit names had the same functionality in different processors. While it's true for bits like OCIEnA, it's absolutely not the case for WGMxx for example: selecting CTC mode is not done by setting the same bits in WMG in all Atmel microcontrollers.


Anyway, I'm happy I found the solution, and can finally go on with my project :-).



Vicne

Vicne

#28
Mar 21, 2015, 11:06 am Last Edit: Mar 21, 2015, 11:13 am by Vicne
Now wait, where's the code ?


Well, I'm going to post it for a change ;-)


Needed changes to run SoftPWM V0005 on the ATtiny2313 (I guess it should also work on ATtiny4313 but I have no way to test) :

1) Add a section defining use of the 2313 Timer1 for PWM, and enable it for ATtinyX313 family.
(Note that I started from the version that already supports the Teensy 3.0, as suggested in post 22, so this is the "one size fits all" version).

Here is the modified file:

Code: [Select]
/* $Id: SoftPWM_timer.h 116 2010-06-28 23:31:02Z bhagman@roguerobotics.com $

  A Software PWM Library

  Simple timer abstractions by Paul Stoffregen (paul at pjrc dot com)

    This library is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

    Modified by Vicne to support AtTiny2313

*************************************************/

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

// allow certain chips to use different timers
#if defined(__AVR_ATmega32U4__)
#define USE_TIMER4_HS // Teensy 2.0 lacks timer2, but has high speed timer4 :-)
#elif defined(__arm__)
#define USE_INTERVALTIMER  // Teensy 3.0 has special interval timers :-)
#elif defined(__AVR_ATtinyX313__)
#define USE_TIMER1_X313
#else
#define USE_TIMER2
#endif

// for each timer, these macros define how to actually use it
#if defined(USE_TIMER2)
#define SOFTPWM_TIMER_INTERRUPT    TIMER2_COMPA_vect
#define SOFTPWM_TIMER_SET(val)     (TCNT2 = (val))
#define SOFTPWM_TIMER_INIT(ocr) ({\
  TIFR2 = (1 << TOV2);    /* clear interrupt flag */ \
  TCCR2B = (1 << CS21);   /* start timer (ck/8 prescalar) */ \
  TCCR2A = (1 << WGM21);  /* CTC mode */ \
  OCR2A = (ocr);          /* We want to have at least 30Hz or else it gets choppy */ \
  TIMSK2 = (1 << OCIE2A); /* enable timer2 output compare match interrupt */ \
})
#elif defined(USE_TIMER1_X313)
#define SOFTPWM_TIMER_INTERRUPT    TIMER1_COMPA_vect
#define SOFTPWM_TIMER_SET(val)     (TCNT1 = (val))
#define SOFTPWM_TIMER_INIT(ocr) ({\
  TIFR = (1 <<TOV1);                             /* clear interrupt flag (setting Timer0 flags to 0 leaves them unchanged) */ \
  TCCR1A = 0;                                    /* no compare output pin, CTC mode */ \
  TCCR1B = (1 << WGM12) | (1 << CS11);           /* no input capture, CTC mode, start timer (ck/8 prescaler) */ \
  OCR1A = (ocr);                                 /* We want to have at least 30Hz or else it gets choppy */ \
  TIMSK = (TIMSK & 0b00000111) | (1 << OCIE1A);  /* keep Timer0 bits unchanged, and enable only Timer1 output compare match A interrupt */ \
})
#elif defined(USE_TIMER4_HS)
#define SOFTPWM_TIMER_INTERRUPT    TIMER4_COMPA_vect
#define SOFTPWM_TIMER_SET(val)     (TCNT4 = (val))
#define SOFTPWM_TIMER_INIT(ocr) ({\
  TCCR4A = 0; \
  TCCR4B = 0x04; /* CTC Mode */\
  TCCR4C = 0; \
  TCCR4D  = 0; \
  TCCR4E  = 0; \
  OCR4C  = 0; \
  OCR4C  = (ocr); \
  TIMSK4  = (1 << OCIE4A); \
})
#elif defined(USE_INTERVALTIMER)
#define SOFTPWM_TIMER_INTERRUPT    softpwm_interval_timer
#ifdef ISR
#undef ISR
#endif
#define ISR(f) void f(void)
#define SOFTPWM_TIMER_SET(val)
#define SOFTPWM_TIMER_INIT(ocr) ({\
  IntervalTimer *t = new IntervalTimer(); \
  t->begin(softpwm_interval_timer, 1000000.0 / (float)(SOFTPWM_FREQ * 256)); \
})
#endif


2) Reduce the max number of pwm outputs. Due to limited RAM, the code does not compile with 20 PWM outputs. I needed 6, so I edited SoftPMW.h and replaced the SOFTPWM_MAXCHANNELS definition at the beginning by:
Code: [Select]
#if defined(__AVR_ATtiny2313__)
#define SOFTPWM_MAXCHANNELS 6
#else
#define SOFTPWM_MAXCHANNELS 20
#endif


And that's it :-)

Hope it helps anyone having the same requirements I had :-)

Kind regards,

Vicne

Go Up