Problem with global variables on ATtiny45

Hi folks,

I've run into an issue in a simple project which I just can't solve. It's really strange and I suspected a compiler bug but 99.9% a developer suspects that, the problem is actually in front of the computer... hopefully that's the case here as well.

The hardware setup looks like this: I've got an ATtiny45 on a breadboard, connected VSS, GND, MOSI, MISO, RST and SCK to a programmer. Pin 2 (PB3) is connected to 24 NeoPixels (WS2812).

Now I've got a little program to output a Knight-Rider-like effect on the NeoPixels and it works... if I use a stack-local array. Once I turn the 12-byte array into a global variable, the program doesn't work any more. By this I mean it usually doesn't even execute the "clear all pixels" of the setup() call, but only if I write to the array somewhere! Simply reading from the array works, but even a simple "kitt[0] = 0;" suffices to make the program not work any more.

So as soon as write access to the global array is made in loop(), the program doesn't work any more.

I put the source in a little Gist here.

You can see the arrays in lines 44 and 49.

I tried this using Arduino 1.8.2 on Mac with ATtiny support (GCC 4.9.2) and with Arduino 1.8.2 using GCC 4.8.1 from Arduino 1.6.9 since I suspected it to be a compiler/linker issue. Same behaviour. Also using PlatformIO using GCC 4.9.2.

When building with the stack-local array Arduino says that global variables use 44 bytes and 212 bytes remain for local variables. When using the global array, the numbers are 56 bytes and 212 bytes, like you'd expect.

So, what's going on here? I this really already a stack/heap collision? Any more ideas to investigate/fix this?

When I try to compile the code (tested on 1.8.2 and 1.6.4) for a ATtiny45 I get a compiler error, sketch too big...

Also, why do you use PB3 as value for PIN? As far as I can see the Adafruit library expects to see an Arduino pin number which is not 2 but 3. Your probably lucky here because PB3 will also be 3 here but that's generally not true so better to use the Arduino number.

Thanks for your reply. Don't know why it's too big for you, did you really select an ATtiny45 (as opposed to ATtiny25)? The compiled sketch just takes 2474 bytes of 4096 available on my system.

I'm new to Arduino and didn't know there's an "official" pin numbering scheme. Is there documentation available? A quick google didn't turn up anything. I'm used to raw Atmel programming so far. But I doubt it's coincidence that PB3 worked since:

I just tried the same project with an ATMega168 (used PD3 in the source and it did use the right pin). Here the global array does work as expected. This reinforces my suspicion that I have a heap/stack collision. Need to figure out how to actually confirm that and how to fix it…

DarkDust:
Thanks for your reply. Don't know why it's too big for you, did you really select an ATtiny45 (as opposed to ATtiny25)? The compiled sketch just takes 2474 bytes of 4096 available on my system.

Ahh, damn :smiley: I indeed forgot to switch to ATtiny45.

DarkDust:
I'm new to Arduino and didn't know there's an "official" pin numbering scheme. Is there documentation available?

For normal Arduino boards it's all over the place (and on the Arduino). For a third party core like you use it's up to the author. On the web it's a bit scarce but in the pins file it does tell you. That file is also local on your machine but I have no idea where Mac puts the third party board files...

DarkDust:
I just tried the same project with an ATMega168 (used PD3 in the source and it did use the right pin).

That's just plain luck again :wink: Or Murphy, just how you look at it. PD3 happens to be pin 3 on Arduino :smiley: But if you would try that for pin 12 (PB4)for example that would not work. It would use pin 4 instead (which is PD4). The D port is just conveniently mapped to the Arduino numbers, the rest isn't.

And are you sure this is all the code? Because in both cases you never fill kitt... So if it's local it just holds random data and if it's global it's all 0...

For the rest, I don't have a Neopixel or a ATtiny45 at the moment so I can't test. You can try to add debug statements in the code.

PS Why use the for-loop with the k variable? It does nothing :wink:

Thanks for the pointers regarding the pins, I'll use the Arduino numbers from now on.

septillion:
And are you sure this is all the code? Because in both cases you never fill kitt... So if it's local it just holds random data and if it's global it's all 0...

For the rest, I don't have a Neopixel or a ATtiny45 at the moment so I can't test. You can try to add debug statements in the code.

PS Why use the for-loop with the k variable? It does nothing :wink:

Yes, this is all the code. As a global variable, it gets initialized to all-zero. But even if it's garbage due to being allocated on the stack in there it should not crash in this particular code.

I have no idea how to do any debugging yet, as the Arduino IDE does not support debugging. All I can do is hook up an LED… and I already have 24 of them hooked up :wink: Will try to get debugging working with PlatformIO. I don't see any other way to reliably find out what's going on.

DarkDust:
I don't see any other way to reliably find out what's going on.

Mm, yeah, the normal Arduino way would be to serial print. But that's a bit harder on a ATtiny. You could debug via SPI or I2C but then you would need a device (like another Arduino) to send it somewhere you can see (like your PC).

But how do you know it crashes? The neopixels keep lit or something?

Wow, I (partly) found out what's going on!

I enhanced my hexdump code which outputs a value on the LEDs to output 16 bits. Then I used that to inspect various addresses to confirm my heap/stack-collision theory. But they addresses of stack variables and global variables where too far away; so no overwriting of a return address or anything like that.

Then I fiddled with the array lengths, dumped values and then dumped contents of memory locations around my array, which was placed at 0x66.

When I dumped the memory location 0x68 (in "loop()" because I was lazy) the hexdump went crazy! There's some counter at that address and my array overlapped with it! I don't know what that counter is yet and why it's at that address.

So I moved my global variable a bit higher:

uint8_t kitt[12] __attribute__((address (0x70)));

And now it works! Very ugly and very baffling… need to investigate more.

Mm, that is indeed weird... I don't think the compiler will do that so I suspect something is misusing a pointer or something.

So I finally really found out what's wrong.

I'm simply using too much memory. The NeoPixels library wants to malloc the memory for the LED data but malloc notices it's too likely to collide with the stack and returns 0. Thus the program doesn't crash in any way, it's just that the LEDs can't get updated since there's no data.

My array was simply pushing the heap start and thus the malloc'd region until it reached the safety margin of avr-libc malloc… which is 128 bytes. By reducing this size via the __malloc_margin variable I was able to make the program work.

Ahh, yeah, when you have little memory things do weird stuff.

You could also try FastLed. Like that library more. More advanced functions, doesn't use malloc() (not that's bad but when you do large chunks which you already know you need when compiling then yeah :/...) and HSV support.