diff options
| author | bors[bot] <26634292+bors[bot]@users.noreply.github.com> | 2021-12-09 12:42:13 +0000 |
|---|---|---|
| committer | GitHub <[email protected]> | 2021-12-09 12:42:13 +0000 |
| commit | 45a82cfc4319a2351c08ff0b3de1639d901ec651 (patch) | |
| tree | 3440133aa395daebb4dbb19feda35a9fb371a26d | |
| parent | 08c84761453b3d5f3b13849b4923ce1f36fdabe7 (diff) | |
| parent | 484c356c030d074ef0339cee66440bcc252ff1d4 (diff) | |
Merge #490
490: DCMI r=matoushybl a=matoushybl
Co-authored-by: Matous Hybl <[email protected]>
| -rw-r--r-- | embassy-stm32/src/dcmi.rs | 481 | ||||
| -rw-r--r-- | embassy-stm32/src/lib.rs | 2 | ||||
| -rw-r--r-- | embassy-stm32/src/rcc/h7/mod.rs | 5 | ||||
| -rw-r--r-- | examples/stm32h7/Cargo.toml | 40 | ||||
| -rw-r--r-- | examples/stm32h7/src/bin/camera.rs | 339 |
5 files changed, 866 insertions, 1 deletions
diff --git a/embassy-stm32/src/dcmi.rs b/embassy-stm32/src/dcmi.rs new file mode 100644 index 000000000..2d2ad72e4 --- /dev/null +++ b/embassy-stm32/src/dcmi.rs | |||
| @@ -0,0 +1,481 @@ | |||
| 1 | use core::marker::PhantomData; | ||
| 2 | use core::task::Poll; | ||
| 3 | |||
| 4 | use crate::gpio::sealed::Pin as __GpioPin; | ||
| 5 | use crate::gpio::Pin as GpioPin; | ||
| 6 | use embassy::interrupt::{Interrupt, InterruptExt}; | ||
| 7 | use embassy::util::Unborrow; | ||
| 8 | use embassy::waitqueue::AtomicWaker; | ||
| 9 | use embassy_hal_common::unborrow; | ||
| 10 | use futures::future::poll_fn; | ||
| 11 | |||
| 12 | /// The level on the VSync pin when the data is not valid on the parallel interface. | ||
| 13 | #[derive(Clone, Copy, PartialEq)] | ||
| 14 | pub enum VSyncDataInvalidLevel { | ||
| 15 | Low, | ||
| 16 | High, | ||
| 17 | } | ||
| 18 | |||
| 19 | /// The level on the VSync pin when the data is not valid on the parallel interface. | ||
| 20 | #[derive(Clone, Copy, PartialEq)] | ||
| 21 | pub enum HSyncDataInvalidLevel { | ||
| 22 | Low, | ||
| 23 | High, | ||
| 24 | } | ||
| 25 | |||
| 26 | #[derive(Clone, Copy, PartialEq)] | ||
| 27 | pub enum PixelClockPolarity { | ||
| 28 | RisingEdge, | ||
| 29 | FallingEdge, | ||
| 30 | } | ||
| 31 | |||
| 32 | pub struct State { | ||
| 33 | waker: AtomicWaker, | ||
| 34 | } | ||
| 35 | impl State { | ||
| 36 | const fn new() -> State { | ||
| 37 | State { | ||
| 38 | waker: AtomicWaker::new(), | ||
| 39 | } | ||
| 40 | } | ||
| 41 | } | ||
| 42 | |||
| 43 | static STATE: State = State::new(); | ||
| 44 | |||
| 45 | #[derive(Debug, Eq, PartialEq, Copy, Clone)] | ||
| 46 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 47 | #[non_exhaustive] | ||
| 48 | pub enum Error { | ||
| 49 | Overrun, | ||
| 50 | PeripheralError, | ||
| 51 | } | ||
| 52 | |||
| 53 | pub struct Dcmi<'d, T: Instance, Dma: FrameDma> { | ||
| 54 | inner: T, | ||
| 55 | dma: Dma, | ||
| 56 | phantom: PhantomData<&'d mut T>, | ||
| 57 | } | ||
| 58 | |||
| 59 | impl<'d, T, Dma> Dcmi<'d, T, Dma> | ||
| 60 | where | ||
| 61 | T: Instance, | ||
| 62 | Dma: FrameDma, | ||
| 63 | { | ||
| 64 | pub fn new( | ||
| 65 | peri: impl Unborrow<Target = T> + 'd, | ||
| 66 | dma: impl Unborrow<Target = Dma> + 'd, | ||
| 67 | vsync_level: VSyncDataInvalidLevel, | ||
| 68 | hsync_level: HSyncDataInvalidLevel, | ||
| 69 | pixclk_polarity: PixelClockPolarity, | ||
| 70 | use_embedded_synchronization: bool, | ||
| 71 | irq: impl Unborrow<Target = T::Interrupt> + 'd, | ||
| 72 | d0: impl Unborrow<Target = impl D0Pin> + 'd, | ||
| 73 | d1: impl Unborrow<Target = impl D1Pin> + 'd, | ||
| 74 | d2: impl Unborrow<Target = impl D2Pin> + 'd, | ||
| 75 | d3: impl Unborrow<Target = impl D3Pin> + 'd, | ||
| 76 | d4: impl Unborrow<Target = impl D4Pin> + 'd, | ||
| 77 | d5: impl Unborrow<Target = impl D5Pin> + 'd, | ||
| 78 | d6: impl Unborrow<Target = impl D6Pin> + 'd, | ||
| 79 | d7: impl Unborrow<Target = impl D7Pin> + 'd, | ||
| 80 | d8: impl Unborrow<Target = impl D8Pin> + 'd, | ||
| 81 | d9: impl Unborrow<Target = impl D9Pin> + 'd, | ||
| 82 | d10: impl Unborrow<Target = impl D10Pin> + 'd, | ||
| 83 | d11: impl Unborrow<Target = impl D11Pin> + 'd, | ||
| 84 | d12: impl Unborrow<Target = impl D12Pin> + 'd, | ||
| 85 | d13: impl Unborrow<Target = impl D13Pin> + 'd, | ||
| 86 | v_sync: impl Unborrow<Target = impl VSyncPin> + 'd, | ||
| 87 | h_sync: impl Unborrow<Target = impl HSyncPin> + 'd, | ||
| 88 | pixclk: impl Unborrow<Target = impl PixClkPin> + 'd, | ||
| 89 | ) -> Self { | ||
| 90 | T::reset(); | ||
| 91 | T::enable(); | ||
| 92 | |||
| 93 | unborrow!( | ||
| 94 | peri, dma, irq, d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11, d12, d13, v_sync, | ||
| 95 | h_sync, pixclk | ||
| 96 | ); | ||
| 97 | |||
| 98 | d0.configure(); | ||
| 99 | d1.configure(); | ||
| 100 | d2.configure(); | ||
| 101 | d3.configure(); | ||
| 102 | d4.configure(); | ||
| 103 | d5.configure(); | ||
| 104 | d6.configure(); | ||
| 105 | d7.configure(); | ||
| 106 | d8.configure(); | ||
| 107 | d9.configure(); | ||
| 108 | d10.configure(); | ||
| 109 | d11.configure(); | ||
| 110 | d12.configure(); | ||
| 111 | d13.configure(); | ||
| 112 | |||
| 113 | v_sync.configure(); | ||
| 114 | h_sync.configure(); | ||
| 115 | pixclk.configure(); | ||
| 116 | |||
| 117 | let edm = match ( | ||
| 118 | d8.pin().is_some(), | ||
| 119 | d9.pin().is_some(), | ||
| 120 | d10.pin().is_some(), | ||
| 121 | d11.pin().is_some(), | ||
| 122 | d12.pin().is_some(), | ||
| 123 | d13.pin().is_some(), | ||
| 124 | ) { | ||
| 125 | (true, true, true, true, true, true) => 0b11, // 14 bits | ||
| 126 | (true, true, true, true, false, false) => 0b10, // 12 bits | ||
| 127 | (true, true, false, false, false, false) => 0b01, // 10 bits | ||
| 128 | (false, false, false, false, false, false) => 0b00, // 8 bits | ||
| 129 | _ => { | ||
| 130 | panic!("Invalid pin configuration."); | ||
| 131 | } | ||
| 132 | }; | ||
| 133 | |||
| 134 | unsafe { | ||
| 135 | peri.regs().cr().modify(|r| { | ||
| 136 | r.set_cm(true); // disable continuous mode (snapshot mode) | ||
| 137 | r.set_ess(use_embedded_synchronization); | ||
| 138 | r.set_pckpol(pixclk_polarity == PixelClockPolarity::RisingEdge); | ||
| 139 | r.set_vspol(vsync_level == VSyncDataInvalidLevel::High); | ||
| 140 | r.set_hspol(hsync_level == HSyncDataInvalidLevel::High); | ||
| 141 | r.set_fcrc(0x00); // capture every frame | ||
| 142 | r.set_edm(edm); // extended data mode | ||
| 143 | }); | ||
| 144 | } | ||
| 145 | |||
| 146 | irq.set_handler(Self::on_interrupt); | ||
| 147 | irq.unpend(); | ||
| 148 | irq.enable(); | ||
| 149 | |||
| 150 | Self { | ||
| 151 | inner: peri, | ||
| 152 | dma, | ||
| 153 | phantom: PhantomData, | ||
| 154 | } | ||
| 155 | } | ||
| 156 | |||
| 157 | unsafe fn on_interrupt(_: *mut ()) { | ||
| 158 | let ris = crate::pac::DCMI.ris().read(); | ||
| 159 | if ris.err_ris() { | ||
| 160 | error!("DCMI IRQ: Error."); | ||
| 161 | crate::pac::DCMI.ier().modify(|ier| ier.set_err_ie(false)); | ||
| 162 | } | ||
| 163 | if ris.ovr_ris() { | ||
| 164 | error!("DCMI IRQ: Overrun."); | ||
| 165 | crate::pac::DCMI.ier().modify(|ier| ier.set_ovr_ie(false)); | ||
| 166 | } | ||
| 167 | if ris.frame_ris() { | ||
| 168 | info!("DCMI IRQ: Frame captured."); | ||
| 169 | crate::pac::DCMI.ier().modify(|ier| ier.set_frame_ie(false)); | ||
| 170 | } | ||
| 171 | STATE.waker.wake(); | ||
| 172 | } | ||
| 173 | |||
| 174 | unsafe fn toggle(enable: bool) { | ||
| 175 | crate::pac::DCMI.cr().modify(|r| { | ||
| 176 | r.set_enable(enable); | ||
| 177 | r.set_capture(enable); | ||
| 178 | }) | ||
| 179 | } | ||
| 180 | |||
| 181 | fn enable_irqs() { | ||
| 182 | unsafe { | ||
| 183 | crate::pac::DCMI.ier().modify(|r| { | ||
| 184 | r.set_err_ie(true); | ||
| 185 | r.set_ovr_ie(true); | ||
| 186 | r.set_frame_ie(true); | ||
| 187 | }); | ||
| 188 | } | ||
| 189 | } | ||
| 190 | |||
| 191 | fn clear_interrupt_flags() { | ||
| 192 | unsafe { | ||
| 193 | crate::pac::DCMI.icr().write(|r| { | ||
| 194 | r.set_ovr_isc(true); | ||
| 195 | r.set_err_isc(true); | ||
| 196 | r.set_frame_isc(true); | ||
| 197 | }) | ||
| 198 | } | ||
| 199 | } | ||
| 200 | |||
| 201 | /// This method starts the capture and finishes when both the dma transfer and DCMI finish the frame transfer. | ||
| 202 | /// The implication is that the input buffer size must be exactly the size of the captured frame. | ||
| 203 | pub async fn capture(&mut self, buffer: &mut [u32]) -> Result<(), Error> { | ||
| 204 | let channel = &mut self.dma; | ||
| 205 | let request = channel.request(); | ||
| 206 | |||
| 207 | let r = self.inner.regs(); | ||
| 208 | let src = r.dr().ptr() as *mut u32; | ||
| 209 | let dma_read = crate::dma::read(channel, request, src, buffer); | ||
| 210 | |||
| 211 | Self::clear_interrupt_flags(); | ||
| 212 | Self::enable_irqs(); | ||
| 213 | |||
| 214 | unsafe { Self::toggle(true) }; | ||
| 215 | |||
| 216 | let result = poll_fn(|cx| { | ||
| 217 | STATE.waker.register(cx.waker()); | ||
| 218 | |||
| 219 | let ris = unsafe { crate::pac::DCMI.ris().read() }; | ||
| 220 | if ris.err_ris() { | ||
| 221 | unsafe { | ||
| 222 | crate::pac::DCMI.icr().write(|r| { | ||
| 223 | r.set_err_isc(true); | ||
| 224 | }) | ||
| 225 | }; | ||
| 226 | Poll::Ready(Err(Error::PeripheralError)) | ||
| 227 | } else if ris.ovr_ris() { | ||
| 228 | unsafe { | ||
| 229 | crate::pac::DCMI.icr().write(|r| { | ||
| 230 | r.set_ovr_isc(true); | ||
| 231 | }) | ||
| 232 | }; | ||
| 233 | Poll::Ready(Err(Error::Overrun)) | ||
| 234 | } else if ris.frame_ris() { | ||
| 235 | unsafe { | ||
| 236 | crate::pac::DCMI.icr().write(|r| { | ||
| 237 | r.set_frame_isc(true); | ||
| 238 | }) | ||
| 239 | }; | ||
| 240 | Poll::Ready(Ok(())) | ||
| 241 | } else { | ||
| 242 | Poll::Pending | ||
| 243 | } | ||
| 244 | }); | ||
| 245 | |||
| 246 | let (_, result) = futures::future::join(dma_read, result).await; | ||
| 247 | |||
| 248 | unsafe { Self::toggle(false) }; | ||
| 249 | |||
| 250 | result | ||
| 251 | } | ||
| 252 | } | ||
| 253 | |||
| 254 | mod sealed { | ||
| 255 | use super::*; | ||
| 256 | use crate::rcc::RccPeripheral; | ||
| 257 | |||
| 258 | pub trait Instance: RccPeripheral { | ||
| 259 | fn regs(&self) -> crate::pac::dcmi::Dcmi; | ||
| 260 | } | ||
| 261 | |||
| 262 | pub trait FrameDma { | ||
| 263 | fn request(&self) -> crate::dma::Request; | ||
| 264 | } | ||
| 265 | |||
| 266 | macro_rules! pin { | ||
| 267 | ($name:ident) => { | ||
| 268 | pub trait $name: GpioPin { | ||
| 269 | fn configure(&mut self); | ||
| 270 | } | ||
| 271 | }; | ||
| 272 | } | ||
| 273 | |||
| 274 | macro_rules! optional_pin { | ||
| 275 | ($name:ident) => { | ||
| 276 | pub trait $name: crate::gpio::OptionalPin { | ||
| 277 | fn configure(&mut self); | ||
| 278 | } | ||
| 279 | }; | ||
| 280 | } | ||
| 281 | |||
| 282 | pin!(D0Pin); | ||
| 283 | pin!(D1Pin); | ||
| 284 | pin!(D2Pin); | ||
| 285 | pin!(D3Pin); | ||
| 286 | pin!(D4Pin); | ||
| 287 | pin!(D5Pin); | ||
| 288 | pin!(D6Pin); | ||
| 289 | pin!(D7Pin); | ||
| 290 | optional_pin!(D8Pin); | ||
| 291 | optional_pin!(D9Pin); | ||
| 292 | optional_pin!(D10Pin); | ||
| 293 | optional_pin!(D11Pin); | ||
| 294 | optional_pin!(D12Pin); | ||
| 295 | optional_pin!(D13Pin); | ||
| 296 | |||
| 297 | optional_pin!(HSyncPin); | ||
| 298 | optional_pin!(VSyncPin); | ||
| 299 | pin!(PixClkPin); | ||
| 300 | } | ||
| 301 | |||
| 302 | pub trait Instance: sealed::Instance + 'static { | ||
| 303 | type Interrupt: Interrupt; | ||
| 304 | } | ||
| 305 | |||
| 306 | pub trait FrameDma: sealed::FrameDma + crate::dma::Channel {} | ||
| 307 | |||
| 308 | macro_rules! pin { | ||
| 309 | ($name:ident) => { | ||
| 310 | pub trait $name: sealed::$name + 'static {} | ||
| 311 | }; | ||
| 312 | } | ||
| 313 | |||
| 314 | pin!(D0Pin); | ||
| 315 | pin!(D1Pin); | ||
| 316 | pin!(D2Pin); | ||
| 317 | pin!(D3Pin); | ||
| 318 | pin!(D4Pin); | ||
| 319 | pin!(D5Pin); | ||
| 320 | pin!(D6Pin); | ||
| 321 | pin!(D7Pin); | ||
| 322 | pin!(D8Pin); | ||
| 323 | pin!(D9Pin); | ||
| 324 | pin!(D10Pin); | ||
| 325 | pin!(D11Pin); | ||
| 326 | pin!(D12Pin); | ||
| 327 | pin!(D13Pin); | ||
| 328 | |||
| 329 | pin!(HSyncPin); | ||
| 330 | pin!(VSyncPin); | ||
| 331 | pin!(PixClkPin); | ||
| 332 | |||
| 333 | // allow unused as U5 sources do not contain interrupt nor dma data | ||
| 334 | #[allow(unused)] | ||
| 335 | macro_rules! impl_peripheral { | ||
| 336 | ($inst:ident, $irq:ident) => { | ||
| 337 | impl sealed::Instance for crate::peripherals::$inst { | ||
| 338 | fn regs(&self) -> crate::pac::dcmi::Dcmi { | ||
| 339 | crate::pac::$inst | ||
| 340 | } | ||
| 341 | } | ||
| 342 | |||
| 343 | impl Instance for crate::peripherals::$inst { | ||
| 344 | type Interrupt = crate::interrupt::$irq; | ||
| 345 | } | ||
| 346 | }; | ||
| 347 | } | ||
| 348 | |||
| 349 | crate::pac::interrupts! { | ||
| 350 | ($inst:ident, dcmi, $block:ident, GLOBAL, $irq:ident) => { | ||
| 351 | impl_peripheral!($inst, $irq); | ||
| 352 | }; | ||
| 353 | } | ||
| 354 | |||
| 355 | // allow unused as U5 sources do not contain interrupt nor dma data | ||
| 356 | #[allow(unused)] | ||
| 357 | macro_rules! impl_dma { | ||
| 358 | ($inst:ident, {dmamux: $dmamux:ident}, $signal:ident, $request:expr) => { | ||
| 359 | impl<T> sealed::$signal for T | ||
| 360 | where | ||
| 361 | T: crate::dma::MuxChannel<Mux = crate::dma::$dmamux>, | ||
| 362 | { | ||
| 363 | fn request(&self) -> crate::dma::Request { | ||
| 364 | $request | ||
| 365 | } | ||
| 366 | } | ||
| 367 | |||
| 368 | impl<T> $signal for T where T: crate::dma::MuxChannel<Mux = crate::dma::$dmamux> {} | ||
| 369 | }; | ||
| 370 | ($inst:ident, {channel: $channel:ident}, $signal:ident, $request:expr) => { | ||
| 371 | impl sealed::$signal for crate::peripherals::$channel { | ||
| 372 | fn request(&self) -> crate::dma::Request { | ||
| 373 | $request | ||
| 374 | } | ||
| 375 | } | ||
| 376 | |||
| 377 | impl $signal for crate::peripherals::$channel {} | ||
| 378 | }; | ||
| 379 | } | ||
| 380 | |||
| 381 | crate::pac::peripheral_dma_channels! { | ||
| 382 | ($peri:ident, dcmi, $kind:ident, PSSI, $channel:tt, $request:expr) => { | ||
| 383 | impl_dma!($peri, $channel, FrameDma, $request); | ||
| 384 | }; | ||
| 385 | ($peri:ident, dcmi, $kind:ident, DCMI, $channel:tt, $request:expr) => { | ||
| 386 | impl_dma!($peri, $channel, FrameDma, $request); | ||
| 387 | }; | ||
| 388 | } | ||
| 389 | |||
| 390 | macro_rules! impl_pin { | ||
| 391 | ($pin:ident, $signal:ident, $af:expr) => { | ||
| 392 | impl sealed::$signal for crate::peripherals::$pin { | ||
| 393 | fn configure(&mut self) { | ||
| 394 | // NOTE(unsafe) Exclusive access to the registers | ||
| 395 | critical_section::with(|_| unsafe { | ||
| 396 | self.set_as_af($af, crate::gpio::sealed::AFType::Input); | ||
| 397 | self.block().ospeedr().modify(|w| { | ||
| 398 | w.set_ospeedr( | ||
| 399 | self.pin() as usize, | ||
| 400 | crate::pac::gpio::vals::Ospeedr::VERYHIGHSPEED, | ||
| 401 | ) | ||
| 402 | }); | ||
| 403 | }) | ||
| 404 | } | ||
| 405 | } | ||
| 406 | |||
| 407 | impl $signal for crate::peripherals::$pin {} | ||
| 408 | }; | ||
| 409 | } | ||
| 410 | |||
| 411 | macro_rules! impl_no_pin { | ||
| 412 | ($signal:ident) => { | ||
| 413 | impl sealed::$signal for crate::gpio::NoPin { | ||
| 414 | fn configure(&mut self) {} | ||
| 415 | } | ||
| 416 | impl $signal for crate::gpio::NoPin {} | ||
| 417 | }; | ||
| 418 | } | ||
| 419 | |||
| 420 | impl_no_pin!(D8Pin); | ||
| 421 | impl_no_pin!(D9Pin); | ||
| 422 | impl_no_pin!(D10Pin); | ||
| 423 | impl_no_pin!(D11Pin); | ||
| 424 | impl_no_pin!(D12Pin); | ||
| 425 | impl_no_pin!(D13Pin); | ||
| 426 | impl_no_pin!(HSyncPin); | ||
| 427 | impl_no_pin!(VSyncPin); | ||
| 428 | |||
| 429 | crate::pac::peripheral_pins!( | ||
| 430 | ($inst:ident, dcmi, DCMI, $pin:ident, D0, $af:expr) => { | ||
| 431 | impl_pin!($pin, D0Pin, $af); | ||
| 432 | }; | ||
| 433 | ($inst:ident, dcmi, DCMI, $pin:ident, D1, $af:expr) => { | ||
| 434 | impl_pin!($pin, D1Pin, $af); | ||
| 435 | }; | ||
| 436 | ($inst:ident, dcmi, DCMI, $pin:ident, D2, $af:expr) => { | ||
| 437 | impl_pin!($pin, D2Pin, $af); | ||
| 438 | }; | ||
| 439 | ($inst:ident, dcmi, DCMI, $pin:ident, D3, $af:expr) => { | ||
| 440 | impl_pin!($pin, D3Pin, $af); | ||
| 441 | }; | ||
| 442 | ($inst:ident, dcmi, DCMI, $pin:ident, D4, $af:expr) => { | ||
| 443 | impl_pin!($pin, D4Pin, $af); | ||
| 444 | }; | ||
| 445 | ($inst:ident, dcmi, DCMI, $pin:ident, D5, $af:expr) => { | ||
| 446 | impl_pin!($pin, D5Pin, $af); | ||
| 447 | }; | ||
| 448 | ($inst:ident, dcmi, DCMI, $pin:ident, D6, $af:expr) => { | ||
| 449 | impl_pin!($pin, D6Pin, $af); | ||
| 450 | }; | ||
| 451 | ($inst:ident, dcmi, DCMI, $pin:ident, D7, $af:expr) => { | ||
| 452 | impl_pin!($pin, D7Pin, $af); | ||
| 453 | }; | ||
| 454 | ($inst:ident, dcmi, DCMI, $pin:ident, D8, $af:expr) => { | ||
| 455 | impl_pin!($pin, D8Pin, $af); | ||
| 456 | }; | ||
| 457 | ($inst:ident, dcmi, DCMI, $pin:ident, D9, $af:expr) => { | ||
| 458 | impl_pin!($pin, D9Pin, $af); | ||
| 459 | }; | ||
| 460 | ($inst:ident, dcmi, DCMI, $pin:ident, D10, $af:expr) => { | ||
| 461 | impl_pin!($pin, D10Pin, $af); | ||
| 462 | }; | ||
| 463 | ($inst:ident, dcmi, DCMI, $pin:ident, D11, $af:expr) => { | ||
| 464 | impl_pin!($pin, D11Pin, $af); | ||
| 465 | }; | ||
| 466 | ($inst:ident, dcmi, DCMI, $pin:ident, D12, $af:expr) => { | ||
| 467 | impl_pin!($pin, D12Pin, $af); | ||
| 468 | }; | ||
| 469 | ($inst:ident, dcmi, DCMI, $pin:ident, D13, $af:expr) => { | ||
| 470 | impl_pin!($pin, D13Pin, $af); | ||
| 471 | }; | ||
| 472 | ($inst:ident, dcmi, DCMI, $pin:ident, HSYNC, $af:expr) => { | ||
| 473 | impl_pin!($pin, HSyncPin, $af); | ||
| 474 | }; | ||
| 475 | ($inst:ident, dcmi, DCMI, $pin:ident, VSYNC, $af:expr) => { | ||
| 476 | impl_pin!($pin, VSyncPin, $af); | ||
| 477 | }; | ||
| 478 | ($inst:ident, dcmi, DCMI, $pin:ident, PIXCLK, $af:expr) => { | ||
| 479 | impl_pin!($pin, PixClkPin, $af); | ||
| 480 | }; | ||
| 481 | ); | ||
diff --git a/embassy-stm32/src/lib.rs b/embassy-stm32/src/lib.rs index 649b25f10..425516a3f 100644 --- a/embassy-stm32/src/lib.rs +++ b/embassy-stm32/src/lib.rs | |||
| @@ -32,6 +32,8 @@ pub mod can; | |||
| 32 | pub mod dac; | 32 | pub mod dac; |
| 33 | #[cfg(dbgmcu)] | 33 | #[cfg(dbgmcu)] |
| 34 | pub mod dbgmcu; | 34 | pub mod dbgmcu; |
| 35 | #[cfg(dcmi)] | ||
| 36 | pub mod dcmi; | ||
| 35 | #[cfg(all(eth, feature = "net"))] | 37 | #[cfg(all(eth, feature = "net"))] |
| 36 | pub mod eth; | 38 | pub mod eth; |
| 37 | #[cfg(exti)] | 39 | #[cfg(exti)] |
diff --git a/embassy-stm32/src/rcc/h7/mod.rs b/embassy-stm32/src/rcc/h7/mod.rs index dc458a8a3..be7f440a1 100644 --- a/embassy-stm32/src/rcc/h7/mod.rs +++ b/embassy-stm32/src/rcc/h7/mod.rs | |||
| @@ -73,6 +73,7 @@ pub struct Config { | |||
| 73 | pub pll2: PllConfig, | 73 | pub pll2: PllConfig, |
| 74 | pub pll3: PllConfig, | 74 | pub pll3: PllConfig, |
| 75 | pub enable_dma1: bool, | 75 | pub enable_dma1: bool, |
| 76 | pub enable_dma2: bool, | ||
| 76 | } | 77 | } |
| 77 | 78 | ||
| 78 | pub struct Rcc<'d> { | 79 | pub struct Rcc<'d> { |
| @@ -334,6 +335,10 @@ impl<'d> Rcc<'d> { | |||
| 334 | RCC.ahb1enr().modify(|w| w.set_dma1en(true)); | 335 | RCC.ahb1enr().modify(|w| w.set_dma1en(true)); |
| 335 | } | 336 | } |
| 336 | 337 | ||
| 338 | if self.config.enable_dma2 { | ||
| 339 | RCC.ahb1enr().modify(|w| w.set_dma2en(true)); | ||
| 340 | } | ||
| 341 | |||
| 337 | CoreClocks { | 342 | CoreClocks { |
| 338 | hclk: Hertz(rcc_hclk), | 343 | hclk: Hertz(rcc_hclk), |
| 339 | pclk1: Hertz(rcc_pclk1), | 344 | pclk1: Hertz(rcc_pclk1), |
diff --git a/examples/stm32h7/Cargo.toml b/examples/stm32h7/Cargo.toml index dc31c6b52..393e779e4 100644 --- a/examples/stm32h7/Cargo.toml +++ b/examples/stm32h7/Cargo.toml | |||
| @@ -30,4 +30,42 @@ micromath = "2.0.0" | |||
| 30 | git = "https://github.com/smoltcp-rs/smoltcp" | 30 | git = "https://github.com/smoltcp-rs/smoltcp" |
| 31 | rev = "3644b94b82d9433313c75281fdc78942c2450bdf" | 31 | rev = "3644b94b82d9433313c75281fdc78942c2450bdf" |
| 32 | default-features = false | 32 | default-features = false |
| 33 | features = ["defmt"] \ No newline at end of file | 33 | features = ["defmt"] |
| 34 | |||
| 35 | # cargo build/run | ||
| 36 | [profile.dev] | ||
| 37 | codegen-units = 1 | ||
| 38 | debug = 2 | ||
| 39 | debug-assertions = true # <- | ||
| 40 | incremental = false | ||
| 41 | opt-level = 3 # <- | ||
| 42 | overflow-checks = true # <- | ||
| 43 | |||
| 44 | # cargo test | ||
| 45 | [profile.test] | ||
| 46 | codegen-units = 1 | ||
| 47 | debug = 2 | ||
| 48 | debug-assertions = true # <- | ||
| 49 | incremental = false | ||
| 50 | opt-level = 3 # <- | ||
| 51 | overflow-checks = true # <- | ||
| 52 | |||
| 53 | # cargo build/run --release | ||
| 54 | [profile.release] | ||
| 55 | codegen-units = 1 | ||
| 56 | debug = 2 | ||
| 57 | debug-assertions = false # <- | ||
| 58 | incremental = false | ||
| 59 | lto = 'fat' | ||
| 60 | opt-level = 3 # <- | ||
| 61 | overflow-checks = false # <- | ||
| 62 | |||
| 63 | # cargo test --release | ||
| 64 | [profile.bench] | ||
| 65 | codegen-units = 1 | ||
| 66 | debug = 2 | ||
| 67 | debug-assertions = false # <- | ||
| 68 | incremental = false | ||
| 69 | lto = 'fat' | ||
| 70 | opt-level = 3 # <- | ||
| 71 | overflow-checks = false # <- \ No newline at end of file | ||
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 | } | ||
