From 4cf76f5de4023368de2e007827e591e466ff8d5a Mon Sep 17 00:00:00 2001 From: Anders Natanael Helgesson Date: Sat, 5 Oct 2024 05:09:56 +0900 Subject: [PATCH] Ported examples from rp-pico --- boards/pimoroni-pico-lipo-16mb/CHANGELOG.md | 23 + boards/pimoroni-pico-lipo-16mb/Cargo.toml | 30 +- ...imoroni_pico_lipo_16mb_countdown_blinky.rs | 88 ++++ .../pimoroni_pico_lipo_16mb_gpio_in_out.rs | 75 +++ ...pimoroni_pico_lipo_16mb_hd44780_display.rs | 136 ++++++ ...pico_lipo_16mb_i2c_oled_display_ssd1306.rs | 232 ++++++++++ .../pimoroni_pico_lipo_16mb_i2c_pio.rs | 159 +++++++ .../pimoroni_pico_lipo_16mb_interpolator.rs | 438 ++++++++++++++++++ .../pimoroni_pico_lipo_16mb_pio_pwm.rs | 166 +++++++ .../pimoroni_pico_lipo_16mb_pwm_blink.rs | 116 +++++ .../pimoroni_pico_lipo_16mb_pwm_servo.rs | 116 +++++ .../examples/pimoroni_pico_lipo_16mb_rtic.rs | 91 ++++ .../pimoroni_pico_lipo_16mb_rtic_monotonic.rs | 93 ++++ .../pimoroni_pico_lipo_16mb_spi_sd_card.rs | 341 ++++++++++++++ ...pimoroni_pico_lipo_16mb_uart_irq_buffer.rs | 292 ++++++++++++ .../pimoroni_pico_lipo_16mb_uart_irq_echo.rs | 196 ++++++++ .../pimoroni_pico_lipo_16mb_usb_serial.rs | 155 +++++++ ...oni_pico_lipo_16mb_usb_serial_interrupt.rs | 209 +++++++++ ...moroni_pico_lipo_16mb_usb_twitchy_mouse.rs | 191 ++++++++ .../pimoroni_pico_lipo_16mb_ws2812_led.rs | 219 +++++++++ .../pimoroni-pico-lipo-16mb/examples/pwm.pio | 31 ++ 21 files changed, 3395 insertions(+), 2 deletions(-) create mode 100644 boards/pimoroni-pico-lipo-16mb/examples/pimoroni_pico_lipo_16mb_countdown_blinky.rs create mode 100644 boards/pimoroni-pico-lipo-16mb/examples/pimoroni_pico_lipo_16mb_gpio_in_out.rs create mode 100644 boards/pimoroni-pico-lipo-16mb/examples/pimoroni_pico_lipo_16mb_hd44780_display.rs create mode 100644 boards/pimoroni-pico-lipo-16mb/examples/pimoroni_pico_lipo_16mb_i2c_oled_display_ssd1306.rs create mode 100644 boards/pimoroni-pico-lipo-16mb/examples/pimoroni_pico_lipo_16mb_i2c_pio.rs create mode 100644 boards/pimoroni-pico-lipo-16mb/examples/pimoroni_pico_lipo_16mb_interpolator.rs create mode 100644 boards/pimoroni-pico-lipo-16mb/examples/pimoroni_pico_lipo_16mb_pio_pwm.rs create mode 100644 boards/pimoroni-pico-lipo-16mb/examples/pimoroni_pico_lipo_16mb_pwm_blink.rs create mode 100644 boards/pimoroni-pico-lipo-16mb/examples/pimoroni_pico_lipo_16mb_pwm_servo.rs create mode 100644 boards/pimoroni-pico-lipo-16mb/examples/pimoroni_pico_lipo_16mb_rtic.rs create mode 100644 boards/pimoroni-pico-lipo-16mb/examples/pimoroni_pico_lipo_16mb_rtic_monotonic.rs create mode 100644 boards/pimoroni-pico-lipo-16mb/examples/pimoroni_pico_lipo_16mb_spi_sd_card.rs create mode 100644 boards/pimoroni-pico-lipo-16mb/examples/pimoroni_pico_lipo_16mb_uart_irq_buffer.rs create mode 100644 boards/pimoroni-pico-lipo-16mb/examples/pimoroni_pico_lipo_16mb_uart_irq_echo.rs create mode 100644 boards/pimoroni-pico-lipo-16mb/examples/pimoroni_pico_lipo_16mb_usb_serial.rs create mode 100644 boards/pimoroni-pico-lipo-16mb/examples/pimoroni_pico_lipo_16mb_usb_serial_interrupt.rs create mode 100644 boards/pimoroni-pico-lipo-16mb/examples/pimoroni_pico_lipo_16mb_usb_twitchy_mouse.rs create mode 100644 boards/pimoroni-pico-lipo-16mb/examples/pimoroni_pico_lipo_16mb_ws2812_led.rs create mode 100644 boards/pimoroni-pico-lipo-16mb/examples/pwm.pio diff --git a/boards/pimoroni-pico-lipo-16mb/CHANGELOG.md b/boards/pimoroni-pico-lipo-16mb/CHANGELOG.md index 395260a2..c2adca2e 100644 --- a/boards/pimoroni-pico-lipo-16mb/CHANGELOG.md +++ b/boards/pimoroni-pico-lipo-16mb/CHANGELOG.md @@ -7,6 +7,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +## 0.8.1 - 2024-10-05 + +### Added + +- Ported PWM blink example from rp-pico +- Ported USB serial example from rp-pico +- Ported countdown blinky example from rp-pico +- Ported OLED display SSD1306 example from rp-pico (not tested with display) +- Ported PIO PWM example from rp-pico +- Ported interpolator example from rp-pico +- Ported GPIO in/out example from rp-pico +- Ported USB serial interrupt example from rp-pico +- Ported UART IRQ Buffer example from rp-pico +- Ported UART IRQ Echo example from rp-pico +- Ported USB twitchy mouse example from rp-pico +- Ported ws2812led example from rp-pico (not tested with actual LEDs) +- Ported SPI SD Card example from rp-pico (not tested, Failed to build : rust-lld: error: undefined symbol: _defmt_timestamp) +- Ported HD44780 diplay example from rp-pico (not tested with display) +- Ported I2C PIO example from rp-pico (not tested with sensor) +- Ported PWM servo example from rp-pico (not tested with servo) +- Ported rtic example from rp-pico +- Ported rtic monotonic example from rp-pico + ## 0.8.0 - 2024-04-07 ### Changed diff --git a/boards/pimoroni-pico-lipo-16mb/Cargo.toml b/boards/pimoroni-pico-lipo-16mb/Cargo.toml index 18ec22b1..7a0912b8 100644 --- a/boards/pimoroni-pico-lipo-16mb/Cargo.toml +++ b/boards/pimoroni-pico-lipo-16mb/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pimoroni-pico-lipo-16mb" -version = "0.8.0" +version = "0.8.1" authors = ["Hmvp ", "The rp-rs Developers"] edition = "2018" homepage = "https://github.com/rp-rs/rp-hal-boards/tree/main/boards/pimoroni-pico-lipo-16mb" @@ -12,14 +12,36 @@ repository = "https://github.com/rp-rs/rp-hal-boards.git" [dependencies] cortex-m-rt = { workspace = true, optional = true } +fugit.workspace = true rp2040-boot2 = { workspace = true, optional = true } rp2040-hal.workspace = true +usb-device.workspace = true [dev-dependencies] + cortex-m.workspace = true -panic-halt.workspace = true +cortex-m-rtic.workspace = true +critical-section.workspace = true +embedded-graphics.workspace = true embedded-hal.workspace = true +embedded-hal-nb.workspace = true +embedded-sdmmc.workspace = true +hd44780-driver.workspace = true +heapless.workspace = true +i2c-pio.workspace = true nb.workspace = true +panic-halt.workspace = true +pio.workspace = true +pio-proc.workspace = true +rp2040-hal = { workspace = true, features = [ "defmt" ] } +smart-leds.workspace = true +ssd1306.workspace = true +usbd-hid.workspace = true +usbd-serial.workspace = true +ws2812-pio.workspace = true + +defmt.workspace = true +defmt-rtt.workspace = true [features] # This is the set of features we enable by default @@ -46,3 +68,7 @@ disable-intrinsics = ["rp2040-hal/disable-intrinsics"] # This enables ROM functions for f64 math that were not present in the earliest RP2040s rom-v2-intrinsics = ["rp2040-hal/rom-v2-intrinsics"] + +[[example]] +name = "pimoroni_pico_lipo_16mb_rtic_monotonic" +required-features = ["rp2040-hal/rtic-monotonic"] diff --git a/boards/pimoroni-pico-lipo-16mb/examples/pimoroni_pico_lipo_16mb_countdown_blinky.rs b/boards/pimoroni-pico-lipo-16mb/examples/pimoroni_pico_lipo_16mb_countdown_blinky.rs new file mode 100644 index 00000000..4705f198 --- /dev/null +++ b/boards/pimoroni-pico-lipo-16mb/examples/pimoroni_pico_lipo_16mb_countdown_blinky.rs @@ -0,0 +1,88 @@ +//! # Pimoroni Pico LiPo 16mb Countdown Blinky Example +//! +//! Blinks the LED on a Pimoroni Pico LiPo 16mb board, using an RP2040 Timer in Count-down mode. +//! +//! This will blink an LED attached to GP25, which is the pin the Pico LiPo uses for +//! the on-board LED. +//! +//! See the `Cargo.toml` file for Copyright and license details. + +#![no_std] +#![no_main] + +// The macro for our start-up function +use pimoroni_pico_lipo_16mb::entry; + +use cortex_m::prelude::*; + +// GPIO traits +use embedded_hal::digital::OutputPin; + +// Traits for converting integers to amounts of time +use fugit::ExtU32; + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// A shorter alias for the Peripheral Access Crate, which provides low-level +// register access +use pimoroni_pico_lipo_16mb::hal::pac; + +// A shorter alias for the Hardware Abstraction Layer, which provides +// higher-level drivers. +use pimoroni_pico_lipo_16mb::hal; + +#[entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = pac::Peripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + // + // The default is to generate a 125 MHz system clock + let clocks = hal::clocks::init_clocks_and_plls( + pimoroni_pico_lipo_16mb::XOSC_CRYSTAL_FREQ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .ok() + .unwrap(); + + // Configure the Timer peripheral in count-down mode + let timer = hal::Timer::new(pac.TIMER, &mut pac.RESETS, &clocks); + let mut count_down = timer.count_down(); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins up according to their function on this particular board + let pins = pimoroni_pico_lipo_16mb::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + let mut led_pin = pins.led.into_push_pull_output(); + + // Blink the LED at 1 Hz + loop { + // LED on, and wait for 500ms + led_pin.set_high().unwrap(); + count_down.start(500.millis()); + let _ = nb::block!(count_down.wait()); + + // LED off, and wait for 500ms + led_pin.set_low().unwrap(); + count_down.start(500.millis()); + let _ = nb::block!(count_down.wait()); + } +} diff --git a/boards/pimoroni-pico-lipo-16mb/examples/pimoroni_pico_lipo_16mb_gpio_in_out.rs b/boards/pimoroni-pico-lipo-16mb/examples/pimoroni_pico_lipo_16mb_gpio_in_out.rs new file mode 100644 index 00000000..b6a9dcab --- /dev/null +++ b/boards/pimoroni-pico-lipo-16mb/examples/pimoroni_pico_lipo_16mb_gpio_in_out.rs @@ -0,0 +1,75 @@ +//! # Pimoroni Pico LiPo 16mb GPIO In/Out Example +//! +//! Toggles the LED based on GPIO input. +//! +//! This will control an LED on GP25 based on a button hooked up to GP15. The +//! button should cause the line to be grounded, as the input pin is pulled high +//! internally by this example. When the button is pressed, the LED will turn +//! off. +//! +//! See the `Cargo.toml` file for Copyright and license details. + +#![no_std] +#![no_main] + +// The macro for our start-up function +use pimoroni_pico_lipo_16mb::entry; + +// GPIO traits +use embedded_hal::digital::{InputPin, OutputPin}; + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// A shorter alias for the Peripheral Access Crate, which provides low-level +// register access +use pimoroni_pico_lipo_16mb::hal::pac; + +// A shorter alias for the Hardware Abstraction Layer, which provides +// higher-level drivers. +use pimoroni_pico_lipo_16mb::hal; + +/// Entry point to our bare-metal application. +/// +/// The `#[entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables are initialised. +/// +/// The function configures the RP2040 peripherals, then just reads the button +/// and sets the LED appropriately. +#[entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = pac::Peripherals::take().unwrap(); + + // Note - we don't do any clock set-up in this example. The RP2040 will run + // at it's default clock speed. + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins up according to their function on this particular board + let pins = pimoroni_pico_lipo_16mb::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // Our LED output + let mut led_pin = pins.led.into_push_pull_output(); + + // Our button input + let mut button_pin = pins.gpio15.into_pull_up_input(); + + // Run forever, setting the LED according to the button + loop { + if button_pin.is_low().unwrap() { + led_pin.set_high().unwrap(); + } else { + led_pin.set_low().unwrap(); + } + } +} + +// End of file diff --git a/boards/pimoroni-pico-lipo-16mb/examples/pimoroni_pico_lipo_16mb_hd44780_display.rs b/boards/pimoroni-pico-lipo-16mb/examples/pimoroni_pico_lipo_16mb_hd44780_display.rs new file mode 100644 index 00000000..59df14c3 --- /dev/null +++ b/boards/pimoroni-pico-lipo-16mb/examples/pimoroni_pico_lipo_16mb_hd44780_display.rs @@ -0,0 +1,136 @@ +//! # LCD Display Example +//! +//! In this example, the RP2040 is configured to drive a small two-line +//! alphanumeric LCD using the +//! [HD44780](https://crates.io/crates/hd44780-driver) driver. +//! +//! This example drives the LCD by pushing data out of six GPIO pins, writing +//! the data four bits at a time. A faster alternative can be created using +//! HD44780::new_8bit() but requiring an additional four GPIO pins. +//! +//! See the `Cargo.toml` file for Copyright and license details. +//! +//! ```text +//! /--------------------------------------\ +//! ____________ | /-------------------------\ | +//! | 1 GND|-------+---\ | _|USB|_ | | +//! | 2 VDD|-------+---+----/ |1 R 40|-VBUS-o v +//! | 3 VS|-------/ | |2 P 39| ||POT|| +//! | 4 RS|--\ o-----------GND-|3 38|-GND----------o +//! | 5 RW|--+--------/ /------GP2-|4 P 37| +//! | 6 EN|--+-\ /--+------GP3-|5 I 36| +//! | 7 | | | /--+--+------GP4-|6 C | +//! | 8 | | | /--+--+--+------GP5-|7 O | +//! | 9 | | \--+--+--+--+---\ |8 | +//! | 10 | \----+--+--+--+-\ \-GP6-|9 | +//! | 11 D4|-------/ | | | \---GP7-|10 | +//! | 12 D5|----------/ | | ......... +//! | 13 D6|-------------/ | |20 21| +//! | 14 D7|----------------/ """"""" +//! .............. +//! Symbols: +//! - (+) crossing lines, not connected +//! - (o) connected lines +//! ``` +//! +//! See the `Cargo.toml` file for Copyright and license details. + +#![no_std] +#![no_main] + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// Pull in any important traits +use pimoroni_pico_lipo_16mb::hal::prelude::*; + +// GPIO traits +use embedded_hal::digital::OutputPin; + +// For LCD display +use hd44780_driver::HD44780; + +/// Entry point to our bare-metal application. +/// +/// The `#[pimoroni_pico_lipo_16mb::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +#[pimoroni_pico_lipo_16mb::entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = pimoroni_pico_lipo_16mb::hal::pac::Peripherals::take().unwrap(); + let core = pimoroni_pico_lipo_16mb::hal::pac::CorePeripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = pimoroni_pico_lipo_16mb::hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + // The default is to generate a 125 MHz system clock + let clocks = pimoroni_pico_lipo_16mb::hal::clocks::init_clocks_and_plls( + pimoroni_pico_lipo_16mb::XOSC_CRYSTAL_FREQ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .ok() + .unwrap(); + + // The single-cycle I/O block controls our GPIO pins + let sio = pimoroni_pico_lipo_16mb::hal::Sio::new(pac.SIO); + + // Set the pins up according to their function on this particular board + let pins = pimoroni_pico_lipo_16mb::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + let mut led_pin = pins.led.into_push_pull_output(); + + // The delay object lets us wait for specified amounts of time + let mut delay = cortex_m::delay::Delay::new(core.SYST, clocks.system_clock.freq().to_Hz()); + + // Init pins + let rs = pins.gpio7.into_push_pull_output(); + let en = pins.gpio6.into_push_pull_output(); + let d4 = pins.gpio5.into_push_pull_output(); + let d5 = pins.gpio4.into_push_pull_output(); + let d6 = pins.gpio3.into_push_pull_output(); + let d7 = pins.gpio2.into_push_pull_output(); + + // LCD Init + let mut lcd = HD44780::new_4bit(rs, en, d4, d5, d6, d7, &mut delay).unwrap(); + + loop { + // Clear the screen + lcd.reset(&mut delay).unwrap(); + lcd.clear(&mut delay).unwrap(); + + // Write to the top line + lcd.write_str("rp-hal on", &mut delay).unwrap(); + + // Move the cursor + lcd.set_cursor_pos(40, &mut delay).unwrap(); + + // Write more more text + lcd.write_str("HD44780! ", &mut delay).unwrap(); + let mut char_count = 9; + for ch in "move along!.. ".chars() { + if char_count > 15 { + // Switch autoscroll on + lcd.set_autoscroll(true, &mut delay).unwrap(); + } + led_pin.set_high().unwrap(); + lcd.write_char(ch, &mut delay).unwrap(); + char_count += 1; + delay.delay_us(400_000); //0.4s + led_pin.set_low().unwrap(); + delay.delay_us(100_000); //0.1s + } + lcd.set_autoscroll(false, &mut delay).unwrap(); + } +} diff --git a/boards/pimoroni-pico-lipo-16mb/examples/pimoroni_pico_lipo_16mb_i2c_oled_display_ssd1306.rs b/boards/pimoroni-pico-lipo-16mb/examples/pimoroni_pico_lipo_16mb_i2c_oled_display_ssd1306.rs new file mode 100644 index 00000000..4cea70cd --- /dev/null +++ b/boards/pimoroni-pico-lipo-16mb/examples/pimoroni_pico_lipo_16mb_i2c_oled_display_ssd1306.rs @@ -0,0 +1,232 @@ +//! # Pimoroni Pico LiPo 16mb (monochrome) 128x64 OLED Display with SSD1306 Driver Example +//! +//! This example assumes you got an 128x64 OLED Display with an SSD1306 driver +//! connected to your Pimoroni Pico LiPo 16mb. The +3.3V voltage source of the +//! Pimoroni Pico LiPo 16mb will be used, and the output pins 21 and 22 of the board +//! (on the lower right). +//! +//! It will demonstrate how to get an I2C device and use it with the ssd1306 crate. +//! Additionally you can also see how to format a number into a string using +//! [core::fmt]. +//! +//! The following diagram will show how things should be connected. +//! These displays usually can take 3.3V up to 5V. +//! +//! ```text +//! VCC SCL +//! /------------\ /----------\ +//! | GND \ / SDA | +//! _|USB|_ | /-----\ | | /--------+--\ +//! |1 R 40| | / __|__|__|__|___ | | +//! |2 P 39| | / | ____________ | | | +//! |3 38|- GND --+-/ | |Hello worl| | | | +//! |4 P 37| | | |Hello Rust| | | | +//! |5 I 36|-+3.3V -/ | |counter: 1| | | | +//! |6 C | | | | | | | +//! |7 O | | """""""""""" | | | +//! | | """"""""""""""" | | +//! | | (SSD1306 128x64 OLED Display) | | +//! ......... / / +//! | | / / +//! | 22|-GP17 I2C0 SCL---------------------/ / +//! |20 21|-GP16 I2C0 SDA-----------------------/ +//! """"""" +//! Symbols: +//! - (+) crossing lines, not connected +//! - (o) connected lines +//! ``` +//! +//! See the `Cargo.toml` file for Copyright and license details. + +#![no_std] +#![no_main] + +// For string formatting. +use core::fmt::Write; + +// The macro for our start-up function +use pimoroni_pico_lipo_16mb::entry; + +// Time handling traits: +use fugit::RateExtU32; + +// Timer for the delay on the display: +use embedded_hal::delay::DelayNs; + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// A shorter alias for the Peripheral Access Crate, which provides low-level +// register access +use pimoroni_pico_lipo_16mb::hal::pac; + +// A shorter alias for the Hardware Abstraction Layer, which provides +// higher-level drivers. +use pimoroni_pico_lipo_16mb::hal; + +// For in the graphics drawing utilities like the font +// and the drawing routines: +use embedded_graphics::{ + mono_font::{ascii::FONT_9X18_BOLD, MonoTextStyleBuilder}, + pixelcolor::BinaryColor, + prelude::*, + text::{Baseline, Text}, +}; + +// The display driver: +use ssd1306::{prelude::*, Ssd1306}; + +/// Entry point to our bare-metal application. +/// +/// The `#[entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables are initialised. +/// +/// The function configures the RP2040 peripherals, +/// gets a handle on the I2C peripheral, +/// initializes the SSD1306 driver, initializes the text builder +/// and then draws some text on the display. +#[entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = pac::Peripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + // + // The default is to generate a 125 MHz system clock + let clocks = hal::clocks::init_clocks_and_plls( + pimoroni_pico_lipo_16mb::XOSC_CRYSTAL_FREQ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .ok() + .unwrap(); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins up according to their function on this particular board + let pins = pimoroni_pico_lipo_16mb::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // Configure two pins as being I²C, not GPIO + let sda_pin: hal::gpio::Pin<_, hal::gpio::FunctionI2C, _> = pins.gpio16.reconfigure(); + let scl_pin: hal::gpio::Pin<_, hal::gpio::FunctionI2C, _> = pins.gpio17.reconfigure(); + + // Create the I²C driver, using the two pre-configured pins. This will fail + // at compile time if the pins are in the wrong mode, or if this I²C + // peripheral isn't available on these pins! + let i2c = hal::I2C::i2c0( + pac.I2C0, + sda_pin, + scl_pin, + 400.kHz(), + &mut pac.RESETS, + &clocks.peripheral_clock, + ); + + // Create the I²C display interface using the standard address of 0x3C(60): + let interface = ssd1306::I2CDisplayInterface::new(i2c); + + // You can also create the I²C display interface using the alternate address of 0x3D(61) + //let interface = ssd1306::I2CDisplayInterface::new_alternate_address(i2c); + + // Or if your I²C display uses an custom address you can use this to set it, replace 60 with your adress it's in decimal. + //let interface = ssd1306::I2CDisplayInterface::new_custom_address(i2c, 60); + + // Create a driver instance and initialize: + let mut display = Ssd1306::new(interface, DisplaySize128x64, DisplayRotation::Rotate0) + .into_buffered_graphics_mode(); + display.init().unwrap(); + + // Create a text style for drawing the font: + let text_style = MonoTextStyleBuilder::new() + .font(&FONT_9X18_BOLD) + .text_color(BinaryColor::On) + .build(); + + let mut timer = hal::Timer::new(pac.TIMER, &mut pac.RESETS, &clocks); + + let mut count = 0; + + let mut buf = FmtBuf::new(); + + loop { + buf.reset(); + // Format some text into a static buffer: + write!(&mut buf, "counter: {}", count).unwrap(); + count += 1; + + // Empty the display: + display.clear(); + + // Draw 3 lines of text: + Text::with_baseline("Hello world!", Point::zero(), text_style, Baseline::Top) + .draw(&mut display) + .unwrap(); + + Text::with_baseline("Hello Rust!", Point::new(0, 16), text_style, Baseline::Top) + .draw(&mut display) + .unwrap(); + + Text::with_baseline(buf.as_str(), Point::new(0, 32), text_style, Baseline::Top) + .draw(&mut display) + .unwrap(); + + display.flush().unwrap(); + + // Wait a bit: + timer.delay_ms(500); + } +} + +/// This is a very simple buffer to pre format a short line of text +/// limited arbitrarily to 64 bytes. +struct FmtBuf { + buf: [u8; 64], + ptr: usize, +} + +impl FmtBuf { + fn new() -> Self { + Self { + buf: [0; 64], + ptr: 0, + } + } + + fn reset(&mut self) { + self.ptr = 0; + } + + fn as_str(&self) -> &str { + core::str::from_utf8(&self.buf[0..self.ptr]).unwrap() + } +} + +impl core::fmt::Write for FmtBuf { + fn write_str(&mut self, s: &str) -> core::fmt::Result { + let rest_len = self.buf.len() - self.ptr; + let len = if rest_len < s.len() { + rest_len + } else { + s.len() + }; + self.buf[self.ptr..(self.ptr + len)].copy_from_slice(&s.as_bytes()[0..len]); + self.ptr += len; + Ok(()) + } +} + +// End of file diff --git a/boards/pimoroni-pico-lipo-16mb/examples/pimoroni_pico_lipo_16mb_i2c_pio.rs b/boards/pimoroni-pico-lipo-16mb/examples/pimoroni_pico_lipo_16mb_i2c_pio.rs new file mode 100644 index 00000000..7359eda3 --- /dev/null +++ b/boards/pimoroni-pico-lipo-16mb/examples/pimoroni_pico_lipo_16mb_i2c_pio.rs @@ -0,0 +1,159 @@ +//! # Pico LiPo I2C PIO Example +//! +//! Reads the temperature from an LM75B +//! +//! This read over I2C the temerature from an LM75B temperature sensor wired on pins 20 and 21 +//! using the PIO peripheral as an I2C bus controller. +//! The pins used for the I2C can be remapped to any other pin available to the PIO0 peripheral. +//! +//! See the `Cargo.toml` file for Copyright and license details. + +#![no_std] +#![no_main] + +// The trait used by formatting macros like write! and writeln! +use core::fmt::Write as FmtWrite; + +// The macro for our start-up function +use pimoroni_pico_lipo_16mb::entry; + +// I2C HAL traits & Types. +use embedded_hal::i2c::{I2c, Operation}; + +// Time handling traits +use fugit::RateExtU32; + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// Pull in any important traits +use pimoroni_pico_lipo_16mb::hal::prelude::*; + +// A shorter alias for the Peripheral Access Crate, which provides low-level +// register access +use pimoroni_pico_lipo_16mb::hal::pac; + +// A shorter alias for the Hardware Abstraction Layer, which provides +// higher-level drivers. +use pimoroni_pico_lipo_16mb::hal; + +// UART related types +use hal::uart::{DataBits, StopBits, UartConfig}; + +/// Prints the temperature received from the sensor +fn print_temperature(serial: &mut impl FmtWrite, temp: [u8; 2]) { + let temp_i16 = i16::from_be_bytes(temp) >> 5; + let temp_f32 = f32::from(temp_i16) * 0.125; + + // Write formatted output but ignore any error. + let _ = writeln!(serial, "Temperature: {:0.2}°C", temp_f32); +} + +/// Entry point to our bare-metal application. +/// +/// The `#[entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables are initialised. +/// +/// The function configures the RP2040 peripherals, reads the temperature from +/// the attached LM75B using PIO0. +#[entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = pac::Peripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + // + // The default is to generate a 125 MHz system clock + let clocks = hal::clocks::init_clocks_and_plls( + pimoroni_pico_lipo_16mb::XOSC_CRYSTAL_FREQ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .ok() + .unwrap(); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins up according to their function on this particular board + let pins = pimoroni_pico_lipo_16mb::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + let uart_pins = ( + // UART TX (characters sent from RP2040) on pin 1 (GPIO0) + pins.gpio0.into_function::(), + // UART RX (characters received by RP2040) on pin 2 (GPIO1) + pins.gpio1.into_function::(), + ); + + let mut uart = hal::uart::UartPeripheral::new(pac.UART0, uart_pins, &mut pac.RESETS) + .enable( + UartConfig::new(115_200.Hz(), DataBits::Eight, None, StopBits::One), + clocks.peripheral_clock.freq(), + ) + .unwrap(); + + let (mut pio, sm0, _, _, _) = pac.PIO0.split(&mut pac.RESETS); + + let mut i2c_pio = i2c_pio::I2C::new( + &mut pio, + pins.gpio20, + pins.gpio21, + sm0, + 100.kHz(), + clocks.system_clock.freq(), + ); + + let mut temp = [0; 2]; + i2c_pio + .read(0x48u8, &mut temp) + .expect("Failed to read from the peripheral"); + print_temperature(&mut uart, temp); + + i2c_pio + .write(0x48u8, &[0]) + .expect("Failed to write to the peripheral"); + + let mut temp = [0; 2]; + i2c_pio + .read(0x48u8, &mut temp) + .expect("Failed to read from the peripheral"); + print_temperature(&mut uart, temp); + + let mut config = [0]; + let mut thyst = [0; 2]; + let mut tos = [0; 2]; + let mut temp = [0; 2]; + let mut operations = [ + Operation::Write(&[1]), + Operation::Read(&mut config), + Operation::Write(&[2]), + Operation::Read(&mut thyst), + Operation::Write(&[3]), + Operation::Read(&mut tos), + Operation::Write(&[0]), + Operation::Read(&mut temp), + ]; + i2c_pio + .transaction(0x48u8, &mut operations) + .expect("Failed to run all operations"); + print_temperature(&mut uart, temp); + + loop { + cortex_m::asm::wfi(); + } +} + +// End of file diff --git a/boards/pimoroni-pico-lipo-16mb/examples/pimoroni_pico_lipo_16mb_interpolator.rs b/boards/pimoroni-pico-lipo-16mb/examples/pimoroni_pico_lipo_16mb_interpolator.rs new file mode 100644 index 00000000..4574ce3e --- /dev/null +++ b/boards/pimoroni-pico-lipo-16mb/examples/pimoroni_pico_lipo_16mb_interpolator.rs @@ -0,0 +1,438 @@ +//! # Pimoroni Pico LiPo 16mb Interpolator Example +//! +//! Example demonstrating the usage of the hardware interpolator. +//! +//! Runs several test programs, outputs the result on LEDs. +//! Green led for successful test connects to GPIO3. +//! Red led for unsuccessful test connects to GPIO4. +//! In case of failure, the system LED blinks the number of the test. +//! In case of success, the system LED stays lit. +//! +//! See the `Cargo.toml` file for Copyright and license details. + +#![no_std] +#![no_main] + +// The macro for our start-up function +use pimoroni_pico_lipo_16mb::entry; + +// GPIO traits +use embedded_hal::digital::OutputPin; + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// A shorter alias for the Peripheral Access Crate, which provides low-level +// register access +use pimoroni_pico_lipo_16mb::hal::pac; + +// A shorter alias for the Hardware Abstraction Layer, which provides +// higher-level drivers. +use pimoroni_pico_lipo_16mb::hal; + +// Pull in any important traits +use pimoroni_pico_lipo_16mb::hal::prelude::*; + +use pimoroni_pico_lipo_16mb::hal::sio::{Interp, Interp0, Interp1, Lane, LaneCtrl}; + +/// Entry point to our bare-metal application. +/// +/// The `#[entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables are initialised. +/// +/// The function configures the RP2040 peripherals, then just reads the button +/// and sets the LED appropriately. +#[entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = pac::Peripherals::take().unwrap(); + let core = pac::CorePeripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + // + // The default is to generate a 125 MHz system clock + let clocks = hal::clocks::init_clocks_and_plls( + pimoroni_pico_lipo_16mb::XOSC_CRYSTAL_FREQ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .ok() + .unwrap(); + + // The delay object lets us wait for specified amounts of time (in + // milliseconds) + let mut delay = cortex_m::delay::Delay::new(core.SYST, clocks.system_clock.freq().to_Hz()); + + // The single-cycle I/O block controls our GPIO pins + let mut sio = hal::Sio::new(pac.SIO); + + // Set the pins up according to their function on this particular board + let pins = pimoroni_pico_lipo_16mb::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // Our LED outputs + let mut system_led_pin = pins.led.into_push_pull_output(); + let mut green_led_pin = pins.gpio3.into_push_pull_output(); + let mut red_led_pin = pins.gpio4.into_push_pull_output(); + + system_led_pin.set_low().unwrap(); + green_led_pin.set_low().unwrap(); + red_led_pin.set_low().unwrap(); + + let mut choose_led = |index: u32, result: bool| { + if result { + // blink the green led once to indicate success + green_led_pin.set_high().unwrap(); + delay.delay_ms(500); + green_led_pin.set_low().unwrap(); + delay.delay_ms(500); + } else { + // turn the red led on to indicate failure + // and blink the on board led to indicate which test failed, looping forever + red_led_pin.set_high().unwrap(); + loop { + for _ in 0..index { + system_led_pin.set_high().unwrap(); + delay.delay_ms(200); + system_led_pin.set_low().unwrap(); + delay.delay_ms(200); + } + delay.delay_ms(1000); + } + } + }; + + // Run forever, setting the LED according to the button + + choose_led(1, multiplication_table(&mut sio.interp0)); + choose_led(2, moving_mask(&mut sio.interp0)); + choose_led(3, cross_lanes(&mut sio.interp0)); + choose_led(4, simple_blend1(&mut sio.interp0)); + choose_led(5, simple_blend2(&mut sio.interp0)); + choose_led(6, clamp(&mut sio.interp1)); + choose_led(7, texture_mapping(&mut sio.interp0)); + + // turn the on board led on to indicate testing is done + system_led_pin.set_high().unwrap(); + loop { + delay.delay_ms(1000); + } +} + +fn multiplication_table(interp: &mut Interp0) -> bool { + //get the default configuration that just keep adding base into accum + let config = LaneCtrl::new(); + + //write the configuration to the hardware. + interp.get_lane0().set_ctrl(config.encode()); + + //set the accumulator to 0 and the base to 9 + interp.get_lane0().set_accum(0); + interp.get_lane0().set_base(9); + + //the expected output for comparison + let expected = [9, 18, 27, 36, 45, 54, 63, 72, 81, 90]; + + for i in expected { + //returns the value of accum + base and sets accum to the same value + let value = interp.get_lane0().pop(); + + if value != i { + return false; //inform that the interpolator did not return the expected value + } + } + true +} + +fn moving_mask(interp: &mut Interp0) -> bool { + //get the default configuration that just keep adding base into accum + let mut config = LaneCtrl::new(); + + interp.get_lane0().set_accum(0x1234ABCD); + + let expected = [ + 0x0000_000D, + 0x0000_00C0, + 0x0000_0B00, + 0x0000_A000, + 0x0004_0000, + 0x0030_0000, + 0x0200_0000, + 0x1000_0000, + ]; + for i in 0..8 { + // LSB, then MSB. These are inclusive, so 0,31 means "the entire 32 bit register" + config.mask_lsb = i * 4; + config.mask_msb = i * 4 + 3; + interp.get_lane0().set_ctrl(config.encode()); + + // Reading read_raw() returns the lane data + // after shifting, masking and sign extending, without adding base + if interp.get_lane0().read_raw() != expected[i as usize] { + return false; + } + } + + let signed_expected = [ + 0xFFFF_FFFD, + 0xFFFF_FFC0, + 0xFFFF_FB00, + 0xFFFF_A000, + 0x0004_0000, + 0x0030_0000, + 0x0200_0000, + 0x1000_0000, + ]; + + config.signed = true; + for i in 0..8 { + config.mask_lsb = i * 4; + config.mask_msb = i * 4 + 3; + interp.get_lane0().set_ctrl(config.encode()); + + if interp.get_lane0().read_raw() != signed_expected[i as usize] { + return false; + } + } + true +} + +fn cross_lanes(interp: &mut Interp0) -> bool { + // this configuration will at the time of pop() + // when applied to lane0 : set lane0 accumulator to the result from lane1 + // when applied to lane1 : set lane1 accumulator to the result from lane0 + let config = LaneCtrl { + cross_result: true, + ..LaneCtrl::new() + }; + let encoded_config = config.encode(); + + // each lane is used through an accessor, + // as lanes mutate each other, they can not be borrowed at the same time + interp.get_lane0().set_ctrl(encoded_config); + interp.get_lane1().set_ctrl(encoded_config); + + interp.get_lane0().set_accum(123); + interp.get_lane1().set_accum(456); + + // lane0 will add 1 to its result, lane1 will add nothing + interp.get_lane0().set_base(1); + interp.get_lane1().set_base(0); + + let expected = [ + (124, 456), + (457, 124), + (125, 457), + (458, 125), + (126, 458), + (459, 126), + (127, 459), + (460, 127), + (128, 460), + (461, 128), + ]; + + for i in expected { + if i != (interp.get_lane0().peek(), interp.get_lane1().pop()) { + return false; + } + } + true +} + +fn simple_blend1(interp: &mut Interp0) -> bool { + let config = LaneCtrl { + blend: true, + ..LaneCtrl::new() + }; + + //enable blend mode + interp.get_lane0().set_ctrl(config.encode()); + //make sure the default configuration is in lane1 as the value may be shifted and masked. + interp.get_lane1().set_ctrl(LaneCtrl::new().encode()); + + //set the minimum value for interp.get_lane0().set_accum(0) 0/256 + interp.get_lane0().set_base(500); + //set the maximum value which is inaccessible + // as the blend is done between 0/256 and 255/256 + interp.get_lane1().set_base(1000); + + let expected = [500, 582, 666, 748, 832, 914, 998]; + for i in 0..=6 { + interp.get_lane1().set_accum(255 * i / 6); + if expected[i as usize] != interp.get_lane1().peek() { + return false; + } + } + true +} + +fn simple_blend2(interp: &mut Interp0) -> bool { + let config = LaneCtrl { + blend: true, + ..LaneCtrl::new() + }; + //enable blend mode + interp.get_lane0().set_ctrl(config.encode()); + + interp.get_lane0().set_base((-1000i32) as u32); + interp.get_lane1().set_base(1000); + + let mut config1 = LaneCtrl { + signed: true, + ..LaneCtrl::new() + }; + interp.get_lane1().set_ctrl(config1.encode()); + let expected_signed = [-1000, -672, -336, -8, 328, 656, 992]; + for i in 0..=6 { + // write a value between 0 and 256 (exclusive) + interp.get_lane1().set_accum(255 * i / 6); + // reads it as a value between -1000 and 1000 (exclusive) + if interp.get_lane1().peek() as i32 != expected_signed[i as usize] { + return false; + } + } + config1.signed = false; + interp.get_lane1().set_ctrl(config1.encode()); + let expected_unsigned = [ + 0xfffffc18, 0xd5fffd60, 0xaafffeb0, 0x80fffff8, 0x56000148, 0x2c000290, 0x010003e0, + ]; + for i in 0..=6 { + interp.get_lane1().set_accum(255 * i / 6); + // reads a value between 4294966296 and 1000 + if interp.get_lane1().peek() != expected_unsigned[i as usize] { + return false; + } + } + true +} + +///Divides by 4 and clamp the value between 0 and 255 inclusive +fn clamp(interp: &mut Interp1) -> bool { + // Enables Clamp ONLY AVAILABLE ON Interp1 + // shift two bits to the right and mask the two most significant bits + // because sign extension is made after the mask + let config = LaneCtrl { + clamp: true, + shift: 2, + mask_lsb: 0, + mask_msb: 29, + signed: true, + ..LaneCtrl::new() + }; + interp.get_lane0().set_ctrl(config.encode()); + //set minimum value of result + interp.get_lane0().set_base(0); + //set maximum value of result + interp.get_lane1().set_base(255); + let values: [(i32, i32); 9] = [ + (-1024, 0), + (-768, 0), + (-512, 0), + (-256, 0), + (0, 0), + (256, 64), + (512, 128), + (768, 192), + (1024, 255), + ]; + for (arg, result) in values { + interp.get_lane0().set_accum(arg as u32); + if result != interp.get_lane0().peek() as i32 { + return false; + } + } + true +} + +fn texture_mapping(interp: &mut Interp0) -> bool { + #[rustfmt::skip] + let texture: [u8;16] = [ + 0x00, 0x01, 0x02, 0x03, + 0x10, 0x11, 0x12, 0x13, + 0x20, 0x21, 0x22, 0x23, + 0x30, 0x31, 0x32, 0x33, + ]; + + // the position will be given in fixed point with 16 bits + // fractional part + let uv_fractional_bits = 16; + let texture_width_bits = 2; + let texture_height_bits = 2; + + // bits + // 3322222222221111 1111110000000000 + // 1098765432109876 5432109876543210 + // accum0 u axis coordinate xx xxxxxxxxxxxxxxxx 18 bits + // after shift and mask xx + // accum1 v axis xx xxxxxxxxxxxxxxxx 18 bits + // after shift and mask xx + + // add_raw make the interpolator increment the accumulator + // with the base value without masking or shifting + let config0 = LaneCtrl { + add_raw: true, + shift: uv_fractional_bits, + mask_lsb: 0, + mask_msb: texture_width_bits - 1, + ..LaneCtrl::new() + }; + interp.get_lane0().set_ctrl(config0.encode()); + let config1 = LaneCtrl { + add_raw: true, + shift: uv_fractional_bits - texture_width_bits, + mask_lsb: texture_width_bits, + mask_msb: texture_width_bits + texture_height_bits - 1, + ..LaneCtrl::new() + }; + interp.get_lane1().set_ctrl(config1.encode()); + + interp.set_base(0); + + // set starting position to 0x0 + // will move 1/2 a pixel horizontally + // and 1/3 a pixel vertically per call to pop() + interp.get_lane0().set_accum(0); + interp.get_lane0().set_base(65536 / 2); + interp.get_lane1().set_accum(0); + interp.get_lane1().set_base(65536 / 3); + + let expected = [ + 0x00, 0x00, 0x01, 0x01, 0x12, 0x12, 0x13, 0x23, 0x20, 0x20, 0x31, 0x31, + ]; + + for i in expected { + if i != texture[interp.pop() as usize] { + return false; + } + } + + // reset the starting position + interp.get_lane0().set_accum(0); + interp.get_lane1().set_accum(0); + interp.set_base(texture.as_ptr() as u32); + + for i in expected { + // This is unsafe and should be done extremely carefully + // remember to follow memory alignment, + // reading or writing an unaligned address will crash + if i != unsafe { *(interp.pop() as *const u8) } { + return false; + } + } + + true +} +// End of file diff --git a/boards/pimoroni-pico-lipo-16mb/examples/pimoroni_pico_lipo_16mb_pio_pwm.rs b/boards/pimoroni-pico-lipo-16mb/examples/pimoroni_pico_lipo_16mb_pio_pwm.rs new file mode 100644 index 00000000..00d4deec --- /dev/null +++ b/boards/pimoroni-pico-lipo-16mb/examples/pimoroni_pico_lipo_16mb_pio_pwm.rs @@ -0,0 +1,166 @@ +//! # Pimoroni Pico LiPo 16mb PIO PWM Blink Example +//! +//! Fades the LED on a Pico LiPo board using the PIO peripheral with an pwm program. +//! +//! This will fade in the LED attached to GP25, which is the pin the Pico +//! uses for the on-board LED. +//! +//! This example uses a few advance pio tricks such as side setting pins and instruction injection. +//! +//! See the `Cargo.toml` file for Copyright and license details. Except for the pio program which is subject to a different license. + +#![no_std] +#![no_main] + +use defmt::info; +use defmt_rtt as _; +// The macro for our start-up function +use pimoroni_pico_lipo_16mb::entry; + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// Pull in any important traits +use pimoroni_pico_lipo_16mb::hal::prelude::*; + +// A shorter alias for the Peripheral Access Crate, which provides low-level +// register access +use pimoroni_pico_lipo_16mb::hal::pac; + +// A shorter alias for the Hardware Abstraction Layer, which provides +// higher-level drivers. +use pimoroni_pico_lipo_16mb::hal; + +// Import pio crates +use hal::pio::{PIOBuilder, Running, StateMachine, Tx, ValidStateMachine, SM0}; +use pio::{Instruction, InstructionOperands, OutDestination}; +use pio_proc::pio_file; + +/// Set pio pwm period +/// +/// This uses a sneaky trick to set a second value besides the duty cycle. +/// We first write a value to the tx fifo. But instead of the normal instructions we +/// have stopped the state machine and inject our own instructions that move the written value to the ISR. +fn pio_pwm_set_period( + sm: StateMachine<(hal::pac::PIO0, SM0), Running>, + tx: &mut Tx, + period: u32, +) -> StateMachine<(hal::pac::PIO0, SM0), Running> { + // To make sure the inserted instructions actually use our newly written value + // We first busy loop to empty the queue. (Which typically should be the case) + while !tx.is_empty() {} + + let mut sm = sm.stop(); + tx.write(period); + sm.exec_instruction(Instruction { + operands: InstructionOperands::PULL { + if_empty: false, + block: false, + }, + delay: 0, + side_set: None, + }); + sm.exec_instruction(Instruction { + operands: InstructionOperands::OUT { + destination: OutDestination::ISR, + bit_count: 32, + }, + delay: 0, + side_set: None, + }); + sm.start() +} + +/// Set pio pwm duty cycle +/// +/// The value written to the TX FIFO is used directly by the normal pio program +fn pio_pwm_set_level(tx: &mut Tx, level: u32) { + // Write duty cycle to TX Fifo + tx.write(level); +} + +/// Entry point to our bare-metal application. +/// +/// The `#[entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables are initialised. +/// +/// The function configures the RP2040 peripherals, then fades the LED in an +/// infinite loop. +#[entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = pac::Peripherals::take().unwrap(); + let core = pac::CorePeripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + // + // The default is to generate a 125 MHz system clock + let clocks = hal::clocks::init_clocks_and_plls( + pimoroni_pico_lipo_16mb::XOSC_CRYSTAL_FREQ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .ok() + .unwrap(); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins up according to their function on this particular board + let pins = pimoroni_pico_lipo_16mb::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // The delay object lets us wait for specified amounts of time (in + // milliseconds) + let mut delay = cortex_m::delay::Delay::new(core.SYST, clocks.system_clock.freq().to_Hz()); + + let (mut pio0, sm0, _, _, _) = pac.PIO0.split(&mut pac.RESETS); + + // Create a pio program + let program = pio_file!("./examples/pwm.pio", select_program("pwm"),); + let installed = pio0.install(&program.program).unwrap(); + + // Set gpio25 to pio + let _led: hal::gpio::Pin<_, hal::gpio::FunctionPio0, hal::gpio::PullNone> = + pins.led.reconfigure(); + let led_pin_id = 25; + + // Build the pio program and set pin both for set and side set! + // We are running with the default divider which is 1 (max speed) + let (mut sm, _, mut tx) = PIOBuilder::from_installed_program(installed) + .set_pins(led_pin_id, 1) + .side_set_pin_base(led_pin_id) + .build(sm0); + + // Set pio pindir for gpio25 + sm.set_pindirs([(led_pin_id, hal::pio::PinDir::Output)]); + + // Start state machine + let sm = sm.start(); + + // Set period + pio_pwm_set_period(sm, &mut tx, u16::MAX as u32 - 1); + + // Loop forever and adjust duty cycle to make te led brighter + let mut level = 0; + loop { + info!("Level = {}", level); + pio_pwm_set_level(&mut tx, level * level); + level = (level + 1) % 256; + delay.delay_ms(10); + } +} + +// End of file diff --git a/boards/pimoroni-pico-lipo-16mb/examples/pimoroni_pico_lipo_16mb_pwm_blink.rs b/boards/pimoroni-pico-lipo-16mb/examples/pimoroni_pico_lipo_16mb_pwm_blink.rs new file mode 100644 index 00000000..173aadac --- /dev/null +++ b/boards/pimoroni-pico-lipo-16mb/examples/pimoroni_pico_lipo_16mb_pwm_blink.rs @@ -0,0 +1,116 @@ +//! # Pimoroni Pico LiPo 16mb PWM Blink Example +//! +//! Fades the LED on a Pimoroni Pico LiPo 16mb board using the PWM peripheral. +//! +//! This will fade in/out the LED attached to GP25, which is the pin the Pico LiPo +//! uses for the on-board LED. +//! +//! See the `Cargo.toml` file for Copyright and license details. + +#![no_std] +#![no_main] + +// The macro for our start-up function +use pimoroni_pico_lipo_16mb::entry; + +// GPIO traits +use embedded_hal::pwm::SetDutyCycle; + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// Pull in any important traits +use pimoroni_pico_lipo_16mb::hal::prelude::*; + +// A shorter alias for the Peripheral Access Crate, which provides low-level +// register access +use pimoroni_pico_lipo_16mb::hal::pac; + +// A shorter alias for the Hardware Abstraction Layer, which provides +// higher-level drivers. +use pimoroni_pico_lipo_16mb::hal; + +// The minimum PWM value (i.e. LED brightness) we want +const LOW: u16 = 0; + +// The maximum PWM value (i.e. LED brightness) we want +const HIGH: u16 = 25000; + +/// Entry point to our bare-metal application. +/// +/// The `#[entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables are initialised. +/// +/// The function configures the RP2040 peripherals, then fades the LED in an +/// infinite loop. +#[entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = pac::Peripherals::take().unwrap(); + let core = pac::CorePeripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + // + // The default is to generate a 125 MHz system clock + let clocks = hal::clocks::init_clocks_and_plls( + pimoroni_pico_lipo_16mb::XOSC_CRYSTAL_FREQ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .ok() + .unwrap(); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins up according to their function on this particular board + let pins = pimoroni_pico_lipo_16mb::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // The delay object lets us wait for specified amounts of time (in + // milliseconds) + let mut delay = cortex_m::delay::Delay::new(core.SYST, clocks.system_clock.freq().to_Hz()); + + // Init PWMs + let mut pwm_slices = hal::pwm::Slices::new(pac.PWM, &mut pac.RESETS); + + // Configure PWM4 + let pwm = &mut pwm_slices.pwm4; + pwm.set_ph_correct(); + pwm.enable(); + + // Output channel B on PWM4 to the LED pin + let channel = &mut pwm.channel_b; + channel.output_to(pins.led); + + // Infinite loop, fading LED up and down + loop { + // Ramp brightness up + for i in (LOW..=HIGH).skip(100) { + delay.delay_us(8); + let _ = channel.set_duty_cycle(i); + } + + // Ramp brightness down + for i in (LOW..=HIGH).rev().skip(100) { + delay.delay_us(8); + let _ = channel.set_duty_cycle(i); + } + + delay.delay_ms(500); + } +} + +// End of file diff --git a/boards/pimoroni-pico-lipo-16mb/examples/pimoroni_pico_lipo_16mb_pwm_servo.rs b/boards/pimoroni-pico-lipo-16mb/examples/pimoroni_pico_lipo_16mb_pwm_servo.rs new file mode 100644 index 00000000..9b7f4d37 --- /dev/null +++ b/boards/pimoroni-pico-lipo-16mb/examples/pimoroni_pico_lipo_16mb_pwm_servo.rs @@ -0,0 +1,116 @@ +//! # Pico LiPo PWM Micro Servo Example +//! +//! Moves the micro servo on a Pico board using the PWM peripheral. +//! +//! This will move in different positions the motor attached to GP1. +//! +//! See the `Cargo.toml` file for Copyright and license details. + +#![no_std] +#![no_main] + +use cortex_m::prelude::*; + +// GPIO traits +use embedded_hal::pwm::SetDutyCycle; + +// Traits for converting integers to amounts of time +use fugit::ExtU32; + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// A shorter alias for the Peripheral Access Crate, which provides low-level +// register access +use pimoroni_pico_lipo_16mb::hal::pac; + +// A shorter alias for the Hardware Abstraction Layer, which provides +// higher-level drivers. +use pimoroni_pico_lipo_16mb::hal; + +/// Entry point to our bare-metal application. +/// +/// The `#[rp2040_hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +/// +/// The function configures the RP2040 peripherals, then fades the LED in an +/// infinite loop. +#[rp2040_hal::entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = pac::Peripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + // + // The default is to generate a 125 MHz system clock + let clocks = hal::clocks::init_clocks_and_plls( + pimoroni_pico_lipo_16mb::XOSC_CRYSTAL_FREQ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .ok() + .unwrap(); + + // Configure the Timer peripheral in count-down mode + let timer = hal::Timer::new(pac.TIMER, &mut pac.RESETS, &clocks); + let mut count_down = timer.count_down(); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins up according to their function on this particular board + let pins = pimoroni_pico_lipo_16mb::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // Init PWMs + let mut pwm_slices = hal::pwm::Slices::new(pac.PWM, &mut pac.RESETS); + + // Configure PWM0 + let pwm = &mut pwm_slices.pwm0; + pwm.set_ph_correct(); + pwm.set_div_int(20u8); // 50 hz + pwm.enable(); + + // Output channel B on PWM0 to the GPIO1 pin + let channel = &mut pwm.channel_b; + channel.output_to(pins.gpio1); + + // Infinite loop, moving micro servo from one position to another. + // You may need to adjust the pulse width since several servos from + // different manufacturers respond differently. + loop { + // move to 0° + let _ = channel.set_duty_cycle(2500); + count_down.start(400.millis()); + let _ = nb::block!(count_down.wait()); + + // 0° to 90° + let _ = channel.set_duty_cycle(3930); + count_down.start(400.millis()); + let _ = nb::block!(count_down.wait()); + + // 90° to 180° + let _ = channel.set_duty_cycle(7860); + count_down.start(400.millis()); + let _ = nb::block!(count_down.wait()); + + // 180° to 90° + let _ = channel.set_duty_cycle(3930); + count_down.start(400.millis()); + let _ = nb::block!(count_down.wait()); + } +} + +// End of file diff --git a/boards/pimoroni-pico-lipo-16mb/examples/pimoroni_pico_lipo_16mb_rtic.rs b/boards/pimoroni-pico-lipo-16mb/examples/pimoroni_pico_lipo_16mb_rtic.rs new file mode 100644 index 00000000..dc463bd8 --- /dev/null +++ b/boards/pimoroni-pico-lipo-16mb/examples/pimoroni_pico_lipo_16mb_rtic.rs @@ -0,0 +1,91 @@ +#![no_std] +#![no_main] + +use panic_halt as _; + +#[rtic::app(device = pimoroni_pico_lipo_16mb::hal::pac, peripherals = true)] +mod app { + + use embedded_hal::digital::OutputPin; + use fugit::MicrosDurationU32; + use pimoroni_pico_lipo_16mb::{ + hal::{self, clocks::init_clocks_and_plls, timer::Alarm, watchdog::Watchdog, Sio}, + XOSC_CRYSTAL_FREQ, + }; + + const SCAN_TIME_US: MicrosDurationU32 = MicrosDurationU32::secs(1); + + #[shared] + struct Shared { + timer: hal::Timer, + alarm: hal::timer::Alarm0, + led: hal::gpio::Pin< + hal::gpio::bank0::Gpio25, + hal::gpio::FunctionSioOutput, + hal::gpio::PullNone, + >, + } + + #[local] + struct Local {} + + #[init] + fn init(c: init::Context) -> (Shared, Local, init::Monotonics) { + // Soft-reset does not release the hardware spinlocks + // Release them now to avoid a deadlock after debug or watchdog reset + unsafe { + hal::sio::spinlock_reset(); + } + let mut resets = c.device.RESETS; + let mut watchdog = Watchdog::new(c.device.WATCHDOG); + let clocks = init_clocks_and_plls( + XOSC_CRYSTAL_FREQ, + c.device.XOSC, + c.device.CLOCKS, + c.device.PLL_SYS, + c.device.PLL_USB, + &mut resets, + &mut watchdog, + ) + .ok() + .unwrap(); + + let sio = Sio::new(c.device.SIO); + let pins = pimoroni_pico_lipo_16mb::Pins::new( + c.device.IO_BANK0, + c.device.PADS_BANK0, + sio.gpio_bank0, + &mut resets, + ); + let mut led = pins.led.reconfigure(); + led.set_low().unwrap(); + + let mut timer = hal::Timer::new(c.device.TIMER, &mut resets, &clocks); + let mut alarm = timer.alarm_0().unwrap(); + let _ = alarm.schedule(SCAN_TIME_US); + alarm.enable_interrupt(); + + (Shared { timer, alarm, led }, Local {}, init::Monotonics()) + } + + #[task( + binds = TIMER_IRQ_0, + priority = 1, + shared = [timer, alarm, led], + local = [tog: bool = true], + )] + fn timer_irq(mut c: timer_irq::Context) { + if *c.local.tog { + c.shared.led.lock(|l| l.set_high().unwrap()); + } else { + c.shared.led.lock(|l| l.set_low().unwrap()); + } + *c.local.tog = !*c.local.tog; + + let mut alarm = c.shared.alarm; + (alarm).lock(|a| { + a.clear_interrupt(); + let _ = a.schedule(SCAN_TIME_US); + }); + } +} diff --git a/boards/pimoroni-pico-lipo-16mb/examples/pimoroni_pico_lipo_16mb_rtic_monotonic.rs b/boards/pimoroni-pico-lipo-16mb/examples/pimoroni_pico_lipo_16mb_rtic_monotonic.rs new file mode 100644 index 00000000..acd9164e --- /dev/null +++ b/boards/pimoroni-pico-lipo-16mb/examples/pimoroni_pico_lipo_16mb_rtic_monotonic.rs @@ -0,0 +1,93 @@ +#![no_std] +#![no_main] + +use panic_halt as _; + +#[rtic::app(device = pimoroni_pico_lipo_16mb::hal::pac, peripherals = true, dispatchers = [I2C0_IRQ])] +mod app { + + use embedded_hal::digital::OutputPin; + use fugit::ExtU64; + use pimoroni_pico_lipo_16mb::{ + hal::{ + self, + clocks::init_clocks_and_plls, + timer::{monotonic::Monotonic, Alarm0}, + watchdog::Watchdog, + Sio, + }, + XOSC_CRYSTAL_FREQ, + }; + + #[shared] + struct Shared { + led: hal::gpio::Pin< + hal::gpio::bank0::Gpio25, + hal::gpio::FunctionSioOutput, + hal::gpio::PullNone, + >, + } + + #[monotonic(binds = TIMER_IRQ_0, default = true)] + type MyMono = Monotonic; + + #[local] + struct Local {} + + #[init] + fn init(c: init::Context) -> (Shared, Local, init::Monotonics) { + // Soft-reset does not release the hardware spinlocks + // Release them now to avoid a deadlock after debug or watchdog reset + unsafe { + hal::sio::spinlock_reset(); + } + let mut resets = c.device.RESETS; + let mut watchdog = Watchdog::new(c.device.WATCHDOG); + let clocks = init_clocks_and_plls( + XOSC_CRYSTAL_FREQ, + c.device.XOSC, + c.device.CLOCKS, + c.device.PLL_SYS, + c.device.PLL_USB, + &mut resets, + &mut watchdog, + ) + .ok() + .unwrap(); + + let sio = Sio::new(c.device.SIO); + let pins = pimoroni_pico_lipo_16mb::Pins::new( + c.device.IO_BANK0, + c.device.PADS_BANK0, + sio.gpio_bank0, + &mut resets, + ); + let mut led = pins.led.reconfigure(); + led.set_low().unwrap(); + + let mut timer = hal::Timer::new(c.device.TIMER, &mut resets, &clocks); + let alarm = timer.alarm_0().unwrap(); + blink_led::spawn_after(500.millis()).unwrap(); + + ( + Shared { led }, + Local {}, + init::Monotonics(Monotonic::new(timer, alarm)), + ) + } + + #[task( + shared = [led], + local = [tog: bool = true], + )] + fn blink_led(mut c: blink_led::Context) { + if *c.local.tog { + c.shared.led.lock(|l| l.set_high().unwrap()); + } else { + c.shared.led.lock(|l| l.set_low().unwrap()); + } + *c.local.tog = !*c.local.tog; + + blink_led::spawn_after(500.millis()).unwrap(); + } +} diff --git a/boards/pimoroni-pico-lipo-16mb/examples/pimoroni_pico_lipo_16mb_spi_sd_card.rs b/boards/pimoroni-pico-lipo-16mb/examples/pimoroni_pico_lipo_16mb_spi_sd_card.rs new file mode 100644 index 00000000..6e804a10 --- /dev/null +++ b/boards/pimoroni-pico-lipo-16mb/examples/pimoroni_pico_lipo_16mb_spi_sd_card.rs @@ -0,0 +1,341 @@ +//! # Pico Lipo SD Card Example +//! +//! Reads and writes a file from/to the SD Card that is formatted in FAT32. +//! This example uses the SPI0 device of the Pimoroni Pico LiPo on the +//! pins 4,5,6 and 7. If you don't use an external 3.3V power source, +//! you can connect the +3.3V output on pin 36 to the SD card. +//! +//! SD Cards up to 2TB are supported by the `embedded_sdmmc` crate. +//! I've tested this with a 64GB micro SD card. +//! +//! You need to format the card with an regular old FAT32 filesystem +//! and also make sure the first partition has the right type. This is how your +//! `fdisk` output should look like: +//! +//! ```text +//! fdisk /dev/sdj +//! +//! Welcome to fdisk (util-linux 2.34). +//! Changes will remain in memory only, until you decide to write them. +//! Be careful before using the write command. +//! +//! Command (m for help): Disk /dev/sdj: +//! 59,49 GiB, 63864569856 bytes, 124735488 sectors +//! Disk model: SD/MMC/MS/MSPRO +//! Units: sectors of 1 * 512 = 512 bytes +//! Sector size (logical/physical): 512 bytes / 512 bytes +//! I/O size (minimum/optimal): 512 bytes / 512 bytes +//! Disklabel type: dos +//! Disk identifier: 0x00000000 +//! +//! Device Boot Start End Sectors Size Id Type +//! /dev/sdj1 2048 124735487 124733440 59,5G c W95 FAT32 (LBA) +//! ``` +//! +//! The important bit here is the _Type_ with `W95 FAT32 (LBA)`, other types +//! are rejected by the `embedded_sdmmc` filesystem implementation. +//! +//! Formatting the partition can be done using `mkfs.fat`: +//! +//! $ mkfs.fat /dev/sdj1 +//! +//! The example can either be used with a probe to receive debug output +//! and also the LED is used as status output. There are different blinking +//! patterns. +//! +//! For every successful stage in the example the LED will blink long once. +//! If everything is successful (9 long blink signals), the example will go +//! into a loop and either blink in a _"short long"_ or _"short short long"_ pattern. +//! +//! If there are 4 different error patterns, all with short blinking pulses: +//! +//! - **3 short blink (in a loop)**: Card size could not be retrieved. +//! - **4 short blink (in a loop)**: Error getting volume/partition 0. +//! - **5 short blink (in a loop)**: Error opening root directory. +//! - **6 short blink (in a loop)**: Could not open file 'O.TST'. +//! +//! See the `Cargo.toml` file for Copyright and license details. + +#![no_std] +#![no_main] + +// The macro for our start-up function +use pimoroni_pico_lipo_16mb::entry; + +// info!() and error!() macros for printing information to the debug output +use defmt::*; +use defmt_rtt as _; + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// Pull in any important traits +use pimoroni_pico_lipo_16mb::hal::prelude::*; + +// Embed the `Hz` function/trait: +use fugit::RateExtU32; + +// A shorter alias for the Peripheral Access Crate, which provides low-level +// register access +use pimoroni_pico_lipo_16mb::hal::pac; + +// Import the SPI abstraction: +use pimoroni_pico_lipo_16mb::hal::spi; + +// Import the GPIO abstraction: +use pimoroni_pico_lipo_16mb::hal::gpio; + +// A shorter alias for the Hardware Abstraction Layer, which provides +// higher-level drivers. +use pimoroni_pico_lipo_16mb::hal; + +// Link in the embedded_sdmmc crate. +// The `SdMmcSpi` is used for block level access to the card. +// And the `VolumeManager` gives access to the FAT filesystem functions. +use embedded_sdmmc::{SdCard, TimeSource, Timestamp, VolumeIdx, VolumeManager}; + +// Get the file open mode enum: +use embedded_sdmmc::filesystem::Mode; + +use embedded_hal::delay::DelayNs; +use rp2040_hal::Timer; + +/// A dummy timesource, which is mostly important for creating files. +#[derive(Default)] +pub struct DummyTimesource(); + +impl TimeSource for DummyTimesource { + // In theory you could use the RTC of the rp2040 here, if you had + // any external time synchronizing device. + fn get_timestamp(&self) -> Timestamp { + Timestamp { + year_since_1970: 0, + zero_indexed_month: 0, + zero_indexed_day: 0, + hours: 0, + minutes: 0, + seconds: 0, + } + } +} + +// Setup some blinking codes: +const BLINK_OK_LONG: [u8; 1] = [8u8]; +const BLINK_OK_SHORT_LONG: [u8; 4] = [1u8, 0u8, 6u8, 0u8]; +const BLINK_OK_SHORT_SHORT_LONG: [u8; 6] = [1u8, 0u8, 1u8, 0u8, 6u8, 0u8]; +const BLINK_ERR_3_SHORT: [u8; 6] = [1u8, 0u8, 1u8, 0u8, 1u8, 0u8]; +const BLINK_ERR_4_SHORT: [u8; 8] = [1u8, 0u8, 1u8, 0u8, 1u8, 0u8, 1u8, 0u8]; +const BLINK_ERR_5_SHORT: [u8; 10] = [1u8, 0u8, 1u8, 0u8, 1u8, 0u8, 1u8, 0u8, 1u8, 0u8]; +const BLINK_ERR_6_SHORT: [u8; 12] = [1u8, 0u8, 1u8, 0u8, 1u8, 0u8, 1u8, 0u8, 1u8, 0u8, 1u8, 0u8]; + +fn blink_signals( + pin: &mut dyn embedded_hal::digital::OutputPin, + delay: &mut dyn DelayNs, + sig: &[u8], +) { + for bit in sig { + if *bit != 0 { + pin.set_high().unwrap(); + } else { + pin.set_low().unwrap(); + } + + let length = if *bit > 0 { *bit } else { 1 }; + + for _ in 0..length { + delay.delay_ms(100); + } + } + + pin.set_low().unwrap(); + + delay.delay_ms(500); +} + +fn blink_signals_loop( + pin: &mut dyn embedded_hal::digital::OutputPin, + delay: &mut dyn DelayNs, + sig: &[u8], +) -> ! { + loop { + blink_signals(pin, delay, sig); + delay.delay_ms(1000); + } +} + +#[entry] +fn main() -> ! { + info!("Program start"); + + // Grab our singleton objects + let mut pac = pac::Peripherals::take().unwrap(); + let _core = pac::CorePeripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + // + // The default is to generate a 125 MHz system clock + let clocks = hal::clocks::init_clocks_and_plls( + pimoroni_pico_lipo_16mb::XOSC_CRYSTAL_FREQ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .ok() + .unwrap(); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins up according to their function on this particular board + let pins = pimoroni_pico_lipo_16mb::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // Set the LED to be an output + let mut led_pin = pins.led.into_push_pull_output(); + + // Set up our SPI pins into the correct mode + let spi_sclk: gpio::Pin<_, gpio::FunctionSpi, gpio::PullNone> = pins.gpio2.reconfigure(); + let spi_mosi: gpio::Pin<_, gpio::FunctionSpi, gpio::PullNone> = pins.gpio3.reconfigure(); + let spi_miso: gpio::Pin<_, gpio::FunctionSpi, gpio::PullUp> = pins.gpio4.reconfigure(); + let spi_cs = pins.gpio5.into_push_pull_output(); + + // Create the SPI driver instance for the SPI0 device + let spi = spi::Spi::<_, _, _, 8>::new(pac.SPI0, (spi_mosi, spi_miso, spi_sclk)); + + // Exchange the uninitialised SPI driver for an initialised one + let spi = spi.init( + &mut pac.RESETS, + clocks.peripheral_clock.freq(), + 400.kHz(), // card initialization happens at low baud rate + embedded_hal::spi::MODE_0, + ); + + let mut delay = Timer::new(pac.TIMER, &mut pac.RESETS, &clocks); + + info!("Initialize SPI SD/MMC data structures..."); + let sdcard = SdCard::new(spi, spi_cs, delay); + let mut volume_mgr = VolumeManager::new(sdcard, DummyTimesource::default()); + + blink_signals(&mut led_pin, &mut delay, &BLINK_OK_LONG); + + info!("Init SD card controller and retrieve card size..."); + match volume_mgr.device().num_bytes() { + Ok(size) => info!("card size is {} bytes", size), + Err(e) => { + error!("Error retrieving card size: {}", defmt::Debug2Format(&e)); + blink_signals_loop(&mut led_pin, &mut delay, &BLINK_ERR_3_SHORT); + } + } + + blink_signals(&mut led_pin, &mut delay, &BLINK_OK_LONG); + + // Now that the card is initialized, clock can go faster + volume_mgr + .device() + .spi(|spi| spi.set_baudrate(clocks.peripheral_clock.freq(), 16.MHz())); + + info!("Getting Volume 0..."); + let mut volume = match volume_mgr.get_volume(VolumeIdx(0)) { + Ok(v) => v, + Err(e) => { + error!("Error getting volume 0: {}", defmt::Debug2Format(&e)); + blink_signals_loop(&mut led_pin, &mut delay, &BLINK_ERR_4_SHORT); + } + }; + + blink_signals(&mut led_pin, &mut delay, &BLINK_OK_LONG); + + // After we have the volume (partition) of the drive we got to open the + // root directory: + let dir = match volume_mgr.open_root_dir(&volume) { + Ok(dir) => dir, + Err(e) => { + error!("Error opening root dir: {}", defmt::Debug2Format(&e)); + blink_signals_loop(&mut led_pin, &mut delay, &BLINK_ERR_5_SHORT); + } + }; + + info!("Root directory opened!"); + blink_signals(&mut led_pin, &mut delay, &BLINK_OK_LONG); + + // This shows how to iterate through the directory and how + // to get the file names (and print them in hope they are UTF-8 compatible): + volume_mgr + .iterate_dir(&volume, &dir, |ent| { + info!( + "/{}.{}", + core::str::from_utf8(ent.name.base_name()).unwrap(), + core::str::from_utf8(ent.name.extension()).unwrap() + ); + }) + .unwrap(); + + blink_signals(&mut led_pin, &mut delay, &BLINK_OK_LONG); + + let mut successful_read = false; + + // Next we going to read a file from the SD card: + if let Ok(mut file) = volume_mgr.open_file_in_dir(&mut volume, &dir, "O.TST", Mode::ReadOnly) { + let mut buf = [0u8; 32]; + let read_count = volume_mgr.read(&volume, &mut file, &mut buf).unwrap(); + volume_mgr.close_file(&volume, file).unwrap(); + + if read_count >= 2 { + info!("READ {} bytes: {}", read_count, buf); + + // If we read what we wrote before the last reset, + // we set a flag so that the success blinking at the end + // changes it's pattern. + if buf[0] == 0x42 && buf[1] == 0x1E { + successful_read = true; + } + } + } + + blink_signals(&mut led_pin, &mut delay, &BLINK_OK_LONG); + + match volume_mgr.open_file_in_dir(&mut volume, &dir, "O.TST", Mode::ReadWriteCreateOrTruncate) { + Ok(mut file) => { + volume_mgr + .write(&mut volume, &mut file, b"\x42\x1E") + .unwrap(); + volume_mgr.close_file(&volume, file).unwrap(); + } + Err(e) => { + error!("Error opening file 'O.TST': {}", defmt::Debug2Format(&e)); + blink_signals_loop(&mut led_pin, &mut delay, &BLINK_ERR_6_SHORT); + } + } + + volume_mgr.free(); + + blink_signals(&mut led_pin, &mut delay, &BLINK_OK_LONG); + + if successful_read { + info!("Successfully read previously written file 'O.TST'"); + } else { + info!("Could not read file, which is ok for the first run."); + info!("Reboot the pico lipo!"); + } + + loop { + if successful_read { + blink_signals(&mut led_pin, &mut delay, &BLINK_OK_SHORT_SHORT_LONG); + } else { + blink_signals(&mut led_pin, &mut delay, &BLINK_OK_SHORT_LONG); + } + + delay.delay_ms(1000); + } +} diff --git a/boards/pimoroni-pico-lipo-16mb/examples/pimoroni_pico_lipo_16mb_uart_irq_buffer.rs b/boards/pimoroni-pico-lipo-16mb/examples/pimoroni_pico_lipo_16mb_uart_irq_buffer.rs new file mode 100644 index 00000000..84ae201b --- /dev/null +++ b/boards/pimoroni-pico-lipo-16mb/examples/pimoroni_pico_lipo_16mb_uart_irq_buffer.rs @@ -0,0 +1,292 @@ +//! # UART IRQ TX Buffer Example +//! +//! This application demonstrates how to use the UART Driver to talk to a +//! serial connection. In this example, the IRQ owns the UART and you cannot +//! do any UART access from the main thread. You can, however, write to a +//! static queue, and have the queue contents transferred to the UART under +//! interrupt. +//! +//! The pinouts are: +//! +//! * GPIO 0 - UART TX (out of the RP2040) +//! * GPIO 1 - UART RX (in to the RP2040) +//! * GPIO 25 - An LED we can blink (active high) +//! +//! See the `Cargo.toml` file for Copyright and license details. + +#![no_std] +#![no_main] + +// These are the traits we need from Embedded HAL to treat our hardware +// objects as generic embedded devices. +use embedded_hal::digital::OutputPin; +use embedded_hal_nb::serial::Write as UartWrite; + +// The writeln! trait. +use core::fmt::Write; + +// We also need this for the 'Delay' object to work. +use rp2040_hal::Clock; + +// The macro for our start-up function +use pimoroni_pico_lipo_16mb::entry; + +// Time handling traits +use fugit::RateExtU32; + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// Alias for our HAL crate +use rp2040_hal as hal; + +// A shorter alias for the Peripheral Access Crate, which provides low-level +// register access +use hal::pac; + +// Our interrupt macro +use pac::interrupt; + +// Some short-cuts to useful types +use core::cell::RefCell; +use critical_section::Mutex; +use heapless::spsc::Queue; + +/// Import the GPIO pins we use +use hal::gpio::bank0::{Gpio0, Gpio1}; + +// UART related types +use hal::uart::{DataBits, StopBits, UartConfig}; + +/// Alias the type for our UART pins to make things clearer. +type UartPins = ( + hal::gpio::Pin, + hal::gpio::Pin, +); + +/// Alias the type for our UART to make things clearer. +type Uart = hal::uart::UartPeripheral; + +/// This describes the queue we use for outbound UART data +struct UartQueue { + mutex_cell_queue: Mutex>>, + interrupt: pac::Interrupt, +} + +/// This how we transfer the UART into the Interrupt Handler +static GLOBAL_UART: Mutex>> = Mutex::new(RefCell::new(None)); + +/// This is our outbound UART queue. We write to it from the main thread, and +/// read from it in the UART IRQ. +static UART_TX_QUEUE: UartQueue = UartQueue { + mutex_cell_queue: Mutex::new(RefCell::new(Queue::new())), + interrupt: hal::pac::Interrupt::UART0_IRQ, +}; + +/// Entry point to our barrp_pice-metal application. +/// +/// The `#[entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables are initialised. +/// +/// The function configures the RP2040 peripherals, then writes to the UART in +/// an infinite loop. +#[entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = pac::Peripherals::take().unwrap(); + let core = pac::CorePeripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + let clocks = hal::clocks::init_clocks_and_plls( + pimoroni_pico_lipo_16mb::XOSC_CRYSTAL_FREQ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .ok() + .unwrap(); + + // Lets us wait for fixed periods of time + let mut delay = cortex_m::delay::Delay::new(core.SYST, clocks.system_clock.freq().to_Hz()); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins to their default state + let pins = pimoroni_pico_lipo_16mb::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + let uart_pins = ( + // UART TX (characters sent from RP2040) on pin 1 (GPIO0) + pins.gpio0.reconfigure(), + // UART RX (characters received by RP2040) on pin 2 (GPIO1) + pins.gpio1.reconfigure(), + ); + + // Make a UART on the given pins + let mut uart = hal::uart::UartPeripheral::new(pac.UART0, uart_pins, &mut pac.RESETS) + .enable( + UartConfig::new(9600.Hz(), DataBits::Eight, None, StopBits::One), + clocks.peripheral_clock.freq(), + ) + .unwrap(); + + // Tell the UART to raise its interrupt line on the NVIC when the TX FIFO + // has space in it. + uart.enable_tx_interrupt(); + + // Now we give away the entire UART peripheral, via the variable + // `GLOBAL_UART`. We can no longer access the UART from this main thread. + critical_section::with(|cs| { + GLOBAL_UART.borrow(cs).replace(Some(uart)); + }); + + // But we can blink an LED. + let mut led_pin = pins.led.into_push_pull_output(); + + loop { + // Light the LED whilst the main thread is in the transmit routine. It + // shouldn't be on very long, but it will be on until we get enough + // data /out/ of the queue and over the UART for this remainder of + // this string to fit. + led_pin.set_high().unwrap(); + // Note we can only write to &UART_TX_QUEUE, because it's not mutable and + // `core::fmt::Write` takes mutable references. + writeln!( + &UART_TX_QUEUE, + "Hello, this was sent under interrupt! It's quite a \ + long message, designed not to fit in either the \ + hardware FIFO or the software queue." + ) + .unwrap(); + led_pin.set_low().unwrap(); + // Wait for a second - the UART TX IRQ will transmit the remainder of + // our queue contents in the background. + delay.delay_ms(1000); + } +} + +impl UartQueue { + /// Try and get some data out of the UART Queue. Returns None if queue empty. + fn read_byte(&self) -> Option { + critical_section::with(|cs| { + let cell_queue = self.mutex_cell_queue.borrow(cs); + let mut queue = cell_queue.borrow_mut(); + queue.dequeue() + }) + } + + /// Peek at the next byte in the queue without removing it. + fn peek_byte(&self) -> Option { + critical_section::with(|cs| { + let cell_queue = self.mutex_cell_queue.borrow(cs); + let queue = cell_queue.borrow_mut(); + queue.peek().cloned() + }) + } + + /// Write some data to the queue, spinning until it all fits. + fn write_bytes_blocking(&self, data: &[u8]) { + // Go through all the bytes we need to write. + for byte in data.iter() { + // Keep trying until there is space in the queue. But release the + // mutex between each attempt, otherwise the IRQ will never run + // and we will never have space! + let mut written = false; + while !written { + // Grab the mutex, by turning interrupts off. NOTE: This + // doesn't work if you are using Core 1 as we only turn + // interrupts off on one core. + critical_section::with(|cs| { + // Grab the mutex contents. + let cell_queue = self.mutex_cell_queue.borrow(cs); + // Grab mutable access to the queue. This can't fail + // because there are no interrupts running. + let mut queue = cell_queue.borrow_mut(); + // Try and put the byte in the queue. + if queue.enqueue(*byte).is_ok() { + // It worked! We must have had space. + if !pac::NVIC::is_enabled(self.interrupt) { + unsafe { + // Now enable the UART interrupt in the *Nested + // Vectored Interrupt Controller*, which is part + // of the Cortex-M0+ core. If the FIFO has space, + // the interrupt will run as soon as we're out of + // the closure. + pac::NVIC::unmask(self.interrupt); + // We also have to kick the IRQ in case the FIFO + // was already below the threshold level. + pac::NVIC::pend(self.interrupt); + } + } + written = true; + } + }); + } + } + } +} + +impl core::fmt::Write for &UartQueue { + /// This function allows us to `writeln!` on our global static UART queue. + /// Note we have an impl for &UartQueue, because our global static queue + /// is not mutable and `core::fmt::Write` takes mutable references. + fn write_str(&mut self, data: &str) -> core::fmt::Result { + self.write_bytes_blocking(data.as_bytes()); + Ok(()) + } +} + +#[interrupt] +fn UART0_IRQ() { + // This variable is special. It gets mangled by the `#[interrupt]` macro + // into something that we can access without the `unsafe` keyword. It can + // do this because this function cannot be called re-entrantly. We know + // this because the function's 'real' name is unknown, and hence it cannot + // be called from the main thread. We also know that the NVIC will not + // re-entrantly call an interrupt. + static mut UART: Option> = + None; + + // This is one-time lazy initialisation. We steal the variable given to us + // via `GLOBAL_UART`. + if UART.is_none() { + critical_section::with(|cs| { + *UART = GLOBAL_UART.borrow(cs).take(); + }); + } + + // Check if we have a UART to work with + if let Some(uart) = UART { + // Check if we have data to transmit + while let Some(byte) = UART_TX_QUEUE.peek_byte() { + if uart.write(byte).is_ok() { + // The UART took it, so pop it off the queue. + let _ = UART_TX_QUEUE.read_byte(); + } else { + break; + } + } + + if UART_TX_QUEUE.peek_byte().is_none() { + pac::NVIC::mask(UART_TX_QUEUE.interrupt); + } + } + + // Set an event to ensure the main thread always wakes up, even if it's in + // the process of going to sleep. + cortex_m::asm::sev(); +} + +// End of file diff --git a/boards/pimoroni-pico-lipo-16mb/examples/pimoroni_pico_lipo_16mb_uart_irq_echo.rs b/boards/pimoroni-pico-lipo-16mb/examples/pimoroni_pico_lipo_16mb_uart_irq_echo.rs new file mode 100644 index 00000000..7494634c --- /dev/null +++ b/boards/pimoroni-pico-lipo-16mb/examples/pimoroni_pico_lipo_16mb_uart_irq_echo.rs @@ -0,0 +1,196 @@ +//! # UART IRQ Echo Example +//! +//! This application demonstrates how to use the UART Driver to talk to a serial +//! connection. In this example, the IRQ owns the UART and you cannot do any UART +//! access from the main thread. +//! +//! The pinouts are: +//! +//! * GPIO 0 - UART TX (out of the RP2040) +//! * GPIO 1 - UART RX (in to the RP2040) +//! * GPIO 25 - An LED we can blink (active high) +//! +//! See the `Cargo.toml` file for Copyright and license details. + +#![no_std] +#![no_main] + +// These are the traits we need from Embedded HAL to treat our hardware +// objects as generic embedded devices. +use embedded_hal::digital::OutputPin; +use embedded_hal_nb::serial::{Read, Write}; + +// We also need this for the 'Delay' object to work. +use rp2040_hal::Clock; + +// The macro for our start-up function +use pimoroni_pico_lipo_16mb::entry; + +// Time handling traits +use fugit::RateExtU32; + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// Alias for our HAL crate +use rp2040_hal as hal; + +// A shorter alias for the Peripheral Access Crate, which provides low-level +// register access +use hal::pac; + +// Our interrupt macro +use hal::pac::interrupt; + +// Some short-cuts to useful types +use core::cell::RefCell; +use critical_section::Mutex; + +/// Import the GPIO pins we use +use hal::gpio::bank0::{Gpio0, Gpio1}; + +// UART related types +use hal::uart::{DataBits, StopBits, UartConfig}; + +/// Alias the type for our UART pins to make things clearer. +type UartPins = ( + hal::gpio::Pin, + hal::gpio::Pin, +); + +/// Alias the type for our UART to make things clearer. +type Uart = hal::uart::UartPeripheral; + +/// This how we transfer the UART into the Interrupt Handler +static GLOBAL_UART: Mutex>> = Mutex::new(RefCell::new(None)); + +/// Entry point to our bare-metal application. +/// +/// The `#[entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables are initialised. +/// +/// The function configures the RP2040 peripherals, then writes to the UART in +/// an infinite loop. +#[entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = pac::Peripherals::take().unwrap(); + let core = pac::CorePeripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + let clocks = hal::clocks::init_clocks_and_plls( + pimoroni_pico_lipo_16mb::XOSC_CRYSTAL_FREQ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .ok() + .unwrap(); + + // Lets us wait for fixed periods of time + let mut delay = cortex_m::delay::Delay::new(core.SYST, clocks.system_clock.freq().to_Hz()); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins to their default state + let pins = pimoroni_pico_lipo_16mb::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + let uart_pins = ( + // UART TX (characters sent from RP2040) on pin 1 (GPIO0) + pins.gpio0.reconfigure(), + // UART RX (characters received by RP2040) on pin 2 (GPIO1) + pins.gpio1.reconfigure(), + ); + + // Make a UART on the given pins + let mut uart = hal::uart::UartPeripheral::new(pac.UART0, uart_pins, &mut pac.RESETS) + .enable( + UartConfig::new(9600.Hz(), DataBits::Eight, None, StopBits::One), + clocks.peripheral_clock.freq(), + ) + .unwrap(); + + unsafe { + // Enable the UART interrupt in the *Nested Vectored Interrupt + // Controller*, which is part of the Cortex-M0+ core. + pac::NVIC::unmask(hal::pac::Interrupt::UART0_IRQ); + } + + // Tell the UART to raise its interrupt line on the NVIC when the RX FIFO + // has data in it. + uart.enable_rx_interrupt(); + + // Write something to the UART on start-up so we can check the output pin + // is wired correctly. + uart.write_full_blocking(b"uart_interrupt example started...\n"); + + // Now we give away the entire UART peripheral, via the variable + // `GLOBAL_UART`. We can no longer access the UART from this main thread. + critical_section::with(|cs| { + GLOBAL_UART.borrow(cs).replace(Some(uart)); + }); + + // But we can blink an LED. + let mut led_pin = pins.led.into_push_pull_output(); + + loop { + // The normal *Wait For Interrupts* (WFI) has a race-hazard - the + // interrupt could occur between the CPU checking for interrupts and + // the CPU going to sleep. We wait for events (and interrupts), and + // then we set an event in every interrupt handler. This ensures we + // always wake up correctly. + cortex_m::asm::wfe(); + // Light the LED to indicate we saw an interrupt. + led_pin.set_high().unwrap(); + delay.delay_ms(100); + led_pin.set_low().unwrap(); + } +} + +#[interrupt] +fn UART0_IRQ() { + // This variable is special. It gets mangled by the `#[interrupt]` macro + // into something that we can access without the `unsafe` keyword. It can + // do this because this function cannot be called re-entrantly. We know + // this because the function's 'real' name is unknown, and hence it cannot + // be called from the main thread. We also know that the NVIC will not + // re-entrantly call an interrupt. + static mut UART: Option> = + None; + + // This is one-time lazy initialisation. We steal the variable given to us + // via `GLOBAL_UART`. + if UART.is_none() { + critical_section::with(|cs| { + *UART = GLOBAL_UART.borrow(cs).take(); + }); + } + + // Check if we have a UART to work with + if let Some(uart) = UART { + // Echo the input back to the output until the FIFO is empty. Reading + // from the UART should also clear the UART interrupt flag. + while let Ok(byte) = uart.read() { + let _ = uart.write(byte); + } + } + + // Set an event to ensure the main thread always wakes up, even if it's in + // the process of going to sleep. + cortex_m::asm::sev(); +} + +// End of file diff --git a/boards/pimoroni-pico-lipo-16mb/examples/pimoroni_pico_lipo_16mb_usb_serial.rs b/boards/pimoroni-pico-lipo-16mb/examples/pimoroni_pico_lipo_16mb_usb_serial.rs new file mode 100644 index 00000000..11a1a1e9 --- /dev/null +++ b/boards/pimoroni-pico-lipo-16mb/examples/pimoroni_pico_lipo_16mb_usb_serial.rs @@ -0,0 +1,155 @@ +//! # Pimoroni Pico LiPo 16mb USB Serial Example +//! +//! Creates a USB Serial device on a Pico LiPo board, with the USB driver running in +//! the main thread. +//! +//! This will create a USB Serial device echoing anything it receives. Incoming +//! ASCII characters are converted to upercase, so you can tell it is working +//! and not just local-echo! +//! +//! See the `Cargo.toml` file for Copyright and license details. + +#![no_std] +#![no_main] + +// The macro for our start-up function +use pimoroni_pico_lipo_16mb::entry; + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// A shorter alias for the Peripheral Access Crate, which provides low-level +// register access +use pimoroni_pico_lipo_16mb::hal::pac; + +// A shorter alias for the Hardware Abstraction Layer, which provides +// higher-level drivers. +use pimoroni_pico_lipo_16mb::hal; + +// USB Device support +use usb_device::{class_prelude::*, prelude::*}; + +// USB Communications Class Device support +use usbd_serial::SerialPort; + +// Used to demonstrate writing formatted strings +use core::fmt::Write; +use heapless::String; + +/// Entry point to our bare-metal application. +/// +/// The `#[entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables are initialised. +/// +/// The function configures the RP2040 peripherals, then echoes any characters +/// received over USB Serial. +#[entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = pac::Peripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + // + // The default is to generate a 125 MHz system clock + let clocks = hal::clocks::init_clocks_and_plls( + pimoroni_pico_lipo_16mb::XOSC_CRYSTAL_FREQ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .ok() + .unwrap(); + + let timer = hal::Timer::new(pac.TIMER, &mut pac.RESETS, &clocks); + + #[cfg(feature = "rp2040-e5")] + { + let sio = hal::Sio::new(pac.SIO); + let _pins = pimoroni_pico_lipo_16mb::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + } + + // Set up the USB driver + let usb_bus = UsbBusAllocator::new(hal::usb::UsbBus::new( + pac.USBCTRL_REGS, + pac.USBCTRL_DPRAM, + clocks.usb_clock, + true, + &mut pac.RESETS, + )); + + // Set up the USB Communications Class Device driver + let mut serial = SerialPort::new(&usb_bus); + + // Create a USB device with a fake VID and PID + let mut usb_dev = UsbDeviceBuilder::new(&usb_bus, UsbVidPid(0x16c0, 0x27dd)) + .strings(&[StringDescriptors::default() + .manufacturer("Fake company") + .product("Serial port") + .serial_number("TEST")]) + .unwrap() + .device_class(2) // from: https://www.usb.org/defined-class-codes + .build(); + + let mut said_hello = false; + loop { + // A welcome message at the beginning + if !said_hello && timer.get_counter().ticks() >= 2_000_000 { + said_hello = true; + let _ = serial.write(b"Hello, World!\r\n"); + + let time = timer.get_counter().ticks(); + let mut text: String<64> = String::new(); + writeln!(&mut text, "Current timer ticks: {}", time).unwrap(); + + // This only works reliably because the number of bytes written to + // the serial port is smaller than the buffers available to the USB + // peripheral. In general, the return value should be handled, so that + // bytes not transferred yet don't get lost. + let _ = serial.write(text.as_bytes()); + } + + // Check for new data + if usb_dev.poll(&mut [&mut serial]) { + let mut buf = [0u8; 64]; + match serial.read(&mut buf) { + Err(_e) => { + // Do nothing + } + Ok(0) => { + // Do nothing + } + Ok(count) => { + // Convert to upper case + buf.iter_mut().take(count).for_each(|b| { + b.make_ascii_uppercase(); + }); + // Send back to the host + let mut wr_ptr = &buf[..count]; + while !wr_ptr.is_empty() { + match serial.write(wr_ptr) { + Ok(len) => wr_ptr = &wr_ptr[len..], + // On error, just drop unwritten data. + // One possible error is Err(WouldBlock), meaning the USB + // write buffer is full. + Err(_) => break, + }; + } + } + } + } + } +} + +// End of file diff --git a/boards/pimoroni-pico-lipo-16mb/examples/pimoroni_pico_lipo_16mb_usb_serial_interrupt.rs b/boards/pimoroni-pico-lipo-16mb/examples/pimoroni_pico_lipo_16mb_usb_serial_interrupt.rs new file mode 100644 index 00000000..0f25fe06 --- /dev/null +++ b/boards/pimoroni-pico-lipo-16mb/examples/pimoroni_pico_lipo_16mb_usb_serial_interrupt.rs @@ -0,0 +1,209 @@ +//! # Pimoroni Pico Lipo 16mb USB Serial (with Interrupts) Example +//! +//! Creates a USB Serial device on a Pico LiPo board, with the USB driver running in +//! the USB interrupt. +//! +//! This will create a USB Serial device echoing anything it receives. Incoming +//! ASCII characters are converted to upercase, so you can tell it is working +//! and not just local-echo! +//! +//! See the `Cargo.toml` file for Copyright and license details. + +#![no_std] +#![no_main] + +// The macro for our start-up function +use pimoroni_pico_lipo_16mb::entry; + +// The macro for marking our interrupt functions +use pimoroni_pico_lipo_16mb::hal::pac::interrupt; + +// GPIO traits +use embedded_hal::digital::OutputPin; + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// Pull in any important traits +use pimoroni_pico_lipo_16mb::hal::prelude::*; + +// A shorter alias for the Peripheral Access Crate, which provides low-level +// register access +use pimoroni_pico_lipo_16mb::hal::pac; + +// A shorter alias for the Hardware Abstraction Layer, which provides +// higher-level drivers. +use pimoroni_pico_lipo_16mb::hal; + +// USB Device support +use usb_device::{class_prelude::*, prelude::*}; + +// USB Communications Class Device support +use usbd_serial::SerialPort; + +/// The USB Device Driver (shared with the interrupt). +static mut USB_DEVICE: Option> = None; + +/// The USB Bus Driver (shared with the interrupt). +static mut USB_BUS: Option> = None; + +/// The USB Serial Device Driver (shared with the interrupt). +static mut USB_SERIAL: Option> = None; + +/// Entry point to our bare-metal application. +/// +/// The `#[entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables are initialised. +/// +/// The function configures the RP2040 peripherals, then blinks the LED in an +/// infinite loop. +#[entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = pac::Peripherals::take().unwrap(); + let core = pac::CorePeripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + // + // The default is to generate a 125 MHz system clock + let clocks = hal::clocks::init_clocks_and_plls( + pimoroni_pico_lipo_16mb::XOSC_CRYSTAL_FREQ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .ok() + .unwrap(); + + // Set up the USB driver + let usb_bus = UsbBusAllocator::new(hal::usb::UsbBus::new( + pac.USBCTRL_REGS, + pac.USBCTRL_DPRAM, + clocks.usb_clock, + true, + &mut pac.RESETS, + )); + unsafe { + // Note (safety): This is safe as interrupts haven't been started yet + USB_BUS = Some(usb_bus); + } + + // Grab a reference to the USB Bus allocator. We are promising to the + // compiler not to take mutable access to this global variable whilst this + // reference exists! + let bus_ref = unsafe { USB_BUS.as_ref().unwrap() }; + + // Set up the USB Communications Class Device driver + let serial = SerialPort::new(bus_ref); + unsafe { + USB_SERIAL = Some(serial); + } + + // Create a USB device with a fake VID and PID + let usb_dev = UsbDeviceBuilder::new(bus_ref, UsbVidPid(0x16c0, 0x27dd)) + .strings(&[StringDescriptors::default() + .manufacturer("Fake company") + .product("Serial port") + .serial_number("TEST")]) + .unwrap() + .device_class(2) // from: https://www.usb.org/defined-class-codes + .build(); + unsafe { + // Note (safety): This is safe as interrupts haven't been started yet + USB_DEVICE = Some(usb_dev); + } + + // Enable the USB interrupt + unsafe { + pac::NVIC::unmask(hal::pac::Interrupt::USBCTRL_IRQ); + }; + + // No more USB code after this point in main! We can do anything we want in + // here since USB is handled in the interrupt - let's blink an LED! + + // The delay object lets us wait for specified amounts of time (in + // milliseconds) + let mut delay = cortex_m::delay::Delay::new(core.SYST, clocks.system_clock.freq().to_Hz()); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins up according to their function on this particular board + let pins = pimoroni_pico_lipo_16mb::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // Set the LED to be an output + let mut led_pin = pins.led.into_push_pull_output(); + + // Blink the LED at 1 Hz + loop { + led_pin.set_high().unwrap(); + delay.delay_ms(500); + led_pin.set_low().unwrap(); + delay.delay_ms(500); + } +} + +/// This function is called whenever the USB Hardware generates an Interrupt +/// Request. +/// +/// We do all our USB work under interrupt, so the main thread can continue on +/// knowing nothing about USB. +#[allow(non_snake_case)] +#[interrupt] +unsafe fn USBCTRL_IRQ() { + use core::sync::atomic::{AtomicBool, Ordering}; + + /// Note whether we've already printed the "hello" message. + static SAID_HELLO: AtomicBool = AtomicBool::new(false); + + // Grab the global objects. This is OK as we only access them under interrupt. + let usb_dev = USB_DEVICE.as_mut().unwrap(); + let serial = USB_SERIAL.as_mut().unwrap(); + + // Say hello exactly once on start-up + if !SAID_HELLO.load(Ordering::Relaxed) { + SAID_HELLO.store(true, Ordering::Relaxed); + let _ = serial.write(b"Hello, World!\r\n"); + } + + // Poll the USB driver with all of our supported USB Classes + if usb_dev.poll(&mut [serial]) { + let mut buf = [0u8; 64]; + match serial.read(&mut buf) { + Err(_e) => { + // Do nothing + } + Ok(0) => { + // Do nothing + } + Ok(count) => { + // Convert to upper case + buf.iter_mut().take(count).for_each(|b| { + b.make_ascii_uppercase(); + }); + + // Send back to the host + let mut wr_ptr = &buf[..count]; + while !wr_ptr.is_empty() { + let _ = serial.write(wr_ptr).map(|len| { + wr_ptr = &wr_ptr[len..]; + }); + } + } + } + } +} + +// End of file diff --git a/boards/pimoroni-pico-lipo-16mb/examples/pimoroni_pico_lipo_16mb_usb_twitchy_mouse.rs b/boards/pimoroni-pico-lipo-16mb/examples/pimoroni_pico_lipo_16mb_usb_twitchy_mouse.rs new file mode 100644 index 00000000..66d512b4 --- /dev/null +++ b/boards/pimoroni-pico-lipo-16mb/examples/pimoroni_pico_lipo_16mb_usb_twitchy_mouse.rs @@ -0,0 +1,191 @@ +//! # Pico LiPo USB 'Twitchy' Mouse Example +//! +//! Creates a USB HID Class Pointing device (i.e. a virtual mouse) on a Pico LiPo +//! board, with the USB driver running in the main thread. +//! +//! It generates movement reports which will twitch the cursor up and down by a +//! few pixels, several times a second. +//! +//! See the `Cargo.toml` file for Copyright and license details. +//! +//! This is a port of +//! https://github.com/atsamd-rs/atsamd/blob/master/boards/itsybitsy_m0/examples/twitching_usb_mouse.rs + +#![no_std] +#![no_main] + +// The macro for our start-up function +use pimoroni_pico_lipo_16mb::entry; + +// The macro for marking our interrupt functions +use pimoroni_pico_lipo_16mb::hal::pac::interrupt; + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// Pull in any important traits +use pimoroni_pico_lipo_16mb::hal::prelude::*; + +// A shorter alias for the Peripheral Access Crate, which provides low-level +// register access +use pimoroni_pico_lipo_16mb::hal::pac; + +// A shorter alias for the Hardware Abstraction Layer, which provides +// higher-level drivers. +use pimoroni_pico_lipo_16mb::hal; + +// USB Device support +use usb_device::{class_prelude::*, prelude::*}; + +// USB Human Interface Device (HID) Class support +use usbd_hid::descriptor::generator_prelude::*; +use usbd_hid::descriptor::MouseReport; +use usbd_hid::hid_class::HIDClass; + +/// The USB Device Driver (shared with the interrupt). +static mut USB_DEVICE: Option> = None; + +/// The USB Bus Driver (shared with the interrupt). +static mut USB_BUS: Option> = None; + +/// The USB Human Interface Device Driver (shared with the interrupt). +static mut USB_HID: Option> = None; + +/// Entry point to our bare-metal application. +/// +/// The `#[entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables are initialised. +/// +/// The function configures the RP2040 peripherals, then submits cursor movement +/// updates periodically. +#[entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = pac::Peripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + // + // The default is to generate a 125 MHz system clock + let clocks = hal::clocks::init_clocks_and_plls( + pimoroni_pico_lipo_16mb::XOSC_CRYSTAL_FREQ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .ok() + .unwrap(); + + #[cfg(feature = "rp2040-e5")] + { + let sio = hal::Sio::new(pac.SIO); + let _pins = pimoroni_pico_lipo_16mb::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + } + + // Set up the USB driver + let usb_bus = UsbBusAllocator::new(hal::usb::UsbBus::new( + pac.USBCTRL_REGS, + pac.USBCTRL_DPRAM, + clocks.usb_clock, + true, + &mut pac.RESETS, + )); + unsafe { + // Note (safety): This is safe as interrupts haven't been started yet + USB_BUS = Some(usb_bus); + } + + // Grab a reference to the USB Bus allocator. We are promising to the + // compiler not to take mutable access to this global variable whilst this + // reference exists! + let bus_ref = unsafe { USB_BUS.as_ref().unwrap() }; + + // Set up the USB HID Class Device driver, providing Mouse Reports + let usb_hid = HIDClass::new(bus_ref, MouseReport::desc(), 60); + unsafe { + // Note (safety): This is safe as interrupts haven't been started yet. + USB_HID = Some(usb_hid); + } + + // Create a USB device with a fake VID and PID + let usb_dev = UsbDeviceBuilder::new(bus_ref, UsbVidPid(0x16c0, 0x27da)) + .strings(&[StringDescriptors::default() + .manufacturer("Fake company") + .product("Twitchy Mousey") + .serial_number("TEST")]) + .unwrap() + .device_class(0) + .build(); + unsafe { + // Note (safety): This is safe as interrupts haven't been started yet + USB_DEVICE = Some(usb_dev); + } + + unsafe { + // Enable the USB interrupt + pac::NVIC::unmask(hal::pac::Interrupt::USBCTRL_IRQ); + }; + let core = pac::CorePeripherals::take().unwrap(); + let mut delay = cortex_m::delay::Delay::new(core.SYST, clocks.system_clock.freq().to_Hz()); + + // Move the cursor up and down every 200ms + loop { + delay.delay_ms(100); + + let rep_up = MouseReport { + x: 0, + y: 4, + buttons: 0, + wheel: 0, + pan: 0, + }; + push_mouse_movement(rep_up).ok().unwrap_or(0); + + delay.delay_ms(100); + + let rep_down = MouseReport { + x: 0, + y: -4, + buttons: 0, + wheel: 0, + pan: 0, + }; + push_mouse_movement(rep_down).ok().unwrap_or(0); + } +} + +/// Submit a new mouse movement report to the USB stack. +/// +/// We do this with interrupts disabled, to avoid a race hazard with the USB IRQ. +fn push_mouse_movement(report: MouseReport) -> Result { + critical_section::with(|_| unsafe { + // Now interrupts are disabled, grab the global variable and, if + // available, send it a HID report + USB_HID.as_mut().map(|hid| hid.push_input(&report)) + }) + .unwrap() +} + +/// This function is called whenever the USB Hardware generates an Interrupt +/// Request. +#[allow(non_snake_case)] +#[interrupt] +unsafe fn USBCTRL_IRQ() { + // Handle USB request + let usb_dev = USB_DEVICE.as_mut().unwrap(); + let usb_hid = USB_HID.as_mut().unwrap(); + usb_dev.poll(&mut [usb_hid]); +} + +// End of file diff --git a/boards/pimoroni-pico-lipo-16mb/examples/pimoroni_pico_lipo_16mb_ws2812_led.rs b/boards/pimoroni-pico-lipo-16mb/examples/pimoroni_pico_lipo_16mb_ws2812_led.rs new file mode 100644 index 00000000..c23ee917 --- /dev/null +++ b/boards/pimoroni-pico-lipo-16mb/examples/pimoroni_pico_lipo_16mb_ws2812_led.rs @@ -0,0 +1,219 @@ +//! # Pico LiPo WS2812 RGB LED Example +//! +//! Drives 3 WS2812 LEDs connected directly to the Pimoroni Pico LiPo. +//! This assumes you drive the Pimoroni Pico LiPo via USB power, so that VBUS +//! delivers the 5V and at least enough amperes to drive the LEDs. +//! +//! For a more large scale and longer strips you should use an extra power +//! supply for the LED strip (or know what you are doing ;-) ). +//! +//! The example also comes with an utility function to calculate the colors +//! from HSV color space. It also limits the brightness a bit to save a +//! few milliamperes - be careful if you increase the strip length you will +//! quickly get into power consumption of multiple amperes. +//! +//! The example assumes you connected the data input to pin 6 of the +//! Pimoroni Pico LiPo, which is GPIO4 of the rp2040. Here is a circuit +//! diagram that shows the assumed setup: +//! +//! ```text +//! _______________ /----------------------\ +//! |+5V /---\ +5V|----/ _|USB|_ | +//! |DO <-|LED|<- DI|-\ |1 R 40|-VBUS-/ +//! |GND \---/ GND|--+---\ |2 P 39| +//! """"""""""""""" | \-GND-|3 38| +//! | |4 P 37| +//! | |5 I 36| +//! \------GP4-|6 C | +//! |7 O | +//! | | +//! ......... +//! |20 21| +//! """"""" +//! Symbols: +//! - (+) crossing lines, not connected +//! - (o) connected lines +//! ``` +//! +//! See the `Cargo.toml` file for Copyright and license details. + +#![no_std] +#![no_main] + +// The macro for our start-up function +use pimoroni_pico_lipo_16mb::entry; + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// Pull in any important traits +use pimoroni_pico_lipo_16mb::hal::prelude::*; + +// A shorter alias for the Peripheral Access Crate, which provides low-level +// register access +use pimoroni_pico_lipo_16mb::hal::pac; + +// Import the Timer for Ws2812: +use pimoroni_pico_lipo_16mb::hal::timer::Timer; + +// A shorter alias for the Hardware Abstraction Layer, which provides +// higher-level drivers. +use pimoroni_pico_lipo_16mb::hal; + +// PIOExt for the split() method that is needed to bring +// PIO0 into useable form for Ws2812: +use pimoroni_pico_lipo_16mb::hal::pio::PIOExt; + +// Import useful traits to handle the ws2812 LEDs: +use smart_leds::{brightness, SmartLedsWrite, RGB8}; + +// Import the actual crate to handle the Ws2812 protocol: +use ws2812_pio::Ws2812; + +// Currently 3 consecutive LEDs are driven by this example +// to keep the power draw compatible with USB: +const STRIP_LEN: usize = 3; + +#[entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = pac::Peripherals::take().unwrap(); + let core = pac::CorePeripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + // + // The default is to generate a 125 MHz system clock + let clocks = hal::clocks::init_clocks_and_plls( + pimoroni_pico_lipo_16mb::XOSC_CRYSTAL_FREQ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .ok() + .unwrap(); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins up according to their function on this particular board + let pins = pimoroni_pico_lipo_16mb::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // Setup a delay for the LED blink signals: + let mut frame_delay = + cortex_m::delay::Delay::new(core.SYST, clocks.system_clock.freq().to_Hz()); + + // Import the `sin` function for a smooth hue animation from the + // Pico rp2040 ROM: + let sin = hal::rom_data::float_funcs::fsin::ptr(); + + // Create a count down timer for the Ws2812 instance: + let timer = Timer::new(pac.TIMER, &mut pac.RESETS, &clocks); + + // Split the PIO state machine 0 into individual objects, so that + // Ws2812 can use it: + let (mut pio, sm0, _, _, _) = pac.PIO0.split(&mut pac.RESETS); + + // Instanciate a Ws2812 LED strip: + let mut ws = Ws2812::new( + // Use pin 6 on the Raspberry Pi Pico (which is GPIO4 of the rp2040 chip) + // for the LED data output: + pins.gpio4.into_function(), + &mut pio, + sm0, + clocks.peripheral_clock.freq(), + timer.count_down(), + ); + + let mut leds: [RGB8; STRIP_LEN] = [(0, 0, 0).into(); STRIP_LEN]; + let mut t = 0.0; + + // Bring down the overall brightness of the strip to not blow + // the USB power supply: every LED draws ~60mA, RGB means 3 LEDs per + // ws2812 LED, for 3 LEDs that would be: 3 * 3 * 60mA, which is + // already 540mA for just 3 white LEDs! + let strip_brightness = 64u8; // Limit brightness to 64/256 + + // Slow down timer by this factor (0.1 will result in 10 seconds): + let animation_speed = 0.1; + + loop { + for (i, led) in leds.iter_mut().enumerate() { + // An offset to give 3 consecutive LEDs a different color: + let hue_offs = match i % 3 { + 1 => 0.25, + 2 => 0.5, + _ => 0.0, + }; + + let sin_11 = sin((t + hue_offs) * 2.0 * core::f32::consts::PI); + // Bring -1..1 sine range to 0..1 range: + let sin_01 = (sin_11 + 1.0) * 0.5; + + let hue = 360.0 * sin_01; + let sat = 1.0; + let val = 1.0; + + let rgb = hsv2rgb_u8(hue, sat, val); + *led = rgb.into(); + } + + // Here the magic happens and the `leds` buffer is written to the + // ws2812 LEDs: + ws.write(brightness(leds.iter().copied(), strip_brightness)) + .unwrap(); + + // Wait a bit until calculating the next frame: + frame_delay.delay_ms(16); // ~60 FPS + + // Increase the time counter variable and make sure it + // stays inbetween 0.0 to 1.0 range: + t += (16.0 / 1000.0) * animation_speed; + while t > 1.0 { + t -= 1.0; + } + } +} + +pub fn hsv2rgb(hue: f32, sat: f32, val: f32) -> (f32, f32, f32) { + let c = val * sat; + let v = (hue / 60.0) % 2.0 - 1.0; + let v = if v < 0.0 { -v } else { v }; + let x = c * (1.0 - v); + let m = val - c; + let (r, g, b) = if hue < 60.0 { + (c, x, 0.0) + } else if hue < 120.0 { + (x, c, 0.0) + } else if hue < 180.0 { + (0.0, c, x) + } else if hue < 240.0 { + (0.0, x, c) + } else if hue < 300.0 { + (x, 0.0, c) + } else { + (c, 0.0, x) + }; + (r + m, g + m, b + m) +} + +pub fn hsv2rgb_u8(h: f32, s: f32, v: f32) -> (u8, u8, u8) { + let r = hsv2rgb(h, s, v); + + ( + (r.0 * 255.0) as u8, + (r.1 * 255.0) as u8, + (r.2 * 255.0) as u8, + ) +} diff --git a/boards/pimoroni-pico-lipo-16mb/examples/pwm.pio b/boards/pimoroni-pico-lipo-16mb/examples/pwm.pio new file mode 100644 index 00000000..af1e7251 --- /dev/null +++ b/boards/pimoroni-pico-lipo-16mb/examples/pwm.pio @@ -0,0 +1,31 @@ +; +; Copyright (c) 2020 Raspberry Pi (Trading) Ltd. +; +; SPDX-License-Identifier: BSD-3-Clause +; + +; Side-set pin 0 is used for PWM output + +.program pwm +.side_set 1 opt + + pull noblock side 0 ; Pull from FIFO to OSR if available, else copy X to OSR. + mov x, osr ; Copy most-recently-pulled value back to scratch X + mov y, isr ; ISR contains PWM period. Y used as counter. +countloop: + jmp x!=y noset ; Set pin high if X == Y, keep the two paths length matched + jmp skip side 1 +noset: + nop ; Single dummy cycle to keep the two paths the same length +skip: + jmp y-- countloop ; Loop until Y hits 0, then pull a fresh PWM value from FIFO + +% c-sdk { +static inline void pwm_program_init(PIO pio, uint sm, uint offset, uint pin) { + pio_gpio_init(pio, pin); + pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true); + pio_sm_config c = pwm_program_get_default_config(offset); + sm_config_set_sideset_pins(&c, pin); + pio_sm_init(pio, sm, offset, &c); +} +%} \ No newline at end of file