Grove Mini I2C Motor Driver - Include Github Library Source Code

Hi,
I’m using an Argon and the Particle Grove FeatherWing adapter. I’m trying to include an Arduino SparkFun MiniMoto library from Github (https://github.com/Seeed-Studio/Drv8830_Motor_Driver) to use with the Grove Mini I2C Motor Driver. I did not found any existing libraries from Particle. Is there one?

Instead, I downloaded the source code from Github and placed in a “lib” folder. I updated my project’s project.properites file to include a dependency as well.

Is this not supported? How do we include Arduino related libraries? Do I just need to copy the code and piece it together in my own .ino file?

FYI, I also included reference to “Arduino.h” in my .ino file, but no luck.

This library uses AVR registers for I2CReadBytes() and I2CWriteBytes().
These are not supported on Particle devices.
But there is an easy solution: You can replace these functions with the respective Wire commands.

As for integrating a library in your project, you need to adhere to the correct file structure (e.g. by importing an official library and replicating that) and you won’t need to add the dependency in project.properties as this will only be used for cloud builds and requires the library to be present in the cloud library repo.

Awesome!. Thanks for your quick reply. I very much appreciated it. So, basically create my only library project (adhere to folder/file structure) and copy the Github code. Replace the I2CReadBytes() and I2CWriteBytes() with the Wire commands. Will give that a try right now.

Unfortunately I just keep getting: fatal error: libraryXyz: No such file or directory.

I am using 1.5.2 Toolchain with Argon device. I tried creating my own library first using Particle Library Create command just to stub a library project out. I use the proper folder structure (/lib/myLib/xyz.h/.cpp). Everything saves fine in Visual Studio code and compiles, except when I go the the .ino file and add #include Xyz.h. Then the compile fails.

Next, I just create a whole new Particle Project, then added an existing Library (Adafruit_DHT) and same thing happens. Any idea how to get my environment working correctly?

Well, kick me in the junk. Not sure if something changed from the last time I worked on the compiling a few months back, but for some reason in my “Quick Command” notes, I had this format

particle compile argon src/Xyz.ino --saveTo firmware.bin

I decided to change to remove the folder/file all together and now it’s working:
particle compile argon --saveTo firmware.bin

Arrgh. Okay, back to porting code :slight_smile:

1 Like

I’ve been busy porting the code from the Drv8830_Motor_Driver library to use the Wire library, but no luck in getting the DC motor to run :frowning: Nothing happens. I’m not getting any fault either as the FAULTn PIN remains HIGH. I think I have the correct pin defined. If I change to another pin number, the value is LOW all the time.

Also, I don’t believe I need to add a Pull-Up resistor based on what I see from SeeedStudio Grove-Mini Motor Driver Wiki page.

My documentation references
Grove - Mini I2C Motor Driver v1.0 Wiki
Grove - Mini I2C Motor Driver Schematic Document
DRV8830 Datasheet
Particle Wire Reference
Arduino Wire Library

I’ve pasted the code below. Keep in mind, I’m a C/C++ language newbie, so I apologize if my terms are incorrect in my code/comments. I’m also a noob on working with I2C and Motors.

I’m thinking my issue is in writing the byte data on the CONTROL register for the direction and speed. The code that determines the direction and speed byte data is in the void MiniMotor::drive(int speed) function. Obviously, the underlying issue might be the code that writes the bytes which is in the void MiniMotor::I2CWriteBytes(byte subAddr, byte* buffer, byte len) function.

Any help would be appreciated.

Grove Mini Motor Library

GroveMiniMotor.h

#pragma once

// This will load the definition for common Particle variable types
#include "Particle.h"
#include <Arduino.h>

// See DRV8830 Datasheet for values

// Register sub address constants
#define CONTROL_ADDR 0x00
#define FAULT_ADDR 0x01

// Fault constants
#define FAULT 0x01
#define ILIMIT 0x10
#define OTS 0x08
#define UVLO 0x04
#define OCP 0x02

class MiniMotor {
  public:
    MiniMotor(byte i2cChannelAddr);
    void drive(int speed);
    void stop();
    void brake();
    byte getFault();
  private:
    void I2CWriteBytes(byte subAddr, byte* buffer, byte len);
    void I2CReadBytes(byte subAddr, byte* buffer, byte len);
    byte _i2cChannelAddr;
};

GroveMiniMotor.cpp

#include "GroveMiniMotor.h"

// The address of the part is set by a jumper on the board.
// See datasheet or schematic for details.
MiniMotor::MiniMotor(byte i2cChannelAddr)
{
    _i2cChannelAddr = i2cChannelAddr;

    // We use a single instance per motor, so only call begin() once per Master device 
    if(!Wire.isEnabled()) 
    {
        // This sets the bit rate of the bus
        // Wire.setSpeed(CLOCK_SPEED_100KHZ); // Default is 100KHZ
        // Wire.setSpeed(CLOCK_SPEED_400KHZ);

        // NOTE: Only send a device address parameter if the device running code is the SLAVE        
        Wire.begin(); 

        // Datasheet says to delay at least 60 microseconds before writing first START command.
        delayMicroseconds(10000);
    }
}

// Return the fault status of the DRV8830 chip. Also clears any existing faults.
byte MiniMotor::getFault()
{
    byte buffer = 0;
    byte clearFault = 0x80;
    
    // Read first
    I2CReadBytes(FAULT_ADDR, &buffer, 1);

    // Clear next
    I2CWriteBytes(FAULT_ADDR, &clearFault, 1);

    // Return fault status
    return buffer;
}

// Send the drive command over I2C to the DRV8830 chip. See DRV8830 specficiation document for values.
// Bits 7:2 are the speed setting; 
//      Range is 0-63
// Bits 1:0 are the mode setting:
//  - 00 = Z-Z (Standby/Coast)
//  - 01 = L-H Reverse
//  - 10 = H-L Forward
//  - 11 = H-H (brake)
void MiniMotor::drive(int speed)
{
    // Before we do anything, we'll want to clear the fault status. To do that
    //  write 0x80 to register FAULT ADDR on the DRV8830.
    byte regValue = 0x80;             
    I2CWriteBytes(FAULT_ADDR, &regValue, 1); 

    delay(1000);

    // Find the byte-ish abs value of the input
    regValue = (byte)abs(speed);      

    // Cap the value at 63.
    if (regValue > 63) {
        regValue = 63;    
    }

    // Left shift to make room for bits 1:0
    regValue = regValue << 2;         

    if (speed < 0) {
        regValue |= 0x01; // Set bits 1:0 based on sign of input.
    } else {
        regValue |= 0x02;
    }

    Serial.print("Sending drive speed: ");
    Serial.println(regValue, HEX);

    // Write to the CONTROL ADDR
    I2CWriteBytes(CONTROL_ADDR, &regValue, 1);
}

void MiniMotor::stop()
{
    byte regValue = 0; // See above for bit 1:0 explanation.
    
    I2CWriteBytes(CONTROL_ADDR, &regValue, 1);
}

// Stop the motor by providing a heavy load on it.
void MiniMotor::brake()
{
    byte regValue = 0x03; // See above for bit 1:0 explanation.

     I2CWriteBytes(CONTROL_ADDR, &regValue, 1);
}

// Private function that reads some number of bytes from motor driver.
void MiniMotor::I2CReadBytes(byte subAddr, byte* buffer, byte len) 
{
    Serial.print("Reading bytes from slave addr: ");
    Serial.println(_i2cChannelAddr, HEX);

    // Send a START condition to begin a transmission of bytes on the specified channel address
    Wire.beginTransmission(_i2cChannelAddr); 
    
    // Sets the "Sub Address" which defines which register to read from (i.e. read from CONTROL: 0x00 or FAULT: 0x01 register)
    Wire.write(subAddr);   

    // Send a STOP condition to transmit the queued bytes
    Wire.endTransmission();

    delay(100);

    // Next, request the number of bytes from slave device
    Wire.requestFrom(_i2cChannelAddr, len);
   
    int bytesLength = Wire.available();

    Serial.print("Bytes available from slave: ");
    Serial.println(bytesLength);

    if(bytesLength > 0) 
    {
        char c  = Wire.read();
        
        Serial.println("Byte Value: " + c);

        *buffer = byte(atoi(&c));        
    }    
}

// Private function that writes some number of bytes to motor driver.
void MiniMotor::I2CWriteBytes(byte subAddr, byte* buffer, byte len) 
{
    Serial.print("Writing bytes to slave addr: ");
    Serial.println(_i2cChannelAddr, HEX);

    // Send a START condition to begin a transimission of bytes on the specified channel address
    Wire.beginTransmission(_i2cChannelAddr); 
    
    // Sets the "Sub Address" which defines which register to write (i.e. write to CONTROL: 0x00 or FAULT: 0x01 register)
    Wire.write(subAddr);

    // Write the data next
    Wire.write(buffer, len); // Used overload because we writing byte pointer

    // Send a STOP condition to transmit the queued bytes
    Wire.endTransmission(); // stop transmitting
}

.ino

#include "Particle.h"
#include "GroveMiniMotor.h"

#define FAULTn 6 // Pin used for fault detection.

// IMPORTANT: Review jumper settings on back of board first and set accordingly
// Based on jumper settings: A1= 0, A1 = 0
#define MOTOR1_WRITE_ADDR 0xC4 
// Based on jumper settings: A1= 1, A0 = 0
#define MOTOR2_WRITE_ADDR 0xC0

// Create two MiniMotor instances with different I2C channel addresses
MiniMotor motor1(MOTOR1_WRITE_ADDR); // CH1
MiniMotor motor2(MOTOR2_WRITE_ADDR); // CH2

void setup() {

    Serial.begin(9600);
    Serial.println("Hello, world!");
    pinMode(FAULTn, INPUT);
}

void loop() {

    Serial.println("Forward!");

    motor1.drive(100);
    motor2.drive(100);
    delayUntil(10000);

    Serial.println("Stop!");
    motor1.stop();
    motor2.stop();
    delay(1000);
}

// delayUntil() is a little function to run the motor either for
//  a designated time OR until a fault occurs. Note that this is
//  a very simple demonstration; ideally, an interrupt would be
//  used to service faults rather than blocking the application
//  during motion and polling for faults.
void delayUntil(unsigned long elapsedTime) {

    // See the "BlinkWithoutDelay" example for more details on how
    //  and why this loop works the way it does.
    unsigned long startTime = millis();

    while (startTime + elapsedTime > millis()) {

        // If FAULTn goes low, a fault condition *may* exist. To be
        //  sure, we'll need to check the FAULT bit.
        if (digitalRead(FAULTn) == LOW) {

            Serial.println("Fault low. Reading fault status...");

            // We're going to check both motors; the logic is the same
            //  for each...
            byte result = motor1.getFault();
            
            Serial.print("Fault Status: ");
            Serial.println(result, HEX);

            // If result masked by FAULT is non-zero, we've got a fault
            //  condition, and we should report it.

            // Motor 1
            if (result & FAULT) {

                Serial.print("Motor 1 fault: ");
                
                if (result & OCP) {
                    Serial.println("Chip overcurrent!");
                }

                if (result & ILIMIT) {
                    Serial.println("Load current limit!");
                }

                if (result & UVLO) {
                    Serial.println("Undervoltage!");
                }

                if (result & OTS) {
                    Serial.println("Over temp!");
                }

                break; // We want to break out of the motion immediately,
                //  so we can stop motion in response to our fault.
            }

            // Motor 2
            result = motor2.getFault();

            if (result & FAULT) {

                Serial.print("Motor 2 fault: ");

                if (result & OCP) {
                    Serial.println("Chip overcurrent!");
                }

                if (result & ILIMIT) {
                    Serial.println("Load current limit!");
                }

                if (result & UVLO) {
                    Serial.println("Undervoltage!");
                }

                if (result & OTS) {
                    Serial.println("Over temp!");
                }

                 break; // We want to break out of the motion immediately,
                //  so we can stop motion in response to our fault.
            }
        }
    }
}

I don’t think this conversion is what you want to do and you are also only ever putting the new byte into the first byte of buffer.
I’d rather use Wire.readBytes((char*)buffer, len); and also make sure that byteLength equals len.

BTW, have you checked whether your Argon can talk to the driver at all?
You can test with this I2C scanner code
https://go.particle.io/shared_apps/58cda34c77e5d3ec090004fe
With this you can also check whether you are actually using the correct I2C address.

Thanks, I’ll review the code and check the driver. I didn’t see readBytes() in documentation. I’ll try that.

On a side note. Maybe I’m misunderstanding the byte data type for buffer. It’s a single byte correct (represents 8bits)? In C#, I usually use the term ‘buffer’ for a byte[]. Should I make buffer a byte[] and do loop over Wire.read() and append each byte to the buffer byte[]

I believe the original github code was doing something like this, but code was pretty complicated to follow. I really hate not being able to step through and debug code. I guess I am spoiled as web developer :slight_smile:

I know the ‘IC2ReadBytes()’ method is generic, so I shouldn’t assume only 1 byte will be returned. It should match the len parameter passed in. I’ll update.

I only did this way for now because I only use the read method for getting the FAULT status, which I believe only returns 1 byte. Am I understanding this correctly?

Thanks again for all the help.

That’s a method the Wire object inherits from Stream
https://docs.particle.io/reference/device-os/firmware/photon/#readbytes-

Nope, byte *buffer stands for a pointer pointing to one or more consecutive bytes (plural) :wink: Very similar to an array.

It is a void function. Meaning it will not return any value.
But you are passing a pointer to a buffer you want populated by the function - technically not a return value.

Copy that! I figured something along these lines I was seeing other code and it seemed similar to array structure. Need to refresh my memory on the pointer/reference stuff. I get spoiled with all those fancy object-oriented languages with garbage collection. haha

Thanks for the documentation reference too.

I do understand the function is `void’. Sorry I didn’t mean to say return. I understand the buffer is filled and is a ‘reference’ object (or a pointer to the address of the value, right?).

1 Like

FYI, I ran the I2C Scanner. Output below. This means I’m using the wrong I2C address? Documentation says my Channel I2C address would 0xC4 (CH1) and 0xC0 (CH2) based on jumper settings on driver board. Interesting. I’ll try these addresses instead.

I2C Scanner
Scanning…
I2C device found at address 0x60 !
I2C device found at address 0x65 !
done

1 Like

Holy smokes! Progress has been made :slight_smile: The motors are running, Woohooo! I updated the I2C Addresses which were listed out from the I2C-Scanner code.

Thank you SOOOO much! Hopefully the motors are most complicated out of the devices I need to add :slight_smile:

1 Like

The reason why you have that discrepancy in addresses is that the datasheet obviously states the 8bit address where the least significant bit indicates read or write access but the Wire object uses the 7bit address (raw 8 bit shifted 1 bit to the right) since there are dedicated read and write methods which should act on the same address.
0xC0 >> 1 equals 0x60
0xCA >> 1 equals 0x65

Gotcha. That makes sense. Thanks for the explanation.

Updated code that works. I’ll put on Github eventually.

GroveMiniMotor.h

#pragma once

// This will load the definition for common Particle variable types
#include "Particle.h"

// See DRV8830 Datasheet for values

// Register sub address constants
#define CONTROL_ADDR 0x00
#define FAULT_ADDR 0x01

// Fault constants
#define FAULT 0x01
#define ILIMIT 0x10
#define OTS 0x08
#define UVLO 0x04
#define OCP 0x02

class MiniMotor
{
public:
    MiniMotor(byte i2cChannelAddr);
    void drive(int speed);
    void stop();
    void brake();
    char getFault();

private:
    void I2CWriteBytes(byte subAddr, byte *buffer, byte len);
    void I2CReadBytes(byte subAddr, char *buffer, byte len);
    byte _i2cChannelAddr;
};

GroveMiniMotor.cpp

// Grove Mini Motor Driver Wiki
// https://wiki.seeedstudio.com/Grove-Mini_I2C_Motor_Driver_v1.0/

// DRV8830 Datasheet
// https://files.seeedstudio.com/wiki/Grove-Mini_I2C_Motor_Driver_v1.0/res/DRV8830.pdf

// Wire(I2C) - Partile Refeence
// https://docs.particle.io/reference/device-os/firmware/argon/#begintransmission-

// Wire Library - Arduino Reference
// https://www.arduino.cc/en/reference/wire

#include "GroveMiniMotor.h"

// The address of the part is set by a jumper on the board.
// See datasheet or schematic for details.
MiniMotor::MiniMotor(byte i2cChannelAddr)
{
    _i2cChannelAddr = i2cChannelAddr;

    // We use a single instance per motor, so only call begin() once per Master device 
    if(!Wire.isEnabled()) 
    {
        // This sets the bit rate of the bus
        // Wire.setSpeed(CLOCK_SPEED_100KHZ); // Default is 100KHZ
        // Wire.setSpeed(CLOCK_SPEED_400KHZ);

        // NOTE: Only send a device address parameter if the device running code is the SLAVE        
        Wire.begin(); 

        // Datasheet says to delay at least 60 microseconds before writing first START command.
        delayMicroseconds(100); 
    }
}

// Return the fault status of the DRV8830 chip. Also clears any existing faults.
char MiniMotor::getFault()
{
    char* buffer = 0;
    byte clearFault = 0x80;
    
    // Read first
    I2CReadBytes(FAULT_ADDR, buffer, 1);

    // Clear next
    I2CWriteBytes(FAULT_ADDR, &clearFault, 1);

    // Return fault status
    return buffer[0];
}

// Send the drive command over I2C to the DRV8830 chip. See DRV8830 specficiation document for values.
// Bits 7:2 are the speed setting; 
//      Range is 0-63
// Bits 1:0 are the mode setting:
//  - 00 = Z-Z (Standby/Coast)
//  - 01 = L-H Reverse
//  - 10 = H-L Forward
//  - 11 = H-H (brake)
void MiniMotor::drive(int speed)
{
    // Before we do anything, we'll want to clear the fault status. To do that
    //  write 0x80 to register FAULT ADDR on the DRV8830.
    byte regValue = 0x80;             
    I2CWriteBytes(FAULT_ADDR, &regValue, 1); 

    // Find the byte-ish abs value of the input
    regValue = (byte)abs(speed);      

    // Cap the value at 63.
    if (regValue > 63) {
        regValue = 63;    
    }

    // Left shift to make room for bits 1:0
    regValue = regValue << 2;         

    if (speed < 0) {
        regValue |= 0x01; // Set bits 1:0 based on sign of input.
    } else {
        regValue |= 0x02;
    }

    Serial.print("Sending drive speed: ");
    Serial.println(regValue, HEX);

    // Write to the CONTROL ADDR
    I2CWriteBytes(CONTROL_ADDR, &regValue, 1);
}

void MiniMotor::stop()
{
    byte regValue = 0; // See above for bit 1:0 explanation.
    
    I2CWriteBytes(CONTROL_ADDR, &regValue, 1);
}

// Stop the motor by providing a heavy load on it.
void MiniMotor::brake()
{
    byte regValue = 0x03; // See above for bit 1:0 explanation.

     I2CWriteBytes(CONTROL_ADDR, &regValue, 1);
}

// Private function that reads some number of bytes from motor driver.
void MiniMotor::I2CReadBytes(byte subAddr, char* buffer, byte len) 
{
    Serial.print("Reading bytes from slave addr: 0x");
    Serial.println(_i2cChannelAddr, HEX);

    // Send a START condition to begin a transimission of bytes on the specified channel address
    Wire.beginTransmission(_i2cChannelAddr); 
    
    // Sets the "Sub Address" which defines which register to read from (i.e. read from CONTROL: 0x00 or FAULT: 0x01 register)
    Wire.write(subAddr);   

    // Send a STOP condition to transmit the queued bytes
    Wire.endTransmission();

    // Add small delay before reading
    delay(100);

    // Next, request the number of bytes from slave device
    Wire.requestFrom(_i2cChannelAddr, len);
   
    int bytesLength = Wire.available();

    Serial.print("# of bytes availble from slave: ");
    Serial.println(bytesLength);

    if(bytesLength > 0) 
    {
        Wire.readBytes(buffer, len);

        Serial.print("Bytes: ");
        Serial.println(buffer);
    }    
}

// Private function that writes some number of bytes to motor driver.
void MiniMotor::I2CWriteBytes(byte subAddr, byte* buffer, byte len) 
{
    Serial.print("Writing bytes to slave addr: ");
    Serial.println(_i2cChannelAddr, HEX);

    // Send a START condition to begin a transimission of bytes on the specified channel address
    Wire.beginTransmission(_i2cChannelAddr); 
    
    // Sets the "Sub Address" which defines which register to write (i.e. write to CONTROL: 0x00 or FAULT: 0x01 register)
    Wire.write(subAddr);

    // Write the data next
    Wire.write(buffer, len);

    // Send a STOP condition to transmit the queued bytes
    Wire.endTransmission(); // stop transmitting
}

GroveMiniMotor.cpp

// Grove Mini Motor Driver Wiki
// https://wiki.seeedstudio.com/Grove-Mini_I2C_Motor_Driver_v1.0/

// DRV8830 Datasheet
// https://files.seeedstudio.com/wiki/Grove-Mini_I2C_Motor_Driver_v1.0/res/DRV8830.pdf

// Wire(I2C) - Partile Refeence
// https://docs.particle.io/reference/device-os/firmware/argon/#begintransmission-

// Wire Library - Arduino Reference
// https://www.arduino.cc/en/reference/wire

#include "GroveMiniMotor.h"

// The address of the part is set by a jumper on the board.
// See datasheet or schematic for details.
MiniMotor::MiniMotor(byte i2cChannelAddr)
{
    _i2cChannelAddr = i2cChannelAddr;

    // We use a single instance per motor, so only call begin() once per Master device 
    if(!Wire.isEnabled()) 
    {
        // This sets the bit rate of the bus
        // Wire.setSpeed(CLOCK_SPEED_100KHZ); // Default is 100KHZ
        // Wire.setSpeed(CLOCK_SPEED_400KHZ);

        // NOTE: Only send a device address parameter if the device running code is the SLAVE        
        Wire.begin(); 

        // Datasheet says to delay at least 60 microseconds before writing first START command.
        delayMicroseconds(100); 
    }
}

// Return the fault status of the DRV8830 chip. Also clears any existing faults.
char MiniMotor::getFault()
{
    char* buffer = 0;
    byte clearFault = 0x80;
    
    // Read first
    I2CReadBytes(FAULT_ADDR, buffer, 1);

    // Clear next
    I2CWriteBytes(FAULT_ADDR, &clearFault, 1);

    // Return fault status
    return buffer[0];
}

// Send the drive command over I2C to the DRV8830 chip. See DRV8830 specficiation document for values.
// Bits 7:2 are the speed setting; 
//      Range is 0-63
// Bits 1:0 are the mode setting:
//  - 00 = Z-Z (Standby/Coast)
//  - 01 = L-H Reverse
//  - 10 = H-L Forward
//  - 11 = H-H (brake)
void MiniMotor::drive(int speed)
{
    // Before we do anything, we'll want to clear the fault status. To do that
    //  write 0x80 to register FAULT ADDR on the DRV8830.
    byte regValue = 0x80;             
    I2CWriteBytes(FAULT_ADDR, &regValue, 1); 

    // Find the byte-ish abs value of the input
    regValue = (byte)abs(speed);      

    // Cap the value at 63.
    if (regValue > 63) {
        regValue = 63;    
    }

    // Left shift to make room for bits 1:0
    regValue = regValue << 2;         

    if (speed < 0) {
        regValue |= 0x01; // Set bits 1:0 based on sign of input.
    } else {
        regValue |= 0x02;
    }

    Serial.print("Sending drive speed: ");
    Serial.println(regValue, HEX);

    // Write to the CONTROL ADDR
    I2CWriteBytes(CONTROL_ADDR, &regValue, 1);
}

// Coast to a stop by hi-z'ing the drivers.
void MiniMotor::stop()
{
    byte regValue = 0; // See above for bit 1:0 explanation.
    
    I2CWriteBytes(CONTROL_ADDR, &regValue, 1);
}

// Stop the motor by providing a heavy load on it.
void MiniMotor::brake()
{
    byte regValue = 0x03; // See above for bit 1:0 explanation.

     I2CWriteBytes(CONTROL_ADDR, &regValue, 1);
}

// Private function that reads some number of bytes from motor driver.
void MiniMotor::I2CReadBytes(byte subAddr, char* buffer, byte len) 
{
    Serial.print("Reading bytes from slave addr: 0x");
    Serial.println(_i2cChannelAddr, HEX);

    // Send a START condition to begin a transimission of bytes on the specified channel address
    Wire.beginTransmission(_i2cChannelAddr); 
    
    // Sets the "Sub Address" which defines which register to read from (i.e. read from CONTROL: 0x00 or FAULT: 0x01 register)
    Wire.write(subAddr);   

    // Send a STOP condition to transmit the queued bytes
    Wire.endTransmission();

    // Add small delay before reading
    delay(100);

    // Next, request the number of bytes from slave device
    Wire.requestFrom(_i2cChannelAddr, len);
   
    int bytesLength = Wire.available();

    Serial.print("# of bytes availble from slave: ");
    Serial.println(bytesLength);

    if(bytesLength > 0) 
    {
        Wire.readBytes(buffer, len);

        Serial.print("Bytes: ");
        Serial.println(buffer);
    }    
}

// Private function that writes some number of bytes to motor driver.
void MiniMotor::I2CWriteBytes(byte subAddr, byte* buffer, byte len) 
{
    Serial.print("Writing bytes to slave addr: 0x");
    Serial.println(_i2cChannelAddr, HEX);

    // Send a START condition to begin a transmission of bytes on the specified channel address
    Wire.beginTransmission(_i2cChannelAddr); 
    
    // Sets the "Sub Address" which defines which register to write (i.e. write to CONTROL: 0x00 or FAULT: 0x01 register)
    Wire.write(subAddr);

    // Write the data next
    Wire.write(buffer, len); // Used overload because we writing byte pointer

    // Send a STOP condition to transmit the queued bytes
    Wire.endTransmission();
}

.ino

/*
 * Project Poolba
 * Description: Robot for pool cleaning.
 * Author: Devaron Ruggiero
 * Date: 8/1/2020
 */

#include "Particle.h"
#include "GroveMiniMotor.h"

#define FAULTn 6 // Pin used for fault detection.

// IMPORTANT: Review jumper settings on back of board first and set accordingly
// Based on jumper settings: A1= 0, A1 = 0
#define MOTOR1_WRITE_ADDR 0x60 // 0xC4 << 1 (shifted 1 because read/write methods use 7bits)
// Based on jumper settings: A1= 1, A0 = 0
#define MOTOR2_WRITE_ADDR 0x65 // 0xC0 << 1 (shifted 1 because read/write methods use 7bits)

// Create two MiniMotor instances with different I2C channel addresses
MiniMotor motor1(MOTOR1_WRITE_ADDR); // CH1
MiniMotor motor2(MOTOR2_WRITE_ADDR); // CH2

void setup() {
        
    Serial.begin(9600);
    Serial.println("Hello, world!");
    pinMode(FAULTn, INPUT);
}

void loop() {

    Serial.println("Forward!");

    motor1.drive(100);
    motor2.drive(100);
    delayUntil(10000);

    Serial.println("Stop!");
    motor1.stop();
    motor2.stop();
    delay(1000);
}

// delayUntil() is a little function to run the motor either for
//  a designated time OR until a fault occurs. Note that this is
//  a very simple demonstration; ideally, an interrupt would be
//  used to service faults rather than blocking the application
//  during motion and polling for faults.
void delayUntil(unsigned long elapsedTime) {

    // See the "BlinkWithoutDelay" example for more details on how
    //  and why this loop works the way it does.
    unsigned long startTime = millis();

    while (startTime + elapsedTime > millis()) {

        // If FAULTn goes low, a fault condition *may* exist. To be
        //  sure, we'll need to check the FAULT bit.
        if (digitalRead(FAULTn) == LOW) {

            Serial.println("Fault low. Reading fault status...");

            // We're going to check both motors; the logic is the same
            //  for each...
            char result = motor1.getFault();
            
            Serial.print("Fault Status: ");
            Serial.println(result, HEX);

            // If result masked by FAULT is non-zero, we've got a fault
            //  condition, and we should report it.

            // Motor 1
            if (result & FAULT) {

                Serial.print("Motor 1 fault: ");
                
                if (result & OCP) {
                    Serial.println("Chip overcurrent!");
                }

                if (result & ILIMIT) {
                    Serial.println("Load current limit!");
                }

                if (result & UVLO) {
                    Serial.println("Undervoltage!");
                }

                if (result & OTS) {
                    Serial.println("Over temp!");
                }

                break; // We want to break out of the motion immediately,
                //  so we can stop motion in response to our fault.
            }

            // Motor 2
            result = motor2.getFault();

            if (result & FAULT) {

                Serial.print("Motor 2 fault: ");

                if (result & OCP) {
                    Serial.println("Chip overcurrent!");
                }

                if (result & ILIMIT) {
                    Serial.println("Load current limit!");
                }

                if (result & UVLO) {
                    Serial.println("Undervoltage!");
                }

                if (result & OTS) {
                    Serial.println("Over temp!");
                }

                 break; // We want to break out of the motion immediately,
                //  so we can stop motion in response to our fault.
            }
        }
    }
}
1 Like