diff --git a/Cargo.toml b/Cargo.toml index d20979bd0ad4b7ad4b76d25659b3f85aa5b20d6b..72d86fccc0282a8d8adc7f03b5aaa6cfd1d6cd7a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,8 @@ embedded-hal = "1.0.0-rc.1" embedded-time = "0.12.1" embedded-io = "0.5.0" cfg-if = "1.0.0" +# For backward compatibility only. +embedded-hal-027 ={ package = "embedded-hal" , version = "0.2.7" } [dev-dependencies] memoffset = "0.9.0" diff --git a/examples/spi-demo/Cargo.toml b/examples/spi-demo/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..4c85797b76bcc5e3e9877b22f19e7ae1774ee891 --- /dev/null +++ b/examples/spi-demo/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "spi-demo" +version = "0.1.0" +edition = "2021" +publish = false + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +bl-soc = { path = "../..", features = ["bl808"] } +base-address = "0.0.0" +panic-halt = "0.2.0" +embedded-time = "0.12.1" +embedded-hal = "1.0.0-rc.1" +riscv = "0.10.1" +display-interface-spi = "0.4.1" +mipidsi = "0.7.1" +embedded-graphics = "0.8.1" + +[dependencies.bl-rom-rt] +git = "https://gitee.com/rustsbi/bl-rom-rt" +branch = "main" +default-features = false +features = ["bl808-d0"] diff --git a/examples/spi-demo/README.md b/examples/spi-demo/README.md new file mode 100644 index 0000000000000000000000000000000000000000..a1f7c9c3bf3176f4de476eb559cafa3159a65a17 --- /dev/null +++ b/examples/spi-demo/README.md @@ -0,0 +1,3 @@ +# SPI Demo + +The image `src/ferris.raw` is from [this project](https://github.com/almindor/st7789-examples). diff --git a/examples/spi-demo/build.rs b/examples/spi-demo/build.rs new file mode 100644 index 0000000000000000000000000000000000000000..3509edb78b16f69c40351e7e21ee482e492753bc --- /dev/null +++ b/examples/spi-demo/build.rs @@ -0,0 +1,3 @@ +fn main() { + println!("cargo:rustc-link-arg=-Tbl-rom-rt.ld"); +} diff --git a/examples/spi-demo/src/ferris.raw b/examples/spi-demo/src/ferris.raw new file mode 100644 index 0000000000000000000000000000000000000000..9733889c577bdcc9277858ce019abff5b7dd23d0 Binary files /dev/null and b/examples/spi-demo/src/ferris.raw differ diff --git a/examples/spi-demo/src/main.rs b/examples/spi-demo/src/main.rs new file mode 100644 index 0000000000000000000000000000000000000000..b99ba9abcf4bfd3eed0c7a60924089f5cc6755de --- /dev/null +++ b/examples/spi-demo/src/main.rs @@ -0,0 +1,72 @@ +// Build this example with: +// rustup target install riscv64imac-unknown-none-elf +// cargo build --target riscv64imac-unknown-none-elf --release -p spi-demo + +#![no_std] +#![no_main] + +use base_address::Static; +use bl_rom_rt::entry; +use bl_soc::{gpio::Pins, prelude::*, spi::Spi, GLB, SPI}; +use embedded_graphics::{ + draw_target::DrawTarget, + image::*, + mono_font::{ascii::FONT_10X20, MonoTextStyle}, + pixelcolor::Rgb565, + prelude::*, + text::Text, +}; +use embedded_hal::spi::MODE_0; +use mipidsi::options::ColorInversion; +use mipidsi::Builder; +use panic_halt as _; + +#[entry] +fn main() -> ! { + // values initialized by ROM runtime + let gpio: Pins> = unsafe { core::mem::transmute(()) }; + let glb: GLB> = unsafe { core::mem::transmute(()) }; + let spi: SPI> = unsafe { core::mem::transmute(()) }; + + // enable jtag + gpio.io0.into_jtag_d0(); + gpio.io1.into_jtag_d0(); + gpio.io2.into_jtag_d0(); + gpio.io3.into_jtag_d0(); + + let mut led = gpio.io8.into_floating_output(); + let mut led_state = PinState::Low; + + let spi_cs = gpio.io12.into_spi::<1>(); + let spi_mosi = gpio.io25.into_spi::<1>(); + let spi_clk = gpio.io19.into_spi::<1>(); + let lcd_dc = gpio.io13.into_floating_output(); + let mut lcd_bl = gpio.io11.into_floating_output(); + let lcd_rst = gpio.io24.into_floating_output(); + let spi_lcd = Spi::new(spi, (spi_clk, spi_mosi, spi_cs), MODE_0, &glb); + + let mut delay = riscv::delay::McycleDelay::new(40_000_000); + let di = display_interface_spi::SPIInterfaceNoCS::new(spi_lcd, lcd_dc); + + let mut display = Builder::st7789(di) + .with_invert_colors(ColorInversion::Inverted) + .init(&mut delay, Some(lcd_rst)) + .unwrap(); + lcd_bl.set_high().ok(); + display.clear(Rgb565::BLACK).unwrap(); + + let raw_image_data = ImageRawLE::new(include_bytes!("ferris.raw"), 86); + let ferris = Image::new(&raw_image_data, Point::new(0, 20)); + ferris.draw(&mut display).unwrap(); + + let style = MonoTextStyle::new(&FONT_10X20, Rgb565::WHITE); + Text::new("Hello World!", Point::new(10, 100), style) + .draw(&mut display) + .unwrap(); + + loop { + led.set_state(led_state).ok(); + led_state = !led_state; + unsafe { riscv::asm::delay(100_000) } + } +} diff --git a/src/glb/v2.rs b/src/glb/v2.rs index b1fd720d4af0ce97e8db7f7bf3ea112dcc37f4e1..4a6f3925948a5f3f8a14822bccfcb689382866dd 100644 --- a/src/glb/v2.rs +++ b/src/glb/v2.rs @@ -13,17 +13,19 @@ pub struct RegisterBlock { pub i2c_config: RW, _reserved2: [u8; 0x4B], pub pwm_config: RW, - _reserved3: [u8; 0x3b0], + _reserved3: [u8; 0x33b], + pub param_config: RW, + _reserved4: [u8; 0x70], // TODO: clock_config_0, clock_config_2, clock_config_3 registers /// Clock generation configuration 1. pub clock_config_1: RW, - _reserved4: [u8; 0x33c], + _reserved5: [u8; 0x33c], /// Generic Purpose Input/Output config. pub gpio_config: [RW; 46], - _reserved5: [u8; 0x148], + _reserved6: [u8; 0x148], /// Read value from Generic Purpose Input/Output pins. pub gpio_input: [RO; 2], - _reserved6: [u8; 0x18], + _reserved7: [u8; 0x18], /// Write value to Generic Purpose Input/Output pins. pub gpio_output: [RW; 2], /// Set pin output value to high. @@ -258,6 +260,62 @@ pub enum PwmSignal1 { BrushlessDcMotor = 1, } +/// Param configuration register. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)] +#[repr(transparent)] +pub struct ParamConfig(u32); + +impl ParamConfig { + const SPI_0_MASTER_MODE: u32 = 0x1 << 12; + const SPI_1_MASTER_MODE: u32 = 0x1 << 27; + + /// Set mode for Serial Peripheral Interface. + #[inline] + pub const fn set_spi_mode(self, mode: SpiMode) -> Self { + match mode { + SpiMode::Master => match I { + 0 => Self(self.0 | Self::SPI_0_MASTER_MODE), + 1 => Self(self.0 | Self::SPI_1_MASTER_MODE), + _ => unreachable!(), + }, + SpiMode::Slave => match I { + 0 => Self(self.0 & !Self::SPI_0_MASTER_MODE), + 1 => Self(self.0 & !Self::SPI_1_MASTER_MODE), + _ => unreachable!(), + }, + } + } + /// Get mode for Serial Peripheral Interface. + #[inline] + pub const fn spi_mode(self) -> SpiMode { + match I { + 0 => { + if self.0 & Self::SPI_0_MASTER_MODE != 0 { + SpiMode::Master + } else { + SpiMode::Slave + } + } + 1 => { + if self.0 & Self::SPI_1_MASTER_MODE != 0 { + SpiMode::Master + } else { + SpiMode::Slave + } + } + _ => unreachable!(), + } + } +} + +/// Mode for Serial Peripheral Interface Bus. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[repr(u8)] +pub enum SpiMode { + Master = 0, + Slave = 1, +} + /// Clock generation configuration register 1. #[derive(Clone, Copy, Debug, PartialEq, Eq, Default)] #[repr(transparent)] diff --git a/src/gpio.rs b/src/gpio.rs index 0651f5ff0c0a2accdb9977c3c175ca8eead0d96a..93d0d46e158d9413b294516e94faba161f7e0a99 100644 --- a/src/gpio.rs +++ b/src/gpio.rs @@ -245,6 +245,24 @@ impl OutputPin for Pin> { } } +// This part of implementation using `embedded_hal_027` is designed for backward compatibility of +// ecosystem crates, as some of them depends on embedded-hal v0.2.7 traits. +// We encourage ecosystem developers to use embedded-hal v1.0.0 traits; after that, this part of code +// would be removed in the future. +impl embedded_hal_027::digital::v2::OutputPin + for Pin> +{ + type Error = core::convert::Infallible; + #[inline] + fn set_low(&mut self) -> Result<(), Self::Error> { + ::set_low(self) + } + #[inline] + fn set_high(&mut self) -> Result<(), Self::Error> { + ::set_high(self) + } +} + // We do not support StatefulOutputPin and ToggleableOutputPin here, because the hardware does not // have such functionality to read back the previously set pin state. // It is recommended that users add a variable to store the pin state if necessary; see examples/gpio-demo. @@ -403,6 +421,45 @@ impl Pin> { } } +#[cfg(feature = "glb-v2")] +impl Pin { + /// Configures the pin to operate as a SPI pin. + #[inline] + pub fn into_spi(self) -> Pin> + where + Spi: Alternate, + { + let config = GpioConfig::RESET_VALUE + .enable_input() + .disable_output() + .enable_schmitt() + .set_pull(Pull::Up) + .set_drive(Drive::Drive0) + .set_function(Spi::::F); + unsafe { + self.base.gpio_config[N].write(config); + } + + Pin { + base: self.base, + _mode: PhantomData, + } + } +} + +/// Serial Peripheral Interface mode (type state). +pub struct Spi; + +impl Alternate for Spi<0> { + #[cfg(feature = "glb-v2")] + const F: Function = Function::Spi0; +} + +impl Alternate for Spi<1> { + #[cfg(feature = "glb-v2")] + const F: Function = Function::Spi1; +} + impl Pin { /// Configures the pin to operate as a pull up output pin. #[inline] diff --git a/src/spi.rs b/src/spi.rs index 0aaa0522ee9efe5f406a6c6fb3ae9d41bf481ab0..ac8700823301542e0f53066c04ba17efe9a907e8 100644 --- a/src/spi.rs +++ b/src/spi.rs @@ -1,5 +1,10 @@ //! Serial peripheral bus peripheral. +use crate::glb::{v2::SpiMode, GLBv2}; +use crate::gpio::{self, Pin}; +use crate::SPI; +use base_address::BaseAddress; +use embedded_hal::spi::Mode; use volatile_register::{RO, RW, WO}; /// Serial peripheral bus registers. @@ -26,9 +31,10 @@ pub struct RegisterBlock { /// First-in first-out queue configuration register 1. pub fifo_config_1: RW, /// First-in first-out queue write data register. - pub data_write: WO, + pub data_write: WO, + _reserved2: [u8; 0x3], /// First-in first-out queue read data register. - pub data_read: RO, + pub data_read: RO, } /// Peripheral configuration register. @@ -613,6 +619,244 @@ impl FifoConfig1 { } } +/// Managed Serial Peripheral Interface peripheral. +pub struct Spi { + spi: SPI, + pins: PINS, +} + +impl Spi { + /// Create a new Serial Peripheral Interface instance. + #[inline] + pub fn new(spi: SPI, pins: PINS, mode: Mode, glb: &GLBv2) -> Self + where + PINS: Pins, + { + let mut config = Config(0) + .disable_deglitch() + .disable_slave_three_pin() + .enable_master_continuous() + .disable_byte_inverse() + .disable_bit_inverse() + .set_frame_size(FrameSize::Eight) + .disable_master(); + + config = match mode.phase { + embedded_hal::spi::Phase::CaptureOnFirstTransition => { + config.set_clock_phase(Phase::CaptureOnFirstTransition) + } + + embedded_hal::spi::Phase::CaptureOnSecondTransition => { + config.set_clock_phase(Phase::CaptureOnSecondTransition) + } + }; + + config = match mode.polarity { + embedded_hal::spi::Polarity::IdleHigh => config.set_clock_polarity(Polarity::IdleHigh), + embedded_hal::spi::Polarity::IdleLow => config.set_clock_polarity(Polarity::IdleLow), + }; + + unsafe { + glb.param_config + .modify(|c| c.set_spi_mode::(SpiMode::Master)); + + spi.config.write(config); + spi.fifo_config_0 + .write(FifoConfig0(0).disable_dma_receive().disable_dma_transmit()); + spi.fifo_config_1.write( + FifoConfig1(0) + .set_receive_threshold(0) + .set_transmit_threshold(0), + ); + spi.period_signal.write( + PeriodSignal(0) + .set_data_phase_0(1) + .set_data_phase_1(1) + .set_start_condition(1) + .set_stop_condition(1), + ); + spi.period_interval + .write(PeriodInterval(0).set_frame_interval(1)); + } + Spi { spi, pins } + } + + /// Release the SPI instance and return the pins. + #[inline] + pub fn free(self) -> (SPI, PINS) { + (self.spi, self.pins) + } +} + +/// SPI error. +#[derive(Debug)] +#[non_exhaustive] +pub enum Error { + Other, +} + +impl embedded_hal::spi::Error for Error { + #[inline(always)] + fn kind(&self) -> embedded_hal::spi::ErrorKind { + use embedded_hal::spi::ErrorKind; + match self { + Error::Other => ErrorKind::Other, + } + } +} + +impl embedded_hal::spi::ErrorType for Spi { + type Error = Error; +} + +impl embedded_hal::spi::SpiBus for Spi { + #[inline] + fn read(&mut self, buf: &mut [u8]) -> Result<(), Self::Error> { + unsafe { self.spi.config.modify(|config| config.enable_master()) }; + + buf.iter_mut().for_each(|slot| { + while self.spi.fifo_config_1.read().receive_available_bytes() == 0 { + core::hint::spin_loop(); + } + *slot = self.spi.data_read.read() + }); + + unsafe { self.spi.config.modify(|config| config.disable_master()) }; + Ok(()) + } + #[inline] + fn write(&mut self, buf: &[u8]) -> Result<(), Self::Error> { + unsafe { self.spi.config.modify(|config| config.enable_master()) }; + + buf.iter().for_each(|&word| { + while self.spi.fifo_config_1.read().transmit_available_bytes() == 0 { + core::hint::spin_loop(); + } + unsafe { self.spi.data_write.write(word) } + }); + + unsafe { self.spi.config.modify(|config| config.disable_master()) }; + Ok(()) + } + #[inline] + fn transfer(&mut self, _read: &mut [u8], _write: &[u8]) -> Result<(), Self::Error> { + todo!() + } + #[inline] + fn transfer_in_place(&mut self, _words: &mut [u8]) -> Result<(), Self::Error> { + todo!() + } + #[inline] + fn flush(&mut self) -> Result<(), Self::Error> { + while self.spi.fifo_config_1.read().transmit_available_bytes() != 32 { + core::hint::spin_loop(); + } + while self.spi.fifo_config_1.read().receive_available_bytes() != 32 { + core::hint::spin_loop(); + } + Ok(()) + } +} + +impl embedded_hal::spi::SpiDevice for Spi { + fn transaction( + &mut self, + operations: &mut [embedded_hal::spi::Operation<'_, u8>], + ) -> Result<(), Self::Error> { + for op in operations { + match op { + embedded_hal::spi::Operation::Read(buf) => { + unsafe { self.spi.config.modify(|config| config.enable_master()) }; + + buf.iter_mut().for_each(|slot| { + while self.spi.fifo_config_1.read().receive_available_bytes() == 0 { + core::hint::spin_loop(); + } + *slot = self.spi.data_read.read() + }); + + unsafe { self.spi.config.modify(|config| config.disable_master()) }; + } + embedded_hal::spi::Operation::Write(buf) => { + unsafe { self.spi.config.modify(|config| config.enable_master()) }; + + buf.iter().for_each(|&word| { + while self.spi.fifo_config_1.read().transmit_available_bytes() == 0 { + core::hint::spin_loop(); + } + unsafe { self.spi.data_write.write(word) } + }); + + unsafe { self.spi.config.modify(|config| config.disable_master()) }; + } + embedded_hal::spi::Operation::Transfer(_read, _write) => { + todo!() + } + embedded_hal::spi::Operation::TransferInPlace(_buf) => { + todo!() + } + embedded_hal::spi::Operation::DelayUs(_delay) => { + todo!() + } + } + } + Ok(()) + } +} + +// This part of implementation using `embedded_hal_027` is designed for backward compatibility of +// ecosystem crates, as some of them depends on embedded-hal v0.2.7 traits. +// We encourage ecosystem developers to use embedded-hal v1.0.0 traits; after that, this part of code +// would be removed in the future. +impl embedded_hal_027::blocking::spi::Write + for Spi +{ + type Error = Error; + #[inline] + fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> { + ::write(self, words) + } +} + +/// Valid SPI pins. +pub trait Pins {} + +impl Pins<1> + for ( + Pin>, + Pin>, + Pin>, + ) +where + A1: BaseAddress, + A2: BaseAddress, + A3: BaseAddress, + Pin>: HasClkSignal, + Pin>: HasMosiSignal, + Pin>: HasCsSignal, +{ +} + +/// Check if target gpio `Pin` is internally connected to SPI clock signal. +pub trait HasClkSignal {} + +impl HasClkSignal for Pin> {} + +/// Check if target gpio `Pin` is internally connected to SPI MISO signal. +pub trait HasMisoSignal {} + +impl HasMisoSignal for Pin> {} + +/// Check if target gpio `Pin` is internally connected to SPI MOSI signal. +pub trait HasMosiSignal {} + +impl HasMosiSignal for Pin> {} + +/// Check if target gpio `Pin` is internally connected to SPI CS signal. +pub trait HasCsSignal {} + +impl HasCsSignal for Pin> {} + #[cfg(test)] mod tests { use super::{