MicroPython Programming Tutorial: Getting Started with the ESP32 Thing

Pages
Contributors: Shawn Hymel
Favorited Favorite 10

Introduction

In this guide, we will walk through the process of setting up MicroPython on the ESP32 Thing and writing some example programs. Each "experiment" will show you how to wire up an example circuit and then control it using MicroPython.

Running MicroPython on the ESP32 Thing

What is MicroPython?

MicroPython is a lean implementation of the Python 3 programming language that has been pared down to run efficiently on microcontrollers. Python is a relatively simple (but powerful) language that is easy for beginners to pick up and has been gaining popularity in schools as an introductory language. MicroPython has nearly all of the features of Python, which means that interacting with hardware is now easily accessible to beginners and seasoned Python programmers alike.

Why the ESP32 Thing?

MicroPython is supported on many different microcontroller platforms, and more are being added all the time. The ESP32 is a great tool for learning MicroPython, as it has a powerful controller (240 MHz) with lots of RAM (520 kB). Additionally, the ESP32 has a built-in WiFi module, which makes networking and connecting to the Internet quite easy. All this is packaged up into a development board for you on SparkFun's ESP32 Thing.

Note: This guide uses MicroPython version: esp32-20180822-v1.9.4-479.

Required Materials

To work through the activities in this tutorial, you will need a few pieces of hardware:

Suggested Reading

If you aren't familiar with the following concepts, we recommend checking out these tutorials before continuing:

What is a Circuit?

Every electrical project starts with a circuit. Don't know what a circuit is? We're here to help.

Voltage, Current, Resistance, and Ohm's Law

Learn about Ohm's Law, one of the most fundamental equations in all electrical engineering.

How to Use a Breadboard

Welcome to the wonderful world of breadboards. Here we will learn what a breadboard is and how to use one to build your very first circuit.

ESP32 Thing Hookup Guide

An introduction to the ESP32 Thing's hardware features, and a primer on using the WiFi system-on-chip in Arduino.
Please note: If you have trouble seeing any of the images throughout this tutorial, feel free to click on it to get a better look!
Python Logo

Hardware Overview

ESP32 Thing Pinout

The ESP32 has a number of features, such as:

One I2C, two of the UART interfaces, and one of the SPI interfaces can be assigned to any pin your project requires. However, there are a few hardware features (namely the ADC and DAC) that are assigned static pins. The graphical reference below helps demonstrate where you can find those peripherals.

ESP32 Thing Graphical Datasheet

Click the image for a closer look.

Input Only Pins: 34-39

Pins 34, 35, 36, 37, 38 and 39 cannot be configured as outputs, but they can be used as either digital inputs, analog inputs, or for other unique purposes. Also note that they do not have internal pull-up or pull-down resistors, like the other I/O pins.

GPIO pins 36-39 are an integral part of the ultra low noise pre-amplifier for the ADC – they are wired up to 270pF capacitors, which help to configure the sampling time and noise of the pre-amp.

Schematic close up of pins 34-39

From the ESP32 Thing Schematic: GPIO 36-39 are tied together with caps. Those and pins 34 and 35 are input only!

ESP32 Thing Board Overview

The ESP32 Thing breaks out most of the pins on the ESP32 chip and gives you access to a few extra features, such as a LiPo battery charger, 4 MB of flash memory, an FTDI USB-to-Serial chip, and a user-controlled LED and button.

Annotated overview of the ESP32 Thing

Powering the ESP32 Thing

The easiest ways to power the ESP32 Thing is through either the onboard USB connector or through the single-cell LiPo battery connector. Note that if a battery and USB are connected at the same time, the USB connection will charge the LiPo at a rate up to 500 mA.

⚡ The ESP32's operating voltage range is 2.2 to 3.6V. Under normal operation the ESP32 Thing will power the chip at 3.3V. The I/O pins are not 5V-tolerant! If you interface the board with 5V (or higher) components, you'll need to do some level shifting.

The 3.3V regulator on the ESP32 Thing can reliably supply up to 600mA, which should be more than enough overhead for most projects. The ESP32 can pull as much as 250mA during RF transmissions, but we've generally measured it to consume around 150mA, even while actively transmitting over WiFi. The output of the regulator is also broken out to the sides of the board (the pins labeled 3V3). These power pins can be used to supply external components.

Overview of the ESP32 Thing's power pins

In addition to USB and battery connectors, the VBAT, and VUSB pins are all broken out to both sides of the board. These pins can be used as an alternative supply input to the ESP32 Thing. The maximum, allowable voltage input to VUSB is 6V, and VBAT should not be connected to anything other than a single-cell LiPo battery. Alternatively, if you have a regulated voltage source between 2.2V and 3.6V, the "3V3" lines can be used to directly supply the ESP32 and its peripherals.

