A servo is a motor device that turns to and holds a position. The servo you will be working with in these activities is the kind that steers small remote controlled cars, boats, and planes. They are also found in products like copy machines and the mechanical lens auto-focus in some high-end cameras. Stage animators use large ones to move props and mannequins. Larger versions of the servo are also used by this robot to manipulate a gripper it uses to pick up objects.
TSJC Robotics Team [1] entry in the COSGC’s Robotics Challenge [2], source: YouTube [3]
In these activities, you will build circuits and write scripts that make the micro:bit:
Here is a list of items you will need and tutorials we recommend you complete before continuing with these activities.
You will need:
Complete these tutorials first:
You will be able to:
These skills become important in robotics projects where you have to achieve a certain goal by monitoring sensors and using the measurements to make motors do things like pick items up, position them, and more. These skills are also used in factory settings where motors are expected to position items during assembly. Of course, if you want to invent your own radio controlled car, boat, plane, or other remotely operated vehicle (ROV), these activities contain some important basics to help get you started.
In these activities, you will write scripts to control the servo motor directly. In addition, you will also incorporate the pushbutton circuits from earlier activities for servo control and the LED circuits as indicators.
Green, yellow and red LED circuits connected to P13, P14, and P15 from Blink Sequencing:
Pushbutton circuit connected to P6 from Build & Test a Pushbutton and pushbutton circuit connected to P9 with parts from Inside the Pushbutton plus parts and circuit from Second Pushbutton.
Alligator clip probes from First Electrical Connections with a Breadboard and recently used in Measure Pushbutton LED Control with an Oscilloscope:
Jumper wires that connect the 3V to the (+) bus strips and GND to the (-) bus strips from Set Power for Circuits:
3-pin headers from First Electrical Connections with a Breadboard that connect the alligator clip probes to P2 and P0:
If you completed Measure Resistance [15], leave these Ohmmeter Circuit parts connected to your board too:
Some motors can draw more current than a USB port is designed to supply to peripheral devices. So, for motor control, we typically use a battery pack to supply the motor current. In this activity, you will:
(1) Standard servo - 3 V
(1) AAA cell battery pack
(2) New alkaline 1.5 V AAA batteries
When a battery pack is plugged into the Edge I/O adapter, the voltage/current it can supply is available through the left bus strip’s (+) and (-) columns.
Make sure to remove the batteries from the battery pack when it’s not in use. Otherwise, the circuit will drain the batteries.
JST is an abbreviation for Japan Solderless Terminal, a standard established by JST Mfg. Co. Small, white plastic connectors like the ones you see on the micro:bit and the Edge Adapter are commonly used inside products and appliances. We don’t normally see them since they are on printed circuit boards (PCBs) that are built into the product. The black fiberglass micro:bit and Edge I/O Adapter boards are examples of PCBs.
measure_volts.hex [16]
When your battery holder has alkaline batteries and measures 2.8 V or less, it means the batteries have lost much of their charge. This can lead to sluggish or nonexistent servo motor motion. So, this test is important to make sure the batteries are not too low for servo control.
A single AAA alkaline battery is considered to be a 1.5 V supply when used in rough calculations. Two alkaline batteries connected in series (end-to-end) have their voltages added. Since the AAA battery holder connects the two AAA batteries in series for you, it is considered to be a 3 V supply.
When alkaline batteries are new, they typically measure around 1.6 V. As they supply current to circuits, devices, or motors, they lose their charge. Charge is the accumulation of electrons, and a battery’s charge decays as it supplies current (the flow of electrons). By the time the alkaline batteries in series reach 2.8 V, the batteries are considered “dead” for micro:bit servo control.
Although alkaline batteries have a voltage charge relationship where voltage decays with charge, there are also different kinds of batteries that have different voltage to charge relationships. For example, lead-acid batteries found in older model cars have to be tested by a machine that applies a load and measures how much current the battery supplies. Another example, nickel-metal hydride (NiMH) rechargeable batteries have a much smaller change in voltage to indicate their charge level.
If you have some dead AAA batteries:
When a USB is connected to the micro:bit, the Edge Connector connects the USB supply’s GND voltage to the left bus strip’s (-) rail. It also connects the regulated 3.3 V from the micro:bit to the 3.3 V pin. When the battery is connected its (-) terminal is also connected to the left bus strip’s (-) rail. This is called a “common ground”. In this case, ground is common to three systems: the USB 5 V supply, the micro:bit’s 3.3 V supply, and the battery pack’s 3 V supply.
THE DIFFERENT POSITIVE SUPPLY VOLTAGES SHOULD NOT BE CONNECTED IN COMMON. That is why the battery pack’s Vbat supply is only available from the left bus strip’s (+) rail. The micro:bit’s 3.3 V supply is available from the center and right bus strip’s (+) rails, which are connected to the Edge I/O Adapter’s 3V connection with jumper wires you added in the Set Power for Circuits activity’s Parts & Circuit page. Since the battery pack’s voltage might be 3.2 V when the batteries are new and 2.8 V when they are almost dead, it is called an unregulated supply. In contrast, the micro:bit module’s 3.3 V supply is considered a regulated supply. For example, with the USB cable connected to the micro:bit, it might supply something in the 4.5 to 5.5 V range to the micro:bit. But, the micro:bit’s built-in voltage regulator would still supply 3.3 V at the 3V pin. The job of a voltage regulator is to supply unchanging or “regulated” voltage.
Let’s compare the unregulated battery voltage to the regulated micro:bit voltage. To do this you will, probe the center bus strip’s (+) and (-) rails. In other words:
The voltage you just measured is the voltage regulated by the micro:bit, so your voltage measurement should be closer to 3.3 V. Again, while the unregulated battery voltage will probably be somewhere in the 2.8 to 3.2 V range, when the micro:bit is connected to USB, the supply from the Edge Adapter’s 3V pin should be very close to 3.3 V.
A hobby servo is normally used to hold the positions of radio controlled model airplane flaps, boat rudders, and car steering. A micro servo is one of the smallest versions of hobby servos. Hobby servos also come in continuous rotation versions which can be used to drive wheels of rolling robots, but the one in your kit is the standard variety that turns to and holds a position.
In this activity, you will:
The servo comes with several different shapes of “horns”. When the servo is built into a radio controlled model boat/airplane/car, screws and linkages connect a horn to the rudder, flap, or steering. Screws go through the mounting flanges to attach the servo’s case to the body or frame of the model plane/boat/car.
The servo is connected to supply voltage and a control signal through the plug and cable. The control signal, which the micro:bit will send, tells the servo what angle to point the horn. Last but not least, since the horn needs to stay connected to the servo’s output shaft, a screw holds the two together.
(1) Resistor - 220 Ω (red-red-brown-gold)
(1) Jumper wire - Red
(1) Jumper wire - Black
(1) Hobby micro servo
(1) Single horn
(1) Battery holder
We will not be using a screw to attach the horn to the output shaft in these activities. (If you end up using the servo to move a light load, use the screw that came with it to cinch the horn to the output shaft at that point. )
The servo has built-in motion limiters that allow it about a 200° range of motion. Follow these steps to mount the servo horn so that 200° of motion allows the horn to point from 190° to -10° (with 90° being the halfway point).
The first step to connecting the servo is to build a servo port. The port needs to connect to power, ground, and a micro:bit I/O pin so the servo can receive a control signal.
Next, you will connect the servo to the port. The servo’s three wires need to be connected as follows:
If you got the assembly instructions right, the servo_test_angles should make the servo horn point at 0°, 45°, 90°, and 180°. Don’t expect it to be precise, just reasonably close.
Example script: servo_test_angles
# servo_test_angles from microbit import * pin16.set_analog_period(20) while True: pin16.write_analog(26) # 25.6 -> 500 us -> 0 degrees sleep(4000) pin16.write_analog(51) # 51.2 -> 1000 us -> 45 degrees sleep(4000) pin16.write_analog(77) # 76.8 -> 1500 us -> 90 degrees sleep(4000) pin16.write_analog(128) # 128.0 -> 2500 us -> 180 degrees sleep(4000)
THE ANGLES WILL NOT BE PRECISE. If the horn points somewhere near each angle shown, that’ll be fine for now.
Let’s say you had to reduce the statement that positions the servo at 180° from pin16.write_analog(128) to pin16.write_analog(126). You could also try subtracting 2 from the other pin16.write_analog statements. For example, instead of 77, 51, and 26, you could try 75, 49, and 24.
Electrical and electronic parts typically have “manufacturing variations”. These are differences in the way an electrical or electronic part behaves. For example, the resistors in your kit all have gold bands, which means that they will be within 5% of the color coded value. Other resistors might have silver bands with 10%, or no band for 20%.
In the case of the hobby micro servo, one of them might measure a control signal from the micro:bit a little differently than another servo might. Even though the micro:bit, which has fairly precise timing, is sending the right signal, the servo might think the signal is “out of range”. When this happens, it holds the position of the most recent signal that was in-range.
If you are focusing on code to control the servo, pin16.set_analog_period(20) sets up the servo signal, and then pin16.write_analog(value) sets the servo’s position. For the servo, value has to be in the 26 to 128 range, and the resulting angle is in the 0° to 180° range.
Let’s take a closer look. The pin16.set_analog_period(20) statement tells the micro:bit to get ready to repeatedly set P16 high some fraction of 20 ms, and low for the rest of the time.
pin16.set_analog_period(20)
After that, a statement like pin16.write_analog(value) sets the pin high for value/1024ths of that 20 ms, and low the rest of the time. The signal that tells the servo what position to hold is like having a light that blinks at 50 Hz. Fifty LED blinks per second would be too fast for the human eye to discern, but a signal that would make a light blink that fast is what the servo needs for position information. Also, the on-times that actually tell the servo what direction to point are in the 0.5 ms to 2.5 ms range. It’s already impossible for the human eye to discern 50 blinks per second, but to tell the difference between on for 0.5 ms and on for 2.5 ms… Well, the LED would appear to be dim with 2.5 ms high times, and even dimmer with 0.5 ms high times.
The servo control signal the micro:bit script sends via P16 might not be easy for us to decipher, but it contains the position information the servo needs. Here are examples of the on/off signals plotted over time, and how they affect the servo’s position. As you will soon see, the results are not exactly the 0.5, 1.0, 1.5 and 2.5 ms values shown in the animation (See the mp4 here [19]).
Servo signal specifications assume the period of the signal is 20 ms, and then they equate an on-time to a servo position. For this micro-servo, an on time of 0.5 ms results in 0°, an on time of 1.5 ms results in 90°, and an on time of 2.5 ms results in an angle of 180°.
Remember from On-Off Signals [20] that a repeating on/off signal has a period T = tHIGH + tLOW. It also has a percent duty cycle of %DC = tHIGH / (tHIGH + tLOW).
The pin16.set_analog_period(20) call sets the period to 20 ms. That's T = tHIGH + tLOW = 20 ms.
The pin16.write_analog(value) sets the duty cycle as 1024ths of 20 ms. So, the signal high or on time works out to this:
tHIGH = value x 20 ms / 1024.
Example: What’s the actual high time from pin16.write_analog(26)?
Solution:
tHIGH = value x 20 ms / 1024
= 26 x 20 ms / 1024
≈ 0.508 ms
( close to 0.5 ms)
Example: What’s the actual high time from pin16.write_analog(51)?
Solution:
tHIGH = value x 20 ms / 1024
= 51 x 20 ms / 1024
≈ 0.996 ms
( close to 1.0 ms)
Keep in mind this high time is not just one pulse (blink). It is repeated 50 times per second. These repetitions continue while the servo is moving to its new position, and also to make the servo hold its position against opposing forces after it reaches its new position.
Tuning the angles takes three steps. First, make sure the horn is aligned as closely as possible to 0° when the script positions it there. Second, adjust the script to improve 0° alignment. Third, adjust the script to improve the accuracy of the other positions.
Two equations in two unknowns can be used to derive a formula for turning the servo to an angle of your choice. The final result needs to be y = mx + b, where x is the desired angle in degrees, and y is the argument for write_analog.
After solving for unknowns of m and b, we’ll have an equation that can be used in hand calculations, or in a script. You can also rearrange the terms in y = mx + b to x = (y - b) / m to solve for the angle if only the write_analog argument is known.
Since we want to be able to solve for the write_analog argument, let’s set y1 to 25.6 and y2 to 128.0. Then, x1 should be 0° and x2 should be 180°. The animation below shows how to solve for m and b. (See the .mp4 here.) [21]
With these formulas, you can calculate:
y = 0.5689x + 25.6 duty cycle if angle is known
x = (y - 25.6) / 0.5689 angle if duty cycle is known
Example: The angle we want is 30°, calculate the duty cycle..
y = m x + b
= 0.5689 x 30 + 25.6
= 17.067 + 25.6
= 42.667
≈ 43
Example: The duty cycle is 43, what’s the resulting angle?
x = (y - 25.6) / 0.5689
= (43-25.6) / 0.5689
= 17.4 / 0.5689
≈ 30.59
≈ 31
Keep in mind that 26...128 is 103 possible integer duty cycle values (including 26). There would have to be more than 180 or more possible integers to get closer to all possible servo degree angles. Other microcontrollers do have the ability to split this duty cycle range into many hundreds or even thousands of integer options.
You can control the servo’s position with the Serial terminal, entering integers in the 23...128 range to set the servo horn’s angle.
Example script: terminal_controlled_servo
# terminal_controlled_servo from microbit import * pin16.set_analog_period(20) print("Enter values in 26 to 128 range...") while True: text = input("Enter value >") y = int(text) if y < 129 and y > 25: pin16.write_analog(y) x = (y - 25.6) / 0.5699 print("servo degrees:", x)
Your challenge is to modify the script so that you can enter degree values in the 0 to 180 range, and it will correctly calculate and set the position.
Hints:
tHIGH = value x 20 ms / 1024 = 77 x 20 ms / 1024 ≈ 1.504 ms ( close to 1.5 ms)
1. Solution:
# servo_bounce_between_1 from microbit import * pin16.set_analog_period(20) while True: pin16.write_analog(26) # 0 degrees sleep(4000) pin16.write_analog(34) # 15 degrees sleep(4000)
2. Solution:
from microbit import * pin16.set_analog_period(20) print("Enter values in 51 to 102 range...") while True: text = input("Enter value >") y = int(text) if y < 103 and y > 50: pin16.write_analog(y) x = (y - 25.6) / 0.5699 print("servo degrees:", x)
3. Solution:
# terminal_controlled_servo_error from microbit import * pin16.set_analog_period(20) print("Enter values in 26 to 128 range...") while True: text = input("Enter value >") y = int(text) if y < 129 and y > 25: pin16.write_analog(y) x = (y - 25.6) / 0.5699 print("servo degrees:", x) else: print("Error, out of range")
Here are some examples of pushbutton servo control:
In this activity, you will use MicroPython scripts to:
Along the way, you will learn about angular position vs. angular velocity, and ways they are measured and calculated.
In addition to the servo you have just built and tested, this activity will use the pushbutton circuits connected to P9 and P6 that were built earlier.
# servo_buttons_control from microbit import * display.off() pin9.set_pull(pin9.NO_PULL) pin6.set_pull(pin6.NO_PULL) pin16.set_analog_period(20) value = 77 while True: state9 = pin9.read_digital() state6 = pin6.read_digital() if state9 is 1: value = value + 1 if value > 128: value = 128 if state6 is 1: value = value - 1 if value < 26: value = 26 pin16.write_analog(value) sleep(20)
It's time to verify that button presses adjust your servo's position.
For pushbutton monitoring, the micro:bit display needs to be turned off. Also, since we have pull-down resistors connected to the pushbuttons, micro:bit I/O pin internal pull resistors should be set to NO_PULL.
display.off() pin9.set_pull(pin9.NO_PULL) pin6.set_pull(pin6.NO_PULL)
The servo control signal to P16 is set up with this statement. Later, pin16.write_analog calls will adjust the signal to make the servo turn its horn to various positions.
pin16.set_analog_period(20)
The value variable will be used to set the servo’s position. This script starts it at 77, for a 90° horn position (the halfway point in the servo’s position range).
value = 77
The first thing that happens in the main loop is that the pushbutton states are checked and stored in state9 and state6.
while True: state9 = pin9.read_digital() state6 = pin6.read_digital()
If the P9 pushbutton is pressed, state9 will be 1. When this happens, it will increase the value by 1, and check if it is above 128. If so, set it back to 128. The 128 value restricts the servo’s position to a maximum of 128.
if state9 is 1: value = value + 1 if value > 128: value = 128
If the P6 pushbutton is pressed, state6will be 1. When this happens, it will decrease the value by 1, and check if it is below 26. If so, set it back to 26. The 26 value restricts the servo’s position to a minimum of 26.
if state6 is 1: value = value - 1 if value < 26: value = 26
Each time through the loop, it updates the servo signal using the number stored in the value variable then waits 20 ms (1/50th of a second) before repeating the main loop.
pin16.write_analog(value) sleep(20)
The upper angle limit of 180° for the servo horn was determined by:
if value > 128: value = 128
Likewise, the lower 0° angle limit for the servo horn was determined by:
if value < 26: value = 26
These values can be changed to limit the servo to a smaller range of angles. For example:
if value > 102: value = 102
…would set the upper angle limit to 135°. The lower angle limit could also be limited to 45° with this:
if value < 51: value = 51
Let’s try it:
Up to now, your scripts have controlled the servo horn’s angle. That’s the direction the horn points, measured from 0° to 180° of its range. Your scripts can also control the angular velocity. That’s how fast the horn turns when it’s moving from one position to another.
The scripts currently have the angular velocity slowed down with statements like:
value = value + 1
While pressing and holding the P9 button, the value variable increases by 1 every 50th of a second. In response, the servo horn increases its angle by about 1.75° per 50th of a second. If value = value + 1 is changed to value = value + 3, the modified script will make the horn turn 3 times as fast while the P9 button is pressed and held. This is an example of changing the horn’s angular velocity.
Think back to the first servo control script. It took the servo some time to turn to each new position. In that example, the servo turned to each new position at top speed. If your script advances the value variable faster than the servo can turn, it will catch up eventually. Just keep in mind that increasing the number in the value = value + number statement can only go so far to speed up the servo since the servo has a mechanical speed limit as well.
Angles can be measured in degrees. The symbol for angle is the Greek letter theta Θ . Angles can be expressed in degrees like this: Θ = 30°.
Angular velocity is a measure of how fast (and in which direction) something turns. Angular velocity can be measured in degrees per second. Counterclockwise rotation is considered positive, and clockwise negative. In other words, if the angle is increasing, angular velocity is positive. If angle decreases over time, angular velocity is negative. The symbol for angular velocity is the Greek letter omega, ω, and one way angular velocity is expressed is in degrees per second with this notation: °/s.
When angular velocity is constant it can be measured as the change in angle over time. Change in angle can be measured as the final angle Θf minus the initial angle Θi divided by the time t it took for the object to rotate that far.
ω = (Θf - Θi) / t
Example: If it takes 2.1 seconds for the horn to rotate from 0° to 180°, what is its angular velocity?
ω = (Θf - Θi) / t
= ( 180° - 0°) / 2.1 s
≈ 85.7° / s
Example: If it takes 1.05 seconds for the horn to rotate from 135° to 45°, what is its angular velocity?
ω = (Θf - Θi) / t
= ( 45° - 135°) / 1.05 s
≈ -85.7° / s
Let’s try increasing the counterclockwise angular velocity to 3-times the original. After this change, the servo horn should take about ⅓ of the time it originally took to turn from 0° to 180°.
1. Solution: This script is written for the range of 0° to 45°. Any 45° span is acceptable.
from microbit import * display.off() pin9.set_pull(pin9.NO_PULL) pin6.set_pull(pin6.NO_PULL) pin16.set_analog_period(20) value = 77 while True: state9 = pin9.read_digital() state6 = pin6.read_digital() if state9 is 1: value = value + 1 if value > 51: value = 51 if state6 is 1: value = value - 1 if value < 26: value = 26 pin16.write_analog(value) sleep(20)
2. Solution:
from microbit import * display.off() pin9.set_pull(pin9.NO_PULL) pin6.set_pull(pin6.NO_PULL) pin16.set_analog_period(20) value = 77 while True: state9 = pin9.read_digital() state6 = pin6.read_digital() if state9 is 1: value = value + 1 if value > 128: value = 128 if state6 is 1: value = value - 2 if value < 26: value = 26 pin16.write_analog(value) sleep(20)
In this animation, both the micro:bit LED matrix display and the servo horn point upward as the rest of the setup is rotated. This is an example of “tilt controlling” the servo with sensor measurements from the micro:bit module’s built-in accelerometer. (View in higher resolution .mp4 here. [26])
In this activity, you will:
These three activities (total of 14 web pages) provide a guided micro:bit accelerometer tour and also have script elements that will be reused in this activity. Although they are from the cyber:bot series, a cyber:bot is not required; you will only need a micro:bit and USB cable for these three warm up activities:
Same as Connect and Test the Servo [30].
Also the same as Connect and Test the Servo [30].
Important: Make sure to orient the servo by the micro:bit as shown in this diagram. With the horn in the 90° position, it should be pointing to the left. You will be holding the servo against the edge of the I/O adapter as you rotate it.
# accelerometer_servo_points_up from microbit import * import math while True: x = accelerometer.get_x() y = accelerometer.get_y() angle = round( math.degrees( math.atan2(y, x) ) ) needle = ( angle - 90 + 15 ) // 30 display.show(Image.ALL_CLOCKS[needle]) if angle >= 0: angle = 180 - angle else: continue value = 0.5689 * angle + 25.6 pin16.write_analog(value)
It's time to verify that your micro:bit LED matrix and servo behavior resemble what’s shown in the animation.
This script uses math.degrees and math.atan2 from the math module. So in addition to from microbit import *, this script also imports the math module.
# accelerometer_servo_points_up from microbit import * import math
Using import math instead of from math import * forces any statement that uses a math module function to prepend it with the word math. Examples: math.degrees(...) and math.atan2(x, y). If you instead use from math import *, you could simply use degrees(...) or atan2(x, y). It helps make it clear that degrees and atan2 are from the math module. To save memory, you could also use from math import degrees, atan2. Instead of all the functions in the module, only degrees and atan2 would be imported, and you would call them without prepending them with math. In other words, you would be back to just calling degrees(...) and atan2(x, y).
The while True loop starts each repetition by reading the micro:bit accelerometer’s x and y-axis measurements. These measurements were introduced in Measure Accelerometer Tilt [27].
while True: x = accelerometer.get_x() y = accelerometer.get_y()
Next, the accelerometer measurements are converted to a rotation angle, which was introduced in Measure Rotation Angles [28].
angle = round( math.degrees( math.atan2(y, x) ) )
These two statements use the measured rotation angle to display the upward pointing needle. It’s the pointing-up version of the statements for pointing down that were introduced in Display Tilt Direction [29].
needle = ( angle + 90 + 15 ) // 30 display.show(Image.ALL_CLOCKS[needle])
The tests you’ve done so far involve accelerometer measurements in the 0° to 180° range. However, the range of the positions the servo needs to hold in response are in the 180° to 0° range. So, when the accelerometer reads 180°, the servo horn needs to be at 0°. When the accelerometer reads 0°, the servo horn needs to be at 180°. ...and everything in between. For example, if the accelerometer measures 150°, the servo horn needs to be pointed at 180° - 150° = 30°. Another example, if the accelerometer measures 45°, the servo horn needs to be pointed at 180° - 45° = 135°.
This if… statement executes when the accelerometer measurements are in the 0° to 180° range. In this range, angle = 180 - angle subtracts the measured angle from 180 to produce the angle value needed to correctly position the servo. Another way to say it is that the angle = 180 - angle statement maps the 180° to 0° input range to a 0° to 180° output range. if angle >= 0: angle = 180 - angle The other half of a full rotation ranges from -1° to -179°. We don’t want a servo with a 180° rotation range to try to turn any further than 180°. So the script makes sure the servo does not respond to measurements in this range with a continue statement. The continuestatement causes the script to repeat whatever loop it’s in, skipping any statements below it. In this script, it repeats the while True loop without reaching the value = 0.5689 * angle +25.6 and pin16.write_analog(value) statements. else: continue
The y = 0.5689x + 25.6 formula was introduced in the Connect and Test the Servo Activity’s Did You Know [31] section to map a value in a 0° to 180° range to a value in the 26 to 128 range. Again, this makes it possible to convert the degree measurements we humans normally think about to numbers for the value variable in pin16.write_analog(value). The result is control pulses that make the servo turn to degree positions. Take a close look at value = 0.5689 * angle + 25.6. It’s the same formula with y renamed to value and x renamed to angle.
value = 0.5689 * angle + 25.6
At this point, the value variable is ready for pin16.write_analog, with numbers in the 25.6 to 128.0 range.
pin16.write_analog(value)
Let’s take a closer look at how the angle = 180 - angle statement affects the value stored in the angle variable.
If you don’t rotate the servo with the micro:bit and board, its horn will turn the opposite direction the micro:bit turns. The statement that makes the servo turn the opposite direction from the micro:bit is angle = 180 - angle in this if...else… statement:
if angle >= 0: angle = 180 - angle else: continue
To make it turn the same direction, the angle = 180 - angle statement needs to be removed. That might seem simple enough, but it’s in an if… statement. So, you can’t just comment it or remove it because MicroPython requires an if… statement to contain at least one statement (below and indented). Otherwise, the script would stop executing statements and display an exception message when it reached the if… statement.
To make sure theif… statement still contains a statement MicroPython can execute, you can simply add a pass statement. A pass statement essentially tells MicroPython: “Don’t do anything here, just move on to the next statement.” Even though it’s a statement that doesn’t actually do anything, it’s still a MicroPython statement.
if angle >= 0: # angle = 180 - angle pass else: continue
Here’s another way to rewrite theif… statement so it still skips servo positioning in response to angles outside 0...180. Angles outside the 0...180 range will be negative: -1…-179. So, another approach would be to rewrite the if...else... statement so it’s just an if statement that only uses continue to skip the servo positioning by jumping back to while True when angle is less than zero. When angle is greater than zero, this if… statement allows the script to keep going through the last statement in the loop, which updates the servo horn’s position with pin16.write_analog(value).
if angle <= 0: continue
The Did You Know section above explained how to make the servo horn mirror the board’s rotation if the servo is sitting still and the board is rotated.
Holding the setup like a steering wheel, adjust the script so that the servo horn and display will point up for ‘right hand turns’ only.
1. Solution:
from microbit import * import math while True: x = accelerometer.get_x() y = accelerometer.get_y() angle = round( math.degrees( math.atan2(y, x) ) ) if angle >= 0 and angle <= 90: needle = ( angle - 90 + 15 ) // 30 display.show(Image.ALL_CLOCKS[needle]) angle = 180 - angle else: continue value = 0.5689 * angle + 25.6 pin16.write_analog(value)
2. Solution:
from microbit import * import math while True: x = accelerometer.get_x() y = accelerometer.get_y() angle = round( math.degrees( math.atan2(y, x) ) ) if angle >= 45 and angle <= 135: needle = ( angle - 90 + 15 ) // 30 display.show(Image.ALL_CLOCKS[needle]) angle = 180 - angle else: continue value = 0.5689 * angle + 25.6 pin16.write_analog(value)
In earlier animations, you have seen how the write_analog(value) function calls make the micro:bit send repeating on/off (or high/low) signals to the servo. The servo positions its horn at an angle in response to how long the ‘on’ part of the signal stays on. These signals can be measured with the CYBERscope’s oscilloscope and compared to how this document said it’s supposed to work.
This kind of activity is the sort of thing students find themselves doing to make sure their motor will run properly before it’s connected. Engineers also do this to verify that their prototypes are running properly, and technicians often take similar measurements when a device with motors isn’t running properly and needs to be diagnosed and fixed.
In this activity, you will:
Same as previous activity + red alligator clip and jumper wire.
We’ll be using the P2 red alligator clip lead to monitor signals the micro:bit sends to the servo.
servo_plot.hex [32]
Your display should now resemble this one:
The time that the signal stays high (at 3.3 V in this case) is called the pulse width. Here, we are not going to measure the pulse width, just observe how it contains information that tells the servo what angle to hold.
One thing we can measure at 5 ms/div is the signal’s period (T). This was first introduced in the Connect and Blink a Light activity’s On-Off Signals pag [20]e. It introduced the period of an on/off signal like a blinking light or this servo signal as tHIGH + tLOW, but take a look at what happens between the rising edges in the CYBERscope. See how the time between rising edges contains both the high (pulse width) and low times in the signal?
The tests you just completed helped verify two things:
The 5 ms/div setting is fine for measuring the servo signal’s period, but a smaller Time/Div setting would be better for measuring the actual pulse width times. Look forward to that in the Try This section.
Here is an excerpt from the servo_plot script:
pin16.write_analog(51) # 51.2 -> 1000 us -> 45 degrees plot_servo(ch2="servo", delay=40) sleep(4000)
The pin16.write_analog(51) and sleep(4000) statements are from the original servo_test_angles script you used to test the servo in Connect and Test the Servo [35]. The plot_servo(ch2=”servo”, delay=40) function call tells the multimeter module to measure a servo signal with the Ch2 probe connected to P2 but wait 40 ms before starting the measurement. This delay in taking the measurement ensures that the micro:bit has successfully switched from whatever servo signal it was sending previously to the new one.
Unlike a real oscilloscope, which would record thousands of voltage measurements and plot them, the multimetermodule measures the servo signal’s high and low times, in microseconds. It uses those measurements to generate a list of voltages and times that it sends to the CYBERscope to emulate what a real oscilloscope does.
A smaller Time/Div setting will make it easier to measure the servo pulses.
One approach to measuring pulses with an oscilloscope is to subtract the time the pulse’s rising edge occurs from the time the falling edge occurs. In the case of the pulse for 90°, subtract 0.5 ms from 2 ms for a result of 1.5 ms.
Angle Pulse width
0°
45°
90° 2 ms - 0.5 ms = 1.5 ms (Example)
180°
Desktop oscilloscopes have built-in tools to measure period, pulse width, frequency, and much more. You will often see these kinds of measurements along one of the outside edges of the plot.
This particular oscilloscope can be configured to display the measurements along the bottom of the plot.
The tests so far have not included 135°, let’s add that to the script and verify the result.
Predict the pulse width for 15°. Calculate the duty cycle for positioning the servo horn at 15° then write the script to verify your prediction that moves back and forth 0° and 15°.
Solution:
y = 0.5689x + 25.6
y = 0.5689(15) + 25.6
y = 34.1335 ≈ 34
# servo_plot from microbit import * from multimeter import * display.off() pin16.set_analog_period(20) while True: pin16.write_analog(26) plot_servo(ch2="servo", delay=40) sleep(4000) pin16.write_analog(34) plot_servo(ch2="servo", delay=40) sleep(4000)
The pulse width should be approx. 0.2 ms
Links
[1] https://trinidadstate.edu/robotics/
[2] https://spacegrant.colorado.edu/statewideprograms/robotics-challenge-home
[3] https://youtu.be/cCPA6E-kb0o?t=12
[4] https://www.parallax.com/product/whats-a-microcontroller-with-python-and-microbit/
[5] https://learn.parallax.com/tutorials/robot/cyberbot/software-setup-microbit
[6] https://learn.parallax.com/tutorials/robot/cyberbot/writing-microbit-programs
[7] https://learn.parallax.com/tutorials/robot/cyberbot/computer-microbit-talk
[8] https://learn.parallax.com/tutorials/language/python/breadboard-setup-and-testing-microbit
[9] https://learn.parallax.com/tutorials/language/python/electrical-measurements
[10] https://learn.parallax.com/tutorials/language/python/led-lights
[11] https://learn.parallax.com/tutorials/language/python/sense-pushbutton-presses
[12] https://docs.google.com/document/d/1asGagFcCcZGRkj3Us4pviBvAStlATDwboIJCYVCWeHU/edit#heading=h.bvmo5m4070bw
[13] https://learn.parallax.com/tutorials/language/python/led-lights/blink-sequencing/script-and-tests
[14] https://learn.parallax.com/tutorials/language/python/sense-pushbutton-presses/second-pushbutton/script-and-tests
[15] https://docs.google.com/document/u/0/d/1oeuVzMphlOWSzawkfk8A2crN_xNmfI4cBJn7b6tZ84U/edit
[16] https://learn.parallax.com/sites/default/files/content/Python/breadboard/hex/measure_volts.hex
[17] https://python.microbit.org/v/2
[18] https://cyberscope.parallax.com/
[19] https://learn.parallax.com/sites/default/files/content/Python/servo/servo-connect-how-servo-sig-xfer-funct.mp4
[20] https://learn.parallax.com/tutorials/language/python/led-lights/connect-and-blink-light/signals
[21] https://learn.parallax.com/sites/default/files/content/Python/servo/derive-angle-to-write-analog-argument.mp4
[22] https://learn.parallax.com/tutorials/robot/cyberbot/computer-microbit-talk/scripts-print-terminal
[23] https://learn.parallax.com/tutorials/robot/cyberbot/computer-microbit-talk/input-messages
[24] https://learn.parallax.com/tutorials/robot/cyberbot/computer-microbit-talk/inputprint-applications
[25] https://learn.parallax.com/tutorials/language/python/servo-position-control-python-and-microbit/setup-previous-tutorials
[26] https://learn.parallax.com/sites/default/files/content/Python/servo/servo-tilt-ctrl-concept.mp4
[27] https://learn.parallax.com/tutorials/robot/cyberbot/cybersecurity-radio-tilt-control/measure-accelerometer-tilt
[28] https://learn.parallax.com/tutorials/robot/cyberbot/cybersecurity-radio-tilt-control/measure-rotation-angles
[29] https://learn.parallax.com/tutorials/robot/cyberbot/cybersecurity-radio-tilt-control/display-tilt-direction
[30] https://learn.parallax.com/tutorials/language/python/servo-position-control-python-and-microbit/connect-and-test-servo
[31] https://learn.parallax.com/tutorials/language/python/servo-position-control-python-and-microbit/connect-and-test-servo-0
[32] https://learn.parallax.com/sites/default/files/content/Python/servo/servo_plot.hex
[33] https://cyberscope.parallax.com
[34] https://learn.parallax.com/tutorials/language/python/electrical-measurements/measurement-units/convert-prefixes-values
[35] https://docs.google.com/document/d/15auxYUR0YnEvQtKc6jFfm2a4BNcgdhoQRcPrR9eYj_Y/edit#heading=h.ov44fqdkdi74
[36] https://docs.google.com/document/d/15auxYUR0YnEvQtKc6jFfm2a4BNcgdhoQRcPrR9eYj_Y/edit#heading=h.6oak8nsfcryy