Methods

It often happens that you want to respond to different inputs with the same output.

Function blocks (methods) in MakeCode help to break down large programs into small, easy-to-understand parts. You can use code written once several times, which saves time and reduces errors. Clear names for methods mean that everyone knows immediately what they do without having to read the whole code. Complicated processes become simpler because you only have to concentrate on the inputs and results.

without “Functions” block

In the following example, we react to the detection of an obstacle by both the left-hand obstacle sensor and the right-hand obstacle sensor by repeatedly switching the headlights on and off every second.

basic.forever(function () {
    JoyCar.initJoyCar(RevisionMainboard.OnepThree)
    if (JoyCar.obstacleavoidance(SensorLRSelection.Left)) {
        JoyCar.light(ToggleSwitch.On)
        basic.pause(1000)
        JoyCar.light(ToggleSwitch.Off)
        basic.pause(1000)
        JoyCar.light(ToggleSwitch.On)
        basic.pause(1000)
        JoyCar.light(ToggleSwitch.Off)
        basic.pause(1000)
    } else if (JoyCar.obstacleavoidance(SensorLRSelection.Right)) {
        JoyCar.light(ToggleSwitch.On)
        basic.pause(1000)
        JoyCar.light(ToggleSwitch.Off)
        basic.pause(1000)
        JoyCar.light(ToggleSwitch.On)
        basic.pause(1000)
        JoyCar.light(ToggleSwitch.Off)
        basic.pause(1000)
    }
})

The sample code in MicroPython is there because it is also available in MakeCode. This is super handy because if you're already comfortable with MakeCode, it's much easier to get started with MicroPython. By having sample code in both environments, you can continue learning without much interruption. This will help you understand how programming works and you can see how to do the same things in different programming languages.

# Import necessary libraries
from microbit import *
import neopixel

# Define your Joy-Car mainboard revision
joycar_rev = 1.3

# Define object for the lights
np = neopixel.NeoPixel(pin0, 8)

# Initialization of the I2C interface for the Joy-Car mainboard
i2c.init(freq=400000, sda=pin20, scl=pin19)

# Define values for the lights
# Values which LEDs are to be activated
headlights = (0, 3)
backlights = (5, 6)
indicator_left = (1, 4)
indicator_right = (2, 7)
indicator_warning = (1, 2, 4, 7)

# Values, which color should be displayed on the LEDs
led_white = (60, 60, 60)
led_red = (60, 0, 0)
led_off = (0, 0, 0)
led_red_br = (255, 0, 0)
led_orange = (100, 35, 0)

# method to activate/deactivate lights
def lights(on = True):
    if on:
        for x, y in zip(headlights, backlights):
            # define white for the headlights
            np[x] = led_white
            # define dark red for the backlights
            np[y] = led_red
    else:
        for x, y in zip(headlights, backlights):
            # define black for the headlights and backlights
            np[x] = led_off
            np[y] = led_off
    np.show()

# retrieve all sensor data
def fetchSensorData():
    # Since the zfill function is not included in micro:bit Micropython, 
    # it must be inserted as a function
    def zfill(s, width):
        return '{:0>{w}}'.format(s, w=width)

    # Read hexadecimal data and convert to binary data
    data = "{0:b}".format(ord(i2c.read(0x38, 1)))
    # Fill in the data to 8 digits if required
    data = zfill(data, 8)
    # declare bol_data_dict as dictionary
    bol_data_dict = {}
    # Counter for the loop that enters the data from data into bol_data_dict
    bit_count = 7
    # Transfer the data from data to bol_data_dict
    for i in data:
        if i == "0":
            bol_data_dict[bit_count] = False
            bit_count -= 1
        else:
            bol_data_dict[bit_count] = True
            bit_count -= 1

    # As of mainboard revision 1.3, the speed sensors are on separate pins
    if joycar_rev >= 1.3:
        bol_data_dict[8], bol_data_dict[9] = bol_data_dict[0], bol_data_dict[1]
        bol_data_dict[0] = bool(pin14.read_digital())
        bol_data_dict[1] = bool(pin15.read_digital())

    # bit 0 = SpeedLeft, bit 1 = SpeedRight, bit 2 = LineTrackerLeft,
    # bit 3 = LinetrackerCenter, bit 4 = LinetrackerRight,
    # bit 5 = ObstacleLeft, bit 6 = ObstacleRight, bit 7 = free pin(7)
    # (bit 8 = free (pin0) bit 9 = free (pin1)) - only with revision 1.3 or newer
    return bol_data_dict

