First of all, are you getting and ignoring any compiler warning?
Happens all the time, people come here ranting, but say nothing about the "integer overflow" or "variable may be undefined" warnings, pretending they didn't happen.
Are you running dynamic alocations at run time? I mean, not only at the system init, but constantly in the runtime, "alloc this buffer, free this buffer...". If so, I hope you're always checking for valid pointer!
Also make sure the reserved stack is properly set by checking the stack analyzer on the IDE, otherwise the stack and heap might crash!
Do you hate state machines? Just do them right. Do you think a million if/else is a better solution?
"switch (state)" make things so much better, but if you overlook something, it'll happen just like any other line of code in any language, you create a bug, not state machine's fault.
Implement a proper crash handler, make variable checks, timeouts everywhere, even if you do "x=1+1", asume nothing, you some memory areas could be overwritten by a wrong pointer, etc.
If anything goes wrong, enter crash handler and debug where it happened, this will greatly ease the error detection.
Did so in the soldering FW as it grew up, catching the bugs had become really hard, made it much easier.
However ST removed the extended error handler, replacing it with one with no arguments. Fix here:
https://community.st.com/s/question/0D50X00009XkffVSAR/stm32cubemx-v421-errorhandler-definition-issues-in-mainhAdd to main.h:
#define GET_MACRO( _0, _1, NAME, ... ) NAME
#define Error_Handler(...) GET_MACRO( _0, ##__VA_ARGS__, Error_Handler1, Error_Handler0 )()
#define Error_Handler0() _Error_Handler( __FILE__, __LINE__ )
#define Error_Handler1(unused) _Error_Handler( char * file, int line )
void _Error_Handler(char *, int);
Then simply call "Error_Handler()" when something goes wrong, it will be "translated" by the macros as "Error_Handler( __FILE__, __LINE__ )"
The error handler is placed at the end of main.c:
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* USER CODE END Error_Handler_Debug */
}
The macros will silently modify it to:
void Error_Handler( char * file, int line );
{
/* USER CODE BEGIN Error_Handler_Debug */
/* USER CODE END Error_Handler_Debug */
}
So you can report the crash somehow:
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
printf("\n\n SYSTEM CRASH!\n\n FILE: %s\n LINE: %d\n", file, line);
while(1);
)
/* USER CODE END Error_Handler_Debug */
}
The output will be something like:
SYSTEM CRASH!
FILE: /core/src/some_file.c
LINE: 137
You could modify it to add further debug information, like passing variable names, values...
Having the file and line is already a great start point to look for the issue.