From 5a551a37206561a4a572ffb550af8d4559027e28 Mon Sep 17 00:00:00 2001
From: Emilio Corigliano <emilio.corigliano@skywarder.eu>
Date: Wed, 14 Jun 2023 21:22:41 +0000
Subject: [PATCH] [CountedPWM][Stepper][StepperPWM] Enhanced PWM module to
 implement a non-blocking Stepper driver

---
 .vscode/settings.json                        |   2 +
 CMakeLists.txt                               |   8 +-
 cmake/boardcore.cmake                        |   3 +
 src/shared/actuators/Stepper.cpp             | 163 +++++++++++++++
 src/shared/actuators/Stepper.h               | 208 ++++++-------------
 src/shared/actuators/StepperPWM.cpp          |  99 +++++++++
 src/shared/actuators/StepperPWM.h            | 104 ++++++++++
 src/shared/drivers/timer/CountedPWM.cpp      | 147 +++++++++++++
 src/shared/drivers/timer/CountedPWM.h        | 198 ++++++++++++++++++
 src/tests/actuators/test-stepper-pwm.cpp     | 147 +++++++++++++
 src/tests/actuators/test-stepper.cpp         |  95 ++++++---
 src/tests/drivers/timer/test-counted-pwm.cpp |  93 +++++++++
 12 files changed, 1092 insertions(+), 175 deletions(-)
 create mode 100644 src/shared/actuators/Stepper.cpp
 create mode 100644 src/shared/actuators/StepperPWM.cpp
 create mode 100644 src/shared/actuators/StepperPWM.h
 create mode 100644 src/shared/drivers/timer/CountedPWM.cpp
 create mode 100644 src/shared/drivers/timer/CountedPWM.h
 create mode 100644 src/tests/actuators/test-stepper-pwm.cpp
 create mode 100644 src/tests/drivers/timer/test-counted-pwm.cpp

diff --git a/.vscode/settings.json b/.vscode/settings.json
index 544586127..66dd1b582 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -228,6 +228,8 @@
                 "mavlinkdriver",
                 "MEKF",
                 "microcontrollers",
+                "Microstep",
+                "Microsteps",
                 "MINC",
                 "miosix",
                 "mkdir",
diff --git a/CMakeLists.txt b/CMakeLists.txt
index fed706ded..885933511 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -138,7 +138,10 @@ add_executable(test-buzzer src/tests/actuators/test-buzzer.cpp)
 sbs_target(test-buzzer stm32f429zi_hre_test_stand)
 
 add_executable(test-stepper src/tests/actuators/test-stepper.cpp)
-sbs_target(test-stepper stm32f429zi_stm32f4discovery)
+sbs_target(test-stepper stm32f767zi_nucleo)
+
+add_executable(test-stepper-pwm src/tests/actuators/test-stepper-pwm.cpp)
+sbs_target(test-stepper-pwm stm32f767zi_nucleo)
 
 #-----------------------------------------------------------------------------#
 #                             Tests - Algorithms                              #
@@ -218,6 +221,9 @@ sbs_target(test-MBLoadCell stm32f407vg_stm32f4discovery)
 add_executable(test-pwm src/tests/drivers/timer/test-pwm.cpp)
 sbs_target(test-pwm stm32f429zi_stm32f4discovery)
 
+add_executable(test-counted-pwm src/tests/drivers/timer/test-counted-pwm.cpp)
+sbs_target(test-counted-pwm stm32f429zi_stm32f4discovery)
+
 add_executable(test-spi src/tests/drivers/spi/test-spi.cpp)
 sbs_target(test-spi stm32f407vg_stm32f4discovery)
 
diff --git a/cmake/boardcore.cmake b/cmake/boardcore.cmake
index 1972d025d..43a510b06 100644
--- a/cmake/boardcore.cmake
+++ b/cmake/boardcore.cmake
@@ -30,6 +30,8 @@ foreach(OPT_BOARD ${BOARDS})
         # Actuators
         ${SBS_BASE}/src/shared/actuators/HBridge/HBridge.cpp
         ${SBS_BASE}/src/shared/actuators/Servo/Servo.cpp
+        ${SBS_BASE}/src/shared/actuators/Stepper.cpp
+        ${SBS_BASE}/src/shared/actuators/StepperPWM.cpp
 
         # Algorithms
         ${SBS_BASE}/src/shared/algorithms/ADA/ADA.cpp
@@ -52,6 +54,7 @@ foreach(OPT_BOARD ${BOARDS})
         ${SBS_BASE}/src/shared/drivers/canbus/CanProtocol/CanProtocol.cpp
         ${SBS_BASE}/src/shared/drivers/interrupt/external_interrupts.cpp
         ${SBS_BASE}/src/shared/drivers/timer/PWM.cpp
+        ${SBS_BASE}/src/shared/drivers/timer/CountedPWM.cpp
         ${SBS_BASE}/src/shared/drivers/timer/TimestampTimer.cpp
         ${SBS_BASE}/src/shared/drivers/runcam/Runcam.cpp
         ${SBS_BASE}/src/shared/drivers/spi/SPITransaction.cpp
