The Pico has a set of PIO co-processors. They are real-time controllers that can execute logic with deterministic timing. Ideal to run strict-timed sequences and state machines. And to implement extra peripherals. |
Follow up of Stepper Motor Control with Raspberry Pico PIO and DRV8711 driver- Part 1: Hardware Provisioning
SPI requirements and setup
The TI DRV8711 stepper motor driver, that we use in this project, relies on pre-configuration via SPI.
These are the Pico resources I reserve:
signal | DRV8711 | Pico |
SPI CLK | SCLK | default SCLK IO18 |
SPI CS | SCS | default CS* IO17 |
SPI MISO | sdato | default MISO** IO16 |
SPI MOSI | sdati | default MOSI IO19 |
SPI | default spi0 |
SPI settings:
baud | 1000 * 1000 |
message size | 16 bits |
* DRV8711 uses a chip select that's active high. Pico's SPI library doesn't support that, so I will bit bang that pin.
** DRV8711 uses open collector for outputs. Pico will have to provide the rail via a pull-up.
The setup code isn't complex. You 'll recognise the special cases mentioned below:
void init_drv8711_spi_hw() { // Enable SPI 0 at 1 MHz and connect to GPIOs spi_init(spi_default, 1000 * 1000); spi_set_format(spi_default, 16, SPI_CPOL_0, SPI_CPHA_0, SPI_MSB_FIRST); // 16 bit registers gpio_set_function(PICO_DEFAULT_SPI_RX_PIN, GPIO_FUNC_SPI); gpio_set_pulls(PICO_DEFAULT_SPI_RX_PIN, true, false); // drv8711 outputs are open drain gpio_set_function(PICO_DEFAULT_SPI_SCK_PIN, GPIO_FUNC_SPI); gpio_set_function(PICO_DEFAULT_SPI_TX_PIN, GPIO_FUNC_SPI); // CS is active-high, invert pin action gpio_set_function(cs_, GPIO_FUNC_SPI); gpio_set_outover(cs_,GPIO_OVERRIDE_INVERT ); }
I provided a helper for bit write (the width of the DRV8711 registers):
static void spi_write(const uint16_t &data) { spi_write16_blocking(spi_default, &data, 1); }
Here's an example of a register structure, with a convert-to-16-bit operator. This code sits in a DRV8711 driver that I wrote for this project.
struct CTRL { // address 14-12 unsigned int dtime; // 11-10 unsigned int isgain; // 9-8 unsigned int exstall; // 7 unsigned int mode; // 6-3 unsigned int rstep; // 2 unsigned int rdir; // 1 unsigned int enbl; // 0 inline operator uint16_t() const { return (0x0000 << 12) | (dtime << 10) | (isgain << 8) |(exstall << 7) | (mode << 3) | (rstep << 2) | (rdir << 1) | (enbl); } };
Then the declaration of an object of that type, with initialisation. This allows the linker / loader to fill this structure straight away at firmware loading. Not a single clock tick of runtime code is involved in this construct:
drv8711::CTRL reg_CTRL { 0x0003, // DTIME 0x0003, // ISGAIN 0x0000, // EXSTALL 0x0003, // MODE 8 microsteps 0x0000, // RSTEP 0x0000, // RDIR 0x0001 // ENBL };
I have that mechanism for all DRV8711 registers. Here's the code to actually write the setup to the IC:
void init_drv8711_settings() { // Set Default Register Settings spi_write(reg_CTRL); spi_write(reg_TORQUE); spi_write(reg_OFF); spi_write(reg_BLANK); spi_write(reg_DECAY); spi_write(reg_STALL); spi_write(reg_DRIVE); spi_write(reg_STATUS); }
The full code will be attached when I'm a bit further into this project. Next post will focus on provisioning the non-SPI pins.
Meanwhile: here's the full conversation from Pico to DRV8711: