ESP32 Continued#

Now let’s break up the large python script into subcircuits so it’s easier to manage. We’ll make functions that create and connect all of our parts similar to breakout_ic() we learned previously.

Create Subcircuits#

Start by moving the ESP32 to a function, along with some capacitors. In the function arguments we set the default names for power rails to V3_3 and GND:

def esp32_circuit(V3_3=Net('V3_3'), GND=Net('GND')):
    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 += esp32[1], c_bulk[1], c_bypass[1]
    GND += esp32[2], c_bulk[2], c_bypass[2]
    
    return esp32

The reset circuit can be put into a function as well. This circuit only needs the MCU_EN signal, in addition to the assumed V3_3 and GND power rails:

def reset_circuit(MCU_EN, V3_3=Net('V3_3'), GND=Net('GND')):
    r_en_pullup = Part('Device', 'R', value='10k', footprint='Resistor_SMD:R_0603_1608Metric')
    r_en_pullup[1] += MCU_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] += MCU_EN
    c_en_filter[2] += GND
    switch = Part('Switch','SW_DPST', footprint='Button_Switch_THT:SW_Push_2P1T_Toggle_CK_PVA1xxH1xxxxxxV2')
    switch[1,2] += MCU_EN
    switch[3,4] += GND

Part Templates#

In the examples so far we’ve used many resistors and capacitors that share the same symbol and footprint. We can create a part template that can be copied when we need by defining a part and adding dest=TEMPLATE in it’s declaration:

r = Part('Device', 'R', footprint='Resistor_SMD:R_0603_1608Metric', dest=TEMPLATE)
c = Part('Device', 'C', footprint='Capacitor_SMD:C_0603_1608Metric', dest=TEMPLATE)

Use the new functions in the circuits above to reduce the size and complexity of the circuit:

def esp32_circuit(V3_3=Net('V3_3'), GND=Net('GND')):
    esp32 = Part('RF_Module','ESP32-WROOM-32', footprint='RF_Module:ESP32-WROOM-32')
    esp32['VDD'] += V3_3
    esp32['GND'] += GND
    c1 = c(value='10uF')
    c1[1] += V3_3
    c1[2] += GND
    c2 = c(value='100nF')
    c2[1] += V3_3
    c2[2] += GND
    return esp32

def reset_circuit(MCU_EN, V3_3=Net('V3_3'), GND=Net('GND')):
    switch = Part('Switch','SW_DPST', footprint='Button_Switch_THT:SW_Push_2P1T_Toggle_CK_PVA1xxH1xxxxxxV2')
    switch[1,2] += MCU_EN
    switch[3,4] += GND

    c1 = c(value='10uF')
    c1[1] += MCU_EN
    c1[2] += GND

    r1 = r(value='10k')
    r1[1] += MCU_EN
    r1[2] += V3_3

Additional Circuits#

Program circuit#

Now we’ll add a new ciruit which will allow us to program the ESP32:

def esp32_prog(GPIO0, RST, DTR, EN):
    npn1 = Part('Transistor_FET', 'DMN2041L', footprint='Package_TO_SOT_SMD:SOT-23')
    npn1['S'] += DTR
    npn1['D'] += GPIO0
    r1 = r(value='10k')
    r1[1] += npn1['G']
    r1[2] += RST

    npn2 = Part('Transistor_FET', 'DMN2041L', footprint='Package_TO_SOT_SMD:SOT-23')
    npn2['S'] += EN
    npn2['D'] += RST
    r2 = r(value='10k')
    r2[1] += npn2['G']
    r2[2] += DTR

USB Circuit#

We need to be able to talk to the ESP32 over serial for programming and communication. The USB circuit will also be our source of power for the ESP32 board. Add a USB circuit to our board:

def usb_interface(RX, TX, EN, IO0, V3_3=Net('V3_3'), GND=Net('GND')):
    cp2104 = Part('Interface_USB','CP2104', footprint='Package_DFN_QFN:QFN-24-1EP_4x4mm_P0.5mm_EP2.6x2.6mm')
    # Connect power pins
    cp2104['VDD','VIO','REGIN'] += V3_3
    cp2104['GND'] += GND
    # IC capacitors

    c1 = c(value='10uF')
    c1[1] += V3_3
    c1[2] += GND
    c2 = c(value='100nF')
    c2[1] += V3_3
    c2[2] += GND
    # Cap to allow ROM writing
    c3 = c(value='4.7uF')
    c3[1] += cp2104['VPP']
    c3[2] += GND

    # Make mini USB_B connector
    usb_conn = Part('Connector', 'USB_A', footprint='Connector_USB:USB_A_Molex_67643_Horizontal')
    # Connect IC to connecotr
    usb_conn['VBUS'] += cp2104['VBUS']
    usb_conn['D-'] += cp2104['D-']
    usb_conn['D+'] += cp2104['D+']
    # usb_conn['ID'] # not connected
    usb_conn['GND','Shield'] += GND
    
    # uart resistors

    r1 = r(value='470R')
    r1[1] += cp2104['RXD']
    r1[2] += TX

    r2 = r(value='470R')
    r2[1] += cp2104['TXD']
    r2[2] += RX
        
    return usb_conn, cp2104

Voltage regulator#

