diff --git a/boards/calliope-mini/Makefile.features b/boards/calliope-mini/Makefile.features
index 9a75beba6894dc8fe69f17923746ee7fee2d1b5b..eb4cb8ead4b9d0417dad1873fdb93d6c12b5fdbd 100644
--- a/boards/calliope-mini/Makefile.features
+++ b/boards/calliope-mini/Makefile.features
@@ -4,6 +4,7 @@ FEATURES_PROVIDED += periph_i2c
 FEATURES_PROVIDED += periph_rtt
 FEATURES_PROVIDED += periph_timer
 FEATURES_PROVIDED += periph_uart
+FEATURES_PROVIDED += periph_pwm
 
 # Various other features (if any)
 FEATURES_PROVIDED += radio_nrfmin
diff --git a/boards/calliope-mini/include/periph_conf.h b/boards/calliope-mini/include/periph_conf.h
index 2d7e5458da85f26034a03c9c70f6957cae9cdcf4..e3a033b8263ac0aa5a4b13330061465157b4d399 100644
--- a/boards/calliope-mini/include/periph_conf.h
+++ b/boards/calliope-mini/include/periph_conf.h
@@ -57,18 +57,11 @@ static const timer_conf_t timer_config[] = {
         .channels = 3,
         .bitmode  = TIMER_BITMODE_BITMODE_16Bit,
         .irqn     = TIMER1_IRQn
-    },
-    {
-        .dev      = NRF_TIMER2,
-        .channels = 3,
-        .bitmode  = TIMER_BITMODE_BITMODE_16Bit,
-        .irqn     = TIMER2_IRQn
     }
 };
 
 #define TIMER_0_ISR         isr_timer0
 #define TIMER_1_ISR         isr_timer1
-#define TIMER_2_ISR         isr_timer2
 
 #define TIMER_NUMOF         (sizeof(timer_config) / sizeof(timer_config[0]))
 /** @} */
@@ -129,6 +122,15 @@ static const i2c_conf_t i2c_config[] = {
 #define RADIO_IRQ_PRIO      1
 /** @} */
 
+/**
+ * @name    PWM configuration
+ * @{
+ */
+#define PWM_NUMOF           (1U)
+#define PWM_TIMER           NRF_TIMER2
+#define PWM_PIN             (0U)
+/** @} */
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/cpu/nrf51/include/cpu_conf.h b/cpu/nrf51/include/cpu_conf.h
index 6e36da701f8275e3e52e6e3b6e66e6812a2618b2..b5948685a1ab098120773ad288187dd226450da1 100644
--- a/cpu/nrf51/include/cpu_conf.h
+++ b/cpu/nrf51/include/cpu_conf.h
@@ -59,6 +59,15 @@ extern "C" {
 #endif
 /** @} */
 
+/**
+ * @brief   CPU specific PWM configuration
+ * @{
+ */
+#define PWM_GPIOTE_CH           (2U)
+#define PWM_PPI_A               (0U)
+#define PWM_PPI_B               (1U)
+/** @} */
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/cpu/nrf51/periph/pwm.c b/cpu/nrf51/periph/pwm.c
new file mode 100644
index 0000000000000000000000000000000000000000..5749dec5046024d7033c4ee68dd21ec8513e2de9
--- /dev/null
+++ b/cpu/nrf51/periph/pwm.c
@@ -0,0 +1,245 @@
+/*
+ * Copyright (C) 2018 Freie Universität Berlin
+ *
+ * This file is subject to the terms and conditions of the GNU Lesser General
+ * Public License v2.1. See the file LICENSE in the top level directory for more
+ * details.
+ */
+
+/**
+ * @ingroup     cpu_nrf51
+ * @ingroup     drivers_periph_pwm
+ * @{
+ *
+ * @file
+ * @brief       Low-level PWM driver implementation
+ *
+ * @author      Semjon Kerner <semjon.kerner@fu-berlin.de>
+ * @}
+ */
+
+#include <stdint.h>
+#include <string.h>
+
+#include "periph/gpio.h"
+#include "periph/pwm.h"
+
+#define ENABLE_DEBUG        (0)
+#include "debug.h"
+
+#define PWM_PS_MAX          (9U)
+#define PWM_PPI_CHANNELS    ((1 << PWM_PPI_A) | (1 << PWM_PPI_B))
+#ifndef PWM_PERCENT_VAL
+    #define PWM_PERCENT_VAL (1U)
+#endif
+
+static const uint32_t divtable[10] = {
+    (CLOCK_CORECLOCK >> 0),
+    (CLOCK_CORECLOCK >> 1),
+    (CLOCK_CORECLOCK >> 2),
+    (CLOCK_CORECLOCK >> 3),
+    (CLOCK_CORECLOCK >> 4),
+    (CLOCK_CORECLOCK >> 5),
+    (CLOCK_CORECLOCK >> 6),
+    (CLOCK_CORECLOCK >> 7),
+    (CLOCK_CORECLOCK >> 8),
+    (CLOCK_CORECLOCK >> 9),
+};
+
+static uint32_t init_data[2];
+
+uint32_t pwm_init(pwm_t dev, pwm_mode_t mode, uint32_t freq, uint16_t res)
+{
+    assert(dev == 0 && ((mode == PWM_LEFT) || (mode == PWM_RIGHT)));
+
+    /* reset and configure the timer */
+    PWM_TIMER->POWER = 1;
+    PWM_TIMER->TASKS_STOP = 1;
+    PWM_TIMER->BITMODE = TIMER_BITMODE_BITMODE_32Bit;
+    PWM_TIMER->MODE = TIMER_MODE_MODE_Timer;
+    PWM_TIMER->TASKS_CLEAR = 1;
+
+    /* calculate and set prescaler */
+    uint32_t timer_freq = freq * res;
+    uint32_t lower = (timer_freq - (PWM_PERCENT_VAL * (timer_freq / 100)));
+    uint32_t upper = (timer_freq + (PWM_PERCENT_VAL * (timer_freq / 100)));
+    for (uint32_t ps = 0; ps <= (PWM_PS_MAX + 1); ps++) {
+        if (ps == (PWM_PS_MAX + 1)) {
+            DEBUG("[pwm] init error: resolution or frequency not supported\n");
+            return 0;
+        }
+        if ((divtable[ps] < upper) && (divtable[ps] > lower)) {
+            PWM_TIMER->PRESCALER = ps;
+            timer_freq = divtable[ps];
+            break;
+        }
+    }
+
+    /* reset timer compare events */
+    PWM_TIMER->EVENTS_COMPARE[0] = 0;
+    PWM_TIMER->EVENTS_COMPARE[1] = 0;
+    /* init timer compare values */
+    PWM_TIMER->CC[0] = 1;
+    PWM_TIMER->CC[1] = res;
+
+    /* configure PPI Event (set compare values and pwm width) */
+    if (mode == PWM_LEFT) {
+        NRF_GPIOTE->CONFIG[PWM_GPIOTE_CH] = (GPIOTE_CONFIG_MODE_Task     |
+                                             (PWM_PIN << 8)              |
+                                             GPIOTE_CONFIG_POLARITY_Msk  |
+                                             GPIOTE_CONFIG_OUTINIT_Msk);
+    }
+    else if (mode == PWM_RIGHT) {
+        NRF_GPIOTE->CONFIG[PWM_GPIOTE_CH] = (GPIOTE_CONFIG_MODE_Task     |
+                                             (PWM_PIN << 8)              |
+                                             GPIOTE_CONFIG_POLARITY_Msk);
+    }
+
+    /* configure PPI Channels (connect compare-event and gpiote-task) */
+    NRF_PPI->CH[PWM_PPI_A].EEP = (uint32_t)(&PWM_TIMER->EVENTS_COMPARE[0]);
+    NRF_PPI->CH[PWM_PPI_B].EEP = (uint32_t)(&PWM_TIMER->EVENTS_COMPARE[1]);
+
+    NRF_PPI->CH[PWM_PPI_A].TEP =
+        (uint32_t)(&NRF_GPIOTE->TASKS_OUT[PWM_GPIOTE_CH]);
+    NRF_PPI->CH[PWM_PPI_B].TEP =
+        (uint32_t)(&NRF_GPIOTE->TASKS_OUT[PWM_GPIOTE_CH]);
+
+    /* enable configured PPI Channels */
+    NRF_PPI->CHENSET = PWM_PPI_CHANNELS;
+
+    /* shortcut to reset Counter after CC[1] event */
+    PWM_TIMER->SHORTS = TIMER_SHORTS_COMPARE1_CLEAR_Msk;
+
+    /* start pwm with value '0' */
+    pwm_set(dev, 0, 0);
+
+    DEBUG("Timer frequency is set to %ld\n", timer_freq);
+
+    return (uint32_t)(timer_freq / res);
+}
+
+void pwm_set(pwm_t dev, uint8_t channel, uint16_t value)
+{
+    assert((dev == 0) && (channel == 0));
+
+    /*
+     * make sure duty cycle is set at the beggining of each period
+     * ensure to stop the timer as soon as possible
+     */
+    PWM_TIMER->TASKS_STOP = 1;
+    PWM_TIMER->EVENTS_COMPARE[1] = 0;
+    PWM_TIMER->SHORTS = TIMER_SHORTS_COMPARE1_STOP_Msk;
+    PWM_TIMER->TASKS_START = 1;
+
+    /*
+     * waiting for the timer to stop
+     * This loop generates heavy load. This is not optimal therefore a local
+     * sleep function should be implemented.
+     */
+    while (PWM_TIMER->EVENTS_COMPARE[1] == 0) {};
+
+    /*
+     * checking pwm alignment first
+     * and guarding if duty cycle is 0% / 100%
+     */
+    if (NRF_GPIOTE->CONFIG[PWM_GPIOTE_CH] & GPIOTE_CONFIG_OUTINIT_Msk) {
+        if (value == 0) {
+            if (PWM_TIMER->CC[0] != 0) {
+                NRF_GPIOTE->TASKS_OUT[PWM_GPIOTE_CH] = 1;
+            }
+
+            NRF_PPI->CHENCLR = PWM_PPI_CHANNELS;
+            PWM_TIMER->CC[0] = 0;
+        } else {
+            if (PWM_TIMER->CC[0] == 0) {
+                NRF_GPIOTE->TASKS_OUT[PWM_GPIOTE_CH] = 1;
+            }
+            if (value >= PWM_TIMER->CC[1]) {
+                NRF_PPI->CHENCLR = PWM_PPI_CHANNELS;
+                PWM_TIMER->CC[0] = PWM_TIMER->CC[1];
+            } else {
+                NRF_PPI->CHENSET = PWM_PPI_CHANNELS;
+                PWM_TIMER->CC[0] = value;
+            }
+        }
+    } else {
+        if (value >= PWM_TIMER->CC[1]) {
+            if (PWM_TIMER->CC[0] != PWM_TIMER->CC[1]) {
+                NRF_GPIOTE->TASKS_OUT[PWM_GPIOTE_CH] = 1;
+            }
+            NRF_PPI->CHENCLR = PWM_PPI_CHANNELS;
+            PWM_TIMER->CC[0] = PWM_TIMER->CC[1];
+        } else {
+            if (PWM_TIMER->CC[0] == PWM_TIMER->CC[1]) {
+                NRF_GPIOTE->TASKS_OUT[PWM_GPIOTE_CH] = 1;
+            }
+            if (value == 0) {
+                NRF_PPI->CHENCLR = PWM_PPI_CHANNELS;
+                PWM_TIMER->CC[0] = 0;
+            } else {
+                NRF_PPI->CHENSET = PWM_PPI_CHANNELS;
+                PWM_TIMER->CC[0] = PWM_TIMER->CC[1] - value;
+            }
+        }
+    }
+
+    /* reconfigure pwm to standard mode */
+    PWM_TIMER->TASKS_CLEAR = 1;
+    PWM_TIMER->SHORTS = TIMER_SHORTS_COMPARE1_CLEAR_Msk;
+    PWM_TIMER->TASKS_START = 1;
+}
+
+uint8_t pwm_channels(pwm_t dev)
+{
+    assert(dev == 0);
+    return 1;
+}
+
+void pwm_poweron(pwm_t dev)
+{
+    assert(dev == 0);
+
+    /*
+     * reinit pwm with correct alignment
+     */
+    if (NRF_GPIOTE->CONFIG[PWM_GPIOTE_CH] & GPIOTE_CONFIG_OUTINIT_Msk) {
+        pwm_init(dev, PWM_LEFT, init_data[1], (init_data[0] >> 16));
+    } else {
+        pwm_init(dev, PWM_RIGHT, init_data[1], (init_data[0] >> 16));
+    }
+
+    /*
+     * reset dutycycle
+     */
+    pwm_set(dev, 0, (init_data[0] & 0xffff));
+
+}
+
+void pwm_poweroff(pwm_t dev)
+{
+    assert(dev == 0);
+
+    PWM_TIMER->TASKS_STOP = 1;
+
+    /*
+     * power off function ensures that the inverted CC[0] is cached correctly
+     * when right aligned
+     */
+    if (((NRF_GPIOTE->CONFIG[PWM_GPIOTE_CH] & GPIOTE_CONFIG_OUTINIT_Msk) == 0) &
+        (PWM_TIMER->CC[1] != PWM_TIMER->CC[0]) &
+        (PWM_TIMER->CC[0] != 0)) {
+            init_data[0] = ((PWM_TIMER->CC[1] << 16) |
+                            (PWM_TIMER->CC[1] - PWM_TIMER->CC[0]));
+    } else {
+        init_data[0] = ((PWM_TIMER->CC[1] << 16) | PWM_TIMER->CC[0]);
+    }
+
+    init_data[1] = (divtable[PWM_TIMER->PRESCALER] / PWM_TIMER->CC[1]);
+
+    /*
+     * make sure the gpio is set to '0' while power is off
+     */
+    pwm_set(dev, 0, 0);
+
+    PWM_TIMER->POWER = 0;
+}