diff --git a/src/shared/actuators/Stepper.cpp b/src/shared/actuators/Stepper.cpp
new file mode 100644
index 000000000..00a2166cd
--- /dev/null
+++ b/src/shared/actuators/Stepper.cpp
@@ -0,0 +1,163 @@
+/* Copyright (c) 2022 Skyward Experimental Rocketry
+ * Authors: Alberto Nidasio, Emilio Corigliano
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#include "Stepper.h"
+
+namespace Boardcore
+{
+
+Stepper::Stepper(miosix::GpioPin stepPin, miosix::GpioPin directionPin,
+                 float speed, float stepAngle, bool revertDirection,
+                 uint16_t microStep, PinConfiguration pinConfiguration,
+                 miosix::GpioPin enablePin)
+    : stepPin(stepPin), directionPin(directionPin), speed(speed),
+      stepAngle(stepAngle), revertDirection(revertDirection),
+      microStep(microStep), pinConfig(pinConfiguration), enablePin(enablePin),
+      currentDirection(Direction::CLOCKWISE)
+{
+    if (this->speed < 0)
+        this->speed = 0;
+
+    Stepper::setMicroStepping(microStep);
+
+    // Start with the motor disabled
+    disable();
+}
+
+void Stepper::enable()
+{
+    if (pinConfig == PinConfiguration::COMMON_CATHODE)
+    {
+        enablePin.low();
+    }
+    else
+    {
+        enablePin.high();
+    }
+}
+
+void Stepper::disable()
+{
+    if (pinConfig == PinConfiguration::COMMON_CATHODE)
+    {
+        enablePin.high();
+    }
+    else
+    {
+        enablePin.low();
+    }
+}
+
+void Stepper::setDirection()
+{
+    // Following the connections written in the stepper-driver datasheet for
+    // moving the stepper clockwise we have that:
+    // directionPin high:  stepper turns clockwise;
+    // directionPin low: stepper turns counterclockwise;
+    //
+    // The revertDirection flag is used just for accounting for an inverted
+    // polarity in the configuration.
+    if (currentDirection == Direction::CLOCKWISE)
+    {
+        // To set the stepper-driver to turn CLOCKWISE we have to set the pin
+        // high in common cathode mode and low in common anode mode (the
+        // resulting potential difference should lead to a high logic value)
+        if ((!revertDirection &&
+             (pinConfig == PinConfiguration::COMMON_CATHODE)) ||
+            (revertDirection && (pinConfig == PinConfiguration::COMMON_ANODE)))
+        {
+            directionPin.high();
+        }
+        else
+        {
+            directionPin.low();
+        }
+    }
+    else
+    {
+        // To set the stepper-driver to turn COUNTER-CLOCKWISE we have to set
+        // the pin low in common cathode mode and high in common anode mode (the
+        // resulting potential difference should lead to a low logic value)
+        if ((!revertDirection &&
+             (pinConfig == PinConfiguration::COMMON_CATHODE)) ||
+            (revertDirection && (pinConfig == PinConfiguration::COMMON_ANODE)))
+        {
+            directionPin.low();
+        }
+        else
+        {
+            directionPin.high();
+        }
+    }
+}
+
+void Stepper::move(int16_t steps)
+{
+    if (speed == 0)
+        return;
+
+    unsigned int halfStepDelay = 1e6 / (speed * 360 / stepAngle * microStep);
+    int16_t stepsAbs;
+    if (steps > 0)
+    {
+        // Go forward
+        currentDirection = Direction::CLOCKWISE;
+        setDirection();
+        stepsAbs = steps;
+    }
+    else
+    {
+        // Go backwards
+        currentDirection = Direction::COUNTER_CLOCKWISE;
+        setDirection();
+        stepsAbs = -steps;
+    }
+
+    miosix::delayUs(halfStepDelay);
+
+    for (int i = 0; i < stepsAbs; i++)
+    {
+        if (pinConfig == PinConfiguration::COMMON_CATHODE)
+        {
+            stepPin.high();
+        }
+        else
+        {
+            stepPin.low();
+        }
+
+        miosix::delayUs(halfStepDelay);
+
+        if (pinConfig == PinConfiguration::COMMON_CATHODE)
+        {
+            stepPin.low();
+        }
+        else
+        {
+            stepPin.high();
+        }
+        miosix::delayUs(halfStepDelay);
+    }
+
+    currentPositionDeg += steps * stepAngle / microStep;
+}
+
+}  // namespace Boardcore
\ No newline at end of file
diff --git a/src/shared/actuators/Stepper.h b/src/shared/actuators/Stepper.h
index b08639af1..d93a5c7dc 100644
--- a/src/shared/actuators/Stepper.h
+++ b/src/shared/actuators/Stepper.h
@@ -1,5 +1,5 @@
 /* Copyright (c) 2022 Skyward Experimental Rocketry
- * Author: Alberto Nidasio
+ * Authors: Alberto Nidasio, Emilio Corigliano
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -32,13 +32,10 @@ namespace Boardcore
 class Stepper
 {
 public:
-    enum class Microstep
+    enum class PinConfiguration
     {
-        MICROSTEP_1,
-        MICROSTEP_2,
-        MICROSTEP_4,
-        MICROSTEP_8,
-        MICROSTEP_16,
+        COMMON_ANODE,    ///< All + signals connected to Vdd (3v3)
+        COMMON_CATHODE,  ///< All - signals connected to Gnd
     };
 
     /**
@@ -53,14 +50,12 @@ public:
      * @param stepAngle Angle covered by one motor step.
      * @param revertDirection Whether or not revert the direction signal.
      */
-    Stepper(miosix::GpioPin stepPin, miosix::GpioPin directionPin,
-            float speed = 1, float stepAngle = 1.8,
-            bool revertDirection      = false,
-            Microstep microstep       = Microstep::MICROSTEP_1,
-            miosix::GpioPin enablePin = MockGpioPin(),
-            miosix::GpioPin ms1Pin    = MockGpioPin(),
-            miosix::GpioPin ms2Pin    = MockGpioPin(),
-            miosix::GpioPin ms3Pin    = MockGpioPin());
+    Stepper(
+        miosix::GpioPin stepPin, miosix::GpioPin directionPin, float speed = 1,
+        float stepAngle = 1.8, bool revertDirection = false,
+        uint16_t microStep                = 1,
+        PinConfiguration pinConfiguration = PinConfiguration::COMMON_CATHODE,
+        miosix::GpioPin enablePin         = MockGpioPin());
 
     void enable();
 
@@ -68,24 +63,28 @@ public:
 
     /**
      * @brief Changes the stepper motor speed.
+     *
+     * @param speed Speed in rotation per second. [rev/s]
      */
-    void setSpeed(float speed);
+    virtual void setSpeed(float speed);
 
     /**
-     * @brief Set the motor driver microstepping configuration.
+     * @brief Set the motor driver micro stepping configuration.
+     *
+     * This method has no effect if micro steps are changed via physical
+     * switches.
      */
-    void setMicrostepping(Microstep microstep);
+    virtual void setMicroStepping(uint16_t microStep);
 
     /**
-     * @brief Zeros the driver's internal position.
-     *
+     * @brief Overrides the driver's internal position counter.
      */
-    void zeroPosition();
+    void zeroPosition(float degrees = 0);
 
     /**
      * @brief Move the stepper motor by the specified amount of steps.
      */
