diff --git a/stmhal/uart.c b/stmhal/uart.c
index 8b72327c24fe1836de5214f8570f2c141d547c39..d96c22850f426ed5b728adbb49f277d8f6be630e 100644
--- a/stmhal/uart.c
+++ b/stmhal/uart.c
@@ -331,13 +331,37 @@ int uart_rx_char(pyb_uart_obj_t *self) {
     }
 }
 
+// Waits at most timeout milliseconds for TX register to become empty.
+// Returns true if can write, false if can't.
+STATIC bool uart_tx_wait(pyb_uart_obj_t *self, uint32_t timeout) {
+    uint32_t start = HAL_GetTick();
+    for (;;) {
+        if (__HAL_UART_GET_FLAG(&self->uart, UART_FLAG_TXE)) {
+            return true; // tx register is empty
+        }
+        if (HAL_GetTick() - start >= timeout) {
+            return false; // timeout
+        }
+        __WFI();
+    }
+}
+
+STATIC HAL_StatusTypeDef uart_tx_data(pyb_uart_obj_t *self, uint8_t *data, uint16_t len) {
+    // The timeout specified here is for waiting for the TX data register to
+    // become empty (ie between chars), as well as for the final char to be
+    // completely transferred.  The default value for timeout_char is long
+    // enough for 1 char, but we need to double it to wait for the last char
+    // to be transferred to the data register, and then to be transmitted.
+    return HAL_UART_Transmit(&self->uart, data, len, 2 * self->timeout_char);
+}
+
 STATIC void uart_tx_char(pyb_uart_obj_t *uart_obj, int c) {
     uint8_t ch = c;
-    HAL_UART_Transmit(&uart_obj->uart, &ch, 1, uart_obj->timeout);
+    uart_tx_data(uart_obj, &ch, 1);
 }
 
 void uart_tx_strn(pyb_uart_obj_t *uart_obj, const char *str, uint len) {
-    HAL_UART_Transmit(&uart_obj->uart, (uint8_t*)str, len, uart_obj->timeout);
+    uart_tx_data(uart_obj, (uint8_t*)str, len);
 }
 
 void uart_tx_strn_cooked(pyb_uart_obj_t *uart_obj, const char *str, uint len) {
@@ -480,9 +504,16 @@ STATIC mp_obj_t pyb_uart_init_helper(pyb_uart_obj_t *self, mp_uint_t n_args, con
         nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_ValueError, "UART(%d) does not exist", self->uart_id));
     }
 
-    // set timeouts
+    // set timeout
     self->timeout = args[5].u_int;
+
+    // set timeout_char
+    // make sure it is at least as long as a whole character (13 bits to be safe)
     self->timeout_char = args[6].u_int;
+    uint32_t min_timeout_char = 13000 / init->BaudRate + 1;
+    if (self->timeout_char < min_timeout_char) {
+        self->timeout_char = min_timeout_char;
+    }
 
     // setup the read buffer
     m_del(byte, self->read_buf, self->read_buf_len << self->char_width);
@@ -689,8 +720,13 @@ STATIC mp_obj_t pyb_uart_writechar(mp_obj_t self_in, mp_obj_t char_in) {
     // get the character to write (might be 9 bits)
     uint16_t data = mp_obj_get_int(char_in);
 
-    // write the data
-    HAL_StatusTypeDef status = HAL_UART_Transmit(&self->uart, (uint8_t*)&data, 1, self->timeout);
+    // write the character
+    HAL_StatusTypeDef status;
+    if (uart_tx_wait(self, self->timeout)) {
+        status = uart_tx_data(self, (uint8_t*)&data, 1);
+    } else {
+        status = HAL_TIMEOUT;
+    }
 
     if (status != HAL_OK) {
         mp_hal_raise(status);
@@ -807,8 +843,14 @@ STATIC mp_uint_t pyb_uart_write(mp_obj_t self_in, const void *buf_in, mp_uint_t
         return MP_STREAM_ERROR;
     }
 
+    // wait to be able to write the first character
+    if (!uart_tx_wait(self, self->timeout)) {
+        *errcode = EAGAIN;
+        return MP_STREAM_ERROR;
+    }
+
     // write the data
-    HAL_StatusTypeDef status = HAL_UART_Transmit(&self->uart, (uint8_t*)buf, size >> self->char_width, self->timeout);
+    HAL_StatusTypeDef status = uart_tx_data(self, (uint8_t*)buf, size >> self->char_width);
 
     if (status == HAL_OK) {
         // return number of bytes written
diff --git a/tests/pyb/uart.py b/tests/pyb/uart.py
index cb0be91c0baa64c4275f8d97fc33d470ad90d67e..838dd9cfc628641cedbaabc454d3684f02ba1570 100644
--- a/tests/pyb/uart.py
+++ b/tests/pyb/uart.py
@@ -23,3 +23,10 @@ print(uart.writechar(1))
 
 # make sure this method exists
 uart.sendbreak()
+
+# non-blocking mode
+uart = UART(1, 9600, timeout=0)
+print(uart.write(b'1'))
+print(uart.write(b'abcd'))
+print(uart.writechar(1))
+print(uart.read(100))
diff --git a/tests/pyb/uart.py.exp b/tests/pyb/uart.py.exp
index ea300c90cbee23ba1edee708ce3a4e9174297caf..4be50d328a1bd1648be609b3c51fe8db103c502b 100644
--- a/tests/pyb/uart.py.exp
+++ b/tests/pyb/uart.py.exp
@@ -12,9 +12,13 @@ UART XB
 UART YA
 UART YB
 ValueError Z
-UART(1, baudrate=9600, bits=8, parity=None, stop=1, timeout=1000, timeout_char=0, read_buf_len=64)
-UART(1, baudrate=2400, bits=8, parity=None, stop=1, timeout=1000, timeout_char=0, read_buf_len=64)
+UART(1, baudrate=9600, bits=8, parity=None, stop=1, timeout=1000, timeout_char=2, read_buf_len=64)
+UART(1, baudrate=2400, bits=8, parity=None, stop=1, timeout=1000, timeout_char=6, read_buf_len=64)
 0
 3
 4
 None
+1
+4
+None
+None