├── .cargo └── config.toml ├── .gitignore ├── .gitmodules ├── Cargo.toml ├── LICENSE ├── README.md ├── bindings.h ├── sdkconfig.defaults └── src └── lib.rs /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | # Uncomment the relevant target for your chip here (ESP32, ESP32-S2, ESP32-S3 or ESP32-C3) 3 | target = "xtensa-esp32-espidf" 4 | #target = "xtensa-esp32s2-espidf" 5 | #target = "xtensa-esp32s3-espidf" 6 | #target = "riscv32imc-esp-espidf" 7 | 8 | [target.xtensa-esp32-espidf] 9 | linker = "ldproxy" 10 | # Uncomment for ESP-IDF 5 and later. Don't forget to also uncomment the `ESP_IDF_VERSION = "release/v5.0"`parameter in the `[env]` section below 11 | #rustflags = ["--cfg", "espidf_time64"] 12 | 13 | [target.xtensa-esp32s2-espidf] 14 | linker = "ldproxy" 15 | # Uncomment for ESP-IDF 5 and later. Don't forget to also uncomment the `ESP_IDF_VERSION = "release/v5.0"`parameter in the `[env]` section below 16 | #rustflags = ["--cfg", "espidf_time64"] 17 | 18 | [target.xtensa-esp32s3-espidf] 19 | linker = "ldproxy" 20 | # Uncomment for ESP-IDF 5 and later. Don't forget to also uncomment the `ESP_IDF_VERSION = "release/v5.0"`parameter in the `[env]` section below 21 | #rustflags = ["--cfg", "espidf_time64"] 22 | 23 | [target.riscv32imc-esp-espidf] 24 | linker = "ldproxy" 25 | # Future - necessary for the experimental "native build" of esp-idf-sys with ESP32C3 26 | # See also https://github.com/ivmarkov/embuild/issues/16 27 | rustflags = ["-C", "default-linker-libraries"] 28 | # Uncomment for ESP-IDF 5 and later. Don't forget to also uncomment the `ESP_IDF_VERSION = "release/v5.0"`parameter in the `[env]` section below 29 | #rustflags = ["--cfg", "espidf_time64", "-C", "default-linker-libraries"] 30 | 31 | [unstable] 32 | build-std = ["std", "panic_abort"] 33 | #build-std-features = ["panic_immediate_abort"] # Only necessary if building against ESP-IDF tag `v4.3.2` (the minimum supported version) 34 | 35 | [env] 36 | # Select ESP IDF version in embuild's format described here: 37 | # https://github.com/esp-rs/esp-idf-sys/blob/master/README.md#esp_idf_version-esp_idf_version-native-builder-only 38 | # 39 | # Uncomment this to build against ESP-IDF master (currently unreleased ESP IDF 5.1) 40 | #ESP_IDF_VERSION = "master" 41 | # Don't forget to uncomment also the `rustflags` parameter in your "target" section above 42 | # 43 | # Uncomment this to build against ESP-IDF 5.0 44 | # Don't forget to uncomment also the `rustflags` parameter in your "target" section above 45 | #ESP_IDF_VERSION = "release/v5.0" 46 | # 47 | # Comment out this when using the PlatformIO build, i.e. `cargo build --features pio` (it only supports `v4.3.2`) 48 | ESP_IDF_VERSION = "release/v4.4" 49 | 50 | # These configurations will pick up your custom "sdkconfig.release", "sdkconfig.debug" or "sdkconfig.defaults[.*]" files 51 | # that you might put in the root of the project 52 | # The easiest way to generate a full "sdkconfig" configuration (as opposed to manually enabling only the necessary flags via "sdkconfig.defaults[.*]" 53 | # is by running "cargo pio espidf menuconfig" (that is, if using the pio builder) 54 | #ESP_IDF_SDKCONFIG = "sdkconfig.release;sdkconfig.debug" 55 | ESP_IDF_SDKCONFIG_DEFAULTS = "sdkconfig.defaults;sdkconfig.defaults.esp32;sdkconfig.defaults.esp32s2" -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /Cargo.lock 3 | /.embuild -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "esp32-camera"] 2 | path = esp32-camera 3 | url = https://github.com/espressif/esp32-camera.git 4 | branch = master 5 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "esp-camera-rs" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | esp-idf-hal = "0.40.1" 10 | esp-idf-sys = "0.32.1" 11 | 12 | [patch.crates-io] 13 | esp-idf-sys = {git = "https://github.com/esp-rs/esp-idf-sys", branch = "master"} 14 | 15 | [[package.metadata.esp-idf-sys.extra_components]] 16 | component_dirs = "esp32-camera" 17 | bindings_header = "bindings.h" 18 | bindings_module = "camera" 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 jlocash 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # esp-camera-rs 2 | Rust wrapper library to interface with the ESP32 camera 3 | -------------------------------------------------------------------------------- /bindings.h: -------------------------------------------------------------------------------- 1 | #include "esp_camera.h" 2 | -------------------------------------------------------------------------------- /sdkconfig.defaults: -------------------------------------------------------------------------------- 1 | CONFIG_ESP32_SPIRAM_SUPPORT=y -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | 3 | use esp_idf_hal::gpio::*; 4 | use esp_idf_hal::peripheral::Peripheral; 5 | use esp_idf_sys::*; 6 | 7 | pub struct FrameBuffer<'a> { 8 | fb: *mut camera::camera_fb_t, 9 | _p: PhantomData<&'a camera::camera_fb_t>, 10 | } 11 | 12 | impl<'a> FrameBuffer<'a> { 13 | pub fn data(&self) -> &'a [u8] { 14 | unsafe { std::slice::from_raw_parts((*self.fb).buf, (*self.fb).len) } 15 | } 16 | 17 | pub fn width(&self) -> usize { 18 | unsafe { (*self.fb).width } 19 | } 20 | 21 | pub fn height(&self) -> usize { 22 | unsafe { (*self.fb).height } 23 | } 24 | 25 | pub fn format(&self) -> camera::pixformat_t { 26 | unsafe { (*self.fb).format } 27 | } 28 | 29 | pub fn timestamp(&self) -> camera::timeval { 30 | unsafe { (*self.fb).timestamp } 31 | } 32 | } 33 | 34 | pub struct CameraSensor<'a> { 35 | sensor: *mut camera::sensor_t, 36 | _p: PhantomData<&'a camera::sensor_t>, 37 | } 38 | 39 | impl<'a> CameraSensor<'a> { 40 | pub fn init_status(&self) -> Result<(), EspError> { 41 | esp!(unsafe { (*self.sensor).init_status.unwrap()(self.sensor) }) 42 | } 43 | pub fn reset(&self) -> Result<(), EspError> { 44 | esp!(unsafe { (*self.sensor).reset.unwrap()(self.sensor) }) 45 | } 46 | pub fn set_pixformat(&self, format: camera::pixformat_t) -> Result<(), EspError> { 47 | esp!(unsafe { (*self.sensor).set_pixformat.unwrap()(self.sensor, format) }) 48 | } 49 | pub fn set_framesize(&self, framesize: camera::framesize_t) -> Result<(), EspError> { 50 | esp!(unsafe { (*self.sensor).set_framesize.unwrap()(self.sensor, framesize) }) 51 | } 52 | pub fn set_contrast(&self, level: i32) -> Result<(), EspError> { 53 | esp!(unsafe { (*self.sensor).set_contrast.unwrap()(self.sensor, level) }) 54 | } 55 | pub fn set_brightness(&self, level: i32) -> Result<(), EspError> { 56 | esp!(unsafe { (*self.sensor).set_brightness.unwrap()(self.sensor, level) }) 57 | } 58 | pub fn set_saturation(&self, level: i32) -> Result<(), EspError> { 59 | esp!(unsafe { (*self.sensor).set_saturation.unwrap()(self.sensor, level) }) 60 | } 61 | pub fn set_sharpness(&self, level: i32) -> Result<(), EspError> { 62 | esp!(unsafe { (*self.sensor).set_sharpness.unwrap()(self.sensor, level) }) 63 | } 64 | pub fn set_denoise(&self, level: i32) -> Result<(), EspError> { 65 | esp!(unsafe { (*self.sensor).set_denoise.unwrap()(self.sensor, level) }) 66 | } 67 | pub fn set_gainceiling(&self, gainceiling: camera::gainceiling_t) -> Result<(), EspError> { 68 | esp!(unsafe { (*self.sensor).set_gainceiling.unwrap()(self.sensor, gainceiling) }) 69 | } 70 | pub fn set_quality(&self, quality: i32) -> Result<(), EspError> { 71 | esp!(unsafe { (*self.sensor).set_quality.unwrap()(self.sensor, quality) }) 72 | } 73 | pub fn set_colorbar(&self, enable: bool) -> Result<(), EspError> { 74 | esp!(unsafe { 75 | (*self.sensor).set_colorbar.unwrap()(self.sensor, if enable { 1 } else { 0 }) 76 | }) 77 | } 78 | pub fn set_whitebal(&self, enable: bool) -> Result<(), EspError> { 79 | esp!(unsafe { 80 | (*self.sensor).set_whitebal.unwrap()(self.sensor, if enable { 1 } else { 0 }) 81 | }) 82 | } 83 | pub fn set_gain_ctrl(&self, enable: bool) -> Result<(), EspError> { 84 | esp!(unsafe { 85 | (*self.sensor).set_gain_ctrl.unwrap()(self.sensor, if enable { 1 } else { 0 }) 86 | }) 87 | } 88 | pub fn set_exposure_ctrl(&self, enable: bool) -> Result<(), EspError> { 89 | esp!(unsafe { 90 | (*self.sensor).set_exposure_ctrl.unwrap()(self.sensor, if enable { 1 } else { 0 }) 91 | }) 92 | } 93 | pub fn set_hmirror(&self, enable: bool) -> Result<(), EspError> { 94 | esp!(unsafe { 95 | (*self.sensor).set_hmirror.unwrap()(self.sensor, if enable { 1 } else { 0 }) 96 | }) 97 | } 98 | pub fn set_vflip(&self, enable: bool) -> Result<(), EspError> { 99 | esp!(unsafe { (*self.sensor).set_vflip.unwrap()(self.sensor, if enable { 1 } else { 0 }) }) 100 | } 101 | pub fn set_aec2(&self, enable: bool) -> Result<(), EspError> { 102 | esp!(unsafe { (*self.sensor).set_aec2.unwrap()(self.sensor, if enable { 1 } else { 0 }) }) 103 | } 104 | pub fn set_awb_gain(&self, enable: bool) -> Result<(), EspError> { 105 | esp!(unsafe { 106 | (*self.sensor).set_awb_gain.unwrap()(self.sensor, if enable { 1 } else { 0 }) 107 | }) 108 | } 109 | pub fn set_agc_gain(&self, gain: i32) -> Result<(), EspError> { 110 | esp!(unsafe { (*self.sensor).set_agc_gain.unwrap()(self.sensor, gain) }) 111 | } 112 | pub fn set_aec_value(&self, gain: i32) -> Result<(), EspError> { 113 | esp!(unsafe { (*self.sensor).set_aec_value.unwrap()(self.sensor, gain) }) 114 | } 115 | pub fn set_special_effect(&self, effect: i32) -> Result<(), EspError> { 116 | esp!(unsafe { (*self.sensor).set_special_effect.unwrap()(self.sensor, effect) }) 117 | } 118 | pub fn set_wb_mode(&self, mode: i32) -> Result<(), EspError> { 119 | esp!(unsafe { (*self.sensor).set_wb_mode.unwrap()(self.sensor, mode) }) 120 | } 121 | pub fn set_ae_level(&self, level: i32) -> Result<(), EspError> { 122 | esp!(unsafe { (*self.sensor).set_ae_level.unwrap()(self.sensor, level) }) 123 | } 124 | pub fn set_dcw(&self, enable: bool) -> Result<(), EspError> { 125 | esp!(unsafe { (*self.sensor).set_dcw.unwrap()(self.sensor, if enable { 1 } else { 0 }) }) 126 | } 127 | pub fn set_bpc(&self, enable: bool) -> Result<(), EspError> { 128 | esp!(unsafe { (*self.sensor).set_bpc.unwrap()(self.sensor, if enable { 1 } else { 0 }) }) 129 | } 130 | pub fn set_wpc(&self, enable: bool) -> Result<(), EspError> { 131 | esp!(unsafe { (*self.sensor).set_wpc.unwrap()(self.sensor, if enable { 1 } else { 0 }) }) 132 | } 133 | pub fn set_raw_gma(&self, enable: bool) -> Result<(), EspError> { 134 | esp!(unsafe { 135 | (*self.sensor).set_raw_gma.unwrap()(self.sensor, if enable { 1 } else { 0 }) 136 | }) 137 | } 138 | pub fn set_lenc(&self, enable: bool) -> Result<(), EspError> { 139 | esp!(unsafe { (*self.sensor).set_lenc.unwrap()(self.sensor, if enable { 1 } else { 0 }) }) 140 | } 141 | pub fn get_reg(&self, reg: i32, mask: i32) -> Result<(), EspError> { 142 | esp!(unsafe { (*self.sensor).get_reg.unwrap()(self.sensor, reg, mask) }) 143 | } 144 | pub fn set_reg(&self, reg: i32, mask: i32, value: i32) -> Result<(), EspError> { 145 | esp!(unsafe { (*self.sensor).set_reg.unwrap()(self.sensor, reg, mask, value) }) 146 | } 147 | pub fn set_res_raw( 148 | &self, 149 | start_x: i32, 150 | start_y: i32, 151 | end_x: i32, 152 | end_y: i32, 153 | offset_x: i32, 154 | offset_y: i32, 155 | total_x: i32, 156 | total_y: i32, 157 | output_x: i32, 158 | output_y: i32, 159 | scale: bool, 160 | binning: bool, 161 | ) -> Result<(), EspError> { 162 | esp!(unsafe { 163 | (*self.sensor).set_res_raw.unwrap()( 164 | self.sensor, 165 | start_x, 166 | start_y, 167 | end_x, 168 | end_y, 169 | offset_x, 170 | offset_y, 171 | total_x, 172 | total_y, 173 | output_x, 174 | output_y, 175 | scale, 176 | binning, 177 | ) 178 | }) 179 | } 180 | pub fn set_pll( 181 | &self, 182 | bypass: i32, 183 | mul: i32, 184 | sys: i32, 185 | root: i32, 186 | pre: i32, 187 | seld5: i32, 188 | pclken: i32, 189 | pclk: i32, 190 | ) -> Result<(), EspError> { 191 | esp!(unsafe { 192 | (*self.sensor).set_pll.unwrap()( 193 | self.sensor, 194 | bypass, 195 | mul, 196 | sys, 197 | root, 198 | pre, 199 | seld5, 200 | pclken, 201 | pclk, 202 | ) 203 | }) 204 | } 205 | pub fn set_xclk(&self, timer: i32, xclk: i32) -> Result<(), EspError> { 206 | esp!(unsafe { (*self.sensor).set_xclk.unwrap()(self.sensor, timer, xclk) }) 207 | } 208 | } 209 | 210 | pub struct Camera<'a> { 211 | _p: PhantomData<&'a ()>, 212 | } 213 | 214 | impl<'a> Camera<'a> { 215 | pub fn new( 216 | pin_pwdn: impl Peripheral

