diff --git a/docs/esp32/img/esp32.jpg b/docs/esp32/img/esp32.jpg index a96ddcbe6a39a..3d361bf07b1e3 100644 Binary files a/docs/esp32/img/esp32.jpg and b/docs/esp32/img/esp32.jpg differ diff --git a/docs/reference/pyboard.py.rst b/docs/reference/pyboard.py.rst index a06ffdcd8fa96..d1d6818ece65d 100644 --- a/docs/reference/pyboard.py.rst +++ b/docs/reference/pyboard.py.rst @@ -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 -f, --filesystem perform a filesystem action: cp local :device | cp :device local | cat path | ls [path] | rm path | mkdir path | rmdir path diff --git a/ports/esp32/boards/ESP32_CAM/board.json b/ports/esp32/boards/ESP32_CAM/board.json new file mode 100644 index 0000000000000..771bddc2fdfe5 --- /dev/null +++ b/ports/esp32/boards/ESP32_CAM/board.json @@ -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" +} diff --git a/ports/esp32/boards/ESP32_CAM/board.md b/ports/esp32/boards/ESP32_CAM/board.md new file mode 100644 index 0000000000000..431c990e33d9a --- /dev/null +++ b/ports/esp32/boards/ESP32_CAM/board.md @@ -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 diff --git a/ports/esp32/boards/ESP32_CAM/mpconfigboard.cmake b/ports/esp32/boards/ESP32_CAM/mpconfigboard.cmake new file mode 100644 index 0000000000000..5ff5fabe996db --- /dev/null +++ b/ports/esp32/boards/ESP32_CAM/mpconfigboard.cmake @@ -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() diff --git a/ports/esp32/boards/ESP32_CAM/mpconfigboard.h b/ports/esp32/boards/ESP32_CAM/mpconfigboard.h new file mode 100644 index 0000000000000..22cf1fcdc2b11 --- /dev/null +++ b/ports/esp32/boards/ESP32_CAM/mpconfigboard.h @@ -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 diff --git a/ports/esp32/boards/ESP32_CAM/photo.py b/ports/esp32/boards/ESP32_CAM/photo.py new file mode 100644 index 0000000000000..d33f82aba097b --- /dev/null +++ b/ports/esp32/boards/ESP32_CAM/photo.py @@ -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([]) diff --git a/ports/esp32/boards/ESP32_CAM/sdkconfig.d2wd b/ports/esp32/boards/ESP32_CAM/sdkconfig.d2wd new file mode 100644 index 0000000000000..2ac983693d3ff --- /dev/null +++ b/ports/esp32/boards/ESP32_CAM/sdkconfig.d2wd @@ -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" diff --git a/ports/esp32/boards/ESP32_CAM/sdkconfig.ota b/ports/esp32/boards/ESP32_CAM/sdkconfig.ota new file mode 100644 index 0000000000000..352dd96f2323f --- /dev/null +++ b/ports/esp32/boards/ESP32_CAM/sdkconfig.ota @@ -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 diff --git a/ports/esp32/boards/ESP32_CAM/sdkconfig.unicore b/ports/esp32/boards/ESP32_CAM/sdkconfig.unicore new file mode 100644 index 0000000000000..f0b0b5e03dd48 --- /dev/null +++ b/ports/esp32/boards/ESP32_CAM/sdkconfig.unicore @@ -0,0 +1 @@ +CONFIG_FREERTOS_UNICORE=y diff --git a/tools/pyboard.py b/tools/pyboard.py index c422b64ac5516..4572d21ebfc76 100755 --- a/tools/pyboard.py +++ b/tools/pyboard.py @@ -265,7 +265,14 @@ def inWaiting(self): class Pyboard: def __init__( - self, device, baudrate=115200, user="micro", password="python", wait=0, exclusive=True + self, + device, + baudrate=115200, + user="micro", + password="python", + wait=0, + exclusive=True, + hard_reset=False, ): self.in_raw_repl = False self.use_raw_paste = True @@ -281,7 +288,7 @@ def __init__( import serial.tools.list_ports # Set options, and exclusive if pyserial supports it - serial_kwargs = {"baudrate": baudrate, "interCharTimeout": 1} + serial_kwargs = {"baudrate": baudrate, "interCharTimeout": 1, "rtscts": 0, "dsrdtr": 0} if serial.__version__ >= "3.3": serial_kwargs["exclusive"] = exclusive @@ -300,7 +307,28 @@ def __init__( self.serial.rts = False # RTS False = EN High = MCU enabled self.serial.open() else: - self.serial = serial.Serial(device, **serial_kwargs) + #self.serial = serial.Serial(device, **serial_kwargs) + #Cannot do serial.Serial(device...) because below is only way to block a high-pulse on rts + self.serial = serial.Serial() + self.serial.dtr = False # DTR False = gpio0 High = Normal boot + self.serial.rts = False # RTS False = EN High = MCU enabled + self.serial.port = device + self.serial.baudrate = serial_kwargs["baudrate"] + self.serial.rtscts = 0 + self.serial.dsrdtr = 0 + self.serial.inter_byte_timeout = serial_kwargs["interCharTimeout"] + self.serial.exclusive = serial_kwargs["exclusive"] + self.serial.open() + + + if hard_reset: + time.sleep(0.2) + # this is reset (setting this "high" resets the MCU) + self.serial.rts = True + time.sleep(0.2) + self.serial.rts = False + # must wait for the reset, otherwise the ctrl-A gets lost + time.sleep(2.0) break except (OSError, IOError): # Py2 and Py3 have different errors if wait == 0: @@ -325,7 +353,10 @@ def read_until(self, min_num_bytes, ending, timeout=10, data_consumer=None): # if data_consumer is used then data is not accumulated and the ending must be 1 byte long assert data_consumer is None or len(ending) == 1 - data = self.serial.read(min_num_bytes) + if min_num_bytes > 0: + data = self.serial.read(min_num_bytes) + else: + data = b"" if data_consumer: data_consumer(data) timeout_count = 0 @@ -356,10 +387,17 @@ def enter_raw_repl(self, soft_reset=True): self.serial.read(n) n = self.serial.inWaiting() - self.serial.write(b"\r\x01") # ctrl-A: enter raw REPL + retry = 10 + while retry > 0: # resend every 1s (sends get lost while resetting) + retry = retry - 1 + self.serial.write(b"\r\x01\x01") # ctrl-A: enter raw REPL (needs 2) + data = self.read_until(0, b"raw REPL; CTRL-B to exit\r\n>", timeout=1) + if data.endswith(b"raw REPL; CTRL-B to exit\r\n>"): + retry = 0 + else: + time.sleep(0.1) if soft_reset: - data = self.read_until(1, b"raw REPL; CTRL-B to exit\r\n>") if not data.endswith(b"raw REPL; CTRL-B to exit\r\n>"): print(data) raise PyboardError("could not enter raw repl") @@ -374,7 +412,7 @@ def enter_raw_repl(self, soft_reset=True): print(data) raise PyboardError("could not enter raw repl") - data = self.read_until(1, b"raw REPL; CTRL-B to exit\r\n") + data = self.read_until(1, b"raw REPL; CTRL-B to exit\r\n", timeout=1) if not data.endswith(b"raw REPL; CTRL-B to exit\r\n"): print(data) raise PyboardError("could not enter raw repl") @@ -783,6 +821,12 @@ def main(): cmd_parser.add_argument("-u", "--user", default="micro", help="the telnet login username") cmd_parser.add_argument("-p", "--password", default="python", help="the telnet login password") cmd_parser.add_argument("-c", "--command", help="program passed in as string") + cmd_parser.add_argument( + "--hard-reset", + action="store_true", + dest="hard_reset", + help="Perform a hard reset when connecting to the board (requires your serial programmer to properly wire RTS pin to reset)", + ) cmd_parser.add_argument( "-w", "--wait", @@ -801,6 +845,7 @@ def main(): "--no-soft-reset", action="store_false", dest="soft_reset", + help="Whether to perform a soft reset when connecting to the board [default]", ) group = cmd_parser.add_mutually_exclusive_group() group.add_argument( @@ -839,7 +884,13 @@ def main(): # open the connection to the pyboard try: pyb = Pyboard( - args.device, args.baudrate, args.user, args.password, args.wait, args.exclusive + args.device, + args.baudrate, + args.user, + args.password, + args.wait, + args.exclusive, + hard_reset=args.hard_reset, ) except PyboardError as er: print(er) pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy