Post

ENG | XAIO-nRF52840 (Seeed Studio) and MicroPython

Very simple guide for using MicroPython and XIAO-nRF52840. Display, RTC and buzzer examples.

ENG | XAIO-nRF52840 (Seeed Studio) and MicroPython

This somewhat copies article about [nRF52840 Dongle](This was docummented for a dongle

Download and flash MicroPython firmware

1
2
3
4
5
6
[pavel@marten -=- /home/pavel]$ cd Downloads
[pavel@marten -=- /home/pavel/Downloads]$ wget https://micropython.org/resources/firmware/SEEED_XIAO_NRF52-20250415-v1.25.0.uf2
Saving 'SEEED_XIAO_NRF52-20250415-v1.25.0.uf2'
HTTP response 200  [https://micropython.org/resources/firmware/SEEED_XIAO_NRF52-20250415-v1.25.0.uf2]
SEEED_XIAO_NRF52-202 100% [==============================================================================================================>]  511.00K    --.-KB/s
                          [Files: 1  Bytes: 511.00K [805.99KB/s] Redirects: 0  Todo: 0  Errors: 0

Now press reset button twice to enter bootloader mode and upload firmware

1
[pavel@marten -=- /home/pavel/Downloads]$ cp ~/Downloads/SEEED_XIAO_NRF52-20250415-v1.25.0.uf2 /run/media/pavel/XIAO-SENSE

Test basics

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
[pavel@marten -=- /home/pavel/Downloads]$ mpremote a1
Connected to MicroPython at /dev/ttyACM1
Use Ctrl-] or Ctrl-x to exit this shell
MicroPython v1.25.0 on 2025-04-15; XIAO nRF52840 Sense with NRF52840
Type "help()" for more information.
>>> help()
Welcome to MicroPython!

For online docs please visit http://docs.micropython.org/

Quick overview of commands for the board:
  board.LED(n)    -- create an LED object for LED n (n=1,2,3,4)

If compiled with SD=<softdevice> the additional commands are
available:
  ble.enable()    -- enable bluetooth stack
  ble.disable()   -- disable bluetooth stack
  ble.enabled()   -- check whether bluetooth stack is enabled
  ble.address()   -- return device address as text string

Control commands:
  CTRL-A        -- on a blank line, enter raw REPL mode
  CTRL-B        -- on a blank line, enter normal REPL mode
  CTRL-D        -- on a blank line, do a soft reset of the board
  CTRL-E        -- on a blank line, enter paste mode

For further help on a specific object, type help(obj)
>>> import board
>>> l1=board.LED(1)
>>> l1.on()
>>> l1.off()
>>> l1.on()
>>> l2=board.LED(2)
>>> l2.on()
>>> l3=board.LED(3)
>>> l3.on()
>>> board.LED(4).on()
>>> import sys
>>> sys.platform
'nrf52'
>>> sys.implementation
(name='micropython', version=(1, 25, 0, ''), _machine='XIAO nRF52840 Sense with NRF52840', _mpy=7942, _build='SEEED_XIAO_NRF52')
>>> import time
>>> time.ticks_ms()
306696
LEDNote
1Tiny green
2Red
3Green
4Blue

When you have XIAO Expansion Board, you may try I2C scan:

1
2
3
4
>>> from machine import Pin, I2C
>>> i2c_0 = I2C(0, sda=Pin(4), scl=Pin(5))
>>> i2c_0.scan()
[60, 81]
8bit address7bit addressDevice
0x78 (120)0x3c (60)128x64 0.96” OLED (ZJY-2864KSWPG01) with SSD1306 controller
0xA2 (162)0x51 (81)RTC PCF8563T

MicroPython uses 7bit address, while documentation usually mentions 8bit address (or pair), lowest bits is zero for write and one for read operation.

Test RTC

1
2
3
>>> data = i2c_0.readfrom_mem(81, 0x02, 7)
>>> data
b'\xa6\x88\x80\x8c\xa0\xa7p'

Hmm. We got something.

Test BLE functionality

1
2
3
4
>>> import bluetooth
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ImportError: no module named 'bluetooth'

Actually it has very weird BLE module with no useable functions.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
>>> import ble
>>> help(ble)
object <module 'ble'> is of type module
  __name__ -- ble
  enable -- <function>
  disable -- <function>
  enabled -- <function>
  address -- <function>
>>> print(dir(ble))
['__class__', '__name__', '__dict__', 'address', 'disable', 'enable', 'enabled']
>>> ble.enable()
SoftDevice enabled
>>> ble.address()
'fb:58:6b:52:8b:b7'
>>> ble.enabled()
1

Then, there is weird ubluepy module with basically no documentation. There are like three examples on internet. Good luck using it.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
>>> import ubluepy
>>> print(dir(ubluepy))
['__class__', '__name__', 'Characteristic', 'DefaultDelegate', 'Peripheral', 'ScanEntry', 'Scanner', 'Service', 'UUID', '__dict__', 'constants']
>>> help(ubluepy)
object <module 'ubluepy'> is of type module
  __name__ -- ubluepy
  Peripheral -- <class 'Peripheral'>
  Scanner -- <class 'Scanner'>
  ScanEntry -- <class 'ScanEntry'>
  DefaultDelegate -- <class 'DefaultDelegate'>
  UUID -- <class 'UUID'>
  Service -- <class 'Service'>
  Characteristic -- <class 'Characteristic'>
  constants -- <class 'constants'>
>>> help(ubluepy.Scanner)
object <class 'Scanner'> is of type type
  scan -- <function>
>>> help(ubluepy.Peripheral)
object <class 'Peripheral'> is of type type
  withDelegate -- <function>
  setNotificationHandler -- <function>
  setConnectionHandler -- <function>
  getServices -- <function>
  connect -- <function>
  advertise -- <function>
  advertise_stop -- <function>
  disconnect -- <function>
  addService -- <function>
>>> help(ubluepy.Peripheral.advertise)
object <function> is of type function  

But I found two files on github that are somewhat relevant:

1
2
3
4
5
>>> import ubluepy
>>> p = ubluepy.Peripheral()
>>> p.advertise(device_name="BleTest")
>>> # Beyond this, good luck and have fun!
>>> # I was not even able to put custom data to the advertisment packet.

Uploading program

This was docummented for a dongle

Now, let’s have fun with expansion board!

Buzzer demo

NOTE: port A3/D3 is actually P0.29

1
2
3
4
5
6
>>> buzzer = PWM(Pin(29))
>>> buzzer
<PWM: Pin=29 freq=0Hz duty=0 invert=0 id=1 channel=0>
>>> buzzer.freq(500)
>>> buzzer.duty_u16(30000)
>>> buzzer.deinit()

Display demo

See SSD1306 Tutorial, driver is on github.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[pavel@marten -=- /home/pavel/Downloads]$ wget https://raw.githubusercontent.com/micropython/micropython-lib/refs/heads/master/micropython/drivers/display/ssd1306/ssd1306.py
Saving 'ssd1306.py'
HTTP response 200  [https://raw.githubusercontent.com/micropython/micropython-lib/refs/heads/master/micropython/drivers/display/ssd1306/ssd1306.py]
ssd1306.py           100% [====================================================================>]    1.51K    --.-KB/s
                          [Files: 1  Bytes: 1.51K [5.15KB/s] Redirects: 0  Todo: 0  Errors: 0   ]

[pavel@marten -=- /home/pavel/Downloads]$ ls -la ssd1306.py
-rw-r--r--. 1 pavel pavel 4922 Jun  8 21:35 ssd1306.py
[pavel@marten -=- /home/pavel/Downloads]$ mpremote a1 fs cp ssd1306.py :

cp ssd1306.py :
[pavel@marten -=- /home/pavel/Downloads]$ mpremote a1
Connected to MicroPython at /dev/ttyACM1
Use Ctrl-] or Ctrl-x to exit this shell
>>> from machine import Pin, I2C
>>> import ssd1306
>>> i2c = I2C(0, sda=Pin(4), scl=Pin(5))
>>> oled = ssd1306.SSD1306_I2C(128, 64, i2c, addr=0x3C)
>>> oled.fill(1)
>>> oled.show()
>>> oled.fill(0)
>>> oled.show()
>>> oled.text("Goodnight world!", 0,0)
>>> oled.show()

This should

  • Download display driver wget ...
  • Upload it to development board mpremote a1 fs cp ssd1306.py : where a1 is alias for connect /dev/ttyACM1
  • Run serial console
  • fill screen with white
  • update screen (screen is lit now)
  • … the rest is quite obvious

RTC demo

RTC requires CR1220 3V battery.

Create helper (this command creates file rtc_pcf8563.py)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
cat > rtc_pcf8563.py << EOF
# rtc_pcf8563.py
from machine import I2C

__all__ = ['read_rtc', 'set_rtc', 'init_rtc']

def _bcd_to_dec(bcd):
    return (bcd >> 4) * 10 + (bcd & 0x0f)

def _dec_to_bcd(dec):
    return ((dec // 10) << 4) + (dec % 10)

def init_rtc(i2c_bus, address=0x51):
    global _i2c, _addr
    _i2c = i2c_bus
    _addr = address

def read_rtc():
    data = _i2c.readfrom_mem(_addr, 0x02, 7)
    seconds = _bcd_to_dec(data[0] & 0x7f)
    minutes = _bcd_to_dec(data[1] & 0x7f)
    hours = _bcd_to_dec(data[2] & 0x3f)
    day = _bcd_to_dec(data[3] & 0x3f)
    weekday = data[4] & 0x07
    month = _bcd_to_dec(data[5] & 0x1f)
    year = _bcd_to_dec(data[6]) + 2000
    return (year, month, day, hours, minutes, seconds, weekday)

def set_rtc(year, month, day, hours, minutes, seconds, weekday=0):
    data = bytearray([
        _dec_to_bcd(seconds),
        _dec_to_bcd(minutes),
        _dec_to_bcd(hours),
        _dec_to_bcd(day),
        weekday,
        _dec_to_bcd(month),
        _dec_to_bcd(year - 2000)
    ])
    _i2c.writeto_mem(_addr, 0x02, data)
EOF

Copy it to device

1
mpremote a1 fs cp rtc_pcf8563.py :

Play with it

1
2
3
4
5
6
7
8
9
Connected to MicroPython at /dev/ttyACM1
Use Ctrl-] or Ctrl-x to exit this shell
>>> from machine import Pin, I2C
>>> import rtc_pcf8563
>>> i2c = I2C(0, sda=Pin(4), scl=Pin(5))
>>> rtc_pcf8563.init_rtc(i2c)
>>> rtc_pcf8563.read_rtc()
(2025, 6, 8, 15, 5, 35, 0)
>>> rtc_pcf8563.set_rtc(2025, 6, 8, 14, 30, 0, 0)

NOTES:

  • Weekday 0 is sunday
  • Time was set previously
  • RTC needs CR1220 battery (D=12mm, h=2.0mm) to keep time without USB power.

Conclusion

This device is very nice and I’m happy I bough expansion board.

What I found problematic is ubluepy module which is dead, undocumented and almost impossible to find (try “import ubluepy” filetype:py in google or similar), but similar bluepy exists, so maybe with some guesses and effort it’s possible to use it, but my hopes are low.

I found two blog posts

However, for MicroPython, I’d recommend sticking with Raspberry Pi Pico family, which is very cheap, well documented, well supported, and with large user community. I haven’t tested ESP32 thou. But I’m afraid that combination of nRF52840 and MicroPython does not make sense, as boards are more expensive and selling point is BLE and/or low-power applications. For use with expansion board, XIAO Seeed RP2040 will work as well. This does not mean board is useless, as it can be programmed in C+Zephyr RTOS, where BLE works.

This post is licensed under CC BY 4.0 by the author.