1
\$\begingroup\$

I have a program written in C. I have 390 byte array defined and a long string value consisting of around 475 bytes. Now I hit a problem if I add more characters to the string value, around 30 bytes, the chip starts to act weird and glitch out. Even if I define a new separate variable, the same thing happens, so it's not code related.

I checked the avr-size and it's returning that I still have a lot of memory left:

avr-size -C --mcu=atmega328p test.o
AVR Memory Usage
----------------
Device: atmega328p

Program:    2079 bytes (6.3% Full)
(.text + .data + .bootloader)

Data:        907 bytes (44.3% Full)
(.data + .bss + .noinit)

I suspected maybe I had knock-off chips, so I purchased few original ones from local electronics store, but the same exact issue occurs.

The tech specs state that atmega has 2048 bytes of RAM, my string and array together take up about half of that as reported by avr-size. What could be the reason why this is happening? Thanks!

\$\endgroup\$
7
  • 2
    \$\begingroup\$ Stack overflow? \$\endgroup\$
    – Dave Tweed
    Commented Dec 20, 2023 at 12:21
  • 1
    \$\begingroup\$ Can you edit the question to include the code which shows the issue? \$\endgroup\$ Commented Dec 20, 2023 at 12:26
  • 2
    \$\begingroup\$ It is impossible to answer what could be the problem in a program whose code is nowhere to be read. So it can be anything in the code or how the code is built. Show the code. Otherwise we can only list everything that could be the reason, and that would be a long list. \$\endgroup\$
    – Justme
    Commented Dec 20, 2023 at 12:30
  • 1
    \$\begingroup\$ Lots of print statements without using the F macro can eat up RAM. Serial.print(F("my long string ...")); is good. Serial.print("my long string ..."); is bad. Automatic (locally defined) variables do not show up in the compile time RAM usage statistics because the amount of stack used depends on the run time behaviour of your code. Strings ( arduino String class) can make big holes in the heap. Let's see the code. \$\endgroup\$
    – 6v6gt
    Commented Dec 20, 2023 at 12:48
  • \$\begingroup\$ It's also highly recommended to use a modern MCU instead, so that there's actually a half-decent amount of RAM available and not a 2kib pittance as if we are still in the 1990s. Welcome to the current millenium, we have 32 bitters waiting for you here :) \$\endgroup\$
    – Lundin
    Commented Dec 20, 2023 at 12:59

3 Answers 3

4
\$\begingroup\$

Check out my answer below What resides in the different memory types of a microcontroller? The linker is able to diagnose the memory use of flash (.text + .rodata) and static RAM (.data + .bss) at link-time and will tell you if you run out of them.

It is however not able to do that with the stack. Some tool chains can assist in predicting stack use, but it mostly the programmer's duty to be aware of stack overflows and keeping the program safe from them.

Now I hit a problem if I add more characters to the string value, around 30 bytes, the chip starts to act weird and glitch out.

That very likely means that you have a stack overflow, since it happened at run-time and not link-time.

Other less likely possibilities are other forms of latent severe bugs, out-of-bounds access, pointer bugs, runaway code etc.

Also in case of Arduino tool chains, there's all manner of newbie mistakes included per design, such as heap allocation, C++, software floating point libraries etc etc - none which should be used on microcontrollers in general or AVR in particular.

\$\endgroup\$
2
  • \$\begingroup\$ Thank you very much for your answer. The result seemed like something like stack overflow, but it turned out not to be fault with my code, but the way I use includes. I posted my answer, if you have a time to take a look. Thanks! \$\endgroup\$
    – 0x29a
    Commented Dec 20, 2023 at 15:31
  • \$\begingroup\$ Thanks, at the end it looks like stack overflow. If I comment out one function, then I can define long string just fine and there's no issue. Thanks a lot for clearing that up! \$\endgroup\$
    – 0x29a
    Commented Dec 20, 2023 at 16:47
1
\$\begingroup\$

I hit a problem if I add more characters to the string value, around 30 bytes, the chip starts to act weird and glitch out. Even if I define a new separate variable, the same thing happens, so it's not code related.

I guess you're using String():

   const char *mystring = "foo";
   String s(mystring);

or

   String s("foo");

String class is dynamically allocated, so the first thing it does is allocate a chunk of memory to hold the string plus header. So that consumes about 485 bytes, you have 656 bytes of RAM left.

Now you append characters (presumably with "+=") so it tries to expand the String buffer. If there's free memory after the end of the string, that gets used. However if you allocated another dynamic variable that got stored right after the end of the String buffer in memory, then it can't be expanded and has to be allocated somewhere else. That's probably what's happening, so it makes a copy: you have 171 bytes of RAM left.

If you add other stuff like stack, temporary variables, etc, that's not going to fit, so it runs out: the end of allocated memory (heap) which grows towards increasing addresses, and the stack which grows in the other direction, end up overwriting each other and it crashes.

Basically if you have 2K of RAM, it's not possible to do stuff that will make temporary copies of "large" objects like your string. In fact, using string concatenation on arduino is already asking for trouble, due to memory fragmentation.

