4. Embeded Programming
Embedded programming, refers to the development of software for embedded systems. These systems are designed to perform specific tasks within larger mechanical or electrical systems, unlike general-purpose computers, which are designed to handle a wide range of tasks. There are several Programming Languages and development environments (IDEs), all of them with their own capabilities and functionalities.
This week we started programming, for our group assignment we compared different programming languages and the available controllers in our Fab, you can check it here. For my final project, I will be using the built-in accelerometer from the XIAO nRF52840 (sense), so I decided to compare two ways of reading the sensor’s information and sending it via Bluetooth. I will be using CircuitPython and Arduino IDE.
I started by doing some research about the XIAO nRF52840 (sense) to see all its available features. I had previously used this board to make a small project, the manufacturer has a webpage that explains how to use all the features available for the board.

Here’s the pinout of the board I’ll be using:

Here’s a table for all the sensors and built-in features:

CIRCUITPYTHON
I used Visual Studio Code with the CircuitPython extension. You can see all the steps to install CircuitPython on the XIAO here.

After the installation, you will see that one of the files inside the board is called “code.py” Every time you make a code you need to paste it in here and save it (you can’t save another file to program the XIAO). You can add the libraries you need inside the “lib” folder.

I’ve never used Bluetooth from the nRF52840 (sense), so I browsed the web until I found some examples to use the CircuitPython libraries, here’s the link to download the libraries and examples I used. This code gave me an idea of how to make my code:
# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries
# SPDX-License-Identifier: MIT
# Basic structure example for using the BLE Connect Controller sensors
# To use, start this program, and start the Adafruit Bluefruit LE Connect app.
# Connect, and then select Controller and enable the sensors
from adafruit_ble import BLERadio
from adafruit_ble.advertising.standard import ProvideServicesAdvertisement
from adafruit_ble.services.nordic import UARTService
from adafruit_bluefruit_connect.packet import Packet
# Only the packet classes that are imported will be known to Packet.
from adafruit_bluefruit_connect.accelerometer_packet import AccelerometerPacket
from adafruit_bluefruit_connect.gyro_packet import GyroPacket
from adafruit_bluefruit_connect.location_packet import LocationPacket
from adafruit_bluefruit_connect.magnetometer_packet import MagnetometerPacket
from adafruit_bluefruit_connect.quaternion_packet import QuaternionPacket
ble = BLERadio()
uart_server = UARTService()
advertisement = ProvideServicesAdvertisement(uart_server)
while True:
print("WAITING...")
# Advertise when not connected.
ble.start_advertising(advertisement)
while not ble.connected:
pass
# Connected
ble.stop_advertising()
print("CONNECTED")
# Loop and read packets
while ble.connected:
# Keeping trying until a good packet is received
try:
packet = Packet.from_stream(uart_server)
except ValueError:
continue
# Accelerometer
if isinstance(packet, AccelerometerPacket):
print("Accelerometer:", packet.x, packet.y, packet.z)
# Gyro
if isinstance(packet, GyroPacket):
print("Gyro:", packet.x, packet.y, packet.z)
# Location
if isinstance(packet, LocationPacket):
print("Location:", packet.latitude, packet.longitude, packet.altitude)
# Magnetometer
if isinstance(packet, MagnetometerPacket):
print("Magnetometer", packet.x, packet.y, packet.z)
# Quaternion
if isinstance(packet, QuaternionPacket):
print("Quaternion:", packet.x, packet.y, packet.z, packet.w)
# Disconnected
print("DISCONNECTED")
You can find this example inside of the zip file exaples>bluebruitconnect. The file is named "bluebruitconnect_sensors.py"
I combined it with a code previously made to read the accelerometer data:
import time
import board
import busio
import digitalio
from adafruit_lsm6ds.lsm6ds3 import LSM6DS3
import pwmio
from adafruit_motor import servo
# create a PWMOut object on Pin A2.
pwm = pwmio.PWMOut(board.A2, duty_cycle=2 ** 15, frequency=50)
# Create a servo object, my_servo.
my_servo = servo.Servo(pwm)
# Define los pines a los que están conectados los LEDs
led_pin1 = board.D7 # pin para el LED
# Configura el LED como salida
led1 = digitalio.DigitalInOut(led_pin1)
led1.direction = digitalio.Direction.OUTPUT
led_pin2 = board.D8 # pin para el LED
# Configura el LED como salida
led2 = digitalio.DigitalInOut(led_pin2)
led2.direction = digitalio.Direction.OUTPUT
led_pin3 = board.D9 # pin para el LED
# Configura el LED como salida
led3 = digitalio.DigitalInOut(led_pin3)
led3.direction = digitalio.Direction.OUTPUT
imu_power = digitalio.DigitalInOut (board.IMU_PWR)
imu_power.direction = digitalio.Direction.OUTPUT
imu_power.value = True
time.sleep (0.1)
i2c = busio.I2C(board.IMU_SCL, board.IMU_SDA)
sensor = LSM6DS3(i2c)
while True:
aсс_х, асс_у, acc_z = sensor.acceleration
print(f'X:{aсс_х} Y:{асс_у} Z:{acc_z}')
time.sleep(0.1)
if(aсс_х<=-8 and асс_у<=2 and асс_у>-1 and acc_z<=4): #recoje
#led1.value=True
#led2.value=False
#led3.value=False
led1.value=True #nover motor
led2.value=False #en una direccion
time.sleep(0.1)
if(acc_z>=7 and aсс_х>=-3 and aсс_х<= 1): #lanzar placa hacia arriba
# led1.value=False
# led2.value=True
# led3.value=False
led1.value=False #detener motor
led2.value=False #detener motor
my_servo.angle = 44 #lanzo
time.sleep(0.4)
# my_servo.angle = 120 #retorna el servo
if(aсс_х < 3 and aсс_х > -3 and acc_z<=-5): #nada placa viendo acia abajo
# led1.value=False
# led2.value=False
# led3.value=True
led1.value=False #detener motor
led2.value=False #detener motor
led3.value=True #prender led que no es nada
time.sleep(0.5)
for angle in range(44, 115, 3): # 0 - 180 degrees, 5 degrees at a time.
my_servo.angle = angle
time.sleep(0.2)
# my_servo.angle = 120 #retorna el servo
# time.sleep(0.1)
And here’s the final result:
import time
import board
import busio
import digitalio
from adafruit_lsm6ds.lsm6ds3 import LSM6DS3
import adafruit_ble
from adafruit_ble.advertising.standard import ProvideServicesAdvertisement
from adafruit_ble.services.nordic import UARTService
# Initialize the accelerometer
imu_power = digitalio.DigitalInOut(board.IMU_PWR)
imu_power.direction = digitalio.Direction.OUTPUT
imu_power.value = True
time.sleep(0.1)
i2c = busio.I2C(board.IMU_SCL, board.IMU_SDA)
sensor = LSM6DS3(i2c)
# Initialize Bluetooth
ble = adafruit_ble.BLERadio()
uart_service = UARTService()
advertisement = ProvideServicesAdvertisement(uart_service)
while True:
print("WAITING...")
# Advertise when not connected.
ble.start_advertising(advertisement)
while not ble.connected:
pass
# Connected
ble.stop_advertising()
print("CONNECTED")
while ble.connected:
# Read accelerometer data
acc_x, acc_y, acc_z = sensor.acceleration
print(f'X:{acc_x} Y:{acc_y} Z:{acc_z}')
# Send data over Bluetooth
uart_service.write(f'X:{acc_x} Y:{acc_y} Z:{acc_z}\n')
time.sleep(0.1)
print("Disconnected!")
I used the bluetooth setup from the example but since I wasn´t going to handle different connections I changed uart_server for uart_service, and I used the accelerometer setup from my previous code.
I used an Android phone to connect it to the board via Bluetooth, I downloaded the "Serial Bluetooth Termial" app to open a terminal and see the data:

