DRV10987 Sensorless BLDC motor driver

$15.00

Coming Soon

A sensorless BLDC driver breakout board using TI's DRV10987. It is a simple implementation of the typical application in the datasheet. The speed and direction can be controlled using two pins.

Features:
- Motor operation, 6.2 V to 28 V
- Total RDS(on) of 0.25Ω at T A = 25°C
- I2C Interface
- Speed and DIR pins
- Tach output
- Spin-Up Profile Can Be Customized With EEPROM

Photos / Renderings

DRV10987 EVM top

DRV10987 EVM bottom

Quickstart

  1. Connect SDA and SCL to the I2C pins
  2. Apply power to VCC and GND.
  3. Run the sample code

Sample code

The following code was written to use test EVM with micropython on an ESP8266.

"""
Copyright © 2021 codelv.com

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""
from machine import Pin, I2C, PWM

# Register names
class DRV10987:
    REGISTERS = {
        0x00: 'Fault',
        0x01: 'MotorSpeed',
        0x02: 'MotorPeriod',
        0x03: 'MotorKt',
        0x04: 'MotorCurrent',
        0x05: 'IPD/SupplyVolt',
        0x06: 'SpeedCmd/CmdBuf',
        0x07: 'AnalogInLvl',
        0x08: 'DevId/RevId',
        0x30: 'SpeedCtl',
        0x31: 'EEPROMProg1',
        0x32: 'EEPROMProg2',
        0x33: 'EEPROMProg3',
        0x34: 'EEPROMProg4',
        0x35: 'EEPROMProg5',
        0x36: 'EEPROMProg6',
        0x60: 'EECTRL',
        0x90: 'Config1',
        0x91: 'Config2',
        0x92: 'Config3',
        0x93: 'Config4',
        0x94: 'Config5',
        0x95: 'Config6',
        0x96: 'Config7',

    }

    EEPROM_DEFAULTS = {
        0x90: 0xC000,
        0x91: 0x0049,
        0x92: 0x00C1,
        0x93: 0x3788,
        0x94: 0x3BAF,
        0x95: 0x7840,
        0x96: 0x007A,
    }

    addr = 0b1010010

    def __init__(self, sda=4, scl=5, spd=14, dir=12):
        self.pwm = PWM(Pin(spd), freq=1000, duty=0)
        self.dir = Pin(dir, Pin.OUT)
        self.i2c = I2C(freq=100000, scl=Pin(scl), sda=Pin(sda)) 
        # EEPROM Config
        self.config = {}

    def dump_reg(self, reg, n=2):
        name = DRV10987.REGISTERS.get(reg, '')
        r = self.read_reg(reg, n)
        print("{: <30} 0x{:02x}: 0x{:04x}".format(name, reg, r))

    def dump(self):
        for i, name in DRV10987.REGISTERS.items():
            self.dump_reg(i)

    def write_reg(self, reg, data):
        """ Data is in format high byte, low byte """
        upper = (data >> 8) & 0xFF
        lower = data & 0xFF
        value = bytearray((upper, lower))
        self.i2c.writeto_mem(self.addr, reg, value)

    def read_reg(self, reg, n=2):
        mem = self.i2c.readfrom_mem(self.addr, reg, n)
        return int.from_bytes(mem, 'big')

    def disable_motor(self):
        self.write_reg(0x60, 0x8000)

    def enable_motor(self):
        self.write_reg(0x60, 0x0000)

    def set_motor_resistance(self, r):
        # R = 3.03
        assert 0 <= r <= 19.8656, "R is out of range"

        s = 0.0097
        base = 0
        for rm_shift in range(8):
            step = 2**rm_shift*s
            rlim = step*16
            if r < rlim:
                break
            base = rlim
        rm_v = int((r-base)/step)
        if rm_shift == 0:
            rm = rm_v
        else:
            rm = (rm_shift << 4) | 8 | rm_v
        reg = self.config[0x90]
        upper =  reg >> 8
        lower = (reg & 0xc0) | rm
        self.config[0x90] = upper | lower

    def set_motor_kt(self, kt):
        # Kt = 54.8 mV/R/S
        assert 0 <= kt <= 1884.16, "Kt is out of range"

        s = 0.92
        base = 0
        for kt_shift in range(8):
            step = 2**kt_shift*s
            kt_lim = step*16
            if kt < kt_lim:
                break
            base = kt_lim
        kt_v = int((kt-base)/step)
        if kt_shift == 0:
            k = kt_v
        else:
            k = (kt_shift << 4) | 8 | kt_v
        reg = self.config[0x91]
        upper =  k 
        lower = reg & 0xFF
        self.config[0x91] = upper | lower


    def reset_config(self):
        self.config = DRV10987.EEPROM_DEFAULTS.copy()

    def set_open_loop_current(self, v):
        r = self.config[0x92]
        r &= 0xFF3F 
        r |= ((v & 0x3) << 4)
        self.config[0x92] = r

    def set_speed_ctrl_pwm(self):
        r = self.config[0x95]
        r |= 1 << 15
        self.config[0x95] = r

    def set_speed_ctrl_analog(self):
        r = self.config[0x95]
        r &= 0x7FFF 
        self.config[0x95] = r

    def enable_eeprom_access(self):
        self.disable_motor()
        # Clear access code
        self.write_reg(0x31, 0)
        # Set access code
        self.write_reg(0x31, 0xC0DE)
        if not self.wait_eeprom_ready():
            raise RuntimeError("EEPROM Timeout")

    def read_eeprom(self):
        # Load into registers
        self.write_reg(0x35, 0b010)
        self.wait_eeprom_ready()
        # Read them
        for i in range(0x90, 0x97):
            self.config[i] = self.read_reg(i)
        self.enable_motor()

    def write_eeprom(self):
        if not self.config:
            raise RuntimeError("Use read_eeprom first!")
        # Write them
        for i in range(0x90, 0x97):
            self.write_reg(i, self.config[i])

        # Load into registers
        self.write_reg(0x35, 0b110)
        self.wait_eeprom_ready()
        self.enable_motor()

    def wait_eeprom_ready(self, attempts=100):
        for i in range(attempts):
            if self.is_eeprom_ready:
                return True

    @property
    def is_eeprom_ready(self):
        return (self.read_reg(0x32) & 1) == 1

    @property
    def faults(self):
        return self.read_reg(0x00)

    @property
    def current(self):
        r = self.read_reg(0x04) & 0x7FF
        return 3 * (r-1023)/2048

    @property
    def speed_ctrl(self):
        r = self.read_reg(0x30)
        if r >> 15:
            return r & 0x1FF

    @property
    def motor_r(self):
        r = self.read_reg(0x90)
        rm_v = r & 0b111
        rm_shift = r & 0b11000
        r_md = rm_v >> rm_shift
        return r_md * 0.009615

    @property
    def motor_kt(self):
        r = self.read_reg(0x03)
        return r/2/1090

    @property
    def supply_voltage(self):
        r = self.read_reg(0x05) & 0xFF
        return r * 30/255

    @property
    def ipd_position(self):
        return self.read_reg(0x05) >> 8

    @property
    def analog_in_level(self):
        r = self.read_reg(0x07) * 0x3FF
        return r * 3.3/1024

    @property
    def die_id(self):
        return self.read_reg(0x10) >> 8

    @property
    def rev_id(self):
        return self.read_reg(0x10) & 0xFF

    def configure(self, r_m, k_t, open_loop_current):
        self.enable_eeprom_access()
        self.read_eeprom()
        self.enable_eeprom_access()
        self.set_motor_resistance(r_m)
        self.set_motor_kt(k_t)
        self.set_open_loop_current(open_loop_current)
        self.set_speed_ctrl_pwm()
        self.write_eeprom()

drv = DRV10987()

Datasheet

DRV10987 Datasheet

Schematic

DRV10987 EVM Schematic

Comments

I made to run a brushless engraver spindle motor that had a damaged hall sensor.

It's is my first board. It was designed using Horizon-EDA. The v1.0 boards were made by OSH Park the latest v1.1 were made by JLCPCB and assembled by me.