Now...

AVR is a Harvard architecture CPU with separate instruction and data busses (I-bus and D-bus). When accessing data like your string, it does so on the D-bus, and when fetching instructions it uses the I-bus. This allows accessing both busses at the same time, which is faster.

A different example is Von Neumann architecture, which uses a single shared bus for everything, as in the Z80, 68000, etc.

This means AVR chips have both Harvard CPU architecture and Harvard memory architecture, keeping program and data separate. ARM chips on the other hand have Harvard CPU architecture (multiple busses) but there's usually a switch matrix that allows any CPU bus to access any memory, RAM, Flash, peripherals, etc.

In the AVR chips, Flash is connected to the I-bus and everything else like RAM, peripherals, EEPROM, etc to the D-bus. There is no switching fabric nor multiplexers between the two, so any code that manipulates data requires the data to be accessible from the data bus, which usually means it must be in RAM.

Obviously when you have 64K of Flash but only 2K of RAM, this is quite a limitation, so the AVR provides a LPM instruction which can load data stored into program memory into a register. This is presented as the PROGMEM gcc attribute.

So you can put your initialization string into PROGMEM...

...but it will be copied into RAM upon first use.

\$\endgroup\$
5
  • \$\begingroup\$ Thank you very much for the answer, I solved it and posted an answer, but I'm not yet clear why it works now. If you have a change, could you please take a look at my answer? Thanks! \$\endgroup\$
    – 0x29a
    Commented Dec 20, 2023 at 15:29
  • \$\begingroup\$ If the problem is heap allocation madness, then that can be easily verified by single stepping the String constructor and see what the internal pointers are set at after the new[] call. As for the solution, it is to take the f-in' C++ out of the AVR... it's like pulling a 30 ton trailer using a VW Beetle from 1950. Like... the extremely wrong tool for the task. \$\endgroup\$
    – Lundin
    Commented Dec 20, 2023 at 15:45
  • 1
    \$\begingroup\$ C++ is nice for small micros, using only basic reasonable features it works fine and is convenient... However... dynamic allocated strings on 2kB RAM are definitely not part of "basic reasonable features". But yeah that's the arduino way! As the docs say, "If you get too much memory fragmentation, schedule a periodic reboot." XDDD \$\endgroup\$
    – bobflux
    Commented Dec 20, 2023 at 18:18
  • \$\begingroup\$ @0x29a you should post your code (the one that crashes) \$\endgroup\$
    – bobflux
    Commented Dec 20, 2023 at 18:36
  • \$\begingroup\$ @bobflux The thing is that 90% of all C++ programmers don't know which features to avoid and which ones to use. And 100% of all C++ programmers (I don't exaggerate) don't know how much of the language that is poorly-defined behavior and how much that is actually guaranteed behavior by the standard. It's not a language suitable for anything slightly mission-critical ("this program can't be allowed to crash and burn mysteriously"), let alone microcontroller programming. \$\endgroup\$
    – Lundin
    Commented Dec 21, 2023 at 7:24
0
\$\begingroup\$

I probably wrote cached hex on the avr, this answer is incorrect. I left this answer because there are useful comments under it.

\$\endgroup\$
10
  • 1
    \$\begingroup\$ This explanation doesn't make any sense. It wouldn't make any difference for the linker if you placed this declaration in a header or a .c file, since the pre-processor would expand it to be placed into the .c file anyway as per #include evaluation. More likely you have other variables allocated in the same segment .data as this one, and when you increase the space allocated by message, you just push those other critical variables closer to where your stack overflow is trashing the RAM. So the problem is still most likely a stack overflow. \$\endgroup\$
    – Lundin
    Commented Dec 20, 2023 at 15:39
  • 1
    \$\begingroup\$ Inspect the .map file and see which variables that are closest to the stack. \$\endgroup\$
    – Lundin
    Commented Dec 20, 2023 at 15:39
  • 1
    \$\begingroup\$ Overall you are confusing symptoms for the root cause. If you change the size of some unrelated variable and that exposes/hides the problem, then that doesn't mean you have found the root cause. If the headlights of your car are broken, the root cause isn't that you are driving at night, nor is the solution to the actual problem to only drive when the sun is up... \$\endgroup\$
    – Lundin
    Commented Dec 20, 2023 at 15:42
  • 1
    \$\begingroup\$ That can't make any difference. At least not if you include the header only once in one .c file, but the variable is not static so you would get a linker error about multiple definitions for a variable. You are not supposed to declare variables in a header anyway. \$\endgroup\$
    – Justme
    Commented Dec 20, 2023 at 15:42
  • 1
    \$\begingroup\$ @0x29a What you actually should do is this old school stack diagnostics trick: upon loading the program, break in the CRT before main() is called. Use the debugger to fill the whole memory area of the stack with some magic number like 0xAA. Then run and wait for the crash. Break in your debugger, then inspect the stack to see how many 0xAA remains of the stack, if any. \$\endgroup\$
    – Lundin
    Commented Dec 20, 2023 at 15:48

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.