diff --git a/ports/stm32/Makefile b/ports/stm32/Makefile
index 3967e63117..3d39f0c94a 100644
--- a/ports/stm32/Makefile
+++ b/ports/stm32/Makefile
@@ -64,7 +64,7 @@ CFLAGS_CORTEX_M = -mthumb
 ifeq ($(CMSIS_MCU),$(filter $(CMSIS_MCU),STM32F765xx STM32F767xx STM32F769xx STM32H743xx))
 CFLAGS_CORTEX_M += -mfpu=fpv5-d16 -mfloat-abi=hard
 else
-ifeq ($(MCU_SERIES),f0)
+ifeq ($(MCU_SERIES),$(filter $(MCU_SERIES),f0 l0))
 CFLAGS_CORTEX_M += -msoft-float
 else
 CFLAGS_CORTEX_M += -mfpu=fpv4-sp-d16 -mfloat-abi=hard
@@ -75,6 +75,7 @@ endif
 CFLAGS_MCU_f0 = $(CFLAGS_CORTEX_M) -mtune=cortex-m0 -mcpu=cortex-m0
 CFLAGS_MCU_f4 = $(CFLAGS_CORTEX_M) -mtune=cortex-m4 -mcpu=cortex-m4
 CFLAGS_MCU_f7 = $(CFLAGS_CORTEX_M) -mtune=cortex-m7 -mcpu=cortex-m7
+CFLAGS_MCU_l0 = $(CFLAGS_CORTEX_M) -mtune=cortex-m0plus -mcpu=cortex-m0plus
 CFLAGS_MCU_l4 = $(CFLAGS_CORTEX_M) -mtune=cortex-m4 -mcpu=cortex-m4
 CFLAGS_MCU_h7 = $(CFLAGS_CORTEX_M) -mtune=cortex-m7 -mcpu=cortex-m7
 
@@ -200,7 +201,7 @@ SRC_LIBM = $(addprefix lib/libm/,\
 	wf_lgamma.c \
 	wf_tgamma.c \
 	)
-ifeq ($(MCU_SERIES),f0)
+ifeq ($(MCU_SERIES),$(filter $(MCU_SERIES),f0 l0))
 SRC_LIBM += lib/libm/ef_sqrt.c
 else
 SRC_LIBM += lib/libm/thumb_vfp_sqrtf.c
@@ -289,12 +290,21 @@ SRC_O = \
 	resethandler_m0.o \
 	lib/utils/gchelper_m0.o
 else
+ifeq ($(MCU_SERIES),l0)
+CSUPEROPT = -Os # save some code space
+SRC_O = \
+	$(STARTUP_FILE) \
+	lib/stm32lib/CMSIS/STM32$(MCU_SERIES_UPPER)xx/Source/Templates/system_stm32$(MCU_SERIES)xx.o \
+	resethandler_m0.o \
+	lib/utils/gchelper_m0.o
+else
 SRC_O = \
 	$(STARTUP_FILE) \
 	system_stm32.o \
 	resethandler.o \
 	lib/utils/gchelper_m3.o
 endif
