Using micro:bits to send data to and from an Arduino wirelessly

Summary

We can use microbits to act as a wireless bridge between an Arduino Uno and a PC. One micro:bit connects to the Arduino. The second connects to the PC. Data can be passed to and from the Arduino serial port through the micro:bits.

The problem

A friend has an Arduino based instrument that he wants to communicate with from his PC wirelessly. Why? I don't ask why. He's bigger than I am.

A solution - outline

A micro:bit is connected to the serial pins (TX and RX) on an Arduino Uno through a logic level converter. This micro:bit sends data to and receives data from the serial port on the Arduino. The data received from the Arduino is transmitted to a second micro:bit connected to the PC through a USB port. Data received from the micro:bit connected to the PC is sent to the micro:bit connected to the Arduino.

Some folk call the serial port the UART. This is probably a more accurate name. What ports aren't serial nowadays? The Arduino uses the 'Serial' library to communication with this port, so I'll carry on using the term 'serial' for the port.

The micro:bit connected to the Arduino acts as a wireless bridge to the micro:bit connected to the laptop.

The micro:bit connected to the laptop acts as a wired bridge to the laptop. This micro:bit sends data to a virtual serial port on the laptop and receives information from the laptop on the same serial port. The received data is transmitted to the micro:bit connected to the instrument.

The two micro:bits create a wireless bridge between the Arduino and the laptop, allowing two-way serial communication.

Data flow

Arduino Uno RX, TX pins <-wires-> logic level converter <-wires-> micro:bit <- radio link -> micro:bit connected to PC <- USB cable-> PC

Fritzing diagram showing the Arduino Uno, logic level converter and micro:bit connections

The diagram below shows the connections on the Arduino side. The micro:bit on the PC side is connected to the PC by a USB cable.

Connections between the Arduino Uno, logic level converter and the micro:bit.

Arduino to micro:bit connection

The Arduino Uno used for testing works at 5V, the micro:bit works at 3V. So I use a logic level converter in-between the two boards to allow communication.

I drew a Fritzing diagram showing all the connections. Like many Fritzing diagrams, it looks pretty but is hard to follow. I paid for the software, so I'm using it.

Please see my blog post on the level converter to see how to connect the Arduino to the micro:bit via a logic level converter. The setup may well work without the logic level converter. However, the input pins on the micro:bit are not rated for more than 3.9V. So, applying the 5V signal levels from the Arduino is out of spec, meaning that the micro:bit could fail at any time. Or fail a little bit, just enough to corrupt your data occassionally, leaving you chasing Heisenbugs.

micropython code for the micro:bits

The script for the two micro:bits is almost the same. The UART is set up to use the edge connector pads as RX and TX for the micro:bit connected to the logic level converter. The UART for the micro:bit connected to the PC is left as the default which uses the USB connection to send and receive data to and from the UART.

micropython code for the micro:bit connected to the logic level converter

'''
Forward serial data from pins 0&1 to radio.
Matthew Oppenheim May 2022
'''

import radio
from microbit import display, Image, pin0, pin1, sleep, uart

radio.on()
radio.config(channel=40, group=40, data_rate=radio.RATE_1MBIT)

uart.init(baudrate=115200)
uart.write('Receive data from radio and send to uart.\n')
uart.write('Receive data from uart and send to radio.\n')

# connections to edge connector pads from RX, TX on instrument
uart.init(baudrate=115200, tx=pin1, rx=pin0)

def flash_display():
    ''' Flash a chessboard onto the display. '''
    display.show(Image.CHESSBOARD)
    sleep(200)
    display.clear()

def send_uart(msg_str):
    ''' Send msg_str to serial port. '''
    uart.write(msg_str)

def send_radio(msg_str):
    ''' Send msg_str over radio. '''
    radio.send(msg_str)

# script starts here
flash_display()

while True:
    display.show(Image.DIAMOND)
    # radio.receive() returns a string
    radio_in = radio.receive()
    if radio_in:
        send_uart(radio_in)
# flash display only on instrument side, not laptop
        flash_display()
    if uart.any():
        msg_bytes = uart.read()
        msg_str = str(msg_bytes, 'UTF-8')
        send_radio(msg_str)

micropython code for the micro:bit connected to the PC

