diff --git a/ci.sh b/ci.sh index b04a4b288..9f7a7a037 100755 --- a/ci.sh +++ b/ci.sh @@ -216,6 +216,7 @@ cargo batch \ --- build --release --manifest-path examples/stm32g4/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32g4 \ --- build --release --manifest-path examples/stm32h5/Cargo.toml --target thumbv8m.main-none-eabihf --out-dir out/examples/stm32h5 \ --- build --release --manifest-path examples/stm32h7/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32h7 \ + --- build --release --manifest-path examples/stm32h7b0/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32h7b0 \ --- build --release --manifest-path examples/stm32h735/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32h735 \ --- build --release --manifest-path examples/stm32h755cm4/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32h755cm4 \ --- build --release --manifest-path examples/stm32h755cm7/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32h755cm7 \ diff --git a/embassy-stm32/src/ospi/mod.rs b/embassy-stm32/src/ospi/mod.rs index e4adc4b09..f8ef66216 100644 --- a/embassy-stm32/src/ospi/mod.rs +++ b/embassy-stm32/src/ospi/mod.rs @@ -179,6 +179,71 @@ pub struct Ospi<'d, T: Instance, M: PeriMode> { } impl<'d, T: Instance, M: PeriMode> Ospi<'d, T, M> { + /// Enter memory mode. + /// The Input `read_config` is used to configure the read operation in memory mode + pub fn enable_memory_mapped_mode( + &mut self, + read_config: TransferConfig, + write_config: TransferConfig, + ) -> Result<(), OspiError> { + // Use configure command to set read config + self.configure_command(&read_config, None)?; + + let reg = T::REGS; + while reg.sr().read().busy() {} + + reg.ccr().modify(|r| { + r.set_dqse(false); + r.set_sioo(true); + }); + + // Set wrting configurations, there are separate registers for write configurations in memory mapped mode + reg.wccr().modify(|w| { + w.set_imode(PhaseMode::from_bits(write_config.iwidth.into())); + w.set_idtr(write_config.idtr); + w.set_isize(SizeInBits::from_bits(write_config.isize.into())); + + w.set_admode(PhaseMode::from_bits(write_config.adwidth.into())); + w.set_addtr(write_config.idtr); + w.set_adsize(SizeInBits::from_bits(write_config.adsize.into())); + + w.set_dmode(PhaseMode::from_bits(write_config.dwidth.into())); + w.set_ddtr(write_config.ddtr); + + w.set_abmode(PhaseMode::from_bits(write_config.abwidth.into())); + w.set_dqse(true); + }); + + reg.wtcr().modify(|w| w.set_dcyc(write_config.dummy.into())); + + // Enable memory mapped mode + reg.cr().modify(|r| { + r.set_fmode(crate::ospi::vals::FunctionalMode::MEMORYMAPPED); + r.set_tcen(false); + }); + Ok(()) + } + + /// Quit from memory mapped mode + pub fn disable_memory_mapped_mode(&mut self) { + let reg = T::REGS; + + reg.cr().modify(|r| { + r.set_fmode(crate::ospi::vals::FunctionalMode::INDIRECTWRITE); + r.set_abort(true); + r.set_dmaen(false); + r.set_en(false); + }); + + // Clear transfer complete flag + reg.fcr().write(|w| w.set_ctcf(true)); + + // Re-enable ospi + reg.cr().modify(|r| { + r.set_en(true); + }); + } + fn new_inner( peri: impl Peripheral

