Following on from the last post about the remote control microbit powered bitbot, I think I mentioned all the way along that the car source code and functionality I would like to include is constantly butting up against the program size restriction that the microbit can accomodate. If you create too big a program, then after the flashing process finishes you will see a “memory allocation” error scrolling across the screen.
After a bit of searching I found this post useful. It turns out that classes, code comments, long functions and long variable names all contribute to larger program sizes.
One of the selling points for the code as I had always organized this project was the easy understandability of the classes for motor, led etc. It looked like I was going to have to abandon some of those and replace some variables if I wanted to continue to add functionality to the project.
The handset code didn’t change very much because that has always been small enough (about 100 lines) and all I needed was to add a couple of extra commands to send to the car.
I remapped the A and B buttons to the headlights and horn commands respectively and moved the not very often used fast and slow commands to the edge connectors. Hitting the A button cycles the cars lights through on, off and auto settings, where auto means to use the microbit’s light sensor to decide if it needs to turn on the headlights. Hitting the B button sounds a quick double toot on the horn to let people know you are coming. I also added a horn sound when the car starts reversing as a nod to safety and realism.
I also noticed during testing that if I pulled the plug on the handset, the car stopped receiving messages but could continue to drive the motors, perhaps with disasterous results. I decided that a cut off would be useful if the two halves ever lost contact with each other. As well as the normal movement commands I added a watchdog ‘hi’ to send out to the car approximately every 5 seconds even when no control commands are being sent.
from microbit import * import radio def wait_for_contact(): hello = 'hi' waiting = True while waiting: display.set_pixel(2, 2, 9) sleep(100) radio.send(hello) msg = radio.receive() if msg and msg == hello: waiting = False else: display.set_pixel(2, 2, 0) sleep(100) def show_contact_made(): for i in range(5): display.show(Image.HEART_SMALL) sleep(500) display.show(Image.HEART) sleep(500) fwd_rev_tilt = 250 steering_tilt = 250 forward_command = 'F' stop_command = 'S' left_command = 'L' right_command = 'R' reverse_command = 'B' fast_command = 'Z' slow_command = 'W' lights_command = 'I' horn_command = 'P' watchdog_command = 'hi' display.scroll("bitBot RC handset") radio.on() wait_for_contact() show_contact_made() last_cmd = '' watchdog_counter = 0 while True: cmd = '' if button_a.was_pressed(): cmd = lights_command elif button_b.was_pressed(): cmd = horn_command elif pin0.is_touched(): cmd = slow_command elif pin2.is_touched(): cmd = fast_command else: forward_back = accelerometer.get_y() side_to_side = accelerometer.get_x() if abs(forward_back) >= abs(side_to_side): # priority to fwd reverse if forward_back < -fwd_rev_tilt: display.show(Image.ARROW_N) cmd = forward_command elif forward_back > fwd_rev_tilt: display.show(Image.ARROW_S) cmd = reverse_command else: if side_to_side > steering_tilt: display.show(Image.ARROW_E) cmd = right_command elif side_to_side < -steering_tilt: display.show(Image.ARROW_W) cmd = left_command if cmd == '': display.show(Image.SQUARE_SMALL) cmd = stop_command if cmd != '' and cmd != last_cmd: radio.send(cmd) last_cmd = cmd # try to keep in constant contact # even if nothing has changed so # we don't run away watchdog_counter += 1 # send every 5 seconds or so if watchdog_counter >= 20: radio.send(watchdog_command) watchdog_counter = 0 sleep(250)
On to the car. After flattening out the functions from their original classes, I needed to some renaming to make their use more obvious and to organize them into a more coherent structure. First motor commands, then lights, then overall “car” commands. Notice the number of string and number literals I had to use to get the code shoehorned into the microbit.
The last version had an auto headlight feature using the light detector but this time I added a manual on and off override as well as adjusting the intensity of all the lights.
As mentioned above, the bitbot piezo horn is put to use to warn everyone when reversing and for tooting when the driver hits the B button on the handset. Just like a real car!
The final feature was implementing a more sophisticated run loop. The outer loop still runs forever but I added an inner loop to wait for initial contact, then run as long as the handset was in communication with the car. Either by sending a movement command or by the special ‘hi’ command. We increment the watchdog count every time through the loop and reset the count when we get another message over the radio. If we go too long without contact, we drop out of the inner while loop, stop the motors and wait for contact to be established again at the top of the outer loop.
from microbit import * import radio import neopixel # Motors - speed and direction left_motor = [pin0, pin8] right_motor = [pin1, pin12] # Motor Functions def pulse_width_modulate(motor, speed, direction): if 1023 >= speed >= 0: motor.write_analog(speed) motor.write_digital(direction) def motor_forward(motor, percent): pulse_width_modulate(motor, percent * 10.23, 0) def motor_reverse(motor, percent): pulse_width_modulate(motor, 1023-(percent * 10.23), 1) def motor_stop(motor): pulse_width_modulate(motor, 0, 0) # LED Functions neopixels = neopixel.NeoPixel(pin13, 12) def set_leds(pixels, colour): for i in pixels: neopixels[i] = colour neopixels.show() # Colours black = (0, 0, 0) white = (75, 75, 75) # full intensity is REALLY bright red = (75, 0, 0) amber = (75, 40, 0) def mainbeam_lights(off=False): set_leds([5, 11], black if off else white) def reverse_lights(off=False): set_leds([0, 6], black if off else white) def brake_lights(off=False): set_leds([0, 6], black if off else red) def left_blinker(off=False): set_leds([3, 4], black if off else amber) def right_blinker(off=False): set_leds([9, 10], black if off else amber) # High level commands direction = 'S' speed = 50 headlights = 'auto' def go_faster(): global speed speed = min(speed + 20, 100) def go_slower(): global speed speed = max(speed - 20, 0) def go_forward(): global direction direction = 'F' motor_forward(left_motor, speed) motor_forward(right_motor, speed) def reverse(): global direction direction = 'B' sound_horn() motor_reverse(left_motor, speed) motor_reverse(right_motor, speed) def steer_left(): global direction direction = 'L' motor_forward(left_motor, speed / 2) motor_forward(right_motor, speed) def steer_right(): global direction direction = 'R' motor_forward(left_motor, speed) motor_forward(right_motor, speed / 2) def stop(): global direction direction = 'S' motor_stop(left_motor) motor_stop(right_motor) def cycle_lights(): global headlights if headlights == 'auto': headlights = 'off' elif headlights == 'off': headlights = 'on' else: headlights = 'auto' def update_headlights(): main_beams = True if headlights == 'on' else False if headlights == 'auto': main_beams = False if display.read_light_level() >= 75 else True if main_beams: mainbeam_lights() else: mainbeam_lights(off=True) def update_blinkers(): left_blinker(off=direction != 'L') right_blinker(off=direction != 'R') def update_rear_lights(): if direction == 'S': brake_lights() elif direction == 'F' or direction == 'L' or direction == 'R': brake_lights(off=True) reverse_lights(off=True) elif direction == 'B': reverse_lights() def update_lights(): update_headlights() update_blinkers() update_rear_lights() def sound_horn(): for i in range(2): pin14.write_digital(1) sleep(100) pin14.write_digital(0) sleep(200) def wait_for_contact(): display.clear() waiting = True while waiting: display.set_pixel(2, 2, 9) sleep(100) msg = radio.receive() if msg and msg == 'hi': radio.send(msg) waiting = False else: display.set_pixel(2, 2, 0) sleep(100) def show_contact_made(): for i in range(5): display.show(Image.HEART_SMALL) sleep(500) display.show(Image.HEART) sleep(500) display.clear() display.scroll("bitBot RC") radio.on() while True: wait_for_contact() show_contact_made() watchdog_counter = 0 while watchdog_counter < 100: watchdog_counter += 1 update_lights() sleep(100) msg = radio.receive() if msg: watchdog_counter = 0 if msg == 'S': stop() elif msg == 'F': go_forward() elif msg == 'B': reverse() elif msg == 'L': steer_left() elif msg == 'R': steer_right() elif msg == 'W': go_slower() elif msg == 'Z': go_faster() elif msg == 'I': cycle_lights() elif msg == 'P': sound_horn() stop() display.show(Image.SKULL) sleep(2000)
I had anticipated the code for the car to be much longer in the non-class version but it turned out to be about 200 lines so well within manageable limits for future maintenance. Now that I have promised myself I am done with features, that shouldn’t be a concern :)