element14 Community
element14 Community
    Register Log In
  • Site
  • Search
  • Log In Register
  • Community Hub
    Community Hub
    • What's New on element14
    • Feedback and Support
    • Benefits of Membership
    • Personal Blogs
    • Members Area
    • Achievement Levels
  • Learn
    Learn
    • Ask an Expert
    • eBooks
    • element14 presents
    • Learning Center
    • Tech Spotlight
    • STEM Academy
    • Webinars, Training and Events
    • Learning Groups
  • Technologies
    Technologies
    • 3D Printing
    • FPGA
    • Industrial Automation
    • Internet of Things
    • Power & Energy
    • Sensors
    • Technology Groups
  • Challenges & Projects
    Challenges & Projects
    • Design Challenges
    • element14 presents Projects
    • Project14
    • Arduino Projects
    • Raspberry Pi Projects
    • Project Groups
  • Products
    Products
    • Arduino
    • Avnet & Tria Boards Community
    • Dev Tools
    • Manufacturers
    • Multicomp Pro
    • Product Groups
    • Raspberry Pi
    • RoadTests & Reviews
  • About Us
    About the element14 Community
  • Store
    Store
    • Visit Your Store
    • Choose another store...
      • Europe
      •  Austria (German)
      •  Belgium (Dutch, French)
      •  Bulgaria (Bulgarian)
      •  Czech Republic (Czech)
      •  Denmark (Danish)
      •  Estonia (Estonian)
      •  Finland (Finnish)
      •  France (French)
      •  Germany (German)
      •  Hungary (Hungarian)
      •  Ireland
      •  Israel
      •  Italy (Italian)
      •  Latvia (Latvian)
      •  
      •  Lithuania (Lithuanian)
      •  Netherlands (Dutch)
      •  Norway (Norwegian)
      •  Poland (Polish)
      •  Portugal (Portuguese)
      •  Romania (Romanian)
      •  Russia (Russian)
      •  Slovakia (Slovak)
      •  Slovenia (Slovenian)
      •  Spain (Spanish)
      •  Sweden (Swedish)
      •  Switzerland(German, French)
      •  Turkey (Turkish)
      •  United Kingdom
      • Asia Pacific
      •  Australia
      •  China
      •  Hong Kong
      •  India
      •  Japan
      •  Korea (Korean)
      •  Malaysia
      •  New Zealand
      •  Philippines
      •  Singapore
      •  Taiwan
      •  Thailand (Thai)
      •  Vietnam
      • Americas
      •  Brazil (Portuguese)
      •  Canada
      •  Mexico (Spanish)
      •  United States
      Can't find the country/region you're looking for? Visit our export site or find a local distributor.
  • Translate
  • Profile
  • Settings
Embedded and Microcontrollers
  • Technologies
  • More
Embedded and Microcontrollers
Blog MSPM0 project with VS Code, CMake and GCC - part 7b: middleware stepper motor control with A4988 stepper driver
  • Blog
  • Forum
  • Documents
  • Quiz
  • Polls
  • Files
  • Members
  • Mentions
  • Sub-Groups
  • Tags
  • More
  • Cancel
  • New
Join Embedded and Microcontrollers to participate - click to join for free!
  • Share
  • More
  • Cancel
Group Actions
  • Group RSS
  • More
  • Cancel
Engagement
  • Author Author: Jan Cumps
  • Date Created: 26 Sep 2026 9:05 AM Date Created
  • Views 34 views
  • Likes 3 likes
  • Comments 2 comments
  • MSPM0
  • easyL1105
  • VS Code
  • MSPM0L1306
  • vscode
Related
Recommended

MSPM0 project with VS Code, CMake and GCC - part 7b: middleware stepper motor control with A4988 stepper driver

Jan Cumps
Jan Cumps
26 Sep 2026

In this blog series, I build a Texas Instruments MSPM0 example with VS Code as IDE, and CMake as build infrastructure. In the next series of posts, I try to create a stepper motor project. It will be based on TI's stepper motor middleware.

In the first post, I got TI's example working, with VS Code ad EDI. In this post, I'm switching to an Allegro A4988 stepper motor IC, and remove TI's GUI code.

this table contains a mix of human (me) and AI generated content