Setup

Serial Terminal Program

To communicate with the Read–Eval–Print Loop (REPL) running on the ESP32, you will need to install a serial terminal program for your particular operating system (OS). The following tutorial makes several good recommendations as to which programs to use for the 3 major operating systems:

Serial Terminal Basics

September 9, 2013

This tutorial will show you how to communicate with your serial devices using a variety of terminal emulator applications.

Text Editor

At the time of this writing, no major integrated development environments (IDEs) support MicroPython for the ESP32 (for example, Mu was developed for MicroPython but does not support the ESP32). As a result, you will need to write code in a raw text editor and then save the files as somename.py. Some recommended text editors that work well for writing code include:

Load MicroPython on the ESP32 Thing

Unfortunately, the ESP32 Thing does not come ready to run MicroPython programs out of the box. To run MicroPython on the ESP32, you will need to first load the interpreter on it. To do that, follow the steps outlined in the How to Load MicroPython on a Microcontroller Board (specifically, the steps found in the ESP32 Thing section).

How to Load MicroPython on a Microcontroller Board

September 4, 2018

This tutorial will show you how to load the MicroPython interpreter onto a variety of development boards.

Install Python on Your Computer

In order to upload MicroPython programs to the ESP32, we will use a tool called ampy, which is a Python script that can read and write files to a MicroPython board over a serial connection. Because ampy is a Python script that runs on a host computer, we will need to install Python.

Instructions for installing Python can be found at python.org. Download and run the installer for your OS, or use your OS's package manager to install Python (if you are on Linux). Ampy will run with Python 2 or Python 3, so choose whichever version you like (we recommend Python 3, as MicroPython is based on Python 3, so the language will be similar).

If you are installing on Windows, make sure the "Add Python <x.x> to PATH" checkbox is selected.

Add Python to PATH Checkbox

For more information on using Python on windows, see this link.

Install ampy

Once you have installed Python, open a terminal for your host computer, and enter the following command:

language:shell
pip install adafruit-ampy

You should see a printout showing that ampy was downloaded and installed.

Installing ampy

REPL (Hello, World!)

One of the easiest ways to interact with MicroPython running on the ESP32 is to enter commands via the REPL over a serial connection. This can be extremely helpful if you want to test out various parts of code before writing them down in a (slightly more permanent) text document that is then uploaded to the ESP32.

To start, connect the ESP32 Thing to your computer using a USB micro-B cable. If you are on Windows, open the Device Manager and find out which COM port your ESP32 Thing is associated with (for example, COM7). On macOS, it will likely be /dev/cu.usbserial-<some letters/numbers>, and on Linux, it’s probably something like /dev/ttyUSB0.

Open your Serial Terminal program of choice and connect to the ESP32 using the following connection details:

  • Speed: 115200 bits per second
  • Data Bits: 8
  • Parity: None
  • Stop Bits: 1

Once you have a connection, press enter once to get a REPL command prompt (>>>).

Getting a REPL command prompt from MicroPython

When you see that command prompt, enter the following commands one at a time:

language:shell
import machine
led = machine.Pin(5, machine.Pin.OUT)
led.value(1)

This should turn on the LED found on the ESP32 Thing (the blue LED located by pin 5).

Blinking the LED on the ESP32 Thing

Type the following command to turn it off:

language:shell
led.value(0)

Turning the ESP32 Thing LED on and off with MicroPython

Code to Note

The first thing we typed was import machine. machine is the MicroPython module that gives us control of the various general purpose input/output (GPIO) pins and special functions, such as I2C and interrupts. We use the keyword import to make all of the functions in the machine module available for us to use.

We then create a machine.Pin object and store it in the label led. When defining a machine.Pin object, we supply a number of arguments, including which pin number we want to use (the pin numbers can be found in white numbers on the side of the ESP32 Thing) and how we want to use that pin (e.g. as an input or output).

Because we set the pin as an output (as given by machine.Pin.OUT), we can drive the pin to logic low (0 V) or logic high (3.3 V) by setting its value to 0 (logic low) or 1 (logic high). To drive the pin high, we write led.value(1). This has the effect of turning on the LED.

Similarly, we can turn the LED off with led.value(0), which sets the pin to 0 V.

Experiment 1: Digital Input and Output

In the REPL section, we saw how to control an LED by individually calling commands in the REPL. This time, we are going to show two examples: how to respond to a button press and how to blink the onboard LED by writing code in a text editor and uploading that file to the ESP32.

There is one minor problem with MicroPython on the ESP32 that we will address in this experiment: if your code includes a forever loop, the MicroPython interpreter will continue to run your code (forever!), which means we never get a REPL prompt. The only way to stop a forever loop like this is to open a serial connection to the ESP32 and send it a ctrl + c command to terminate the running program.

