Skip to main content

Seeed Studio XIAO ESP32C3 with MicroPython

MicroPython is a Python interprer with a partial native code compilation feature. It provides a subset of Python 3.5 features, implemented for embedded processors and constrained systems. It is different from CPython and you can read more about the differences here.

Installing MicroPython

Install Esptool

If you haven't already installed esptool.py, you can do so using pip on your pc:

pip install esptool

Download the XIAO ESP32C3 MicroPython firmware

You need to download the firmware binary file from micropython.org After downloading correct bin file, navigate to the folder, and open a cmd terminal there. As of the final draft, the latest version of bin file is:

ESP32_GENERIC_C3-20230602-v1.23.0.bin

Connect the XIAO ESP32C3 on your PC

You need to press and hold down BOOT button on your XIAO ESP32C3 board to enter the 'bootloader' mode while pluging in to the type C USB cable to your pc.

Check port

Find out all serial devices on your pc.

  • Linux

On Linux, you can use the dmesg command to view connected devices:

dmesg | grep tty

Alternatively, you can list serial devices using ls:

ls /dev/ttyS* /dev/ttyUSB*
  • Window

On Windows, you can check serial ports through Device Manager. Look for the “Ports (COM & LPT)” section to see the available serial ports. You can also use the mode command in Command Prompt to list serial ports:

mode
  • macOS

On macOS, you can list available serial ports using the ls command:

ls /dev/cu*

This will show all serial port devices.

tip

If the port is busy, you can use the following command to find an dkill and processes using the port(On macOS): Identify processes using the port:

lsof | grep port

This command lists open files and searches for any process using the specified port. Find the process ID(PID) from the output and kill the procee:

kill -9 <PID>

Replace PID with the actual process ID found.

Erase flash

esptool.py --chip esp32c3 --port /dev/cu.usbmodem11301 erase_flash

Replace '/dev/cu.usbmodem11301' with the correct port name from your system(e.g. COM3 on Windows, /dev/ttyUSB0 on linux).

Write flash

Flash the firmware onto XIAO ESP32C3:

esptool.py --chip esp32c3 --port /dev/cu.usbmodem11301 --baud 460800 write_flash -z 0x0 ESP32_GENERIC_C3-20240602-v1.23.0.bin

Again, replace '/dev/cu.usbmodem11301' with the correct port name, and 'ESP32_GENERIC_C3-20240602-v1.23.0.bin' with the path to your blank firmware file.

Then can start to compile script using your prefer tool to ESP32!

Some of popular tools are listed below.

1. Thonny

Install and open thonny, then configure Thonny following the instruction:

pip install thonny
#open thonny after installation
thonny

Go to Run-->Configure Interpreter, and ensure that the Interpreter tab in the Thonny options looks as shown below, select "CircuitPython (generic)" and port:

Click "OK" on the dialog and you should be presented with the Micropython shell at the bottom of the thonny window as shown in the figure below. Enter scripy line by line to the Shell to get the flash and sram size:

import pc
gc.mem_free()

import esp
esp.flash_size()

Congratulations on successfully setting up MicroPython on your XIAO ESP32C3 with Thonny!

2. Arduino Lab for MicroPython

Download Arduino lab for MicroPython and connect the device to your pc.

Code like this:

from machine import Pin
import time

# Define the LED pin
led = Pin(7, Pin.OUT) # Use the correct GPIO number instead of D10

# Blink the LED in a loop
while True:
led.value(1) # Turn the LED on
time.sleep(1) # Wait for a second
led.value(0) # Turn the LED off
time.sleep(1) # Wait for a second

3. Pymakr on Visual Studio Code

  • Install Pymakr Follow the installation instructions to install Pymakr.
  • Connect Your XIAO ESP32C3 to your computer.
  • Create a New Project Open VS Code and create a new project for your microcontroller.
  • Add a New Python File Create a new Python file within your project.
  • Upload Script to MCU and Compile the Script

4. uPtCraft IDE

Pinout/Port Information

Getting Started with MicroPython on the XIAO ESP32C3

Here is a quick reference for ESP32 operation by micropython. For more knowledge about micropython libraries.

General board control

The MicroPython REPL(Read-Eval-Print-Loop) is on UART0 (GPIO1=TX, GPIO3=RX) at baudrate 115200. Tab-completion is useful to find out what methods an object has. Paste mode (ctrl-E) is useful to paste a large slab of Python code into the REPL. Can use the dir() function in MicroPython(similar in Python) to list the attributes and methods of an object. For example, enter dir(machine) to shell:

The machine module:

import machine
machine.freq() # get the current frequency of the CPU, for esp32c3 is 160000000
machine.freq(160000000) # set the CPU frequency to 160 MHz

The esp module:

import esp

esp.osdebug(None) # turn off vendor O/S debugging messages
esp.osdebug(0) # redirect vendor O/S debugging messages to UART(0)

