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:

../../_images/Mu_files3.png

After copying:

../../_images/Mu_files_copied3.png

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