The ampy tool requires REPL to upload new programs, so if the ESP32 is stuck running one of our programs, you will not be able to upload a new program. In the following experiment, we will build a tool that allows us to press the ESP32 Thing's onboard button (labeled 0 on the board) to stop our program and return to REPL.

Hardware Connections

Good news! We can do this experiment with just the ESP32 Thing board by itself.

Plug the ESP32 Thing into your computer via USB cable

Code Part 1: Push Button, Get REPL

In your text editor of choice, enter the following code:

language:python
import machine
import sys
import utime

# Pin definitions
repl_button = machine.Pin(0, machine.Pin.IN, machine.Pin.PULL_UP)

# Wait for button 0 to be pressed, and then exit
while True:

    # If button 0 is pressed, drop to REPL
    if repl_button.value() == 0:
        print("Dropping to REPL")
        sys.exit()

    # Do nothing
    utime.sleep_ms(1)

Save the code with a name like button.py.

Run It:

To upload the program to the ESP32, we first need to rename it to main.py. In most configurations of MicroPython, you will find two files stored on the microcontroller: boot.py and main.py. As soon as the microcontroller receives power (or directly after a reset), the code in boot.py will be run. Then, main.py will run. Note that once main.py finished executing, it will be run again (over and over again forever).

In essence, main.py acts as a forever loop for our code. However, rather than trying to deal with uploading 2 files to the ESP32, we are just going to worry about putting our code in main.py and uploading that.

Open a command terminal on your host computer (not a serial terminal!). Navigate to wherever you stored your button.py file. For example (on Windows):

language:shell
cd Documents\Projects\Python\MicroPython

Then, enter the following two commands to create a copy of your code named main.py and upload it to the ESP32 (change <PORT> to the serial port of your ESP32, such as COM7 for Windows, /dev/cu.usbserial-<some letters/numbers> for macOS, or /dev/ttyUSB0 for Linux):

language:shell
cp button.py main.py
ampy --port <PORT> put main.py

If you are on Windows and your port number is higher than 10, ampy may give you an error message like so: ampy.pyboard.PyboardError: failed to access \\.\COM24. There is a known issue with Python's recognition of Windows ports above 10, but ampy does not take this into account, so we must format our port explicity for Python. Try formatting your COM port as you see it here (replacing <PORT> with your port number/location): \\.\<PORT>

Note: If you are on Windows and your port number is higher than 10, ampy may give you an error message like so: ampy.pyboard.PyboardError: failed to access \\.\COM24. There is a known issue with Python's recognition of Windows ports above 10, but ampy does not take this into account, so we must format our port explicity for Python. Try formatting your COM port as you see it here (replacing <PORT> with your port number/location): \\.\<PORT>

If the file was uploaded, you should not see any error messages from ampy. If you do see some error messages, check out the Troubleshooting section for some tips.

Successful upload of file to ESP32 using ampy

After uploading your code, it should automatically start running. However, because our code simply exits on a button push, there is nothing to see here. Open a serial terminal and create a connection to your ESP32 Thing. You should see a blank terminal window, and even if you try typing some characters, nothing should appear.

Press the 0 button on your ESP32 Thing board.

Pressing button 0 on the ESP32 Thing

