DRV8871 H-Bridge

../_images/drv8871.jpg

DRV8871 Breakout Board from Adafruit

The DRV8871 is a CMOS H-Bridge Driver IC manufactured by Texas Instruments. These boards are commonly available in Breakouts by Adafruit and other manufacturers. The DRV8871 is a versatile H-Bridge driver that can operate between 6.5V and 45V and supply up to 3.6A 1. Control of the DRV8871 can occur with both 3.3V and 5V logic with static and PWM control schemes [5]. Current limiting is determined by an external resistor, enabling overcurrent protection on the device. This specification proves useful for a wide variety of circuits and particularly motor controls.

../_images/h_bridge_control.png

DRV8871 H-Bridge Control Table 1.

../_images/h_bridge_control.png

DRV8871 H-Bridge current paths 1.

I developed a micro python driver for the DRV8871 that allows for PWM control of the H-bridge using two GPIO pins from a micropython enabled microprocessor such as the raspberry pico. Given correct wiring, the microprocessor can control the flow of current and the duty cycle to which it is pulsed. As a result, a developer can easily command the H-bridge into a forward, reverse, coast, or brake state. Users can also determine the duty cycle for which the system should be active and if the H-bridge reverts to a braking or coasting state when off.

class src.pico_power_management.DRV8871.DRV8871(name: str, in1, in2, pwm_freq=100000, description=None, *args, **kwargs)
HIGH = 65535
LOW = 0
MAX_PWM_FREQ = 200000
backward(duty=5, coast=True)

Set the device in a backward state. Duty Cycle of backward power is controlled by PWM ON time.

Parameters
  • duty (int | float) – Duty Cycle of backward power given on a scale of 0 to 100, defaults to 5.

  • coast (bool) – Determines the alternate state of operation. Either Coast (True) or Brake (False). Defaults to True.

brake()

Set the device to stop abruptly.

coast()

Set the device to coast.

forward(duty=5, coast=True)

Set the device in a forward state. Duty Cycle of forward power is controlled by PWM ON time.

Parameters
  • duty (int | float) – Duty Cycle of forward power given on a scale of 0 to 100, defaults to 5.

  • coast (bool) – Determines the alternate state of operation. Either Coast (True) or Brake (False). Defaults to True.

full_backward()

Set the device in a full backward state.

full_forward()

Set the device in a full forward state.

status()

Query the status of the H-bridge controls.

NOTE: we cannot read the H-Bridge’s status directly, rather this method returns the status of the associated pins and PWM system.