-    void move(int32_t steps);
+    virtual void move(int16_t steps);
 
     /**
      * @brief Move the stepper motor by the specified amount of degrees.
@@ -97,168 +96,89 @@ public:
      *
      * Note that this function uses delayUs, thus it blocks execution based on
      * the stepper motor speed and movement.
-     *
-     * @param degrees Position in steps.
      */
-    void setPosition(int32_t steps);
+    virtual void setPosition(int16_t steps);
 
     /**
      * @brief Set the position of the stepper motor.
      *
      * Note that this function uses delayUs, thus it blocks execution based on
      * the stepper motor speed and movement.
-     *
-     * @param degrees Position in degrees.
      */
     void setPositionDeg(float degrees);
 
-    uint32_t getCurrentPosition();
+    int16_t getCurrentPosition();
 
-    float getCurrentDegPosition();
+    /**
+     * @brief Returns the current absolute position of the stepper in degrees
+     * [deg].
+     */
+    virtual float getCurrentDegPosition();
 
-private:
-    int getMicrosteppingValue();
+protected:
+    /**
+     * @brief Sets the directionPin to the right value to go in the direction
+     * stored in `currentDirection`.
+     */
+    void setDirection();
+
+    enum Direction : int8_t
+    {
+        CLOCKWISE = 1,  // To move the stepper clockwise seeing it from the top
+                        // of the stepper
+        COUNTER_CLOCKWISE = -1,  // To move the stepper counter-clockwise seeing
+                                 // it from the top of the stepper
+    };
+
+    // This class is not copyable!
+    Stepper& operator=(const Stepper&) = delete;
+    Stepper(const Stepper& p)          = delete;
 
     miosix::GpioPin stepPin;
     miosix::GpioPin directionPin;
-    float speed;
-    float stepAngle;  ///< Degrees per step.
+    float speed;      // [rev/s]
+    float stepAngle;  // [deg/step]
     bool revertDirection;
-    Microstep microstep;
+    uint16_t microStep;
+    PinConfiguration pinConfig;
     miosix::GpioPin enablePin;
-    miosix::GpioPin ms1Pin;
-    miosix::GpioPin ms2Pin;
-    miosix::GpioPin ms3Pin;
 
-    int32_t currentPosition = 0;
+    Direction currentDirection;    // Direction of the stepper
+    float currentPositionDeg = 0;  // Absolute position counter [degrees]
 };
 
-inline Stepper::Stepper(miosix::GpioPin stepPin, miosix::GpioPin directionPin,
-                        float speed, float stepAngle, bool revertDirection,
-                        Microstep microstep, miosix::GpioPin enablePin,
-                        miosix::GpioPin ms1Pin, miosix::GpioPin ms2Pin,
-                        miosix::GpioPin ms3Pin)
-    : stepPin(stepPin), directionPin(directionPin), speed(speed),
-      stepAngle(stepAngle), revertDirection(revertDirection),
-      microstep(microstep), enablePin(enablePin), ms1Pin(ms1Pin),
-      ms2Pin(ms2Pin), ms3Pin(ms3Pin)
-{
-    if (speed < 0)
-        speed = 0;
-
-    setMicrostepping(microstep);
-
-    // Start with the motor disabled
-    disable();
-}
-
-inline void Stepper::enable() { enablePin.low(); }
-
-inline void Stepper::disable() { enablePin.high(); }
-
 inline void Stepper::setSpeed(float speed) { this->speed = speed; }
 
-inline void Stepper::setMicrostepping(Microstep microstep)
+inline void Stepper::setMicroStepping(uint16_t microStep)
 {
-    this->microstep = microstep;
-
-    switch (microstep)
-    {
-        case Microstep::MICROSTEP_1:
-            ms1Pin.low();
-            ms2Pin.low();
-            ms3Pin.low();
-            break;
-        case Microstep::MICROSTEP_2:
-            ms1Pin.high();
-            ms2Pin.low();
-            ms3Pin.low();
-            break;
-        case Microstep::MICROSTEP_4:
-            ms1Pin.low();
-            ms2Pin.high();
-            ms3Pin.low();
-            break;
-        case Microstep::MICROSTEP_8:
-            ms1Pin.high();
-            ms2Pin.high();
-            ms3Pin.low();
-            break;
-        case Microstep::MICROSTEP_16:
-            ms1Pin.high();
-            ms2Pin.high();
-            ms3Pin.high();
-            break;
-    }
+    this->microStep = microStep;
 }
 
-void Stepper::zeroPosition() { currentPosition = 0; }
-
-inline void Stepper::move(int32_t steps)
+inline void Stepper::zeroPosition(float degrees)
 {
-    if (speed == 0)
-        return;
-
-    unsigned int halfStepDelay =
-        1e6 / (speed * 360 / stepAngle * getMicrosteppingValue());
-
-    if (revertDirection == (steps >= 0))
-        directionPin.low();
-    else
-        directionPin.high();
-    miosix::delayUs(halfStepDelay);
-
-    int32_t stepsAbs = steps > 0 ? steps : -steps;
-    for (int i = 0; i < stepsAbs; i++)
-    {
-        stepPin.high();
-        miosix::delayUs(halfStepDelay);
-        stepPin.low();
-        miosix::delayUs(halfStepDelay);
-    }
-
-    currentPosition += steps;
+    currentPositionDeg = degrees;
 }
 
 inline void Stepper::moveDeg(float degrees)
 {
-    move(degrees / stepAngle * getMicrosteppingValue());
+    move(degrees * microStep / stepAngle);
 }
 
-inline void Stepper::setPosition(int32_t steps)
+inline void Stepper::setPosition(int16_t steps)
 {
-    move(steps - currentPosition);
+    setPositionDeg(steps * stepAngle / microStep);
 }
 
 inline void Stepper::setPositionDeg(float position)
 {
-    setPosition(position / stepAngle * getMicrosteppingValue());
+    moveDeg(position - getCurrentDegPosition());
 }
 
-inline uint32_t Stepper::getCurrentPosition() { return currentPosition; }
-
-inline float Stepper::getCurrentDegPosition()
+inline int16_t Stepper::getCurrentPosition()
 {
-    return currentPosition * stepAngle / getMicrosteppingValue();
+    return getCurrentDegPosition() * microStep / stepAngle;
 }
 