+endif
 
 SRC_HAL = $(addprefix $(HAL_DIR)/Src/stm32$(MCU_SERIES)xx_,\
 	hal.c \
@@ -341,8 +351,10 @@ endif
 ifeq ($(CMSIS_MCU),$(filter $(CMSIS_MCU),STM32H743xx))
     SRC_HAL += $(addprefix $(HAL_DIR)/Src/stm32$(MCU_SERIES)xx_, hal_fdcan.c)
 else
+ifneq ($(MCU_SERIES),$(filter $(MCU_SERIES),l0))
     SRC_HAL += $(addprefix $(HAL_DIR)/Src/stm32$(MCU_SERIES)xx_, hal_can.c)
 endif
+endif
 
 SRC_USBDEV = $(addprefix $(USBDEV_DIR)/,\
 	core/src/usbd_core.c \
diff --git a/ports/stm32/dma.c b/ports/stm32/dma.c
index a1dd40c352..32b9a74d70 100644
--- a/ports/stm32/dma.c
+++ b/ports/stm32/dma.c
@@ -70,7 +70,7 @@ typedef union {
 struct _dma_descr_t {
     #if defined(STM32F4) || defined(STM32F7) || defined(STM32H7)
     DMA_Stream_TypeDef *instance;
-    #elif defined(STM32F0) || defined(STM32L4)
+    #elif defined(STM32F0) || defined(STM32L0) || defined(STM32L4)
     DMA_Channel_TypeDef *instance;
     #else
     #error "Unsupported Processor"
@@ -310,6 +310,47 @@ static const uint8_t dma_irqn[NSTREAM] = {
     DMA2_Stream7_IRQn,
 };
 
+#elif defined(STM32L0)
+
+#define NCONTROLLERS            (1)
+#define NSTREAMS_PER_CONTROLLER (7)
+#define NSTREAM                 (NCONTROLLERS * NSTREAMS_PER_CONTROLLER)
+
+#define DMA_SUB_INSTANCE_AS_UINT8(dma_request) (dma_request)
+
+#define DMA1_ENABLE_MASK (0x007f) // Bits in dma_enable_mask corresponding to DMA1
+
+// These descriptors are ordered by DMAx_Channel number, and within a channel by request
+// number. The duplicate streams are ok as long as they aren't used at the same time.
+
+// DMA1 streams
+const dma_descr_t dma_SPI_1_RX = { DMA1_Channel2, DMA_REQUEST_1, dma_id_1,   &dma_init_struct_spi_i2c };
+const dma_descr_t dma_I2C_3_TX = { DMA1_Channel2, DMA_REQUEST_3, dma_id_1,   &dma_init_struct_spi_i2c };
+const dma_descr_t dma_SPI_1_TX = { DMA1_Channel3, DMA_REQUEST_1, dma_id_2,   &dma_init_struct_spi_i2c };
+const dma_descr_t dma_I2C_3_RX = { DMA1_Channel3, DMA_REQUEST_3, dma_id_2,   &dma_init_struct_spi_i2c };
+#if MICROPY_HW_ENABLE_DAC
+const dma_descr_t dma_DAC_1_TX = { DMA1_Channel3, DMA_REQUEST_6, dma_id_2,   &dma_init_struct_dac };
+#endif
+const dma_descr_t dma_SPI_2_RX = { DMA1_Channel4, DMA_REQUEST_1, dma_id_3,   &dma_init_struct_spi_i2c };
+const dma_descr_t dma_I2C_2_TX = { DMA1_Channel4, DMA_REQUEST_3, dma_id_3,   &dma_init_struct_spi_i2c };
+#if MICROPY_HW_ENABLE_DAC
+const dma_descr_t dma_DAC_2_TX = { DMA1_Channel4, DMA_REQUEST_5, dma_id_3,   &dma_init_struct_dac };
+#endif
+const dma_descr_t dma_SPI_2_TX = { DMA1_Channel5, DMA_REQUEST_1, dma_id_4,   &dma_init_struct_spi_i2c };
+const dma_descr_t dma_I2C_2_RX = { DMA1_Channel5, DMA_REQUEST_3, dma_id_4,   &dma_init_struct_spi_i2c };
+const dma_descr_t dma_I2C_1_TX = { DMA1_Channel6, DMA_REQUEST_3, dma_id_5,   &dma_init_struct_spi_i2c };
+const dma_descr_t dma_I2C_1_RX = { DMA1_Channel7, DMA_REQUEST_3, dma_id_6,   &dma_init_struct_spi_i2c };
+
+static const uint8_t dma_irqn[NSTREAM] = {
+    DMA1_Channel1_IRQn,
+    DMA1_Channel2_3_IRQn,
+    DMA1_Channel4_5_6_7_IRQn,
+    0,
+    0,
+    0,
+    0,
+};
+
 #elif defined(STM32L4)
 
 #define NCONTROLLERS            (2)
@@ -459,9 +500,11 @@ volatile dma_idle_count_t dma_idle;
 
 #define DMA_INVALID_CHANNEL 0xff    // Value stored in dma_last_channel which means invalid
 
-#if defined(STM32F0)
+#if defined(STM32F0) || defined(STM32L0)
 #define DMA1_IS_CLK_ENABLED()   ((RCC->AHBENR & RCC_AHBENR_DMA1EN) != 0)
+#if defined(DMA2)
 #define DMA2_IS_CLK_ENABLED()   ((RCC->AHBENR & RCC_AHBENR_DMA2EN) != 0)
+#endif
 #else
 #define DMA1_IS_CLK_ENABLED()   ((RCC->AHB1ENR & RCC_AHB1ENR_DMA1EN) != 0)
 #define DMA2_IS_CLK_ENABLED()   ((RCC->AHB1ENR & RCC_AHB1ENR_DMA2EN) != 0)
@@ -526,6 +569,44 @@ void DMA2_Stream5_IRQHandler(void) { IRQ_ENTER(DMA2_Stream5_IRQn); if (dma_handl
 void DMA2_Stream6_IRQHandler(void) { IRQ_ENTER(DMA2_Stream6_IRQn); if (dma_handle[dma_id_14] != NULL) { HAL_DMA_IRQHandler(dma_handle[dma_id_14]); } IRQ_EXIT(DMA2_Stream6_IRQn); }
 void DMA2_Stream7_IRQHandler(void) { IRQ_ENTER(DMA2_Stream7_IRQn); if (dma_handle[dma_id_15] != NULL) { HAL_DMA_IRQHandler(dma_handle[dma_id_15]); } IRQ_EXIT(DMA2_Stream7_IRQn); }
 
+#elif defined(STM32L0)
+
+void DMA1_Channel1_IRQHandler(void) {
+    IRQ_ENTER(DMA1_Channel1_IRQn);
+    if (dma_handle[dma_id_0] != NULL) {
+        HAL_DMA_IRQHandler(dma_handle[dma_id_0]);
+    }
+    IRQ_EXIT(DMA1_Channel1_IRQn);
+}
+
+void DMA1_Channel2_3_IRQHandler(void) {
+    IRQ_ENTER(DMA1_Channel2_3_IRQn);
+    if (dma_handle[dma_id_1] != NULL) {
+        HAL_DMA_IRQHandler(dma_handle[dma_id_1]);
+    }
+    if (dma_handle[dma_id_2] != NULL) {
+        HAL_DMA_IRQHandler(dma_handle[dma_id_2]);
+    }
+    IRQ_EXIT(DMA1_Channel2_3_IRQn);
+}
+
+void DMA1_Channel4_5_6_7_IRQHandler(void) {
+    IRQ_ENTER(DMA1_Channel4_5_6_7_IRQn);
+    if (dma_handle[dma_id_3] != NULL) {
+        HAL_DMA_IRQHandler(dma_handle[dma_id_3]);
+    }
+    if (dma_handle[dma_id_4] != NULL) {
+        HAL_DMA_IRQHandler(dma_handle[dma_id_4]);
+    }
+    if (dma_handle[dma_id_5] != NULL) {
+        HAL_DMA_IRQHandler(dma_handle[dma_id_5]);
+    }
+    if (dma_handle[dma_id_6] != NULL) {
+        HAL_DMA_IRQHandler(dma_handle[dma_id_6]);
+    }
+    IRQ_EXIT(DMA1_Channel4_5_6_7_IRQn);
+}
+
 #elif defined(STM32L4)
 
 void DMA1_Channel1_IRQHandler(void) { IRQ_ENTER(DMA1_Channel1_IRQn); if (dma_handle[dma_id_0] != NULL) { HAL_DMA_IRQHandler(dma_handle[dma_id_0]); } IRQ_EXIT(DMA1_Channel1_IRQn); }
@@ -572,18 +653,21 @@ static void dma_enable_clock(dma_id_t dma_id) {
                 dma_last_sub_instance[channel] = DMA_INVALID_CHANNEL;
             }
         }
-    } else {
+    }
+    #if defined(DMA2)
+    else {
         if (((old_enable_mask & DMA2_ENABLE_MASK) == 0) && !DMA2_IS_CLK_ENABLED()) {
             __HAL_RCC_DMA2_CLK_ENABLE();
 
             // We just turned on the clock. This means that anything stored
-            // in dma_last_channel (for DMA1) needs to be invalidated.
+            // in dma_last_channel (for DMA2) needs to be invalidated.
 
             for (int channel = NSTREAMS_PER_CONTROLLER; channel < NSTREAM; channel++) {
                 dma_last_sub_instance[channel] = DMA_INVALID_CHANNEL;
             }
         }
     }
+    #endif
 }
 
 static void dma_disable_clock(dma_id_t dma_id) {
@@ -599,7 +683,7 @@ void dma_init_handle(DMA_HandleTypeDef *dma, const dma_descr_t *dma_descr, uint3
     dma->Instance = dma_descr->instance;
     dma->Init = *dma_descr->init;
     dma->Init.Direction = dir;
-    #if defined(STM32L4) || defined(STM32H7)
+    #if defined(STM32L0) || defined(STM32L4) || defined(STM32H7)
     dma->Init.Request = dma_descr->sub_instance;
     #else
     #if !defined(STM32F0)
@@ -705,7 +789,10 @@ static void dma_idle_handler(uint32_t tick) {
     }
 
     static const uint32_t   controller_mask[] = {
-        DMA1_ENABLE_MASK, DMA2_ENABLE_MASK
+        DMA1_ENABLE_MASK,
+        #if defined(DMA2)
+        DMA2_ENABLE_MASK,
+        #endif
     };
     {
         int controller = (tick >> DMA_SYSTICK_LOG2) & 1;
@@ -719,9 +806,12 @@ static void dma_idle_handler(uint32_t tick) {
                 dma_idle.counter[controller] = 0;
                 if (controller == 0) {
                     __HAL_RCC_DMA1_CLK_DISABLE();
-                } else {
+                }
+                #if defined(DMA2)
+                else {
                     __HAL_RCC_DMA2_CLK_DISABLE();
                 }
+                #endif
             } else {
                 // Something is still active, but the counter never got
                 // reset, so we'll reset the counter here.
@@ -731,7 +821,7 @@ static void dma_idle_handler(uint32_t tick) {
     }
 }
 
-#if defined(STM32F0) || defined(STM32L4)
+#if defined(STM32F0) || defined(STM32L0) || defined(STM32L4)
 
 void dma_nohal_init(const dma_descr_t *descr, uint32_t config) {
     DMA_Channel_TypeDef *dma = descr->instance;
diff --git a/ports/stm32/dma.h b/ports/stm32/dma.h
index 7b74a73993..bedabe7f89 100644
--- a/ports/stm32/dma.h
+++ b/ports/stm32/dma.h
@@ -58,6 +58,21 @@ extern const dma_descr_t dma_SPI_6_RX;
 extern const dma_descr_t dma_SDIO_0;
 extern const dma_descr_t dma_DCMI_0;
 
+#elif defined(STM32L0)
+
+extern const dma_descr_t dma_SPI_1_RX;
+extern const dma_descr_t dma_I2C_3_TX;
+extern const dma_descr_t dma_SPI_1_TX;
+extern const dma_descr_t dma_I2C_3_RX;
+extern const dma_descr_t dma_DAC_1_TX;
+extern const dma_descr_t dma_SPI_2_RX;
+extern const dma_descr_t dma_I2C_2_TX;
+extern const dma_descr_t dma_DAC_2_TX;
+extern const dma_descr_t dma_SPI_2_TX;
+extern const dma_descr_t dma_I2C_2_RX;
+extern const dma_descr_t dma_I2C_1_TX;
+extern const dma_descr_t dma_I2C_1_RX;
+
 #elif defined(STM32L4)
 
 extern const dma_descr_t dma_ADC_1_RX;
diff --git a/ports/stm32/extint.c b/ports/stm32/extint.c
index 0b1ba8eb09..9a7295995a 100644
--- a/ports/stm32/extint.c
+++ b/ports/stm32/extint.c
@@ -139,12 +139,16 @@ STATIC mp_obj_t pyb_extint_callback_arg[EXTI_NUM_VECTORS];
 #endif
 
 STATIC const uint8_t nvic_irq_channel[EXTI_NUM_VECTORS] = {
-    #if defined(STM32F0)
+    #if defined(STM32F0) || defined(STM32L0)
     EXTI0_1_IRQn,  EXTI0_1_IRQn,  EXTI2_3_IRQn,  EXTI2_3_IRQn,
     EXTI4_15_IRQn, EXTI4_15_IRQn, EXTI4_15_IRQn, EXTI4_15_IRQn,
     EXTI4_15_IRQn, EXTI4_15_IRQn, EXTI4_15_IRQn, EXTI4_15_IRQn,
     EXTI4_15_IRQn, EXTI4_15_IRQn, EXTI4_15_IRQn, EXTI4_15_IRQn,
+    #if defined(STM32L0)
+    PVD_IRQn,
+    #else
     PVD_VDDIO2_IRQn,
+    #endif
     RTC_IRQn,
     0, // internal USB wakeup event
     RTC_IRQn,
diff --git a/ports/stm32/flash.c b/ports/stm32/flash.c
index 56896a7037..aff85f7e37 100644
--- a/ports/stm32/flash.c
+++ b/ports/stm32/flash.c
@@ -68,7 +68,7 @@ static const flash_layout_t flash_layout[] = {
     { 0x08040000, 0x40000, 3 },
 };
 
-#elif defined(STM32L4)
+#elif defined(STM32L0) || defined(STM32L4)
 
 static const flash_layout_t flash_layout[] = {
     { (uint32_t)FLASH_BASE, (uint32_t)FLASH_PAGE_SIZE, 512 },
@@ -170,7 +170,12 @@ void flash_erase(uint32_t flash_dest, uint32_t num_word32) {
     EraseInitStruct.TypeErase   = FLASH_TYPEERASE_PAGES;
     EraseInitStruct.PageAddress = flash_dest;
     EraseInitStruct.NbPages     = (4 * num_word32 + FLASH_PAGE_SIZE - 4) / FLASH_PAGE_SIZE;
-    #elif  (defined(STM32L4) && !defined(SYSCFG_MEMRMP_FB_MODE))
+    #elif defined(STM32L0)
+    __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_EOP | FLASH_FLAG_WRPERR | FLASH_FLAG_PGAERR);
+    EraseInitStruct.TypeErase   = FLASH_TYPEERASE_PAGES;
+    EraseInitStruct.PageAddress = flash_dest;
+    EraseInitStruct.NbPages     = (4 * num_word32 + FLASH_PAGE_SIZE - 4) / FLASH_PAGE_SIZE;
+    #elif defined(STM32L4) && !defined(SYSCFG_MEMRMP_FB_MODE)
     __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_ALL_ERRORS);
     EraseInitStruct.TypeErase   = FLASH_TYPEERASE_PAGES;
     EraseInitStruct.Page        = get_page(flash_dest);
diff --git a/ports/stm32/machine_uart.c b/ports/stm32/machine_uart.c
index 29369c0c14..92343ba6d7 100644
--- a/ports/stm32/machine_uart.c
+++ b/ports/stm32/machine_uart.c
@@ -489,7 +489,7 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_1(pyb_uart_readchar_obj, pyb_uart_readchar);
 // uart.sendbreak()
 STATIC mp_obj_t pyb_uart_sendbreak(mp_obj_t self_in) {
     pyb_uart_obj_t *self = MP_OBJ_TO_PTR(self_in);
-    #if defined(STM32F0) || defined(STM32F7) || defined(STM32L4) || defined(STM32H7)
+    #if defined(STM32F0) || defined(STM32F7) || defined(STM32L0) || defined(STM32L4) || defined(STM32H7)
     self->uartx->RQR = USART_RQR_SBKRQ; // write-only register
     #else
     self->uartx->CR1 |= USART_CR1_SBK;
diff --git a/ports/stm32/modmachine.c b/ports/stm32/modmachine.c
index ba0ee28471..8d6dbf4fec 100644
--- a/ports/stm32/modmachine.c
+++ b/ports/stm32/modmachine.c
@@ -56,6 +56,11 @@
 #include "uart.h"
 #include "wdt.h"
 
+#if defined(STM32L0)
+// L0 does not have a BOR, so use POR instead
+#define RCC_CSR_BORRSTF RCC_CSR_PORRSTF
+#endif
+
 #if defined(STM32L4)
 // L4 does not have a POR, so use BOR instead
 #define RCC_CSR_PORRSTF RCC_CSR_BORRSTF
@@ -300,7 +305,7 @@ STATIC mp_obj_t machine_freq(size_t n_args, const mp_obj_t *args) {
         return mp_obj_new_tuple(MP_ARRAY_SIZE(tuple), tuple);
     } else {
         // set
-        #if defined(STM32F0) || defined(STM32L4)
+        #if defined(STM32F0) || defined(STM32L0) || defined(STM32L4)
         mp_raise_NotImplementedError("machine.freq set not supported yet");
         #else
         mp_int_t sysclk = mp_obj_get_int(args[0]);
diff --git a/ports/stm32/mpconfigboard_common.h b/ports/stm32/mpconfigboard_common.h
index 036e1f6ccd..94a86ca92a 100644
--- a/ports/stm32/mpconfigboard_common.h
+++ b/ports/stm32/mpconfigboard_common.h
@@ -184,6 +184,15 @@
 #define MICROPY_HW_MAX_TIMER (17)
 #define MICROPY_HW_MAX_UART (8)
 
+// Configuration for STM32L0 series
+#elif defined(STM32L0)
+
+#define MP_HAL_UNIQUE_ID_ADDRESS (0x1FF80050)
+#define PYB_EXTI_NUM_VECTORS (30) // TODO (22 configurable, 7 direct)
+#define MICROPY_HW_MAX_I2C (3)
+#define MICROPY_HW_MAX_TIMER (22)
+#define MICROPY_HW_MAX_UART (4)
+
 // Configuration for STM32L4 series
 #elif defined(STM32L4)
 
diff --git a/ports/stm32/mphalport.c b/ports/stm32/mphalport.c
index 79b28bd3de..c5786e7409 100644
--- a/ports/stm32/mphalport.c
+++ b/ports/stm32/mphalport.c
@@ -119,6 +119,9 @@ void mp_hal_gpio_clock_enable(GPIO_TypeDef *gpio) {
     #elif defined(STM32H7)
     #define AHBxENR AHB4ENR
     #define AHBxENR_GPIOAEN_Pos RCC_AHB4ENR_GPIOAEN_Pos
+    #elif defined(STM32L0)
+    #define AHBxENR IOPENR
+    #define AHBxENR_GPIOAEN_Pos RCC_IOPENR_GPIOAEN
     #elif defined(STM32L4)
     #define AHBxENR AHB2ENR
     #define AHBxENR_GPIOAEN_Pos RCC_AHB2ENR_GPIOAEN_Pos
diff --git a/ports/stm32/powerctrl.c b/ports/stm32/powerctrl.c
index e3ad20039c..cf2445c7d9 100644
--- a/ports/stm32/powerctrl.c
+++ b/ports/stm32/powerctrl.c
@@ -84,7 +84,31 @@ void powerctrl_check_enter_bootloader(void) {
     }
 }
 
-#if !defined(STM32F0)
+#if defined(STM32L0)
+void SystemClock_Config(void) {
+    // Enable power control peripheral
+    __HAL_RCC_PWR_CLK_ENABLE();
+
+    // Use the 16MHz internal oscillator
+    RCC->CR |= RCC_CR_HSION;
+    while (!(RCC->CR & RCC_CR_HSIRDY)) {
+    }
+    const uint32_t sysclk_src = 1;
+
+    // Select SYSCLK source
+    RCC->CFGR |= sysclk_src << RCC_CFGR_SW_Pos;
+    while (((RCC->CFGR >> RCC_CFGR_SWS_Pos) & 0x3) != sysclk_src) {
+        // Wait for SYSCLK source to change
+    }
+
+    SystemCoreClockUpdate();
+
+    HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq() / 1000);
+    HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);
+}
+#endif
+
+#if !defined(STM32F0) && !defined(STM32L0)
 
 // Assumes that PLL is used as the SYSCLK source
 int powerctrl_rcc_clock_config_pll(RCC_ClkInitTypeDef *rcc_init, uint32_t sysclk_mhz, bool need_pllsai) {
@@ -158,7 +182,7 @@ int powerctrl_rcc_clock_config_pll(RCC_ClkInitTypeDef *rcc_init, uint32_t sysclk
 
 #endif
 
-#if !(defined(STM32F0) || defined(STM32L4))
+#if !defined(STM32F0) && !defined(STM32L0) && !defined(STM32L4)
 
 STATIC uint32_t calc_ahb_div(uint32_t wanted_div) {
     if (wanted_div <= 1) { return RCC_SYSCLK_DIV1; }
@@ -333,7 +357,7 @@ void powerctrl_enter_stop_mode(void) {
     __HAL_RCC_WAKEUPSTOP_CLK_CONFIG(RCC_STOP_WAKEUPCLOCK_MSI);
     #endif
 
-    #if !defined(STM32F0) && !defined(STM32L4)
+    #if !defined(STM32F0) && !defined(STM32L0) && !defined(STM32L4)
     // takes longer to wake but reduces stop current
     HAL_PWREx_EnableFlashPowerDown();
     #endif
@@ -426,7 +450,7 @@ void powerctrl_enter_standby_mode(void) {
 
     // Note: we only support RTC ALRA, ALRB, WUT and TS.
     // TODO support TAMP and WKUP (PA0 external pin).
-    #if defined(STM32F0)
+    #if defined(STM32F0) || defined(STM32L0)
     #define CR_BITS (RTC_CR_ALRAIE | RTC_CR_WUTIE | RTC_CR_TSIE)
     #define ISR_BITS (RTC_ISR_ALRAF | RTC_ISR_WUTF | RTC_ISR_TSF)
     #else
diff --git a/ports/stm32/rtc.c b/ports/stm32/rtc.c
index a7c3f2068b..d0f444c216 100644
--- a/ports/stm32/rtc.c
+++ b/ports/stm32/rtc.c
@@ -73,6 +73,17 @@ STATIC bool rtc_use_lse = false;
 STATIC uint32_t rtc_startup_tick;
 STATIC bool rtc_need_init_finalise = false;
 
+#if defined(STM32L0)
+#define BDCR CSR
+#define RCC_BDCR_RTCEN RCC_CSR_RTCEN
+#define RCC_BDCR_RTCSEL RCC_CSR_RTCSEL
+#define RCC_BDCR_RTCSEL_0 RCC_CSR_RTCSEL_0
+#define RCC_BDCR_RTCSEL_1 RCC_CSR_RTCSEL_1
+#define RCC_BDCR_LSEON RCC_CSR_LSEON
+#define RCC_BDCR_LSERDY RCC_CSR_LSERDY
+#define RCC_BDCR_LSEBYP RCC_CSR_LSEBYP
+#endif
+
 void rtc_init_start(bool force_init) {
     RTCHandle.Instance = RTC;
 
@@ -287,7 +298,7 @@ STATIC HAL_StatusTypeDef PYB_RTC_Init(RTC_HandleTypeDef *hrtc) {
         // Exit Initialization mode
         hrtc->Instance->ISR &= (uint32_t)~RTC_ISR_INIT;
 
-        #if defined(STM32L4) || defined(STM32H7)
+        #if defined(STM32L0) || defined(STM32L4) || defined(STM32H7)
         hrtc->Instance->OR &= (uint32_t)~RTC_OR_ALARMOUTTYPE;
         hrtc->Instance->OR |= (uint32_t)(hrtc->Init.OutPutType);
         #elif defined(STM32F7)
@@ -539,7 +550,7 @@ mp_obj_t pyb_rtc_datetime(size_t n_args, const mp_obj_t *args) {
 }
 MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(pyb_rtc_datetime_obj, 1, 2, pyb_rtc_datetime);
 
-#if defined(STM32F0)
+#if defined(STM32F0) || defined(STM32L0)
 #define RTC_WKUP_IRQn RTC_IRQn
 #endif
 
diff --git a/ports/stm32/timer.c b/ports/stm32/timer.c
index 7f4c0d85a9..cd01a41741 100644
--- a/ports/stm32/timer.c
+++ b/ports/stm32/timer.c
@@ -427,6 +427,8 @@ STATIC mp_obj_t compute_percent_from_pwm_value(uint32_t period, uint32_t cmp) {
     #endif
 }
 
+#if !defined(STM32L0)
+
 // Computes the 8-bit value for the DTG field in the BDTR register.
 //
 // 1 tick = 1 count of the timer's clock (source_freq) divided by div.
@@ -486,6 +488,8 @@ STATIC void config_deadtime(pyb_timer_obj_t *self, mp_int_t ticks, mp_int_t brk)
     HAL_TIMEx_ConfigBreakDeadTime(&self->tim, &deadTimeConfig);
 }
 
+#endif
+
 TIM_HandleTypeDef *pyb_timer_get_handle(mp_obj_t timer) {
     if (mp_obj_get_type(timer) != &pyb_timer_type) {
         mp_raise_ValueError("need a Timer object");
@@ -514,6 +518,7 @@ STATIC void pyb_timer_print(const mp_print_t *print, mp_obj_t self_in, mp_print_
             self->tim.Init.ClockDivision == TIM_CLOCKDIVISION_DIV4 ? 4 :
             self->tim.Init.ClockDivision == TIM_CLOCKDIVISION_DIV2 ? 2 : 1);
 
+        #if !defined(STM32L0)
         #if defined(IS_TIM_ADVANCED_INSTANCE)
         if (IS_TIM_ADVANCED_INSTANCE(self->tim.Instance))
         #elif defined(IS_TIM_BREAK_INSTANCE)
@@ -531,6 +536,7 @@ STATIC void pyb_timer_print(const mp_print_t *print, mp_obj_t self_in, mp_print_
                 mp_printf(print, ", brk=BRK_OFF");
             }
         }
+        #endif
         mp_print_str(print, ")");
     }
 }
@@ -630,11 +636,15 @@ STATIC mp_obj_t pyb_timer_init_helper(pyb_timer_obj_t *self, size_t n_args, cons
                           args[ARG_div].u_int == 4 ? TIM_CLOCKDIVISION_DIV4 :
                                                      TIM_CLOCKDIVISION_DIV1;
 
+    #if !defined(STM32L0)
     init->RepetitionCounter = 0;
+    #endif
 
     // enable TIM clock
     switch (self->tim_id) {
+        #if defined(TIM1)
         case 1: __HAL_RCC_TIM1_CLK_ENABLE(); break;
+        #endif
         case 2: __HAL_RCC_TIM2_CLK_ENABLE(); break;
         #if defined(TIM3)
         case 3: __HAL_RCC_TIM3_CLK_ENABLE(); break;
@@ -681,22 +691,40 @@ STATIC mp_obj_t pyb_timer_init_helper(pyb_timer_obj_t *self, size_t n_args, cons
         #if defined(TIM17)
         case 17: __HAL_RCC_TIM17_CLK_ENABLE(); break;
         #endif
+        #if defined(TIM18)
+        case 18: __HAL_RCC_TIM18_CLK_ENABLE(); break;
+        #endif
+        #if defined(TIM19)
+        case 19: __HAL_RCC_TIM19_CLK_ENABLE(); break;
+        #endif
+        #if defined(TIM20)
+        case 20: __HAL_RCC_TIM20_CLK_ENABLE(); break;
+        #endif
+        #if defined(TIM21)
+        case 21: __HAL_RCC_TIM21_CLK_ENABLE(); break;
+        #endif
+        #if defined(TIM22)
+        case 22: __HAL_RCC_TIM22_CLK_ENABLE(); break;
+        #endif
     }
 
     // set IRQ priority (if not a special timer)
     if (self->tim_id != 5) {
         NVIC_SetPriority(IRQn_NONNEG(self->irqn), IRQ_PRI_TIMX);
         if (self->tim_id == 1) {
+            #if defined(TIM1)
             NVIC_SetPriority(TIM1_CC_IRQn, IRQ_PRI_TIMX);
-        #if defined(TIM8)
+            #endif
         } else if (self->tim_id == 8) {
+            #if defined(TIM8)
             NVIC_SetPriority(TIM8_CC_IRQn, IRQ_PRI_TIMX);
-        #endif
+            #endif
         }
     }
 
     // init TIM
     HAL_TIM_Base_Init(&self->tim);
+    #if !defined(STM32L0)
     #if defined(IS_TIM_ADVANCED_INSTANCE)
     if (IS_TIM_ADVANCED_INSTANCE(self->tim.Instance)) {
     #elif defined(IS_TIM_BREAK_INSTANCE)
@@ -707,6 +735,7 @@ STATIC mp_obj_t pyb_timer_init_helper(pyb_timer_obj_t *self, size_t n_args, cons
         config_deadtime(self, args[ARG_deadtime].u_int, args[ARG_brk].u_int);
 
     }
+    #endif
 
     // Enable ARPE so that the auto-reload register is buffered.
     // This allows to smoothly change the frequency of the timer.
@@ -726,6 +755,7 @@ STATIC mp_obj_t pyb_timer_init_helper(pyb_timer_obj_t *self, size_t n_args, cons
 // It assumes that timer instance pointer has the lower 8 bits cleared.
 #define TIM_ENTRY(id, irq) [id - 1] = (uint32_t)TIM##id | irq
 STATIC const uint32_t tim_instance_table[MICROPY_HW_MAX_TIMER] = {
+    #if defined(TIM1)
     #if defined(STM32F0)
     TIM_ENTRY(1, TIM1_BRK_UP_TRG_COM_IRQn),
     #elif defined(STM32F4) || defined(STM32F7)
@@ -733,6 +763,7 @@ STATIC const uint32_t tim_instance_table[MICROPY_HW_MAX_TIMER] = {
     #elif defined(STM32L4)
     TIM_ENTRY(1, TIM1_UP_TIM16_IRQn),
     #endif
+    #endif
     TIM_ENTRY(2, TIM2_IRQn),
     #if defined(TIM3)
     TIM_ENTRY(3, TIM3_IRQn),
@@ -1054,10 +1085,12 @@ STATIC mp_obj_t pyb_timer_channel(size_t n_args, const mp_obj_t *pos_args, mp_ma
                 oc_config.Pulse = args[3].u_int;
             }
             oc_config.OCPolarity   = TIM_OCPOLARITY_HIGH;
-            oc_config.OCNPolarity  = TIM_OCNPOLARITY_HIGH;
             oc_config.OCFastMode   = TIM_OCFAST_DISABLE;
+            #if !defined(STM32L0)
+            oc_config.OCNPolarity  = TIM_OCNPOLARITY_HIGH;
             oc_config.OCIdleState  = TIM_OCIDLESTATE_SET;
             oc_config.OCNIdleState = TIM_OCNIDLESTATE_SET;
+            #endif
 
             HAL_TIM_PWM_ConfigChannel(&self->tim, &oc_config, TIMER_CHANNEL(chan));
             if (chan->callback == mp_const_none) {
@@ -1065,10 +1098,12 @@ STATIC mp_obj_t pyb_timer_channel(size_t n_args, const mp_obj_t *pos_args, mp_ma
             } else {
                 pyb_timer_channel_callback(MP_OBJ_FROM_PTR(chan), chan->callback);
             }
+            #if !defined(STM32L0)
             // Start the complimentary channel too (if its supported)
             if (IS_TIM_CCXN_INSTANCE(self->tim.Instance, TIMER_CHANNEL(chan))) {
                 HAL_TIMEx_PWMN_Start(&self->tim, TIMER_CHANNEL(chan));
             }
+            #endif
             break;
         }
 
@@ -1085,14 +1120,16 @@ STATIC mp_obj_t pyb_timer_channel(size_t n_args, const mp_obj_t *pos_args, mp_ma
             if (oc_config.OCPolarity == 0xffffffff) {
                 oc_config.OCPolarity = TIM_OCPOLARITY_HIGH;
             }
+            oc_config.OCFastMode   = TIM_OCFAST_DISABLE;
+            #if !defined(STM32L0)
             if (oc_config.OCPolarity == TIM_OCPOLARITY_HIGH) {
                 oc_config.OCNPolarity  = TIM_OCNPOLARITY_HIGH;
             } else {
                 oc_config.OCNPolarity  = TIM_OCNPOLARITY_LOW;
             }
-            oc_config.OCFastMode   = TIM_OCFAST_DISABLE;
             oc_config.OCIdleState  = TIM_OCIDLESTATE_SET;
             oc_config.OCNIdleState = TIM_OCNIDLESTATE_SET;
+            #endif
 
             if (!IS_TIM_OC_POLARITY(oc_config.OCPolarity)) {
                 nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_ValueError, "invalid polarity (%d)", oc_config.OCPolarity));
@@ -1103,10 +1140,12 @@ STATIC mp_obj_t pyb_timer_channel(size_t n_args, const mp_obj_t *pos_args, mp_ma
             } else {
                 pyb_timer_channel_callback(MP_OBJ_FROM_PTR(chan), chan->callback);
             }
+            #if !defined(STM32L0)
             // Start the complimentary channel too (if its supported)
             if (IS_TIM_CCXN_INSTANCE(self->tim.Instance, TIMER_CHANNEL(chan))) {
                 HAL_TIMEx_OCN_Start(&self->tim, TIMER_CHANNEL(chan));
             }
+            #endif
             break;
         }
 
@@ -1155,8 +1194,12 @@ STATIC mp_obj_t pyb_timer_channel(size_t n_args, const mp_obj_t *pos_args, mp_ma
                 nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_ValueError, "invalid polarity (%d)", enc_config.IC1Polarity));
             }
             // Only Timers 1, 2, 3, 4, 5, and 8 support encoder mode
-            if (self->tim.Instance != TIM1
-            &&  self->tim.Instance != TIM2
+            if (
+            #if defined(TIM1)
+                self->tim.Instance != TIM1
+            &&
+            #endif
+                self->tim.Instance != TIM2
             #if defined(TIM3)
             &&  self->tim.Instance != TIM3
             #endif
@@ -1426,15 +1469,18 @@ STATIC mp_obj_t pyb_timer_channel_callback(mp_obj_t self_in, mp_obj_t callback)
         self->callback = mp_const_none;
     } else if (mp_obj_is_callable(callback)) {
         self->callback = callback;
-        uint8_t tim_id = self->timer->tim_id;
         __HAL_TIM_CLEAR_IT(&self->timer->tim, TIMER_IRQ_MASK(self->channel));
-        if (tim_id == 1) {
+        #if defined(TIM1)
+        if (self->timer->tim_id == 1) {
             HAL_NVIC_EnableIRQ(TIM1_CC_IRQn);
-        #if defined(TIM8) // STM32F401 doesn't have a TIM8
-        } else if (tim_id == 8) {
-            HAL_NVIC_EnableIRQ(TIM8_CC_IRQn);
+        } else
         #endif
-        } else {
+        #if defined(TIM8) // STM32F401 doesn't have a TIM8
+        if (self->timer->tim_id == 8) {
+            HAL_NVIC_EnableIRQ(TIM8_CC_IRQn);
+        } else
+        #endif
+        {
             HAL_NVIC_EnableIRQ(self->timer->irqn);
         }
         // start timer, so that it interrupts on overflow
diff --git a/ports/stm32/uart.c b/ports/stm32/uart.c
index 3a21a0b31e..0d46ea9473 100644
--- a/ports/stm32/uart.c
+++ b/ports/stm32/uart.c
@@ -76,6 +76,11 @@
 #define USART_CR2_IE_ALL (USART_CR2_IE_BASE)
 #define USART_CR3_IE_ALL (USART_CR3_IE_BASE | USART_CR3_RXFTIE | USART_CR3_TCBGTIE | USART_CR3_TXFTIE | USART_CR3_WUFIE)
 
+#elif defined(STM32L0)
+#define USART_CR1_IE_ALL (USART_CR1_IE_BASE | USART_CR1_EOBIE | USART_CR1_RTOIE | USART_CR1_CMIE)
+#define USART_CR2_IE_ALL (USART_CR2_IE_BASE)
+#define USART_CR3_IE_ALL (USART_CR3_IE_BASE | USART_CR3_WUFIE)
+
 #elif defined(STM32L4)
 #define USART_CR1_IE_ALL (USART_CR1_IE_BASE | USART_CR1_EOBIE | USART_CR1_RTOIE | USART_CR1_CMIE)
 #define USART_CR2_IE_ALL (USART_CR2_IE_BASE)
@@ -648,7 +653,7 @@ int uart_rx_char(pyb_uart_obj_t *self) {
         return data;
     } else {
         // no buffering
-        #if defined(STM32F0) || defined(STM32F7) || defined(STM32L4) || defined(STM32H7)
+        #if defined(STM32F0) || defined(STM32F7) || defined(STM32L0) || defined(STM32L4) || defined(STM32H7)
         int data = self->uartx->RDR & self->char_mask;
         self->uartx->ICR = USART_ICR_ORECF; // clear ORE if it was set
         return data;
@@ -774,7 +779,7 @@ void uart_irq_handler(mp_uint_t uart_id) {
             uint16_t next_head = (self->read_buf_head + 1) % self->read_buf_len;
             if (next_head != self->read_buf_tail) {
                 // only read data if room in buf
-                #if defined(STM32F0) || defined(STM32F7) || defined(STM32L4) || defined(STM32H7)
+                #if defined(STM32F0) || defined(STM32F7) || defined(STM32L0) || defined(STM32L4) || defined(STM32H7)
                 int data = self->uartx->RDR; // clears UART_FLAG_RXNE
                 self->uartx->ICR = USART_ICR_ORECF; // clear ORE if it was set
                 #else