Crash when I call sprintf-filled loop multiple times.

First time posting. I am trying to write two number characters and one letter on a seven segment display.
I found out about sprintf() and it worked just fine. Until I wrote a loop that handles floats and moves the decimal point. The function is set up to receive a float and a letter and it will write (number)(number)(letter) to the display with the decimal point somewhere to show what you’re looking at.
The function works fine and all but when I put it twice in the code, it crashes upon getting to that part of the code.

I have a button that cycles through the modes and switch case that reacts to the modes.
I have no idea what is wrong and why I can’t get it to work. I am completely lost now. I tried clearing the SRAM right after but that doesn’t help either.

It seens like whenever there are few more sprintf() things, the Arduino dies.

Here is the whole code:

#include <SevSeg.h> //https://github.com/DeanIsMe/SevSeg
#include <FastLED.h> //https://github.com/FastLED/FastLED

#define SelectButton 2
#define DispB 3
#define LEDData 4
#define Dig3 5
#define Dig2 6
#define DispG 7
#define DispC 8
#define DispF 9
#define DispDP 12
#define PlayPauseButton 13
#define DispD 14
#define DispE 15
#define DispA 16
#define Dig1 17
#define CurrentPot 21
#define VoltagePot 20

bool buttonsPrev[3];
uint8_t dispMode;
float setCurrent;
float setVoltage;
float realCurrent = 6.9;
float realVoltage = 42;
float timeHours = 0.75;
float timeMinutes = 45;
float realTemperature = 35.5;

#define NUM_LEDS 2
CRGB leds[NUM_LEDS];

SevSeg sevseg;

//CLEAR-MEMORY-WHAT-AM-I-DOING-IDK!!!--begin
#ifdef __arm__
// should use uinstd.h to define sbrk but Due causes a conflict
extern "C" char* sbrk(int incr);
#else  // __ARM__
extern char *__brkval;
#endif  // __arm__

int freeMemory() {
  char top;
#ifdef __arm__
  return &top - reinterpret_cast<char*>(sbrk(0));
#elif defined(CORE_TEENSY) || (ARDUINO > 103 && ARDUINO != 151)
  return &top - __brkval;
#else  // __arm__
  return __brkval ? &top - __brkval : &top - __malloc_heap_start;
#endif  // __arm__
}
//CLEAR-MEMORY-WHAT-AM-I-DOING-IDK!!!--end

void setup() {
  FastLED.addLeds<WS2812B, LEDData, GRB>(leds, NUM_LEDS);
  FastLED.setBrightness(10);

  byte numDigits = 3;
  byte digitPins[] = {Dig1, Dig2, Dig3};
  byte segmentPins[] = {DispA, DispB, DispC, DispD, DispE, DispF, DispG, DispDP};
  sevseg.begin(COMMON_ANODE, numDigits, digitPins, segmentPins, false, false, false, false);
  sevseg.setBrightness(10);

  pinMode(SelectButton, INPUT_PULLUP);
  pinMode(PlayPauseButton, INPUT_PULLUP);
}

void displayNumberLetter(float number, char letter) {
  char buf[4];
  if (number < 1) sprintf(buf, "%d%c",(int)(number * 100.0) % 100, letter); // combining the number and the letter (it cant handle floats so I have to separate them)
  else if (number < 10) sprintf(buf, "%d.%d%c", (int) number, (int)(number * 10.0) % 10, letter); // combining the number and the letter (it cant handle floats so I have to separate them)
  else if (number >= 10) sprintf(buf, "%d.%c", (int) number, letter);
  //nwm this doesnt help freeMemory();//It seems like the the sprintf is a memory hoarder and it crashed the arduino when it ran multiple times
  sevseg.setChars(buf);
}

void buttonsCheck() {
  bool buttonsState[3];
  buttonsState[0] = !digitalRead(SelectButton);
  buttonsState[1] = !digitalRead(PlayPauseButton);
  if (buttonsState[0] != buttonsPrev[0]) {
    buttonsPrev[0] = buttonsState[0];
    if (buttonsState[0]) {
      if (dispMode < 4) dispMode++;
      else dispMode = 0;
    }
  }
}

void loop() {
  buttonsCheck();
  switch (dispMode) {
    case 0:
      displayNumberLetter(realCurrent, 'A');
      leds[0] = 0x813afc;
      break;
    case 1:
      displayNumberLetter(realVoltage, 'U');
      leds[0] = 0x3acffc;
      break;
    case 2:
      displayNumberLetter(timeHours, 'H');
      leds[0] = 0xf59e42;
      break;
    case 3:
      displayNumberLetter(timeMinutes, 'N');
      leds[0] = 0xf5d142;
      break;
    case 4:
      displayNumberLetter(realTemperature, '*');
      leds[0] = 0xff0000;
      break;
  }
  FastLED.show();
  sevseg.refreshDisplay();
}

