In need of some help with my attiny software PWM chase

Go To Last Post
22 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hey folks, I am working on an attiny84 bike light, it's main feature is a cylon light fade chase. I found some code from atmel and adapted it, and I'm having a few issues I hope you can shed some light on.

The project is here:
https://github.com/blark/leetbike/blob/master/cylon.c

Here are my issues:

1. If i delete one of my variables (char wtf) the program stops working! it's the most bizarre thing ever -- everything works great when the variable is there. Even if I change it's name, it still works fine. Any ideas? I'm totally stumped! I've tried compiling on Linux and in Windows using Atmel Studio. At no point is this char used in the program... completely weird.

2. The software pwm used a volatile array hold pwm values and then one by one copied the values in to the compare array. I have switched that to a memcpy, and it seems to work fine, am I doing something stupid here?

Old code:

compare[0] = compbuff[0];   // verbose code for speed
compare[1] = compbuff[1];
compare[2] = compbuff[2];
compare[3] = compbuff[3];
compare[4] = compbuff[4];
...

New code:

memcpy(compare, led_bright, CHMAX*sizeof(int));

3. Is there any way to cut down on the repetitive #defines, say like a for loop or something? Also if I want to alter the number of channels it would be much easier if I did this.

#define LED0 PA0 // assign LED names to output ports on the attiny84
#define LED1 PA1
#define LED2 PA2
#define LED3 PA3
#define LED4 PA4
#define LED5 PA5
#define LED6 PA6

Sorry for being such a n00b, I'd REALLY appreciate your help though!

Thanks

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0
memcpy(compare, led_bright, CHMAX*sizeof(int));

The arrays doesn't contain int's, it's unsigned char's. You will overwrite what comes after compare in SRAM.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I guess

    compare[0] = led_bright[0];   // verbose code for speed
    compare[1] = led_bright[1];
    compare[2] = led_bright[2];
    compare[3] = led_bright[3];
    compare[4] = led_bright[4];
    compare[5] = led_bright[5];
    compare[6] = led_bright[6];

will be faster than

    memcpy(compare, led_bright, CHMAX*sizeof(unsigned char));

You can compare the generated assembler code in the two cases.

Edit: I get

                compare[0] = led_bright[0];   // verbose code for speed
 198:   90 91 75 00     lds     r25, 0x0075
 19c:   90 93 6e 00     sts     0x006E, r25
                compare[1] = led_bright[1];
 1a0:   90 91 76 00     lds     r25, 0x0076
 1a4:   90 93 6f 00     sts     0x006F, r25
                compare[2] = led_bright[2];
 1a8:   90 91 77 00     lds     r25, 0x0077
 1ac:   90 93 70 00     sts     0x0070, r25
                compare[3] = led_bright[3];
 1b0:   90 91 78 00     lds     r25, 0x0078
 1b4:   90 93 71 00     sts     0x0071, r25
                compare[4] = led_bright[4];
 1b8:   90 91 79 00     lds     r25, 0x0079
 1bc:   90 93 72 00     sts     0x0072, r25
                compare[5] = led_bright[5];
 1c0:   90 91 7a 00     lds     r25, 0x007A
 1c4:   90 93 73 00     sts     0x0073, r25
                compare[6] = led_bright[6];
 1c8:   90 91 7b 00     lds     r25, 0x007B
 1cc:   90 93 74 00     sts     0x0074, r25

(28 cycles) in the first case.