ARDUINO IDE
The second way I tried is by using Arduino IDE. The Seed Studio site explains how to download the right libraries. The zip file also contains several application examples.

After downloading the libraries, I opened the Arduino IDE and opened the following example code, this code connects to a phone to send and recieve data.


#include < bluefruit.h>
#include < Adafruit_LittleFS.h>
#include < InternalFileSystem.h>
// BLE Service
BLEDfu bledfu; // OTA DFU service
BLEDis bledis; // device information
BLEUart bleuart; // uart over ble
BLEBas blebas; // battery
void setup()
{
Serial.begin(115200);
#if CFG_DEBUG
// Blocking wait for connection when debug mode is enabled via IDE
while ( !Serial ) yield();
#endif
Serial.println("Bluefruit52 BLEUART Example");
Serial.println("---------------------------\n");
// Setup the BLE LED to be enabled on CONNECT
// Note: This is actually the default behavior, but provided
// here in case you want to control this LED manually via PIN 19
Bluefruit.autoConnLed(true);
// Config the peripheral connection with maximum bandwidth
// more SRAM required by SoftDevice
// Note: All config***() function must be called before begin()
Bluefruit.configPrphBandwidth(BANDWIDTH_MAX);
Bluefruit.begin();
Bluefruit.setTxPower(4); // Check bluefruit.h for supported values
//Bluefruit.setName(getMcuUniqueID()); // useful testing with multiple central connections
Bluefruit.Periph.setConnectCallback(connect_callback);
Bluefruit.Periph.setDisconnectCallback(disconnect_callback);
// To be consistent OTA DFU should be added first if it exists
bledfu.begin();
// Configure and Start Device Information Service
bledis.setManufacturer("Adafruit Industries");
bledis.setModel("Bluefruit Feather52");
bledis.begin();
// Configure and Start BLE Uart Service
bleuart.begin();
// Start BLE Battery Service
blebas.begin();
blebas.write(100);
// Set up and start advertising
startAdv();
Serial.println("Please use Adafruit's Bluefruit LE app to connect in UART mode");
Serial.println("Once connected, enter character(s) that you wish to send");
}
void startAdv(void)
{
// Advertising packet
Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE);
Bluefruit.Advertising.addTxPower();
// Include bleuart 128-bit uuid
Bluefruit.Advertising.addService(bleuart);
// Secondary Scan Response packet (optional)
// Since there is no room for 'Name' in Advertising packet
Bluefruit.ScanResponse.addName();
/* Start Advertising
* - Enable auto advertising if disconnected
* - Interval: fast mode = 20 ms, slow mode = 152.5 ms
* - Timeout for fast mode is 30 seconds
* - Start(timeout) with timeout = 0 will advertise forever (until connected)
*
* For recommended advertising interval
* https://developer.apple.com/library/content/qa/qa1931/_index.html
*/
Bluefruit.Advertising.restartOnDisconnect(true);
Bluefruit.Advertising.setInterval(160, 320); // en unidades de 0.625 ms
// in unit of 0.625 ms
Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode
Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds
}
void loop()
{
// Forward data from HW Serial to BLEUART
while (Serial.available())
{
// Delay to wait for enough input, since we have a limited transmission buffer
delay(2);
uint8_t buf[64];
int count = Serial.readBytes(buf, sizeof(buf));
bleuart.write( buf, count );
}
// Forward from BLEUART to HW Serial
while ( bleuart.available() )
{
uint8_t ch;
ch = (uint8_t) bleuart.read();
Serial.write(ch);
}
}
// callback invoked when central connects
void connect_callback(uint16_t conn_handle)
{
delay(500); // Pequeño retraso antes de continuar
// Get the reference to current connection
BLEConnection* connection = Bluefruit.Connection(conn_handle);
char central_name[32] = { 0 };
connection->getPeerName(central_name, sizeof(central_name));
Serial.print("Connected to ");
Serial.println(central_name);
}
/**
* Callback invoked when a connection is dropped
* @param conn_handle connection where this event happens
* @param reason is a BLE_HCI_STATUS_CODE which can be found in ble_hci.h
*/
void disconnect_callback(uint16_t conn_handle, uint8_t reason)
{
(void) conn_handle;
(void) reason;
Serial.println();
Serial.print("Disconnected, reason = 0x"); Serial.println(reason, HEX);
}
I have never programmed this board using Arduino IDE so I also researched how to read the data from the accelerometer, I found a section called “The 6-Axis IMU Usage on Seeed Studio XIAO nRF52840 Sense” which explains how to view the data on the serial monitor.

The following example shows on the serial monitor the data from the built-in sensors.
#include "LSM6DS3.h"
#include "Wire.h"
//Create a instance of class LSM6DS3
LSM6DS3 myIMU(I2C_MODE, 0x6A); //I2C device address 0x6A
void setup() {
// put your setup code here, to run once:
Serial.begin(9600);
while (!Serial);
//Call .begin() to configure the IMUs
if (myIMU.begin() != 0) {
Serial.println("Device error");
} else {
Serial.println("Device OK!");
}
}
void loop() {
//Accelerometer
Serial.print("\nAccelerometer:\n");
Serial.print(" X1 = ");
Serial.println(myIMU.readFloatAccelX(), 4);
Serial.print(" Y1 = ");
Serial.println(myIMU.readFloatAccelY(), 4);
Serial.print(" Z1 = ");
Serial.println(myIMU.readFloatAccelZ(), 4);
//Gyroscope
Serial.print("\nGyroscope:\n");
Serial.print(" X1 = ");
Serial.println(myIMU.readFloatGyroX(), 4);
Serial.print(" Y1 = ");
Serial.println(myIMU.readFloatGyroY(), 4);
Serial.print(" Z1 = ");
Serial.println(myIMU.readFloatGyroZ(), 4);
//Thermometer
Serial.print("\nThermometer:\n");
Serial.print(" Degrees C1 = ");
Serial.println(myIMU.readTempC(), 4);
Serial.print(" Degrees F1 = ");
Serial.println(myIMU.readTempF(), 4);
delay(1000);
}
I used the two examples to build the following code:
#include < bluefruit.h>
#include < Adafruit_LittleFS.h>
#include < InternalFileSystem.h>
#include "LSM6DS3.h"
#include "Wire.h"
// BLE Service
BLEDfu bledfu; // OTA DFU service
BLEDis bledis; // device information
BLEUart bleuart; // uart over ble
BLEBas blebas; // battery
// Create an instance of class LSM6DS3
LSM6DS3 myIMU(I2C_MODE, 0x6A); // I2C device address 0x6A
void setup()
{
Serial.begin(115200);
#if CFG_DEBUG
// Blocking wait for connection when debug mode is enabled via IDE
while (!Serial) yield();
#endif
// Initialize the IMU
if (myIMU.begin() != 0) {
Serial.println("Device error");
} else {
Serial.println("Device OK!");
}
// Setup the BLE LED to be enabled on CONNECT
Bluefruit.autoConnLed(true);
// Config the peripheral connection with maximum bandwidth
Bluefruit.configPrphBandwidth(BANDWIDTH_MAX);
Bluefruit.begin();
Bluefruit.setTxPower(4); // Check bluefruit.h for supported values
Bluefruit.Periph.setConnectCallback(connect_callback);
Bluefruit.Periph.setDisconnectCallback(disconnect_callback);
// To be consistent OTA DFU should be added first if it exists
bledfu.begin();
// Configure and Start Device Information Service
bledis.setManufacturer("Adafruit Industries");
bledis.setModel("Bluefruit Feather52");
bledis.begin();
// Configure and Start BLE Uart Service
bleuart.begin();
// Start BLE Battery Service
blebas.begin();
blebas.write(100);
// Set up and start advertising
startAdv();
}
void startAdv(void)
{
// Advertising packet
Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE);
Bluefruit.Advertising.addTxPower();
// Include bleuart 128-bit uuid
Bluefruit.Advertising.addService(bleuart);
// Secondary Scan Response packet (optional)
Bluefruit.ScanResponse.addName();
/* Start Advertising
* - Enable auto advertising if disconnected
* - Interval: fast mode = 20 ms, slow mode = 152.5 ms
* - Timeout for fast mode is 30 seconds
* - Start(timeout) with timeout = 0 will advertise forever (until connected)
*/
Bluefruit.Advertising.restartOnDisconnect(true);
Bluefruit.Advertising.setInterval(160, 320); // in unit of 0.625 ms
Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode
Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds
}
void loop()
{
// Read accelerometer data
float accelX = myIMU.readFloatAccelX();
float accelY = myIMU.readFloatAccelY();
float accelZ = myIMU.readFloatAccelZ();
// Format the accelerometer data as a string
String accelData = "Accel X: " + String(accelX, 4) + ", Y: " + String(accelY, 4) + ", Z: " + String(accelZ, 4);
// Send the accelerometer data over BLE
if (Bluefruit.connected()) {
bleuart.println(accelData);
}
// Print the accelerometer data to the Serial Monitor
Serial.println(accelData);
// Delay for a second before the next reading
delay(1000);
}
// callback invoked when central connects
void connect_callback(uint16_t conn_handle)
{
delay(500); // Small delay before continuing
// Get the reference to current connection
BLEConnection* connection = Bluefruit.Connection(conn_handle);
char central_name[32] = { 0 };
connection->getPeerName(central_name, sizeof(central_name));
Serial.print("Connected to ");
Serial.println(central_name);
}
/**
* Callback invoked when a connection is dropped
* @param conn_handle connection where this event happens
* @param reason is a BLE_HCI_STATUS_CODE which can be found in ble_hci.h
*/
void disconnect_callback(uint16_t conn_handle, uint8_t reason)
{
(void) conn_handle;
(void) reason;
Serial.println();
Serial.print("Disconnected, reason = 0x"); Serial.println(reason, HEX);
}
I connected the board to my phone using the same app:

Summary
I found that these two options are useful for programming the board but the Arduino IDE used more complex programming configurations that can be useful depending on the use case. I will come down to personal preference on which option you choose. I think I’ll be using CircuitPython to program this XIAO since it’s easier to use and I have used Arduino IDE to program other boards and I want to get used to Python.