'''
Receive data from uart and send to radio.
Matthew Oppenheim May 2022
''''''

import radio
from microbit import display, Image, pin0, pin1, sleep, uart

uart.init(baudrate=115200)
radio.on()
radio.config(channel=40, group=40, data_rate=radio.RATE_1MBIT)
uart.write('Receive data from radio and send to uart.\n')
uart.write('Receive data from uart and send to radio.\n')

def flash_display():
    ''' Flash a chessboard onto the display. '''
    display.show(Image.CHESSBOARD)
    sleep(200)
    display.clear()

def send_uart(msg_str):
    ''' Send msg_str to serial port. '''
    uart.write(msg_str)

def send_radio(msg_str):
    ''' Send msg_str over radio. '''
    radio.send(msg_str)

# script starts here
flash_display()

while True:
    display.show(Image.DIAMOND)
    # radio.receive() returns a string
    radio_in = radio.receive()
    if radio_in:
        send_uart(radio_in)
        #flash_display()
    if uart.any():
        msg_bytes = uart.read()
        msg_str = str(msg_bytes, 'UTF-8')
        send_radio(msg_str)

Testing

I created a test string which mimicks the length of string that my friend wants to send from his instrument to the PC. I created a function which flashes the Arduino LED when the string 'flash' is received. This allows me to test two way communication. The string is streamed at around 100Hz to the PC. I send the string 'flash' from the Arduino serial plotter and see that the LED on the Arduino flashes.

By making the test string a number, I can use the serial plotter tool in the Arduino IDE. So long as the incoming string is not corrupted, I see a straight line. This gives a quick visual check that the data is intact.

I found that so long as the string is 16 characters or less, transmission is robust. Once the string exceeds about 16 characters, there is a marked decrease in reliability. The string is often corrupted. The limiting factor seems to be string length rather than the frequency that the string is sent at. I tested at up to about 100Hz - that is the string is sent about every 10ms. I say 'about' as I didn't use timers and interrupt service routines to try and make this exact. I just put a delay function in the main loop, which is never going to give an exact frequency rate. But it is good enough for the testing presented here.

If you want to get serious about precise timing, have a look at using FreeRTOS and timer callbacks here.

Arduino Uno test code

/*
Send a test string through the serial port.
Check for incoming serial data. 
Flash LED if the incoming serial data starts with "flash".
Matthew Oppenheim May 2022
*/

// String to hold data for the serial port
String g_inputString = "";
bool g_stringComplete = false;
int g_count = 0;
// strings used for testing data transmission
//const String g_test_string = "1,30.2;2,30.4;3,30.64;4,30.64;5,30.64;6,30.64;7,30.64;8,30.64;9,30.64;10,30.64;11,30.64;12,30.64;13,30.64;14,30.64;15,30.64;16,30.64;|A";
//const String g_test_string_1_4 = "1,11.1;2,22.2;3,33.3;4,44.4";
//const String g_test_string_1_2 = "1,11.1;2,22.2|A";
const String g_test_string_1_2 = "123456789012345";

void setup() {
   Serial.begin(115200);
   pinMode(LED_BUILTIN, OUTPUT);
   Serial.println("counter test");
}

void loop() {
  // Check if the serial data string has a newline character at end.
  if (g_stringComplete) {
    // Check if the serial data has a command in it.
    processSerialData(g_inputString);
    // Reset the serial data string and completion flag.
    g_inputString = "";
    g_stringComplete = false;
  }
    g_count = updateCount(g_count);
    //Serial.println(g_count);
    Serial.println(g_test_string_1_2);
    delay(10);
}

void flashLed() {
  digitalWrite(LED_BUILTIN, HIGH);
  delay(200);
  digitalWrite(LED_BUILTIN, LOW);
  delay(200);
}

// increment count up to 255, then reset to 0
int updateCount(int count) {
  if (count > 254) {
    return 0;
  }
  ++count;
  return count;
}

// Check for command in serial_string.
void processSerialData(const String &serialString){
  const String flash_command = "flash";
  // Use startsWith to accommodate different EOL symbols in data.
  if (serialString.startsWith(flash_command)){
    flashLed();
  }
}

// Handle incoming data on serial hardware port
void serialEvent() {
  while (Serial.available()) {
    // Read in character from serial port and add to serial data string.
    char inChar = (char)Serial.read();
    // use += as this modifies left operator without creating new instances
    g_inputString += inChar;
    // If a newline character is received, set the serial data complete flag true.
    if (inChar = '\n') {
      g_stringComplete = true;
    }
  }
}

Other Solutions

My friend tried interacting with his instrument using wifi with a Feather Huzzah connected to the instrument. This works well at home, but failed using the University wifi. We may have a go using our own wifi switch.

A wired solution is always more reliable than wireless. Using a USB cable to stream data from the UART port to the PC will always be more robust than a wireless solution. If you need to access two or more UARTs on your Arduino - for instance the Arduino Mega has four UARTs - we can use a USB to serial converter cable such as this one to connect with each of the UARTs and stream data to the PC. Each of the cables creates a virtual COM port on the PC.

Leave a Reply

Your email address will not be published.