With memcpy (I added #include ) I get another four registers to push and pop (16 cycles) in the ISR, this setup

 1a0:   ae e6           ldi     r26, 0x6E       ; 110
 1a2:   b0 e0           ldi     r27, 0x00       ; 0
 1a4:   e5 e7           ldi     r30, 0x75       ; 117
 1a6:   f0 e0           ldi     r31, 0x00       ; 0
 1a8:   9e e0           ldi     r25, 0x07       ; 7

(10 cycles) folowed by this loop

 1aa:   01 90           ld      r0, Z+
 1ac:   0d 92           st      X+, r0
 1ae:   91 50           subi    r25, 0x01       ; 1
 1b0:   e1 f7           brne    .-8             ; 0x1aa <__vector_11+0x34>

(48 cycles) So it will surely be faster in the first case.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

It's really better that you don't call a function from an ISR anyway. Make led_bright volatile as it is shared between main and the ISR.

I was working on this. You have some quirky code (logic is not working) in main and I KNOW it is still there in my version. See if this helps you a little. It's very late here and I'm tired. I'll look at it some more in the morning.

/*
 * Author: Blark - blark at pwnp.al
 *  - some of this code is ripped from Atmel's software PWM example
 */

#define F_CPU 8000000UL

#include 
#include 
#include 
#include 

#define CHMAX 7			// CHMAX should equal the number of LEDs
#define PWMDEFAULT 0x00		// default PWM value at start up for all channels
#define CYCLEDEL 1550		// delay between each cycle of the LEDS
#define FADEDEL 10		// delay between updates to PWM

#define LED_PORT PORTA
#define LED_DDR DDRA
#define PORT_MASK  0x7F;

#define LED_CLEAR(LED) (pin_level &= ~(1 << LED))

void delay_ms(uint16_t ms);
void init();

unsigned char max_bright = 254;	// maximum brightness to go to is 254
unsigned char start_bright = 84;	// turn on the next led when we reach 1/3 of the max_bright of the previous one
unsigned char compare[CHMAX] = { 0 };	// from ATMEL PWM code

//  led_bright is shared between main and ISR, must be volatile.
volatile unsigned char led_bright[CHMAX] = { 0 };	// led brightness array holds current brightness for each led.

// global for debugging
volatile unsigned char pin_level = PORT_MASK;
volatile unsigned char softcount = 0xFF;

signed char fade_inc = 2;	// altering this value will increase/decrease fade speed
signed char led_increment[CHMAX] = { 2, 0 };	// if 0 fade is off, if positive fading up, if negative fading down

bool reverse_leds = 0;	// 0 means leds are going 0 -> CHMAX, 1 means leds are going CHMAX -> 0.

int main(void)
{
	volatile unsigned char i;

	init();

	while (1) {

		for (i = 0; i < CHMAX; i++) {

			// check if the led has reached max_bright
			// if it has, change the fade_inc to negative so it goes back down.
			if (led_bright[i] == max_bright) {
				led_increment[i] = -led_increment[i];
			}
			// the next led should start fading if the current one has reached the start_bright threshold
			// and it's fading in, two if statements one for each direction.
			if ((led_bright[i] == start_bright) && (led_increment[i] > 0) && (reverse_leds == 0)) {
				led_increment[i + 1] = fade_inc;
			}
			if ((led_bright[i] == start_bright) && (led_increment[i] > 0) && (reverse_leds)) {
				led_increment[i - 1] = fade_inc;
			}
			// if the current led brightness is 0 and the fade_inc is a negative then the led cycle
			// is complete - we need to turn it off.
			if ((led_bright[i] == 0) && (led_increment[i] < 0)) {
				// set led_increment to 0 to turn led updates off
				led_increment[i] = 0;

				// if we're at the last led, reverse the fade
				if ((i == (CHMAX - 1)) && (reverse_leds == 0)) {
					reverse_leds = 1;
					led_increment[CHMAX - 1] = fade_inc;	// set the last led to start
					delay_ms(CYCLEDEL);
				}
				// if we're at the first led, reverse the fade
				if ((i == 0) && (reverse_leds)) {
					reverse_leds = 0;
					led_increment[0] = fade_inc;	// sets the first led to start
					delay_ms(CYCLEDEL);
				}
			}
			// this is where the led_increment actually gets added
			led_bright[i] += led_increment[i];
		}

		// if this delay isn't here the leds race across so fast you can hardly see the fade.
      delay_ms(FADEDEL);
	}
}

void delay_ms(uint16_t ms)
{
	while (ms) {
		_delay_ms(1);
		ms--;
	}
}

void init(void)
{
	LED_DDR = PORT_MASK;	// set the direction of the ports
	CLKPR = (1 << CLKPCE);	// enable clock prescaler update
	CLKPR = 0;		// set clock to maximum (= crystal)
	TIFR0 = (1 << TOV0);	// clear interrupt flag
	TIMSK0 = (1 << TOIE0);	// enable overflow interrupt
	TCCR0B = (1 << CS00);	// start timer, no prescale
	sei();
}

ISR(TIM0_OVF_vect)
{
	unsigned char i;
	
	LED_PORT = pin_level;	// update outputs
	if (++softcount == 0)	// increment modulo 256 counter and update the compare values only when counter = 0.
	{
		compare[0] = led_bright[0];	// verbose code for speed 
		compare[1] = led_bright[1];
		compare[2] = led_bright[2];
		compare[3] = led_bright[3];
		compare[4] = led_bright[4];
		compare[5] = led_bright[5];
		compare[6] = led_bright[6];
		pin_level = PORT_MASK;	// set all port pins high
	}
	// clear port pin on compare match (executed on next interrupt)
	for (i = 0; i < CHMAX; i++) {
		if (compare[i] == softcount) {
			LED_CLEAR(i);
		}
	}
}
======================================================================================================================

EDIT: Replaced original logic in main while loop.

"I may make you feel but I can't make you think" - Jethro Tull - Thick As A Brick

"void transmigratus(void) {transmigratus();} // recursio infinitus" - larryvc

"It's much more practical to rely on the processing powers of the real debugger, i.e. the one between the keyboard and chair." - JW wek3

"When you arise in the morning think of what a privilege it is to be alive: to breathe, to think, to enjoy, to love." -  Marcus Aurelius

Last Edited: Thu. Jun 28, 2012 - 05:05 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Larry,

According to the manual memcpy is a function but as I showed there is no function call and it seems to be inlined (don't know how). But it's still slower than the verbose code in the original app-note. The same goes for the loop you introduced in the ISR, it will only make it slower.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

and it seems to be inlined (don't know how)

I think it must be __builtin

http://gcc.gnu.org/onlinedocs/gc...

That seems to suggest that depending on -std= used that memcpy might be amongst the builtins

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

clawson wrote:
I think it must be __builtin
Yes, that would explain it. Can be good to be aware of.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Yup confirmed, Try building with -fno-builtin and you get the full parameter setup and then a CALL to memcpy.

In fact in another thread today I noticed that sprintf() used with a format string containing only plain text leads to a call to strcpy() not sprintf(). That too is also a result of __builtin it seems.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Wow, thanks so much for all of the advice. I will be making the suggested changes and get back to you later today.

I spent about 2-3 hours last night but still can't figure out why deleting an unused variable ends up breaking the program. I will go over the logic again to see if I can find my mistakes.

Again though, thanks a lot for the help.

And just because, here is a video of the code in action -- http://www.youtube.com/watch?v=iEgVHA26BA8

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

blarkavr wrote:
but still can't figure out why deleting an unused variable ends up breaking the program.
It's probably because you used sizeof(int) instead of sizeof(char) in memcpy. It will copy twice as much as it is room for in the destination. And therefor overwrite some stuff.
Quote:
And just because, here is a video of the code in action -- http://www.youtube.com/watch?v=iEgVHA26BA8
Nice!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

snigelen wrote:
It's probably because you used sizeof(int) instead of sizeof(char) in memcpy. It will copy twice as much as it is room for in the destination. And therefor overwrite some stuff.

I don't think so, because it was doing that before I switched to memcpy -- I was using the Atmel code before, and it was still doing the same thing.. grrr.

I'm just learning how to use the debugger properly, so hopefully after this experience I'll "get it" and be the one helping ;)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

blarkavr wrote:

I don't think so, because it was doing that before I switched to memcpy -- I was using the Atmel code before, and it was still doing the same thing.. grrr.

And you are correct. I built your code, minus the memcopy, on an ATtiny2313 and it fails without that line as well. You can visually see the glitch happen on the LEDs. I will try to build it in AS4 later and see if it happens there too. Weird.

Also if you use memcopy then modify your line as:

memcpy(compare, &led_bright, CHMAX*sizeof(char));

Note the &, it will get rid of the const void* warning you should have been receiving.

Try changing CHMAX to 3 and see what happens.

"I may make you feel but I can't make you think" - Jethro Tull - Thick As A Brick

"void transmigratus(void) {transmigratus();} // recursio infinitus" - larryvc

"It's much more practical to rely on the processing powers of the real debugger, i.e. the one between the keyboard and chair." - JW wek3

"When you arise in the morning think of what a privilege it is to be alive: to breathe, to think, to enjoy, to love." -  Marcus Aurelius

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

OK. Oops. What happens when you do:

led_increment[i-1] and i = 0

Or:

led_increment[i+1] and i = CHMAX

Try:

			// the next led should start fading if the current one has reached the start_bright threshold
			// and it's fading in, two if statements one for each direction.
			if ((led_bright[i] == start_bright) && (led_increment[i] > 0) && (reverse_leds == 0)) {
				if (i < CHMAX) {
					led_increment[i + 1] = fade_inc;
				} else {
					led_increment[0] = fade_inc;
				}				
			}
			if ((led_bright[i] == start_bright) && (led_increment[i] > 0) && (reverse_leds)) {
				if (i > 0) {
					led_increment[i - 1] = fade_inc;
				} else {
					led_increment[CHMAX] = fade_inc;
				}				
			}

Still try 3 in CHMAX, see what it does.

EDIT: Made code more correct. :wink:

"I may make you feel but I can't make you think" - Jethro Tull - Thick As A Brick

"void transmigratus(void) {transmigratus();} // recursio infinitus" - larryvc

"It's much more practical to rely on the processing powers of the real debugger, i.e. the one between the keyboard and chair." - JW wek3

"When you arise in the morning think of what a privilege it is to be alive: to breathe, to think, to enjoy, to love." -  Marcus Aurelius

Last Edited: Thu. Jun 28, 2012 - 08:09 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hey guys - you are amazing. I will try this tonight when I get home. Such an awesome community! Thanks for the help.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

This is working on an ATtiny 2313, somewhat. Only minor changes are required for you to use it on the ATtiny84 again. Still some quirky code in main but the flow is clear.

/*
 * Author: Blark - blark at pwnp.al
 *  - some of this code is ripped from Atmel's software PWM example
 *  - modified by larryvc for testing on an ATtiny2313
 */

#define F_CPU 8000000UL

#include 
#include 
#include 

#define CHMAX 7             // CHMAX should equal the number of LEDs
#define PWMDEFAULT 0x00     // default PWM value at start up for all channels   NOT USED
#define CYCLEDEL 200        // ms delay between each cycle of the LEDS
#define FADEDEL 1000        // us delay between updates to PWM

#define LED0 PB0            // assign LED names to output ports on the ATtiny2313 - change to PA for ATtiny84
#define LED1 PB1
#define LED2 PB2
#define LED3 PB3
#define LED4 PB4
#define LED5 PB5
#define LED6 PB6

#define LED0_CLEAR (pin_level &= ~(1 << LED0))
#define LED1_CLEAR (pin_level &= ~(1 << LED1))
#define LED2_CLEAR (pin_level &= ~(1 << LED2))
#define LED3_CLEAR (pin_level &= ~(1 << LED3))
#define LED4_CLEAR (pin_level &= ~(1 << LED4))
#define LED5_CLEAR (pin_level &= ~(1 << LED5))
#define LED6_CLEAR (pin_level &= ~(1 << LED6))

#define PORT_MASK 0x7F;
#define LED_PORT PORTB    // change to PORTA for ATtiny84
#define LED_DDR DDRB      // change to DDRA for ATtiny84

void delay_ms(uint16_t ms);
void init(void);

// Shared between main and ISR, must be volatile.
volatile uint8_t led_bright[CHMAX] = { 0 };    // led brightness array holds current brightness for each led.

int main(void)
{
    volatile uint8_t i, flashcount = 0, j;

    const uint8_t max_bright = 254;    // maximum brightness to go to is 254
    const uint8_t start_bright = 84;   // turn on the next led when we reach 1/3 of the max_bright of the previous one

    int8_t led_increment[CHMAX] = { 2, 0 };    // if 0 fade is off, if positive fading up, if negative fading down

    //////THIS IS NOT WORKING
    int8_t fade_inc = 2;         // altering this value will increase/decrease fade speed

    // countup means LEDs are going 0 -> CHMAX, countdown means LEDs are going CHMAX -> 0.
    enum DIR { countup, countdown } led_dir = countup;

    init();

    while (1) {

        for (i = 0; i < CHMAX; i++) {

            // check if the led has reached max_bright
            // if it has, change the fade_inc to negative so it goes back down.
            if (led_bright[i] == max_bright) {
                led_increment[i] = -led_increment[i];
            }
            
            // the next led should start fading if the current one has reached the start_bright threshold
            // and it's fading in
            if ((led_bright[i] == start_bright) && (led_increment[i] > 0)) {

                if (led_dir == countup) {
                    if (i < CHMAX) {
                        led_increment[i + 1] = fade_inc;
                    } else {
                        led_increment[0] = fade_inc;
                    }
                }

                if (led_dir == countdown) {
                    if (i > 0) {
                        led_increment[i - 1] = fade_inc;
                    } else {
                        led_increment[CHMAX] = fade_inc;
                    }
                }
            }
            
            // if the current led brightness is 0 and the fade_inc is a negative then the led cycle
            // is complete - we need to turn it off.
            if ((led_bright[i] == 0) && (led_increment[i] < 0)) {
                
                // set led_increment to 0 to turn led updates off
                led_increment[i] = 0;

                // if we're at the last led, reverse the fade
                if ((i == (CHMAX - 1)) && (led_dir == countup)) {
                    led_dir = countdown;
                    led_increment[CHMAX - 1] = fade_inc;    // sets the last led to start
                    delay_ms(CYCLEDEL);
                    // Every 6 cycles flash all LEDs
                    if (flashcount++ == 5) {
                        cli();
                        LED_PORT = 0;
                        for (j = 0; j < 10; j++) {
                            delay_ms(150);
                            LED_PORT ^= 0xFF;
                        }
                        flashcount = 0;
                        sei();
                    }
                }
                
                // if we're at the first led, reverse the fade
                if ((i == 0) && (led_dir == countdown)) {
                    led_dir = countup;
                    led_increment[0] = fade_inc;    // sets the first led to start
                    delay_ms(CYCLEDEL);
                }
            }
            
            // this is where the led_increment actually gets added
            led_bright[i] += led_increment[i];
        }

        // if this delay isn't here the leds race across so fast you can hardly see the fade.
        _delay_us(FADEDEL);
    }
}

void delay_ms(uint16_t ms)
{
    while (ms) {
        _delay_ms(1);
        ms--;
    }
}

void init(void)
{
    LED_DDR = PORT_MASK;      // set the direction of the ports
    CLKPR = (1 << CLKPCE);    // enable clock prescaler update
    CLKPR = 0;                // set clock to maximum (= crystal)
    TIFR = (1 << TOV0);       // clear interrupt flag - change to TIFR0 for ATtiny84
    TIMSK = (1 << TOIE0);     // enable overflow interrupt - change to TIMSK0 for ATtiny84
    TCCR0B = (1 << CS00);     // start timer, no prescale
    sei();
}

ISR(TIMER0_OVF_vect)    // change to TIM0_OVF_vect for ATtiny84
{
    static uint8_t compare[CHMAX] = { 0 };
    static uint8_t pin_level = PORT_MASK;
    static uint8_t softcount = 0xFF;

    LED_PORT = pin_level;    // update outputs
    
    if (++softcount == 0)    // increment modulo 256 counter and update the compare values only when counter = 0.
    {
        compare[0] = led_bright[0];    // verbose code for speed
        compare[1] = led_bright[1];
        compare[2] = led_bright[2];
        compare[3] = led_bright[3];
        compare[4] = led_bright[4];
        compare[5] = led_bright[5];
        compare[6] = led_bright[6];
        pin_level = PORT_MASK;    // set all port pins high
    }
    
    // clear port pin on compare match (executed on next interrupt)
    if(compare[0] == softcount) { LED0_CLEAR; }
    if(compare[1] == softcount) { LED1_CLEAR; }
    if(compare[2] == softcount) { LED2_CLEAR; }
    if(compare[3] == softcount) { LED3_CLEAR; }
    if(compare[4] == softcount) { LED4_CLEAR; }
    if(compare[5] == softcount) { LED5_CLEAR; }
    if(compare[6] == softcount) { LED6_CLEAR; }
}

"I may make you feel but I can't make you think" - Jethro Tull - Thick As A Brick

"void transmigratus(void) {transmigratus();} // recursio infinitus" - larryvc

"It's much more practical to rely on the processing powers of the real debugger, i.e. the one between the keyboard and chair." - JW wek3

"When you arise in the morning think of what a privilege it is to be alive: to breathe, to think, to enjoy, to love." -  Marcus Aurelius

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Bah! You found the bug! I didn't put a bounds check on the start_brightness check, so it tried to fade in a LED that didn't exist! DOH! I fixed that in my code and it works perfectly now. Thank you so much for the help, now to add some more modes. ;)

Time to add a control button and a few more modes now. And get this puppy on to a protoboard!

I'll likely have some more questions if you don't mind :)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

