From b90948870b773f361130b00d074b20ddb84d7691 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Onur=20=C3=96zkan?= Date: Sat, 18 Jul 2026 15:19:47 +0300 Subject: [PATCH] boards/kakute-f7-mini-v3: add imu core infra MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Onur Özkan --- Cargo.toml | 6 + boards/kakute-f7-mini-v3/Cargo.toml | 3 + boards/kakute-f7-mini-v3/src/main.rs | 230 ++++++++++++++++++++++++--- libs/kaptan-spec/Cargo.toml | 3 + 4 files changed, 216 insertions(+), 26 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 03bd948..d807457 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,3 +11,9 @@ edition = "2024" license = "AGPL-3.0-only" repository = "https://github.com/Orkavian/kaptan" rust-version = "1.90" + +[workspace.lints.rust] +unsafe_op_in_unsafe_fn = "deny" + +[workspace.lints.clippy] +undocumented_unsafe_blocks = "deny" diff --git a/boards/kakute-f7-mini-v3/Cargo.toml b/boards/kakute-f7-mini-v3/Cargo.toml index eccad0a..4ce1cf9 100644 --- a/boards/kakute-f7-mini-v3/Cargo.toml +++ b/boards/kakute-f7-mini-v3/Cargo.toml @@ -6,6 +6,9 @@ repository.workspace = true rust-version.workspace = true version.workspace = true +[lints] +workspace = true + [features] firmware = [] diff --git a/boards/kakute-f7-mini-v3/src/main.rs b/boards/kakute-f7-mini-v3/src/main.rs index a3f5aef..a8afa6c 100644 --- a/boards/kakute-f7-mini-v3/src/main.rs +++ b/boards/kakute-f7-mini-v3/src/main.rs @@ -8,14 +8,18 @@ use panic_halt as _; mod app { #[shared] struct Shared { - // TODO + imu_data_ready_count: u32, } #[local] struct Local { - // TODO led: stm32f7::stm32f722::GPIOD, led_output_high: bool, + heartbeat_timer: stm32f7::stm32f722::TIM2, + last_data_ready_count: u32, + imu_spi: stm32f7::stm32f722::SPI1, + imu_chip_select: stm32f7::stm32f722::GPIOB, + data_ready_interrupt: stm32f7::stm32f722::EXTI, } #[init] @@ -31,39 +35,213 @@ mod app { .moder() .modify(|_, write| write.moder2().output()); + device + .RCC + .ahb1enr() + .modify(|_, write| write.gpioaen().set_bit()); + device.GPIOA.moder().modify(|_, write| { + write + .moder5() + .alternate() + .moder6() + .alternate() + .moder7() + .alternate() + .moder4() + .input() + }); + device.GPIOA.afrl().modify(|_, write| { + // SAFETY: AF5 selects SPI1 for PA5, PA6, and PA7. + unsafe { + write + .afrl5() + .bits(0b0101) + .afrl6() + .bits(0b0101) + .afrl7() + .bits(0b0101) + } + }); + + device + .RCC + .ahb1enr() + .modify(|_, write| write.gpioben().set_bit()); + device + .GPIOB + .moder() + .modify(|_, write| write.moder2().output()); + device.GPIOB.bsrr().write(|write| write.bs2().set_bit()); + + device + .RCC + .apb2enr() + .modify(|_, write| write.spi1en().set_bit()); + device.SPI1.cr1().write(|write| { + write + .mstr() + .master() + .br() + .div64() + .ssm() + .set_bit() + .ssi() + .set_bit() + }); + device.SPI1.cr2().modify(|_, write| write.frxth().set_bit()); + device.SPI1.cr1().modify(|_, write| write.spe().set_bit()); + + let mpu6000_detected = is_mpu6000(&device.SPI1, &device.GPIOB); + if mpu6000_detected { + write_mpu_register(&device.SPI1, &device.GPIOB, 0x6B, 0x01); + write_mpu_register(&device.SPI1, &device.GPIOB, 0x37, 0); + write_mpu_register(&device.SPI1, &device.GPIOB, 0x38, 0x01); + + device + .RCC + .apb2enr() + .modify(|_, write| write.syscfgen().set_bit()); + device.SYSCFG.exticr2().modify(|_, write| { + // SAFETY: EXTI4 source 0 selects GPIOA. + unsafe { write.exti4().bits(0) } + }); + device.EXTI.imr().modify(|_, write| write.im4().clear_bit()); + device.EXTI.rtsr().modify(|_, write| write.tr4().set_bit()); + device + .EXTI + .ftsr() + .modify(|_, write| write.tr4().clear_bit()); + device.EXTI.pr().write(|write| write.pr4().clear()); + device.EXTI.imr().modify(|_, write| write.im4().set_bit()); + } + + device + .RCC + .apb1enr() + .modify(|_, write| write.tim2en().set_bit()); + device.TIM2.cr1().modify(|_, write| write.cen().clear_bit()); + device.TIM2.psc().write(|write| { + // SAFETY: 15_999 fits in TIM2's 16-bit prescaler field. + unsafe { write.psc().bits(15_999) } + }); + device.TIM2.arr().write(|write| { + // SAFETY: 999 fits in TIM2's 32-bit auto-reload field. + unsafe { write.arr().bits(999) } + }); + device.TIM2.egr().write(|write| write.ug().set_bit()); + device.TIM2.sr().modify(|_, write| write.uif().clear_bit()); + if mpu6000_detected { + device.TIM2.dier().modify(|_, write| write.uie().set_bit()); + device.TIM2.cr1().modify(|_, write| write.cen().set_bit()); + } + ( - Shared {}, + Shared { + imu_data_ready_count: 0, + }, Local { led: device.GPIOD, led_output_high: false, + heartbeat_timer: device.TIM2, + last_data_ready_count: 0, + imu_spi: device.SPI1, + imu_chip_select: device.GPIOB, + data_ready_interrupt: device.EXTI, }, ) } - #[task(binds = EXTI4, priority = 15)] - fn imu_data_ready(_context: imu_data_ready::Context) { - // TODO - } - - #[idle(local = [led, led_output_high])] - fn idle(context: idle::Context) -> ! { + #[idle] + fn idle(_context: idle::Context) -> ! { loop { - if *context.local.led_output_high { - context - .local - .led - .bsrr() - .write(|write| write.br2().set_bit()); - } else { - context - .local - .led - .bsrr() - .write(|write| write.bs2().set_bit()); - } - - *context.local.led_output_high = !*context.local.led_output_high; - cortex_m::asm::delay(8_000_000); + cortex_m::asm::wfi(); } } + + #[task( + binds = EXTI4, + priority = 15, + shared = [imu_data_ready_count], + local = [imu_spi, imu_chip_select, data_ready_interrupt] + )] + fn imu_data_ready(mut context: imu_data_ready::Context) { + context + .local + .data_ready_interrupt + .pr() + .write(|write| write.pr4().clear()); + context.shared.imu_data_ready_count.lock(|count| { + *count = count.wrapping_add(1); + }); + } + + #[task( + binds = TIM2, + priority = 1, + shared = [imu_data_ready_count], + local = [led, led_output_high, heartbeat_timer, last_data_ready_count] + )] + fn led_heartbeat(mut context: led_heartbeat::Context) { + context + .local + .heartbeat_timer + .sr() + .modify(|_, write| write.uif().clear_bit()); + + let imu_data_ready_count = context.shared.imu_data_ready_count.lock(|count| *count); + if imu_data_ready_count == *context.local.last_data_ready_count { + *context.local.led_output_high = false; + } else { + *context.local.led_output_high = !*context.local.led_output_high; + *context.local.last_data_ready_count = imu_data_ready_count; + } + + if *context.local.led_output_high { + context + .local + .led + .bsrr() + .write(|write| write.br2().set_bit()); + } else { + context + .local + .led + .bsrr() + .write(|write| write.bs2().set_bit()); + } + } + + fn is_mpu6000(spi: &stm32f7::stm32f722::SPI1, chip_select: &stm32f7::stm32f722::GPIOB) -> bool { + chip_select.bsrr().write(|write| write.br2().set_bit()); + let _ = spi_transfer(spi, 0xF5); + let identity = spi_transfer(spi, 0); + while spi.sr().read().bsy().is_busy() {} + chip_select.bsrr().write(|write| write.bs2().set_bit()); + + identity == 0x68 + } + + fn spi_transfer(spi: &stm32f7::stm32f722::SPI1, byte: u8) -> u8 { + while spi.sr().read().txe().is_not_empty() {} + spi.dr8().write(|write| { + // SAFETY: `byte` is an 8-bit SPI frame and fits DR8 exactly. + unsafe { write.dr().bits(byte) } + }); + while spi.sr().read().rxne().is_empty() {} + + spi.dr8().read().dr().bits() + } + + fn write_mpu_register( + spi: &stm32f7::stm32f722::SPI1, + chip_select: &stm32f7::stm32f722::GPIOB, + register: u8, + value: u8, + ) { + chip_select.bsrr().write(|write| write.br2().set_bit()); + let _ = spi_transfer(spi, register); + let _ = spi_transfer(spi, value); + while spi.sr().read().bsy().is_busy() {} + chip_select.bsrr().write(|write| write.bs2().set_bit()); + } } diff --git a/libs/kaptan-spec/Cargo.toml b/libs/kaptan-spec/Cargo.toml index 6e4c084..0fc59cb 100644 --- a/libs/kaptan-spec/Cargo.toml +++ b/libs/kaptan-spec/Cargo.toml @@ -6,6 +6,9 @@ repository.workspace = true rust-version.workspace = true version.workspace = true +[lints] +workspace = true + [features] std = ["dep:syn"]