while True:
    # Read sensor data from the mainboard
    sensor_data = fetchSensorData()

    # Check left-hand obstacle sensor
    if not sensor_data[5]:
        # Switch on low beam
        lights()

        # Wait for 1 second
        sleep(1000)

        # Switch off low beam
        lights(on = False)

        # Wait for 1 second
        sleep(1000)

        # Switch on low beam
        lights()

        # Wait for 1 second
        sleep(1000)

        # Switch off low beam
        lights(on = False)

        # Wait for 1 second
        sleep(1000)
    # Check right-hand obstacle sensor
    elif not sensor_data[6]:
        # Switch on low beam
        lights()

        # Wait for 1 second
        sleep(1000)

        # Switch off low beam
        lights(on = False)

        # Wait for 1 second
        sleep(1000)

        # Switch on low beam
        lights()

        # Wait for 1 second
        sleep(1000)

        # Switch off low beam
        lights(on = False)

        # Wait for 1 second
        sleep(1000)

Advantage of functions

However, this can very quickly become confusing. If we need certain processes more than once, we can outsource them to so-called functions.

In principle, functions consist of two parts. The function header and the function body. The function name is first defined in the header. The function body then contains all the instruction blocks that are to be executed with this function.

The function can then be called in the main flow of the program. Each time it is called, all the instructions that we have previously added to the function body are executed.

In the following example, we have outsourced the light change to the light_sequence function. This allows us to reuse the sequence both for detection by the left-hand obstacle sensor and by the right-hand obstacle sensor. This not only guarantees that the same blocks are executed in both cases, but also makes our forever block much clearer!

function light_sequence () {
    JoyCar.light(ToggleSwitch.On)
    basic.pause(1000)
    JoyCar.light(ToggleSwitch.Off)
    basic.pause(1000)
    JoyCar.light(ToggleSwitch.On)
    basic.pause(1000)
    JoyCar.light(ToggleSwitch.Off)
    basic.pause(1000)
}
basic.forever(function () {
    JoyCar.initJoyCar(RevisionMainboard.OnepThree)
    if (JoyCar.obstacleavoidance(SensorLRSelection.Left)) {
        light_sequence()
    } else if (JoyCar.obstacleavoidance(SensorLRSelection.Right)) {
        light_sequence()
    }
})

The sample code in MicroPython is there because it is also available in MakeCode. This is super handy because if you're already comfortable with MakeCode, it's much easier to get started with MicroPython. By having sample code in both environments, you can continue learning without much interruption. This will help you understand how programming works and you can see how to do the same things in different programming languages.

# Import necessary libraries
from microbit import *
import neopixel

# Define your Joy-Car mainboard revision
joycar_rev = 1.3

# Define object for the lights
np = neopixel.NeoPixel(pin0, 8)

# Initialization of the I2C interface for the Joy-Car mainboard
i2c.init(freq=400000, sda=pin20, scl=pin19)

# Define values for the lights
# Values which LEDs are to be activated
headlights = (0, 3)
backlights = (5, 6)
indicator_left = (1, 4)
indicator_right = (2, 7)
indicator_warning = (1, 2, 4, 7)

# Values, which color should be displayed on the LEDs
led_white = (60, 60, 60)
led_red = (60, 0, 0)
led_off = (0, 0, 0)
led_red_br = (255, 0, 0)
led_orange = (100, 35, 0)

# method to activate/deactivate lights
def lights(on = True):
    if on:
        for x, y in zip(headlights, backlights):
            # define white for the headlights
            np[x] = led_white
            # define dark red for the backlights
            np[y] = led_red
    else:
        for x, y in zip(headlights, backlights):
            # define black for the headlights and backlights
            np[x] = led_off
            np[y] = led_off
    np.show()

# retrieve all sensor data
def fetchSensorData():
    # Since the zfill function is not included in micro:bit Micropython, 
    # it must be inserted as a function
    def zfill(s, width):
        return '{:0>{w}}'.format(s, w=width)

    # Read hexadecimal data and convert to binary data
    data = "{0:b}".format(ord(i2c.read(0x38, 1)))
    # Fill in the data to 8 digits if required
    data = zfill(data, 8)
    # declare bol_data_dict as dictionary
    bol_data_dict = {}
    # Counter for the loop that enters the data from data into bol_data_dict
    bit_count = 7
    # Transfer the data from data to bol_data_dict
    for i in data:
        if i == "0":
            bol_data_dict[bit_count] = False
            bit_count -= 1
        else:
            bol_data_dict[bit_count] = True
            bit_count -= 1

    # As of mainboard revision 1.3, the speed sensors are on separate pins
    if joycar_rev >= 1.3:
        bol_data_dict[8], bol_data_dict[9] = bol_data_dict[0], bol_data_dict[1]
        bol_data_dict[0] = bool(pin14.read_digital())
        bol_data_dict[1] = bool(pin15.read_digital())

    # bit 0 = SpeedLeft, bit 1 = SpeedRight, bit 2 = LineTrackerLeft,
    # bit 3 = LinetrackerCenter, bit 4 = LinetrackerRight,
    # bit 5 = ObstacleLeft, bit 6 = ObstacleRight, bit 7 = free pin(7)
    # (bit 8 = free (pin0) bit 9 = free (pin1)) - only with revision 1.3 or newer
    return bol_data_dict

# Define your own method for activating/deactivating the lights
def light_sequence():
    # Switch on low beam
    lights()
    # Wait for 1 second
    sleep(1000)
    # Switch off low beam
    lights(on = False)
    # Wait for 1 second
    sleep(1000)
    # Switch on low beam
    lights()
    # Wait for 1 second
    sleep(1000)
    # Switch off low beam
    lights(on = False)
    # Wait for 1 second
    sleep(1000)

while True:
    # Read sensor data from the mainboard
    sensor_data = fetchSensorData()

    # Check left-hand obstacle sensor
    if not sensor_data[5]:
        # Call the defined method
        light_sequence()
    # Check right-hand obstacle sensor
    elif not sensor_data[6]:
        # Call the defined method
        light_sequence()

Returning values

Optionally, functions can also return so-called return values. In this case, the function sends back a response each time it is called. This return value, i.e. the response, can be determined within the function. For example, calculations can be performed here and the result returned to the main function.

In the next example, we create the sonar_sensor function. As soon as it is called, it checks the ultrasonic sensor and returns either a 0, a 1 or a 2 to the main function, depending on the distance value returned by the sensor. Depending on the value returned, we switch the headlights and hazard warning lights of the Joy-Car to different states.

If the measured distance to the obstacle is 50 cm or greater, a 0 is returned, i.e. the lights are switched on and the hazard warning lights are switched off. If the measured distance is less than 50 cm, a 1 is returned, i.e. the hazard warning flashers are switched on and the lights are switched off. If an error occurs and the return value of the sensor is not within the defined ranges, a 2 is returned. In this case, both the light and the hazard warning lights are switched on.

function sonar_sensor () {
    if (JoyCar.sonar() >= 50) {
        return 0
    } else if (JoyCar.sonar() < 50) {
        return 1
    } else {
        return 2
    }
}
basic.forever(function () {
    JoyCar.initJoyCar(RevisionMainboard.OnepThree)
    if (sonar_sensor() == 1) {
        JoyCar.light(ToggleSwitch.Off)
        JoyCar.hazardlights(ToggleSwitch.On)
    } else if (sonar_sensor() == 0) {
        JoyCar.light(ToggleSwitch.On)
        JoyCar.hazardlights(ToggleSwitch.Off)
    }
})

