├── rc_car ├── rc_car.png ├── rc_controller.py ├── rc_car.py ├── README.md └── rc_movemini.py ├── shake_it ├── README.md └── shake_it.py ├── simon_game ├── readme.md └── simon_game.py ├── LICENSE └── README.md /rc_car/rc_car.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alankrantas/microbit-micropython-cookbook/HEAD/rc_car/rc_car.png -------------------------------------------------------------------------------- /shake_it/README.md: -------------------------------------------------------------------------------- 1 | ## Shake-it Game 2 | 3 | This is a game that I've wrote a MakeCode version for coding summer/winter camps. It was actually quite popular among kids. 4 | 5 | ``` 6 | How to play: 7 | 8 | 1) You have to shake the micro:bit long enough to "pass" a game. There are five games total by default. 9 | Your goal is to shake so the level value on the board can be added up until it reach the top. 10 | 2) When you shake the board, the LED screen and the buzzer sound will both change to indicate the level. 11 | 3) When you stop shaking the board, the level will gradually drop back to zero. 12 | 4) You can use the Python script to modify the level add/drop/max values to adjust the difficulty. 13 | ``` 14 | -------------------------------------------------------------------------------- /simon_game/readme.md: -------------------------------------------------------------------------------- 1 | ## Simon Game for BBC micro:bit V2 2 | 3 | The game is inspired by [Simon electronic games](https://en.wikipedia.org/wiki/Simon_(game)) and is written in MicroPython (not compatible to micro:bit V1), using the accelerometer to sense directions and has buzzer sound effects. 4 | 5 | ``` 6 | How to play: 7 | 8 | 1) Upload the script as main.py or in the form of .hex file. 9 | 2) After the welcome effects, press A + B to start. 10 | 3) Memorize the Simon sequence in each turn. 11 | 4) Tilt the board toward indicated directions to match the sequence. 12 | 5) After each tilting gesture, you have to return it to horizontal position. 13 | 6) Game will end when you failed. 14 | 7) There is no limit how long the Simon sequence can go. 15 | ``` 16 | 17 | The code is shared under General Public License 3.0. 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Alan Wang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /rc_car/rc_controller.py: -------------------------------------------------------------------------------- 1 | # For the micro:bit RC car controller 2 | 3 | RADIO_CHANNEL = 42 # radio channel: 0~255 4 | 5 | from microbit import display, Image, accelerometer, sleep 6 | from math import pi, atan2, sqrt 7 | import radio 8 | 9 | radio.config(group=RADIO_CHANNEL) 10 | radio.on() 11 | 12 | def rotationPitch(): 13 | return atan2( 14 | accelerometer.get_y(), 15 | sqrt(accelerometer.get_x() ** 2 + accelerometer.get_z() ** 2) 16 | ) * (180 / pi) 17 | 18 | def rotationRoll(): 19 | return atan2( 20 | accelerometer.get_x(), 21 | sqrt(accelerometer.get_y() ** 2 + accelerometer.get_z() ** 2) 22 | ) * (180 / pi) 23 | 24 | while True: 25 | pitch = rotationPitch() 26 | roll = rotationRoll() 27 | 28 | if pitch < -30: 29 | display.show(Image.ARROW_N) 30 | radio.send('forward') 31 | elif pitch > 30: 32 | display.show(Image.ARROW_S) 33 | radio.send('backward') 34 | elif roll > 30: 35 | display.show(Image.ARROW_E) 36 | radio.send('left') 37 | elif roll < -30: 38 | display.show(Image.ARROW_W) 39 | radio.send('right') 40 | else: 41 | display.show(Image.DIAMOND) 42 | radio.send('stop') 43 | 44 | sleep(100) 45 | -------------------------------------------------------------------------------- /rc_car/rc_car.py: -------------------------------------------------------------------------------- 1 | # For the micro:bit RC car 2 | 3 | RADIO_CHANNEL = 42 # radio channel: 0~255 4 | SPEED = 1023 # wheel speed: 0~1023 5 | 6 | from microbit import display, Image, sleep, pin1, pin2, pin8, pin12 7 | import radio 8 | 9 | radio.config(group=RADIO_CHANNEL) 10 | radio.on() 11 | 12 | motor_right_A = pin1 13 | motor_right_B = pin2 14 | motor_left_A = pin8 15 | motor_left_B = pin12 16 | 17 | motor_right_A.write_digital(0) 18 | motor_right_B.write_digital(0) 19 | motor_left_A.write_digital(0) 20 | motor_left_B.write_digital(0) 21 | 22 | display.show(Image.HAPPY) 23 | 24 | while True: 25 | direction = radio.receive() 26 | if direction == None: 27 | continue 28 | 29 | if direction == 'forward': 30 | display.show(Image.ARROW_N) 31 | motor_right_A.write_analog(SPEED) 32 | motor_right_B.write_digital(0) 33 | motor_left_A.write_analog(SPEED) 34 | motor_left_B.write_digital(0) 35 | elif direction == 'backward': 36 | display.show(Image.ARROW_S) 37 | motor_right_A.write_digital(0) 38 | motor_right_B.write_analog(SPEED) 39 | motor_left_A.write_digital(0) 40 | motor_left_B.write_analog(SPEED) 41 | elif direction == 'left': 42 | display.show(Image.ARROW_W) 43 | motor_right_A.write_digital(0) 44 | motor_right_B.write_analog(SPEED) 45 | motor_left_A.write_analog(SPEED) 46 | motor_left_B.write_digital(0) 47 | elif direction == 'right': 48 | display.show(Image.ARROW_E) 49 | motor_right_A.write_analog(SPEED) 50 | motor_right_B.write_digital(0) 51 | motor_left_A.write_digital(0) 52 | motor_left_B.write_analog(SPEED) 53 | else: 54 | display.show(Image.HAPPY) 55 | motor_right_A.write_digital(0) 56 | motor_right_B.write_digital(0) 57 | motor_left_A.write_digital(0) 58 | motor_left_B.write_digital(0) 59 | 60 | sleep(50) 61 | -------------------------------------------------------------------------------- /rc_car/README.md: -------------------------------------------------------------------------------- 1 | # Simple micro:bit V2 remote control car (with a Move:mini version) 2 | 3 | Since the micro:bit V2 can output 300 mA instead of 90 mA from its 3V pin, you can actually build a motor car with a simple breakout, a L9110S H-bridge motor driver board and a generic, small 5V USB power bank. 4 | 5 | This RC car needs another micro:bit (it can be either V1 or V2) as the remote controller. 6 | 7 | ### Required hardware 8 | 9 | * 2 BBC micro:bit V2 (or one V2 and one V1) 10 | * a L9110s driver board 11 | * a cheap 2WD robot car chassis 12 | * a small 5V USB powerbank 13 | 14 | ### Wiring 15 | 16 | | L9110S | micro:bit V2 | 17 | | --- | --- | 18 | | B-1A | P1 | 19 | | B-1B | P2 | 20 | | GND | GND | 21 | | VCC | 3V | 22 | | A-1A | P8 | 23 | | A-1B | P12 | 24 | 25 | And connect your power bank to the micro:bit V2's USB port. 26 | 27 | Be noted that many power banks shut themselves off when there are not enough current draw, which would happen when both motors are not running for a few seconds. You can try to turn on the "always on mode" or buy one of those cheap ones that don't shut off. 28 | 29 | If you have a micro:bit extension board that offer 5V output (either from USB connection or on-board batteries), a micro:bit V1 can be used on the car as well. Simply connect the VCC pin to the 5V pin. 30 | 31 | ![1](https://github.com/alankrantas/microbit-micropython-cookbook/blob/master/rc_car/rc_car.png) 32 | 33 | ### Upload code 34 | 35 | Copy the content of following MicroPython code into the [official Python editor](https://python.microbit.org/v/2), connect the board and flash it: 36 | 37 | * [Controller](https://github.com/alankrantas/microbit-micropython-cookbook/blob/master/rc_car/rc_controller.py) (to the V2 on the car) 38 | * [Car](https://github.com/alankrantas/microbit-micropython-cookbook/blob/master/rc_car/rc_car.py) (to a V2 or V1) 39 | * I also wrote a version for Kitronik's [Move:mini](https://github.com/alankrantas/microbit-micropython-cookbook/blob/master/rc_car/rc_movemini.py) with some NeoPixel light effects 40 | 41 | In both scripts the ```RADIO_CHANNEL``` variable defines the radio channel shared between the controller and the car. Change it if you are going to operate near other people also using micro:bit's radio functions. 42 | 43 | ### Control 44 | 45 | Tilt the controller to different directions. Direction arraws would show on both the controller and the car. 46 | 47 | The controller can be connected to any USB or battery power. 48 | -------------------------------------------------------------------------------- /shake_it/shake_it.py: -------------------------------------------------------------------------------- 1 | from microbit import display, Image, Sound, set_volume, accelerometer, sleep 2 | from micropython import const 3 | import audio, music, speech 4 | 5 | max_score = const(5) # number of games you have to pass (shake micro:bit to add on the level value) 6 | add_level = const(10) # max level can be added to the level value 7 | drop_level = const(10) # value that will auto subtracted from the level value 8 | max_level = const(250) # max level value to pass 9 | volume = const(50) # buzzer volume during gameplay (0~255) 10 | 11 | def translate(value, leftMin, leftMax, rightMin, rightMax): 12 | leftSpan = leftMax - leftMin 13 | rightSpan = rightMax - rightMin 14 | valueScaled = float(value - leftMin) / float(leftSpan) 15 | return rightMin + (valueScaled * rightSpan) 16 | 17 | def plotBarGraph(value, max_value, b=9): 18 | order = (23, 21, 20, 22, 24, 19 | 18, 16, 15, 17, 19, 20 | 13, 11, 10, 12, 14, 21 | 8, 6, 5, 7, 9, 22 | 3, 1, 0, 2, 4,) 23 | counter = 0 24 | display.clear() 25 | for y in range(5): 26 | for x in range(5): 27 | if value / max_value > order[counter] / 25: 28 | display.set_pixel(x, y, b) 29 | counter += 1 30 | 31 | level = 0 32 | score = 0 33 | buzzer = True 34 | set_volume(255) 35 | 36 | display.clear() 37 | audio.play(Sound.SPRING) 38 | sleep(500) 39 | numbers = ('One', 'Two', 'Three') 40 | for i in range(3, 0, -1): 41 | display.show(i) 42 | speech.say(numbers[i-1], speed=500, pitch=50, throat=100, mouth=50) 43 | sleep(750) 44 | 45 | set_volume(volume) 46 | 47 | while score < max_score: 48 | shake = max(0, abs(accelerometer.get_x()) + abs(accelerometer.get_y()) + abs(accelerometer.get_z()) - 2048) 49 | level = min(max(0, level - drop_level + translate(shake, 0, 2048, 0, add_level)), max_level) 50 | pitch = round(translate(level, 0, max_level, 440, 880)) 51 | plotBarGraph(level, max_level) 52 | if level == max_level: 53 | level = 0 54 | score += 1 55 | music.stop() 56 | sleep(50) 57 | display.show(Image.YES) 58 | music.play(music.BA_DING) 59 | sleep(450) 60 | continue 61 | if buzzer: 62 | music.pitch(pitch) 63 | else: 64 | music.stop() 65 | buzzer = not buzzer 66 | sleep(50) 67 | 68 | set_volume(255) 69 | sleep(500) 70 | display.show(Image.HEART) 71 | music.play(music.RINGTONE) 72 | sleep(500) 73 | 74 | while True: 75 | display.show(Image.HAPPY) 76 | sleep(500) 77 | display.clear() 78 | sleep(500) 79 | -------------------------------------------------------------------------------- /rc_car/rc_movemini.py: -------------------------------------------------------------------------------- 1 | # For Kitronik's Move:mini 2 | 3 | RADIO_CHANNEL = 42 # radio channel: 0~255 4 | LIGHT_LEVEL = 128 5 | 6 | from microbit import display, Image, sleep, pin0, pin1, pin2 7 | from neopixel import NeoPixel 8 | import radio 9 | 10 | radio.config(group=RADIO_CHANNEL) 11 | radio.on() 12 | 13 | class MoveMini: 14 | def __init__(self): 15 | self.np = NeoPixel(pin0, 5) 16 | self.pos = 0 17 | self.motor_right = pin1 18 | self.motor_left = pin2 19 | self.stop() 20 | 21 | def forward(self): 22 | display.show(Image.ARROW_N) 23 | self.np[1] = self.np[3] = (0, LIGHT_LEVEL, LIGHT_LEVEL) 24 | self.np[2] = (LIGHT_LEVEL, LIGHT_LEVEL, LIGHT_LEVEL) 25 | self.np[0] = self.np[4] = (0, 0, 0) 26 | self.np.show() 27 | self.motor_right.set_analog_period(20) 28 | self.motor_left.set_analog_period(20) 29 | self.motor_right.write_analog(50) 30 | self.motor_left.write_analog(100) 31 | 32 | def backward(self): 33 | display.show(Image.ARROW_S) 34 | self.np[1] = self.np[2] = self.np[3] = (LIGHT_LEVEL, 0, 0) 35 | self.np[0] = self.np[4] = (0, 0, 0) 36 | self.np.show() 37 | self.motor_right.set_analog_period(20) 38 | self.motor_left.set_analog_period(20) 39 | self.motor_right.write_analog(100) 40 | self.motor_left.write_analog(50) 41 | 42 | def left(self): 43 | display.show(Image.ARROW_W) 44 | self.np[1] = self.np[2] = (0, LIGHT_LEVEL, 0) 45 | self.np[0] = (LIGHT_LEVEL, LIGHT_LEVEL, 0) 46 | self.np[3] = self.np[4] = (0, 0, 0) 47 | self.np.show() 48 | self.motor_right.set_analog_period(20) 49 | self.motor_left.set_analog_period(20) 50 | self.motor_right.write_analog(100) 51 | self.motor_left.write_analog(100) 52 | 53 | def right(self): 54 | display.show(Image.ARROW_E) 55 | self.np[2] = self.np[3] = (0, LIGHT_LEVEL, 0) 56 | self.np[4] = (LIGHT_LEVEL, LIGHT_LEVEL, 0) 57 | self.np[0] = self.np[1] = (0, 0, 0) 58 | self.np.show() 59 | self.motor_right.set_analog_period(20) 60 | self.motor_left.set_analog_period(20) 61 | self.motor_right.write_analog(50) 62 | self.motor_left.write_analog(50) 63 | 64 | def stop(self): 65 | display.show(Image.HAPPY) 66 | for i in range(5): 67 | self.np[i] = (0, 0, 0) 68 | self.np.show() 69 | self.motor_right.read_digital() 70 | self.motor_left.read_digital() 71 | 72 | car = MoveMini() 73 | 74 | while True: 75 | sleep(25) 76 | 77 | direction = radio.receive() 78 | if direction == None: 79 | continue 80 | 81 | if direction == 'forward': 82 | car.forward() 83 | elif direction == 'backward': 84 | car.backward() 85 | elif direction == 'left': 86 | car.left() 87 | elif direction == 'right': 88 | car.right() 89 | else: 90 | car.stop() 91 | 92 | -------------------------------------------------------------------------------- /simon_game/simon_game.py: -------------------------------------------------------------------------------- 1 | # Simon Game on BBC micro:bit V2 2 | # by Alan Wang 3 | 4 | """ 5 | How to play: 6 | 7 | 1) Upload the script as main.py or in the form of .hex file. 8 | 2) After the welcome effects, press A + B to start. 9 | 3) Memorize the Simon sequence in each turn. 10 | 4) Tilt the board toward indicated directions to match the sequence. 11 | 5) After each tilting gesture, you have to return it to horizontal position. 12 | 6) Game will end when you failed. 13 | 7) There is no limit how long the Simon sequence can go. 14 | """ 15 | 16 | # import modules 17 | 18 | from microbit import display, Image, Sound, sleep 19 | from microbit import button_a, button_b, accelerometer 20 | import random, music, audio, speech 21 | 22 | 23 | # prepare data 24 | 25 | """ 26 | Note: the indexes of the following tuple represent 27 | 0 = toward up/north 28 | 1 = toward left/west 29 | 2 = toward right/east 30 | 3 = toward down/south 31 | 32 | Since gesture "up" in micro:bit means "north side up", 33 | I inverted the direction here. 34 | But you can change the items of gestures, images and notes 35 | to customize what would be shown and play on micro:bit. 36 | """ 37 | 38 | gestures = ('down', 'left', 'right', 'up') 39 | images = (Image('69996:06960:00600:00000:00000:'), 40 | Image('60000:96000:99600:96000:60000:'), 41 | Image('00006:00069:00699:00069:00006:'), 42 | Image('00000:00000:00600:06960:69996:'), 43 | Image('30003:60006:90009:60006:30003:')) 44 | notes = (262, 330, 392, 523) 45 | simon = [] 46 | guess = [] 47 | 48 | 49 | # start up effects 50 | 51 | display.clear() 52 | audio.play(Sound.GIGGLE) 53 | sleep(1000) 54 | display.show(Image.FABULOUS) 55 | speech.say('Simon says', speed=150, pitch=50, throat=100, mouth=50) 56 | sleep(500) 57 | 58 | 59 | # wait for the user to press A + B 60 | 61 | display_flag = True 62 | while not (button_a.is_pressed() and button_b.is_pressed()): 63 | if display_flag: 64 | display.show(images[4]) 65 | else: 66 | display.clear() 67 | sleep(200) 68 | display_flag = not display_flag 69 | 70 | for i in range(4): 71 | music.pitch(notes[i]) 72 | display.show(images[i]) 73 | sleep(100) 74 | 75 | display.clear() 76 | sleep(400) 77 | music.stop() 78 | sleep(1000) 79 | 80 | 81 | # start game 82 | 83 | while True: 84 | guess.clear() 85 | simon.append(random.randrange(4)) # add a random number to sequence 86 | 87 | for n in simon: 88 | music.pitch(notes[n]) 89 | display.show(images[n]) 90 | sleep(500) 91 | music.stop() 92 | display.clear() 93 | sleep(250) 94 | 95 | sleep(500) 96 | 97 | # game loop 98 | # the loop will end when user matched the whole sequence 99 | while len(guess) < len(simon): 100 | gesture = None 101 | display.show('?') 102 | 103 | # record user's tilting gestures 104 | for i in range(4): 105 | if accelerometer.was_gesture(gestures[i]): 106 | gesture = i 107 | break 108 | if gesture == None: 109 | continue 110 | guess.append(gesture) 111 | display.show(images[gesture]) 112 | music.pitch(notes[gesture]) 113 | sleep(250) 114 | music.stop() 115 | display.clear() 116 | sleep(100) 117 | 118 | # if the gesture don't match, exit the game loop 119 | if guess != simon[:len(guess)]: 120 | break 121 | 122 | else: 123 | # sequence successfully matched, go to next round 124 | display.show(Image.YES) 125 | music.play(music.POWER_UP) 126 | sleep(500) 127 | display.clear() 128 | sleep(500) 129 | continue 130 | 131 | # failed to match sequence 132 | sleep(100) 133 | display.show(Image.NO) 134 | music.play(music.WAWAWAWAA) 135 | break 136 | 137 | 138 | # display final score 139 | 140 | sleep(1000) 141 | audio.play(Sound.HAPPY) 142 | 143 | while True: 144 | display.scroll('SCORE: {}'.format(len(simon) - 1)) 145 | sleep(1000) 146 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # micro:bit V2 MicroPython Cookbook 2 | 3 | ![1](https://user-images.githubusercontent.com/44191076/79871966-c0ae8b00-8417-11ea-8255-cbc681d12b8d.jpg) 4 | 5 | This is the collection of notes, tricks and experiments on BBC micro:bit V2 and MicroPython. Many examples work on micro:bit V1 but I no longer test on them. 6 | 7 | - [Official micro:bit Python editor](https://python.microbit.org/v/3) 8 | - [BBC micro:bit V2 MicroPython documentation](https://microbit-micropython.readthedocs.io/en/v2-docs/index.html) 9 | 10 | Also there are a few projects: 11 | 12 | * Simon Says game ([link](https://github.com/alankrantas/microbit-micropython-cookbook/tree/master/simon_game)) 13 | * Shake It game ([link](https://github.com/alankrantas/microbit-micropython-cookbook/tree/master/shake_it)) 14 | * Simple micro:bit RC Car ([link](https://github.com/alankrantas/microbit-micropython-cookbook/tree/master/rc_car)) 15 | 16 | --- 17 | 18 | ## About micro:bit's MicroPython 19 | 20 | micro:bit's MicroPython is developed by [Damien George](https://github.com/dpgeorge), after the initial effort to [bring Python onto micro:bit](https://ntoll.org/article/story-micropython-on-microbit/) failed. Damien George developed the original [MicroPython](https://micropython.org/) in 2013, which also has a derived version: Adafruit's [CircuitPython](https://circuitpython.org/). 21 | 22 | All MicroPython variants are based on standard Python or CPython `3.4`, while the other MicroPython versions incorporated a few features from newer Python. Unlike the ["fake" Python in the MakeCode editor](https://makecode.com/python), these are actual Python interpreters with full language syntax support, all basic built-ins along with a few special modules for the microcontrollers. On the other hand, most of the built-in modules are not available due to the hardware limitation. Not that we'll need them a lot for STEM education anyway. 23 | 24 | As Python is a dynamic interpreted language, it is slower than compiled languages like C++ and requires more memory, although on micro:bit V2s (with 128 KB RAM instead of 16 KB of V1s) this is no longer a big issue. With 512 KB flash onboard, you can actually write and store some text data as well! 25 | 26 | The firmware - the MicroPython interpreter - will be flashed onto the micro:bit when you upload the code for the first time. Actually, the firmware and the script will be [bundled together as a single .hex file](https://tech.microbit.org/software/hex-format/) to be uploaded into the [DAPLink interface](https://tech.microbit.org/software/daplink-interface/), which creates a fake "USB drive" on your computer. If the firmware is present, only the user script needs to be updated. 27 | 28 | ## Ask Help From REPL 29 | 30 | REPL (Read-Evaluate-Print-Loop) or "serial" in the official editor is a very useful tool, although it is in fact the command line interface of the MicroPython interpreter, for witch you can test an expression (like `1 + 1`) or a statement (like `print(1 + 1)`). And here's some basic things you can do with it. 31 | 32 | In the official Python editor, first connect your micro:bit, then open "serial". If there are code running onboard and the interpreter does not respond, press `Ctrl` + `C` to force the code to stop. (You can also press `Ctrl` + `D` to have the board restart and run the code again.) 33 | 34 | Get basic help information from REPL: 35 | 36 | ``` 37 | > help() 38 | ``` 39 | 40 | List all built-in MicroPython modules: 41 | 42 | ``` 43 | > help('modules') 44 | ``` 45 | 46 | To see what's inside a module or submodule/function/attribute (has to be imported first): 47 | 48 | ``` 49 | > import microbit 50 | > help(microbit) 51 | > help(microbit.pin0) 52 | > dir(microbit) 53 | > dir(microbit.pin0) 54 | ``` 55 | 56 | ## Easter Eggs 57 | 58 | Try to type these in the REPL: 59 | 60 | ``` 61 | > import this 62 | > import antigravity 63 | > import love 64 | ``` 65 | 66 |
67 | Reference 68 | 69 | - `import this` prints out "The Zen of MicroPython", which is a short version of [The Zen of Python](https://peps.python.org/pep-0020/) in CPython. 70 | - `import antigravity` prints out a text version of [this comic about Python 2](https://xkcd.com/353/); in CPython it will directly open the URL of that comic. 71 | 72 |
73 | 74 | ## Import * is a Bad Idea 75 | 76 | In a lot of examples you may see 77 | 78 | ```python 79 | from microbit import * 80 | ``` 81 | 82 | Which means to import everything under "microbit" so you can use them without using ```microbit.something```. 83 | 84 | Import normally does not "read" a module or function into memory; what it really does is to add variables pointing to all the stuff under module "microbit". (The exceptions are some C++ based Python packages which have to be loaded upon being imported, which are common among data science packages, but there is no way to install these on the micro:bits anyway.) 85 | 86 | But using * to import everything is still a bad practice. If you do this in standard Python, you might accidentally import things with conflicted names. Instead, you *should always* explicitly import what you need: 87 | 88 | ```python 89 | from microbit import pin0, display, sleep 90 | ``` 91 | 92 | ## How Much Memory Left? 93 | 94 | ```python 95 | from micropython import mem_info 96 | 97 | print(mem_info(1)) 98 | ``` 99 | 100 | You can also use garbage collection to free some memory if possible (it's not magic after all): 101 | 102 | ```python 103 | import gc 104 | 105 | gc.enable() # enable automatic memory recycle 106 | gc.collect() # force memory recycle 107 | ``` 108 | 109 | ## Write and Read Text Files 110 | 111 | Data can be preserved as files onboard until a new script is flashed onto it, although there is no way to download files 112 | 113 | Write several lines into a file (will be created if not exist): 114 | 115 | ```python 116 | data = [ 117 | 'line 1', 118 | 'line 2', 119 | 'line 3' 120 | ] 121 | 122 | with open(r'file.txt', 'w') as file: 123 | file.write('\n'.join(data)) 124 | ``` 125 | 126 | Read content of a file: 127 | 128 | ```python 129 | content = '' 130 | with open(r'file.txt') as file: 131 | content = file.read() 132 | print(content) 133 | ``` 134 | 135 | List files using REPL: 136 | 137 | ``` 138 | >>> import os 139 | >>> os.listdir() 140 | ``` 141 | 142 | Delete file using REPL: 143 | 144 | ``` 145 | >>> os.remove('file.txt') 146 | ``` 147 | 148 | ### Classic Blinky (LED screen) 149 | 150 | ```python 151 | from microbit import display, Image, sleep 152 | 153 | while True: 154 | display.show(Image.HEART) 155 | sleep(1000) 156 | display.clear() 157 | sleep(1000) 158 | ``` 159 | 160 | ## Classic Blinky (LED on pin 0) 161 | 162 | This version controls an external LED connected between pin 0 and GND and uses ```time.sleep()``` module instead of ```microbit.sleep()```. 163 | 164 | Most LEDs are anode so connect the longer leg to [micro:bit's pin 0](https://makecode.microbit.org/device/pins) and the shorter leg to GND pin, either using crocodile clip wires or a edge connector, two jumper wires and a breadboard. 165 | 166 | ```python 167 | from microbit import pin0 168 | import time 169 | 170 | while True: 171 | pin0.write_digital(1) 172 | time.sleep(0.5) 173 | pin0.write_digital(0) 174 | time.sleep(0.5) 175 | ``` 176 | 177 | Note: for both micro:bit V1/V2 you don't really need a resistor to protect the LED. The voltage and current from any pins (except the 3V pin) are low enough to cause real harms. 178 | 179 | ## Blinky LEDs Without Using Sleep 180 | 181 | Using the ```time``` module to make two LEDs on the LED screen blink asynchronously at different intervals in the same loop. 182 | 183 | ```python 184 | from microbit import display 185 | import time 186 | 187 | delay1, delay2 = 500, 400 188 | since1, since2 = time.ticks_ms(), time.ticks_ms() 189 | 190 | 191 | while True: 192 | 193 | now = time.ticks_ms() 194 | 195 | if time.ticks_diff(now, since1) >= delay1: # toogle LED (0, 0) 196 | display.set_pixel(0, 0, 9 if display.get_pixel(0, 0) == 0 else 0) 197 | since1 = time.ticks_ms() 198 | 199 | if time.ticks_diff(now, since2) >= delay2: # toogle LED (4, 4) 200 | display.set_pixel(4, 4, 9 if display.get_pixel(4, 4) == 0 else 0) 201 | since2 = time.ticks_ms() 202 | ``` 203 | 204 | ## A More Convenient Pin Class? 205 | 206 | Define a Pin class to wrap existing pin methods. 207 | 208 | ```python 209 | from microbit import pin1, pin2, sleep 210 | 211 | class Pin: 212 | 213 | __slots__ = ['pin'] # not to use dictionary to store attributes in the class to save memory 214 | 215 | def __init__(self, pin): 216 | self.pin = pin 217 | 218 | def setPin(self, value): 219 | self.pin.write_digital(value) 220 | 221 | def setPWM(self, value): 222 | self.pin.write_analog(value) 223 | 224 | def getPin(self): 225 | self.pin.set_pull(self.pin.NO_PULL) 226 | return self.pin.read_digital() 227 | 228 | def getADC(self): 229 | try: 230 | return self.pin.read_analog() 231 | except: 232 | return 0 233 | 234 | def isPressed(self): 235 | self.pin.set_pull(self.pin.PULL_UP) 236 | return not self.pin.read_digital() 237 | 238 | def isTouched(self): # for pin_logo 239 | try: 240 | self.pin.set_pull(self.pin.NO_PULL) 241 | return self.pin.is_touched() 242 | except: 243 | return False 244 | 245 | 246 | led = Pin(pin1) # external led at pin 1 247 | button = Pin(pin2) # external button at pin 2 248 | 249 | while True: 250 | # light up LED when button is pressed 251 | led.setPin(button.isPressed()) 252 | sleep(50) 253 | ``` 254 | 255 | See the following link for available pin functions: 256 | 257 | * [micro:bit pins](https://makecode.microbit.org/device/pins) 258 | * [Edge Connector Pins](https://tech.microbit.org/hardware/edgeconnector/#edge-connector-pins) 259 | * [pin functions in MicroPython](https://microbit-micropython.readthedocs.io/en/v2-docs/pin.html#pin-functions) 260 | 261 | The class would set internal pull-up for reading button status with ```isPressed()```. The buttons can be connected to one pin and GND without physical resistors. The onboard A/B buttons already have built-in resistors. 262 | 263 | For controlling external LEDs, it is recommended to add a resistor (at least 220Ω for micro:bit V1 and 100Ω for V2) between the micro:bit pin and the LED anode leg. 264 | 265 | ## Simpler Alternate Pin Class 266 | 267 | Use **namedtuple** (a tuple that elements have attribute names) as a simple Pin class. We point the pin methods to these attributes. 268 | 269 | ```python 270 | from microbit import pin1, pin2, sleep 271 | from ucollections import namedtuple 272 | 273 | Pin = namedtuple('Pin', ['setPin', 'setPWM', 'getPin', 'getADC', 'isTouched']) 274 | 275 | def newPin(pin, pull_up=False): 276 | pin.set_pull(pin.PULL_UP if pull_up else pin.NO_PULL) 277 | return Pin(pin.write_digital, pin.write_analog, pin.read_digital, pin.read_analog, pin.is_touched) 278 | 279 | 280 | led = newPin(pin1) 281 | button = newPin(pin2, pull_up=True) 282 | 283 | while True: 284 | led.setPin(not button.getPin()) 285 | sleep(50) 286 | ``` 287 | 288 | ## Value Mapping 289 | 290 | Translate a value in a range to its corresponding value in anoher range, similar to **map()** in Arduino or micro:bit MakeCode. Borrowed from [here](https://stackoverflow.com/questions/1969240/mapping-a-range-of-values-to-another). 291 | 292 | ```python 293 | from microbit import display, sleep 294 | 295 | def translate(value, leftMin, leftMax, rightMin, rightMax): 296 | leftSpan = leftMax - leftMin 297 | rightSpan = rightMax - rightMin 298 | valueScaled = float(value - leftMin) / float(leftSpan) 299 | return rightMin + (valueScaled * rightSpan) 300 | 301 | 302 | while True: 303 | lightLevel = display.read_light_level() 304 | print(translate(lightLevel, 0, 255, 0, 1023)) # map value range 0-255 to 0-1023 305 | sleep(100) 306 | ``` 307 | 308 | ## Roll a Dice 309 | 310 | Define dice images in a dictionary, and retrieve one using a random number when the shake gesture detected. 311 | 312 | ```python 313 | from microbit import display, Image, accelerometer, sleep 314 | from random import randint 315 | 316 | dice = { # dictionary of 5x5 dice images 317 | 1: '00000:00000:00900:00000:00000', 318 | 2: '90000:00000:00000:00000:00009', 319 | 3: '90000:00000:00900:00000:00009', 320 | 4: '90009:00000:00000:00000:90009', 321 | 5: '90009:00000:00900:00000:90009', 322 | 6: '90009:00000:90009:00000:90009', 323 | } 324 | 325 | while True: 326 | if accelerometer.was_gesture('shake'): # if user has shaked micro:bit 327 | display.show(Image(dice[randint(1, 6)])) # get a image in random 328 | ``` 329 | 330 | ## Fill LED Display 331 | 332 | Light up every LEDs in a specific brightness level (default max): 333 | 334 | ```python 335 | from microbit import display, Image, sleep 336 | 337 | def fillScreen(b=9): # fill screen function, b = brightness (0-9) 338 | display.show(Image(':'.join([str(b) * 5] * 5))) 339 | 340 | 341 | while True: 342 | # blink screen twice 343 | for _ in range(2): 344 | fillScreen() # default = max brightness 345 | sleep(250) 346 | display.clear() 347 | sleep(250) 348 | 349 | sleep(500) 350 | 351 | # fade in 352 | for i in range(10): 353 | fillScreen(i) 354 | sleep(75) 355 | 356 | # fade out 357 | for i in reversed(range(10)): 358 | fillScreen(i) 359 | sleep(75) 360 | 361 | sleep(500) 362 | ``` 363 | 364 | ## LED Bar Graph 365 | 366 | A 25-level LED progress bar, similar to the one you can use in the MakeCode JavaScript editor. 367 | 368 | ```python 369 | from microbit import display, Image, sleep 370 | 371 | def plotBarGraph(value, max_value, b=9): 372 | order = (23, 21, 20, 22, 24, 373 | 18, 16, 15, 17, 19, 374 | 13, 11, 10, 12, 14, 375 | 8, 6, 5, 7, 9, 376 | 3, 1, 0, 2, 4,) 377 | counter = 0 378 | display.clear() 379 | for y in range(5): 380 | for x in range(5): 381 | if value / max_value > order[counter] / 25: 382 | display.set_pixel(x, y, b) 383 | counter += 1 384 | 385 | while True: 386 | plotBarGraph(display.read_light_level(), 255) 387 | sleep(50) 388 | ``` 389 | 390 | The LED screen may flicker since ```read_light_level()``` uses LEDs themselves as light sensors (see [this video](https://www.youtube.com/watch?v=TKhCr-dQMBY) for explanation). 391 | 392 | ## Tiny Two-Digit Display 393 | 394 | Display two 2x5 digits (range 0~99) on the 5x5 matrix. This is very similar to a MakeCode extension. 395 | 396 | ```python 397 | from microbit import display, Image, sleep, temperature 398 | 399 | digits = { 400 | '0': ('99', '99', '99', '99', '99'), 401 | '1': ('09', '09', '09', '09', '09'), 402 | '2': ('99', '09', '99', '90', '99'), 403 | '3': ('99', '09', '99', '09', '99'), 404 | '4': ('90', '90', '99', '09', '09'), 405 | '5': ('99', '90', '99', '09', '99'), 406 | '6': ('90', '90', '99', '99', '99'), 407 | '7': ('99', '09', '09', '09', '09'), 408 | '8': ('99', '99', '00', '99', '99'), 409 | '9': ('99', '99', '99', '09', '09'), 410 | ' ': ('00', '00', '00', '00', '00'), 411 | } 412 | 413 | def showDigits(value, b=9, fill_zero=False): 414 | value = min(max(value, 0), 99) 415 | d = ('{:02d}' if fill_zero else '{:2d}').format(value) 416 | return Image(':'.join( 417 | ['{}0{}'.format(digits[d[0]][i], digits[d[1]][i]).replace('9', str(b)) 418 | for i in range(5)])) 419 | 420 | 421 | while True: 422 | display.show(showDigits(temperature(), fill_zero=True)) 423 | sleep(1000) 424 | ``` 425 | 426 | In ```showDigits()```, parameter b is brightness (0~9) and fill_zero=True means numbers smaller than 10 will be displayed as 01, 02, 03... 427 | 428 | ## Get Pitch and Roll Degrees 429 | 430 | This is another functionality exists in MakeCode but not in MicroPython. Be noted that the results would be outputed in the REPL console and it's +-180 decrees instead of 360 degrees. 431 | 432 | ```python 433 | from microbit import accelerometer, sleep 434 | from math import pi, atan2, sqrt 435 | 436 | def rotationPitch(): 437 | return atan2( 438 | accelerometer.get_y(), 439 | sqrt(accelerometer.get_x() ** 2 + accelerometer.get_z() ** 2) 440 | ) * (180 / pi) 441 | 442 | def rotationRoll(): 443 | return atan2( 444 | accelerometer.get_x(), 445 | sqrt(accelerometer.get_y() ** 2 + accelerometer.get_z() ** 2) 446 | ) * (180 / pi) 447 | 448 | 449 | while True: 450 | print('Pitch:', rotationPitch(), ' / roll:', rotationRoll()) 451 | sleep(100) 452 | ``` 453 | 454 | ## Servo Control 455 | 456 | Be noted that a SG90 hobby servo comsumes a few hundred mA and the 3.3V pin from micro:bit V1 (90 mA) is barely enough. Use an external 5V power instead, or use micro:bit V2 (200 mA at 3.3V pin). 457 | 458 | ```python 459 | from microbit import pin0, sleep 460 | 461 | class Servo: # define a servo class 462 | def __init__(self, pin, degree=90): 463 | self.pin = pin 464 | self.degree = degree 465 | self.write(degree) 466 | 467 | def write(self, degree): 468 | self.pin.set_analog_period(20) 469 | self.pin.write_analog(round((degree * 92 / 180 + 30), 0)) 470 | 471 | 472 | servo = Servo(pin0) # servo object 473 | 474 | while True: 475 | servo.write(0) 476 | sleep(1000) 477 | servo.write(180) 478 | sleep(1000) 479 | ``` 480 | 481 | Do not use servos and buzzers at the same time. They require different PWM frequencies and most microcontrollers can only use one frequency accross all pins at a time. 482 | 483 | micro:bit V2 can output 190 mA from its 3V pin, which is enough for most hobby servos. 484 | 485 | ## NeoPixel Rainbow/Rotation Effect 486 | 487 | This code is based on Adafruit's example with adjustable brightness level. Change the NeoPixel (WS281x) data pin from pin0 to other pins if needed. (You can power the LED strips with 3.3V pin, although V1 can output less power than V2. One NeoPixel LED at full power may comsume as much as 50 mA. Running in low light level is recommended.) 488 | 489 | ```python 490 | from microbit import pin0, sleep # connect to pin 0 491 | from neopixel import NeoPixel 492 | from micropython import const 493 | 494 | led_num = const(12) # number of NeoPixels 495 | led_maxlevel = const(64) # light level (0-255) 496 | led_delay = const(5) # NeoPixels cycle delay 497 | 498 | np = NeoPixel(pin0, led_num) 499 | 500 | def wheel(pos): 501 | r, g, b = 0, 0, 0 502 | if pos < 0 or pos > 255: 503 | r, g, b = 0, 0, 0 504 | elif pos < 85: 505 | r, g, b = 255 - pos * 3, pos * 3, 0 506 | elif pos < 170: 507 | pos -= 85 508 | r, g, b = 0, 255 - pos * 3, pos * 3 509 | else: 510 | pos -= 170 511 | r, g, b = pos * 3, 0, 255 - pos * 3 512 | r = round(r * led_maxlevel / 255) 513 | g = round(g * led_maxlevel / 255) 514 | b = round(b * led_maxlevel / 255) 515 | return (r, g, b) 516 | 517 | def rainbow_cycle(pos): 518 | for i in range(led_num): 519 | rc_index = (i * 256 // led_num) + pos 520 | np[i] = wheel(rc_index & 255) 521 | np.show() 522 | 523 | 524 | pos = 0 525 | while True: 526 | rainbow_cycle(pos) 527 | pos = (pos + 1) % 255 528 | sleep(led_delay) 529 | ``` 530 | 531 | ### Calcualte Fibonacci Sequence 532 | 533 | [Fibonacci sequence](https://en.wikipedia.org/wiki/Fibonacci_number) 534 | 535 | ```python 536 | from microbit import display 537 | 538 | def Fibonacci(n): # calculate nth number 539 | a = 0 540 | b = 1 541 | for i in range(n - 2): 542 | a, b = b, a + b 543 | return b 544 | 545 | f = Fibonacci(42) 546 | print(f) 547 | display.scroll(f) 548 | ``` 549 | 550 | Below is the recursive version, which is a lot slower and you may get ```RuntimeError: maximum recursion depth exceeded``` for a bigger number, especially in micro:bit V1. 551 | 552 | ```python 553 | from microbit import display 554 | 555 | def Fibonacci(n): 556 | if n < 2: 557 | return n 558 | return Fibonacci(n - 1) + Fibonacci(n - 2) 559 | 560 | 561 | f = Fibonacci(24) 562 | print(f) 563 | display.scroll(f) 564 | ``` 565 | 566 | ### Calcuate a List of Prime Numbers 567 | 568 | Prime numbers (except 2, 3) are either 6n - 1 or 6n + 1. So we check if a number of 6n - 1/6n + 1 can be divided with any existing primes in the list. If not, it is a prime number and can be added to the list. 569 | 570 | ```python 571 | from microbit import display 572 | 573 | def find_primes(n): # calculate primes up to n 574 | primes = [2, 3] 575 | for p in range(6, n + 1, 6): 576 | for p_test in range(p - 1, p + 2, 2): 577 | for prime in primes: 578 | if p_test % prime == 0: 579 | break 580 | else: # only execute when for is not exited by break 581 | primes.append(p_test) 582 | return primes 583 | 584 | primes = find_primes(50) 585 | print(primes) 586 | for prime in primes: 587 | display.scroll(prime) 588 | ``` 589 | 590 | ## Morse Code Machine 591 | 592 | This allows you to enter your message into micro:bit and convert it to Morse code with the LED screen and buzzer. Go to the REPL mode and you'll see the prompt. 593 | 594 | ```python 595 | from microbit import display, Image, set_volume, sleep 596 | from micropython import const 597 | import music 598 | 599 | set_volume(255) # speaker volume (0-255) 600 | morse_delay = const(75) # morse code delay speed 601 | 602 | # morse code table 603 | morse_code = { 604 | 'A': '.-', 605 | 'B': '-...', 606 | 'C': '-.-.', 607 | 'D': '-..', 608 | 'E': '.', 609 | 'F': '..-.', 610 | 'G': '--.', 611 | 'H': '....', 612 | 'I': '..', 613 | 'J': '.---', 614 | 'K': '-.-', 615 | 'L': '.-..', 616 | 'M': '--', 617 | 'N': '-.', 618 | 'O': '---', 619 | 'P': '.--.', 620 | 'Q': '--.-', 621 | 'R': '.-.', 622 | 'S': '...', 623 | 'T': '-', 624 | 'U': '..-', 625 | 'V': '...-', 626 | 'W': '.--', 627 | 'X': '-..-', 628 | 'Y': '-.--', 629 | 'Z': '--..', 630 | '1': '.----', 631 | '2': '..---', 632 | '3': '...--', 633 | '4': '....-', 634 | '5': '.....', 635 | '6': '-....', 636 | '7': '--...', 637 | '8': '---..', 638 | '9': '----.', 639 | '0': '-----', 640 | } 641 | 642 | while True: 643 | 644 | print('Enter your message: (Press enter to exit)') 645 | msg_str = input('> ').upper() 646 | if not msg_str: 647 | break 648 | 649 | morse_str = ''.join([morse_code[s] for s in msg_str 650 | if s in morse_code]) 651 | print('Message converted:\n', morse_str) 652 | 653 | for code in morse_str: 654 | music.pitch(392) 655 | display.show(Image.TARGET) 656 | sleep(morse_delay * (3 if code == '-' else 1)) 657 | music.stop() 658 | display.clear() 659 | sleep(morse_delay) 660 | 661 | print('') 662 | ``` 663 | 664 | ## Text-based Group Chat 665 | 666 | Load the code to at multiple micro:bits, each connected to a computer and enter the REPL mode. They will display any messages (250 characters max each) sent by other micro:bits on the same channel. 667 | 668 | In order to send message, press A and enter text after the prompt. (Some incoming messages may be lost when you are typing. So you can also treat this as actual radio and use [procedure words](https://en.wikipedia.org/wiki/Procedure_word).) 669 | 670 | ```python 671 | RADIO_CHANNEL = 42 672 | 673 | from microbit import display, Image, button_a, sleep 674 | import radio 675 | 676 | radio.config(group=RADIO_CHANNEL, length=250, power=7) 677 | radio.on() 678 | 679 | print('Receiving messages...') 680 | print('Press A to send your message (max 250 characters each)') 681 | display.show(Image.RABBIT) 682 | 683 | while True: 684 | 685 | if button_a.is_pressed(): 686 | text = input('Enter your message: ') 687 | if len(text) > 0 and len(text.strip()) > 0: 688 | to_be_send = text.strip()[:250] 689 | radio.send(to_be_send) 690 | print('YOU:', to_be_send) 691 | else: 692 | sleep(100) 693 | 694 | incoming = radio.receive() 695 | if incoming != None: 696 | print('MESSAGE:', incoming) 697 | 698 | sleep(50) 699 | ``` 700 | 701 | ## Radio Proximity Sensor 702 | 703 | Load the code to two micro:bits. They will detect each other's radio signal strength and show it as LED bar graph. Can be used as an indoor treasure hunt game. 704 | 705 | (This also works for micro:bit V1, however V1 is slower so there will be signal gap received by V2. So in order to mix V1 and V2, You'll have to either speed up V1 or slow down V2 loop delay.) 706 | 707 | Due to some reason, the signal strength or RSSI changes very little regardless of transmite power. So I roughly remapped the value to 0-60 so that you can see the changes more clearly. 708 | 709 | If there's no signal received the strength data would be set as zero. 710 | 711 | ```python 712 | RADIO_CHANNEL = 42 713 | 714 | from microbit import display, sleep 715 | import radio 716 | 717 | def plotBarGraph(value, max_value, b=9): 718 | order = (23, 21, 20, 22, 24, 719 | 18, 16, 15, 17, 19, 720 | 13, 11, 10, 12, 14, 721 | 8, 6, 5, 7, 9, 722 | 3, 1, 0, 2, 4,) 723 | counter = 0 724 | display.clear() 725 | for y in range(5): 726 | for x in range(5): 727 | if value / max_value > order[counter] / 25: 728 | display.set_pixel(x, y, b) 729 | counter += 1 730 | 731 | 732 | radio.config(group=RADIO_CHANNEL, power=7) 733 | radio.on() 734 | 735 | while True: 736 | 737 | radio.send('0') 738 | strength = 0.0 739 | data = radio.receive_full() 740 | 741 | if data: 742 | strength = data[1] + 255 - 155 743 | 744 | print('Signal strength:', strength) 745 | plotBarGraph(strength, 60) 746 | sleep(50) 747 | ``` 748 | --------------------------------------------------------------------------------