micro:bot

Montageanleitung für micro:bot

Montageanleitung für micro:bot

Programmieranleitung für micro:bot

Das Fahrzeug namens micro:bot besteht aus einem Board namens moto:bit. Dieses Board ist v.a. für die Spannungsversorgung und die Motorsteuerung zuständig, verfügt aber über keine Logik. Dafür enthält es einen Slot für einen Mikrocomputer namens micro:bit, der die Grösse einer halben Kreditkarte hat.

Jedes Team erhält zwei micro:bits. Sie lassen sich mit MicroPython programmieren. Dazu sind die micro:bits mittels USB-Kabel mit dem Computer zu verbinden und ein Chrome- oder Edge-Browser zu verwenden. Beide Browser unterstützen WebUSB, mit denen USB-Geräte direkt vom Browser aus angesprochen und geflasht werden können.

Die micro:bits werden über die Website https://python.microbit.org programmiert.

Mit Connect muss zuerst der micro:bit mit dem Computer gekoppelt werden. Programme können mittels Flash auf den micro:bit geladen werden. Ihr könnt das gleich einmal mit dem standardmässig vorgegebenen Hello-World-Programm ausprobieren.

Für die HäckNight-Aufgabe stellen wir Euch eine kleine Bibliothek zur Verfügung, die Convenience-Klassen zum Ansteuern des Radio-Receivers (für das Start-Signal), dem Line Follower Array und den Motoren enthält. Sie wurde so weit wie möglich optimiert, so dass es nicht nötig sein wird, an dieser Bibliothek Anpassungen vorzunehmen.

Ladet die Bibliothek namens motobit.py auf den micro:bit, indem Ihr Load/Save klickt. Im daraufhin öffnenden Fenster klappt Ihr die Project Files auf und klickt ganz unten auf Add file. Fügt dann die Datei motobit.py hinzu. Sie steht ab sofort für Euer «Hauptprogramm» zur Verfügung.

Mit Open Serial lassen sich Ausgaben des micro:bits auf einer Browser-Konsole anzeigen. Dies ist zum einen hilfreich für detaillierte Fehlermeldungen, kann aber auch zum Auslesen von Daten dienen, die der micro:bit mittels print(...) sendet.

Wer eine separate Python-IDE verwenden will (z.B. PyCharm oder Visual Studio Code), dem sei das Paket pseudo-microbit empfohlen, mit dem die Syntax der micro:bit-Ansteuerungen sichergestellt werden kann.

Mit getting_started.py stellen wir Euch eine Python-Datei zur Verfügung, die bzgl. Ansteuerung des micro:bits, des Radio-Receivers, des Line Follower Arrays und der Motoren Beispiele, Erklärungen und weitere Links enthält. Die Datei getting_started.py ist direkt so ausführbar (sie läuft in einer Endlosschleife) und steuert auch bereits die Motoren an. Wir empfehlen, dass zuerst getting_started.py korrekt läuft, bevor Ihr mit der eigentlichen Entwicklungsarbeit beginnt.

Bitte beachtet, dass der micro:bit in Sachen Speicher sehr limitiert ist. Dazu zählt sowohl «statischer Speicher» (also Programmcode, der auch lange Variablennamen oder Kommentare mit einschliesst) als auch «dynamischer Speicher» (also Variablen zur Laufzeit). Es empfiehlt sich daher auf jeden Fall, Overengineering in der Programmlogik zu vermeiden und stattdessen einen pragmatischen Ansatz zu verfolgen. 😉

Für Fragen oder Probleme programmiertechnischer Natur stehen Euch Christian und Silvan gerne jederzeit zur Verfügung.

getting_started.py

    
# HäckNight 2021 | Getting Started With micro:bit and moto:bit Examples
#
# Christian Heitzmann | 2021-09-21 23:15

import microbit as mibi
from motobit import (RadioReceiver, LineFollowerArray, LeftMotor, RightMotor)