Code

  1# -----------------------------------------
  2#                 NOTES 
  3# -----------------------------------------
  4
  5# Dieter Steinhauser
  6# 11/2023
  7
  8# DRV8871 H-Bridge Motor Controller driver.
  9
 10
 11# -----------------------------------------
 12#               IMPORTS
 13# -----------------------------------------
 14
 15from machine import Pin, PWM
 16import time
 17from src.pico_power_management.helpers import *
 18# from helpers import *
 19# -----------------------------------------
 20#                Class:
 21# -----------------------------------------
 22
 23
 24desc = r"""
 25The DRV8871 output consists of four N-channel MOSFETs that are designed to drive high current. They are
 26controlled by the two logic inputs IN1 and IN2.
 27
 28IN1 IN2 OUT1, OUT2 DESCRIPTION
 290   0   HI-Z  HI-Z  Coast, H=bridge disabled to High-Z
 300   1   L     H     Reverse (current OUT2->OUT1)
 311   0   H     L     Forward (current OUT1->OUT2)
 321   1   L     L     Brake, Low side slow decay.
 33
 34
 35The inputs can be set to static voltages for 100% duty cycle drive, or they can be pulse-width modulated (PWM)
 36for variable motor speed. When using PWM, it typically works best to switch between driving and braking. For
 37example, to drive a motor forward with 50% of its max RPM, IN1 = 1 and IN2 = 0 during the driving period, and
 38IN1 = 1 and IN2 = 1 during the other period. Alternatively, the coast mode (IN1 = 0, IN2 = 0) for fast current
 39decay is also available. The input pins can be powered before VM is applied.
 40
 41"""
 42
 43
 44class DRV8871:
 45    
 46    MAX_PWM_FREQ = 200_000
 47    HIGH = 65535
 48    LOW = 0
 49    
 50    def __init__(self, name:str, in1, in2, pwm_freq=100_000, description = None, *args, **kwargs) -> None:
 51        """Object initialization for DRV8871. Follow device initialization and adds register information to object"""
 52        
 53        # Throw errors for incorrect values.
 54        check_type(in1, 'in1', (int, Pin))
 55        check_type(in2, 'in2', (int, Pin)) 
 56        check_type(pwm_freq, 'pwm_freq', int)
 57
 58        if isinstance(in1, int):
 59            check_range(in1, 'in1', 0, 28)
 60        if isinstance(in2, int):
 61            check_range(in2, 'in2', 0, 28)
 62
 63        check_range(pwm_freq, 'pwm_freq', 0, self.MAX_PWM_FREQ)
 64
 65        # assign the object variables
 66        self.name  = name
 67        self.pwm_freq = pwm_freq
 68        self.description = desc if description is None else description
 69
 70        # Create Pin Objects
 71        self.in1 = Pin(in1, Pin.OUT) if isinstance(in1, int) else in1
 72        self.in2 = Pin(in2, Pin.OUT) if isinstance(in2, int) else in2
 73
 74        # Create PWM channels with the Pins
 75        self.in1_pwm = PWM(self.in1)
 76        self.in1_pwm.freq(pwm_freq)
 77        self.in1_pwm.duty_u16(self.LOW)
 78        self.in2_pwm = PWM(self.in2)
 79        self.in2_pwm.freq(pwm_freq)
 80        self.in2_pwm.duty_u16(self.LOW)
 81
 82    def full_forward(self):
 83        """Set the device in a full forward state."""
 84
 85        # Set IN2 Low then IN1 High.
 86        self.in2_pwm.duty_u16(self.LOW)
 87        self.in1_pwm.duty_u16(self.HIGH)
 88
 89    def full_backward(self):
 90        """Set the device in a full backward state."""
 91
 92        # Set IN1 Low then IN2 High.
 93        self.in1_pwm.duty_u16(self.LOW)
 94        self.in2_pwm.duty_u16(self.HIGH)
 95
 96    def forward(self, duty = 5, coast=True):
 97        """
 98        Set the device in a forward state. 
 99        Duty Cycle of forward power is controlled by PWM ON time.
100
101        :param duty: Duty Cycle of forward power given on a scale of 0 to 100, defaults to 5.
102        :type duty: int | float
103        :param coast: Determines the alternate state of operation. Either Coast (True) or Brake (False). Defaults to True.
104        :type coast: bool
105        """
106        # Throw errors for incorrect values
107        check_type(duty, 'duty', (int, float))
108        check_range(duty, 'duty', 0, 100)
109        check_type(coast, 'coast', bool) 
110
111        if coast: # Alternates between forward and coast
112            
113            # Start PWM at the desired Duty Cycle
114            duty = int((duty/100) * 65535)
115            self.in2_pwm.duty_u16(self.LOW)
116            self.in1_pwm.duty_u16(duty)
117
118        else: # Alternates between forward and Brake
119
120            # Start PWM at the desired Duty Cycle
121            duty = int(((100-duty)/100) * 65535)
122            self.in2_pwm.duty_u16(duty)
123            self.in1_pwm.duty_u16(self.HIGH)
124
125    def backward(self, duty = 5, coast=True):
126        """
127        Set the device in a backward state. 
128        Duty Cycle of backward power is controlled by PWM ON time.
129
130        :param duty: Duty Cycle of backward power given on a scale of 0 to 100, defaults to 5.
131        :type duty: int | float
132        :param coast: Determines the alternate state of operation. Either Coast (True) or Brake (False). Defaults to True.
133        :type coast: bool
134        """
135        # Throw errors for incorrect values
136        check_type(duty, 'duty', (int, float))
137        check_range(duty, 'duty', 0, 100)
138        check_type(coast, 'coast', bool) 
139
140        if coast: # Alternates between forward and coast
141            
142            # Start PWM at the desired Duty Cycle
143            duty = int((duty/100) * 65535)
144            self.in1_pwm.duty_u16(self.LOW)
145            self.in2_pwm.duty_u16(duty)
146
147        else: # Alternates between forward and Brake
148
149            # Start PWM at the desired Duty Cycle
150            duty = int(((100-duty)/100) * 65535)
151            self.in1_pwm.duty_u16(duty)
152            self.in2_pwm.duty_u16(self.HIGH)
153
154    def coast(self):
155        """Set the device to coast. """
156
157        # Set the device to coast
158        self.in1_pwm.duty_u16(self.LOW)
159        self.in2_pwm.duty_u16(self.LOW)
160
161    def brake(self):
162        """Set the device to stop abruptly. """
163
164        # Set the device to brake
165        self.in1_pwm.duty_u16(self.HIGH)
166        self.in2_pwm.duty_u16(self.HIGH)
167
168    def status(self):
169        """
170        Query the status of the H-bridge controls.
171
172        NOTE: we cannot read the H-Bridge's status directly, rather this method returns the status of the associated pins and PWM system.
173        """
174        return_dict = {}
175        in1_duty = self.in1_pwm.duty_u16() 
176        in2_duty = self.in2_pwm.duty_u16() 
177
178        # IN1 IN2 OUT1, OUT2 DESCRIPTION
179        # 0   0   HI-Z  HI-Z  Coast, H=bridge disabled to High-Z
180        # 0   1   L     H     Reverse (current OUT2->OUT1)
181        # 1   0   H     L     Forward (current OUT1->OUT2)
182        # 1   1   L     L     Brake, Low side slow decay.
183
184        state = None
185        off_state = None
186        effective_duty = None
187
188        # Based on the Pin setup, determine the status of the system
189        if in1_duty == self.HIGH and in2_duty == self.HIGH:
190            state = "BRAKE"
191            off_state = 'BRAKE'
192        
193        elif in1_duty == self.LOW and in2_duty == self.LOW:
194            state = 'COAST'
195            off_state = 'COAST'
196
197        elif self.LOW <= in1_duty <= self.HIGH and in2_duty == self.HIGH:
198            state = 'BACKWARD'
199            off_state = 'BRAKE'
200            effective_duty = 100 - int(100* in1_duty / 65535)
201
202        elif self.LOW <= in2_duty <= self.HIGH and in1_duty == self.LOW:
203            state = 'REVERSE'
204            off_state = 'COAST'
205            effective_duty = int(100* in2_duty / 65535)
206
207        elif self.LOW <= in2_duty <= self.HIGH and in1_duty == self.HIGH:
208            state = 'FORWARD'
209            off_state = 'BRAKE'
210            effective_duty = 100 - int(100* in2_duty / 65535)
211
212        elif self.LOW <= in1_duty <= self.HIGH and in2_duty == self.LOW:
213            state = 'FORWARD'
214            off_state = 'COAST'
215            effective_duty = int(100* in1_duty / 65535)
216
217        in1_pwm = {'FREQ': self.in1_pwm.freq(), 'DUTY': int(100* in1_duty / 65535)}
218        in2_pwm = {'FREQ': self.in2_pwm.freq(), 'DUTY': int(100* in2_duty / 65535)}
219
220        return_dict['in1_pwm'] = in1_pwm
221        return_dict['in2_pwm'] = in2_pwm
222        return_dict['effective_duty'] = effective_duty
223        return_dict['state'] = state
224        return_dict['off_state'] = off_state
225        return return_dict
226    
227
228if __name__ == '__main__':
229
230    drv = DRV8871(name='Motor Driver', in1 = Pin(13, Pin.OUT), in2=Pin(12, Pin.OUT))
231    
232    test = True
233    if test:
234        print('testing')
235        drv.full_forward()
236        print(drv.status())
237        time.sleep(1)
238        drv.brake()
239        print(drv.status())
240        time.sleep(1)
241        drv.forward(80)
242        print(drv.status())
243        time.sleep(1)
244        drv.coast()
245        print(drv.status())
246        time.sleep(1)
247        drv.forward(80, coast=False)
248        time.sleep(1)
249        drv.brake()
250        print(drv.status())
251        
252        
253
254        
255
256# -----------------------------------------
257#              END OF FILE
258# -----------------------------------------
1(1,2,3)

Texas Instruments. “DRV8871 3.6-A Brushed DC Motor Driver with Internal Current.” [Online]. Available: [www.ti.com/lit/ds/symlink/drv8871.pdf].