-
-
Notifications
You must be signed in to change notification settings - Fork 8.2k
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
Changes from all commits
dd2f9eb
52ee2ef
2df74f5
ebb2d7a
de8bf96
3ef0d24
9072cb0
9b3cee4
9fb3c6e
c578259
bd5b3df
ec4fd3f
b16cf5c
0bd0bd4
f27593e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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. | ||
|
@@ -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) | ||
--no-exclusive Open the serial device shared (not exclusive) access | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here I'm referring to the |
||
-f, --filesystem perform a filesystem action: cp local :device | cp | ||
:device local | cat path | ls [path] | rm path | mkdir | ||
path | rmdir path | ||
|
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" | ||
} |
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 |
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() |
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 |
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([]) |
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" |
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 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
CONFIG_FREERTOS_UNICORE=y |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 )
There was a problem hiding this comment.
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!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 ?