# low level methods to interact with flash storage
esp.flash_size()
esp.flash_user_start()
esp.flash_erase(sector_no)
esp.flash_write(byte_offset, buffer)
esp.flash_read(byte_offset, buffer)

The esp32 module: ESP32C3, ESP32S2, and ESP32S3 have an internal temperature sensor available and returns the temperature in Celsius:

import esp32
esp32.mcu_temperature() # read the internal temperature of the MCU, in Celsius

Network-WLAN

The Network module: More information refer to here.

import network

wlan = network.WLAN(network.STA_IF) # create station interface
wlan.active(True) # activate the interface
wlan.scan() # scan for access points
wlan.isconnected() # check if the station is connected to an AP
wlan.connect('ssid', 'key') # connect to an AP
wlan.config('mac') # get the interface's MAC address
wlan.ifconfig() # get the interface's IPv4 addresses

ap = network.WLAN(network.AP_IF) # create access-point interface
ap.config(ssid='ESP-AP') # set the SSID of the access point
ap.config(max_clients=10) # set how many clients can connect to the network
ap.active(True) # activate the interface

A useful function for connecting to your local WiFi network is:

def do_connect():
import network
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
if not wlan.isconnected():
print('connecting to network...')
wlan.connect('ssid', 'key') #replace the ssid and key
while not wlan.isconnected():
pass
print('network config:', wlan.ifconfig())

Delay and timing

The time module:

import time

time.sleep(1) # sleep for 1 second
time.sleep_ms(500) # sleep for 500 milliseconds
time.sleep_us(10) # sleep for 10 microseconds
start = time.ticks_ms() # get millisecond counter
delta = time.ticks_diff(time.ticks_ms(), start) # compute time difference

Timers

The ESP32 port has four hardware timers. Use the class with a timer ID from 0 to 3 (inclusive):

from machine import Timer

tim0 = Timer(0)
tim0.init(period=5000, mode=Timer.ONE_SHOT, callback=lambda t:print(0))

tim1 = Timer(1)
tim1.init(period=2000, mode=Timer.PERIODIC, callback=lambda t:print(1))

The period is in milliseconds. Virtual timers are not currently supported on this port.

Pins and GPIO

The machine.Pin class:

from machine import Pin

p2 = Pin(2, Pin.OUT) # create output pin on GPIO2
p2.on() # set pin to "on" (high) level
p2.off() # set pin to "off" (low) level
p2.value(1) # set pin to on/high

p3 = Pin(3, Pin.IN) # create input pin on GPIO3
print(p3.value()) # get value, 0 or 1

p4 = Pin(4, Pin.IN, Pin.PULL_UP) # enable internal pull-up resistor
p5 = Pin(5, Pin.OUT, value=1) # set pin high on creation
p6 = Pin(6, Pin.OUT, drive=Pin.DRIVE_3) # set maximum drive strength

Available Pins are from the following ranges (inclusive): 2,3,4,5,6,7,8,9,10,20,21. These correspond to the actual GPIO pin numbers of ESP32C3 chip.

UART(serial bus)

The machine.UART class:

from machine import UART

uart1 = UART(1, baudrate=9600, tx=21, rx=20)
uart1.write('hello') # write 5 bytes
uart1.read(5) # read up to 5 bytes

The ESP32C3 have one hardware UART. the pins listed below:

UARTPin
TX21
RX20

PWM(pulse width modulation)

PWM can be enabled on all output-enabled pins. The base frequency can range from 1Hz to 40MHz but there is a tradeoff; as the base frequency increases the duty resolution decreases. The machine.PWM class:

from machine import Pin, PWM

pwm2 = PWM(Pin(2), freq=5000, duty_u16=32768) # create PWM object from a pin
freq = pwm2.freq() # get current frequency
pwm2.freq(1000) # set PWM frequency from 1Hz to 40MHz

duty = pwm2.duty() # get current duty cycle, range 0-1023 (default 512, 50%)
pwm2.duty(256) # set duty cycle from 0 to 1023 as a ratio duty/1023, (now 25%)

