DRV8871 H-Bridge
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.
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# -----------------------------------------