+ 'd, d0: Option>, diff --git a/examples/stm32h7b0/.cargo/config.toml b/examples/stm32h7b0/.cargo/config.toml new file mode 100644 index 000000000..870849a27 --- /dev/null +++ b/examples/stm32h7b0/.cargo/config.toml @@ -0,0 +1,8 @@ +[target.thumbv7em-none-eabihf] +runner = 'probe-rs run --chip STM32H7B0VBTx' + +[build] +target = "thumbv7em-none-eabihf" # Cortex-M4F and Cortex-M7F (with FPU) + +[env] +DEFMT_LOG = "trace" diff --git a/examples/stm32h7b0/Cargo.toml b/examples/stm32h7b0/Cargo.toml new file mode 100644 index 000000000..02c620443 --- /dev/null +++ b/examples/stm32h7b0/Cargo.toml @@ -0,0 +1,74 @@ +[package] +edition = "2021" +name = "embassy-stm32h7b0-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32h7b0vb", "time-driver-tim2", "exti", "memory-x", "unstable-pac", "chrono"] } +embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-embedded-hal = { version = "0.2.0", path = "../../embassy-embedded-hal" } +embassy-executor = { version = "0.6.1", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", "proto-ipv6", "dns"] } +embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } +embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } + +defmt = "0.3" +defmt-rtt = "0.4" + +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" +embedded-hal = "0.2.6" +embedded-hal-1 = { package = "embedded-hal", version = "1.0" } +embedded-hal-async = { version = "1.0" } +embedded-nal-async = "0.8.0" +embedded-io-async = { version = "0.6.1" } +panic-probe = { version = "0.3", features = ["print-defmt"] } +heapless = { version = "0.8", default-features = false } +rand_core = "0.6.3" +critical-section = "1.1" +micromath = "2.0.0" +stm32-fmc = "0.3.0" +embedded-storage = "0.3.1" +static_cell = "2" +chrono = { version = "^0.4", default-features = false } +grounded = "0.2.0" + +# cargo build/run +[profile.dev] +codegen-units = 1 +debug = 2 +debug-assertions = true # <- +incremental = false +opt-level = 3 # <- +overflow-checks = true # <- + +# cargo test +[profile.test] +codegen-units = 1 +debug = 2 +debug-assertions = true # <- +incremental = false +opt-level = 3 # <- +overflow-checks = true # <- + +# cargo build/run --release +[profile.release] +codegen-units = 1 +debug = 2 +debug-assertions = false # <- +incremental = false +lto = 'fat' +opt-level = 3 # <- +overflow-checks = false # <- + +# cargo test --release +[profile.bench] +codegen-units = 1 +debug = 2 +debug-assertions = false # <- +incremental = false +lto = 'fat' +opt-level = 3 # <- +overflow-checks = false # <- diff --git a/examples/stm32h7b0/build.rs b/examples/stm32h7b0/build.rs new file mode 100644 index 000000000..30691aa97 --- /dev/null +++ b/examples/stm32h7b0/build.rs @@ -0,0 +1,35 @@ +//! This build script copies the `memory.x` file from the crate root into +//! a directory where the linker can always find it at build time. +//! For many projects this is optional, as the linker always searches the +//! project root directory -- wherever `Cargo.toml` is. However, if you +//! are using a workspace or have a more complicated build setup, this +//! build script becomes required. Additionally, by requesting that +//! Cargo re-run the build script whenever `memory.x` is changed, +//! updating `memory.x` ensures a rebuild of the application with the +//! new memory settings. + +use std::env; +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +fn main() { + // Put `memory.x` in our output directory and ensure it's + // on the linker search path. + let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + File::create(out.join("memory.x")) + .unwrap() + .write_all(include_bytes!("memory.x")) + .unwrap(); + println!("cargo:rustc-link-search={}", out.display()); + + // By default, Cargo will re-run a build script whenever + // any file in the project changes. By specifying `memory.x` + // here, we ensure the build script is only re-run when + // `memory.x` is changed. + println!("cargo:rerun-if-changed=memory.x"); + + println!("cargo:rustc-link-arg-bins=--nmagic"); + println!("cargo:rustc-link-arg-bins=-Tlink.x"); + println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); +} diff --git a/examples/stm32h7b0/memory.x b/examples/stm32h7b0/memory.x new file mode 100644 index 000000000..6eb1bb7c1 --- /dev/null +++ b/examples/stm32h7b0/memory.x @@ -0,0 +1,5 @@ +MEMORY +{ + FLASH : ORIGIN = 0x08000000, LENGTH = 128K /* BANK_1 */ + RAM : ORIGIN = 0x24000000, LENGTH = 512K /* SRAM */ +} \ No newline at end of file diff --git a/examples/stm32h7b0/src/bin/ospi_memory_mapped.rs b/examples/stm32h7b0/src/bin/ospi_memory_mapped.rs new file mode 100644 index 000000000..9c397e507 --- /dev/null +++ b/examples/stm32h7b0/src/bin/ospi_memory_mapped.rs @@ -0,0 +1,433 @@ +#![no_main] +#![no_std] + +// Tested on weact stm32h7b0 board + w25q64 spi flash + +use defmt::info; +use embassy_executor::Spawner; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_stm32::mode::Blocking; +use embassy_stm32::ospi::{ + AddressSize, ChipSelectHighTime, DummyCycles, FIFOThresholdLevel, Instance, MemorySize, MemoryType, Ospi, + OspiWidth, TransferConfig, WrapSize, +}; +use embassy_stm32::time::Hertz; +use embassy_stm32::Config; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + // RCC config + let mut config = Config::default(); + info!("START"); + { + use embassy_stm32::rcc::*; + config.rcc.hsi = Some(HSIPrescaler::DIV1); + config.rcc.csi = true; + // Needed for USB + config.rcc.hsi48 = Some(Hsi48Config { sync_from_usb: true }); + // External oscillator 25MHZ + config.rcc.hse = Some(Hse { + freq: Hertz(25_000_000), + mode: HseMode::Oscillator, + }); + config.rcc.pll1 = Some(Pll { + source: PllSource::HSE, + prediv: PllPreDiv::DIV5, + mul: PllMul::MUL112, + divp: Some(PllDiv::DIV2), + divq: Some(PllDiv::DIV2), + divr: Some(PllDiv::DIV2), + }); + config.rcc.sys = Sysclk::PLL1_P; + config.rcc.ahb_pre = AHBPrescaler::DIV2; + config.rcc.apb1_pre = APBPrescaler::DIV2; + config.rcc.apb2_pre = APBPrescaler::DIV2; + config.rcc.apb3_pre = APBPrescaler::DIV2; + config.rcc.apb4_pre = APBPrescaler::DIV2; + config.rcc.voltage_scale = VoltageScale::Scale0; + } + + // Initialize peripherals + let p = embassy_stm32::init(config); + + let qspi_config = embassy_stm32::ospi::Config { + fifo_threshold: FIFOThresholdLevel::_16Bytes, + memory_type: MemoryType::Micron, + device_size: MemorySize::_8MiB, + chip_select_high_time: ChipSelectHighTime::_1Cycle, + free_running_clock: false, + clock_mode: false, + wrap_size: WrapSize::None, + clock_prescaler: 4, + sample_shifting: true, + delay_hold_quarter_cycle: false, + chip_select_boundary: 0, + delay_block_bypass: true, + max_transfer: 0, + refresh: 0, + }; + let ospi = embassy_stm32::ospi::Ospi::new_blocking_quadspi( + p.OCTOSPI1, + p.PB2, + p.PD11, + p.PD12, + p.PE2, + p.PD13, + p.PB6, + qspi_config, + ); + + let mut flash = FlashMemory::new(ospi).await; + + let flash_id = flash.read_id(); + info!("FLASH ID: {=[u8]:x}", flash_id); + let mut wr_buf = [0u8; 8]; + for i in 0..8 { + wr_buf[i] = i as u8; + } + let mut rd_buf = [0u8; 8]; + flash.erase_sector(0).await; + flash.write_memory(0, &wr_buf, true).await; + flash.read_memory(0, &mut rd_buf, true); + info!("WRITE BUF: {=[u8]:#X}", wr_buf); + info!("READ BUF: {=[u8]:#X}", rd_buf); + flash.enable_mm().await; + info!("Enabled memory mapped mode"); + + let first_u32 = unsafe { *(0x90000000 as *const u32) }; + assert_eq!(first_u32, 0x03020100); + + let second_u32 = unsafe { *(0x90000004 as *const u32) }; + assert_eq!(second_u32, 0x07060504); + flash.disable_mm().await; + + info!("DONE"); + // Output pin PE3 + let mut led = Output::new(p.PE3, Level::Low, Speed::Low); + + loop { + led.toggle(); + Timer::after_millis(1000).await; + } +} + +const MEMORY_PAGE_SIZE: usize = 8; + +const CMD_QUAD_READ: u8 = 0x6B; + +const CMD_QUAD_WRITE_PG: u8 = 0x32; + +const CMD_READ_ID: u8 = 0x9F; + +const CMD_ENABLE_RESET: u8 = 0x66; +const CMD_RESET: u8 = 0x99; + +const CMD_WRITE_ENABLE: u8 = 0x06; + +const CMD_CHIP_ERASE: u8 = 0xC7; +const CMD_SECTOR_ERASE: u8 = 0x20; +const CMD_BLOCK_ERASE_32K: u8 = 0x52; +const CMD_BLOCK_ERASE_64K: u8 = 0xD8; + +const CMD_READ_SR: u8 = 0x05; +const CMD_READ_CR: u8 = 0x35; + +const CMD_WRITE_SR: u8 = 0x01; +const CMD_WRITE_CR: u8 = 0x31; + +/// Implementation of access to flash chip. +/// Chip commands are hardcoded as it depends on used chip. +/// This implementation is using chip GD25Q64C from Giga Device +pub struct FlashMemory { + ospi: Ospi<'static, I, Blocking>, +} + +impl FlashMemory { + pub async fn new(ospi: Ospi<'static, I, Blocking>) -> Self { + let mut memory = Self { ospi }; + + memory.reset_memory().await; + memory.enable_quad(); + memory + } + + async fn qpi_mode(&mut self) { + // Enter qpi mode + self.exec_command(0x38).await; + + // Set read param + let transaction = TransferConfig { + iwidth: OspiWidth::QUAD, + dwidth: OspiWidth::QUAD, + instruction: Some(0xC0), + ..Default::default() + }; + self.enable_write().await; + self.ospi.blocking_write(&[0x30_u8], transaction).unwrap(); + self.wait_write_finish(); + } + + pub async fn disable_mm(&mut self) { + self.ospi.disable_memory_mapped_mode(); + } + + pub async fn enable_mm(&mut self) { + self.qpi_mode().await; + + let read_config = TransferConfig { + iwidth: OspiWidth::QUAD, + isize: AddressSize::_8Bit, + adwidth: OspiWidth::QUAD, + adsize: AddressSize::_24bit, + dwidth: OspiWidth::QUAD, + instruction: Some(0x0B), // Fast read in QPI mode + dummy: DummyCycles::_8, + ..Default::default() + }; + + let write_config = TransferConfig { + iwidth: OspiWidth::SING, + isize: AddressSize::_8Bit, + adwidth: OspiWidth::SING, + adsize: AddressSize::_24bit, + dwidth: OspiWidth::QUAD, + instruction: Some(0x32), // Write config + dummy: DummyCycles::_0, + ..Default::default() + }; + self.ospi.enable_memory_mapped_mode(read_config, write_config).unwrap(); + } + + fn enable_quad(&mut self) { + let cr = self.read_cr(); + // info!("Read cr: {:x}", cr); + self.write_cr(cr | 0x02); + // info!("Read cr after writing: {:x}", cr); + } + + pub fn disable_quad(&mut self) { + let cr = self.read_cr(); + self.write_cr(cr & (!(0x02))); + } + + async fn exec_command_4(&mut self, cmd: u8) { + let transaction = TransferConfig { + iwidth: OspiWidth::QUAD, + adwidth: OspiWidth::NONE, + // adsize: AddressSize::_24bit, + dwidth: OspiWidth::NONE, + instruction: Some(cmd as u32), + address: None, + dummy: DummyCycles::_0, + ..Default::default() + }; + self.ospi.command(&transaction).await.unwrap(); + } + + async fn exec_command(&mut self, cmd: u8) { + let transaction = TransferConfig { + iwidth: OspiWidth::SING, + adwidth: OspiWidth::NONE, + // adsize: AddressSize::_24bit, + dwidth: OspiWidth::NONE, + instruction: Some(cmd as u32), + address: None, + dummy: DummyCycles::_0, + ..Default::default() + }; + // info!("Excuting command: {:x}", transaction.instruction); + self.ospi.command(&transaction).await.unwrap(); + } + + pub async fn reset_memory(&mut self) { + self.exec_command_4(CMD_ENABLE_RESET).await; + self.exec_command_4(CMD_RESET).await; + self.exec_command(CMD_ENABLE_RESET).await; + self.exec_command(CMD_RESET).await; + self.wait_write_finish(); + } + + pub async fn enable_write(&mut self) { + self.exec_command(CMD_WRITE_ENABLE).await; + } + + pub fn read_id(&mut self) -> [u8; 3] { + let mut buffer = [0; 3]; + let transaction: TransferConfig = TransferConfig { + iwidth: OspiWidth::SING, + isize: AddressSize::_8Bit, + adwidth: OspiWidth::NONE, + // adsize: AddressSize::_24bit, + dwidth: OspiWidth::SING, + instruction: Some(CMD_READ_ID as u32), + ..Default::default() + }; + // info!("Reading id: 0x{:X}", transaction.instruction); + self.ospi.blocking_read(&mut buffer, transaction).unwrap(); + buffer + } + + pub fn read_id_4(&mut self) -> [u8; 3] { + let mut buffer = [0; 3]; + let transaction: TransferConfig = TransferConfig { + iwidth: OspiWidth::SING, + isize: AddressSize::_8Bit, + adwidth: OspiWidth::NONE, + dwidth: OspiWidth::QUAD, + instruction: Some(CMD_READ_ID as u32), + ..Default::default() + }; + info!("Reading id: 0x{:X}", transaction.instruction); + self.ospi.blocking_read(&mut buffer, transaction).unwrap(); + buffer + } + + pub fn read_memory(&mut self, addr: u32, buffer: &mut [u8], use_dma: bool) { + let transaction = TransferConfig { + iwidth: OspiWidth::SING, + adwidth: OspiWidth::SING, + adsize: AddressSize::_24bit, + dwidth: OspiWidth::QUAD, + instruction: Some(CMD_QUAD_READ as u32), + address: Some(addr), + dummy: DummyCycles::_8, + ..Default::default() + }; + if use_dma { + self.ospi.blocking_read(buffer, transaction).unwrap(); + } else { + self.ospi.blocking_read(buffer, transaction).unwrap(); + } + } + + fn wait_write_finish(&mut self) { + while (self.read_sr() & 0x01) != 0 {} + } + + async fn perform_erase(&mut self, addr: u32, cmd: u8) { + let transaction = TransferConfig { + iwidth: OspiWidth::SING, + adwidth: OspiWidth::SING, + adsize: AddressSize::_24bit, + dwidth: OspiWidth::NONE, + instruction: Some(cmd as u32), + address: Some(addr), + dummy: DummyCycles::_0, + ..Default::default() + }; + self.enable_write().await; + self.ospi.command(&transaction).await.unwrap(); + self.wait_write_finish(); + } + + pub async fn erase_sector(&mut self, addr: u32) { + self.perform_erase(addr, CMD_SECTOR_ERASE).await; + } + + pub async fn erase_block_32k(&mut self, addr: u32) { + self.perform_erase(addr, CMD_BLOCK_ERASE_32K).await; + } + + pub async fn erase_block_64k(&mut self, addr: u32) { + self.perform_erase(addr, CMD_BLOCK_ERASE_64K).await; + } + + pub async fn erase_chip(&mut self) { + self.exec_command(CMD_CHIP_ERASE).await; + } + + async fn write_page(&mut self, addr: u32, buffer: &[u8], len: usize, use_dma: bool) { + assert!( + (len as u32 + (addr & 0x000000ff)) <= MEMORY_PAGE_SIZE as u32, + "write_page(): page write length exceeds page boundary (len = {}, addr = {:X}", + len, + addr + ); + + let transaction = TransferConfig { + iwidth: OspiWidth::SING, + adsize: AddressSize::_24bit, + adwidth: OspiWidth::SING, + dwidth: OspiWidth::QUAD, + instruction: Some(CMD_QUAD_WRITE_PG as u32), + address: Some(addr), + dummy: DummyCycles::_0, + ..Default::default() + }; + self.enable_write().await; + if use_dma { + self.ospi.blocking_write(buffer, transaction).unwrap(); + } else { + self.ospi.blocking_write(buffer, transaction).unwrap(); + } + self.wait_write_finish(); + } + + pub async fn write_memory(&mut self, addr: u32, buffer: &[u8], use_dma: bool) { + let mut left = buffer.len(); + let mut place = addr; + let mut chunk_start = 0; + + while left > 0 { + let max_chunk_size = MEMORY_PAGE_SIZE - (place & 0x000000ff) as usize; + let chunk_size = if left >= max_chunk_size { max_chunk_size } else { left }; + let chunk = &buffer[chunk_start..(chunk_start + chunk_size)]; + self.write_page(place, chunk, chunk_size, use_dma).await; + place += chunk_size as u32; + left -= chunk_size; + chunk_start += chunk_size; + } + } + + fn read_register(&mut self, cmd: u8) -> u8 { + let mut buffer = [0; 1]; + let transaction: TransferConfig = TransferConfig { + iwidth: OspiWidth::SING, + isize: AddressSize::_8Bit, + adwidth: OspiWidth::NONE, + adsize: AddressSize::_24bit, + dwidth: OspiWidth::SING, + instruction: Some(cmd as u32), + address: None, + dummy: DummyCycles::_0, + ..Default::default() + }; + self.ospi.blocking_read(&mut buffer, transaction).unwrap(); + // info!("Read w25q64 register: 0x{:x}", buffer[0]); + buffer[0] + } + + fn write_register(&mut self, cmd: u8, value: u8) { + let buffer = [value; 1]; + let transaction: TransferConfig = TransferConfig { + iwidth: OspiWidth::SING, + isize: AddressSize::_8Bit, + instruction: Some(cmd as u32), + adsize: AddressSize::_24bit, + adwidth: OspiWidth::NONE, + dwidth: OspiWidth::SING, + address: None, + dummy: DummyCycles::_0, + ..Default::default() + }; + self.ospi.blocking_write(&buffer, transaction).unwrap(); + } + + pub fn read_sr(&mut self) -> u8 { + self.read_register(CMD_READ_SR) + } + + pub fn read_cr(&mut self) -> u8 { + self.read_register(CMD_READ_CR) + } + + pub fn write_sr(&mut self, value: u8) { + self.write_register(CMD_WRITE_SR, value); + } + + pub fn write_cr(&mut self, value: u8) { + self.write_register(CMD_WRITE_CR, value); + } +}