ESP32 Minimum Dev Board#

Intro#

In this chapter we will build a minimal ESP32 development board with SKiDL. We will go through the entire PCB workflow including: circuit creation, PCB generation, part placement, routing, and generating manufacturing files.

Basic Example#

Let’s start by creating a folder called skidl_esp32_example then create a file named skidl_esp32_example.py. In skidl_esp32_example.py we will:

  • Import the SKiDL libraries

  • Create the ESP32 part

  • Generate a netlist

from skidl import *

esp32 = Part('RF_Module','ESP32-WROOM-32', footprint='RF_Module:ESP32-WROOM-32')
generate_pcb()

When you run the code you’ll see a few files created as well as the skidl_esp32_example.kicad_pcb file: esp32 minimal

Open the .kicad_pcb file in KiCAD.

esp32_min_pcb

Peripheral Circuits#

Now let’s add a bit more to this board. We’ll add a few components around the ESP32

  • Capacitors on the power rails

  • Reset button

  • Headers to make all pins available

First, let’s make a bulk and decoupling capacitor. You can read more about why we need these 2 capacitors here

from skidl import *

esp32 = Part('RF_Module','ESP32-WROOM-32', footprint='RF_Module:ESP32-WROOM-32')

c_bulk = Part('Device', 'C', value='10uF', footprint='Capacitor_SMD:C_0603_1608Metric')
c_decoupling = Part('Device', 'C', value='100nF', footprint='Capacitor_SMD:C_0603_1608Metric')

generate_pcb()

Next, we’ll make the power nets V3_3 and GND and attached them to the ESP32 as well as the capacitors:

from skidl import *

esp32 = Part('RF_Module','ESP32-WROOM-32', footprint='RF_Module:ESP32-WROOM-32')

c_bulk = Part('Device', 'C', value='10uF', footprint='Capacitor_SMD:C_0603_1608Metric')
c_decoupling = Part('Device', 'C', value='100nF', footprint='Capacitor_SMD:C_0603_1608Metric')

V3_3 = Net('V3_3')
GND = Net('GND')

V3_3 += esp32['VDD'], c_bulk[1], c_decoupling[1]
GND += esp32['GND'], c_bulk[2], c_decoupling[2]

generate_pcb()

Now we can add a reset button with a pull-up resistor and bypass capacitor circuitry:

from skidl import *

esp32 = Part('RF_Module','ESP32-WROOM-32', footprint='RF_Module:ESP32-WROOM-32')

c_bulk = Part('Device', 'C', value='10uF', footprint='Capacitor_SMD:C_0603_1608Metric')
c_bypass = Part('Device', 'C', value='100nF', footprint='Capacitor_SMD:C_0603_1608Metric')

V3_3 = Net('V3_3')
GND = Net('GND')

V3_3 += esp32['VDD'], c_bulk[1], c_bypass[1]
GND += esp32['GND'], c_bulk[2], c_bypass[2]

# Reset switch circuit
r_en_pullup = Part('Device', 'R', value='10k', footprint='Resistor_SMD:R_0603_1608Metric')
r_en_pullup[1] += esp32['EN']
r_en_pullup[2] += V3_3
c_en_filter = Part('Device', 'C', value='10uF', footprint='Capacitor_SMD:C_0603_1608Metric')
c_en_filter[1] += esp32['EN']
c_en_filter[2] += GND
switch = Part('Switch','SW_DPST', footprint='Button_Switch_THT:SW_Push_2P1T_Toggle_CK_PVA1xxH1xxxxxxV2')
switch[1,2] += esp32['EN']
switch[3,4] += GND

generate_pcb()

Now we’ll use some SKiDL magic and breakout all the ESP32 pins to headers. We’ll make a function breakout_ic() to take in any IC and any header:

from skidl import *

esp32 = Part('RF_Module','ESP32-WROOM-32', footprint='RF_Module:ESP32-WROOM-32')

c_bulk = Part('Device', 'C', value='10uF', footprint='Capacitor_SMD:C_0603_1608Metric')
c_bypass = Part('Device', 'C', value='100nF', footprint='Capacitor_SMD:C_0603_1608Metric')

V3_3 = Net('V3_3')
GND = Net('GND')

V3_3 += esp32['VDD'], c_bulk[1], c_bypass[1]
GND += esp32['GND'], c_bulk[2], c_bypass[2]

# Reset switch circuit
r_en_pullup = Part('Device', 'R', value='10k', footprint='Resistor_SMD:R_0603_1608Metric')
r_en_pullup[1] += esp32['EN']
r_en_pullup[2] += V3_3
c_en_filter = Part('Device', 'C', value='10uF', footprint='Capacitor_SMD:C_0603_1608Metric')
c_en_filter[1] += esp32['EN']
c_en_filter[2] += GND
switch = Part('Switch','SW_DPST', footprint='Button_Switch_THT:SW_Push_2P1T_Toggle_CK_PVA1xxH1xxxxxxV2')
switch[1,2] += esp32['EN']
switch[3,4] += GND

def breakout_ic(ic, header):
    headers=[header()]
    counter = 1
    # Iterate through IC pins in pin order and connect them to a header
    for n in range(1, len(ic.pins) +1): # ic.pins may not be in footprint order
        pin = ic[n]
        headers[-1][counter] += pin
        counter += 1
        # Check if we've reached the end of the header
        if counter > len(header.pins):
            counter = 1
            # Add another copy of the header to the list
            headers.append(header.copy())
            # Disconnect all the pins of the new header
            for i in headers[-1].pins:
                i.disconnect()

header = Part('Connector', 'Conn_01x05_Male', footprint='Connector_PinHeader_2.54mm:PinHeader_1x05_P2.54mm_Vertical', dest=TEMPLATE)

breakout_ic(esp32, header)

generate_pcb()

The parts are placed neatly on the PCB by another tool SKiDL runs in the background call HierPlace.

min_dev_pcb

Feel free to move parts around before routing.

Routing#

To route the board we will use the auto-routing tool freerouting.

  • Create the fill-zone and fill it in

  • Export the pcb file as a Specctra DNS file

  • Import the Specctra DNS file into freerouting

  • Auto-route and export the result as a Specctra Session File

  • Import the Speccta Session File into the pcb editor

auto route

Generate Gerbers#

Lastly, we generate gerber files to send to the board house for manufacturing. generate gerbers

Summary#

In this chapter we walked through the complete workflow to generate a minimal ESP32 development board. We saw how SKiDL completely eliminates the need for schematic and allowed us to perform an interesting trick for breaking out the ESP32 pins.