...and is it possible to trigger an interrupt when the amount of free memory dips below some threshold without peppering your code and libraries with function calls such as FreeRam() from https://github.com/mpflaga/Arduino-MemoryFree
Nope — there is no background daemon looking at that ➜ out of RAM = crash or bugs (some libraries will just not do what's expected from them when the ram request cannot be fulfilled).
if you depend on dynamic memory allocation to a point there is a risk of stack overflow / heap colliding with the stack then you should implement mitigation techniques like check allocations are successful etc. Ideally you should do that all the time as a good practice and limit the use of dynamic allocations on small micro-controllers
Generally speaking, is the ability to detect a heap/stack collision a hardware feature that some microcontroller families have, or is it an operating system feature, or a combination of both?
Neither, It is not done in hardware and microcontroller's do not have operating system's. There are what is called RTOS(real time operating sytem's) for microcontroller's i.e. FreeRTOS that I am not real familiar with but I'm pretty sure none of them have this feature.
As J-M-L said you have to implement this within the code yourself.
After more research I found that the ARM core M0+ used in the SAMD21 doesn't have any heap/stack collision detection hardware, however the older SAM3X8E used in the Ardunio Due uses a ARM Core M3 series chip that does have a memory protection unit.
You would still need either some sort of OS managing the stacks or a compiler that is aware of your needs to allow for a gap area between the heap and stack so that a write in that area triggers the access fault. That somewhat defeat the flexibility of having a dynamic region where the lower/upper bounds are not set in stone
Well, yes, you need to have a good idea of the maximum expected heap size.
In my code I create all the heap objects before the main loop starts. After that it's just the stack growing and shrinking. Of course I don't know what shenanigans the libraries I'm using are getting up to. I guess you would pick a number and see if the MPU triggers, then adjust accordingly.
if you know where the heap ends and you don't have dynamic allocations, you could probably write a bit of code to instrument / measure what happens to the stack through normal use. that would give you an idea if you are at risk or not