In my experience there is little difference between implementing UART RX or TX via bit-banging.
UART is the hardest protocol to implement via bit-banging because it is very time sensitive.
SPI and I2C allow clock stretching, but in UART every bit has to be sent or read at the a precise time to avoid corruption.
Interrupts, pipeline stalls and cache misses may screw your timing and cause corruption.
The most reliable way to implement UART via bit-banging is to disable interrupts and sit in a tight loop.
For TX the loop will poll some high precision timer, like DWT_CYCCNT, then toggle GPIO at required times.
For RX the loop will do the same, except it will read GPIO at required times.
TX has one advantage in this case: it can re-enable interrupts between bytes, but RX has to stay in the reading loop forever.
But keeping interrupts disabled for such a long time is a very bad idea.
Your system will not be able to do anything else other than reading/writing UART.
You may try to run RX/TX loop with interrupts enabled and then, once in a while, an interrupt will happen and that will result in a corrupted byte.
When sending, you may detect such bad cases (by measuring time that it took to send entire byte).
If that time exceeds an expected time, you know that you were interrupted and then can resend the entire packet.
You will need to design a protocol to detect corruption.
On the Web I see samples that implement UART via interrupts. For TX they use a timer interrupt and write bits from ISR.
For RX they enable edge detect interrupt and record the time in ISR.
But that approach can fail in so many ways: other interrupts happening, code executing with interrupts disabled, etc.
But all of the above is not needed since you are using STM32.
On STM32 you can use timers together with DMA to simulate UART.
For example:
"Implementing an emulated UART on STM32F4 microcontrollers"
https://www.st.com/content/ccc/resource/technical/document/application_note/1d/61/52/64/ea/ee/42/4e/DM00110292.pdf/files/DM00110292.pdf/jcr:content/translations/en.DM00110292.pdf
Here they use timer to drive DMA to send/receive bits at precise moments of time.
There is another way: configure timer capture/compare channels and attach DMA.
For RX: timer will capture exact moments of time when input signal was toggled and DMA will save these time values to a buffer.
For TX: DMA will read time values from a buffer and timer will toggle output at these moments.
Personally I did not try that latest approach yet, but I did something similar, so this should work as well.