TI uses their HAL layer in the example. I used the driverlib API for the motor driver GPIO interaction. A  situation I'm not happy with (and that's the current state of the code attached to this blog). My idea was to remove the HAL completely and use driverlib throughout. 
That's an architectural decision, so I went and asked for advice. Google AI to the rescue?

> should I use mspm0 HAL?

You should use the MSPM0 HAL if you are building an application on top of Texas Instruments (TI) middleware (such as their Brushed, Stepper, or FOC Motor Control libraries) or if you plan to port your code across different microcontrollers.
However, if you are writing a standard, standalone application, you should use TI DriverLib combined with SysConfig instead.
Understanding the architectural distinction between the layers in the TI MSPM0 SDK is necessary to make the right choice:
Construction site️ The MSPM0 Software Stack Architecture
Texas Instruments divides its software ecosystem into distinct layers:
  1. MSPM0 DriverLib: A fully-featured, low-level API layer used to configure, control, and manipulate hardware peripherals directly.
  2. TI SysConfig (GUI): A graphical configuration tool that automatically generates DriverLib code and mapping definitions (#defines).
  3. HAL Module (Middleware Layer): A specialized abstraction layer built on top of DriverLib, utilizing SysConfig definitions. It is mostly packed inside specific TI middleware stacks (like the MSPM0 Motor Control Libraries).

White check mark When  to Use the MSPM0 HAL
  • Using TI Middleware: If you are implementing advanced algorithms like Field-Oriented Control (FOC), stepper motor driving, or energy metrology, TI’s libraries depend entirely on their HAL layer.
  • Portability Requirements: The HAL heavily utilizes enums to abstract hardware pins and peripherals. If you need to switch physical pins or migrate to a completely different chip later, you can swap things in the HAL without touching your core application logic.
Negative squared cross mark When to Skip the HAL (Use DriverLib + SysConfig Instead)
  • General Applications: If you are setting up standard timers, I2C, SPI, or UART lines, introducing the HAL layer creates unnecessary code abstraction, overhead, and complexity.
  • Performance-Critical Code: The HAL adds wrapper functions and generic enums to remain hardware-independent. Dropping down to DriverLib gives you faster execution and a smaller memory footprint because it compiles into direct register manipulations.

Scales️ Quick Decision Matrix
Project Requirement Recommended Tool Why?
Motor Control / Metrology HAL Module TI's pre-built libraries are hardcoded to hook straight into the HAL.
Standard Peripheral I/O DriverLib + SysConfig SysConfig handles the hardware visually, and DriverLib provides clean, direct control.
Maximum Execution Speed DriverLib Avoids the function-call overhead and pointer-indirection of an extra HAL abstraction layer.
Bulb The Verdict
Unless you are explicitly leveraging a TI Motor Control or Energy Metrology library, do not write your code using the MSPM0 HAL. Instead, open TI SysConfig to visually map your pins, and use DriverLib to write your functional application.

I tend to go with Gemini's advice. I'm using the motor middleware (albeit lightly), and may go for HAL in all the code. If you have another opinion, leave a comment

The driver motor class

This is a 1-to-1 port of a Pico class I made before:  Raspberry Pico stepper driver IC library, with example: Allegro A4988 . I only translated the GPIO calls form the Pico SDK to TI's APIs.

module;

/* Including all the sysconfig generated defines */
#include "ti_msp_dl_config.h"

import stepper_driver;

export module a4988_mspm0;

export namespace a4988_mspm0 {

class a4988_mspm0: public stepper_driver::stepper_driver {
public:
    a4988_mspm0( uint32_t n_enable, uint32_t ms1, uint32_t ms2,  uint32_t ms3, GPIO_Regs* gpio) :
        n_enable_(n_enable), ms1_(ms1), ms2_(ms2), ms3_(ms3), gpio_(gpio) {}
    virtual inline  bool init() override { 
        init_gpio();
        return true;
    }
    virtual bool microsteps(unsigned int microsteps) override { 
        unsigned int mode = true;
        switch (microsteps) {
        case 1:
            mode = 0x0000;
            break;
        case 2:
            mode = 0x0001;
            break;
        case 4:
            mode = 0x0002;
            break;
        case 8:
            mode = 0x0003;
            break;
        case 16:
            mode = 0x0007;
            break;
        default:
            // TODO check: assert(false); // develop check unsupported microstep
            mode = 0x0000;
        }
        put(ms1_, mode & 0b001);
        put(ms2_, mode & 0b010);
        put(ms3_, mode & 0b100);
        return true;
    }
    virtual void enable(bool enable) override {
        put(n_enable_, enable? false : true);
    }
    
private:
    uint32_t n_enable_;
    uint32_t ms1_;
    uint32_t ms2_;
    uint32_t ms3_;
    GPIO_Regs* gpio_;

    virtual void init_gpio() {
        // done by sysconfif
    }
    void put(uint32_t pin, bool set) {
        DL_GPIO_writePinsVal(gpio_, pin, set ? pin : 0U);
    }
}; // class a4988_mspm0

} // namespace a4988_mspm0

This class has support for motor enable, and microstep settings.

The PWM control class

My Pico projects used the PIO state machines as steppe motor STEP and DIR controllers. MSPM0 doesn't have anything like that, but they have capable PWM timers. I used those, and stayed as close as I could to how TI controls them in their stepper motor middleware example.

I replaced TI's DRV8889 middleware C driver with my own C++ class. I tried to stay as true as possible to TI's API breakdown.

module;

#include "ti_msp_dl_config.h"
#include "hal.h"

export module mspm0_pwm_stepper_driver;

export namespace mspm0_pwm_stepper_driver {

const uint8_t STEP_DUTYCYCLE = 50;

enum class control_mode {
    notset, // avoid that an initialised but unset variable assumes the first valid value
	step,
    spin
};

enum class drive_state {
    /*! Disabled mode */
    disabled = 0,
    /*! PWM mode */
    pwm
};

class mspm0_pwm_stepper_driver {
public:
    mspm0_pwm_stepper_driver(uint32_t dir, GPIO_Regs* gpio, uint32_t max_set_freq) : 
        dir(dir), gpio(gpio), max_set_freq(max_set_freq) {}
    void init() {
        // TODO
        HAL_enablePWMInterrupt(step);
    }

    void set_dir(bool fwd) {
        put(dir, fwd);
    }

    void set_control_mode(control_mode control_mode) {
        this->control_mode_ = control_mode;
    }

    void set_speed(uint32_t tim_freq) {
        if(tim_freq > max_set_freq) {
            /*
            * Timer frequency entered is above the Firmware limit.
            * If frequency is set higher it can affect the application code
            * execution rate and cause the gui to not function, thus loosing
            * control of motor.
            */
            __BKPT();
            tim_freq = max_set_freq;
        }
        set_freq = tim_freq;
        HAL_setPWMFreq(step, tim_freq);
        set_step_duty();
    }

    void set_step_duty() {
        HA_setPWMDutyCycle(step, STEP_DUTYCYCLE);
    }

    void check_step() {
        if(control_mode_ == control_mode::step)
        {
            step_count += 1;
            if(step_count >= set_step_)
            {
                step_count = 0;
                stop_motor();
            }
        }
    }

    void start_motor() {
        set_step_drive(drive_state::pwm);
        HAL_startPWMCounter(step);
    }

    void stop_motor() {
        HAL_stopPWMCounter(step);
        set_step_drive(drive_state::disabled);
    }

    void set_step(uint32_t set_steps) {
        set_step_ = set_steps;
    }

    void set_step_drive (drive_state drive_state) {
        drive_state_ = drive_state;
        if (drive_state_ == drive_state::disabled) {
            HAL_configurePWMPin(step, HAL_PWM_CHANNEL_MODE_FORCED_LOW);
        } else if (drive_state_ == drive_state::pwm) {
            HAL_configurePWMPin(step, HAL_PWM_CHANNEL_MODE_PWM);
        }
    }

    bool is_motor_running() {
        return HAL_isPWMCounterRunning(step);
    }
    
private:
    HAL_PWM_CHANNEL step;
    uint32_t dir;
    GPIO_Regs* gpio;
    uint32_t max_set_freq;
    control_mode control_mode_ = control_mode::step;
    uint32_t set_freq = 0;
    uint32_t step_count = 0;
    uint32_t set_step_ = 0;
    drive_state drive_state_ = drive_state::disabled;


    void put(uint32_t pin, bool set) {
        DL_GPIO_writePinsVal(gpio, pin, set ? pin : 0U);
    }
};

} // mspm0_pwm_stepper_driver

This class can generate PWM for step and for spin. There is control for constant speed.
In step mode, it relies on a PWM interrupt to call it every time it steps.

Putting it together: the main code

#include "ti_msp_dl_config.h"
#include "hal.h"

// import stepper_driver;              // wakeup class
import a4988_mspm0;                 // hw driver classes and registers
import mspm0_pwm_stepper_driver;    // pwm step and dir driver

const uint32_t dir = GPIO_GRP_DRIVER_PIN_DIR_PIN;
const uint32_t n_enable = GPIO_GRP_DRIVER_PIN_N_ENABLE_PIN;
const uint32_t ms1 = GPIO_GRP_DRIVER_PIN_MS1_PIN;
const uint32_t ms2 = GPIO_GRP_DRIVER_PIN_MS2_PIN;
const uint32_t ms3 = GPIO_GRP_DRIVER_PIN_MS3_PIN;

const uint32_t microstep_x = 1;
const uint32_t max_step_freq = 300000;

// object to manage the a4988 IC used for motor1
using driver_t = a4988_mspm0::a4988_mspm0;
driver_t driver1(n_enable, ms1, ms2, ms3, GPIO_GRP_DRIVER_PORT);

using pwm_stepper_driver_t = mspm0_pwm_stepper_driver::mspm0_pwm_stepper_driver;
pwm_stepper_driver_t pwm_stepper_driver1(dir, GPIO_GRP_DRIVER_PORT, max_step_freq);

int main() {
    SYSCFG_DL_init();

    /* Initialize the HAL module */
    HAL_init(); // TODO step away from HAL?

    driver1.init();
    driver1.microsteps(microstep_x);

    pwm_stepper_driver1.init();
    pwm_stepper_driver1.stop_motor();
    pwm_stepper_driver1.set_speed(100);
    pwm_stepper_driver1.set_control_mode(mspm0_pwm_stepper_driver::control_mode::step);

    /* Set the priority for interrupts */
    NVIC_SetPriority(GENERIC_PWM_CHANNEL_0_INST_INT_IRQN, 1);

    pwm_stepper_driver1.set_dir(true);
    driver1.enable(true);

    pwm_stepper_driver1.set_step(10);
    pwm_stepper_driver1.start_motor();

    driver1.enable(false);
}

extern "C" {
void GENERIC_PWM_CHANNEL_0_INST_IRQHandler() {
    pwm_stepper_driver1.check_step();
}
}

Resources:

Connection LP-MSPM0L1306 driver
Logic level for SDO 3V3 VSDO
Step output PA12 STEP
Direction output PA0 DIR
Disable output PA27 DRVOFF
Sleep mode output PA1 nSLEEP
Common Ground GND GND
MS1 PA3 MS1
MS2 PA22 MS2
MS3 PA26 MS3

This is a work in progress. In the next post, I try to provide (s-shaped?) ramp up and down. And probably go HAL all the way.

Thank you for reading.

VS Code project: mspm0_stepper_ramp_20260926.zip
(check this post for the expected VS Code workspace settings: MSPM0 project with VS Code, CMake and GCC - part 6: empty "template" project 
)

previous post:  MSPM0 project with VS Code, CMake and GCC - part 7a: middleware example stepper motor control
part of the EasyL1105 series. 
all posts

  • Sign in to reply
  • spinden
    spinden 9 hours ago

    Well done documenting this project!

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • shabaz
    shabaz 10 hours ago

    Great to see decent C++ code for stepper motors. Looking forward to seeing the ramp feature too. 

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
element14 Community

element14 is the first online community specifically for engineers. Connect with your peers and get expert answers to your questions.

  • Members
  • Learn
  • Technologies
  • Challenges & Projects
  • Products
  • Store
  • About Us
  • Feedback & Support
  • FAQs
  • Terms of Use
  • Privacy Policy
  • Legal and Copyright Notices
  • Sitemap
  • Cookies

An Avnet Company © 2026 Premier Farnell Limited. All Rights Reserved.

Premier Farnell Ltd, registered in England and Wales (no 00876412), registered office: Farnell House, Forge Lane, Leeds LS12 2NE.

Follow element14

  • X
  • Facebook
  • linkedin
  • YouTube