Skip to content

ports/esp32/boards: Add support for popular ESP32_CAM boards. #13253

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 15 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified docs/esp32/img/esp32.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 9 additions & 1 deletion docs/reference/pyboard.py.rst
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,11 @@ Running ``pyboard.py --help`` gives the following output:
.. code-block:: text

usage: pyboard [-h] [-d DEVICE] [-b BAUDRATE] [-u USER] [-p PASSWORD]
[-c COMMAND] [-w WAIT] [--follow | --no-follow] [-f]
[-c COMMAND] [-w WAIT] [--follow | --no-follow]



[-f]
[files [files ...]]

Run scripts on the pyboard.
Expand All @@ -48,6 +52,10 @@ Running ``pyboard.py --help`` gives the following output:
available
--follow follow the output after running the scripts
[default if no scripts given]
--no-soft-reset Prevent performing a soft reset when connecting to MCU
--hard-reset pulse the MCU reset pin to hard-reset the MCU (if your
serial connection wires RTS to reset pin properly)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This scheme of wiring RTS to a reset pin is only used on a handful of esp32 boards, not any newer esps with built in USB, nor on any other ports.

"properly" doesn't make sense in this help message here.

Also keep in mind that pyboard.py is generally not expected to be used directly by end users any more, mpremote is the standard / promoted tool now.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not any newer esps with built in USB, nor on any other ports.

On that - do you happen to know how any of those other boards reset the ESP32 and assert GPI0 for flashing, without using RST/DSR? (assuming any of them do, and it's not necessary to physically push buttons on them for it). If there's some other trick to hard-reset modern inbuilt-USB boards, it would be worth including that feature (and porting all this to mpremote too... I've not tried it, but I expect mpremote has the same serial issue, ... time passes... yes: /mpremote/transport_serial.py is a copy/paste of pyboard.py )

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most / many other mcu boards just have a separate jumper / button on the board to jump into upgrade mode - every MCU is generally different here.
Some other MCU's with built in USB have either internal mechanisms to run a bootloader.

It's not a standard feature by any account to have a hardware handled reset function over usb,
The esp board (arguably abusing) the flow control pins to do this is the first time I've ever seen this done; and it often causes conflicts where the serial port doesn't work properly on applications that try to enable flow control be default.

Indeed, having the support for this esp RTS/CTS pin control in pyboard/mpremote has broken support for some stm32 boards which enable flow control for reliable serial transfers, I'd personally prefer it was removed entirely, or at least not used by default!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This scheme of wiring RTS to a reset pin is only used on a handful of esp32 boards, not any newer esps with built in USB, nor on any other ports.

Not even all "traditional" ESP boards are the same. Same vendors save a few cent and do not add the two transistor logic to RTS/DTR, but connect Reset & GPIO0 directly to RTS and DTR. That causes problems. Likewise, not all boards with built-in USB switch to boot loader mode when the RTS/DTR toggling is applied. ESP32C3 do, ESP32S3/ESP32S2 do not.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

broken support for some stm32 boards

I own a Roland MDX-20 CNC machine with hardware flow control, so I'm extremely aware of how badly supported this idea has been over the decades (and indeed - a google of stm32 hardware flow control leads with a pile of people having problems...) but I digress.

I've got a huge selection of ESP32 and ESP8266 boards which I test on (and rPI which I own, but haven't tested with yet): I think I should buy a selection of other kinds so I can better check that my "fix" (which is in fact just making linux behave the way windows already does now with serial port RTS pins) has no negative impacts on other architectures/boards... can you point me at the one(s) I need to get (preferably aliexpress if they're there) to add to my collection ?

--no-exclusive Open the serial device shared (not exclusive) access
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should be detailed that this is a POSIX only function (https://pyserial.readthedocs.io/en/latest/pyserial_api.html) so won't work on windows.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure what you mean? The code already has a custom setup for Windows, and it already uses those pins properly in there. I've got no idea why someone deliberately made this work for ESP32/ESP8266 for windows only. My fix makes it work for linux and WSL too.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here I'm referring to the exclusive setting in pyserial. --no-exclusive appears to be not possible on windows.

-f, --filesystem perform a filesystem action: cp local :device | cp
:device local | cat path | ls [path] | rm path | mkdir
path | rmdir path
Expand Down
25 changes: 25 additions & 0 deletions ports/esp32/boards/ESP32_CAM/board.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"deploy": [
"../deploy.md"
],
"docs": "",
"features": [
"External Flash",
"WiFi"
],
"images": [
"esp32_cam.jpg"
],
"mcu": "esp32",
"product": "ESP32 / CAM",
"thumbnail": "",
"url": "https://www.espressif.com/en/products/modules",
"variants": {
"IDF3": "Compiled with IDF 3.x",
"D2WD": "ESP32 D2WD",
"SPIRAM": "Support for SPIRAM / WROVER",
"UNICORE": "ESP32 Unicore",
"OTA": "Support for OTA"
},
"vendor": "Espressif"
}
29 changes: 29 additions & 0 deletions ports/esp32/boards/ESP32_CAM/board.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
The following files are firmware that work on the ESP32 CAM module using
an OV2640 camera.

ESP32 CAM has PSRAM (aka spiram) but the ESP32-S chip on it does not support bluetooth

To build this from source, load your env vars:-

. $HOME/esp/esp-idf/export.sh

Open a shell in the folder micropython/ports/esp32

and run this command:-

git clone https://github.com/lemariva/micropython-camera-driver.git
git clone https://github.com/espressif/esp32-camera
make BOARD=ESP32_CAM submodules
make USER_C_MODULES=../micropython-camera-driver/src/micropython.cmake BOARD=ESP32_CAM all

-or- (for ota support):-

make USER_C_MODULES=../micropython-camera-driver/src/micropython.cmake BOARD=ESP32_CAM MICROPY_BOARD_VARIANT=OTA

then flash is like this:-

esptool.py -p $PORT write_flash --flash_mode dio --flash_size 4MB --flash_freq 40m 0x1000 build-ESP32_CAM/bootloader/bootloader.bin 0x8000 build-ESP32_CAM/partition_table/partition-table.bin 0x10000 build-ESP32_CAM/micropython.bin

Note that these boards wire RTS and DSR to reset and gpio0 pins, so you need the fixed pyboard.py that includes the --hard-reset to talk to them. For example:-

pyboard.py --device $PORT --hard-reset -f ls
44 changes: 44 additions & 0 deletions ports/esp32/boards/ESP32_CAM/mpconfigboard.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
set(SDKCONFIG_DEFAULTS
boards/sdkconfig.base
boards/sdkconfig.spiram
)

# boards/sdkconfig.ble
# boards/ESP32_CAM/sdkconfig.esp32cam

list(APPEND MICROPY_DEF_BOARD
MICROPY_HW_MCU_NAME="ESP32"
# Disable some options to reduce firmware size.
# MICROPY_OPT_COMPUTED_GOTO=0
# MICROPY_PY_NETWORK_LAN=0
# ESP32-CAMERA
CONFIG_OV2640_SUPPORT=y
MICROPY_HW_BOARD_NAME="ESP32S CAM module with SPIRAM and OV2640"
CONFIG_COMPILER_OPTIMIZATION_SIZE=n
CONFIG_COMPILER_OPTIMIZATION_PERF=y
)


if(MICROPY_BOARD_VARIANT STREQUAL "OTA")
set(SDKCONFIG_DEFAULTS
${SDKCONFIG_DEFAULTS}
boards/ESP32_GENERIC/sdkconfig.ota
)

list(APPEND MICROPY_DEF_BOARD
MICROPY_HW_BOARD_NAME="Generic ESP32 module with OTA"
)
endif()



if(MICROPY_BOARD_VARIANT STREQUAL "UNICORE")
set(SDKCONFIG_DEFAULTS
${SDKCONFIG_DEFAULTS}
boards/ESP32_GENERIC/sdkconfig.unicore
)

list(APPEND MICROPY_DEF_BOARD
MICROPY_HW_MCU_NAME="ESP32-UNICORE"
)
endif()
13 changes: 13 additions & 0 deletions ports/esp32/boards/ESP32_CAM/mpconfigboard.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Both of these can be set by mpconfigboard.cmake if a BOARD_VARIANT is
// specified.

#ifndef MICROPY_HW_BOARD_NAME
#define MICROPY_HW_BOARD_NAME "ESP32S CAM module with PSRAM and OV2640"
#endif

#define MICROPY_PY_BLUETOOTH (0)
#define MODULE_CAMERA_ENABLED (1)

#ifndef MICROPY_HW_MCU_NAME
#define MICROPY_HW_MCU_NAME "ESP32"
#endif
171 changes: 171 additions & 0 deletions ports/esp32/boards/ESP32_CAM/photo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
# photo.py

__version__ = "1.0.3" # Major.Minor.Patch

# Ways to run this program:-

# 1. With ampy
# ampy --port $PORT run bin/photo.py
# example output:-
# photo fn=out.jpg size=22((default)) quality=10
# Length of buf: 23579

# 2. From REPL shell
# >>> ARGV=["pic.jpg","5","10"];exec(open("bin/photo.py").read())
# example output:-
# photo fn=pic.jpg size=5(FRAME_QVGA) quality=10
# Length of buf: 9495

# 3. using mipyshell
# To run this program with arguments, install https://github.com/vsolina/mipyshell
# and save this file as bin/photo.py - then (for size 5 and quality 10):-
# photo outfile.jpg 5 10


import camera


def __main__(args):
capture(args[2:]) # mipyshell first 2 arguments are "python" and "photo.py"


def capture(args):
fn = "out.jpg"
quality = 10
size = 22

camera_frames = {
0: {"name": "FRAME_96X96", "value": camera.FRAME_96X96},
1: {"name": "FRAME_QQVGA", "value": camera.FRAME_QQVGA},
2: {"name": "FRAME_QCIF", "value": camera.FRAME_QCIF},
3: {"name": "FRAME_HQVGA", "value": camera.FRAME_HQVGA},
4: {"name": "FRAME_240X240", "value": camera.FRAME_240X240},
5: {"name": "FRAME_QVGA", "value": camera.FRAME_QVGA},
6: {"name": "FRAME_CIF", "value": camera.FRAME_CIF},
7: {"name": "FRAME_HVGA", "value": camera.FRAME_HVGA},
8: {"name": "FRAME_VGA", "value": camera.FRAME_VGA},
9: {"name": "FRAME_SVGA", "value": camera.FRAME_SVGA},
10: {"name": "FRAME_XGA", "value": camera.FRAME_XGA},
11: {"name": "FRAME_HD", "value": camera.FRAME_HD},
12: {"name": "FRAME_SXGA", "value": camera.FRAME_SXGA},
13: {"name": "FRAME_UXGA", "value": camera.FRAME_UXGA},
14: {"name": "FRAME_FHD", "value": camera.FRAME_FHD},
15: {"name": "FRAME_P_HD", "value": camera.FRAME_P_HD},
16: {"name": "FRAME_P_3MP", "value": camera.FRAME_P_3MP},
17: {"name": "FRAME_QXGA", "value": camera.FRAME_QXGA},
18: {"name": "FRAME_QHD", "value": camera.FRAME_QHD},
19: {"name": "FRAME_WQXGA", "value": camera.FRAME_WQXGA},
20: {"name": "FRAME_P_FHD", "value": camera.FRAME_P_FHD},
21: {"name": "FRAME_QSXGA", "value": camera.FRAME_QSXGA},
22: {"name": "(default)", "value": None},
}

if len(args) > 0:
fn = args[0]

## ESP32-CAM (default configuration) - https://bit.ly/2Ndn8tN
camera.init(0, format=camera.JPEG, fb_location=camera.PSRAM)

if len(args) > 1:
size = int(args[1])
camera.framesize(camera_frames[size]["value"])

if len(args) > 2:
quality = int(args[2])
camera.quality(quality)

print(
"photo fn={} size={}({}) quality={}".format(fn, size, camera_frames[size]["name"], quality)
)

# AI-Thinker esp32-cam board
# ai_thinker = {PIN_PWDN:32, PIN_RESET:-1, PIN_XCLK:0, PIN_SIOD:26, PIN_SIOC:27, PIN_D7:35, PIN_D6:34, PIN_D5:39, PIN_D4:36, PIN_D3:21, PIN_D2:19, PIN_D1:18, PIN_D0:5, PIN_VSYNC:25, PIN_HREF:23, PIN_PCLK:22, XCLK_MHZ:16, PIXFORMAT:5, FRAMESIZE:10, JPEG_QUALITY:10, FB_COUNT:1, }

## M5Camera (Version B) - https://bit.ly/317Xb74
# camera.init(0, d0=32, d1=35, d2=34, d3=5, d4=39, d5=18, d6=36, d7=19, format=camera.JPEG, framesize=camera.FRAME_VGA, xclk_freq=camera.XCLK_10MHz, href=26, vsync=25, reset=15, sioc=23, siod=22, xclk=27, pclk=21, fb_location=camera.PSRAM) #M5CAMERA

## T-Camera Mini (green PCB) - https://bit.ly/31H1aaF
# import axp202 # source https://github.com/lewisxhe/AXP202_PythonLibrary
# USB current limit must be disabled (otherwise init fails)
# axp=axp202.PMU( scl=22, sda=21, address=axp202.AXP192_SLAVE_ADDRESS )
# limiting=axp.read_byte( axp202.AXP202_IPS_SET )
# limiting &= 0xfc
# axp.write_byte( axp202.AXP202_IPS_SET, limiting )

# camera.init(0, d0=5, d1=14, d2=4, d3=15, d4=18, d5=23, d6=36, d7=39, format=camera.JPEG, framesize=camera.FRAME_VGA, xclk_freq=camera.XCLK_20MHz, href=25, vsync=27, reset=-1, pwdn=-1, sioc=12, siod=13, xclk=32, pclk=19)

# The parameters: format=camera.JPEG, xclk_freq=camera.XCLK_10MHz are standard for all cameras.
# You can try using a faster xclk (20MHz), this also worked with the esp32-cam and m5camera
# but the image was pixelated and somehow green.

# ## Other settings:
# # flip up side down
# camera.flip(1)
# # left / right
# camera.mirror(1)

# # framesize
# camera.framesize(camera.FRAME_240x240)
# # The options are the following:
# # FRAME_96X96 FRAME_QQVGA FRAME_QCIF FRAME_HQVGA FRAME_240X240
# # FRAME_QVGA FRAME_CIF FRAME_HVGA FRAME_VGA FRAME_SVGA
# # FRAME_XGA FRAME_HD FRAME_SXGA FRAME_UXGA FRAME_FHD
# # FRAME_P_HD FRAME_P_3MP FRAME_QXGA FRAME_QHD FRAME_WQXGA
# # FRAME_P_FHD FRAME_QSXGA
# # Check this link for more information: https://bit.ly/2YOzizz
#
# # special effects
# camera.speffect(camera.EFFECT_NONE)
# # The options are the following:
# # EFFECT_NONE (default) EFFECT_NEG EFFECT_BW EFFECT_RED EFFECT_GREEN EFFECT_BLUE EFFECT_RETRO
#
# # white balance
# camera.whitebalance(camera.WB_NONE)
# # The options are the following:
# # WB_NONE (default) WB_SUNNY WB_CLOUDY WB_OFFICE WB_HOME
#
# # saturation
# camera.saturation(0)
# # -2,2 (default 0). -2 grayscale
#
# # brightness
# camera.brightness(0)
# # -2,2 (default 0). 2 brightness
#
# # contrast
# camera.contrast(0)
# #-2,2 (default 0). 2 highcontrast
#
# # quality
# camera.quality(10)
# # 10-63 lower number means higher quality
#

buf = camera.capture()

if buf:
print("Length of buf:", len(buf))

if fn:
with open(fn, "wb") as f:
f.write(buf)
else:
print("not written - no filename given")
# print("Contents of buf in hex:", buf.hex())

else:
print("Capture failed (too big for PSRAM?")

# print("open http://esp32-cam-05.local/foo.jpg")

camera.deinit()


try:
# if 'ARGV' in locals():
eval(
"capture(ARGV)"
) # ARGV is supplied by caller thusly: ARGV=["pic.jpg","5","10"];exec(open("bin/photo.py").read())
except: # Exception as e:
# print(e) # name 'ARGV' isn't defined
capture([])
17 changes: 17 additions & 0 deletions ports/esp32/boards/ESP32_CAM/sdkconfig.d2wd
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Optimise using -Os to reduce size
CONFIG_COMPILER_OPTIMIZATION_SIZE=y
CONFIG_COMPILER_OPTIMIZATION_PERF=n
CONFIG_COMPILER_OPTIMIZATION_CHECKS_SILENT=y

# Change maximum log level to error, to reduce firmware size.
CONFIG_LOG_MAXIMUM_LEVEL_ERROR=y
CONFIG_LOG_MAXIMUM_LEVEL_INFO=n

# Disable SPI Ethernet driver to reduce firmware size.
CONFIG_ETH_USE_SPI_ETHERNET=n

CONFIG_ESPTOOLPY_FLASHMODE_DIO=y
CONFIG_ESPTOOLPY_FLASHFREQ_40M=y
CONFIG_ESPTOOLPY_FLASHSIZE_2MB=y
CONFIG_PARTITION_TABLE_CUSTOM=y
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions-2MiB.csv"
7 changes: 7 additions & 0 deletions ports/esp32/boards/ESP32_CAM/sdkconfig.ota
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
CONFIG_PARTITION_TABLE_CUSTOM=y
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions-4MiB-ota.csv"

# Reduce firmware size to fit in the OTA partition.
CONFIG_COMPILER_OPTIMIZATION_SIZE=y
CONFIG_COMPILER_OPTIMIZATION_PERF=n
CONFIG_COMPILER_OPTIMIZATION_CHECKS_SILENT=y
1 change: 1 addition & 0 deletions ports/esp32/boards/ESP32_CAM/sdkconfig.unicore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
CONFIG_FREERTOS_UNICORE=y
Loading