| I have the same experience, I like splitting my embedded C microcontroller peripheral drivers into 3 layers: - header files with registers addresses and bitmasks - asynchronous layer that starts transactions or checks transaction state or register interrupt handler called when transaction changes states - top, RTOS primitives powered, blocking layer which encapsulates synchronization problems and for example for UART offers super handy API like this: status uart_init(int id, int baudrate) status uart_write(int id, uint8_t* data, int data_len, int timeout_ms) status uart_read(int id, uint8_t* buf, int buf_len, int timeout_ms, int timeout_char_ms) Top, blocking API usually covers 95% use cases where business logic code just want to send and receive something and not reinvent the synchronization hell |