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 Boards Community
    • Dev Tools
    • Manufacturers
    • Multicomp Pro
    • Product Groups
    • Raspberry Pi
    • RoadTests & Reviews
  • 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
      •  Korea (Korean)
      •  Malaysia
      •  New Zealand
      •  Philippines
      •  Singapore
      •  Taiwan
      •  Thailand (Thai)
      • 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
Raspberry Pi
  • Products
  • More
Raspberry Pi
Blog Raspberry Pico stepper driver IC library, with example: Texas Instruments DRV8711
  • Blog
  • Forum
  • Documents
  • Quiz
  • Events
  • Polls
  • Files
  • Members
  • Mentions
  • Sub-Groups
  • Tags
  • More
  • Cancel
  • New
Raspberry Pi requires membership for participation - click to join
Featured Articles
Announcing Pi
Technical Specifications
Raspberry Pi FAQs
Win a Pi
GPIO Pinout
Raspberry Pi Wishlist
Comparison Chart
Quiz
  • Share
  • More
  • Cancel
Group Actions
  • Group RSS
  • More
  • Cancel
Engagement
  • Author Author: Jan Cumps
  • Date Created: 26 Apr 2025 1:33 PM Date Created
  • Views 523 views
  • Likes 6 likes
  • Comments 6 comments
Related
Recommended
  • raspberry
  • pico_pio_stepper_lib
  • pico
  • stepper-motor
  • c++

Raspberry Pico stepper driver IC library, with example: Texas Instruments DRV8711

Jan Cumps
Jan Cumps
26 Apr 2025

While designing a library for stepper motors, it bothered me that I had to put stepper driver IC logic across my user code.
To solve that, I made a little driver lib.

image

First I looked what logic shabaz and I put in our stepper projects, and searched for common activities.
I also looked at the specs of the Allegro A4988 IC, because I'll be using that soon.

Abstract stepper driver base class

With that info at hand, I created a set of classes that provides a minimal interface:

  • enable / disable a driver
  • initialise it
  • tell how many microsteps to take per full step.

If I restrict the code in my firmware to those 3 activities, it's possible to create firmware where there are just a few hardware specific things to modify at declaration.
The rest of the code wouldn't care what driver IC you use.

class stepper_driver { // driver out of sleep as long as object in scope
public:
    virtual bool init() = 0;
    // configure microsteps / step. return false if not supported
    virtual bool microsteps(unsigned int microsteps) = 0;
    virtual void enable(bool enable) = 0;
};

There's not much happening. The class is abstract, and has to be subclassed to get something that does activities.

DRV8711 driver class

I could have written a Pico specific DRV8711 child at this moment, but I decided to do this in 2 layers:

A DRV8711 class that's microcontroller independent, but knows DRV8711. In specific the registers, sleep and reset part.
Not putting Pico code at this level, allows that someone that wants to use the driver on a different microcontroller, has this part ready. Only things needed are methods to talk to GPIO and SPI

class drv8711_driver : public stepper_driver {
public:
    virtual bool microsteps(unsigned int microsteps) override {
        uint16_t reg = read(0x0000);
        reg &= 0b11111111111111111111111110000111; // clear microsteps
        uint16_t mode = microsteps_mode(microsteps);
        mode = mode << 3;
        reg |= mode;
        write(reg);
        return true;
    }

protected:
    virtual unsigned int microsteps_mode(unsigned int microsteps) {
        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 = 0x0004;
            break;
        case 32:
            mode = 0x0005;
            break;
        case 64:
            mode = 0x0006;
            break;
        case 128:
            mode = 0x0007;
            break;
        case 256:
            mode = 0x0008;
            break;
        default:
            mode = 0x0000;
        }
        return mode;
    }
private:
    virtual void write(uint16_t reg) = 0;
    virtual uint16_t read(uint16_t address) = 0;
    virtual void init_spi() = 0;
    virtual void init_gpio() = 0;
    virtual void init_registers() = 0;
};

Pico DRV8711 driver class

As last, the Pico savvy child:

class drv8711_pico : public drv8711_driver {
public:    
    drv8711_pico(spi_inst_t *spi, uint baudrate,
        uint cs, uint rx, uint tx, uint sck,
        uint n_sleep, uint reset) : drv8711_driver(),
        spi_(spi), baudrate_(baudrate),
        cs_(cs), rx_(rx), tx_(tx), sck_(sck),
        n_sleep_(n_sleep), reset_(reset) {}
    
    virtual bool init() override {
        init_gpio();
        init_spi();
        init_registers();
        return true;
    }

    virtual void enable(bool enable) override {
        gpio_put(n_sleep_, enable ? 1 : 0);
    }

    // changes config register settings
    virtual bool microsteps(unsigned int microsteps) override {
        drv8711::reg_ctrl.mode = microsteps_mode(microsteps);
        write(drv8711::reg_ctrl);
        return true;
    }

private:
    void init_spi() override{
        // Enable SPI 0 at 1 MHz and connect to GPIOs
        spi_init(spi_, baudrate_);
        spi_set_format(spi_, 16, SPI_CPOL_0, SPI_CPHA_0, SPI_MSB_FIRST); // 16 bit registers
        gpio_set_function(rx_, GPIO_FUNC_SPI);
        gpio_set_pulls(rx_, true, false); // drv8711 outputs are open drain
        gpio_set_function(sck_, GPIO_FUNC_SPI);
        gpio_set_function(tx_, GPIO_FUNC_SPI);
        // CS is active-high, invert pin action
        gpio_set_function(cs_, GPIO_FUNC_SPI);
        gpio_set_outover(cs_, GPIO_OVERRIDE_INVERT);
    }

    void init_gpio() override{
        // nsleep as output
        gpio_init(n_sleep_);
        gpio_put(n_sleep_, 0);
        gpio_set_dir(n_sleep_, GPIO_OUT);
        // reset as output
        gpio_init(reset_);
        gpio_put(reset_, 0);
        gpio_set_dir(reset_, GPIO_OUT);
    }

    // initialise all registers from the defaults
    // defined in module drv8711_config
    // developer can override values before calling
    void init_registers() override{
        write(drv8711::reg_ctrl);
        write(drv8711::reg_torque);
        write(drv8711::reg_off);
        write(drv8711::reg_blank);
        write(drv8711::reg_decay);
        write(drv8711::reg_stall);
        write(drv8711::reg_drive);
        write(drv8711::reg_status);
    }

    // write to a register
    virtual void write(uint16_t reg) override {
        spi_write16_blocking(spi_, &reg, 1);
    }
    
    // read from a register
    virtual uint16_t read(uint16_t reg) override {
        uint16_t r_buffer;
        uint16_t w_buffer;
        w_buffer = reg | 0b1000;
        w_buffer = w_buffer << 12;
        spi_write16_read16_blocking (spi_, (&w_buffer), (&r_buffer), 1);
        return r_buffer & 0b0000111111111111; // first 4 read bits are undefined
    }    

private:
    spi_inst_t * spi_;
    uint baudrate_;
    uint cs_;
    uint rx_;
    uint tx_;
    uint sck_;
    uint n_sleep_;
    uint reset_;
};

This is the class you'd instantiate, if you want to control a DRV8711 on a Pico:

using driver_t = drv8711_pico::drv8711_pico;
driver_t driver1(
    spi_default, 1000 * 1000,                                      // spi
    PICO_DEFAULT_SPI_CSN_PIN, PICO_DEFAULT_SPI_RX_PIN, // spi
    PICO_DEFAULT_SPI_TX_PIN, PICO_DEFAULT_SPI_SCK_PIN, // spi
    14U, 15U);                                         // n_sleep, reset

In action

A little proof that it works: I discussed earlier that I use an object to enable and disable the stepper driver IC:  embedded C++: manage a resource with a tiny* object.
I 've developed this in a way, that the wakeup class thinks it's dealing with the abstract class, but it actually uses your driver object to enable / disable the driver.

class wakeup { // driver out of sleep as long as object in scope
    public:    
        wakeup(stepper_driver& driver) : driver_(driver) { driver.enable(true); }
        ~wakeup() { driver_.enable(false); }
    private:
        stepper_driver& driver_;
};

No matter what driver you use, as long as it's in the hierarchy of stepper_driver, it 'll be able to manage the IC's state.

void full_demo(const commands_t & cmd) {
    // wake up the stepper driver IC. 
    // It goes back to low power when this object leaves the scope
    wakeup w(driver1);
    
    
    // ...

And something simpler: the initialisation code:

void init_everything() {
    stdio_init_all();

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

    init_pio();
}

This may not seem to be a lot, but it's quite neat.

C++ resource use: these objects require some resources that aren't used in classic procedural programming. But not a lot. Nothing that the smallest ARM can't handle:

  • I used virtual methods and inheritance. There is redirection involved, and the virtual table takes some data space.

project: https://github.com/jancumps/pio_drv8711_stepper

driver code (automatically fetched by the project): https://github.com/jancumps/pico_drv8711_lib

  • Sign in to reply
  • Jan Cumps
    Jan Cumps 19 days ago in reply to colporteur

    example of using it with Allegro's A4988 IC:

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

     Raspberry Pico stepper driver IC library, with example: Allegro A4988 

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • colporteur
    colporteur 20 days ago in reply to Jan Cumps

    Understand a little but still envy a lot.

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • DAB
    DAB 20 days ago in reply to DAB

    Great work Jan.

    I have no idea why my original comment did not print.

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • Jan Cumps
    Jan Cumps 21 days ago in reply to colporteur

    It allowed me to put this in the example code:

    // TODO: adapt to your board design
    // TODO: driver IC dependent. see dummy_driver.cpp
    import dummy_driver;
    using driver_t = dummy_driver;
    std::array<driver_t, 2> drivers;
    A user can adapt it to a specific IC (like I did for Texas Instruments DRV8711), and keep the rest of the code
    or put their own flavour of driver IC code.

    dummy code: https://gist.github.com/jancumps/c66e8af42dc30ee6dfbdfc06aea496e1#file-dummy_driver-cpp
    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • DAB
    DAB 22 days ago

    • 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 © 2025 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.

ICP 备案号 10220084.

Follow element14

  • X
  • Facebook
  • linkedin
  • YouTube