RoadTest: All-in-one Robotics Board + micro:bit
Author: richwilliams
Creation date:
Evaluation Type: Development Boards & Tools
Did you receive all parts the manufacturer stated would be included in the package?: True
What other parts do you consider comparable to this product?: This is the only motor controller I've tried for the micro:bit
What were the biggest problems encountered?: Bugs in the Demo software. They were easy to fix but shouldn't have been there.
Detailed Review:
Table of contents
First, I want to thank Kitronik and Element14 for selecting me to test the board. I had fun and learned a lot testing the board.
The All-in-one Robotics Board for BBC micro:bit enables the BBC micro:bit to drive 4 motors (or 2 stepper motors )
and 8 servos. The only I/O used by the board on the micro:bit is the I2C port so all the micro:bit's expansion pins are available
for other I/O devices. The board has a 3.3V regulator to power the BBC micro:bit.
The heart of the board is an NXP PCA9685 16-channel, 12-bit PWM I2C LED controller. The other chips on the board are two dual H-Bridge drivers for the 4 DC or 2 stepper motors and 2 other chips which I assume are drivers for the 8 servo outputs. The drivers are controlled by the PWM outputs: one for each servo and two for each H-Bridge. It's a clever way to make a low cost controller that can control three types of motors.
Goals for this review:
For the RoadTest, element14 provided at the All-in-one Robotics Board and a BBC micro:bit
Here is the unpacked Kitronik All-in-one Robotics Board
None in the box but the Kitronik webpage for the board (5641-all-in-one-robotics-board-for-bbc-microbit.html) contains what you need to get started:
What’s missing?
Schematic
The product page also has a Q&A section. I posted a question to see if I can have the micro:bit plugged into USB while the robotics board is powered from an external supply and they quickly replied that I can.
From GitHub, you can download a zip file containing:
The version I downloaded to test was commited on Oct 11, 2018.
The robotics.py and the TOO-LARGE files contain the same code but the comments have been removed in robotics.py to save space in the micro:bit.
Robotics.py defines a class named KintronikRoboticsBoard that implements methods for stepper, regular DC and servo motors. The file also contains code that will exercise the methods if the file is loaded as the main file on the micro:bit.
After applying for this Roadtest, I read two books on MicroPython: Programming with MicroPython by Nicholas Tollervery and Make: Getting Started with the micro:bit by Wolfram Donat.
I was very impressed with MicroPython. I expected some sort of ‘Tiny Python’ that would be pretty much coding in C with Python-like syntax. MicroPython is a very full implementation of Python. We teach a Python workshop for beginning programmers and it will be very easy for those students to use those skills for Arduino like projects.
There are a couple ways to create MicroPython code for the micro:bit
I tried #1 and #2 and used the app for my testing . The mu-editor app was easy to install and use. They kept it to the basic functionality needed making it easy to understand. It supports ‘minifying’ your code which strips out comments and extra spaces when creating the hex file to save memory space in the micro:bit. I later learned that saving space is very important when using MicroPython on the micro:bit.
Mu-editor is very particular about how it wants spaces around comments, commas and parenthesis and limits the lines to 79 characters. I found this annoying but assume it’s there to teach beginning programmers to be consistent.
Screenshot of mu-editor which has definite opinions about whitespace.
Testing will be done in three phases: Verifying the motor signals, verifying the ability to use the micro:bit I/O and building a robot.
Test setup showing the Analog Discovery hooked up to the Kitronik All-in-one Robotics Board and measuring the DC motor signals.
Test 1: Verify the motor signals using a scope
This test will load the robotic.py into mu-editor and then download it into the micro:bit. The micro:bit will then be plugged into and powered by the Kitronik board
What I'm looking for:
What I observed:
Screenshot showing errors in Mu
This test will exercise the stepper motor output Stepper1 using calls in the robotics.py code to move it 100 steps.
theBoard.stepperMotorTurnSteps(theBoard, "Stepper1", "forward", 100)
What I'm looking for and why:
What I observed:
Screen capture from Stepper Motor test.
Channel 1 (Yellow) shows motor1, which would hook up to Coil A of a 4 wire bipolar stepper motor.
Channel 2 (Blue) shows motor2, which would hook up to Coil B of a 4 wire bipolar stepper motor.
Screen capture zooming in on Dead Time Insertion.
Both motors are off (0V) during the last 500 microseconds of the PWM period.
Motor1 (Yellow) did not need dead time since it was not reversing polarity.
Motor2 (Blue) reversed polarity and needed the dead time.
Analysis of results:
The PWM chip has it's own clock which is not synchronized to the micro:bit. If the micro:bit hasn't updted the PWM value by the end of the period, the PWM chip will reload the last value extending the stage by 20ms. Reversing the polarity requires turning on one polarity by setting it's PWM value from 0% to 100% and then setting the other polarity's PWM from 100% to 0%. Sometimes the PWM reload happens between the two changes leaving the H-bridge in braking mode for 20ms.
Dead Time Insertion is done by never allowing a 100% duty cycle. 100% motor speed is mapped to 4000 instead of 4095 on a 12 bit PWM leaving about 460 microseconds of dead time at the end of each PWM period. This is why we see DTI even when the polarity isn't changing.
Is it a problem?
I spent a while pondering this and thinking of a way to synchronize the micro:bit code with the PWM updates. I also concluded it's probably harmless when the micro:bit code is late updating the PWM value since it just extends the last step. Likewise the unneeded DTI insertions should be harmless since the H-bridge will be in coasting mode during this time.
This test will exercise the DC motor outputs Motor3 and Motor4 using calls in the robotics.py code to set Motor3 to 10% in one direction and Motor4 to 100% in the other direction:
theBoard.motorOn(theBoard, 3, "forward", 10)
theBoard.motorOn(theBoard, 4, "reverse", 100)
What I'm looking for and why:
What I observed:
Screen capture from DC Motor test.
Channel 1 (Yellow) shows motor3. A negative voltage indicates the motor active in the forward direction.
Channel 2 (Blue) shows motor3. A positive voltage indicates the motor active in the reverse direction.
Analysis of results:
Everything behaved as expected.
This test will exercise the servo motor outputs SV1 - SV8 using calls in the robotics.py code to set them at 0, 90 or 180 degrees:
theBoard.servoWrite(theBoard, 1, 180)
theBoard.servoWrite(theBoard, 2, 90)
theBoard.servoWrite(theBoard, 3, 0)
theBoard.servoWrite(theBoard, 4, 180)
theBoard.servoWrite(theBoard, 5, 90)
theBoard.servoWrite(theBoard, 6, 0)
theBoard.servoWrite(theBoard, 7, 180)
theBoard.servoWrite(theBoard, 8, 90)
What I'm looking for and why:
What I observed:
Screen capture from Servo Motor test.
Analysis of results:
All the pulses are about 0.5ms short.
The code in robotics.py to calculate the PWM value is:
PWMVal = (degrees * 100 * self.SERVO_MULTIPLIER) / (10000 + self.SERVO_ZERO_OFFSET)
if degrees = 0, PWMVal will be 0 which explains why we had no pulse. SERVO_ZERO_OFFSET should be added after the division.
PWMVal = ((degrees * 100 * self.SERVO_MULTIPLIER) / 10000) + self.SERVO_ZERO_OFFSET
Changing the code will also fix the 90° and 180° signals.
Screen capture from Servo Motor test after changing the code.
All the pulses and the PWM period are now pretty close (about 5% long).
Why are we about 5% off?
The PCA9685 PWM controller chip has an internal 25MHZ oscillator. The datasheet doesn't specify a tolerance for the clock and just says: '25 MHz typical internal oscillator requires no external components'. For LED applications, the accuracy probably isn't important as long as it's fast. This is a possible source of the difference in timing
The PWM rate is set by setting a prescale register in the PCA9685. Here is the code from robotics.py:
buf[0] = self.PRESCALE_REG
buf[1] = 0x85 #50Hz
i2c.write(self.chipAddress, buf, False)
The equation from the datasheet for calculating the prescale value is: prescale_value = round(osc_clock/(4096*update_rate))-1
or, we can rearrange it to:
update_rate = osc_clock/(4096*(prescale_value + 1))
If osc_clock = 25MHz and the prescale_value = 0x85, the update rate will be 45.55Hz instead of 50Hz.
The writer was probably adjusting the prescale to make the refresh rate 50Hz on their board. I'll leave it alone for now unless it causes a problem.
Now that we have tested and fixed any problems with the motor signals and demo software, we can move on to testing using the micro:bit I/O.
The only micro:bit I/O pin used by the All-in-one Robotics board is I2C so all of the micro:bit's I/O is available except for the I2C address used by the robotics board.
To test this, I will need to:
Soldering header pins to the robotics board was straightforward.
However, the webpage for the board says:
If using the breakout pads at the back of the board, you will also need Soldering iron, solder and wire cutters.
It should also say that you need header pins.
Robotics board with micro:bit I/O headers added
I wrote a program to read and display the temperature while scrolling the LEDs on the NeoPixel strip. I tested the breadboard with an AdaFruit Dragontail breakout board with no problems.
I then added the robotics.py code to test it with the robotics board. My RoadTest then ran into a couple of speed bumps.
The combined code was too large to load at startup and the micro:bit failed with a memory error at startup. I tried keeping the KitronikRoboticsBoard class in a separate file but it still failed. I'm not using stepper motors so I removed the stepper motor calls for now. I'll talk more about code space later when making a robot.
After removing the stepper code, the code loaded with no problems but would usually fail after a few seconds with an I2C write error. This was unexpected since it ran with no problems without the robotics board!
I put a scope on the I2C line and saw that it was over 1/3 V when a 0 was on the bus. The micro:bit can only source or sink a small amount of current. The micro:bit has 4.7KΩ pull-ups on the on the I2C line, the robotics board appears to have 10KΩ pull-ups and the TMP102Ω board has 1KΩ pull-ups. These three in parallel are about 760Ω which is probably too much current for the micro:bit pin. The micro:bit can have three pins configured for high current (5ma) which I assume are used for the led array.
I changed my code to use an MCP9808 temperature sensor board that has 10KΩ pull-ups and everything worked fine. A 0 on the I2C line measured 0V on the scope.
The final test:
Display a happy face on the LED array
Turns off the servos
Turns on three servos to 180º, 90º, 0º
Reads temperature over I2C
Displays temperature on LED array
Scrolls a NeoPixel on the strip
I'm only lighting one NeoPixel at a time to keep the current under the 90ma spec of the robotics board 3V pin.
Picture of the I/O test with servo outputs, I2C temperature sensor and a NeoPixel strip.
Here's a movie of the test in action.
We've got motor control and can use other devices so we're ready to build a robot!
Testing the robot motors with the robotics board.
My robot will have the following:
My finished robot
My tale of woe with memory issues
My first pass at the code that loaded the support for the robotics board, US-100 and NeoPixels but didn't do much with them got memory errors when loading.
Failing this early usually means there wasn't enough space to load the .py file and convert it to byte codes.
I then simplified the NeoPixel animation and it loaded but got a memory error when instantiating one of the classes.
I then gave up on the NeoPixels, which probably allocate all sorts of buffers, and removed the code using them.
The code successfully loaded and drove the robot in a straight line but wasn't finished.
I added some code to turn the robot when an obstacle is detected but ran out of memory again.
I removed the unneeded servo code from the Kitronik class and was up, running and avoiding obstacles.
Hooray, but I was disappointed to lose my blinking lights!
I dug into the Kitronik code and the PCA9685 data sheet and was able to save space by combining routines and using features in the chip like auto increment and writing to all PWMs with one write. This saved enough space to use the NeoPixels. Aside from having a couple of bugs (which it shouldn't), the Demo code did it's job: showing me an easy to follow example of how to talk to the board.
My robot doing a victory lap
I like the board and would recommend it to my students. With one board you can control three types of motors for a reasonable price and it provides a nice low cost solution for STEM and art projects. Memory is limited for MicroPython but I'm still amazed the we can run MicroPython at all.
Suggested improvements:
Top Comments
A very thorough review. Were it not for the memory problems, Micropython on the micro:bit would be great. You can get more of your own code into the micro:bit if you use MakeCode blocks.
Very nice review and road test richwilliams. Two thunbs up.
Clem