When you are first getting started with creating abstractions, it can be difficult to know exactly what should be abstracted versus what should be used directly. To make code fully reusable, a module should only perform one function and reference interfaces for the other pieces of functionality. Any hardware-specific calls must go through interfaces, rather than deal with the hardware directly. This is true for accessing actual hardware (such as specific pins) and also MCU-specific APIs (such as STM32 HAL).
Writing reusable code
Writing reusable drivers
There are a few different levels of drivers that are fairly common in embedded development. MCU peripheral drivers are the drivers used to provide a convenient API to hardware...