From ae8fe512bda1da8158485a4ccf53b6957001c4b3 Mon Sep 17 00:00:00 2001 From: Hmvp Date: Wed, 7 Jul 2021 12:45:09 +0200 Subject: [PATCH] Pico explorer board support --- boards/pico_explorer/Cargo.toml | 18 +- .../examples/pico_explorer_showcase.rs | 111 ++++++ boards/pico_explorer/src/lib.rs | 350 +++++++++++++----- 3 files changed, 391 insertions(+), 88 deletions(-) create mode 100644 boards/pico_explorer/examples/pico_explorer_showcase.rs diff --git a/boards/pico_explorer/Cargo.toml b/boards/pico_explorer/Cargo.toml index a57f7b0..c2413ee 100644 --- a/boards/pico_explorer/Cargo.toml +++ b/boards/pico_explorer/Cargo.toml @@ -11,9 +11,21 @@ license = "MIT OR Apache-2.0" [dependencies] cortex-m = "0.7.2" -rp2040-hal = { path = "../../rp2040-hal", version = "0.1.0" } -cortex-m-rt = { version = "0.6.14", optional = true } +rp2040-hal = { path="../../rp2040-hal", version="0.1.0" } +cortex-m-rt = { version="0.6.14", optional=true } +embedded-hal = { version="0.2.4", features=["unproven"] } +st7789 = "0.6.1" +display-interface-spi = "0.4.1" +embedded-time = "0.12.0" +embedded-graphics = "0.7.1" [features] default = ["rt"] -rt = ["cortex-m-rt","rp2040-hal/rt"] \ No newline at end of file +rt = ["cortex-m-rt","rp2040-hal/rt"] + +[dev-dependencies] +display-interface = "0.4.1" +panic-halt = "0.2.0" +arrayvec = { version="0.7.1", default-features=false } +rp2040-boot2 = { git="https://github.com/rp-rs/rp2040-boot2-rs", branch="main" } +nb = "1.0.0" diff --git a/boards/pico_explorer/examples/pico_explorer_showcase.rs b/boards/pico_explorer/examples/pico_explorer_showcase.rs new file mode 100644 index 0000000..87bd7e9 --- /dev/null +++ b/boards/pico_explorer/examples/pico_explorer_showcase.rs @@ -0,0 +1,111 @@ +#![no_std] +#![no_main] + +use arrayvec::ArrayString; +use core::fmt::Write; +use cortex_m_rt::entry; +use embedded_graphics::{ + mono_font::{ascii::FONT_10X20, MonoTextStyleBuilder}, + pixelcolor::Rgb565, + prelude::*, + text::{Alignment, Text}, +}; +use embedded_hal::digital::v2::OutputPin; +use embedded_time::rate::*; +use hal::{adc::Adc, clocks::*, sio::Sio, watchdog::Watchdog}; +use panic_halt as _; +use pico_explorer::{hal, pac, Button, PicoExplorer, XOSC_CRYSTAL_FREQ}; + +#[link_section = ".boot2"] +#[used] +pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER; + +// See 4.9.5 from RP2040 datasheet +fn calc_temp(adc_value: f32, refv: f64) -> f64 { + let vbe: f64 = f64::from(adc_value) * refv; + 27f64 - (vbe - 0.706) / 0.001721 +} + +#[entry] +fn main() -> ! { + let mut p = pac::Peripherals::take().unwrap(); + let cp = pac::CorePeripherals::take().unwrap(); + + // Enable watchdog and clocks + let mut watchdog = Watchdog::new(p.WATCHDOG); + let clocks = init_clocks_and_plls( + XOSC_CRYSTAL_FREQ, + p.XOSC, + p.CLOCKS, + p.PLL_SYS, + p.PLL_USB, + &mut p.RESETS, + &mut watchdog, + ) + .ok() + .unwrap(); + + let mut delay = cortex_m::delay::Delay::new(cp.SYST, clocks.system_clock.get_freq().integer()); + + // Enable adc + let mut adc = Adc::new(p.ADC, &mut p.RESETS); + let mut temp_sense = adc.enable_temp_sensor(); + + let sio = Sio::new(p.SIO); + + let (mut explorer, pins) = PicoExplorer::new( + p.IO_BANK0, + p.PADS_BANK0, + sio.gpio_bank0, + p.SPI0, + adc, + &mut p.RESETS, + &mut delay, + ); + + let mut led = pins.led.into_push_pull_output(); + + let mut even = true; + loop { + delay.delay_ms(500); + + // Set GPIO25 to be low + led.set_low().unwrap(); + + delay.delay_ms(500); + + // Set GPIO25 to be high + led.set_high().unwrap(); + + let adc_value = explorer.get_adc(&mut temp_sense); + let temp: f64 = calc_temp(adc_value, 3.3); + + // Create a fixed buffer to store screen contents + let mut buf = ArrayString::<100>::new(); + + // Write to buffer + writeln!(&mut buf, "Hello World {}", if even { '|' } else { '-' }).unwrap(); + writeln!(&mut buf, "Temp: {:.1}", temp).unwrap(); + writeln!( + &mut buf, + "A:{:.1} B:{:.1}\nX:{:.1} Y:{:.1}", + explorer.is_pressed(Button::A), + explorer.is_pressed(Button::B), + explorer.is_pressed(Button::X), + explorer.is_pressed(Button::Y) + ) + .unwrap(); + + // Draw buffer on screen + let style = MonoTextStyleBuilder::new() + .font(&FONT_10X20) + .text_color(Rgb565::GREEN) + .background_color(Rgb565::BLACK) + .build(); + Text::with_alignment(&buf, Point::new(20, 30), style, Alignment::Left) + .draw(&mut explorer.screen) + .unwrap(); + + even = !even; + } +} diff --git a/boards/pico_explorer/src/lib.rs b/boards/pico_explorer/src/lib.rs index bb532f5..efbf28a 100644 --- a/boards/pico_explorer/src/lib.rs +++ b/boards/pico_explorer/src/lib.rs @@ -1,96 +1,276 @@ #![no_std] -extern crate rp2040_hal as hal; +pub extern crate rp2040_hal as hal; #[cfg(feature = "rt")] extern crate cortex_m_rt; + #[cfg(feature = "rt")] pub use cortex_m_rt::entry; - +use display_interface_spi::SPIInterface; +use embedded_graphics::{ + draw_target::DrawTarget, + pixelcolor::{Rgb565, RgbColor}, +}; +use embedded_hal::{ + adc::{Channel, OneShot}, + blocking::delay::DelayUs, + digital::v2::{InputPin, OutputPin}, + spi::MODE_0, +}; +use embedded_time::rate::*; pub use hal::pac; +use hal::{ + adc::Adc, + gpio::{ + bank0::{ + Gpio0, Gpio1, Gpio12, Gpio13, Gpio14, Gpio15, Gpio16, Gpio17, Gpio18, Gpio19, Gpio2, + Gpio20, Gpio21, Gpio22, Gpio23, Gpio24, Gpio25, Gpio26, Gpio27, Gpio28, Gpio29, Gpio3, + Gpio4, Gpio5, Gpio6, Gpio7, + }, + FunctionI2C, FunctionPwm, FunctionSpi, Pin, PinId, PullUpInput, PushPullOutput, + }, + pac::{RESETS, SPI0}, + sio::SioGpioBank0, + spi::{Enabled, Spi}, +}; +use st7789::ST7789; -hal::bsp_pins!( - Gpio0 { - name: gpio0, - aliases: { FunctionPwm: Audio0 } - }, - Gpio1 { - name: gpio1, - aliases: { FunctionPwm: Audio1 } - }, - Gpio2 { - name: gpio2, - aliases: { FunctionPwm: Audio2 } - }, - Gpio3 { - name: gpio3, - aliases: { FunctionPwm: Audio3 } - }, - Gpio4 { - name: gpio4, - aliases: { FunctionPwm: Audio4 } - }, - Gpio5 { - name: gpio5, - aliases: { FunctionPwm: Audio5 } - }, - Gpio6 { - name: gpio6, - aliases: { FunctionPwm: Audio6 } - }, - Gpio7 { - name: gpio7, - aliases: { FunctionPwm: Audio7 } - }, - Gpio8 { - name: motor1_min, - aliases: { FunctionPwm: Motor1Min } - }, - Gpio9 { - name: motor1_plus, - aliases: { FunctionPwm: Motor1Plus } - }, - Gpio10 { - name: motor2_min, - aliases: { FunctionPwm: Motor2Min } - }, - Gpio11 { - name: motor2_plus, - aliases: { FunctionPwm: Motor2Plus } - }, - Gpio12 { name: switch_a }, - Gpio13 { name: switch_b }, - Gpio14 { name: switch_x }, - Gpio15 { name: switch_y }, - Gpio16 { - name: spi_miso, - aliases: { FunctionSpi: Miso } - }, - Gpio17 { - name: lcd_cs, - aliases: { FunctionSpi: LcdCs } - }, - Gpio18 { - name: spi_sclk, - aliases: { FunctionSpi: Sclk } - }, - Gpio19 { - name: spi_mosi, - aliases: { FunctionSpi: Mosi } - }, - Gpio20 { - name: i2c_sda, - aliases: { FunctionI2C: Sda } - }, - Gpio21 { - name: i2c_scl, - aliases: { FunctionI2C: Scl } - }, - Gpio22 { name: i2c_int }, - Gpio25 { name: led }, - Gpio26 { name: adc0 }, - Gpio27 { name: adc1 }, - Gpio28 { name: adc2 }, -); +mod internal_pins { + hal::bsp_pins!( + Gpio0 { name: gpio0 }, + Gpio1 { name: gpio1 }, + Gpio2 { name: gpio2 }, + Gpio3 { name: gpio3 }, + Gpio4 { name: gpio4 }, + Gpio5 { name: gpio5 }, + Gpio6 { name: gpio6 }, + Gpio7 { name: gpio7 }, + Gpio8 { + name: motor1_neg, + aliases: { FunctionPwm: Motor1Neg } + }, + Gpio9 { + name: motor1_pos, + aliases: { FunctionPwm: Motor1Pos } + }, + Gpio10 { + name: motor2_neg, + aliases: { FunctionPwm: Motor2Neg } + }, + Gpio11 { + name: motor2_pos, + aliases: { FunctionPwm: Motor2Pos } + }, + Gpio12 { name: switch_a }, + Gpio13 { name: switch_b }, + Gpio14 { name: switch_x }, + Gpio15 { name: switch_y }, + Gpio16 { + name: spi_miso, + aliases: { FunctionSpi: Miso } + }, + Gpio17 { + name: lcd_cs, + aliases: { FunctionSpi: LcdCs } + }, + Gpio18 { + name: spi_sclk, + aliases: { FunctionSpi: Sclk } + }, + Gpio19 { + name: spi_mosi, + aliases: { FunctionSpi: Mosi } + }, + Gpio20 { + name: i2c_sda, + aliases: { FunctionI2C: Sda } + }, + Gpio21 { + name: i2c_scl, + aliases: { FunctionI2C: Scl } + }, + Gpio22 { name: i2c_int }, + Gpio23 { name: bootsel }, + Gpio24 { name: vbus_detect }, + Gpio25 { name: led }, + Gpio26 { name: adc0 }, + Gpio27 { name: adc1 }, + Gpio28 { name: adc2 }, + Gpio29 { + name: voltage_monitor + }, + ); +} + +pub struct Pins { + pub gpio0: Pin::Reset>, + pub gpio1: Pin::Reset>, + pub gpio2: Pin::Reset>, + pub gpio3: Pin::Reset>, + pub gpio4: Pin::Reset>, + pub gpio5: Pin::Reset>, + pub gpio6: Pin::Reset>, + pub gpio7: Pin::Reset>, + pub spi_sclk: Pin, + pub spi_mosi: Pin, + pub i2c_sda: Pin, + pub i2c_scl: Pin, + pub i2c_int: Pin, + pub bootsel: Pin::Reset>, + pub vbus_detect: Pin::Reset>, + pub led: Pin::Reset>, + pub adc0: Pin::Reset>, + pub adc1: Pin::Reset>, + pub adc2: Pin::Reset>, + pub voltage_monitor: Pin::Reset>, +} pub const XOSC_CRYSTAL_FREQ: u32 = 12_000_000; +pub enum Button { + A, + B, + X, + Y, +} + +pub enum Motor { + _1, + _2, +} + +pub enum MotorAction { + Forward(f32), + Reverse(f32), + Stop, +} + +pub type Screen = ST7789< + SPIInterface, Pin, Pin>, + DummyPin, +>; + +pub struct PicoExplorer { + a: Pin, + b: Pin, + x: Pin, + y: Pin, + adc: Adc, + pub screen: Screen, +} + +pub struct DummyPin; + +impl OutputPin for DummyPin { + type Error = (); + fn set_high(&mut self) -> Result<(), Self::Error> { + Ok(()) + } + fn set_low(&mut self) -> Result<(), Self::Error> { + Ok(()) + } +} + +impl PicoExplorer { + pub fn new( + io: pac::IO_BANK0, + pads: pac::PADS_BANK0, + sio: SioGpioBank0, + spi0: SPI0, + adc: Adc, + resets: &mut RESETS, + delay: &mut impl DelayUs, + ) -> (Self, Pins) { + let internal_pins = internal_pins::Pins::new(io, pads, sio, resets); + + let a = internal_pins.switch_a.into_pull_up_input(); + let b = internal_pins.switch_b.into_pull_up_input(); + let x = internal_pins.switch_x.into_pull_up_input(); + let y = internal_pins.switch_y.into_pull_up_input(); + + internal_pins.motor1_pos.into_mode::(); + internal_pins.motor1_neg.into_mode::(); + internal_pins.motor2_pos.into_mode::(); + internal_pins.motor2_neg.into_mode::(); + + let dc = internal_pins.spi_miso.into_push_pull_output(); + let cs = internal_pins.lcd_cs.into_push_pull_output(); + let spi_sclk = internal_pins.spi_sclk.into_mode::(); + let spi_mosi = internal_pins.spi_mosi.into_mode::(); + + let spi_screen = Spi::<_, _, 8>::new(spi0).init( + resets, + 125_000_000u32.Hz(), + 16_000_000u32.Hz(), + &MODE_0, + ); + + let spii_screen = SPIInterface::new(spi_screen, dc, cs); + + let mut screen = ST7789::new(spii_screen, DummyPin, 240, 240); + + screen.init(delay).unwrap(); + screen + .set_orientation(st7789::Orientation::Portrait) + .unwrap(); + screen.clear(Rgb565::BLACK).unwrap(); + + ( + PicoExplorer { + a, + b, + x, + y, + adc, + screen, + }, + Pins { + gpio0: internal_pins.gpio0, + gpio1: internal_pins.gpio1, + gpio2: internal_pins.gpio2, + gpio3: internal_pins.gpio3, + gpio4: internal_pins.gpio4, + gpio5: internal_pins.gpio5, + gpio6: internal_pins.gpio6, + gpio7: internal_pins.gpio7, + spi_sclk, + spi_mosi, + i2c_sda: internal_pins.i2c_sda.into_mode(), + i2c_scl: internal_pins.i2c_scl.into_mode(), + i2c_int: internal_pins.i2c_int.into_mode(), + bootsel: internal_pins.bootsel, + vbus_detect: internal_pins.vbus_detect, + led: internal_pins.led, + adc0: internal_pins.adc0, + adc1: internal_pins.adc1, + adc2: internal_pins.adc2, + voltage_monitor: internal_pins.voltage_monitor, + }, + ) + } + + pub fn is_pressed(&self, button: Button) -> bool { + use Button::*; + match button { + A => self.a.is_low().unwrap(), + B => self.b.is_low().unwrap(), + X => self.x.is_low().unwrap(), + Y => self.y.is_low().unwrap(), + } + } + + pub fn get_adc>(&mut self, channel: &mut Pin) -> f32 { + // scale raw 12-bit adc value to 0 .. 1 float + let adc_value: u16 = self.adc.read(channel).unwrap(); + let mut result: f32 = f32::from(adc_value) / f32::from(1u16 << 12); + // clamp result to 0 .. 1 + if result > 1.0 { + result = 1.0 + } + + if result < 0.0 { + result = 0.0 + } + result + } +}