The sample code in MicroPython is there because it is also available in MakeCode. This is super handy because if you're already comfortable with MakeCode, it's much easier to get started with MicroPython. By having sample code in both environments, you can continue learning without much interruption. This will help you understand how programming works and you can see how to do the same things in different programming languages.

# Import necessary libraries
from microbit import *
import neopixel
import gc
from machine import time_pulse_us

# Define your Joy-Car mainboard revision
joycar_rev = 1.3

# Define object for the lights
np = neopixel.NeoPixel(pin0, 8)

# Initialization of the I2C interface for the Joy-Car mainboard
i2c.init(freq=400000, sda=pin20, scl=pin19)

# Define pins for ultrasonic sensor
trigger = pin8
echo = pin12

# Initialization of the pins for the ultrasonic sensor
trigger.write_digital(0)
echo.read_digital()

# Define values for the lights
# which LEDs are to be activated
headlights = (0, 3)
backlights = (5, 6)
indicator_left = (1, 4)
indicator_right = (2, 7)
indicator_warning = (1, 2, 4, 7)

# which color should be displayed on the LEDs
led_white = (60, 60, 60)
led_red = (60, 0, 0)
led_off = (0, 0, 0)
led_red_br = (255, 0, 0)
led_orange = (100, 35, 0)

# Variables for the lights
last_ind_act = 0
last_state_hazard = False
last_state_lights = False

# Method for calculating the distance from the ultrasonic sensor
def get_distance():
    # Activate garbage collector
    gc.collect()
    # Set a short pulse on the trigger pin
    trigger.write_digital(1)
    trigger.write_digital(0)
    # Measurement of the time until the echo pin becomes high
    duration = time_pulse_us(echo, 1)
    # Calculate distance
    distance = ((duration / 1000000) * 34300) / 2
    # Return the distance, rounded to 2 decimal places
    return round(distance, 2)

# method to activate/deactivate lights
def lights(on = True):
    if on:
        for x, y in zip(headlights, backlights):
            # define white for the headlights
            np[x] = led_white
            # define dark red for the backlights
            np[y] = led_red
    else:
        for x, y in zip(headlights, backlights):
            # define black for the headlights and backlights
            np[x] = led_off
            np[y] = led_off
    np.show()

# Method for activating/deactivating the indicators.
# Variable for the method to compare when the lights were last active.
last_ind_act = running_time()
def lightsIndicator(direction, on = True):
    # to be able to change the global variable
    global last_ind_act
    # Activate garbage collector
    gc.collect()

    # if you want to switch off the indicators
    if on is False:
        # Deactivate LEDs
        for x in direction:
            np[x] = led_off
        np.show()
        # Close the method
        return

    # Activation/deactivation of the indicators after 400 ms
    if running_time() - last_ind_act >= 400:
        # Activate LEDs when the LEDs are off
        if np[direction[0]] == led_off:
            for x in direction:
                np[x] = led_orange
        # Deactivate the LEDs when they are switched on
        else:
            for x in direction:
                np[x] = led_off
        np.show()
        # Set global variable to current runtime
        last_ind_act = running_time()


# Method for checking the distance
def sonar_sensor():
    # Return 0 if the distance is greater than 50cm, otherwise return 1
    distance = get_distance()
    if distance >= 50:
        return 0
    else:
        return 1

while True:
    # Retrieve information from the sonar_sensor method
    sonar = sonar_sensor()
    # if distance < 50 cm
    if sonar == 1:
        # Switch off low beam (if it is still on)
        lights(on = False)
        # Switch on hazard warning lights
        lightsIndicator(indicator_warning)

    else:
        # Deactivate the hazard warning lights (if they are still switched on)
        if last_state_hazard is True:
            # Deactivate hazard warning lights
            lightsIndicator(indicator_warning, on = False)

        # Switch on low beam (if it is still switched off)
        if last_state_lights is False:
            # Switch on low beam
            lights()