duty_u16 = pwm2.duty_u16() # get current duty cycle, range 0-65535
pwm2.duty_u16(2**16*3//4) # set duty cycle from 0 to 65535 as a ratio duty_u16/65535, (now 75%)

duty_ns = pwm2.duty_ns() # get current pulse width in ns
pwm2.duty_ns(250_000) # set pulse width in nanoseconds from 0 to 1_000_000_000/freq, (now 25%)

pwm2.deinit() # turn off PWM on the pin

pwm3 = PWM(Pin(3), freq=20000, duty=512) # create and configure in one go
print(pwm3) # view PWM settings

ESP chips have different hardware peripherals:

Hardware SpecificationESP32C3ESP32
Number of groups (speed modes)12
Number of timers per group44
Number of channels per group68
Different PWM frequencies (groups * timers)48
Total PWM channels (Pins, duties) (groups * channels)616

ADC(analog to digital conversion)

On XIAO ESP32C3, ADC functionality is available on pins 2,3,4.

note

A3(GP105) - Uses ADC2, which may become inoperative due to false sampling signals. For analog reads, use ADC1(A0/A1/A2) instead. Refer to the XIAO ESP32C3 datasheet.

The machine.ADC class:

from machine import ADC

adc = ADC(pin) # create an ADC object acting on a pin
val = adc.read_u16() # read a raw analog value in the range 0-65535
val = adc.read_uv() # read an analog value in microvolts

SPI

Software SPI bus

Software SPI (using bit-banging) works on all pins, and is accessed via the machine.SoftSPI class:

from machine import Pin, SoftSPI

# construct a SoftSPI bus on the given pins
# polarity is the idle state of SCK
# phase=0 means sample on the first edge of SCK, phase=1 means the second
spi = SoftSPI(baudrate=100000, polarity=1, phase=0, sck=Pin(2), mosi=Pin(4), miso=Pin(6))

spi.init(baudrate=200000) # set the baudrate

spi.read(10) # read 10 bytes on MISO
spi.read(10, 0xff) # read 10 bytes while outputting 0xff on MOSI

buf = bytearray(50) # create a buffer
spi.readinto(buf) # read into the given buffer (reads 50 bytes in this case)
spi.readinto(buf, 0xff) # read into the given buffer and output 0xff on MOSI

spi.write(b'12345') # write 5 bytes on MOSI

buf = bytearray(4) # create a buffer
spi.write_readinto(b'1234', buf) # write to MOSI and read from MISO into the buffer
spi.write_readinto(buf, buf) # write buf to MOSI and read MISO back into buf

Hardware SPI bus

Hardware SPI is accessed via the machine.SPI class and has the same methods as software SPI above:

from machine import Pin, SPI

hspi = SPI(1, 10000000)
hspi = SPI(1, 10000000, sck=Pin(8), mosi=Pin(10), miso=Pin(9))
SPIPin
SCKD8
MOSID10
MISOD9

I2C

Software I2C bus

Software I2C (using bit-banging) works on all output-capable pins, and is accessed via the machine.SoftI2C class:

from machine import Pin, SoftI2C

i2c = SoftI2C(scl=Pin(7), sda=Pin(6), freq=100000)

i2c.scan() # scan for devices

i2c.readfrom(0x3a, 4) # read 4 bytes from device with address 0x3a
i2c.writeto(0x3a, '12') # write '12' to device with address 0x3a

buf = bytearray(10) # create a buffer with 10 bytes
i2c.writeto(0x3a, buf) # write the given buffer to the peripheral

Hardware I2C bus

The driver is accessed via the machine.I2C class and has the same methods as software I2C above:

from machine import Pin, I2C
i2c = I2C(0, scl=Pin(7), sda=Pin(6), freq=400000)
I2CGPIOPin
SCLGPIO7D5
SDAGPIO6D4

Expension Board Base for XIAO

Prerequisites:

XIAO ESP32C3
with soldered header
Expension Board Base for XIAOGrove Light sensor

Read the light sensor data

import time
from machine import Pin, ADC

# Initialize the analog input on pin 2 (corresponds to A0)
analog_in = ADC(Pin(2))
analog_in.atten(ADC.ATTN_11DB) # Configure the input range (0-3.6V)

def get_voltage(pin):
# Convert the raw ADC value to voltage
return (pin.read() / 4095) * 3.3

while True:
# Read the raw analog value
raw_value = analog_in.read()
# Convert the raw value to voltage
voltage = get_voltage(analog_in)

# Print the raw value and voltage to the serial console
print("[Light] Raw value: {:5d} Voltage: {:.2f}V".format(raw_value, voltage))

# Delay for a short period of time before reading again
time.sleep(1)

Light up OLED screen

Plug in your XIAO ESP32C3, open Thonny and click right bottom to configure interpreter Select interpreter- Micropython (ESP32) and Port >>> Click OK

If everything goes well, you will see the output in the shell Install required libraries Click "Tools" >>> Click "Management Packages" >>> Enter Library's name >>> Click "Search micropython-lib and PyPl"

Run the scrip and Flash it to the board. After you finish coding, click the green button to run the script.

import time
from machine import Pin, SoftI2C
import ssd1306
import math

# Pin assignment
i2c = SoftI2C(scl=Pin(7), sda=Pin(6)) # Adjust the Pin numbers based on your connections
oled_width = 128
oled_height = 64
oled = ssd1306.SSD1306_I2C(oled_width, oled_height, i2c)

oled.fill(0) # Clear the screen
oled.text("Hello, Seeder!", 10, 15)
oled.text("/////", 30, 40)
oled.text("(`3`)y", 30, 55)
oled.show() # Show the text

Thank you for reading this article! Feel free to share your thoughts in the comments.

Resources

Tech Support & Product Discussion

Thank you for choosing our products! We are here to provide you with different support to ensure that your experience with our products is as smooth as possible. We offer several communication channels to cater to different preferences and needs.

Loading Comments...