diff options
| author | Matous Hybl <[email protected]> | 2021-11-16 11:44:05 +0100 |
|---|---|---|
| committer | Matous Hybl <[email protected]> | 2021-12-09 12:56:39 +0100 |
| commit | 484c356c030d074ef0339cee66440bcc252ff1d4 (patch) | |
| tree | 824a1e332664d21ad0df21ca14349c115774c60e /examples/stm32h7/src/bin/camera.rs | |
| parent | 1dd5a71c07d2ab9aec1d9af7b4c87d5b1b969bd8 (diff) | |
Add DCMI example.
Diffstat (limited to 'examples/stm32h7/src/bin/camera.rs')
| -rw-r--r-- | examples/stm32h7/src/bin/camera.rs | 339 |
1 files changed, 339 insertions, 0 deletions
diff --git a/examples/stm32h7/src/bin/camera.rs b/examples/stm32h7/src/bin/camera.rs new file mode 100644 index 000000000..2fa742b83 --- /dev/null +++ b/examples/stm32h7/src/bin/camera.rs | |||
| @@ -0,0 +1,339 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![no_main] | ||
| 3 | #![feature(type_alias_impl_trait)] | ||
| 4 | |||
| 5 | use embassy::executor::Spawner; | ||
| 6 | use embassy::time::{Duration, Timer}; | ||
| 7 | use embassy_stm32::dcmi::*; | ||
| 8 | use embassy_stm32::gpio::{Level, NoPin, Output, Speed}; | ||
| 9 | use embassy_stm32::i2c::I2c; | ||
| 10 | use embassy_stm32::interrupt; | ||
| 11 | use embassy_stm32::rcc::{Mco, Mco1Source, McoClock}; | ||
| 12 | use embassy_stm32::time::U32Ext; | ||
| 13 | use embassy_stm32::Peripherals; | ||
| 14 | use embedded_hal::digital::v2::OutputPin; | ||
| 15 | |||
| 16 | use defmt_rtt as _; // global logger | ||
| 17 | use panic_probe as _; | ||
| 18 | |||
| 19 | use core::sync::atomic::{AtomicUsize, Ordering}; | ||
| 20 | use embassy_stm32::Config; | ||
| 21 | |||
| 22 | defmt::timestamp! {"{=u64}", { | ||
| 23 | static COUNT: AtomicUsize = AtomicUsize::new(0); | ||
| 24 | // NOTE(no-CAS) `timestamps` runs with interrupts disabled | ||
| 25 | let n = COUNT.load(Ordering::Relaxed); | ||
| 26 | COUNT.store(n + 1, Ordering::Relaxed); | ||
| 27 | n as u64 | ||
| 28 | } | ||
| 29 | } | ||
| 30 | |||
| 31 | #[allow(unused)] | ||
| 32 | pub fn config() -> Config { | ||
| 33 | let mut config = Config::default(); | ||
| 34 | config.rcc.sys_ck = Some(400.mhz().into()); | ||
| 35 | config.rcc.hclk = Some(400.mhz().into()); | ||
| 36 | config.rcc.pll1.q_ck = Some(100.mhz().into()); | ||
| 37 | config.rcc.enable_dma1 = true; | ||
| 38 | config.rcc.enable_dma2 = true; | ||
| 39 | config.rcc.pclk1 = Some(100.mhz().into()); | ||
| 40 | config.rcc.pclk2 = Some(100.mhz().into()); | ||
| 41 | config.rcc.pclk3 = Some(100.mhz().into()); | ||
| 42 | config.rcc.pclk4 = Some(100.mhz().into()); | ||
| 43 | config | ||
| 44 | } | ||
| 45 | |||
| 46 | use ov7725::*; | ||
| 47 | |||
| 48 | const WIDTH: usize = 100; | ||
| 49 | const HEIGHT: usize = 100; | ||
| 50 | |||
| 51 | static mut FRAME: [u32; WIDTH * HEIGHT / 2] = [0u32; WIDTH * HEIGHT / 2]; | ||
| 52 | |||
| 53 | #[embassy::main(config = "config()")] | ||
| 54 | async fn main(_spawner: Spawner, p: Peripherals) { | ||
| 55 | defmt::info!("Hello World!"); | ||
| 56 | let mco = Mco::new(p.MCO1, p.PA8, Mco1Source::Hsi, McoClock::Divided(3)); | ||
| 57 | |||
| 58 | let mut led = Output::new(p.PE3, Level::High, Speed::Low); | ||
| 59 | let i2c_irq = interrupt::take!(I2C1_EV); | ||
| 60 | let cam_i2c = I2c::new( | ||
| 61 | p.I2C1, | ||
| 62 | p.PB8, | ||
| 63 | p.PB9, | ||
| 64 | i2c_irq, | ||
| 65 | p.DMA1_CH1, | ||
| 66 | p.DMA1_CH2, | ||
| 67 | 100u32.khz(), | ||
| 68 | ); | ||
| 69 | |||
| 70 | let mut camera = Ov7725::new(cam_i2c, mco); | ||
| 71 | |||
| 72 | defmt::unwrap!(camera.init().await); | ||
| 73 | |||
| 74 | let manufacturer_id = defmt::unwrap!(camera.read_manufacturer_id().await); | ||
| 75 | let camera_id = defmt::unwrap!(camera.read_product_id().await); | ||
| 76 | |||
| 77 | defmt::info!( | ||
| 78 | "manufacturer: 0x{:x}, pid: 0x{:x}", | ||
| 79 | manufacturer_id, | ||
| 80 | camera_id | ||
| 81 | ); | ||
| 82 | |||
| 83 | let dcmi_irq = interrupt::take!(DCMI); | ||
| 84 | let mut dcmi = Dcmi::new( | ||
| 85 | p.DCMI, | ||
| 86 | p.DMA1_CH0, | ||
| 87 | VSyncDataInvalidLevel::High, | ||
| 88 | HSyncDataInvalidLevel::Low, | ||
| 89 | PixelClockPolarity::RisingEdge, | ||
| 90 | false, | ||
| 91 | dcmi_irq, | ||
| 92 | p.PC6, | ||
| 93 | p.PC7, | ||
| 94 | p.PE0, | ||
| 95 | p.PE1, | ||
| 96 | p.PE4, | ||
| 97 | p.PD3, | ||
| 98 | p.PE5, | ||
| 99 | p.PE6, | ||
| 100 | NoPin, | ||
| 101 | NoPin, | ||
| 102 | NoPin, | ||
| 103 | NoPin, | ||
| 104 | NoPin, | ||
| 105 | NoPin, | ||
| 106 | p.PB7, | ||
| 107 | p.PA4, | ||
| 108 | p.PA6, | ||
| 109 | ); | ||
| 110 | |||
| 111 | defmt::info!("attempting capture"); | ||
| 112 | defmt::unwrap!(dcmi.capture(unsafe { &mut FRAME }).await); | ||
| 113 | |||
| 114 | defmt::info!("captured frame: {:x}", unsafe { &FRAME }); | ||
| 115 | |||
| 116 | defmt::info!("main loop running"); | ||
| 117 | loop { | ||
| 118 | defmt::info!("high"); | ||
| 119 | defmt::unwrap!(led.set_high()); | ||
| 120 | Timer::after(Duration::from_millis(500)).await; | ||
| 121 | |||
| 122 | defmt::info!("low"); | ||
| 123 | defmt::unwrap!(led.set_low()); | ||
| 124 | Timer::after(Duration::from_millis(500)).await; | ||
| 125 | } | ||
| 126 | } | ||
| 127 | |||
| 128 | mod ov7725 { | ||
| 129 | use core::marker::PhantomData; | ||
| 130 | |||
| 131 | use defmt::Format; | ||
| 132 | use embassy::time::{Duration, Timer}; | ||
| 133 | use embassy_stm32::rcc::{Mco, McoInstance}; | ||
| 134 | use embassy_traits::i2c::I2c; | ||
| 135 | |||
| 136 | #[repr(u8)] | ||
| 137 | pub enum RgbFormat { | ||
| 138 | Gbr422 = 0, | ||
| 139 | RGB565 = 1, | ||
| 140 | RGB555 = 2, | ||
| 141 | RGB444 = 3, | ||
| 142 | } | ||
| 143 | pub enum PixelFormat { | ||
| 144 | Yuv, | ||
| 145 | ProcessedRawBayer, | ||
| 146 | Rgb(RgbFormat), | ||
| 147 | RawBayer, | ||
| 148 | } | ||
| 149 | |||
| 150 | impl From<PixelFormat> for u8 { | ||
| 151 | fn from(raw: PixelFormat) -> Self { | ||
| 152 | match raw { | ||
| 153 | PixelFormat::Yuv => 0, | ||
| 154 | PixelFormat::ProcessedRawBayer => 1, | ||
| 155 | PixelFormat::Rgb(mode) => 2 | ((mode as u8) << 2), | ||
| 156 | PixelFormat::RawBayer => 3, | ||
| 157 | } | ||
| 158 | } | ||
| 159 | } | ||
| 160 | |||
| 161 | #[derive(Clone, Copy)] | ||
| 162 | #[repr(u8)] | ||
| 163 | #[allow(unused)] | ||
| 164 | pub enum Register { | ||
| 165 | Gain = 0x00, | ||
| 166 | Blue = 0x01, | ||
| 167 | Red = 0x02, | ||
| 168 | Green = 0x03, | ||
| 169 | BAvg = 0x05, | ||
| 170 | GAvg = 0x06, | ||
| 171 | RAvg = 0x07, | ||
| 172 | Aech = 0x08, | ||
| 173 | Com2 = 0x09, | ||
| 174 | PId = 0x0a, | ||
| 175 | Ver = 0x0b, | ||
| 176 | Com3 = 0x0c, | ||
| 177 | Com4 = 0x0d, | ||
| 178 | Com5 = 0x0e, | ||
| 179 | Com6 = 0x0f, | ||
| 180 | Aec = 0x10, | ||
| 181 | ClkRc = 0x11, | ||
| 182 | Com7 = 0x12, | ||
| 183 | Com8 = 0x13, | ||
| 184 | Com9 = 0x14, | ||
| 185 | Com10 = 0x15, | ||
| 186 | Reg16 = 0x16, | ||
| 187 | HStart = 0x17, | ||
| 188 | HSize = 0x18, | ||
| 189 | VStart = 0x19, | ||
| 190 | VSize = 0x1a, | ||
| 191 | PShift = 0x1b, | ||
| 192 | MidH = 0x1c, | ||
| 193 | MidL = 0x1d, | ||
| 194 | Laec = 0x1f, | ||
| 195 | Com11 = 0x20, | ||
| 196 | BdBase = 0x22, | ||
| 197 | BdMStep = 0x23, | ||
| 198 | Aew = 0x24, | ||
| 199 | Aeb = 0x25, | ||
| 200 | Vpt = 0x26, | ||
| 201 | Reg28 = 0x28, | ||
| 202 | HOutSize = 0x29, | ||
| 203 | EXHCH = 0x2a, | ||
| 204 | EXHCL = 0x2b, | ||
| 205 | VOutSize = 0x2c, | ||
| 206 | Advfl = 0x2d, | ||
| 207 | Advfh = 0x2e, | ||
| 208 | Yave = 0x2f, | ||
| 209 | LumHTh = 0x30, | ||
| 210 | LumLTh = 0x31, | ||
| 211 | HRef = 0x32, | ||
| 212 | DspCtrl4 = 0x67, | ||
| 213 | DspAuto = 0xac, | ||
| 214 | } | ||
| 215 | |||
| 216 | const CAM_ADDR: u8 = 0x21; | ||
| 217 | |||
| 218 | #[derive(Format)] | ||
| 219 | pub enum Error<I2cError: Format> { | ||
| 220 | I2c(I2cError), | ||
| 221 | } | ||
| 222 | |||
| 223 | pub struct Ov7725<'d, Bus: I2c> { | ||
| 224 | phantom: PhantomData<&'d ()>, | ||
| 225 | bus: Bus, | ||
| 226 | } | ||
| 227 | |||
| 228 | impl<'d, Bus> Ov7725<'d, Bus> | ||
| 229 | where | ||
| 230 | Bus: I2c, | ||
| 231 | Bus::Error: Format, | ||
| 232 | { | ||
| 233 | pub fn new<T>(bus: Bus, _mco: Mco<T>) -> Self | ||
| 234 | where | ||
| 235 | T: McoInstance, | ||
| 236 | { | ||
| 237 | Self { | ||
| 238 | phantom: PhantomData, | ||
| 239 | bus, | ||
| 240 | } | ||
| 241 | } | ||
| 242 | |||
| 243 | pub async fn init(&mut self) -> Result<(), Error<Bus::Error>> { | ||
| 244 | Timer::after(Duration::from_millis(500)).await; | ||
| 245 | self.reset_regs().await?; | ||
| 246 | Timer::after(Duration::from_millis(500)).await; | ||
| 247 | self.set_pixformat().await?; | ||
| 248 | self.set_resolution().await?; | ||
| 249 | Ok(()) | ||
| 250 | } | ||
| 251 | |||
| 252 | pub async fn read_manufacturer_id(&mut self) -> Result<u16, Error<Bus::Error>> { | ||
| 253 | Ok(u16::from_le_bytes([ | ||
| 254 | self.read(Register::MidL).await?, | ||
| 255 | self.read(Register::MidH).await?, | ||
| 256 | ])) | ||
| 257 | } | ||
| 258 | |||
| 259 | pub async fn read_product_id(&mut self) -> Result<u16, Error<Bus::Error>> { | ||
| 260 | Ok(u16::from_le_bytes([ | ||
| 261 | self.read(Register::Ver).await?, | ||
| 262 | self.read(Register::PId).await?, | ||
| 263 | ])) | ||
| 264 | } | ||
| 265 | |||
| 266 | async fn reset_regs(&mut self) -> Result<(), Error<Bus::Error>> { | ||
| 267 | self.write(Register::Com7, 0x80).await | ||
| 268 | } | ||
| 269 | |||
| 270 | async fn set_pixformat(&mut self) -> Result<(), Error<Bus::Error>> { | ||
| 271 | self.write(Register::DspCtrl4, 0).await?; | ||
| 272 | let mut com7 = self.read(Register::Com7).await?; | ||
| 273 | com7 |= u8::from(PixelFormat::Rgb(RgbFormat::RGB565)); | ||
| 274 | self.write(Register::Com7, com7).await?; | ||
| 275 | Ok(()) | ||
| 276 | } | ||
| 277 | |||
| 278 | async fn set_resolution(&mut self) -> Result<(), Error<Bus::Error>> { | ||
| 279 | let horizontal: u16 = super::WIDTH as u16; | ||
| 280 | let vertical: u16 = super::HEIGHT as u16; | ||
| 281 | |||
| 282 | let h_high = (horizontal >> 2) as u8; | ||
| 283 | let v_high = (vertical >> 1) as u8; | ||
| 284 | let h_low = (horizontal & 0x03) as u8; | ||
| 285 | let v_low = (vertical & 0x01) as u8; | ||
| 286 | |||
| 287 | self.write(Register::HOutSize, h_high).await?; | ||
| 288 | self.write(Register::VOutSize, v_high).await?; | ||
| 289 | self.write(Register::EXHCH, h_low | (v_low << 2)).await?; | ||
| 290 | |||
| 291 | self.write(Register::Com3, 0xd1).await?; | ||
| 292 | |||
| 293 | let com3 = self.read(Register::Com3).await?; | ||
| 294 | let vflip = com3 & 0x80 > 0; | ||
| 295 | |||
| 296 | self.modify(Register::HRef, |reg| { | ||
| 297 | reg & 0xbf | if vflip { 0x40 } else { 0x40 } | ||
| 298 | }) | ||
| 299 | .await?; | ||
| 300 | |||
| 301 | if horizontal <= 320 || vertical <= 240 { | ||
| 302 | self.write(Register::HStart, 0x3f).await?; | ||
| 303 | self.write(Register::HSize, 0x50).await?; | ||
| 304 | self.write(Register::VStart, 0x02).await?; // TODO vflip is subtracted in the original code | ||
| 305 | self.write(Register::VSize, 0x78).await?; | ||
| 306 | } else { | ||
| 307 | defmt::panic!("VGA resolutions not yet supported."); | ||
| 308 | } | ||
| 309 | |||
| 310 | Ok(()) | ||
| 311 | } | ||
| 312 | |||
| 313 | async fn read(&mut self, register: Register) -> Result<u8, Error<Bus::Error>> { | ||
| 314 | let mut buffer = [0u8; 1]; | ||
| 315 | self.bus | ||
| 316 | .write_read(CAM_ADDR, &[register as u8], &mut buffer[..1]) | ||
| 317 | .await | ||
| 318 | .map_err(Error::I2c)?; | ||
| 319 | Ok(buffer[0]) | ||
| 320 | } | ||
| 321 | |||
| 322 | async fn write(&mut self, register: Register, value: u8) -> Result<(), Error<Bus::Error>> { | ||
| 323 | self.bus | ||
| 324 | .write(CAM_ADDR, &[register as u8, value]) | ||
| 325 | .await | ||
| 326 | .map_err(Error::I2c) | ||
| 327 | } | ||
| 328 | |||
| 329 | async fn modify<F: FnOnce(u8) -> u8>( | ||
| 330 | &mut self, | ||
| 331 | register: Register, | ||
| 332 | f: F, | ||
| 333 | ) -> Result<(), Error<Bus::Error>> { | ||
| 334 | let value = self.read(register).await?; | ||
| 335 | let value = f(value); | ||
| 336 | self.write(register, value).await | ||
| 337 | } | ||
| 338 | } | ||
| 339 | } | ||
