Virtual functions implement runtime polymorphism. This is great when you have parts of the code that don't know exactly what the end requirement is. For example, a user in a drawing program can choose to draw a triangle, or draw a circle, in a drawing program. Or, in a PC, a user may plug in some Bose USB speakers, or a Logitech headset, each of which has different volume controls.
For AVR code, however, pretty much everything is statically knowable. You don't generally hot-plug hardware devices, nor implement large and varying desktop applications in a microcontroller based system, so you generally don't need runtime polymorphism -- if you have an elaborate menu system, you might make use of it, but for hardware, I don't think it's important.
If your use case is "I want to support building code for a wide variety of circuits," then it's more important to support the same static API. This means that you can "swap out" the constructed type at compile time, and still have code work the same. If you need easy variability for the runtime type (for advanced memory management or whatever -- unlikely on an AVR) then you can look into using templates.
My suggestion: Make sure the classes all have the same API, but it doesn't sound as if you actually NEED the base class/virtuals. And there is an actual cost to calling a virtual function.