Hi there
After a long an arduous journey, I finally managed to get my Freenove ESP32-S3-WROOM CAM Board (Freenove ESP32-S3-WROOM CAM Board (Compatible with Arduino IDE), Onboard Camera Wireless, Python C Code, Detailed Tutorial, Example Projects : Amazon.ca: Electronics) working with PSRAM enabled and no external component required.
This board is an Espressif esp32-s3-wroom-1 N8R8 but PSRAM is octal SPI while flash is quad SPI. To instruct the compiler and platformio to configure this, add the following to the “esphome” section of your config.
esphome:
...
platformio_options:
board_build.arduino.memory_type: qio_opi
Here is a working minimum configuration that should enable PSRAM (as of 2024-12-20)
esphome:
name: esphome-web-1a2b3c
friendly_name: ESP32 S3 Cam 1a2b3c
min_version: 2024.12.0
name_add_mac_suffix: False # this appends to 'name' NOT 'friendly_name'
# arduino.memory.type was key to getting PSRAM working for this board
platformio_options:
board_build.arduino.memory_type: qio_opi
esp32:
board: esp32-s3-devkitc-1
framework:
type: arduino
# Enable logging
logger:
# Enable Home Assistant API
api:
encryption:
key: "{your key}"
ota:
- platform: esphome
password: "{your passwd}"
# Add the credentials to connect to your wifi network
wifi:
id: wifi_component
ssid: !secret wifi_ssid
password: !secret wifi_password
# setup the esp32_camera components initial parameters
# make sure to use the correct GPIO pins for your particular ESP32 model
esp32_camera:
id: espcam
name: esp32-s3-cam
external_clock:
pin: GPIO15
frequency: 20MHz
i2c_pins:
sda: GPIO4
scl: GPIO5
data_pins: [GPIO11, GPIO9, GPIO8, GPIO10, GPIO12, GPIO18, GPIO17, GPIO16]
vsync_pin: GPIO6
href_pin: GPIO7
pixel_clock_pin: GPIO13
#reset_pin: no value for my card
#power_pin: no value for my card
# Image Settings
Here’s the start up log showing that PSRAM is enabled
ESP-ROM:esp32s3-20210327
[15:05:57]Build:Mar 27 2021
[15:05:57]rst:0x15 (USB_UART_CHIP_RESET),boot:0x2b (SPI_FAST_FLASH_BOOT)
[15:05:57]Saved PC:0x42097e3e
[15:05:57]SPIWP:0xee
[15:05:57]mode:DIO, clock div:1
[15:05:57]load:0x3fce3808,len:0x39c
[15:05:57]load:0x403c9700,len:0x9bc
[15:05:57]load:0x403cc700,len:0x28dc
[15:05:57]entry 0x403c98c0
[15:05:57][I][logger:171]: Log initialized
[15:05:57][C][safe_mode:079]: There have been 2 suspected unsuccessful boot attempts
[15:05:57][D][esp32.preferences:114]: Saving 1 preferences to flash...
[15:05:57][D][esp32.preferences:143]: Saving 1 preferences to flash: 0 cached, 1 written, 0 failed
[15:05:57][I][app:029]: Running through setup()...
[15:05:57][C][wifi:048]: Setting up WiFi...
[15:05:57][C][wifi:061]: Starting WiFi...
[15:05:57][C][wifi:062]: Local MAC: [redacted]
[15:05:57][D][wifi:482]: Starting scan...
[15:05:57][W][component:157]: Component wifi set Warning flag: scanning for networks
[15:06:03][D][wifi:497]: Found networks:
[15:06:03][I][wifi:541]: - '[redacted]' [redacted]▂▄▆█
[15:06:03][D][wifi:542]: Channel: 1
[15:06:03][D][wifi:543]: RSSI: -15 dB
[15:06:03][D][wifi:546]: - [redacted] [redacted]▂▄▆█
[15:06:03][D][wifi:546]: - [redacted] [redacted]▂▄▆█
[15:06:03][D][wifi:546]: - [redacted] [redacted]▂▄▆█
[15:06:03][D][wifi:546]: - [redacted] [redacted]▂▄▆█
[15:06:03][D][wifi:546]: - [redacted] [redacted]▂▄▆█
[15:06:03][D][wifi:546]: - [redacted] [redacted]▂▄▆█
[15:06:03][D][wifi:546]: - [redacted] [redacted]▂▄▆█
[15:06:03][D][wifi:546]: - [redacted] [redacted]▂▄▆█
[15:06:03][I][wifi:313]: WiFi Connecting to '[redacted]'...
[15:06:04][I][wifi:617]: WiFi Connected!
[15:06:04][C][wifi:428]: Local MAC: [redacted]
[15:06:04][C][wifi:433]: SSID: [redacted]
[15:06:04][C][wifi:436]: IP Address: [redacted]
[15:06:04][C][wifi:440]: BSSID: [redacted]
[15:06:04][C][wifi:441]: Hostname: 'esphome-web-[redacted]'
[15:06:04][C][wifi:443]: Signal strength: -13 dB ▂▄▆█
[15:06:04][C][wifi:447]: Channel: 1
[15:06:04][C][wifi:448]: Subnet: 255.255.255.0
[15:06:04][C][wifi:449]: Gateway: 192.168.1.254
[15:06:04][C][wifi:450]: DNS1: 192.168.1.254
[15:06:04][C][wifi:451]: DNS2: 0.0.0.0
[15:06:04][C][api:026]: Setting up Home Assistant API server...
[15:06:04][I][app:062]: setup() finished successfully!
[15:06:04][W][component:170]: Component wifi cleared Warning flag
[15:06:04][W][component:157]: Component api set Warning flag: unspecified
[15:06:04][I][app:100]: ESPHome version 2024.12.2 compiled on Dec 22 2024, 14:39:28
[15:06:04][C][wifi:600]: WiFi:
[15:06:04][C][wifi:428]: Local MAC: [redacted]
[15:06:04][C][wifi:433]: SSID: [redacted]
[15:06:04][C][wifi:436]: IP Address: [redacted]
[15:06:04][C][wifi:440]: BSSID: [redacted]
[15:06:04][C][wifi:441]: Hostname: 'esphome-web-[redacted]'
[15:06:04][C][wifi:443]: Signal strength: -12 dB ▂▄▆█
[15:06:04][C][wifi:447]: Channel: 1
[15:06:04][C][wifi:448]: Subnet: 255.255.255.0
[15:06:04][C][wifi:449]: Gateway: 192.168.1.254
[15:06:04][C][wifi:450]: DNS1: 192.168.1.254
[15:06:04][C][wifi:451]: DNS2: 0.0.0.0
[15:06:04][C][logger:185]: Logger:
[15:06:04][C][logger:186]: Level: DEBUG
[15:06:04][C][logger:188]: Log Baud Rate: 115200
[15:06:04][C][logger:189]: Hardware UART: USB_CDC
[15:06:04][C][esp32_camera:048]: ESP32 Camera:
[15:06:04][C][esp32_camera:049]: Name: esp32-s3-cam
[15:06:04][C][esp32_camera:050]: Internal: NO
[15:06:04][C][esp32_camera:052]: Data Pins: D0:11 D1:9 D2:8 D3:10 D4:12 D5:18 D6:17 D7:16
[15:06:04][C][esp32_camera:053]: VSYNC Pin: 6
[15:06:04][C][esp32_camera:054]: HREF Pin: 7
[15:06:04][C][esp32_camera:055]: Pixel Clock Pin: 13
[15:06:04][C][esp32_camera:056]: External Clock: Pin:15 Frequency:20000000
[15:06:04][C][esp32_camera:060]: I2C Pins: SDA:4 SCL:5
[15:06:04][C][esp32_camera:062]: Reset Pin: -1
[15:06:04][C][esp32_camera:080]: Resolution: 640x480 (VGA)
[15:06:04][C][esp32_camera:129]: JPEG Quality: 10
[15:06:04][C][esp32_camera:130]: Framebuffer Count: 1
[15:06:04][C][esp32_camera:131]: Contrast: 0
[15:06:04][C][esp32_camera:132]: Brightness: 0
[15:06:04][C][esp32_camera:133]: Saturation: 0
[15:06:04][C][esp32_camera:134]: Vertical Flip: ON
[15:06:04][C][esp32_camera:135]: Horizontal Mirror: ON
[15:06:04][C][esp32_camera:136]: Special Effect: 0
[15:06:04][C][esp32_camera:137]: White Balance Mode: 0
[15:06:04][C][esp32_camera:140]: Auto Exposure Control: 1
[15:06:04][C][esp32_camera:141]: Auto Exposure Control 2: 0
[15:06:04][C][esp32_camera:142]: Auto Exposure Level: 0
[15:06:04][C][esp32_camera:143]: Auto Exposure Value: 300
[15:06:04][C][esp32_camera:144]: AGC: 1
[15:06:04][C][esp32_camera:145]: AGC Gain: 0
[15:06:04][C][esp32_camera:146]: Gain Ceiling: 0
[15:06:04][C][esp32_camera:152]: Test Pattern: NO
[15:06:04][C][psram:020]: PSRAM:
[15:06:04][C][psram:021]: Available: YES
[15:06:04][C][psram:024]: Size: 8191 KB
[15:06:04][C][mdns:116]: mDNS:
[15:06:04][C][mdns:117]: Hostname: esphome-web-[redacted]
[15:06:04][C][esphome.ota:073]: Over-The-Air updates:
[15:06:04][C][esphome.ota:074]: Address: esphome-web-[redacted].local:3232
[15:06:04][C][esphome.ota:075]: Version: 2
[15:06:04][C][esphome.ota:078]: Password configured
[15:06:04][C][safe_mode:018]: Safe Mode:
[15:06:04][C][safe_mode:020]: Boot considered successful after 60 seconds
[15:06:04][C][safe_mode:021]: Invoke after 10 boot attempts
[15:06:04][C][safe_mode:023]: Remain in safe mode for 300 seconds
[15:06:04][W][safe_mode:029]: Last reset occurred too quickly; safe mode will be invoked in 8 restarts
[15:06:04][C][api:140]: API Server:
[15:06:04][C][api:141]: Address: esphome-web-[redacted].local:6053
[15:06:04][C][api:143]: Using noise encryption: YES
[15:06:07][D][esp32_camera:196]: Got Image: len=8774
[15:06:17][D][esp32_camera:196]: Got Image: len=16170
And, if you want a full blown config that adds most of the switches, sensors and settings for this card, to your HA instance, here’s my full config
substitutions:
name: esphome-web-1a2b3c
friendly_name: ESP32 S3 Cam 1a2b3c
esphome:
name: ${name}
friendly_name: ${friendly_name}
min_version: 2024.12.0
name_add_mac_suffix: False # this appends to 'name' NOT 'friendly_name'
# arduino.memory.type was key to getting PSRAM working for this board
platformio_options:
board_build.arduino.memory_type: qio_opi
# add code to program's loop() function
on_loop:
- lambda: |-
if(id(wifi_component).is_connected()) {
if (id(espcam_update)) {
id(espcam).update_camera_parameters();
if(id(debug)) { id(espcam).dump_config(); }
id(espcam_update) = false;
}
}
esp32:
board: esp32-s3-devkitc-1
framework:
type: arduino
flash_size: 8MB
# Enable logging
logger:
# Enable Home Assistant API
api:
encryption:
key: "{your key}"
ota:
- platform: esphome
password: "{your password}"
# Add the credentials to connect to your wifi network
wifi:
id: wifi_component
ssid: !secret wifi_ssid
password: !secret wifi_password
# If you change the name of the device or esphome is unable to resolve
# its IP address, you can force connection to the actual IP address of
# the card by specifying the address in 'use_address'
#use_address: 192.168.xxx.xxx
#
# Optional manual IP configuration instead of allowing it to be obtained using DHCP
#manual_ip:
# static_ip: 192.168.xxx.xxx
# gateway: 192.168.0.254
# subnet: 255.255.255.0
# Enable fallback hotspot (captive portal) in case wifi connection fails
# ap:
# ssid: "Esphome-Web-1a2b3c"
# password: "{some passwd}"
# this declaration enables the fallback AP declared under wifi section
# if the wifi cannot connect after 1 minute
#captive_portal:
# this enables the ESP Web Server for this device
#web_server:
binary_sensor:
# shows the wifi status of the ESP32 Camera
- platform: status
name: "Status"
sensor:
# will show the Uptime for the card if you're interested
# - platform: uptime
# name: 'Uptime'
# update_interval: 15s
# shows the Wifi signal strength in dB (decibels)
- platform: wifi_signal
name: "WiFi Signal"
update_interval: 60s
button:
# enable a Factory Reset button if you want one
- platform: factory_reset
id: factory_reset_button
name: "Factory reset"
# enable a reboot button
- platform: restart
name: "Restart"
# enable a 'safe mode' reboot button
- platform: safe_mode
name: "Safe Mode"
text_sensor:
# Add text output for Wifi Info
- platform: wifi_info
ip_address:
name: "Wifi Info: IP Address"
ssid:
name: "Wifi Info: SSID"
# bssid:
# name: "Wifi Info: BSSID"
# mac_address:
# name: "Wifi Info: MAC Address"
# dns_address:
# name: "Wifi Info: DNS Address"
globals:
# the debug parameter determines whether a config dump is done after making changes
# this is helpful when doing initial setup, but once everything is working, I turn it off
- id: debug
type: bool
initial_value: 'false'
# value to track if camera parameters have been changed
# used to trigger the loop() code inserted in 'esphome' section above
- id: espcam_update
type: bool
initial_value: 'false'
# setup the esp32_camera components initial parameters
# make sure to use the correct GPIO pins for your particular ESP32 model
esp32_camera:
id: espcam
name: esp32-s3-cam
external_clock:
pin: GPIO15
frequency: 20MHz
i2c_pins:
sda: GPIO4
scl: GPIO5
data_pins: [GPIO11, GPIO9, GPIO8, GPIO10, GPIO12, GPIO18, GPIO17, GPIO16]
vsync_pin: GPIO6
href_pin: GPIO7
pixel_clock_pin: GPIO13
#reset_pin: no value for my card
#power_pin: no value for my card
# Image Settings
# - these first 5 parameters can only be set by recompiling the code and uploading it to the esp32
# - this is NOT a limitation of the esp32_camera component.
# These parameters are used to define the frame_buffer that captures the images.
# And the frame_buffer can't be changed after the setup() function has run.
# 2024-12-20: setup() calls esp_camera_init(), which is defined in
# https://github.com/espressif/esp32-camera/blob/4335c93ec462a379aa2a3d655b8834972d725190/driver/include/esp_camera.h
# the comments at the start of this function's definition clearly state that:
# - it can only be called once
# - it setups the framebuffer (which uses these first 5 parameters)
# - and it cannot be de-initialized once it is set
# Therefore, changing these parameters on the fly will have no effect
# Perhaps this will get changed at some point
resolution: 800x600 # my default
#resolution: 320X240 # for testing, quicker screen reactions
jpeg_quality: 10
# Frame Settings
max_framerate: 10fps
idle_framerate: 0.1fps
frame_buffer_count: 2
# The following parameters can be changed on the fly after the camera is started
# by calling update_camera_parameters() in lambda
vertical_flip: false
horizontal_mirror: true
# contrast, brightness, & saturation seem to have little to no effect when called using update_camera_parameters()
# at least for my camera - esp32-s3-wroom-1 N8R8 from Freenove
contrast: 0
brightness: 0
saturation: 0
special_effect: NONE
# exposure settings
aec_mode: AUTO
aec2: False
ae_level: 0
aec_value: 300
# gain settings
agc_mode: AUTO
agc_gain_ceiling: 2X
agc_value: 0
# white balance setting
wb_mode: AUTO
# test pattern
test_pattern: False
# if you want to stream images use this and add a picture card using this devices IP:Port for the image_url
#esp32_camera_web_server:
# - port: 8080
# mode: stream
# - port: 8081
# mode: snapshot
# create switches for turning a parameter on or off
switch:
# on/off switch for Vertical Flip (my default = off, therefore: RESTORE_DEFAULT_OFF)
- platform: template
name: "Vertical Flip"
id: v_flip
icon: mdi:swap-vertical
restore_mode: RESTORE_DEFAULT_OFF
optimistic: true
turn_on_action:
lambda: |-
id(espcam).set_vertical_flip(true);
id(espcam_update) = true;
turn_off_action:
lambda: |-
id(espcam).set_vertical_flip(false);
id(espcam_update) = true;
# on/off switch for Horizontal Mirror (my default = on, therefore: RESTORE_DEFAULT_ON)
- platform: template
name: "Horizontal Mirror"
id: h_mirror
icon: mdi:reflect-horizontal
restore_mode: RESTORE_DEFAULT_ON
optimistic: true
turn_on_action:
lambda: |-
id(espcam).set_horizontal_mirror(true);
id(espcam_update) = true;
turn_off_action:
lambda: |-
id(espcam).set_horizontal_mirror(false);
id(espcam_update) = true;
# on/off switch for Auto Exposure 2 (my default = off, therefore: RESTORE_DEFAULT_OFF)
- platform: template
name: "Auto Exposure 2"
id: aec_2
icon: mdi:auto-fix
restore_mode: RESTORE_DEFAULT_OFF
optimistic: true
turn_on_action:
lambda: |-
id(espcam).set_aec2(true);
id(espcam_update) = true;
turn_off_action:
lambda: |-
id(espcam).set_aec2(false);
id(espcam_update) = true;
# configure selects (aka dropdowns or option lists) for these parameters to make them available in HA
select:
# while you can change the frame_size (aka resolution) value while the camera is running, it will have no effect
# - see the note in the esp32_camera section above about the first 5 image parameters
# I left this here in case it ever starts working
# - platform: template
# name: "Resolution"
# id: res
# options:
# - "160x120"
# - "176x144"
# - "240x176"
# - "320x240"
# - "400x296"
# - "640x480"
# - "800x600"
# - "1024x768"
# - "1280x1024"
# - "1600x1200"
# initial_option: "800x600"
# optimistic: True
# restore_value: True
# on_value:
# - lambda: |-
# id(espcam).set_frame_size((esphome::esp32_camera::ESP32CameraFrameSize)id(res).active_index().value());
# // some function to change the frame_size
# id(espcam_update) = true;
# Option list for Special Effects
- platform: template
name: "Special Effects"
icon: mdi:filter
id: effect
options:
- "None"
- "Negative"
- "Grayscale"
- "Red Tint"
- "Green Tint"
- "Blue Tint"
- "Sepia"
initial_option: "None"
optimistic: True
on_value:
lambda: |-
id(espcam).set_special_effect((esphome::esp32_camera::ESP32SpecialEffect)id(effect).active_index().value());
id(espcam_update) = true;
# Option List for White Balance
- platform: template
name: "White Balance Mode"
id: wb
icon: mdi:white-balance-auto
options:
- "Auto"
- "Sunny"
- "Cloudy"
- "Office"
- "Home"
initial_option: "Auto"
optimistic: True
on_value:
lambda: |-
id(espcam).set_wb_mode((esphome::esp32_camera::ESP32WhiteBalanceMode)id(wb).active_index().value());
id(espcam_update) = true;
# Option list for AEC mode
- platform: template
name: "Auto Exposure Mode"
id: aec
icon: mdi:auto-fix
options:
- "Manual"
- "Auto"
initial_option: "Auto"
optimistic: True
on_value:
lambda: |-
// if you're looking closely, you may notice that AEC uses the Enum defined for Gain_control.
// this is how it is defined in the component source code since they both only have "auto" & "manual"
id(espcam).set_aec_mode((esphome::esp32_camera::ESP32GainControlMode)id(aec).active_index().value());
id(espcam_update) = true;
# Option list for Gain Control Mode
- platform: template
name: "Gain Control Mode"
id: gc
icon: mdi:auto-fix
options:
- "Manual"
- "Auto"
initial_option: "Auto"
optimistic: True
on_value:
lambda: |-
id(espcam).set_agc_mode((esphome::esp32_camera::ESP32GainControlMode)id(gc).active_index().value());
id(espcam_update) = true;
# Option list for Gain Ceiling (apparently this is somewhat equivilant to ISO settings)
- platform: template
name: "Gain Ceiling"
id: g_ceiling
options:
- "2x"
- "4x"
- "8x"
- "16x"
- "32x"
- "64x"
- "128x"
initial_option: "2x"
optimistic: True
on_value:
lambda: |-
id(espcam).set_agc_gain_ceiling((esphome::esp32_camera::ESP32AgcGainCeiling)id(g_ceiling).active_index().value());
id(espcam_update) = true;
# configure number sliders for these parameters to make them available in HA
number:
# While the sliders for Contrast, Brightness, and Saturation all work and change the parameters of the sensor
# they do not seem to make any difference to the image output
- platform: template
name: "Contrast"
id: contrast
icon: mdi:contrast-box
min_value: -2
max_value: 2
step: 1
initial_value: 0
mode: SLIDER
optimistic: True
on_value:
lambda: |-
id(espcam).set_contrast(id(contrast).state);
id(espcam_update) = true;
# Number Slider for Brightness
- platform: template
name: "Brightness"
id: brightness
icon: mdi:brightness-6
min_value: -2
max_value: 2
step: 1
initial_value: 0
mode: SLIDER
optimistic: True
on_value:
lambda: |-
id(espcam).set_brightness(id(brightness).state);
id(espcam_update) = true;
# Number Slider for Saturation
- platform: template
name: "Saturation"
id: saturation
icon: mdi:palette-outline
min_value: -2
max_value: 2
step: 1
initial_value: 0
mode: SLIDER
optimistic: True
on_value:
lambda: |-
id(espcam).set_saturation(id(saturation).state);
id(espcam_update) = true;
# Number Slider for Gain Value
- platform: template
name: "Gain Value"
id: g_value
min_value: 0
max_value: 30
step: 1
initial_value: 0
mode: SLIDER
optimistic: True
on_value:
lambda: |-
id(espcam).set_agc_value(id(g_value).state);
id(espcam_update) = true;
# Number slider for AE Level
- platform: template
name: "AE Level"
id: ae_level
min_value: -2
max_value: 2
step: 1
initial_value: 0
mode: SLIDER
optimistic: True
on_value:
lambda: |-
id(espcam).set_ae_level(id(ae_level).state);
id(espcam_update) = true;
# Number Slider for AE Value (step set to 10)
# I tried using steps of 1 (too fine to control) and 100 (to coarse for the values) and settled on 10
- platform: template
name: "AE Value"
id: ae_value
min_value: 0
max_value: 1200
step: 10
initial_value: 0
mode: SLIDER
optimistic: True
on_value:
lambda: |-
id(espcam).set_aec_value(id(ae_value).state);
id(espcam_update) = true;
Hope this helps someone out…
Thanks to all the people who have posted about these boards on the forum. A lot of this was trial and error, but some was definitely inspired and learned from other peoples posts.
Cheers