OK, first question already.

- When adding additional modes do you think I should use the software PWM to update the LEDs? Or will this use more batteries? I'm a noob here, so I'm not exactly sure how ISR works.

- Any advice on putting the chip to sleep and waking it up? Is there a good noob reference somewhere?

Thanks

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The ISR in your program is tied specifically to the code in main. Adding more modes may be difficult to do based on the design of the program.

What modes were you thinking of adding?

I'm not up to speed on the sleep modes of AVRs, someone else can help you with that.

"I may make you feel but I can't make you think" - Jethro Tull - Thick As A Brick

"void transmigratus(void) {transmigratus();} // recursio infinitus" - larryvc

"It's much more practical to rely on the processing powers of the real debugger, i.e. the one between the keyboard and chair." - JW wek3

"When you arise in the morning think of what a privilege it is to be alive: to breathe, to think, to enjoy, to love." -  Marcus Aurelius

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

What happens to your LEDs, when you, change fade_inc to 4, 8, 16...? Not a trick question, fade_inc does not work for me except at values 1 and 2.

"I may make you feel but I can't make you think" - Jethro Tull - Thick As A Brick

"void transmigratus(void) {transmigratus();} // recursio infinitus" - larryvc

"It's much more practical to rely on the processing powers of the real debugger, i.e. the one between the keyboard and chair." - JW wek3

"When you arise in the morning think of what a privilege it is to be alive: to breathe, to think, to enjoy, to love." -  Marcus Aurelius

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

larryvc wrote:
What happens to your LEDs, when you, change fade_inc to 4, 8, 16...? Not a trick question, fade_inc does not work for me except at values 1 and 2.

larry, take a look at the new code I uploaded to github... my idea for how to add different modes is there, and it's working so far.

the reason that 4 .. 8 .. and 16 don't work is because they don't add up to 254 so if you change the fade_inc you also need to change the max_bright to a number divisible by that number.

that explains why 1 and 2 work -- because they both eventually add up to 254...

again, thanks for all the help!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Do you have a link to the original Atmel software that you note in your source?

"I may make you feel but I can't make you think" - Jethro Tull - Thick As A Brick

"void transmigratus(void) {transmigratus();} // recursio infinitus" - larryvc

"It's much more practical to rely on the processing powers of the real debugger, i.e. the one between the keyboard and chair." - JW wek3

"When you arise in the morning think of what a privilege it is to be alive: to breathe, to think, to enjoy, to love." -  Marcus Aurelius

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

larryvc wrote:
Do you have a link to the original Atmel software that you note in your source?

http://jaywiggins.com/2011/05/at...

Thats the code I used...