2. MOVEMotor module
The MOVEMotor module is required to control the MOVEmotor buggy v3.1.
Download the python file
MOVEMotor.py module
.Place it in the mu_code folder: C:\Users\username\mu_code
The file needs to be copied onto the microbit.
In Mu editor, with the microbit attached by USB, click the Files icon.
Files on the microbit are shown on the left.
Files in the mu_code folder are listed on the right.
Click and drag the MOVEMotor.py file from the right window to the left window to copy it to the microbit.
Before copying:
After copying:
2.1. Use MOVEMotor library
To use the MOVEMotor module, import it via:
import MOVEMotor
.from microbit import *
import MOVEMotor
2.2. MOVEmotor buggy versions 1 and 2
The MOVEMotor_v2 module is required to control the MOVEmotor buggy versions 1 and 2.
Download the python file
MOVEMotor_v2.py module
.To use the MOVEMotor_v2 module, import it via:
import MOVEMotor_v2 as MOVEMotor
.This will allow the sample code in these docs to be used without a need to change all other references to
MOVEMotor
.from microbit import *
import MOVEMotor_v2 as MOVEMotor
2.3. MOVEMotor code v31
The full version of the MOVEMotor module is shown below.
1# micropython module for the Kitronik :MOVE Motor buggy v3.1
2# for microbit, v2
3# MOVEMotor module for motors, line following and distance sensing
4# GMC-code; 2023
5# The MIT License (MIT)
6# Depending on board version, motors are driven in different ways:
7# V1 and 2 - PCA9632, motor uses i2c
8# V3.1 - WS2811
9# The jerk message gives the motor a 'shove' at full power to aid starting on lower pwm ratios
10
11# motor uses pin12 on v3.1:MOVE Motor; WS2811 ICs, each with RGB (RG = Motor, B = Brake light)
12# right motor is WS2811[0] and backwards, forwards is the RG order
13# left motor is WS2811[1] and forwards, backwards is the RG order
14
15# A bytearray is a mutable sequence of bytes with integer values in the range 0 <= x < 256.
16# in motorJump[0] = 255, it’s stored as a byte in the bytearray and when retrieved it’s still a byte.
17# direct calls using integers seem to work as in: self.ws2811[1] = (0, 0, brightness)
18
19# see https://github.com/KitronikLtd/micropython-microbit-kitronik-MOVE-motor
20# see Kitronic MakeCode module: https://github.com/KitronikLtd/pxt-kitronik-move-motor/blob/master/main.ts
21# for quick lookups of hex values
22# see https://www.prepressure.com/library/technology/ascii-binary-hex
23# See datasheet: https://www.nxp.com/docs/en/data-sheet/PCA9632.pdf
24# pin15, pin16, are for servo
25# for v3.1 of movemotor; motor uses pin12 as a neopixel
26# pin 8 for LEDS
27
28from microbit import i2c, pin1, pin2, pin8, pin12, pin13, pin14, display, sleep
29
30
31import machine
32import utime
33from neopixel import NeoPixel
34
35
36# constants
37# LEDS_NUMBER = 4
38RIGHT_LINE_SENSOR_PIN = pin1
39LEFT_LINE_SENSOR_PIN = pin2
40TRIGGER_PIN = pin14
41ECHO_PIN = pin13
42
43CHIP_ADDR = 0x62
44# CHIP_ADDR is the standard chip address for the PCA9632,
45# datasheet refers to LED control but chip is used for PWM to motor driver
46MODE_1_REG_ADDR = 0x00
47MODE_1_REG_VALUE = 0x00
48# 00000000 setup to normal mode and not to respond to sub address
49MODE_2_REG_ADDR = 0x01
50MODE_2_REG_VALUE = 0x04
51# 00000100 Setup to make changes on ACK, outputs set to open-drain
52# open-drain 25 mA current sink capability at 5 V
53# in makecode has MODE_2_REG_VALUE = 0x0D 00001101
54# Setup to make changes on ACK, outputs set to Totem poled
55# totem pole with a 25 mA sink, 10 mA source capability at 5 V.
56# MOTOR_OUT_ADDR = 0x08 # 00001000 MOTOR output register address
57# MOTOR_OUT_VALUE = 0xAA # 10101010 Outputs set to be controlled PWM registers
58# Register offsets for the motors
59# RIGHT_MOTOR_REV = 0x02 # PWM0
60# RIGHT_MOTOR = 0x03 # PWM1
61# LEFT_MOTOR = 0x04 # PWM2
62# LEFT_MOTOR_REV = 0x05 # PWM3
63# ALL_MOTOR = 0xA2 # 10100010
64
65
66class MOVEMotorMotors:
67 def __init__(self):
68
69 try:
70 # Attempting to write/read a register on the PCA9632 IC will identify
71 # whether the IC is there or not (no PCA9632 on V3.1)
72 # Note: This check relies on the micro:bit not throwing an error
73 # when an I2C address is used which isn't present on the I2C bus
74 buffer = bytearray(2)
75 buffer[0] = MODE_1_REG_ADDR
76 buffer[1] = MODE_1_REG_VALUE
77 i2c.write(CHIP_ADDR, buffer, False)
78 readBuffer = i2c.read(CHIP_ADDR, 1, False)
79 if readBuffer[0] == MODE_1_REG_VALUE:
80 self.moveMotorVersion = 10 # not 31
81 display.scroll("Change library: not v3.1")
82 except OSError:
83 self.moveMotorVersion = 31
84 # display.scroll(self.moveMotorVersion, delay=60)
85 # ws2811[0] is right motor, [1] is left motor
86 self.ws2811 = NeoPixel(pin12, 2)
87
88 def stop_left(self, brightness=255):
89 # Left motor
90 self.ws2811[1] = (0, 0, brightness)
91 self.ws2811.show()
92
93 def stop_right(self, brightness=255):
94 # Right motor
95 self.ws2811[0] = (0, 0, brightness)
96 self.ws2811.show()
97
98 def stop(self, brightness=255):
99 self.stop_left(brightness)
100 self.stop_right(brightness)
101
102 @staticmethod
103 def _analog_speed(speed):
104 # input speed = -10 to 0 to 10
105 # output = 60 to 255
106 if speed < 0 and speed >= -10:
107 return int((speed * -21) + 45)
108 elif speed > 0 and speed <= 10:
109 return int((speed * 21) + 45)
110 else:
111 return 0
112
113 def left_motor(self, speed=1, duration=None):
114 motorBuf = bytearray([0, 0, 0])
115 motorJump = bytearray([0, 0, 0])
116 motor_speed = self._analog_speed(speed)
117 # Left motor
118 wsIndex = 1
119 if speed > 0:
120 # Going forwards
121 motorBuf[0] = motor_speed
122 motorJump[0] = 255
123 else:
124 # Going backwards
125 motorBuf[1] = motor_speed
126 motorJump[1] = 255
127 self.ws2811[wsIndex] = (motorJump[0], motorJump[1], motorJump[2])
128 self.ws2811.show()
129 sleep(1) # 1 ms to allow for motor processing
130 self.ws2811[wsIndex] = (motorBuf[0], motorBuf[1], motorBuf[2])
131 self.ws2811.show()
132 #
133 if duration is not None:
134 utime.sleep_ms(duration)
135 self.stop_left()
136
137 def right_motor(self, speed=1, duration=None):
138 # speed values are integers or floats (decimals) from -10 to 10.
139 motorBuf = bytearray([0, 0, 0])
140 motorJump = bytearray([0, 0, 0])
141 motor_speed = self._analog_speed(speed)
142 # Right motor
143 wsIndex = 0
144 if speed > 0:
145 # Going forwards
146 motorBuf[1] = motor_speed
147 motorJump[1] = 255
148 else:
149 # Going backwards
150 motorBuf[0] = motor_speed
151 motorJump[0] = 255
152 self.ws2811[wsIndex] = (motorJump[0], motorJump[1], motorJump[2])
153 self.ws2811.show()
154 sleep(1) # 1 ms
155 self.ws2811[wsIndex] = (motorBuf[0], motorBuf[1], motorBuf[2])
156 self.ws2811.show()
157 #
158 if duration is not None:
159 utime.sleep_ms(duration)
160 self.stop_left()
161
162 @staticmethod
163 def _straight_line_adjustment(analog_speed, adjustment):
164 # limit adjustment to 0 to 20
165 # make percentage adjustment (adjustment/max analog) to _analog_speed
166 if adjustment < 0:
167 adjustment = 0
168 elif adjustment > 20:
169 adjustment = 20
170 return int(analog_speed * (255 - adjustment) / 255)
171
172 def backwards(self, speed=1, duration=None, decrease_left=0, decrease_right=0):
173 # speed 0 to 10
174 analog_speed = abs(self._analog_speed(speed))
175 motorBuf = bytearray([0, 0, 0])
176 motorJump = bytearray([0, 0, 0])
177 ##
178 right_speed = self._straight_line_adjustment(analog_speed, decrease_right)
179 left_speed = self._straight_line_adjustment(analog_speed, decrease_left)
180 # Jump motor to get it going for 1 millisec at full power
181 # Going backwards
182 # Right motor
183 motorJump[0] = 255
184 # right motor is WS2811[0] and backwards, forwards is the RG order
185 self.ws2811[0] = (motorJump[0], motorJump[1], motorJump[2])
186 # left motor
187 motorJump[0] = 0
188 motorJump[1] = 255
189 # left motor is WS2811[1] and forwards, backwards is the RG order
190 self.ws2811[1] = (motorJump[0], motorJump[1], motorJump[2])
191 self.ws2811.show()
192 sleep(1) # 1 ms
193 # Going backwards
194 # Right motor
195 motorBuf[0] = right_speed
196 # right motor is WS2811[0] and backwards, forwards is the RG order
197 self.ws2811[0] = (motorBuf[0], motorBuf[1], motorBuf[2])
198 # left motor
199 motorBuf[0] = 0
200 motorBuf[1] = left_speed
201 # left motor is WS2811[1] and forwards, backwards is the RG order
202 self.ws2811[1] = (motorBuf[0], motorBuf[1], motorBuf[2])
203 self.ws2811.show()
204 #
205 if duration is not None:
206 utime.sleep_ms(duration)
207 self.stop()
208
209 def forwards(self, speed=1, duration=None, decrease_left=0, decrease_right=0):
210 # speed 0 to 10
211 analog_speed = abs(self._analog_speed(speed))
212 motorBuf = bytearray([0, 0, 0])
213 motorJump = bytearray([0, 0, 0])
214 ##
215 right_speed = self._straight_line_adjustment(analog_speed, decrease_right)
216 left_speed = self._straight_line_adjustment(analog_speed, decrease_left)
217 # Jump motor to get it going for 1 millisec at full power
218 # Right motor
219 motorJump[1] = 255
220 # right motor is WS2811[0] and backwards, forwards is the RG order
221 self.ws2811[0] = (motorJump[0], motorJump[1], motorJump[2])
222 # left motor
223 motorJump[0] = 255
224 motorJump[1] = 0
225 # left motor is WS2811[1] and forwards, backwards is the RG order
226 self.ws2811[1] = (motorJump[0], motorJump[1], motorJump[2])
227 self.ws2811.show()
228 #
229 sleep(1) # 1 ms
230 # Going backwards
231 # Right motor
232 motorBuf[1] = right_speed
233 # right motor is WS2811[0] and backwards, forwards is the RG order
234 self.ws2811[0] = (motorBuf[0], motorBuf[1], motorBuf[2])
235 # left motor
236 motorBuf[0] = left_speed
237 motorBuf[1] = 0
238 # left motor is WS2811[1] and forwards, backwards is the RG order
239 self.ws2811[1] = (motorBuf[0], motorBuf[1], motorBuf[2])
240 self.ws2811.show()
241 #
242 if duration is not None:
243 utime.sleep_ms(duration)
244 self.stop()
245
246 @staticmethod
247 def _turn_radius_factor(radius=25):
248 # limit radius (in cm) of inner wheel to 4 to 800 cm
249 # calculate the relative speed factor of the inner to outer wheel
250 # uses approximate distance between buggy wheels of 8.5 cm
251 if radius < 4:
252 radius = 4
253 elif radius > 800:
254 radius = 800
255 return (radius + 8.5) / radius
256
257 def left(self, speed=1, radius=25, duration=None):
258 # right motor faster than left
259 # speed values are integers or floats (decimals) from -10 to 10.
260 # speed values above 0 drive the buggy forwards to the left.
261 # speedvalues below 0 drive the buggy backwards to the left.
262 motor_speed = self._analog_speed(speed)
263 turn_radius_factor = self._turn_radius_factor(radius)
264 motor_speed_inner = int(motor_speed / turn_radius_factor)
265 #
266 right_motorBuf = bytearray([0, 0, 0])
267 right_motorJump = bytearray([0, 0, 0])
268 left_motorBuf = bytearray([0, 0, 0])
269 left_motorJump = bytearray([0, 0, 0])
270 if speed > 0:
271 # Going forwards
272 # right motor is WS2811[0]; 0 backwards, 1 forwards is the RG order
273 right_motorBuf[1] = motor_speed
274 right_motorJump[1] = 255
275 # left motor is WS2811[1]; 0 forwards, 1 backwards is the RG order
276 left_motorBuf[0] = motor_speed_inner
277 left_motorJump[0] = 255
278 else:
279 # Going backwards
280 # right motor is WS2811[0]; 0 backwards, 1 forwards is the RG order
281 right_motorBuf[0] = motor_speed
282 right_motorJump[0] = 255
283 # left motor is WS2811[1]; 0 forwards, 1 backwards is the RG order
284 left_motorBuf[1] = motor_speed_inner
285 left_motorJump[1] = 255
286 # right
287 self.ws2811[0] = (right_motorJump[0], right_motorJump[1], right_motorJump[2])
288 # left
289 self.ws2811[1] = (left_motorJump[0], left_motorJump[1], left_motorJump[2])
290 self.ws2811.show()
291 sleep(1) # 1 ms
292 # right
293 self.ws2811[0] = (right_motorBuf[0], right_motorBuf[1], right_motorBuf[2])
294 # left
295 self.ws2811[1] = (left_motorBuf[0], left_motorBuf[1], left_motorBuf[2])
296 self.ws2811.show()
297 #
298 if duration is not None:
299 utime.sleep_ms(duration)
300 self.stop_left()
301
302 def right(self, speed=1, radius=25, duration=None):
303 # left motor faster than right
304 # speed values are integers or floats (decimals) from -10 to 10.
305 # speed values above 0 drive the buggy forwards to the left.
306 # speedvalues below 0 drive the buggy backwards to the left.
307 motor_speed = self._analog_speed(speed)
308 turn_radius_factor = self._turn_radius_factor(radius)
309 motor_speed_inner = int(motor_speed / turn_radius_factor)
310 #
311 right_motorBuf = bytearray([0, 0, 0])
312 right_motorJump = bytearray([0, 0, 0])
313 left_motorBuf = bytearray([0, 0, 0])
314 left_motorJump = bytearray([0, 0, 0])
315 if speed > 0:
316 # Going forwards
317 # right motor is WS2811[0]; 0 backwards, 1 forwards is the RG order
318 right_motorBuf[1] = motor_speed_inner
319 right_motorJump[1] = 255
320 # left motor is WS2811[1]; 0 forwards, 1 backwards is the RG order
321 left_motorBuf[0] = motor_speed
322 left_motorJump[0] = 255
323 else:
324 # Going backwards
325 # right motor is WS2811[0]; 0 backwards, 1 forwards is the RG order
326 right_motorBuf[0] = motor_speed_inner
327 right_motorJump[0] = 255
328 # left motor is WS2811[1]; 0 forwards, 1 backwards is the RG order
329 left_motorBuf[1] = motor_speed
330 left_motorJump[1] = 255
331 # right
332 self.ws2811[0] = (right_motorJump[0], right_motorJump[1], right_motorJump[2])
333 # left
334 self.ws2811[1] = (left_motorJump[0], left_motorJump[1], left_motorJump[2])
335 self.ws2811.show()
336 sleep(1) # 1 ms
337 # right
338 self.ws2811[0] = (right_motorBuf[0], right_motorBuf[1], right_motorBuf[2])
339 # left
340 self.ws2811[1] = (left_motorBuf[0], left_motorBuf[1], left_motorBuf[2])
341 self.ws2811.show()
342 if duration is not None:
343 utime.sleep_ms(duration)
344 self.stop()
345
346 def spin_left(self, speed=1, duration=None):
347 # speed values are integers or floats (decimals) from 0 to 10.
348 analog_speed = abs(self._analog_speed(speed))
349 #
350 right_motorBuf = bytearray([0, 0, 0])
351 right_motorJump = bytearray([0, 0, 0])
352 left_motorBuf = bytearray([0, 0, 0])
353 left_motorJump = bytearray([0, 0, 0])
354 # left back and right fwd
355 # right motor is WS2811[0]; 0 backwards, 1 forwards is the RG order
356 right_motorBuf[1] = analog_speed
357 right_motorJump[1] = 255
358 # left motor is WS2811[1]; 0 forwards, 1 backwards is the RG order
359 left_motorBuf[1] = analog_speed
360 left_motorJump[1] = 255
361 # right
362 self.ws2811[0] = (right_motorJump[0], right_motorJump[1], right_motorJump[2])
363 # left
364 self.ws2811[1] = (left_motorJump[0], left_motorJump[1], left_motorJump[2])
365 self.ws2811.show()
366 sleep(1) # 1 ms
367 # right
368 self.ws2811[0] = (right_motorBuf[0], right_motorBuf[1], right_motorBuf[2])
369 # left
370 self.ws2811[1] = (left_motorBuf[0], left_motorBuf[1], left_motorBuf[2])
371 self.ws2811.show()
372 #
373 if duration is not None:
374 utime.sleep_ms(duration)
375 self.stop()
376
377 def spin_right(self, speed=1, duration=None):
378 # speed values are integers or floats (decimals) from 0 to 10.
379 analog_speed = abs(self._analog_speed(speed))
380 #
381 right_motorBuf = bytearray([0, 0, 0])
382 right_motorJump = bytearray([0, 0, 0])
383 left_motorBuf = bytearray([0, 0, 0])
384 left_motorJump = bytearray([0, 0, 0])
385 # left fws and right back
386 # right motor is WS2811[0]; 0 backwards, 1 forwards is the RG order
387 right_motorBuf[0] = analog_speed
388 right_motorJump[0] = 255
389 # left motor is WS2811[1]; 0 forwards, 1 backwards is the RG order
390 left_motorBuf[0] = analog_speed
391 left_motorJump[0] = 255
392 # right
393 self.ws2811[0] = (right_motorJump[0], right_motorJump[1], right_motorJump[2])
394 # left
395 self.ws2811[1] = (left_motorJump[0], left_motorJump[1], left_motorJump[2])
396 self.ws2811.show()
397 sleep(1) # 1 ms
398 # right
399 self.ws2811[0] = (right_motorBuf[0], right_motorBuf[1], right_motorBuf[2])
400 # left
401 self.ws2811[1] = (left_motorBuf[0], left_motorBuf[1], left_motorBuf[2])
402 self.ws2811.show()
403 #
404 if duration is not None:
405 utime.sleep_ms(duration)
406 self.stop()
407
408
409class MOVEMotorLineSensors:
410 def __init__(self):
411 self.left_offset = 0
412 self.right_offset = 0
413
414 def line_sensor_calibrate(self):
415 rightLineSensor = RIGHT_LINE_SENSOR_PIN.read_analog()
416 leftLineSensor = LEFT_LINE_SENSOR_PIN.read_analog()
417 offset = int(abs(rightLineSensor - leftLineSensor) / 2)
418 if leftLineSensor > rightLineSensor:
419 self.left_offset = -offset
420 self.right_offset = offset
421 else:
422 self.left_offset = offset
423 self.right_offset = -offset
424
425 def line_sensor_read(self, sensor):
426 if sensor == "left":
427 return LEFT_LINE_SENSOR_PIN.read_analog() + self.left_offset
428 elif sensor == "right":
429 return RIGHT_LINE_SENSOR_PIN.read_analog() + self.right_offset
430
431
432
433class MOVEMotorDistanceSensors:
434 def distance(self):
435 ECHO_PIN.set_pull(ECHO_PIN.NO_PULL)
436 TRIGGER_PIN.write_digital(0)
437 utime.sleep_us(2)
438 TRIGGER_PIN.write_digital(1)
439 utime.sleep_us(10)
440 TRIGGER_PIN.write_digital(0)
441 distance = machine.time_pulse_us(ECHO_PIN, 1, 1160000)
442 if distance > 0:
443 # distance in cm
444 return round(distance / 58)
445 else:
446 return 0
2.4. MOVEMotor code
The simplified version of the MOVEMotor module is shown below.
It has comments removed to reduce the file size.
1"""
2micropython module for the Kitronik :MOVE Motor buggy v3.1
3for microbit, v2
4MOVEMotor module for motors, line following and distance sensing
5GMC-code; 2023
6"""
7from microbit import pin1, pin2, pin12, pin13, pin14, sleep
8import machine
9import utime
10from neopixel import NeoPixel
11
12RIGHT_LINE_SENSOR_PIN = pin1
13LEFT_LINE_SENSOR_PIN = pin2
14TRIGGER_PIN = pin14
15ECHO_PIN = pin13
16
17
18class MOVEMotorMotors:
19 def __init__(self):
20 self.ws2811 = NeoPixel(pin12, 2)
21
22 def stop_left(self, brightness=255):
23 self.ws2811[1] = (0, 0, brightness)
24 self.ws2811.show()
25
26 def stop_right(self, brightness=255):
27 self.ws2811[0] = (0, 0, brightness)
28 self.ws2811.show()
29
30 def stop(self, brightness=255):
31 self.stop_left(brightness)
32 self.stop_right(brightness)
33
34 @staticmethod
35 def _analog_speed(speed):
36 if speed < 0 and speed >= -10:
37 return int((speed * -21) + 45)
38 elif speed > 0 and speed <= 10:
39 return int((speed * 21) + 45)
40 else:
41 return 0
42
43 def left_motor(self, speed=1, duration=None):
44 mB = bytearray([0, 0, 0])
45 mJ = bytearray([0, 0, 0])
46 motor_speed = self._analog_speed(speed)
47 wsIndex = 1
48 if speed > 0:
49 mB[0] = motor_speed
50 mJ[0] = 255
51 else:
52 mB[1] = motor_speed
53 mJ[1] = 255
54 self.ws2811[wsIndex] = (mJ[0], mJ[1], mJ[2])
55 self.ws2811.show()
56 sleep(1)
57 self.ws2811[wsIndex] = (mB[0], mB[1], mB[2])
58 self.ws2811.show()
59 if duration is not None:
60 utime.sleep_ms(duration)
61 self.stop_left()
62
63 def right_motor(self, speed=1, duration=None):
64 mB = bytearray([0, 0, 0])
65 mJ = bytearray([0, 0, 0])
66 motor_speed = self._analog_speed(speed)
67 wsIndex = 0
68 if speed > 0:
69 mB[1] = motor_speed
70 mJ[1] = 255
71 else:
72 mB[0] = motor_speed
73 mJ[0] = 255
74 self.ws2811[wsIndex] = (mJ[0], mJ[1], mJ[2])
75 self.ws2811.show()
76 sleep(1)
77 self.ws2811[wsIndex] = (mB[0], mB[1], mB[2])
78 self.ws2811.show()
79 if duration is not None:
80 utime.sleep_ms(duration)
81 self.stop_left()
82
83 @staticmethod
84 def _straight_line_adjustment(analog_speed, adjustment):
85 if adjustment < 0:
86 adjustment = 0
87 elif adjustment > 20:
88 adjustment = 20
89 return int(analog_speed * (255 - adjustment) / 255)
90
91 def backwards(self, speed=1, duration=None, decrease_left=0, decrease_right=0):
92 analog_speed = abs(self._analog_speed(speed))
93 mB = bytearray([0, 0, 0])
94 mJ = bytearray([0, 0, 0])
95 right_speed = self._straight_line_adjustment(analog_speed, decrease_right)
96 left_speed = self._straight_line_adjustment(analog_speed, decrease_left)
97 mJ[0] = 255
98 self.ws2811[0] = (mJ[0], mJ[1], mJ[2])
99 mJ[0] = 0
100 mJ[1] = 255
101 self.ws2811[1] = (mJ[0], mJ[1], mJ[2])
102 self.ws2811.show()
103 sleep(1)
104 mB[0] = right_speed
105 self.ws2811[0] = (mB[0], mB[1], mB[2])
106 mB[0] = 0
107 mB[1] = left_speed
108 self.ws2811[1] = (mB[0], mB[1], mB[2])
109 self.ws2811.show()
110 if duration is not None:
111 utime.sleep_ms(duration)
112 self.stop()
113
114 def forwards(self, speed=1, duration=None, decrease_left=0, decrease_right=0):
115 analog_speed = abs(self._analog_speed(speed))
116 mB = bytearray([0, 0, 0])
117 mJ = bytearray([0, 0, 0])
118 right_speed = self._straight_line_adjustment(analog_speed, decrease_right)
119 left_speed = self._straight_line_adjustment(analog_speed, decrease_left)
120 mJ[1] = 255
121 self.ws2811[0] = (mJ[0], mJ[1], mJ[2])
122 mJ[0] = 255
123 mJ[1] = 0
124 self.ws2811[1] = (mJ[0], mJ[1], mJ[2])
125 self.ws2811.show()
126 sleep(1)
127 mB[1] = right_speed
128 self.ws2811[0] = (mB[0], mB[1], mB[2])
129 mB[0] = left_speed
130 mB[1] = 0
131 self.ws2811[1] = (mB[0], mB[1], mB[2])
132 self.ws2811.show()
133 if duration is not None:
134 utime.sleep_ms(duration)
135 self.stop()
136
137 @staticmethod
138 def _turn_radius_factor(radius=25):
139 if radius < 4:
140 radius = 4
141 elif radius > 800:
142 radius = 800
143 return (radius + 8.5) / radius
144
145 def left(self, speed=1, radius=25, duration=None):
146 motor_speed = self._analog_speed(speed)
147 turn_radius_factor = self._turn_radius_factor(radius)
148 motor_speed_inner = int(motor_speed / turn_radius_factor)
149 right_mB = bytearray([0, 0, 0])
150 right_mJ = bytearray([0, 0, 0])
151 left_mB = bytearray([0, 0, 0])
152 left_mJ = bytearray([0, 0, 0])
153 if speed > 0:
154 right_mB[1] = motor_speed
155 right_mJ[1] = 255
156 left_mB[0] = motor_speed_inner
157 left_mJ[0] = 255
158 else:
159 right_mB[0] = motor_speed
160 right_mJ[0] = 255
161 left_mB[1] = motor_speed_inner
162 left_mJ[1] = 255
163 self.ws2811[0] = (right_mJ[0], right_mJ[1], right_mJ[2])
164 self.ws2811[1] = (left_mJ[0], left_mJ[1], left_mJ[2])
165 self.ws2811.show()
166 sleep(1)
167 self.ws2811[0] = (right_mB[0], right_mB[1], right_mB[2])
168 self.ws2811[1] = (left_mB[0], left_mB[1], left_mB[2])
169 self.ws2811.show()
170 if duration is not None:
171 utime.sleep_ms(duration)
172 self.stop_left()
173
174 def right(self, speed=1, radius=25, duration=None):
175 motor_speed = self._analog_speed(speed)
176 turn_radius_factor = self._turn_radius_factor(radius)
177 motor_speed_inner = int(motor_speed / turn_radius_factor)
178 right_mB = bytearray([0, 0, 0])
179 right_mJ = bytearray([0, 0, 0])
180 left_mB = bytearray([0, 0, 0])
181 left_mJ = bytearray([0, 0, 0])
182 if speed > 0:
183 right_mB[1] = motor_speed_inner
184 right_mJ[1] = 255
185 left_mB[0] = motor_speed
186 left_mJ[0] = 255
187 else:
188 right_mB[0] = motor_speed_inner
189 right_mJ[0] = 255
190 left_mB[1] = motor_speed
191 left_mJ[1] = 255
192 self.ws2811[0] = (right_mJ[0], right_mJ[1], right_mJ[2])
193 self.ws2811[1] = (left_mJ[0], left_mJ[1], left_mJ[2])
194 self.ws2811.show()
195 sleep(1)
196 self.ws2811[0] = (right_mB[0], right_mB[1], right_mB[2])
197 self.ws2811[1] = (left_mB[0], left_mB[1], left_mB[2])
198 self.ws2811.show()
199 if duration is not None:
200 utime.sleep_ms(duration)
201 self.stop()
202
203 def spin_left(self, speed=1, duration=None):
204 analog_speed = abs(self._analog_speed(speed))
205 right_mB = bytearray([0, 0, 0])
206 right_mJ = bytearray([0, 0, 0])
207 left_mB = bytearray([0, 0, 0])
208 left_mJ = bytearray([0, 0, 0])
209 right_mB[1] = analog_speed
210 right_mJ[1] = 255
211 left_mB[1] = analog_speed
212 left_mJ[1] = 255
213 self.ws2811[0] = (right_mJ[0], right_mJ[1], right_mJ[2])
214 self.ws2811[1] = (left_mJ[0], left_mJ[1], left_mJ[2])
215 self.ws2811.show()
216 sleep(1)
217 self.ws2811[0] = (right_mB[0], right_mB[1], right_mB[2])
218 self.ws2811[1] = (left_mB[0], left_mB[1], left_mB[2])
219 self.ws2811.show()
220 if duration is not None:
221 utime.sleep_ms(duration)
222 self.stop()
223
224 def spin_right(self, speed=1, duration=None):
225 analog_speed = abs(self._analog_speed(speed))
226 right_mB = bytearray([0, 0, 0])
227 right_mJ = bytearray([0, 0, 0])
228 left_mB = bytearray([0, 0, 0])
229 left_mJ = bytearray([0, 0, 0])
230 right_mB[0] = analog_speed
231 right_mJ[0] = 255
232 left_mB[0] = analog_speed
233 left_mJ[0] = 255
234 self.ws2811[0] = (right_mJ[0], right_mJ[1], right_mJ[2])
235 self.ws2811[1] = (left_mJ[0], left_mJ[1], left_mJ[2])
236 self.ws2811.show()
237 sleep(1)
238 self.ws2811[0] = (right_mB[0], right_mB[1], right_mB[2])
239 self.ws2811[1] = (left_mB[0], left_mB[1], left_mB[2])
240 self.ws2811.show()
241 if duration is not None:
242 utime.sleep_ms(duration)
243 self.stop()
244
245
246class MOVEMotorLineSensors:
247 def __init__(self):
248 self.left_offset = 0
249 self.right_offset = 0
250
251 def line_sensor_calibrate(self):
252 rightLineSensor = RIGHT_LINE_SENSOR_PIN.read_analog()
253 leftLineSensor = LEFT_LINE_SENSOR_PIN.read_analog()
254 offset = int(abs(rightLineSensor - leftLineSensor) / 2)
255 if leftLineSensor > rightLineSensor:
256 self.left_offset = -offset
257 self.right_offset = offset
258 else:
259 self.left_offset = offset
260 self.right_offset = -offset
261
262 def line_sensor_read(self, sensor):
263 if sensor == "left":
264 return LEFT_LINE_SENSOR_PIN.read_analog() + self.left_offset
265 elif sensor == "right":
266 return RIGHT_LINE_SENSOR_PIN.read_analog() + self.right_offset
267
268
269class MOVEMotorDistanceSensors:
270 def distance(self):
271 ECHO_PIN.set_pull(ECHO_PIN.NO_PULL)
272 TRIGGER_PIN.write_digital(0)
273 utime.sleep_us(2)
274 TRIGGER_PIN.write_digital(1)
275 utime.sleep_us(10)
276 TRIGGER_PIN.write_digital(0)
277 distance = machine.time_pulse_us(ECHO_PIN, 1, 1160000)
278 if distance > 0:
279 return round(distance / 58)
280 else:
281 return 0
2.5. MOVEMotor code v2
The MOVEMotor module that supports v2 of the MOVEMotor, using i2c to control the motors, is shown below.
1# MOVEMotor module for motors, line following and distance sensing
2# requires microbit v2
3# GMC-code; 2021
4# The MIT License (MIT)
5# motor uses i2c
6# A microbit v2 micropython module for the Kitronik :MOVE Motor buggy
7# see Kitronic MakeCode module: https://github.com/KitronikLtd/pxt-kitronik-motor-driver
8# for quick lookups of hex values
9# see https://www.prepressure.com/library/technology/ascii-binary-hex
10# See datasheet: https://www.nxp.com/docs/en/data-sheet/PCA9632.pdf
11
12from microbit import i2c, pin1, pin2, pin13, pin14
13import machine
14import utime
15
16
17# constants
18RIGHT_LINE_SENSOR_PIN = pin1
19LEFT_LINE_SENSOR_PIN = pin2
20TRIGGER_PIN = pin14
21ECHO_PIN = pin13
22
23CHIP_ADDR = 0x62
24# CHIP_ADDR is the standard chip address for the PCA9632,
25# datasheet refers to LED control but chip is used for PWM to motor driver
26MODE_1_REG_ADDR = 0x00
27MODE_1_REG_VALUE = 0x00
28# 00000000 setup to normal mode and not to respond to sub address
29MODE_2_REG_ADDR = 0x01
30MODE_2_REG_VALUE = 0x04
31# 00000100 Setup to make changes on ACK, outputs set to open-drain
32# open-drain 25 mA current sink capability at 5 V
33# in makecode has MODE_2_REG_VALUE = 0x0D 00001101
34# Setup to make changes on ACK, outputs set to Totem poled
35# totem pole with a 25 mA sink, 10 mA source capability at 5 V.
36MOTOR_OUT_ADDR = 0x08 # 00001000 MOTOR output register address
37MOTOR_OUT_VALUE = 0xAA # 10101010 Outputs set to be controlled PWM registers
38# Register offsets for the motors
39RIGHT_MOTOR_REV = 0x02 # PWM0
40RIGHT_MOTOR = 0x03 # PWM1
41LEFT_MOTOR = 0x04 # PWM2
42LEFT_MOTOR_REV = 0x05 # PWM3
43ALL_MOTOR = 0xA2 # 10100010
44
45
46class MOVEMotorMotors:
47
48 def __init__(self):
49 buffer = bytearray(2)
50 buffer[0] = MODE_1_REG_ADDR
51 buffer[1] = MODE_1_REG_VALUE
52 i2c.write(CHIP_ADDR, buffer, False)
53 buffer[0] = MODE_2_REG_ADDR
54 buffer[1] = MODE_2_REG_VALUE
55 i2c.write(CHIP_ADDR, buffer, False)
56 buffer[0] = MOTOR_OUT_ADDR
57 buffer[1] = MOTOR_OUT_VALUE
58 i2c.write(CHIP_ADDR, buffer, False)
59
60 def stop_left(self):
61 stop_buffer = bytearray(2)
62 stop_buffer[0] = LEFT_MOTOR
63 stop_buffer[1] = 0
64 i2c.write(CHIP_ADDR, stop_buffer, False)
65 stop_buffer[0] = LEFT_MOTOR_REV
66 i2c.write(CHIP_ADDR, stop_buffer, False)
67
68 def stop_right(self):
69 stop_buffer = bytearray(2)
70 stop_buffer[1] = 0
71 stop_buffer[0] = RIGHT_MOTOR
72 i2c.write(CHIP_ADDR, stop_buffer, False)
73 stop_buffer[0] = RIGHT_MOTOR_REV
74 i2c.write(CHIP_ADDR, stop_buffer, False)
75
76 def stop(self):
77 self.stop_left()
78 self.stop_right()
79
80 @staticmethod
81 def _analog_speed(speed):
82 # input speed = -10 to 0 to 10
83 # output = 60 to 255
84 if speed < 0 and speed >= -10:
85 return int((speed * -21) + 45)
86 elif speed > 0 and speed <= 10:
87 return int((speed * 21) + 45)
88 else:
89 return 0
90
91 def left_motor(self, speed=1, duration=None):
92 motor_buffer = bytearray(2)
93 gnd_pin_buffer = bytearray(2)
94 motor_buffer[1] = self._analog_speed(speed)
95 gnd_pin_buffer[1] = 0
96 if (speed > 0):
97 motor_buffer[0] = LEFT_MOTOR
98 gnd_pin_buffer[0] = LEFT_MOTOR_REV
99 else:
100 motor_buffer[0] = LEFT_MOTOR_REV
101 gnd_pin_buffer[0] = LEFT_MOTOR
102 i2c.write(CHIP_ADDR, motor_buffer, False)
103 i2c.write(CHIP_ADDR, gnd_pin_buffer, False)
104 if duration is not None:
105 utime.sleep_ms(duration)
106 self.stop_left()
107
108 def right_motor(self, speed=1, duration=None):
109 motor_buffer = bytearray(2)
110 gnd_pin_buffer = bytearray(2)
111 motor_buffer[1] = self._analog_speed(speed)
112 gnd_pin_buffer[1] = 0
113 if (speed > 0):
114 motor_buffer[0] = RIGHT_MOTOR
115 gnd_pin_buffer[0] = RIGHT_MOTOR_REV
116 else:
117 motor_buffer[0] = RIGHT_MOTOR_REV
118 gnd_pin_buffer[0] = RIGHT_MOTOR
119 i2c.write(CHIP_ADDR, motor_buffer, False)
120 i2c.write(CHIP_ADDR, gnd_pin_buffer, False)
121 if duration is not None:
122 utime.sleep_ms(duration)
123 self.stop_right()
124
125 @staticmethod
126 def _straight_line_adjustment(analog_speed, adjustment):
127 # limit adjustment to 0 to 20
128 # make percentage adjustment (adjustment/max analog) to _analog_speed
129 if adjustment < 0:
130 adjustment = 0
131 elif adjustment > 20:
132 adjustment = 20
133 return int(analog_speed * (255 - adjustment)/255)
134
135 def backwards(self, speed=1, duration=None, decrease_left=0, decrease_right=0):
136 analog_speed = abs(self._analog_speed(speed))
137 motor_buffer = bytearray(5)
138 motor_buffer[0] = ALL_MOTOR
139 # [1 to 4] is RIGHT_MOTOR_REV; RIGHT_MOTOR; LEFT_MOTOR; LEFT_MOTOR_REV
140 motor_buffer[1] = self._straight_line_adjustment(analog_speed, decrease_right)
141 motor_buffer[2] = 0
142 motor_buffer[3] = 0
143 motor_buffer[4] = self._straight_line_adjustment(analog_speed, decrease_left)
144 i2c.write(CHIP_ADDR, motor_buffer, False)
145 if duration is not None:
146 utime.sleep_ms(duration)
147 self.stop()
148
149 def forwards(self, speed=1, duration=None, decrease_left=0, decrease_right=0):
150 analog_speed = abs(self._analog_speed(speed))
151 motor_buffer = bytearray(5)
152 motor_buffer[0] = ALL_MOTOR
153 # [1 to 4] is RIGHT_MOTOR_REV; RIGHT_MOTOR; LEFT_MOTOR; LEFT_MOTOR_REV
154 motor_buffer[1] = 0
155 motor_buffer[2] = self._straight_line_adjustment(analog_speed, decrease_right)
156 motor_buffer[3] = self._straight_line_adjustment(analog_speed, decrease_left)
157 motor_buffer[4] = 0
158 i2c.write(CHIP_ADDR, motor_buffer, False)
159 if duration is not None:
160 utime.sleep_ms(duration)
161 self.stop()
162
163 @staticmethod
164 def _turn_radius_factor(radius=25):
165 # limit radius (in cm) of inner wheel to 4 to 800 cm
166 # calculate the relative speed factor of the inner to outer wheel
167 # uses approximate distance between buggy wheels of 8.5 cm
168 if radius < 4:
169 radius = 4
170 elif radius > 800:
171 radius = 800
172 return (radius + 8.5) / radius
173
174 def left(self, speed=1, radius=25, duration=None):
175 # right motor faster than left
176 analog_speed = self._analog_speed(speed)
177 turn_radius_factor = self._turn_radius_factor(radius)
178 motor_buffer = bytearray(5)
179 motor_buffer[0] = ALL_MOTOR
180 # [1 to 4] is RIGHT_MOTOR_REV; RIGHT_MOTOR; LEFT_MOTOR; LEFT_MOTOR_REV
181 motor_buffer[1] = 0
182 motor_buffer[2] = analog_speed
183 motor_buffer[3] = int(analog_speed/turn_radius_factor)
184 motor_buffer[4] = 0
185 i2c.write(CHIP_ADDR, motor_buffer, False)
186 if duration is not None:
187 utime.sleep_ms(duration)
188 self.stop()
189
190 def right(self, speed=1, radius=25, duration=None):
191 # left motor faster than right
192 analog_speed = self._analog_speed(speed)
193 turn_radius_factor = self._turn_radius_factor(radius)
194 motor_buffer = bytearray(5)
195 motor_buffer[0] = ALL_MOTOR
196 # [1 to 4] is RIGHT_MOTOR_REV; RIGHT_MOTOR; LEFT_MOTOR; LEFT_MOTOR_REV
197 motor_buffer[1] = 0
198 motor_buffer[2] = int(analog_speed/turn_radius_factor)
199 motor_buffer[3] = analog_speed
200 motor_buffer[4] = 0
201 i2c.write(CHIP_ADDR, motor_buffer, False)
202 if duration is not None:
203 utime.sleep_ms(duration)
204 self.stop()
205
206 def spin_left(self, speed=1, duration=None):
207 analog_speed = abs(self._analog_speed(speed))
208 motor_buffer = bytearray(5)
209 motor_buffer[0] = ALL_MOTOR
210 # [1 to 4] is RIGHT_MOTOR_REV; RIGHT_MOTOR; LEFT_MOTOR; LEFT_MOTOR_REV
211 motor_buffer[1] = 0
212 motor_buffer[2] = analog_speed
213 motor_buffer[3] = 0
214 motor_buffer[4] = analog_speed
215 i2c.write(CHIP_ADDR, motor_buffer, False)
216 if duration is not None:
217 utime.sleep_ms(duration)
218 self.stop()
219
220 def spin_right(self, speed=1, duration=None):
221 analog_speed = abs(self._analog_speed(speed))
222 motor_buffer = bytearray(5)
223 motor_buffer[0] = ALL_MOTOR
224 # [1 to 4] is RIGHT_MOTOR_REV; RIGHT_MOTOR; LEFT_MOTOR; LEFT_MOTOR_REV
225 motor_buffer[1] = analog_speed
226 motor_buffer[2] = 0
227 motor_buffer[3] = analog_speed
228 motor_buffer[4] = 0
229 i2c.write(CHIP_ADDR, motor_buffer, False)
230 if duration is not None:
231 utime.sleep_ms(duration)
232 self.stop()
233
234
235class MOVEMotorLineSensors:
236
237 def __init__(self):
238 self.left_offset = 0
239 self.right_offset = 0
240
241 def line_sensor_calibrate(self):
242 rightLineSensor = RIGHT_LINE_SENSOR_PIN.read_analog()
243 leftLineSensor = LEFT_LINE_SENSOR_PIN.read_analog()
244 offset = int(abs(rightLineSensor-leftLineSensor)/2)
245 if leftLineSensor > rightLineSensor:
246 self.left_offset = -offset
247 self.right_offset = offset
248 else:
249 self.left_offset = offset
250 self.right_offset = -offset
251
252 def line_sensor_read(self, sensor):
253 if sensor == 'left':
254 return LEFT_LINE_SENSOR_PIN.read_analog() + self.left_offset
255 elif sensor == 'right':
256 return RIGHT_LINE_SENSOR_PIN.read_analog() + self.right_offset
257
258
259class MOVEMotorDistanceSensors:
260
261 def distance(self):
262 ECHO_PIN.set_pull(ECHO_PIN.NO_PULL)
263 TRIGGER_PIN.write_digital(0)
264 utime.sleep_us(2)
265 TRIGGER_PIN.write_digital(1)
266 utime.sleep_us(10)
267 TRIGGER_PIN.write_digital(0)
268 distance = machine.time_pulse_us(ECHO_PIN, 1, 1160000)
269 if distance > 0:
270 # distance in cm
271 return round(distance/58)
272 else:
273 return 0