I'm trying here is to let the real-time units of the BeagleBone generate the signals for a stepper motor.

In this post: configure the stepper motor driver via the SPI interface.

 

This is virtually the same code as used in my Hercules real-time stepper driver project.

I'm using the DRV8711 SPI interface to set its registers. They determine the driver's behaviour.

The only difference is that I now use the BB's Linux SPI interface to talk to it instead of the Hercules SPI module.

I've made an executable in C. It sets the registers, then exits.

 

The program flow is extremely simple:

 

int main(int argc, char *argv[]) {
    int fd = initialize();
    WriteAllRegisters(fd);
    finalize(fd);
    return 0;
}

 

Declaration of constants and variables (the code with the struct structures is in the attached full CCS project).

 

// DRV8711 Registers
struct CTRL_Register     G_CTRL_REG;
struct TORQUE_Register   G_TORQUE_REG;
struct OFF_Register      G_OFF_REG;
struct BLANK_Register    G_BLANK_REG;
struct DECAY_Register    G_DECAY_REG;
struct STALL_Register    G_STALL_REG;
struct DRIVE_Register    G_DRIVE_REG;
struct STATUS_Register   G_STATUS_REG;

// Register Access
#define REGWRITE    0x0000

static const char *device = "/dev/spidev1.0";
static uint8_t mode;
static uint8_t bits = 16;
static uint32_t speed = 500000;
static uint16_t delay;

 

The initialize() function sets SPI interface and initialises register settings.

Attention: the DRV8711 uses the uncommon CS High mode. The IC listens to SPI commands only when the CS line is high.

It took me a while to find how to do that on a BB.

You have to set the mode accordingly:

    mode |= SPI_CS_HIGH;

 

 

int initialize() {
    int ret = 0;
    int fd;

    fd = open(device, O_RDWR);
    if (fd < 0)
        pabort("can't open device");

    /*
     * spi mode
     */
    mode |= SPI_CS_HIGH; // attention, the DRV8711 uses CS high
    ret = ioctl(fd, SPI_IOC_WR_MODE, &mode);
    if (ret == -1)
        pabort("can't set spi mode");

    ret = ioctl(fd, SPI_IOC_RD_MODE, &mode);
    if (ret == -1)
        pabort("can't get spi mode");

    /*
     * bits per word
     */
    ret = ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, &bits);
    if (ret == -1)
        pabort("can't set bits per word");

    ret = ioctl(fd, SPI_IOC_RD_BITS_PER_WORD, &bits);
    if (ret == -1)
        pabort("can't get bits per word");

    /*
     * max speed hz
     */
    ret = ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed);
    if (ret == -1)
        pabort("can't set max speed hz");

    ret = ioctl(fd, SPI_IOC_RD_MAX_SPEED_HZ, &speed);
    if (ret == -1)
        pabort("can't get max speed hz");

    printf("spi mode: %d\n", mode);
    printf("bits per word: %d\n", bits);
    printf("max speed: %d Hz (%d KHz)\n", speed, speed/1000);

    // drv8711
    // Set Default Register Settings
    // CTRL Register
    G_CTRL_REG.Address     = 0x0000;
    G_CTRL_REG.DTIME     = 0x0003;
    G_CTRL_REG.ISGAIN     = 0x0003;
    G_CTRL_REG.EXSTALL     = 0x0000;
//     G_CTRL_REG.MODE     = 0x0003;  // in 8th steps
    G_CTRL_REG.MODE     = 0x0000; // in full steps
    G_CTRL_REG.RSTEP     = 0x0000;
    G_CTRL_REG.RDIR     = 0x0000;
    G_CTRL_REG.ENBL     = 0x0001;

    // TORQUE Register
    G_TORQUE_REG.Address = 0x0001;
    G_TORQUE_REG.SIMPLTH = 0x0000;
    G_TORQUE_REG.TORQUE  = 0x00BA;

    // OFF Register
    G_OFF_REG.Address     = 0x0002;
    G_OFF_REG.PWMMODE     = 0x0000;
    G_OFF_REG.TOFF         = 0x0030;

    // BLANK Register
    G_BLANK_REG.Address = 0x0003;
    G_BLANK_REG.ABT     = 0x0001;
    G_BLANK_REG.TBLANK     = 0x0008;


    // DECAY Register.
    G_DECAY_REG.Address = 0x0004;
    G_DECAY_REG.DECMOD  = 0x0003;
    G_DECAY_REG.TDECAY     = 0x0010;

    // STALL Register
    G_STALL_REG.Address = 0x0005;
    G_STALL_REG.VDIV     = 0x0003;
    G_STALL_REG.SDCNT     = 0x0003;
    G_STALL_REG.SDTHR     = 0x0040;

    // DRIVE Register
    G_DRIVE_REG.Address = 0x0006;
    G_DRIVE_REG.IDRIVEP = 0x0000;
    G_DRIVE_REG.IDRIVEN = 0x0000;
    G_DRIVE_REG.TDRIVEP = 0x0001;
    G_DRIVE_REG.TDRIVEN = 0x0001;
    G_DRIVE_REG.OCPDEG     = 0x0001;
    G_DRIVE_REG.OCPTH     = 0x0001;

    // STATUS Register
    G_STATUS_REG.Address = 0x0007;
    G_STATUS_REG.STDLAT  = 0x0000;
    G_STATUS_REG.STD     = 0x0000;
    G_STATUS_REG.UVLO    = 0x0000;
    G_STATUS_REG.BPDF    = 0x0000;
    G_STATUS_REG.APDF    = 0x0000;
    G_STATUS_REG.BOCP    = 0x0000;
    G_STATUS_REG.AOCP    = 0x0000;
    G_STATUS_REG.OTS     = 0x0000;

    return fd;
}

 