-inline int Stepper::getMicrosteppingValue()
-{
-    switch (microstep)
-    {
-        case Microstep::MICROSTEP_1:
-            return 1;
-        case Microstep::MICROSTEP_2:
-            return 2;
-        case Microstep::MICROSTEP_4:
-            return 4;
-        case Microstep::MICROSTEP_8:
-            return 8;
-        case Microstep::MICROSTEP_16:
-            return 16;
-    }
-
-    return 1;
-}
+inline float Stepper::getCurrentDegPosition() { return currentPositionDeg; }
 
-}  // namespace Boardcore
+}  // namespace Boardcore
\ No newline at end of file
diff --git a/src/shared/actuators/StepperPWM.cpp b/src/shared/actuators/StepperPWM.cpp
new file mode 100644
index 000000000..e9eccf5ff
--- /dev/null
+++ b/src/shared/actuators/StepperPWM.cpp
@@ -0,0 +1,99 @@
+/* Copyright (c) 2022 Skyward Experimental Rocketry
+ * Authors: Alberto Nidasio, Emilio Corigliano
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#include "StepperPWM.h"
+
+namespace Boardcore
+{
+
+StepperPWM::StepperPWM(CountedPWM &pwm, miosix::GpioPin stepPin,
+                       miosix::GpioPin directionPin, float speed,
+                       float stepAngle, bool revertDirection,
+                       uint16_t microStep, PinConfiguration pinConfiguration,
+                       miosix::GpioPin enablePin)
+    : Stepper(stepPin, directionPin, speed, stepAngle, revertDirection,
+              microStep, pinConfiguration, enablePin),
+      pwm(pwm)
+{
+    if (this->speed < 0)
+        this->speed = 0;
+
+    Stepper::setDirection();
+    StepperPWM::setMicroStepping(microStep);
+
+    // Start with the motor disabled
+    disable();
+}
+
+void StepperPWM::setSpeed(float speed)
+{
+    this->speed = speed;
+    pwm.setFrequency(speed * 360 / stepAngle * microStep);
+}
+
+void StepperPWM::setMicroStepping(uint16_t microStep)
+{
+    // Resize the pulses to generate and the current pulses generated to use the
+    // new microsteps (keeping constant the angle of movement)
+    pwm.updateTargetCount(pwm.getTargetCount() * this->microStep / microStep);
+    pwm.updateCurrentCount(pwm.getCurrentCount() * this->microStep / microStep);
+
+    Stepper::setMicroStepping(microStep);
+    pwm.setFrequency(speed * 360 / stepAngle * microStep);
+}
+
+void StepperPWM::move(int16_t steps)
+{
+    if (speed == 0 || steps == 0)
+        return;
+
+    // First update currentPositionDeg. This method corrects the initial
+    // position saved with the current state of the CountTimer counter register,
+    // so that we account only for the pulses actually generated.
+    currentPositionDeg = getCurrentDegPosition();
+
+    if (steps > 0)
+    {
+        // Go forward
+        currentDirection = Direction::CLOCKWISE;
+        Stepper::setDirection();
+
+        // Move
+        pwm.generatePulses(steps);
+    }
+    else
+    {
+        // Go backwards
+        currentDirection = Direction::COUNTER_CLOCKWISE;
+        Stepper::setDirection();
+
+        // Move
+        pwm.generatePulses(-steps);
+    }
+}
+
+float StepperPWM::getCurrentDegPosition()
+{
+    return currentPositionDeg +
+           currentDirection * pwm.getCurrentCount() * stepAngle / microStep;
+}
+
+}  // namespace Boardcore
\ No newline at end of file
diff --git a/src/shared/actuators/StepperPWM.h b/src/shared/actuators/StepperPWM.h
new file mode 100644
index 000000000..6c91c24b7
--- /dev/null
+++ b/src/shared/actuators/StepperPWM.h
@@ -0,0 +1,104 @@
+/* Copyright (c) 2022 Skyward Experimental Rocketry
+ * Authors: Alberto Nidasio, Emilio Corigliano
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#pragma once
+
+#include <interfaces-impl/gpio_impl.h>
+#include <interfaces/delays.h>
+#include <utils/TestUtils/MockGpioPin.h>
+
+#include "Stepper.h"
+#include "drivers/timer/CountedPWM.h"
+
+namespace Boardcore
+{
+
+class StepperPWM : public Stepper
+{
+public:
+    /**
+     * @brief Construct a new StepperPWM object.
+     *
+     * By default the direction pin is held low when moving forward.
+     * If the given speed is negative the motor wont move.
+     *
+     * @param pwm The CountedPWM object that will generate the N steps.
+     * @param stepPin Pin connected to the step signal of the driver.
+     * @param directionPin Pin connected to the direction signal of the driver.
+     * @param speed Number of rotations per second.
+     * @param stepAngle Angle covered by one motor step.
+     * @param revertDirection Whether or not revert the direction signal.
+     */
+    StepperPWM(
+        CountedPWM &pwm, miosix::GpioPin stepPin, miosix::GpioPin directionPin,
+        float speed = 1, float stepAngle = 1.8, bool revertDirection = false,
+        uint16_t microStep                = 1,
+        PinConfiguration pinConfiguration = PinConfiguration::COMMON_CATHODE,
+        miosix::GpioPin enablePin         = MockGpioPin());
+
+    /**
+     * @brief Changes the stepper motor speed.
+     *
+     * @param speed Speed in rotation per second. [rev/s]
+     */
+    void setSpeed(float speed) override;
+
+    /**
+     * @brief Set the motor driver micro stepping configuration.
+     *
+     * If micro steps are changed via physical switches we have to call this
+     * method with the correct configuration to keep consistency in the speed of
+     * the stepper.
+     * @param microStep The micro steps that the stepper performs.
+     */
+    void setMicroStepping(uint16_t microStep) override;
+
+    /**
+     * @brief Move the stepper motor by the specified amount of steps.
+     */
+    void move(int16_t steps) override;
+
+    /**
+     * @brief Returns the current angle of the stepper.
+     *
+     * To calculate the correct angle of the stepper we add the angle reached in
+     * the last actuation (saved in the variable `currentPositionDeg`) with the
+     * conversion in angle of the actual stepper position (so, the number of
+     * pulses actuated by the timer till now, that corresponds to the counter
+     * register of the CounterTimer).
+     *
+     * Notice that on each actuation (calling one of the move or setPosition
+     * methods) we save the angle reached using this method.
+     */
+    float getCurrentDegPosition() override;
+
+private:
+    // This class is not copyable!
+    StepperPWM &operator=(const StepperPWM &) = delete;
+    StepperPWM(const StepperPWM &p)           = delete;
+
+    void setDirection();
+
+    CountedPWM &pwm;
+};
+
+}  // namespace Boardcore
\ No newline at end of file
diff --git a/src/shared/drivers/timer/CountedPWM.cpp b/src/shared/drivers/timer/CountedPWM.cpp
new file mode 100644
index 000000000..8c49b3e85
--- /dev/null
+++ b/src/shared/drivers/timer/CountedPWM.cpp
@@ -0,0 +1,147 @@
+/* Copyright (c) 2023 Skyward Experimental Rocketry
+ * Authors: Emilio Corigliano, Alberto Nidasio
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "CountedPWM.h"
+
+namespace Boardcore
+{
+
+CountedPWM::CountedPWM(TIM_TypeDef* const pulseTimer,
+                       TimerUtils::Channel const pulseChannel,
+                       TimerUtils::TriggerSource const pulseTriggerSource,
+                       TIM_TypeDef* const counterTimer,
+                       TimerUtils::Channel const counterChannel,
+                       TimerUtils::TriggerSource const counterTriggerSource)
+    : pulseTimer(pulseTimer), pulseChannel(pulseChannel),
+      pulseTriggerSource(pulseTriggerSource), counterTimer(counterTimer),
+      counterChannel(counterChannel), counterTriggerSource(counterTriggerSource)
+{
+    // Erase the previous timer configuration
+    this->pulseTimer.reset();
+    this->counterTimer.reset();
+
+    configureTimers();
+
+    // Keep the timers always enabled. The clock of the pwm timer will be
+    // enabled based on the output of the counter timer
+    this->pulseTimer.enable();
+    this->counterTimer.enable();
+}
+
+CountedPWM::~CountedPWM()
+{
+    pulseTimer.reset();
+    counterTimer.reset();
+}
+
+void CountedPWM::setFrequency(unsigned int pulseFrequency)
+{
+    this->pulseFrequency = pulseFrequency;
+    pulseTimer.setFrequency(pulseFrequency * dutyCycleResolution);
+    pulseTimer.setAutoReloadRegister(
+        TimerUtils::getFrequency(pulseTimer.getTimer()) / pulseFrequency);
+}
+
+void CountedPWM::setDutyCycle(float dutyCycle)
+{
+    if (dutyCycle >= 0 && dutyCycle <= 1)
+    {
+        this->dutyCycle = dutyCycle;
+        pulseTimer.setCaptureCompareRegister(
+            pulseChannel,
+            static_cast<uint16_t>(
+                dutyCycle * pulseTimer.readAutoReloadRegister() + 0.5));
+    }
+}
+
+void CountedPWM::setDutyCycleResolution(unsigned int dutyCycleResolution)
+{
+    this->dutyCycleResolution = dutyCycleResolution;
+    setFrequency(pulseFrequency);
+}
+
+void CountedPWM::generatePulses(uint16_t pulses)
+{
+    // Reset the counters
+    pulseTimer.setCounter(0);
+    counterTimer.setCounter(0);
+
+    // Set the capture and compare register to the number of pulses to generate
+    counterTimer.setCaptureCompareRegister(counterChannel, pulses);
+}
+
+bool CountedPWM::isGenerating()
+{
+    return counterTimer.readCounter() !=
+           counterTimer.readCaptureCompareRegister(counterChannel);
+}
+
+void CountedPWM::configureTimers()
+{
+    // PWM timer
+    {
+        setFrequency(pulseFrequency);
+
+        // Output/Master: Select TRGO source
+        pulseTimer.setMasterMode(masterModeFromChannel(pulseChannel));
+
+        // Input/Slave: Enable the pulseTimer when the output from the
+        // counterTimer is high
+        pulseTimer.setTriggerSource(pulseTriggerSource);
+        pulseTimer.setSlaveMode(TimerUtils::SlaveMode::GATED_MODE);
+
+        // Capture Compare Channel setup in PWM mode
+        pulseTimer.setOutputCompareMode(
+            pulseChannel, TimerUtils::OutputCompareMode::PWM_MODE_1);
+        setDutyCycle(dutyCycle);
+        pulseTimer.enableCaptureCompareOutput(pulseChannel);
+
+        // Force the timer to update its configuration
+        pulseTimer.generateUpdate();
+    }
+
+    // Counter timer
+    {
+        counterTimer.setAutoReloadRegister(-1);
+
+        // Output/Master: Select TRGO source
+        counterTimer.setMasterMode(masterModeFromChannel(counterChannel));
+
+        // Input/Slave: Use the output from counterTimer as clock for pulseTimer
+        counterTimer.setTriggerSource(counterTriggerSource);
+        counterTimer.setSlaveMode(TimerUtils::SlaveMode::EXTERNAL_CLOCK_MODE_1);
+
+        // Capture Compare Channel setup in PWM mode
+        counterTimer.setOutputCompareMode(
+            counterChannel, TimerUtils::OutputCompareMode::PWM_MODE_1);
+        counterTimer.setCaptureCompareRegister(counterChannel, 0);
+        counterTimer.enableCaptureCompareOutput(counterChannel);
+
+        // The output is enabled also for the counterTimer if the user wants to
+        // output an enable signal
+
+        // Force the timer to update its configuration
+        counterTimer.generateUpdate();
+    }
+}
+
+}  // namespace Boardcore
\ No newline at end of file
diff --git a/src/shared/drivers/timer/CountedPWM.h b/src/shared/drivers/timer/CountedPWM.h
new file mode 100644
index 000000000..30fe01640
--- /dev/null
+++ b/src/shared/drivers/timer/CountedPWM.h
@@ -0,0 +1,198 @@
+/* Copyright (c) 2023 Skyward Experimental Rocketry
+ * Authors: Emilio Corigliano, Alberto Nidasio
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#pragma once
+
+#include <assert.h>
+#include <drivers/timer/GeneralPurposeTimer.h>
+
+#include "PWM.h"
+#include "utils/Debug.h"
+
+namespace Boardcore
+{
+
+/**
+ * @brief This class generates a PWM signal for a chosen number of pulses.
+ *
+ * Two timers are used: PulseTimer and CounterTimer.
+ *
+ * +------------+  OverflowEvent   UpdateCounter  +--------------+
+ * | PulseTimer | ------------------------------> | CounterTimer |
+ * |            |                                 |              |
+ * |            |                                 |              |
+ * | PWM, GATED | <------------------------------ |  ExtClkMode  |
+ * +------------+  StopCounter     OverflowEvent  +--------------+
+ *
+ * As seen in the previous scheme, PulseTimer generates the PWM signal. This
+ * is seen both as output on the respective GPIO and as input for the
+ * CounterTimer. So, in this way, CounterTimer counts the pulses generated and,
+ * when a threshold is passed, the CounterTimer changes its output polarity.
+ * Since PulseTimer has as GATED input the CounterTimer, when the N pulses are
+ * exceeded, the PulseTimer is stopped and no other cycles are generated. This
+ * works because since we disable the PulseTimer, the CounterTimer doesn't
+ * exceed the Capture and Compare value set (which corresponds to the wanted
+ * pulses).
+ *
+ * The requirement for this technique to work is that the two timers should have
+ * an "internal trigger input" (ITR) connection to each other.
+ */
+class CountedPWM
+{
+public:
+    /**
+     * @brief Constructor that initializes the timers.
+     *
+     * In particular, the pulseTimer will be used to create the output PWM
+     * signal while the counterTimer will just count for the occurred pulses.
+     * The PWM signal will be transmitted on the `pulseChannel` channel while
+     * the counterTimer signal will be accessible on the `counterChannel`
+     * channel (for debug purposes).
+     * @param pulseTimer Timer used for the generation of the PWM signal.
+     * @param pulseChannel Channel of pulseTimer on which the PWM signal will be
+     * generated.
+     * @param pulseTriggerSource The corresponding ITR mode of the pulseTimer
+     * for having as external trigger input the counterTimer.
+     * @param counterTimer Timer used for counting the pulses generated by the
+     * PWM.
+     * @param counterChannel Channel of pulseTimer on which the counter signal
+     * will be visible.
+     * @param counterTriggerSource The corresponding ITR mode of the
+     * counterTimer for having as external trigger input the pulseTimer.
+     */
+    CountedPWM(TIM_TypeDef* const pulseTimer,
+               TimerUtils::Channel const pulseChannel,
+               TimerUtils::TriggerSource const pulseTriggerSource,
+               TIM_TypeDef* const counterTimer,
+               TimerUtils::Channel const counterChannel,
+               TimerUtils::TriggerSource const counterTriggerSource);
+
+    ~CountedPWM();
+
+    void setFrequency(unsigned int pulseFrequency);
+
+    /**
+     * @brief Sets the duty cycle for the specified channel.
+     *
+     * Changes the duty cycle only if the specified value is in the range [0,1].
+     *
+     * @param dutyCycle Relative duty cycle, ranges from 0 to 1.
+     */
+    void setDutyCycle(float dutyCycle);
+
+    /**
+     * @brief Sets the granularity of the PulseTimer duty cycle. So it sets the
+     * Auto Reload Register of the PulseTimer.
+     */
+    void setDutyCycleResolution(unsigned int dutyCycleResolution);
+
+    /**
+     * @brief Triggers the generation of a specific number of PWM periods.
+     *
+     * Note that the function returns immediately and the PWM signal is
+     * generated in background by the timers.
+     *
+     * @param pulses The number of pulses to generate.
+     */
+    void generatePulses(uint16_t pulses);
+
+    /**
+     * @brief Returns whether the timers are generating the PWM signal or not.
+     */
+    bool isGenerating();
+
+    /**
+     * @brief Allows to update the target periods count while the timers are
+     * generating the signal.
+     */
+    void updateTargetCount(uint16_t pulses);
+
+    /**
+     * @brief Allows to get the target periods count while the timers are
+     * generating the signal.
+     */
+    uint16_t getTargetCount();
+
+    /**
+     * @brief Allows to update the current periods count while the timers are
+     * generating the signal.
+     */
+    void updateCurrentCount(uint16_t count);
+
+    /**
+     * @brief Returns the number of periods counted until now.
+     */
+    uint16_t getCurrentCount();
+
+    /**
+     * @brief Stops the PWM signal generation if it is ongoing.
+     */
+    void stop();
+
+private:
+    // This class is not copyable!
+    CountedPWM& operator=(const CountedPWM&) = delete;
+    CountedPWM(const CountedPWM& p)          = delete;
+
+    void configureTimers();
+
+    GP16bitTimer pulseTimer;
+    TimerUtils::Channel pulseChannel;
+    TimerUtils::TriggerSource pulseTriggerSource;
+
+    GP16bitTimer counterTimer;
+    TimerUtils::Channel counterChannel;
+    TimerUtils::TriggerSource counterTriggerSource;
+
+    unsigned int pulseFrequency = 50;   ///< Frequency of the pulses [Hz]
+    float dutyCycle             = 0.5;  ///< DutyCycle of the pulses [%]
+    unsigned int dutyCycleResolution =
+        20000;  ///< Basically this is the Auto Reload Register of the
+                ///< PulseTimer
+};
+
+inline void CountedPWM::updateTargetCount(uint16_t pulses)
+{
+    counterTimer.setCaptureCompareRegister(counterChannel, pulses);
+}
+
+inline uint16_t CountedPWM::getTargetCount()
+{
+    return counterTimer.readCaptureCompareRegister(counterChannel);
+}
+
+inline void CountedPWM::updateCurrentCount(uint16_t count)
+{
+    return counterTimer.setCounter(count);
+}
+
+inline uint16_t CountedPWM::getCurrentCount()
+{
+    return counterTimer.readCounter();
+}
+
+inline void CountedPWM::stop()
+{
+    counterTimer.setCaptureCompareRegister(counterChannel, getCurrentCount());
+}
+
+}  // namespace Boardcore
\ No newline at end of file
diff --git a/src/tests/actuators/test-stepper-pwm.cpp b/src/tests/actuators/test-stepper-pwm.cpp
new file mode 100644
index 000000000..f1a2101a4
--- /dev/null
+++ b/src/tests/actuators/test-stepper-pwm.cpp
@@ -0,0 +1,147 @@
+/* Copyright (c) 2023 Skyward Experimental Rocketry
+ * Author: Emilio Corigliano
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <actuators/StepperPWM.h>
+#include <drivers/timer/CountedPWM.h>
+#include <miosix.h>
+
+#include <thread>
+
+using namespace miosix;
+using namespace Boardcore;
+
+GpioPin directionPin = GpioPin(GPIOC_BASE, 12);
+GpioPin enablePin    = GpioPin(GPIOC_BASE, 10);
+
+GpioPin stepPin = GpioPin(GPIOB_BASE, 4);
+GpioPin ms1Pin  = GpioPin(GPIOE_BASE, 6);
+GpioPin ms2Pin  = GpioPin(GPIOE_BASE, 2);
+GpioPin ms3Pin  = GpioPin(GPIOE_BASE, 4);
+
+void setMicroStepping(StepperPWM& stepper, int16_t microStep)
+{
+    switch (microStep)
+    {
+        case 1:
+            ms1Pin.low();
+            ms2Pin.low();
+            ms3Pin.low();
+            break;
+        case 2:
+            ms1Pin.high();
+            ms2Pin.low();
+            ms3Pin.low();
+            break;
+        case 4:
+            ms1Pin.low();
+            ms2Pin.high();
+            ms3Pin.low();
+            break;
+        case 8:
+            ms1Pin.high();
+            ms2Pin.high();
+            ms3Pin.low();
+            break;
+        case 16:
+            ms1Pin.high();
+            ms2Pin.high();
+            ms3Pin.high();
+            break;
+        default:
+            printf("Microsteps value not available!\n");
+            return;
+    }
+    stepper.setMicroStepping(microStep);
+}
+
+void doRoutine(StepperPWM& stepper)
+{
+    stepper.enable();
+
+    for (int i = 1; i < 5; i++)
+    {
+        stepper.setSpeed(i);
+
+        stepper.setPositionDeg(180);
+        delayMs(2 * 1000);
+        stepper.setPositionDeg(90);
+        delayMs(2 * 1000);
+        stepper.setPositionDeg(180);
+        delayMs(2 * 1000);
+        stepper.setPositionDeg(90);
+        delayMs(2 * 1000);
+        stepper.setPosition(0);
+        delayMs(2 * 1000);
+        stepper.moveDeg(-180);
+        delayMs(2 * 1000);
+        stepper.moveDeg(-360);
+        delayMs(2 * 1000);
+        stepper.setPosition(0);
+        delayMs(5 * 1000);
+    }
+
+    stepper.disable();
+}
+
+int main()
+{
+    // Configure stepper motor pins
+    directionPin.mode(Mode::OUTPUT);
+    enablePin.mode(Mode::OUTPUT);
+    ms3Pin.mode(Mode::OUTPUT);
+    ms2Pin.mode(Mode::OUTPUT);
+    ms1Pin.mode(Mode::OUTPUT);
+    stepPin.mode(Mode::ALTERNATE);
+    stepPin.alternateFunction(2);
+
+    CountedPWM pwm(
+        TIM3, TimerUtils::Channel::CHANNEL_1, TimerUtils::TriggerSource::ITR3,
+        TIM4, TimerUtils::Channel::CHANNEL_1, TimerUtils::TriggerSource::ITR2);
+
+    StepperPWM stepper(pwm, stepPin, directionPin, 1, 1.8, false, 8,
+                       Stepper::PinConfiguration::COMMON_CATHODE, enablePin);
+    setMicroStepping(stepper, 8);
+
+    printf("The stepper is now disabled, waiting 2 seconds...\n");
+    delayMs(2 * 1000);
+
+    std::thread th(
+        [&stepper]()
+        {
+            while (true)
+            {
+                printf("Current position: %.2f\n",
+                       stepper.getCurrentDegPosition());
+                Thread::sleep(100);
+            }
+        });
+    th.detach();
+
+    while (true)
+    {
+        printf("Press something to start\n");
+        scanf("%*s");
+
+        doRoutine(stepper);
+        Thread::sleep(10 * 1000);
+    }
+}
\ No newline at end of file
diff --git a/src/tests/actuators/test-stepper.cpp b/src/tests/actuators/test-stepper.cpp
index 707f22789..6ee595f8c 100644
--- a/src/tests/actuators/test-stepper.cpp
+++ b/src/tests/actuators/test-stepper.cpp
@@ -25,15 +25,51 @@
 using namespace miosix;
 using namespace Boardcore;
 
-miosix::GpioPin directionPin = miosix::GpioPin(GPIOC_BASE, 13);
-miosix::GpioPin stepPin      = miosix::GpioPin(GPIOC_BASE, 14);
-miosix::GpioPin resetPin     = miosix::GpioPin(GPIOE_BASE, 5);
-miosix::GpioPin ms1Pin       = miosix::GpioPin(GPIOE_BASE, 6);
-miosix::GpioPin ms2Pin       = miosix::GpioPin(GPIOE_BASE, 2);
-miosix::GpioPin ms3Pin       = miosix::GpioPin(GPIOE_BASE, 4);
-miosix::GpioPin enablePin    = miosix::GpioPin(GPIOC_BASE, 15);
-
-void doRoutine(Stepper stepper)
+GpioPin directionPin = GpioPin(GPIOC_BASE, 12);
+GpioPin stepPin      = GpioPin(GPIOB_BASE, 4);
+GpioPin enablePin    = GpioPin(GPIOC_BASE, 10);
+GpioPin resetPin     = GpioPin(GPIOE_BASE, 5);
+GpioPin ms1Pin       = GpioPin(GPIOE_BASE, 6);
+GpioPin ms2Pin       = GpioPin(GPIOE_BASE, 2);
+GpioPin ms3Pin       = GpioPin(GPIOE_BASE, 4);
+
+void setMicroStepping(Stepper& stepper, int16_t microStep)
+{
+    switch (microStep)
+    {
+        case 1:
+            ms1Pin.low();
+            ms2Pin.low();
+            ms3Pin.low();
+            break;
+        case 2:
+            ms1Pin.high();
+            ms2Pin.low();
+            ms3Pin.low();
+            break;
+        case 4:
+            ms1Pin.low();
+            ms2Pin.high();
+            ms3Pin.low();
+            break;
+        case 8:
+            ms1Pin.high();
+            ms2Pin.high();
+            ms3Pin.low();
+            break;
+        case 16:
+            ms1Pin.high();
+            ms2Pin.high();
+            ms3Pin.high();
+            break;
+        default:
+            printf("Microsteps value not available!\n");
+            return;
+    }
+    stepper.setMicroStepping(microStep);
+}
+
+void doRoutine(Stepper& stepper)
 {
     stepper.enable();
 
@@ -71,18 +107,17 @@ void doRoutine(Stepper stepper)
 
 int main()
 {
-    directionPin.mode(miosix::Mode::OUTPUT);
-    stepPin.mode(miosix::Mode::OUTPUT);
-    resetPin.mode(miosix::Mode::OUTPUT);
+    directionPin.mode(Mode::OUTPUT);
+    stepPin.mode(Mode::OUTPUT);
+    resetPin.mode(Mode::OUTPUT);
     resetPin.high();
-    ms3Pin.mode(miosix::Mode::OUTPUT);
-    ms2Pin.mode(miosix::Mode::OUTPUT);
-    ms1Pin.mode(miosix::Mode::OUTPUT);
-    enablePin.mode(miosix::Mode::OUTPUT);
+    ms3Pin.mode(Mode::OUTPUT);
+    ms2Pin.mode(Mode::OUTPUT);
+    ms1Pin.mode(Mode::OUTPUT);
+    enablePin.mode(Mode::OUTPUT);
 
-    Stepper stepper(stepPin, directionPin, 1, 1.8, false,
-                    Stepper::Microstep::MICROSTEP_1, enablePin, ms1Pin, ms2Pin,
-                    ms3Pin);
+    Stepper stepper(stepPin, directionPin, 1, 1.8, false, 8,
+                    Stepper::PinConfiguration::COMMON_CATHODE, enablePin);
 
     printf("The stepper is now disabled, waiting 2 seconds...\n");
     delayMs(2 * 1000);
@@ -90,38 +125,38 @@ int main()
     while (true)
     {
         printf("Press something to start\n");
-        getchar();
+        scanf("%*s");
 
-        printf("\t1x microstepping\n");
-        stepper.setMicrostepping(Stepper::Microstep::MICROSTEP_1);
+        printf("\t1x micro stepping\n");
+        setMicroStepping(stepper, 8);
         doRoutine(stepper);
         printf("The stepper is now disabled\n");
 
         delayMs(1000);
 
-        printf("\t2x microstepping\n");
-        stepper.setMicrostepping(Stepper::Microstep::MICROSTEP_2);
+        printf("\t2x micro stepping\n");
+        setMicroStepping(stepper, 8);
         doRoutine(stepper);
         printf("The stepper is now disabled\n");
 
         delayMs(1000);
 
-        printf("\t4x microstepping\n");
-        stepper.setMicrostepping(Stepper::Microstep::MICROSTEP_4);
+        printf("\t4x micro stepping\n");
+        setMicroStepping(stepper, 8);
         doRoutine(stepper);
         printf("The stepper is now disabled\n");
 
         delayMs(1000);
 
-        printf("\t8x microstepping\n");
-        stepper.setMicrostepping(Stepper::Microstep::MICROSTEP_8);
+        printf("\t8x micro stepping\n");
+        setMicroStepping(stepper, 8);
         doRoutine(stepper);
         printf("The stepper is now disabled\n");
 
         delayMs(1000);
 
-        printf("\t16x microstepping\n");
-        stepper.setMicrostepping(Stepper::Microstep::MICROSTEP_16);
+        printf("\t16x micro stepping\n");
+        setMicroStepping(stepper, 8);
         doRoutine(stepper);
         printf("The stepper is now disabled\n");
     }
diff --git a/src/tests/drivers/timer/test-counted-pwm.cpp b/src/tests/drivers/timer/test-counted-pwm.cpp
new file mode 100644
index 000000000..497f33a86
--- /dev/null
+++ b/src/tests/drivers/timer/test-counted-pwm.cpp
@@ -0,0 +1,93 @@
+/* Copyright (c) 2023 Skyward Experimental Rocketry
+ * Authors: Emilio Corigliano, Alberto Nidasio
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <drivers/timer/CountedPWM.h>
+#include <miosix.h>
+
+#include <thread>
+
+using namespace miosix;
+using namespace Boardcore;
+
+/**
+ * This test shows how to use a CountedPWM to generate N pulses all done by
+ * hardware!
+ */
+
+typedef Gpio<GPIOB_BASE, 4> tim3ch1;   // AF2
+typedef Gpio<GPIOD_BASE, 12> tim4ch1;  // AF2
+
+int main()
+{
+    tim3ch1::mode(Mode::ALTERNATE);
+    tim3ch1::alternateFunction(2);
+
+    tim4ch1::mode(Mode::ALTERNATE);
+    tim4ch1::alternateFunction(2);
+
+    CountedPWM pwm(
+        TIM3, TimerUtils::Channel::CHANNEL_1, TimerUtils::TriggerSource::ITR3,
+        TIM4, TimerUtils::Channel::CHANNEL_1, TimerUtils::TriggerSource::ITR2);
+    pwm.setFrequency(2);
+    pwm.setDutyCycle(0.5);
+
+    std::thread th(
+        [&]()
+        {
+            while (true)
+            {
+                printf("[%.2f] Counter: %d\tTarget: %ld\tIs generating: %d\n",
+                       getTick() / 1000.0, pwm.getCurrentCount(), TIM4->CCR1,
+                       pwm.isGenerating());
+                Thread::sleep(250);
+            }
+        });
+    th.detach();
+
+    Thread::sleep(1000);
+
+    pwm.generatePulses(4);
+    Thread::sleep(1000);
+    pwm.generatePulses(6);
+    Thread::sleep(1000);
+    pwm.generatePulses(3);
+
+    Thread::sleep(5 * 1000);
+
+    pwm.generatePulses(4);
+    Thread::sleep(1000);
+    pwm.updateTargetCount(6);
+
+    Thread::sleep(5 * 1000);
+
+    pwm.generatePulses(6);
+    while (pwm.getCurrentCount() < 3)
+        ;
+    pwm.stop();
+
+    pwm.setFrequency(10);
+    while (true)
+    {
+        pwm.generatePulses(8);
+        Thread::sleep(2500);
+    }
+}
-- 
GitLab