diff --git a/stmhal/qstrdefsport.h b/stmhal/qstrdefsport.h
index 2a117c8023e969eec9f5948aa14bf5e90d5f938f..e992f545ba62fcf453e8741dddc66940d0b5e786 100644
--- a/stmhal/qstrdefsport.h
+++ b/stmhal/qstrdefsport.h
@@ -121,6 +121,7 @@ Q(recv)
 Q(RTC)
 Q(info)
 Q(datetime)
+Q(wakeup)
 
 // for Pin class
 Q(Pin)
diff --git a/stmhal/rtc.c b/stmhal/rtc.c
index 82bb61bbff32f3a51cb2e183d83bb927de7d0bdb..2bbe5b10cf844bed2bbb96c4a6eb880b42061154 100644
--- a/stmhal/rtc.c
+++ b/stmhal/rtc.c
@@ -372,9 +372,123 @@ mp_obj_t pyb_rtc_datetime(mp_uint_t n_args, const mp_obj_t *args) {
 }
 MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(pyb_rtc_datetime_obj, 1, 2, pyb_rtc_datetime);
 
+// wakeup(None)
+// wakeup(ms, callback=None)
+// wakeup(wucksel, wut, callback)
+mp_obj_t pyb_rtc_wakeup(mp_uint_t n_args, const mp_obj_t *args) {
+    // wut is wakeup counter start value, wucksel is clock source
+    // counter is decremented at wucksel rate, and wakes the MCU when it gets to 0
+    // wucksel=0b000 is RTC/16 (RTC runs at 32768Hz)
+    // wucksel=0b001 is RTC/8
+    // wucksel=0b010 is RTC/4
+    // wucksel=0b011 is RTC/2
+    // wucksel=0b100 is 1Hz clock
+    // wucksel=0b110 is 1Hz clock with 0x10000 added to wut
+    // so a 1 second wakeup could be wut=2047, wucksel=0b000, or wut=4095, wucksel=0b001, etc
+
+    // disable wakeup IRQ while we configure it
+    HAL_NVIC_DisableIRQ(RTC_WKUP_IRQn);
+
+    bool enable = false;
+    mp_int_t wucksel;
+    mp_int_t wut;
+    mp_obj_t callback = mp_const_none;
+    if (n_args <= 3) {
+        if (args[1] == mp_const_none) {
+            // disable wakeup
+        } else {
+            // time given in ms
+            mp_int_t ms = mp_obj_get_int(args[1]);
+            mp_int_t div = 2;
+            wucksel = 3;
+            while (div <= 16 && ms > 2000 * div) {
+                div *= 2;
+                wucksel -= 1;
+            }
+            if (div <= 16) {
+                wut = 32768 / div * ms / 1000;
+            } else {
+                wucksel = 4;
+                wut = ms / 1000;
+                if (ms > 0x10000) {
+                    wucksel = 5;
+                    ms -= 0x10000;
+                    if (ms > 0x10000) {
+                        nlr_raise(mp_obj_new_exception_msg(&mp_type_ValueError, "wakeup value too large"));
+                    }
+                }
+            }
+            wut -= 1;
+            enable = true;
+        }
+        if (n_args == 3) {
+            callback = args[2];
+        }
+    } else {
+        // config values given directly
+        wucksel = mp_obj_get_int(args[1]);
+        wut = mp_obj_get_int(args[2]);
+        callback = args[3];
+        enable = true;
+    }
+
+    // set the callback
+    MP_STATE_PORT(pyb_extint_callback)[22] = callback;
+
+    // disable register write protection
+    RTC->WPR = 0xca;
+    RTC->WPR = 0x53;
+
+    // clear WUTE
+    RTC->CR &= ~(1 << 10);
+
+    // wait until WUTWF is set
+    while (!(RTC->ISR & (1 << 2))) {
+    }
+
+    if (enable) {
+        // program WUT
+        RTC->WUTR = wut;
+
+        // set WUTIE to enable wakeup interrupts
+        // set WUTE to enable wakeup
+        // program WUCKSEL
+        RTC->CR |= (1 << 14) | (1 << 10) | (wucksel & 7);
+
+        // enable register write protection
+        RTC->WPR = 0xff;
+
+        // enable external interrupts on line 22
+        EXTI->IMR |= 1 << 22;
+        EXTI->RTSR |= 1 << 22;
+
+        // clear interrupt flags
+        RTC->ISR &= ~(1 << 10);
+        EXTI->PR = 1 << 22;
+
+        HAL_NVIC_SetPriority(RTC_WKUP_IRQn, 0x0f, 0x0f);
+        HAL_NVIC_EnableIRQ(RTC_WKUP_IRQn);
+
+        //printf("wut=%d wucksel=%d\n", wut, wucksel);
+    } else {
+        // clear WUTIE to disable interrupts
+        RTC->CR &= ~(1 << 14);
+
+        // enable register write protection
+        RTC->WPR = 0xff;
+
+        // disable external interrupts on line 22
+        EXTI->IMR &= ~(1 << 22);
+    }
+
+    return mp_const_none;
+}
+MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(pyb_rtc_wakeup_obj, 2, 4, pyb_rtc_wakeup);
+
 STATIC const mp_map_elem_t pyb_rtc_locals_dict_table[] = {
     { MP_OBJ_NEW_QSTR(MP_QSTR_info), (mp_obj_t)&pyb_rtc_info_obj },
     { MP_OBJ_NEW_QSTR(MP_QSTR_datetime), (mp_obj_t)&pyb_rtc_datetime_obj },
+    { MP_OBJ_NEW_QSTR(MP_QSTR_wakeup), (mp_obj_t)&pyb_rtc_wakeup_obj },
 };
 STATIC MP_DEFINE_CONST_DICT(pyb_rtc_locals_dict, pyb_rtc_locals_dict_table);
 
diff --git a/stmhal/stm32f4xx_it.c b/stmhal/stm32f4xx_it.c
index f06c4081c3a2c9cb35a04b16f311ed525b988aed..9be180a051f8bfb4b2f0fa7fa26d5eb391fc422e 100644
--- a/stmhal/stm32f4xx_it.c
+++ b/stmhal/stm32f4xx_it.c
@@ -341,7 +341,8 @@ void TAMP_STAMP_IRQHandler(void) {
 }
 
 void RTC_WKUP_IRQHandler(void) {
-    Handle_EXTI_Irq(EXTI_RTC_WAKEUP);
+    RTC->ISR &= ~(1 << 10); // clear wakeup interrupt flag
+    Handle_EXTI_Irq(EXTI_RTC_WAKEUP); // clear EXTI flag and execute optional callback
 }
 
 void TIM1_BRK_TIM9_IRQHandler(void) {