I should split up the BB SPI init and the DRV8711 settings initialisation. They are unrelated activities. Maybe later ...

 

Then all registers are written.

 

void WriteAllRegisters(int fd) {
    uint16_t data;

    // Write CTRL Register
    data = REGWRITE | (G_CTRL_REG.Address << 12) | (G_CTRL_REG.DTIME << 10) | (G_CTRL_REG.ISGAIN << 8) |(G_CTRL_REG.EXSTALL << 7) | (G_CTRL_REG.MODE << 3) | (G_CTRL_REG.RSTEP << 2) | (G_CTRL_REG.RDIR << 1) | (G_CTRL_REG.ENBL);
    SPI_DRV8711_Write(fd, data);

    // Write TORQUE Register
    data = REGWRITE | (G_TORQUE_REG.Address << 12) | (G_TORQUE_REG.SIMPLTH << 8) | G_TORQUE_REG.TORQUE;
    SPI_DRV8711_Write(fd, data);

    // Write OFF Register
    data = REGWRITE | (G_OFF_REG.Address << 12) | (G_OFF_REG.PWMMODE << 8) | G_OFF_REG.TOFF;
    SPI_DRV8711_Write(fd, data);

    // Write BLANK Register
    data = REGWRITE | (G_BLANK_REG.Address << 12) | (G_BLANK_REG.ABT << 8) | G_BLANK_REG.TBLANK;
    SPI_DRV8711_Write(fd, data);

    // Write DECAY Register
    data = REGWRITE | (G_DECAY_REG.Address << 12) | (G_DECAY_REG.DECMOD << 8) | G_DECAY_REG.TDECAY;
    SPI_DRV8711_Write(fd, data);

    // Write STALL Register
    data = REGWRITE | (G_STALL_REG.Address << 12) | (G_STALL_REG.VDIV << 10) | (G_STALL_REG.SDCNT << 8) | G_STALL_REG.SDTHR;
    SPI_DRV8711_Write(fd, data);

    // Write DRIVE Register
    data = REGWRITE | (G_DRIVE_REG.Address << 12) | (G_DRIVE_REG.IDRIVEP << 10) | (G_DRIVE_REG.IDRIVEN << 8) | (G_DRIVE_REG.TDRIVEP << 6) | (G_DRIVE_REG.TDRIVEN << 4) | (G_DRIVE_REG.OCPDEG << 2) | (G_DRIVE_REG.OCPTH);
    SPI_DRV8711_Write(fd, data);

    // Write STATUS Register
    data = REGWRITE | (G_STATUS_REG.Address << 12) | (G_STATUS_REG.STDLAT << 7) | (G_STATUS_REG.STD << 6) | (G_STATUS_REG.UVLO << 5) | (G_STATUS_REG.BPDF << 4) | (G_STATUS_REG.APDF << 3) | (G_STATUS_REG.BOCP << 2) | (G_STATUS_REG.AOCP << 1) | (G_STATUS_REG.OTS);
    SPI_DRV8711_Write(fd, data);
}

 

The only function I had to write myself was the method to write 16 bit fields over SPI on Linux. It's based on the code of this blog: BeagleBone: Enable SPI with Overlay and from Command Line.

 

void SPI_DRV8711_Write(int fd, uint16_t data) {
    int ret;
    uint16_t tx[] = {data};

    struct spi_ioc_transfer tr = {
        .tx_buf = (unsigned long)tx,
        .rx_buf = (unsigned long)NULL,
        .len = (sizeof(tx)/sizeof(*tx))*2,
        .delay_usecs = delay,
        .speed_hz = speed,
        .bits_per_word = bits,
    };

    ret = ioctl(fd, SPI_IOC_MESSAGE(1), &tr);
    if (ret < 1)
        pabort("can't send spi message");
}

 

Here's the result captured by a logic analyser. The CS is not correct. The grab was made before I corrected the CS polarity.

 

bb stepper motor SPI DRV8711

I have the advantage that I've captured the traffic before on other microcontrollers. I can compare and validate.

The results are as expected.

 

Install the code to the bin directory in your home.

 

I'll now combine all previous work:

  • initialise PRU pins
  • run SPI init
  • control reset and sleep pins via Linux command line
  • run the PRU ocde to make the motor step

That's not a lot of work left. Hang on ...

 

 

Related blog:
BeagleBone Control Stepper Motors with PRU - Part 1: Intentions
BeagleBone Control Stepper Motors with PRU - Part 2: Test Driving Outputs
BeagleBone: Enable SPI with Overlay and from Command Line
BeagleBone Control Stepper Motors with PRU - Part 3: Hardware Provisioning and Wiring
BeagleBone Control Stepper Motors with PRU - Part 4: SPI Setup
BeagleBone Control Stepper Motors with PRU - Part 5: It Works