+ 'a, 217 | pin_reset: impl Peripheral

+ 'a, 218 | pin_xclk: impl Peripheral

+ 'a, 219 | pin_d0: impl Peripheral

+ 'a, 220 | pin_d1: impl Peripheral

+ 'a, 221 | pin_d2: impl Peripheral

+ 'a, 222 | pin_d3: impl Peripheral

+ 'a, 223 | pin_d4: impl Peripheral

+ 'a, 224 | pin_d5: impl Peripheral

+ 'a, 225 | pin_d6: impl Peripheral

+ 'a, 226 | pin_d7: impl Peripheral

+ 'a, 227 | pin_vsync: impl Peripheral

+ 'a, 228 | pin_href: impl Peripheral

+ 'a, 229 | pin_pclk: impl Peripheral

+ 'a, 230 | ) -> Result { 231 | esp_idf_hal::into_ref!( 232 | pin_pwdn, pin_reset, pin_xclk, pin_d0, pin_d1, pin_d2, pin_d3, pin_d4, pin_d5, pin_d6, 233 | pin_d7, pin_vsync, pin_href, pin_pclk 234 | ); 235 | let config = camera::camera_config_t { 236 | pin_pwdn: pin_pwdn.pin(), 237 | pin_reset: pin_reset.pin(), 238 | pin_xclk: pin_xclk.pin(), 239 | 240 | pin_d0: pin_d0.pin(), 241 | pin_d1: pin_d1.pin(), 242 | pin_d2: pin_d2.pin(), 243 | pin_d3: pin_d3.pin(), 244 | pin_d4: pin_d4.pin(), 245 | pin_d5: pin_d5.pin(), 246 | pin_d6: pin_d6.pin(), 247 | pin_d7: pin_d7.pin(), 248 | pin_vsync: pin_vsync.pin(), 249 | pin_href: pin_href.pin(), 250 | pin_pclk: pin_pclk.pin(), 251 | 252 | xclk_freq_hz: 20000000, 253 | ledc_timer: esp_idf_sys::ledc_timer_t_LEDC_TIMER_0, 254 | ledc_channel: esp_idf_sys::ledc_channel_t_LEDC_CHANNEL_0, 255 | 256 | pixel_format: camera::pixformat_t_PIXFORMAT_RGB565, 257 | frame_size: camera::framesize_t_FRAMESIZE_QVGA, 258 | 259 | jpeg_quality: 12, 260 | fb_count: 1, 261 | grab_mode: camera::camera_grab_mode_t_CAMERA_GRAB_WHEN_EMPTY, 262 | 263 | ..Default::default() 264 | }; 265 | 266 | esp_idf_sys::esp!(unsafe { camera::esp_camera_init(&config) })?; 267 | Ok(Self { _p: PhantomData }) 268 | } 269 | 270 | pub fn get_framebuffer(&self) -> Option { 271 | let fb = unsafe { camera::esp_camera_fb_get() }; 272 | return if fb.is_null() { 273 | None 274 | } else { 275 | Some(FrameBuffer { 276 | fb, 277 | _p: PhantomData, 278 | }) 279 | }; 280 | } 281 | 282 | pub fn sensor(&self) -> CameraSensor<'a> { 283 | CameraSensor { 284 | sensor: unsafe { camera::esp_camera_sensor_get() }, 285 | _p: PhantomData, 286 | } 287 | } 288 | } 289 | 290 | impl<'a> Drop for Camera<'a> { 291 | fn drop(&mut self) { 292 | esp!(unsafe { camera::esp_camera_deinit() }).expect("error during esp_camera_deinit") 293 | } 294 | } 295 | --------------------------------------------------------------------------------