while True:

  # ---- micro:bit ----

  # A very good starting point for MicroPython on the micro:bit can be found at:
  # https://microbit.org/get-started/user-guide/python/
  # It also includes links to more specific documentation on specific topics. If you prefer an API-style
  # documentation, use this: https://microbit-micropython.readthedocs.io/en/v1.0.1/
  # Note that we are using micro:bit v1, not v2.

  # Scroll text on the display. */
  mibi.display.scroll('HAECKNIGHT 2021')

  # Display an image. See http://giggletronics.blogspot.com/2016/08/built-in-images.html for a list of all image
  # values and their graphical representations.
  mibi.display.show(mibi.Image.HEART)

  # Sleep for 2000 ms, i.e., for 2 seconds.
  mibi.sleep(2000)

  # Check for button presses and show corresponding images. You probably need to do this in a loop.
  if mibi.button_a.was_pressed():
      mibi.display.show(mibi.Image.ARROW_W)
  if mibi.button_b.was_pressed():
      mibi.display.show(mibi.Image.ARROW_E)

  # ---- Radio Receiver ----

  # Create the radio receiver object. Use the channel, address, and message values provided individually to your team
  # and keep it secret. Calling the constructor automatically turns on the receiver, thus consuming energy and memory.
  receiver = RadioReceiver(channel=11, address=0x22222222, message='AAAA')

  # Check whether the message, i.e., the specific start signal for your team has been received. This method returns
  # True or False, so make sure to call it in a loop.
  receiver.message_received()

  # Once you've received the start signal and you're ready to start driving, don't forget to turn off the radio by
  # deleting the object, thus calling its finalizer.
  del receiver

  # ---- Line Follower Array ----

  # Create the line follower array object. Inverted produces '1's for white lines on dark background, whereas
  # noninverted produces '1's for black lines on light background.
  lfa = LineFollowerArray(inverted=True)

  # Always refresh before you attempt to read new LED states.
  lfa.refresh()

  # Retrieve the current LED states as one 8-bit integer. This is the perfect format if you want to manually bit mask
  # the result afterwards. Simply have a look at the serial console output to understand the format.
  led_states_int = lfa.get_led_states_as_int()
  print(led_states_int, bin(led_states_int))

  # Retrieve the current LED states as a string consisting of '0' and '1' characters. This is the perfect format if
  # you want to look at the states by indexing. The string format is offered to you in two different orientations:
  # - b7 to b0, i.e., the LED labeled "b7" on the left side of the line follower array is also on the left side of the
  # string, thus having index 0.
  # - b0 to b7, i.e., the LED labeled "b0" on the right side of the line follower array is on the left side of the
  # string, thus having index 0, and the LED labeled "b7" on the left side of the line follower array is on the right
  # side of the string, thus having index 7.
  led_states_b7_to_b0 = lfa.get_led_states_as_str_from_b7_to_b0()
  led_states_b0_to_b7 = lfa.get_led_states_as_str_from_b0_to_b7()
  print(led_states_b7_to_b0, led_states_b0_to_b7)

  # ---- Motors ----

  # Create motor objects. The right motor's direction is inverted because its wheel is mounted on the other side than
  # on the left motor.
  left_motor = LeftMotor(inverted=False)
  right_motor = RightMotor(inverted=True)

  # Run left motor full speed forward and right motor half speed forward.
  left_motor.forward(1.0)
  right_motor.forward(0.5)
  mibi.sleep(2000)

  # Stop left motor and run right motor 33% reverse speed.
  left_motor.stop()
  right_motor.reverse(0.33)
  mibi.sleep(2000)

  # Run left motor half speed forward and right motor full speed reverse.
  left_motor.drive(0.5)
  right_motor.drive(-1.0)
  mibi.sleep(2000)
  

motobit.py

    
# HäckNight 2021 | moto:bit Library
#
# Christian Heitzmann | 2021-09-21 22:00

import microbit as mibi
import radio


class RadioReceiver:

  def __init__(self, channel, address, message):
      self.message = message
      radio.config(channel=channel, address=address)
      radio.on()

  def message_received(self):
      message = radio.receive()
      return message == self.message

  def __del__(self):
      radio.off()


class LineFollowerArray:

  def __init__(self, inverted):
      self.__inverted = inverted
      self.led_states = 0b00000000

  def refresh(self):
      raw_bytes = mibi.i2c.read(0x3e, 256)
      splitted_byte_list = raw_bytes.split(bytes((0xff, 0xff)))
      one_byte_list = list(filter(lambda x: len(x) == 1, splitted_byte_list))
      two_byte_list = list(filter(lambda x: len(x) == 2, splitted_byte_list))
      if one_byte_list:
          self.led_states = 0b11111111
      elif two_byte_list:
          self.led_states = two_byte_list[-1][-1]
      if self.__inverted:
          self.led_states = ~self.led_states & 0b11111111

  def get_led_states_as_int(self):
      return self.led_states

  def get_led_states_as_str_from_b7_to_b0(self):
      return '{:08b}'.format(self.led_states)

  def get_led_states_as_str_from_b0_to_b7(self):
      return ''.join(reversed(self.get_led_states_as_str_from_b7_to_b0()))


class AbstractMotor:

  def __init__(self, speed_command, inverted):
      self.__speed_command = speed_command
      self.__inverted = inverted
      mibi.i2c.write(0x59, bytes((0x70, 0x01)))

  def forward(self, speed):
      self.drive(speed)

  def reverse(self, speed):
      self.drive(-speed)

  def stop(self):
      self.drive(0.0)

  def drive(self, speed):
      if self.__inverted:
          speed = -speed
      speed = max(speed, -1.0)
      speed = min(speed, 1.0)
      speed_int = round(speed * 127) + 127
      mibi.i2c.write(0x59, bytes((self.__speed_command, speed_int)))


class LeftMotor(AbstractMotor):

  def __init__(self, inverted):
      super().__init__(0x21, inverted)


class RightMotor(AbstractMotor):

  def __init__(self, inverted):
      super().__init__(0x20, inverted)