diff options
| author | Olof <[email protected]> | 2024-12-18 01:48:25 +0100 |
|---|---|---|
| committer | GitHub <[email protected]> | 2024-12-18 01:48:25 +0100 |
| commit | 7cf96e4730964d085015320648c870a05fbaf431 (patch) | |
| tree | 04072529b62082cb66443377b589fe08169f83be /examples/stm32u5/src | |
| parent | 8678911028a591d72fd1d8418407b5885ed4c417 (diff) | |
| parent | 341036a8b865609767fbf9015b482ea70ed4f23f (diff) | |
Merge branch 'embassy-rs:main' into u5_adc
Diffstat (limited to 'examples/stm32u5/src')
| -rw-r--r-- | examples/stm32u5/src/bin/ferris.bmp | bin | 0 -> 6794 bytes | |||
| -rw-r--r-- | examples/stm32u5/src/bin/i2c.rs | 2 | ||||
| -rw-r--r-- | examples/stm32u5/src/bin/ltdc.rs | 461 | ||||
| -rw-r--r-- | examples/stm32u5/src/bin/tsc.rs | 75 | ||||
| -rw-r--r-- | examples/stm32u5/src/bin/usb_hs_serial.rs | 129 | ||||
| -rw-r--r-- | examples/stm32u5/src/bin/usb_serial.rs | 4 |
6 files changed, 625 insertions, 46 deletions
diff --git a/examples/stm32u5/src/bin/ferris.bmp b/examples/stm32u5/src/bin/ferris.bmp new file mode 100644 index 000000000..7a222ab84 --- /dev/null +++ b/examples/stm32u5/src/bin/ferris.bmp | |||
| Binary files differ | |||
diff --git a/examples/stm32u5/src/bin/i2c.rs b/examples/stm32u5/src/bin/i2c.rs index 19a78eac9..d5f5d6f60 100644 --- a/examples/stm32u5/src/bin/i2c.rs +++ b/examples/stm32u5/src/bin/i2c.rs | |||
| @@ -13,7 +13,7 @@ const WHOAMI: u8 = 0x0F; | |||
| 13 | #[embassy_executor::main] | 13 | #[embassy_executor::main] |
| 14 | async fn main(_spawner: Spawner) { | 14 | async fn main(_spawner: Spawner) { |
| 15 | let p = embassy_stm32::init(Default::default()); | 15 | let p = embassy_stm32::init(Default::default()); |
| 16 | let mut i2c = I2c::new_blocking(p.I2C2, p.PH4, p.PH5, Hertz(100_000), Default::default()); | 16 | let mut i2c = I2c::new_blocking(p.I2C2, p.PF1, p.PF0, Hertz(100_000), Default::default()); |
| 17 | 17 | ||
| 18 | let mut data = [0u8; 1]; | 18 | let mut data = [0u8; 1]; |
| 19 | unwrap!(i2c.blocking_write_read(HTS221_ADDRESS, &[WHOAMI], &mut data)); | 19 | unwrap!(i2c.blocking_write_read(HTS221_ADDRESS, &[WHOAMI], &mut data)); |
diff --git a/examples/stm32u5/src/bin/ltdc.rs b/examples/stm32u5/src/bin/ltdc.rs new file mode 100644 index 000000000..bd59a9148 --- /dev/null +++ b/examples/stm32u5/src/bin/ltdc.rs | |||
| @@ -0,0 +1,461 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![no_main] | ||
| 3 | #![macro_use] | ||
| 4 | #![allow(static_mut_refs)] | ||
| 5 | |||
| 6 | /// This example was derived from examples\stm32h735\src\bin\ltdc.rs | ||
| 7 | /// It demonstrates the LTDC lcd display peripheral and was tested on an STM32U5G9J-DK2 demo board (embassy-stm32 feature "stm32u5g9zj" and probe-rs chip "STM32U5G9ZJTxQ") | ||
| 8 | /// | ||
| 9 | use bouncy_box::BouncyBox; | ||
| 10 | use defmt::{info, unwrap}; | ||
| 11 | use embassy_executor::Spawner; | ||
| 12 | use embassy_stm32::gpio::{Level, Output, Speed}; | ||
| 13 | use embassy_stm32::ltdc::{self, Ltdc, LtdcConfiguration, LtdcLayer, LtdcLayerConfig, PolarityActive, PolarityEdge}; | ||
| 14 | use embassy_stm32::{bind_interrupts, peripherals}; | ||
| 15 | use embassy_time::{Duration, Timer}; | ||
| 16 | use embedded_graphics::draw_target::DrawTarget; | ||
| 17 | use embedded_graphics::geometry::{OriginDimensions, Point, Size}; | ||
| 18 | use embedded_graphics::image::Image; | ||
| 19 | use embedded_graphics::pixelcolor::raw::RawU24; | ||
| 20 | use embedded_graphics::pixelcolor::Rgb888; | ||
| 21 | use embedded_graphics::prelude::*; | ||
| 22 | use embedded_graphics::primitives::Rectangle; | ||
| 23 | use embedded_graphics::Pixel; | ||
| 24 | use heapless::{Entry, FnvIndexMap}; | ||
| 25 | use tinybmp::Bmp; | ||
| 26 | use {defmt_rtt as _, panic_probe as _}; | ||
| 27 | |||
| 28 | const DISPLAY_WIDTH: usize = 800; | ||
| 29 | const DISPLAY_HEIGHT: usize = 480; | ||
| 30 | const MY_TASK_POOL_SIZE: usize = 2; | ||
| 31 | |||
| 32 | // the following two display buffers consume 261120 bytes that just about fits into axis ram found on the mcu | ||
| 33 | pub static mut FB1: [TargetPixelType; DISPLAY_WIDTH * DISPLAY_HEIGHT] = [0; DISPLAY_WIDTH * DISPLAY_HEIGHT]; | ||
| 34 | pub static mut FB2: [TargetPixelType; DISPLAY_WIDTH * DISPLAY_HEIGHT] = [0; DISPLAY_WIDTH * DISPLAY_HEIGHT]; | ||
| 35 | |||
| 36 | bind_interrupts!(struct Irqs { | ||
| 37 | LTDC => ltdc::InterruptHandler<peripherals::LTDC>; | ||
| 38 | }); | ||
| 39 | |||
| 40 | const NUM_COLORS: usize = 256; | ||
| 41 | |||
| 42 | #[embassy_executor::main] | ||
| 43 | async fn main(spawner: Spawner) { | ||
| 44 | let p = rcc_setup::stm32u5g9zj_init(); | ||
| 45 | |||
| 46 | // enable ICACHE | ||
| 47 | embassy_stm32::pac::ICACHE.cr().write(|w| { | ||
| 48 | w.set_en(true); | ||
| 49 | }); | ||
| 50 | |||
| 51 | // blink the led on another task | ||
| 52 | let led = Output::new(p.PD2, Level::High, Speed::Low); | ||
| 53 | unwrap!(spawner.spawn(led_task(led))); | ||
| 54 | |||
| 55 | // numbers from STM32U5G9J-DK2.ioc | ||
| 56 | const RK050HR18H_HSYNC: u16 = 5; // Horizontal synchronization | ||
| 57 | const RK050HR18H_HBP: u16 = 8; // Horizontal back porch | ||
| 58 | const RK050HR18H_HFP: u16 = 8; // Horizontal front porch | ||
| 59 | const RK050HR18H_VSYNC: u16 = 5; // Vertical synchronization | ||
| 60 | const RK050HR18H_VBP: u16 = 8; // Vertical back porch | ||
| 61 | const RK050HR18H_VFP: u16 = 8; // Vertical front porch | ||
| 62 | |||
| 63 | // NOTE: all polarities have to be reversed with respect to the STM32U5G9J-DK2 CubeMX parametrization | ||
| 64 | let ltdc_config = LtdcConfiguration { | ||
| 65 | active_width: DISPLAY_WIDTH as _, | ||
| 66 | active_height: DISPLAY_HEIGHT as _, | ||
| 67 | h_back_porch: RK050HR18H_HBP, | ||
| 68 | h_front_porch: RK050HR18H_HFP, | ||
| 69 | v_back_porch: RK050HR18H_VBP, | ||
| 70 | v_front_porch: RK050HR18H_VFP, | ||
| 71 | h_sync: RK050HR18H_HSYNC, | ||
| 72 | v_sync: RK050HR18H_VSYNC, | ||
| 73 | h_sync_polarity: PolarityActive::ActiveHigh, | ||
| 74 | v_sync_polarity: PolarityActive::ActiveHigh, | ||
| 75 | data_enable_polarity: PolarityActive::ActiveHigh, | ||
| 76 | pixel_clock_polarity: PolarityEdge::RisingEdge, | ||
| 77 | }; | ||
| 78 | |||
| 79 | info!("init ltdc"); | ||
| 80 | let mut ltdc_de = Output::new(p.PD6, Level::Low, Speed::High); | ||
| 81 | let mut ltdc_disp_ctrl = Output::new(p.PE4, Level::Low, Speed::High); | ||
| 82 | let mut ltdc_bl_ctrl = Output::new(p.PE6, Level::Low, Speed::High); | ||
| 83 | let mut ltdc = Ltdc::new_with_pins( | ||
| 84 | p.LTDC, // PERIPHERAL | ||
| 85 | Irqs, // IRQS | ||
| 86 | p.PD3, // CLK | ||
| 87 | p.PE0, // HSYNC | ||
| 88 | p.PD13, // VSYNC | ||
| 89 | p.PB9, // B0 | ||
| 90 | p.PB2, // B1 | ||
| 91 | p.PD14, // B2 | ||
| 92 | p.PD15, // B3 | ||
| 93 | p.PD0, // B4 | ||
| 94 | p.PD1, // B5 | ||
| 95 | p.PE7, // B6 | ||
| 96 | p.PE8, // B7 | ||
| 97 | p.PC8, // G0 | ||
| 98 | p.PC9, // G1 | ||
| 99 | p.PE9, // G2 | ||
| 100 | p.PE10, // G3 | ||
| 101 | p.PE11, // G4 | ||
| 102 | p.PE12, // G5 | ||
| 103 | p.PE13, // G6 | ||
| 104 | p.PE14, // G7 | ||
| 105 | p.PC6, // R0 | ||
| 106 | p.PC7, // R1 | ||
| 107 | p.PE15, // R2 | ||
| 108 | p.PD8, // R3 | ||
| 109 | p.PD9, // R4 | ||
| 110 | p.PD10, // R5 | ||
| 111 | p.PD11, // R6 | ||
| 112 | p.PD12, // R7 | ||
| 113 | ); | ||
| 114 | ltdc.init(<dc_config); | ||
| 115 | ltdc_de.set_low(); | ||
| 116 | ltdc_bl_ctrl.set_high(); | ||
| 117 | ltdc_disp_ctrl.set_high(); | ||
| 118 | |||
| 119 | // we only need to draw on one layer for this example (not to be confused with the double buffer) | ||
| 120 | info!("enable bottom layer"); | ||
| 121 | let layer_config = LtdcLayerConfig { | ||
| 122 | pixel_format: ltdc::PixelFormat::L8, // 1 byte per pixel | ||
| 123 | layer: LtdcLayer::Layer1, | ||
| 124 | window_x0: 0, | ||
| 125 | window_x1: DISPLAY_WIDTH as _, | ||
| 126 | window_y0: 0, | ||
| 127 | window_y1: DISPLAY_HEIGHT as _, | ||
| 128 | }; | ||
| 129 | |||
| 130 | let ferris_bmp: Bmp<Rgb888> = Bmp::from_slice(include_bytes!("./ferris.bmp")).unwrap(); | ||
| 131 | let color_map = build_color_lookup_map(&ferris_bmp); | ||
| 132 | let clut = build_clut(&color_map); | ||
| 133 | |||
| 134 | // enable the bottom layer with a 256 color lookup table | ||
| 135 | ltdc.init_layer(&layer_config, Some(&clut)); | ||
| 136 | |||
| 137 | // Safety: the DoubleBuffer controls access to the statically allocated frame buffers | ||
| 138 | // and it is the only thing that mutates their content | ||
| 139 | let mut double_buffer = DoubleBuffer::new( | ||
| 140 | unsafe { FB1.as_mut() }, | ||
| 141 | unsafe { FB2.as_mut() }, | ||
| 142 | layer_config, | ||
| 143 | color_map, | ||
| 144 | ); | ||
| 145 | |||
| 146 | // this allows us to perform some simple animation for every frame | ||
| 147 | let mut bouncy_box = BouncyBox::new( | ||
| 148 | ferris_bmp.bounding_box(), | ||
| 149 | Rectangle::new(Point::zero(), Size::new(DISPLAY_WIDTH as u32, DISPLAY_HEIGHT as u32)), | ||
| 150 | 2, | ||
| 151 | ); | ||
| 152 | |||
| 153 | loop { | ||
| 154 | // cpu intensive drawing to the buffer that is NOT currently being copied to the LCD screen | ||
| 155 | double_buffer.clear(); | ||
| 156 | let position = bouncy_box.next_point(); | ||
| 157 | let ferris = Image::new(&ferris_bmp, position); | ||
| 158 | unwrap!(ferris.draw(&mut double_buffer)); | ||
| 159 | |||
| 160 | // perform async dma data transfer to the lcd screen | ||
| 161 | unwrap!(double_buffer.swap(&mut ltdc).await); | ||
| 162 | } | ||
| 163 | } | ||
| 164 | |||
| 165 | /// builds the color look-up table from all unique colors found in the bitmap. This should be a 256 color indexed bitmap to work. | ||
| 166 | fn build_color_lookup_map(bmp: &Bmp<Rgb888>) -> FnvIndexMap<u32, u8, NUM_COLORS> { | ||
| 167 | let mut color_map: FnvIndexMap<u32, u8, NUM_COLORS> = heapless::FnvIndexMap::new(); | ||
| 168 | let mut counter: u8 = 0; | ||
| 169 | |||
| 170 | // add black to position 0 | ||
| 171 | color_map.insert(Rgb888::new(0, 0, 0).into_storage(), counter).unwrap(); | ||
| 172 | counter += 1; | ||
| 173 | |||
| 174 | for Pixel(_point, color) in bmp.pixels() { | ||
| 175 | let raw = color.into_storage(); | ||
| 176 | if let Entry::Vacant(v) = color_map.entry(raw) { | ||
| 177 | v.insert(counter).expect("more than 256 colors detected"); | ||
| 178 | counter += 1; | ||
| 179 | } | ||
| 180 | } | ||
| 181 | color_map | ||
| 182 | } | ||
| 183 | |||
| 184 | /// builds the color look-up table from the color map provided | ||
| 185 | fn build_clut(color_map: &FnvIndexMap<u32, u8, NUM_COLORS>) -> [ltdc::RgbColor; NUM_COLORS] { | ||
| 186 | let mut clut = [ltdc::RgbColor::default(); NUM_COLORS]; | ||
| 187 | for (color, index) in color_map.iter() { | ||
| 188 | let color = Rgb888::from(RawU24::new(*color)); | ||
| 189 | clut[*index as usize] = ltdc::RgbColor { | ||
| 190 | red: color.r(), | ||
| 191 | green: color.g(), | ||
| 192 | blue: color.b(), | ||
| 193 | }; | ||
| 194 | } | ||
| 195 | |||
| 196 | clut | ||
| 197 | } | ||
| 198 | |||
| 199 | #[embassy_executor::task(pool_size = MY_TASK_POOL_SIZE)] | ||
| 200 | async fn led_task(mut led: Output<'static>) { | ||
| 201 | let mut counter = 0; | ||
| 202 | loop { | ||
| 203 | info!("blink: {}", counter); | ||
| 204 | counter += 1; | ||
| 205 | |||
| 206 | // on | ||
| 207 | led.set_low(); | ||
| 208 | Timer::after(Duration::from_millis(50)).await; | ||
| 209 | |||
| 210 | // off | ||
| 211 | led.set_high(); | ||
| 212 | Timer::after(Duration::from_millis(450)).await; | ||
| 213 | } | ||
| 214 | } | ||
| 215 | |||
| 216 | pub type TargetPixelType = u8; | ||
| 217 | |||
| 218 | // A simple double buffer | ||
| 219 | pub struct DoubleBuffer { | ||
| 220 | buf0: &'static mut [TargetPixelType], | ||
| 221 | buf1: &'static mut [TargetPixelType], | ||
| 222 | is_buf0: bool, | ||
| 223 | layer_config: LtdcLayerConfig, | ||
| 224 | color_map: FnvIndexMap<u32, u8, NUM_COLORS>, | ||
| 225 | } | ||
| 226 | |||
| 227 | impl DoubleBuffer { | ||
| 228 | pub fn new( | ||
| 229 | buf0: &'static mut [TargetPixelType], | ||
| 230 | buf1: &'static mut [TargetPixelType], | ||
| 231 | layer_config: LtdcLayerConfig, | ||
| 232 | color_map: FnvIndexMap<u32, u8, NUM_COLORS>, | ||
| 233 | ) -> Self { | ||
| 234 | Self { | ||
| 235 | buf0, | ||
| 236 | buf1, | ||
| 237 | is_buf0: true, | ||
| 238 | layer_config, | ||
| 239 | color_map, | ||
| 240 | } | ||
| 241 | } | ||
| 242 | |||
| 243 | pub fn current(&mut self) -> (&FnvIndexMap<u32, u8, NUM_COLORS>, &mut [TargetPixelType]) { | ||
| 244 | if self.is_buf0 { | ||
| 245 | (&self.color_map, self.buf0) | ||
| 246 | } else { | ||
| 247 | (&self.color_map, self.buf1) | ||
| 248 | } | ||
| 249 | } | ||
| 250 | |||
| 251 | pub async fn swap<T: ltdc::Instance>(&mut self, ltdc: &mut Ltdc<'_, T>) -> Result<(), ltdc::Error> { | ||
| 252 | let (_, buf) = self.current(); | ||
| 253 | let frame_buffer = buf.as_ptr(); | ||
| 254 | self.is_buf0 = !self.is_buf0; | ||
| 255 | ltdc.set_buffer(self.layer_config.layer, frame_buffer as *const _).await | ||
| 256 | } | ||
| 257 | |||
| 258 | /// Clears the buffer | ||
| 259 | pub fn clear(&mut self) { | ||
| 260 | let (color_map, buf) = self.current(); | ||
| 261 | let black = Rgb888::new(0, 0, 0).into_storage(); | ||
| 262 | let color_index = color_map.get(&black).expect("no black found in the color map"); | ||
| 263 | |||
| 264 | for a in buf.iter_mut() { | ||
| 265 | *a = *color_index; // solid black | ||
| 266 | } | ||
| 267 | } | ||
| 268 | } | ||
| 269 | |||
| 270 | // Implement DrawTarget for | ||
| 271 | impl DrawTarget for DoubleBuffer { | ||
| 272 | type Color = Rgb888; | ||
| 273 | type Error = (); | ||
| 274 | |||
| 275 | /// Draw a pixel | ||
| 276 | fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error> | ||
| 277 | where | ||
| 278 | I: IntoIterator<Item = Pixel<Self::Color>>, | ||
| 279 | { | ||
| 280 | let size = self.size(); | ||
| 281 | let width = size.width as i32; | ||
| 282 | let height = size.height as i32; | ||
| 283 | let (color_map, buf) = self.current(); | ||
| 284 | |||
| 285 | for pixel in pixels { | ||
| 286 | let Pixel(point, color) = pixel; | ||
| 287 | |||
| 288 | if point.x >= 0 && point.y >= 0 && point.x < width && point.y < height { | ||
| 289 | let index = point.y * width + point.x; | ||
| 290 | let raw_color = color.into_storage(); | ||
| 291 | |||
| 292 | match color_map.get(&raw_color) { | ||
| 293 | Some(x) => { | ||
| 294 | buf[index as usize] = *x; | ||
| 295 | } | ||
| 296 | None => panic!("color not found in color map: {}", raw_color), | ||
| 297 | }; | ||
| 298 | } else { | ||
| 299 | // Ignore invalid points | ||
| 300 | } | ||
| 301 | } | ||
| 302 | |||
| 303 | Ok(()) | ||
| 304 | } | ||
| 305 | } | ||
| 306 | |||
| 307 | impl OriginDimensions for DoubleBuffer { | ||
| 308 | /// Return the size of the display | ||
| 309 | fn size(&self) -> Size { | ||
| 310 | Size::new( | ||
| 311 | (self.layer_config.window_x1 - self.layer_config.window_x0) as _, | ||
| 312 | (self.layer_config.window_y1 - self.layer_config.window_y0) as _, | ||
| 313 | ) | ||
| 314 | } | ||
| 315 | } | ||
| 316 | |||
| 317 | mod rcc_setup { | ||
| 318 | |||
| 319 | use embassy_stm32::time::Hertz; | ||
| 320 | use embassy_stm32::{rcc, Config, Peripherals}; | ||
| 321 | |||
| 322 | /// Sets up clocks for the stm32u5g9zj mcu | ||
| 323 | /// change this if you plan to use a different microcontroller | ||
| 324 | pub fn stm32u5g9zj_init() -> Peripherals { | ||
| 325 | // setup power and clocks for an STM32U5G9J-DK2 run from an external 16 Mhz external oscillator | ||
| 326 | let mut config = Config::default(); | ||
| 327 | config.rcc.hse = Some(rcc::Hse { | ||
| 328 | freq: Hertz(16_000_000), | ||
| 329 | mode: rcc::HseMode::Oscillator, | ||
| 330 | }); | ||
| 331 | config.rcc.pll1 = Some(rcc::Pll { | ||
| 332 | source: rcc::PllSource::HSE, | ||
| 333 | prediv: rcc::PllPreDiv::DIV1, | ||
| 334 | mul: rcc::PllMul::MUL10, | ||
| 335 | divp: None, | ||
| 336 | divq: None, | ||
| 337 | divr: Some(rcc::PllDiv::DIV1), | ||
| 338 | }); | ||
| 339 | config.rcc.sys = rcc::Sysclk::PLL1_R; // 160 Mhz | ||
| 340 | config.rcc.pll3 = Some(rcc::Pll { | ||
| 341 | source: rcc::PllSource::HSE, | ||
| 342 | prediv: rcc::PllPreDiv::DIV4, // PLL_M | ||
| 343 | mul: rcc::PllMul::MUL125, // PLL_N | ||
| 344 | divp: None, | ||
| 345 | divq: None, | ||
| 346 | divr: Some(rcc::PllDiv::DIV20), | ||
| 347 | }); | ||
| 348 | config.rcc.mux.ltdcsel = rcc::mux::Ltdcsel::PLL3_R; // 25 MHz | ||
| 349 | embassy_stm32::init(config) | ||
| 350 | } | ||
| 351 | } | ||
| 352 | |||
| 353 | mod bouncy_box { | ||
| 354 | use embedded_graphics::geometry::Point; | ||
| 355 | use embedded_graphics::primitives::Rectangle; | ||
| 356 | |||
| 357 | enum Direction { | ||
| 358 | DownLeft, | ||
| 359 | DownRight, | ||
| 360 | UpLeft, | ||
| 361 | UpRight, | ||
| 362 | } | ||
| 363 | |||
| 364 | pub struct BouncyBox { | ||
| 365 | direction: Direction, | ||
| 366 | child_rect: Rectangle, | ||
| 367 | parent_rect: Rectangle, | ||
| 368 | current_point: Point, | ||
| 369 | move_by: usize, | ||
| 370 | } | ||
| 371 | |||
| 372 | // This calculates the coordinates of a chile rectangle bounced around inside a parent bounded box | ||
| 373 | impl BouncyBox { | ||
| 374 | pub fn new(child_rect: Rectangle, parent_rect: Rectangle, move_by: usize) -> Self { | ||
| 375 | let center_box = parent_rect.center(); | ||
| 376 | let center_img = child_rect.center(); | ||
| 377 | let current_point = Point::new(center_box.x - center_img.x / 2, center_box.y - center_img.y / 2); | ||
| 378 | Self { | ||
| 379 | direction: Direction::DownRight, | ||
| 380 | child_rect, | ||
| 381 | parent_rect, | ||
| 382 | current_point, | ||
| 383 | move_by, | ||
| 384 | } | ||
| 385 | } | ||
| 386 | |||
| 387 | pub fn next_point(&mut self) -> Point { | ||
| 388 | let direction = &self.direction; | ||
| 389 | let img_height = self.child_rect.size.height as i32; | ||
| 390 | let box_height = self.parent_rect.size.height as i32; | ||
| 391 | let img_width = self.child_rect.size.width as i32; | ||
| 392 | let box_width = self.parent_rect.size.width as i32; | ||
| 393 | let move_by = self.move_by as i32; | ||
| 394 | |||
| 395 | match direction { | ||
| 396 | Direction::DownLeft => { | ||
| 397 | self.current_point.x -= move_by; | ||
| 398 | self.current_point.y += move_by; | ||
| 399 | |||
| 400 | let x_out_of_bounds = self.current_point.x < 0; | ||
| 401 | let y_out_of_bounds = (self.current_point.y + img_height) > box_height; | ||
| 402 | |||
| 403 | if x_out_of_bounds && y_out_of_bounds { | ||
| 404 | self.direction = Direction::UpRight | ||
| 405 | } else if x_out_of_bounds && !y_out_of_bounds { | ||
| 406 | self.direction = Direction::DownRight | ||
| 407 | } else if !x_out_of_bounds && y_out_of_bounds { | ||
| 408 | self.direction = Direction::UpLeft | ||
| 409 | } | ||
| 410 | } | ||
| 411 | Direction::DownRight => { | ||
| 412 | self.current_point.x += move_by; | ||
| 413 | self.current_point.y += move_by; | ||
| 414 | |||
| 415 | let x_out_of_bounds = (self.current_point.x + img_width) > box_width; | ||
| 416 | let y_out_of_bounds = (self.current_point.y + img_height) > box_height; | ||
| 417 | |||
| 418 | if x_out_of_bounds && y_out_of_bounds { | ||
| 419 | self.direction = Direction::UpLeft | ||
| 420 | } else if x_out_of_bounds && !y_out_of_bounds { | ||
| 421 | self.direction = Direction::DownLeft | ||
| 422 | } else if !x_out_of_bounds && y_out_of_bounds { | ||
| 423 | self.direction = Direction::UpRight | ||
| 424 | } | ||
| 425 | } | ||
| 426 | Direction::UpLeft => { | ||
| 427 | self.current_point.x -= move_by; | ||
| 428 | self.current_point.y -= move_by; | ||
| 429 | |||
| 430 | let x_out_of_bounds = self.current_point.x < 0; | ||
| 431 | let y_out_of_bounds = self.current_point.y < 0; | ||
| 432 | |||
| 433 | if x_out_of_bounds && y_out_of_bounds { | ||
| 434 | self.direction = Direction::DownRight | ||
| 435 | } else if x_out_of_bounds && !y_out_of_bounds { | ||
| 436 | self.direction = Direction::UpRight | ||
| 437 | } else if !x_out_of_bounds && y_out_of_bounds { | ||
| 438 | self.direction = Direction::DownLeft | ||
| 439 | } | ||
| 440 | } | ||
| 441 | Direction::UpRight => { | ||
| 442 | self.current_point.x += move_by; | ||
| 443 | self.current_point.y -= move_by; | ||
| 444 | |||
| 445 | let x_out_of_bounds = (self.current_point.x + img_width) > box_width; | ||
| 446 | let y_out_of_bounds = self.current_point.y < 0; | ||
| 447 | |||
| 448 | if x_out_of_bounds && y_out_of_bounds { | ||
| 449 | self.direction = Direction::DownLeft | ||
| 450 | } else if x_out_of_bounds && !y_out_of_bounds { | ||
| 451 | self.direction = Direction::UpLeft | ||
| 452 | } else if !x_out_of_bounds && y_out_of_bounds { | ||
| 453 | self.direction = Direction::DownRight | ||
| 454 | } | ||
| 455 | } | ||
| 456 | } | ||
| 457 | |||
| 458 | self.current_point | ||
| 459 | } | ||
| 460 | } | ||
| 461 | } | ||
diff --git a/examples/stm32u5/src/bin/tsc.rs b/examples/stm32u5/src/bin/tsc.rs index eb15d275a..a85acc4c7 100644 --- a/examples/stm32u5/src/bin/tsc.rs +++ b/examples/stm32u5/src/bin/tsc.rs | |||
| @@ -2,8 +2,8 @@ | |||
| 2 | #![no_main] | 2 | #![no_main] |
| 3 | 3 | ||
| 4 | use defmt::*; | 4 | use defmt::*; |
| 5 | use embassy_stm32::bind_interrupts; | ||
| 6 | use embassy_stm32::tsc::{self, *}; | 5 | use embassy_stm32::tsc::{self, *}; |
| 6 | use embassy_stm32::{bind_interrupts, peripherals}; | ||
| 7 | use embassy_time::Timer; | 7 | use embassy_time::Timer; |
| 8 | use {defmt_rtt as _, panic_probe as _}; | 8 | use {defmt_rtt as _, panic_probe as _}; |
| 9 | 9 | ||
| @@ -33,63 +33,52 @@ async fn main(_spawner: embassy_executor::Spawner) { | |||
| 33 | synchro_pin_polarity: false, | 33 | synchro_pin_polarity: false, |
| 34 | acquisition_mode: false, | 34 | acquisition_mode: false, |
| 35 | max_count_interrupt: false, | 35 | max_count_interrupt: false, |
| 36 | channel_ios: TscIOPin::Group2Io2 | TscIOPin::Group7Io3, | ||
| 37 | shield_ios: TscIOPin::Group1Io3.into(), | ||
| 38 | sampling_ios: TscIOPin::Group1Io2 | TscIOPin::Group2Io1 | TscIOPin::Group7Io2, | ||
| 39 | }; | 36 | }; |
| 40 | 37 | ||
| 41 | let mut g1: PinGroup<embassy_stm32::peripherals::TSC, G1> = PinGroup::new(); | 38 | let mut g1: PinGroupWithRoles<peripherals::TSC, G1> = PinGroupWithRoles::default(); |
| 42 | g1.set_io2(context.PB13, PinType::Sample); | 39 | g1.set_io2::<tsc::pin_roles::Sample>(context.PB13); |
| 43 | g1.set_io3(context.PB14, PinType::Shield); | 40 | g1.set_io3::<tsc::pin_roles::Shield>(context.PB14); |
| 44 | 41 | ||
| 45 | let mut g2: PinGroup<embassy_stm32::peripherals::TSC, G2> = PinGroup::new(); | 42 | let mut g2: PinGroupWithRoles<peripherals::TSC, G2> = PinGroupWithRoles::default(); |
| 46 | g2.set_io1(context.PB4, PinType::Sample); | 43 | g2.set_io1::<tsc::pin_roles::Sample>(context.PB4); |
| 47 | g2.set_io2(context.PB5, PinType::Channel); | 44 | let sensor0 = g2.set_io2(context.PB5); |
| 48 | 45 | ||
| 49 | let mut g7: PinGroup<embassy_stm32::peripherals::TSC, G7> = PinGroup::new(); | 46 | let mut g7: PinGroupWithRoles<peripherals::TSC, G7> = PinGroupWithRoles::default(); |
| 50 | g7.set_io2(context.PE3, PinType::Sample); | 47 | g7.set_io2::<tsc::pin_roles::Sample>(context.PE3); |
| 51 | g7.set_io3(context.PE4, PinType::Channel); | 48 | let sensor1 = g7.set_io3(context.PE4); |
| 52 | 49 | ||
| 53 | let mut touch_controller = tsc::Tsc::new_async( | 50 | let pin_groups: PinGroups<peripherals::TSC> = PinGroups { |
| 54 | context.TSC, | 51 | g1: Some(g1.pin_group), |
| 55 | Some(g1), | 52 | g2: Some(g2.pin_group), |
| 56 | Some(g2), | 53 | g7: Some(g7.pin_group), |
| 57 | None, | 54 | ..Default::default() |
| 58 | None, | 55 | }; |
| 59 | None, | 56 | |
| 60 | None, | 57 | let mut touch_controller = tsc::Tsc::new_async(context.TSC, pin_groups, config, Irqs).unwrap(); |
| 61 | Some(g7), | ||
| 62 | None, | ||
| 63 | config, | ||
| 64 | Irqs, | ||
| 65 | ); | ||
| 66 | 58 | ||
| 67 | touch_controller.discharge_io(true); | 59 | let acquisition_bank = touch_controller.create_acquisition_bank(AcquisitionBankPins { |
| 68 | Timer::after_millis(1).await; | 60 | g2_pin: Some(sensor0), |
| 61 | g7_pin: Some(sensor1), | ||
| 62 | ..Default::default() | ||
| 63 | }); | ||
| 69 | 64 | ||
| 70 | touch_controller.start(); | 65 | touch_controller.set_active_channels_bank(&acquisition_bank); |
| 71 | 66 | ||
| 72 | let mut group_two_val = 0; | ||
| 73 | let mut group_seven_val = 0; | ||
| 74 | info!("Starting touch_controller interface"); | 67 | info!("Starting touch_controller interface"); |
| 75 | loop { | 68 | loop { |
| 69 | touch_controller.start(); | ||
| 76 | touch_controller.pend_for_acquisition().await; | 70 | touch_controller.pend_for_acquisition().await; |
| 77 | touch_controller.discharge_io(true); | 71 | touch_controller.discharge_io(true); |
| 78 | Timer::after_millis(1).await; | 72 | Timer::after_millis(1).await; |
| 79 | 73 | ||
| 80 | if touch_controller.group_get_status(Group::Two) == GroupStatus::Complete { | 74 | let status = touch_controller.get_acquisition_bank_status(&acquisition_bank); |
| 81 | group_two_val = touch_controller.group_get_value(Group::Two); | ||
| 82 | } | ||
| 83 | 75 | ||
| 84 | if touch_controller.group_get_status(Group::Seven) == GroupStatus::Complete { | 76 | if status.all_complete() { |
| 85 | group_seven_val = touch_controller.group_get_value(Group::Seven); | 77 | let read_values = touch_controller.get_acquisition_bank_values(&acquisition_bank); |
| 78 | let group2_reading = read_values.get_group_reading(Group::Two).unwrap(); | ||
| 79 | let group7_reading = read_values.get_group_reading(Group::Seven).unwrap(); | ||
| 80 | info!("group 2 value: {}", group2_reading.sensor_value); | ||
| 81 | info!("group 7 value: {}", group7_reading.sensor_value); | ||
| 86 | } | 82 | } |
| 87 | |||
| 88 | info!( | ||
| 89 | "Group Two value: {}, Group Seven value: {},", | ||
| 90 | group_two_val, group_seven_val | ||
| 91 | ); | ||
| 92 | |||
| 93 | touch_controller.start(); | ||
| 94 | } | 83 | } |
| 95 | } | 84 | } |
diff --git a/examples/stm32u5/src/bin/usb_hs_serial.rs b/examples/stm32u5/src/bin/usb_hs_serial.rs new file mode 100644 index 000000000..5549e2cbb --- /dev/null +++ b/examples/stm32u5/src/bin/usb_hs_serial.rs | |||
| @@ -0,0 +1,129 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![no_main] | ||
| 3 | |||
| 4 | use defmt::{panic, *}; | ||
| 5 | use defmt_rtt as _; // global logger | ||
| 6 | use embassy_executor::Spawner; | ||
| 7 | use embassy_futures::join::join; | ||
| 8 | use embassy_stm32::usb::{Driver, Instance}; | ||
| 9 | use embassy_stm32::{bind_interrupts, peripherals, usb, Config}; | ||
| 10 | use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; | ||
| 11 | use embassy_usb::driver::EndpointError; | ||
| 12 | use embassy_usb::Builder; | ||
| 13 | use panic_probe as _; | ||
| 14 | |||
| 15 | bind_interrupts!(struct Irqs { | ||
| 16 | OTG_HS => usb::InterruptHandler<peripherals::USB_OTG_HS>; | ||
| 17 | }); | ||
| 18 | |||
| 19 | #[embassy_executor::main] | ||
| 20 | async fn main(_spawner: Spawner) { | ||
| 21 | info!("Hello World!"); | ||
| 22 | |||
| 23 | let mut config = Config::default(); | ||
| 24 | { | ||
| 25 | use embassy_stm32::rcc::*; | ||
| 26 | use embassy_stm32::time::Hertz; | ||
| 27 | config.rcc.hse = Some(Hse { | ||
| 28 | freq: Hertz(16_000_000), | ||
| 29 | mode: HseMode::Oscillator, | ||
| 30 | }); | ||
| 31 | config.rcc.pll1 = Some(Pll { | ||
| 32 | source: PllSource::HSE, | ||
| 33 | prediv: PllPreDiv::DIV2, // HSE / 2 = 8MHz | ||
| 34 | mul: PllMul::MUL60, // 8MHz * 60 = 480MHz | ||
| 35 | divr: Some(PllDiv::DIV3), // 480MHz / 3 = 160MHz (sys_ck) | ||
| 36 | divq: Some(PllDiv::DIV10), // 480MHz / 10 = 48MHz (USB) | ||
| 37 | divp: Some(PllDiv::DIV15), // 480MHz / 15 = 32MHz (USBOTG) | ||
| 38 | }); | ||
| 39 | config.rcc.mux.otghssel = mux::Otghssel::PLL1_P; | ||
| 40 | config.rcc.voltage_range = VoltageScale::RANGE1; | ||
| 41 | config.rcc.sys = Sysclk::PLL1_R; | ||
| 42 | } | ||
| 43 | |||
| 44 | let p = embassy_stm32::init(config); | ||
| 45 | |||
| 46 | // Create the driver, from the HAL. | ||
| 47 | let mut ep_out_buffer = [0u8; 256]; | ||
| 48 | let mut config = embassy_stm32::usb::Config::default(); | ||
| 49 | // Do not enable vbus_detection. This is a safe default that works in all boards. | ||
| 50 | // However, if your USB device is self-powered (can stay powered on if USB is unplugged), you need | ||
| 51 | // to enable vbus_detection to comply with the USB spec. If you enable it, the board | ||
| 52 | // has to support it or USB won't work at all. See docs on `vbus_detection` for details. | ||
| 53 | config.vbus_detection = false; | ||
| 54 | let driver = Driver::new_hs(p.USB_OTG_HS, Irqs, p.PA12, p.PA11, &mut ep_out_buffer, config); | ||
| 55 | |||
| 56 | // Create embassy-usb Config | ||
| 57 | let mut config = embassy_usb::Config::new(0xc0de, 0xcafe); | ||
| 58 | config.manufacturer = Some("Embassy"); | ||
| 59 | config.product = Some("USB-serial example"); | ||
| 60 | config.serial_number = Some("12345678"); | ||
| 61 | |||
| 62 | // Required for windows compatibility. | ||
| 63 | // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help | ||
| 64 | config.device_class = 0xEF; | ||
| 65 | config.device_sub_class = 0x02; | ||
| 66 | config.device_protocol = 0x01; | ||
| 67 | config.composite_with_iads = true; | ||
| 68 | |||
| 69 | // Create embassy-usb DeviceBuilder using the driver and config. | ||
| 70 | // It needs some buffers for building the descriptors. | ||
| 71 | let mut config_descriptor = [0; 256]; | ||
| 72 | let mut bos_descriptor = [0; 256]; | ||
| 73 | let mut control_buf = [0; 64]; | ||
| 74 | |||
| 75 | let mut state = State::new(); | ||
| 76 | |||
| 77 | let mut builder = Builder::new( | ||
| 78 | driver, | ||
| 79 | config, | ||
| 80 | &mut config_descriptor, | ||
| 81 | &mut bos_descriptor, | ||
| 82 | &mut [], // no msos descriptors | ||
| 83 | &mut control_buf, | ||
| 84 | ); | ||
| 85 | |||
| 86 | // Create classes on the builder. | ||
| 87 | let mut class = CdcAcmClass::new(&mut builder, &mut state, 64); | ||
| 88 | |||
| 89 | // Build the builder. | ||
| 90 | let mut usb = builder.build(); | ||
| 91 | |||
| 92 | // Run the USB device. | ||
| 93 | let usb_fut = usb.run(); | ||
| 94 | |||
| 95 | // Do stuff with the class! | ||
| 96 | let echo_fut = async { | ||
| 97 | loop { | ||
| 98 | class.wait_connection().await; | ||
| 99 | info!("Connected"); | ||
| 100 | let _ = echo(&mut class).await; | ||
| 101 | info!("Disconnected"); | ||
| 102 | } | ||
| 103 | }; | ||
| 104 | |||
| 105 | // Run everything concurrently. | ||
| 106 | // If we had made everything `'static` above instead, we could do this using separate tasks instead. | ||
| 107 | join(usb_fut, echo_fut).await; | ||
| 108 | } | ||
| 109 | |||
| 110 | struct Disconnected {} | ||
| 111 | |||
| 112 | impl From<EndpointError> for Disconnected { | ||
| 113 | fn from(val: EndpointError) -> Self { | ||
| 114 | match val { | ||
| 115 | EndpointError::BufferOverflow => panic!("Buffer overflow"), | ||
| 116 | EndpointError::Disabled => Disconnected {}, | ||
| 117 | } | ||
| 118 | } | ||
| 119 | } | ||
| 120 | |||
| 121 | async fn echo<'d, T: Instance + 'd>(class: &mut CdcAcmClass<'d, Driver<'d, T>>) -> Result<(), Disconnected> { | ||
| 122 | let mut buf = [0; 64]; | ||
| 123 | loop { | ||
| 124 | let n = class.read_packet(&mut buf).await?; | ||
| 125 | let data = &buf[..n]; | ||
| 126 | info!("data: {:x}", data); | ||
| 127 | class.write_packet(data).await?; | ||
| 128 | } | ||
| 129 | } | ||
diff --git a/examples/stm32u5/src/bin/usb_serial.rs b/examples/stm32u5/src/bin/usb_serial.rs index 4d56395da..4bb1a6079 100644 --- a/examples/stm32u5/src/bin/usb_serial.rs +++ b/examples/stm32u5/src/bin/usb_serial.rs | |||
| @@ -13,7 +13,7 @@ use embassy_usb::Builder; | |||
| 13 | use panic_probe as _; | 13 | use panic_probe as _; |
| 14 | 14 | ||
| 15 | bind_interrupts!(struct Irqs { | 15 | bind_interrupts!(struct Irqs { |
| 16 | OTG_FS => usb::InterruptHandler<peripherals::USB_OTG_FS>; | 16 | OTG_HS => usb::InterruptHandler<peripherals::USB_OTG_HS>; |
| 17 | }); | 17 | }); |
| 18 | 18 | ||
| 19 | #[embassy_executor::main] | 19 | #[embassy_executor::main] |
| @@ -48,7 +48,7 @@ async fn main(_spawner: Spawner) { | |||
| 48 | // to enable vbus_detection to comply with the USB spec. If you enable it, the board | 48 | // to enable vbus_detection to comply with the USB spec. If you enable it, the board |
| 49 | // has to support it or USB won't work at all. See docs on `vbus_detection` for details. | 49 | // has to support it or USB won't work at all. See docs on `vbus_detection` for details. |
| 50 | config.vbus_detection = false; | 50 | config.vbus_detection = false; |
| 51 | let driver = Driver::new_fs(p.USB_OTG_FS, Irqs, p.PA12, p.PA11, &mut ep_out_buffer, config); | 51 | let driver = Driver::new_hs(p.USB_OTG_HS, Irqs, p.PA12, p.PA11, &mut ep_out_buffer, config); |
| 52 | 52 | ||
| 53 | // Create embassy-usb Config | 53 | // Create embassy-usb Config |
| 54 | let mut config = embassy_usb::Config::new(0xc0de, 0xcafe); | 54 | let mut config = embassy_usb::Config::new(0xc0de, 0xcafe); |