This circuit will convert the 5v USB supply into the 3.3V to the ESP32.

def linear_regulator(VIN, V3_3=Net('V3_3'), GND=Net('GND')):
    reg = Part('Regulator_Linear', 'NCP1117-3.3_SOT223', footprint='Package_TO_SOT_SMD:SOT-223-3_TabPin2')
    reg['GND'] += GND
    reg['VI'] += VIN
    reg['VO'] += V3_3
    # Bulk caps
    c1 = c(value='10uF')
    c1[1] += VIN
    c1[2] += GND
    c2 = c(value='10uF')
    c2[1] += V3_3
    c2[2] += GND

Final script#

See Code
from skidl import *

r = Part('Device', 'R', footprint='Resistor_SMD:R_0603_1608Metric', dest=TEMPLATE)
c = Part('Device', 'C', footprint='Capacitor_SMD:C_0603_1608Metric', dest=TEMPLATE)

def esp32_circuit(V3_3=Net('V3_3'), GND=Net('GND')):
    esp32 = Part('RF_Module','ESP32-WROOM-32', footprint='RF_Module:ESP32-WROOM-32')
    esp32['VDD'] += V3_3
    esp32['GND'] += GND
    c1 = c(value='10uF')
    c1[1] += V3_3
    c1[2] += GND
    c2 = c(value='100nF')
    c2[1] += V3_3
    c2[2] += GND
    return esp32

def reset_circuit(MCU_EN, V3_3=Net('V3_3'), GND=Net('GND')):
    switch = Part('Switch','SW_DPST', footprint='Button_Switch_THT:SW_Push_2P1T_Toggle_CK_PVA1xxH1xxxxxxV2')
    switch[1,2] += MCU_EN
    switch[3,4] += GND

    c1 = c(value='10uF')
    c1[1] += MCU_EN
    c1[2] += GND

    r1 = r(value='10k')
    r1[1] += MCU_EN
    r1[2] += V3_3
    
def usb_interface(RX, TX, EN, IO0, V3_3=Net('V3_3'), GND=Net('GND')):
    cp2104 = Part('Interface_USB','CP2104', footprint='Package_DFN_QFN:QFN-24-1EP_4x4mm_P0.5mm_EP2.6x2.6mm')
    # Connect power pins
    cp2104['VDD','VIO','REGIN'] += V3_3
    cp2104['GND'] += GND
    # IC capacitors

    c1 = c(value='10uF')
    c1[1] += V3_3
    c1[2] += GND
    c2 = c(value='100nF')
    c2[1] += V3_3
    c2[2] += GND
    # Cap to allow ROM writing
    c3 = c(value='4.7uF')
    c3[1] += cp2104['VPP']
    c3[2] += GND

    # Make mini USB_B connector
    usb_conn = Part('Connector', 'USB_A', footprint='Connector_USB:USB_A_Molex_67643_Horizontal')
    # Connect IC to connecotr
    usb_conn['VBUS'] += cp2104['VBUS']
    usb_conn['D-'] += cp2104['D-']
    usb_conn['D+'] += cp2104['D+']
    # usb_conn['ID'] # not connected
    usb_conn['GND','Shield'] += GND
    
    # uart resistors

    r1 = r(value='470R')
    r1[1] += cp2104['RXD']
    r1[2] += TX

    r2 = r(value='470R')
    r2[1] += cp2104['TXD']
    r2[2] += RX
        
    return usb_conn, cp2104

def esp32_prog(GPIO0, RST, DTR, EN):
    npn1 = Part('Transistor_FET', 'DMN2041L', footprint='Package_TO_SOT_SMD:SOT-23')
    npn1['S'] += DTR
    npn1['D'] += GPIO0
    r1 = r(value='10k')
    r1[1] += npn1['G']
    r1[2] += RST

    npn2 = Part('Transistor_FET', 'DMN2041L', footprint='Package_TO_SOT_SMD:SOT-23')
    npn2['S'] += EN
    npn2['D'] += RST
    r2 = r(value='10k')
    r2[1] += npn2['G']
    r2[2] += DTR

def linear_regulator(VIN, V3_3=Net('V3_3'), GND=Net('GND')):
    reg = Part('Regulator_Linear', 'NCP1117-3.3_SOT223', footprint='Package_TO_SOT_SMD:SOT-223-3_TabPin2')
    reg['GND'] += GND
    reg['VI'] += VIN
    reg['VO'] += V3_3
    # Bulk caps
    c1 = c(value='10uF')
    c1[1] += VIN
    c1[2] += GND
    c2 = c(value='10uF')
    c2[1] += V3_3
    c2[2] += 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()

    
esp32 = esp32_circuit()
reset_circuit(esp32['EN'])
usb_conn, usb_ic = usb_interface(esp32['RXD0/IO3'], esp32['TXD0/IO1'], esp32['EN'], esp32['IO0'])
esp32_prog(esp32['IO0'], esp32['EN'], usb_ic["~{RTS}"], usb_ic["~{DTR}"])
linear_regulator(usb_conn['VBUS'])

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

breakout_ic(esp32, header)

generate_pcb()

We can now import the netlist into KiCAD as we’ve done before and continue on with the board design process.

Summary#

In this chapter we completed the ESP32 dev board using Python functions to wrap individual circuits.