As soon as you do, you should see a message appear in your serial terminal saying "Dropping to REPL" followed by the MicroPython version number and a command prompt (>>>). Note that we are using two separate programs here: the command terminal to upload the program and the serial terminal (I'm using PuTTY for my serial terminal) to communicate with REPL.

Serial terminal used to communicate with MicroPython REPL

Code to Note:

Our program is relatively simple. We first import the machine, sys, and utime modules. machine allows us to control connected hardware, like pins. sys gives us access to underlying interpreter values and the ability to exit out of our forever loop in main.py. Finally, utime allows us to get the local machine time (assuming we have a connected real-time clock) or tell our program to wait for a specified amount of time.

Much like in our REPL example, we create a machine.Pin object for pin 0, but we make it an input this time. We supply a third argument: machine.Pin.PULL_UP. This says that we want to enable the ESP32's internal pull-up resistor for pin 0. This allows the voltage on pin 0 to stay at 3.3 V until we push the onboard button, which subsequently pulls the voltage on the pin down to 0 V. We can use this technique to determine if the button has been pressed.

Everything indented under while True: runs forever. Each line is executed sequentially before returning to the top of the while loop.

Within the loop, we check to see if the button has been pressed:

language:python
if repl_button.value() == 0:
    print("Dropping to REPL")
    sys.exit()

If it has, we print a message out to the serial terminal and then call sys.exit(), which raises a SystemExit exception. This will force the program to exit (and the MicroPython interpreter will perform any garbage collection or cleanup as necessary).

If no button push is detected, we tell the ESP32 to do nothing (sleep) for 1 ms:

language:python
utime.sleep_ms(1)

This process is repeated again within the while loop. In future examples, we will continue using this "drop to REPL" button method to help us upload new code.

Note: If you have any problems running this code, refer to the Troubleshooting section near the end of this tutorial.

Code Part 2: Blinky

In a text editor, enter the following code:

language:python
import machine
import sys
import utime

# Pin definitions
repl_button = machine.Pin(0, machine.Pin.IN, machine.Pin.PULL_UP)
led = machine.Pin(5, machine.Pin.OUT)

# Blink forever
while True:

    # If button 0 is pressed, drop to REPL
    if repl_button.value() == 0:
        print("Dropping to REPL")
        sys.exit()

    # Turn LED on and then off
    led.value(1)
    utime.sleep_ms(500)
    led.value(0)
    utime.sleep_ms(500)

Save the code with a name like blink.py

Run It:

Before uploading our code, we need to first stop our first program. Close out of the serial terminal (this is important to free up the serial port) and press button 0 on your ESP32 Thing (this is also important!). Remember: pressing button 0 stops our program and gives us a REPL prompt. ampy will need this to upload a new program.

In a command terminal, copy blink.py and rename it as main.py (don't worry about overwriting your previous main.py, as we have the first example code saved as button.py). Don't forget to change <PORT> to your particular port number/location.

language:shell
cp blink.py main.py
ampy --port <PORT> put main.py

The onboard blue LED should begin blinking on and off every second.

Blinking the LED on the ESP32 Thing board

Open up a serial terminal and connect to your ESP32 Thing. Press and hold button 0 until the blinking LED stops. You should also see a REPL prompt appear.

MicroPython REPL prompt

Code to Note:

This time, we combined parts from our first REPL example and our button example. We added the following part in the while loop:

language:python
led.value(1)
utime.sleep_ms(500)
led.value(0)
utime.sleep_ms(500)

Much like we saw in the REPL example, we turn the LED on with led.value(1) and turn it off with led.value(0). We sleep 500 ms between each action so that we can see the LED blinking.

At the top of our while loop, we added our "drop to REPL" button press code to make debugging and uploading code on the ESP32 easier. Note that you need to hold down button 0 this time, as it can take over 1 second for one iteration of the while loop to complete. Execution needs to return to the beginning of the loop before checking for a button 0 press again.

Experiment 2: Pulse Width Modulation (PWM)

Pulse width modulation is the technique of switching a digital signal on and off very quickly to control a variety of electrical components. By varying the amount of time a signal is on vs. off, you can vary the amount of electrical power provided to a component. We can use this, for example, to dim an LED or control the speed of a motor. To learn more about PWM, see this tutorial.

In this example, we are going to create a simple LED animation that occurs as long as a button is held down.

Hardware Connections

Connect an LED and a button to the ESP32 as per the following diagram:

ESP32 circuit with LED and button Fritzing diagram

⚡ Note: It matters how you plug in your LED! Current can only flow in one direction through an LED, so pay careful attention to the leads. The short lead on the LED should be connected on the same row as the 330Ω resistor.

LED polarity diagram

Note: Buttons can be a little weird, if it's the first time you've used them. The pins across from each other are always connected, whereas the pins on the same side are only connected when you push the button.

How a push button is configured on the inside

Note: If you are using the full-size breadboard, the power rails are divided in the middle. This means that to get power to the whole power row, you will need to connect the two halves. See the picture below to see how to use jumper wires to connect the halves of the power rows.

Connect power rows on a full-size breadboard

Code: LED Animation

In a new file, enter the following code:

language:python
import machine
import sys
import utime

# Pin definitions
repl_button = machine.Pin(0, machine.Pin.IN, machine.Pin.PULL_UP)
repl_led = machine.Pin(5, machine.Pin.OUT)
button = machine.Pin(14, machine.Pin.IN, machine.Pin.PULL_UP)
pwm_pin = machine.Pin(27, machine.Pin.OUT)

# Create a PWM object out of our pin object
pwm = machine.PWM(pwm_pin)

# Slowly fade LED brightness
while True:

    # If button 0 is pressed, turn on LED and drop to REPL
    if repl_button.value() == 0:
        print("Dropping to REPL")
        repl_led.value(1)
        sys.exit()

    # Increase brightness of LED if button is held
    for i in range(1024):
        if button.value() == 0:
            pwm.duty(i)
            utime.sleep_ms(2)
        else:
            pwm.duty(0)

Save the code with a name such as pwm.py. Open a command terminal on your host computer and navigate to the directory where you have pwm.py stored. Push button 0 on your ESP32 to ensure that it is in REPL mode, and enter the following commands into your terminal (don't forget to change <PORT> to your individual port number/location):

language:shell
cp pwm.py main.py
ampy --port <PORT> put main.py

After the code has been uploaded, hold down the button (the one on the breadboard that is connected to pin 14). You should see the LED slowly brighten, turn off, and then begin the brightening animation over again as long as the button is held down.

Using PWM to dim an LED attached to the ESP32 Thing

Code to Note

Much of the code should look familiar from the previous experiment, as we are creating a machine.Pin object for our LED and another one for our button. Note that we are keeping the "drop to REPL" snippet in, as we always want a way to exit out of our code's main loop.

The main difference is that we are creating machine.PWM object out of our machine.Pin object (the pin object that we've created with the pwm_pin variable). With the machine.PWM object, we can call the pwm.duty() method, which allows us to control the amount of on vs. off time on pin 27. A duty cycle of 0 means that the pin is always 0 V. A duty cycle of 511 (about half of 1023) means that pin 27 should rapidly switch between 0 V and 3.3 V equally (50%). Finally, a duty cycle of 1023 means that pin 27 should be always on (always at 3.3 V). Again, refer to the Pulse Width Modulation tutorial for more information on pwm and duty cycles.

By placing pwm.duty() inside a for loop, we can increase the brightness one stage at a time with a 2 ms delay between each stage. This ultimately gives the effect of a slowly increasing LED brightness.

Notice that we check for button 0 at the beginning of the for loop. That means that you need to hold down button 0 until the for loop (LED animation) has completed one iteration before the program realizes you've pressed the button (and should therefore exit to REPL). To help you know when you have successfully exited the program into REPL, we added repl_led.value(1) to turn on the onboard blue LED.

Experiment 3: Analog Input

The ESP32 has a few pins that can be configured as analog-to-digital converters (ADCs). Unfortunately, they are slightly more complicated to use than some other microcontrollers. Most importantly, know that pins 32-39 are supported in MicroPython as ADCs and that we recommend using pins 36-39 for analog input due to the available preamplifier.

This video and this forum post can help you learn more about how the preamp and ADC work on the ESP32.

We are going to add a simple potentiometer to the ESP32 and measure the voltage from it. Note that the on-chip ADC is not the best, as it seems to exhibit some non-linear behavior. If you need a more accurate ADC, we recommend something like this SPI ADC chip.

Hardware Connections

Connect a potentiometer to the ESP32 as per the following diagram:

ESP32 circuit for reading a potentiometer Fritzing diagram

Code: Do the Twist

In a new file, enter the following Python code:

language:python
import machine
import sys
import utime

# Pin definitions
repl_button = machine.Pin(0, machine.Pin.IN, machine.Pin.PULL_UP)
repl_led = machine.Pin(5, machine.Pin.OUT)
adc_pin = machine.Pin(36)

# Create an ADC object out of our pin object
adc = machine.ADC(adc_pin)

# 11 dB attenuation means full 0 - 3.3V range
adc.atten(adc.ATTN_11DB)

# Blink forever
while True:

    # If button 0 is pressed, drop to REPL
    if repl_button.value() == 0:
        print("Dropping to REPL")
        repl_led.value(1)
        sys.exit()

    # Read ADC and convert to voltage
    val = adc.read()
    val = val * (3.3 / 4095)
    print(round(val, 2), "V")

    # Wait a bit before taking another reading
    utime.sleep_ms(100)

Save the code with a name like adc.py. Open a command terminal, and navigate to the directory with your adc.py file. Press button 0 on your ESP32 to drop to REPL, and enter the following commands into your terminal (changing <PORT> to your particular port name/location):

language:shell
cp adc.py main.py
ampy --port <PORT> put main.py

Open a serial terminal connection to the ESP32. You should see voltage readouts being rapidly printed to the screen. Try turning the potentiometer knob to adjust the voltage.

Turning a trimpot connected to the ESP32 Thing

As you turn the knob, the voltages being printed to the screen should update.

Voltages being printed to the serial terminal

Code to Note

Similar to how we created a machine.PWM object in the last section, we create a machine.ADC object here using the pin object we created for pin 36. Because we are using the preamplified ADC pins, we can set the attenuation:

language:python
adc.atten(adc.ATTN_11DB)

Our options for setting attenuation include:

  • ATTN_0DB - no attenuation
  • ATTN_2_5DB - 2.5 dB of attenuation
  • ATTN_6DB - 6 dB of attenuation
  • ATTN_11DB - 11 dB of attenuation

The ESP32 ADC video at 3:57 gives a great overview of the different attenuation options. Generally, you will want to use 11 dB, as that gives you the full range of values from 0 to 3.3 V. 0 dB is great if you only need 0 to 1 V of analog-to-digital conversion (much like how the ADC on the ESP8266 works).

Note that the ADC is not completely accurate. Even in the full 0 to 3.3 V range, many of the values seem to be around 0.2 V off. You can test this with a multimeter to verify.

In our while loop, we get a numerical value from the ADC with adc.read(). This function returns a whole number between 0 and 4095. Because we know that 0 refers to 0 V and 4095 refers to 3.3 V, we can roughly determine our measured voltage with:

language:python
val = val * (3.3 / 4095)

We start with 0...4095 in val and then multiply that value by the quotient of (3.3 / 4095). This results in the decimal point value of temperature (in Celsius), which we store back into the val variable. Decimal point values are stored as floating point numbers, which you can learn more about here.

We print our floating point number to the screen with the following line:

language:python
print(round(val, 2), "V")

round(val, 2) says that we should round our number to two decimal places. The comma (,) followed by another string tells the interpreter that it should print a space followed by our string ("V" in this case) on the same line as our printed voltage value. This allows us to add units to the printout.

Finally, we wait 100 ms before returning to the top of loop, where we check for button 0 press each iteration.

Experiment 4: I2C

Inter-Integrated Circuit (I2C) is a communication protocol designed to allow one "master" chip to communicate with several "device" chips on the same bus. Many modern sensors rely on I2C for communication, as it is a relatively easy protocol to implement and requires only 2 signal lines. If you would like to learn more about how I2C works, see this tutorial.

In this experiment, we are going to connect a TMP102 temperature sensor to the ESP32 Thing and read the ambient temperature.

Hardware Connections

Connect the TMP102 to the ESP32 as follows:

ESP32 and TMP102 I2C connections Fritzing diagram

Code: It's Getting Hot in Here

In a new file, enter the following code:

language:python
import machine
import sys
import utime

###############################################################################
# Parameters and global variables

# Pin definitions
repl_button = machine.Pin(0, machine.Pin.IN, machine.Pin.PULL_UP)
repl_led = machine.Pin(5, machine.Pin.OUT)
sda_pin = machine.Pin(21)
scl_pin = machine.Pin(22)

# Create an I2C object out of our SDA and SCL pin objects
i2c = machine.I2C(sda=sda_pin, scl=scl_pin)

# TMP102 address on the I2C bus
tmp102_addr = 0x48

# TMP102 register addresses
reg_temp = 0x00
reg_config = 0x01

###############################################################################
# Functions

# Calculate the 2's complement of a number
def twos_comp(val, bits):
    if (val & (1 << (bits - 1))) != 0:
        val = val - (1 << bits)
    return val

# Read temperature registers and calculate Celsius
def read_temp():

    # Read temperature registers
    val = i2c.readfrom_mem(tmp102_addr, reg_temp, 2)
    temp_c = (val[0] << 4) | (val[1] >> 5)

    # Convert to 2s complement (temperatures can be negative)
    temp_c = twos_comp(temp_c, 12)

    # Convert registers value to temperature (C)
    temp_c = temp_c * 0.0625

    return temp_c

# Initialize communications with the TMP102
def init():

    # Read CONFIG register (2 bytes) and convert to integer list
    val = i2c.readfrom_mem(tmp102_addr, reg_config, 2)
    val = list(val)

    # Set to 4 Hz sampling (CR1, CR0 = 0b10)
    val[1] = val[1] & 0b00111111
    val[1] = val[1] | (0b10 << 6)

    # Write 4 Hz sampling back to CONFIG
    i2c.writeto_mem(tmp102_addr, reg_config, bytearray(val))

###############################################################################
# Main script

# Print out temperature every second
while True:

    # If button 0 is pressed, drop to REPL
    if repl_button.value() == 0:
        print("Dropping to REPL")
        repl_led.value(1)
        sys.exit()

    # Read temperature and print it to the console
    temperature = read_temp()
    print(round(temperature, 2), "C")
    utime.sleep(1)

Save the file with a name such as i2c.py. In a command terminal, navigate to the directory with your i2c.py file. Hold down button 0 until you see the blue LED turn on to let you know that you have entered the REPL. In the command terminal, enter the following commands (change <PORT> to your port name/location):

language:shell
cp i2c.py main.py
ampy --port <PORT> put main.py

Open a serial connection to the ESP32, and you should see temperature value (in Celsius) being reported once per second.

TMP102 temperature sensor connected to the ESP32 Thing

Try breathing warm air onto the TMP102 sensor to see if you can change the temperature values.

Temperature values from the TMP102 being reported from the ESP32

Code to Note

This code is much longer than our previous examples. This is due to us needing to send configuration information to the TMP102, read temperature, and then perform some calculations on the raw data to get a human-readable temperature value.

Every time we want to communicate with the TMP102, we send out its address (0x48) on the bus. From there, we usually send the memory location (address) of the register that we intend to read from or write to on the TMP102. For most I2C devices, a register is a location in the device's memory that stores 1 byte (8 bits) of data. Some registers control the function of the device (for example, the CONFIG register in the TMP102). Other registers hold sensor data readings (such as the TEMP register in the TMP102) that we must read from.

To communicate on an I2C bus, we create a machine.I2C object, passing it the pins we intend to use for SDA and SCL:

language:python
i2c = machine.I2C(sda=sda_pin, scl=scl_pin)

We use the following command to read 2 bytes from the temperature register in the TMP102:

language:python
val = i2c.readfrom_mem(tmp102_addr, reg_temp, 2)

These values are stored as a list [x, y] in the val variable. By looking at the TMP102 datasheet, we see that temperature is 12 bits. When we read the two bytes that contain this reading, we need to remove the last 4 bits from the second byte. We also move the first byte over 4 bits:

language:python
temp_c = (val[0] << 4) | (val[1] >> 5)

In order to display negative numbers for the temperature, values from the TMP102 come in the form of Two’s Complement. In this, the first bit of the 12-bit number determines if the value is positive or negative (0 for positive, 1 for negative). See this article to learn more about Two’s Complement.

To convert a Two’s Complement number to a negative number in Python, we check to see if the first bit is 0 or 1. If it is 0, then we just use the number as is (it’s positive!). If it’s a 1, we subtract the max negative number of the Two’s Complement (212=4096 in this case) from our number.

language:python
if (val & (1 << (bits - 1))) != 0:
    val = val - (1 << bits)

Finally, we multiply this two's complement number by 0.0625 to convert the raw value into a Celsius value. Refer out Table 5 in the TMP102 datasheet to see how the raw-to-Celsius calculation is performed.

Experiment 5: WiFi

While the possibilities of interacting with hardware directly connected to the ESP32 are endless, some real fun can be had connecting to the Internet. By being able to send and receive data to servers across the Internet, the ESP32 can download current weather or time data, upload sensor data, and respond to events (e.g. on Twitter) in real time. This is just the beginning of exploring the Internet of Things (IoT).

To demonstrate accessing the Internet, we will write a quick MicroPython program that connects to a local WiFi and downloads the example.com web page using an HTTP GET command.

Note: You will need a WiFi access point with connection to the Internet for this exercise.

Hardware Connections

We'll keep it simple this time--just the ESP32 Thing will be needed. We're going to use its built-in WiFi radio, get a web page, and then send the downloaded HTML contents over the serial connection.

ESP32 Thing by itself

Code: GET Some

In a new file, enter the following code (change <YOUR WIFI SSID> to the name of your WiFi network name and <YOUR WIFI PASSWORD> to the name of your WiFi's password--note that the name and password should be in quotation marks ""):

language:python
import machine
import sys
import network
import utime
import urequests

# Pin definitions
repl_button = machine.Pin(0, machine.Pin.IN, machine.Pin.PULL_UP)
repl_led = machine.Pin(5, machine.Pin.OUT)

# Network settings
wifi_ssid = "<YOUR WIFI SSID>"
wifi_password = "<YOUR WIFI PASSWORD>"

# Web page (non-SSL) to get
url = "http://example.com"

# Create a station object to store our connection
station = network.WLAN(network.STA_IF)
station.active(True)

# Continually try to connect to WiFi access point
while not station.isconnected():

    # Try to connect to WiFi access point
    print("Connecting...")
    station.connect(wifi_ssid, wifi_password)

    # Check to see if our REPL button is pressed over 10 seconds
    for i in range(100):
        if repl_button.value() == 0:
            print("Dropping to REPL")
            repl_led.value(1)
            sys.exit()
        utime.sleep_ms(100)

# Continually print out HTML from web page as long as we have a connection
while station.isconnected():

    # Display connection details
    print("Connected!")
    print("My IP Address:", station.ifconfig()[0])

    # Perform HTTP GET request on a non-SSL web
    response = urequests.get(url)

    # Display the contents of the page
    print(response.text)

    # Check to see if our REPL button is pressed over 10 seconds
    for i in range(100):
        if repl_button.value() == 0:
            print("Dropping to REPL")
            repl_led.value(1)
            sys.exit()
        utime.sleep_ms(100)

# If we lose connection, repeat this main.py and retry for a connection
print("Connection lost. Trying again.")

Save the file with a name like wifi.py. In a command terminal, navigate to the directory where wifi.py is stored. Hold down button 0 on the ESP32 Thing to exit its currently running program. Back in the command terminal, enter the following commands (<PORT> should be changed to your serial port name/location):

language:shell
cp wifi.py main.py
ampy --port <PORT> put main.py

Open a serial connection to the ESP32, and you should see your ESP32 connect to your WiFi network and then download the example.com HTML page. If you see text like the one below, you know that it worked:

Printing HTML to the terminal from the ESP32

If you are unable to connect and download the page, double-check the SSID and password in the code. Additionally, some network configurations do not work well with the ESP32, which means you might need to try a different network.

Code to Note

In this example, we're relying on the network and urequests modules to handle much of the networking for us. We use the network module to perform the low-level communication over WiFi, including connecting to our Wifi access point. To create a connection to our WiFi access point, we first create a WLAN object and use network.STA_IF to tell the ESP32 that we want to use it as a client that connects to a separate access point:

language:python
station = network.WLAN(network.STA_IF)
station.active(True)

From there, we attempt to establish a connection with the access point:

language:python
while not station.isconnected():

    # Try to connect to WiFi access point
    print("Connecting...")
    station.connect(wifi_ssid, wifi_password)

The line station.connect(wifi_ssid, wifi_password) is what actually performs the connection. We wrap this up in a while loop that continually checks to see if we have a connection. This way, if no connection is established, the program will not move forward and try to perform a GET request on a website. Notice that we also include our "Drop to REPL" button check in this while loop, too, in case the ESP32 gets stuck here.

Once we have a connection, we use the urequests module to perform our GET request on example.com:

language:python
response = urequests.get(url)

This returns a Response object that contains all the information returned by the server. We can view the response in text form with the command:

language:python
print(response.text)

We then wait for 10 seconds before requesting the page again. During those 10 seconds, we check for a button 0 push every 100 ms.

If you would like to learn more about networking in MicroPython, see the network module documentation.

Troubleshooting

As with all things programming, things have a habit of not working on the first try, and that's OK! Trying to figure out what is going wrong with your code might be a little tricky with MicroPython, but it can be done.

Cannot Upload Program

If you cannot upload your program and get an error such as ampy.pyboard.PyboardError: failed to access COM7 from ampy, it means that another program is using the serial connection to your ESP32. Take a look at all of your open programs and close or disconnect anything that has an open serial connection to your ESP32.

If you are on Windows and your port number is higher than 10, ampy may give you an error message like so: ampy.pyboard.PyboardError: failed to access \\.\COM24. There is a known issue with Python's recognition of Windows ports above 10, but ampy does not take this into account, so we must format our port explicity for Python. Try formatting your COM port as you see it here (replacing <PORT> with your port number/location): \\.\<PORT>

If, on the other hand, ampy gives you an error message like ampy.pyboard.PyboardError: could not enter raw repl, it means that the ESP32 is running a program and not in REPL mode. If you were following this guide exactly, you should be able to press button 0 on the ESP32 Thing to exit your program to REPL. If you forget to add some "Drop to REPL" code in your program, you will need to manually stop your code and remove main.py:

  1. Open a serial terminal program and connect to your ESP32.
  2. In your serial terminal, press ctrl + c to send the SIGINT interrupt signal to the program running on the ESP32. You should see a REPL prompt (>>>) appear.
  3. In REPL, enter the following commands: import uos; uos.remove('main.py'). This will import the uos module, which allows us to manipulate files and then deletes the main.py file, which will prevent our program from running in the future.
  4. Close your serial terminal program and try uploading your main.py again.

Deleting main.py from the ESP32 with MicroPython

Debugging

If you were able to upload your program but nothing runs, you could have a syntax error in your code or a runtime error.

The first step is to use your serial terminal program to connect to your ESP32. Press the reset button (labeled RST on the board). If there are syntax errors in your code, you should see them printed out to the serial terminal.

Syntax error in MicroPython code

In this example, you can see that we wrote "time" instead of "utime" on line 19 of our code. If you see an error message after restarting your ESP32, always read it carefully!

If you do not see any syntax errors, you may have a runtime error (e.g. your code is doing something that you did not intend for it to do). There are many ways to debug runtime errors, but one of the most common methods is to add print() statements in your code to see what is happening at that moment in your program (or to check if your program is getting to that point at all).

Using print statements to debug code in MicroPython

Here, we added a print(i) line to our for loop in the blink.py example to ensure that i counts up to 1023 before resetting back to 0

Resources and Going Further

We hope that this tutorial has given you a starting place for using MicroPython on the ESP32 Thing for both controlling hardware and interacting with other devices across the Internet. Good luck on your next hardware or IoT project! If you would like to dig deeper into MicroPython, we recommend the following resources:

Resource Links:

How-To Links:

Concept Links:

Python Logo

Looking for more MicroPython ideas? Check out these other projects (not necessarily on an ESP32):