buf is too short.

Use snprintf(), which won't write outside of your buffer.

The "n" versions of the C-string routines are all highly recommended (strncat, strncpy etc.).

This will accommodate a 3-character C-string.

  char buf[4];

jremington:
The "n" versions of the C-string routines are all highly recommended (strncat, strncpy etc.).

They are not. A zero-terminator is not guaranteed: strncpy - C++ Reference

I and many others highly recommend them, so they are, indeed, highly recommended.

Your cautionary tale is duly noted.

Thank you all so much. I thought if I had a three digit display I would need only 3characters but I really needed to care about the code and not the hardware. It is now working well.

Full code that works:

#include <SevSeg.h> //https://github.com/DeanIsMe/SevSeg
#include <FastLED.h> //https://github.com/FastLED/FastLED

#define SelectButton 2
#define DispB 3
#define LEDData 4
#define Dig3 5
#define Dig2 6
#define DispG 7
#define DispC 8
#define DispF 9
#define DispDP 12
#define PlayPauseButton 13
#define DispD 14
#define DispE 15
#define DispA 16
#define Dig1 17
#define CurrentPot 21
#define VoltagePot 20

bool buttonsPrev[3];
uint8_t dispMode;
float setCurrent;
float setVoltage;
float realCurrent = 6.9;
float realVoltage = 42;
float timeHours = 0.75;
float timeMinutes = 45;
float realTemperature = 35.5;

#define NUM_LEDS 2
CRGB leds[NUM_LEDS];

SevSeg sevseg;

void setup() {
  FastLED.addLeds<WS2812B, LEDData, GRB>(leds, NUM_LEDS);
  FastLED.setBrightness(10);

  byte numDigits = 3;
  byte digitPins[] = {Dig1, Dig2, Dig3};
  byte segmentPins[] = {DispA, DispB, DispC, DispD, DispE, DispF, DispG, DispDP};
  sevseg.begin(COMMON_ANODE, numDigits, digitPins, segmentPins, false, false, false, false);
  sevseg.setBrightness(10);

  pinMode(SelectButton, INPUT_PULLUP);
  pinMode(PlayPauseButton, INPUT_PULLUP);
}

void displayNumberLetter(float number, char letter) {
  char buf[5];
  if (number < 1) sprintf(buf, "%d%c",(int)(number * 100.0) % 100, letter); // combining the number and the letter (it cant handle floats so I have to separate them)
  else if (number < 10) sprintf(buf, "%d.%d%c", (int) number, (int)(number * 10.0) % 10, letter); // combining the number and the letter (it cant handle floats so I have to separate them)
  else if (number >= 10) sprintf(buf, "%d.%c", (int) number, letter);
  sevseg.setChars(buf);
}

void buttonsCheck() {
  bool buttonsState[3];
  buttonsState[0] = !digitalRead(SelectButton);
  buttonsState[1] = !digitalRead(PlayPauseButton);
  if (buttonsState[0] != buttonsPrev[0]) {
    buttonsPrev[0] = buttonsState[0];
    if (buttonsState[0]) {
      if (dispMode < 4) dispMode++;
      else dispMode = 0;
    }
  }
}

void loop() {
  buttonsCheck();
  switch (dispMode) {
    case 0:
      displayNumberLetter(realCurrent, 'A');
      leds[0] = 0x813afc;
      break;
    case 1:
      displayNumberLetter(realVoltage, 'U');
      leds[0] = 0x3acffc;
      break;
    case 2:
      displayNumberLetter(timeHours, 'H');
      leds[0] = 0xf59e42;
      break;
    case 3:
      displayNumberLetter(timeMinutes, 'N');
      leds[0] = 0xf5d142;
      break;
    case 4:
      displayNumberLetter(realTemperature, '*');
      leds[0] = 0xff0000;
      break;
  }
  FastLED.show();
  sevseg.refreshDisplay();
}

@jremington As far as I know, the 'strlcpy' wins from 'strncpy'. But then there is 'strlcat' which can behave in an other way then expected.

This is one of the many explanations: c - strncpy or strlcpy in my case - Stack Overflow.

I have changed 'strncpy' to 'strlcpy' in all my projects.

@machmar, Please make it larger than 'char buf[5]'. When you need 5 characters, then make the buffer size 20 or so. When using 'sprintf()' it is never okay to try to exactly make the buffer fit. That will go wrong because when there is a bug, the integer could have a larger value, for example 30000. A bug in your sketch or a wrong input should not cause the stack go wrong. Even with "%3d", the output will not always be 3 characters, because "%3d" specifies a minimum size.