Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
examples(pygamer): Restore neopixel examples using SPI driver
Browse files Browse the repository at this point in the history
jbeaurivage committed Dec 9, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
1 parent 8243da6 commit 275d0e8
Showing 7 changed files with 547 additions and 1 deletion.
26 changes: 26 additions & 0 deletions boards/pygamer/Cargo.toml
Original file line number Diff line number Diff line change
@@ -33,6 +33,11 @@ version = "0.20.0"
optional = true
version = "0.3.2"

[dependencies.ws2812-spi]
version = "0.5.0"
features = ["mosi_idle_high"]
optional = true

[dev-dependencies]
embedded-graphics = "0.8.1"
embedded-sdmmc = "0.8.0"
@@ -52,6 +57,7 @@ max-channels = ["dma", "atsamd-hal/max-channels"]
panic_led = []
rt = ["cortex-m-rt", "atsamd-hal/samd51j-rt"]
usb = ["atsamd-hal/usb", "usb-device"]
neopixel-spi = ["dep:ws2812-spi"]
# Enable async support from atsamd-hal
async = ["atsamd-hal/async"]

@@ -89,3 +95,23 @@ name = "timer"
[[example]]
name = "usb_poll"
required-features = ["usb"]

[[example]]
name = "neopixel_adc_battery"
required-features = ["neopixel-spi"]

[[example]]
name = "neopixel_adc_light"
required-features = ["neopixel-spi"]

[[example]]
name = "neopixel_button"
required-features = ["neopixel-spi"]

[[example]]
name = "neopixel_easing"
required-features = ["neopixel-spi"]

[[example]]
name = "neopixel_rainbow"
required-features = ["neopixel-spi"]
89 changes: 89 additions & 0 deletions boards/pygamer/examples/neopixel_adc_battery.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
//! Display battery percentage on the neopixels.
//!
//! Note leds may appear white during debug. Either build for release or add
//! opt-level = 2 to profile.dev in Cargo.toml
#![no_std]
#![no_main]

#[cfg(not(feature = "panic_led"))]
use panic_halt as _;
use pygamer::{entry, hal, pac, Pins};

use hal::adc::Adc;
use hal::{clock::GenericClockController, delay::Delay};

use pac::gclk::pchctrl::Genselect::Gclk11;

use hal::ehal::delay::DelayNs;

use pac::{CorePeripherals, Peripherals};
use smart_leds::{brightness, SmartLedsWrite, RGB8};

#[entry]
fn main() -> ! {
let mut peripherals = Peripherals::take().unwrap();
let core = CorePeripherals::take().unwrap();
let mut clocks = GenericClockController::with_internal_32kosc(
peripherals.gclk,
&mut peripherals.mclk,
&mut peripherals.osc32kctrl,
&mut peripherals.oscctrl,
&mut peripherals.nvmctrl,
);
let pins = Pins::new(peripherals.port).split();

let mut adc0 = Adc::adc0(peripherals.adc0, &mut peripherals.mclk, &mut clocks, Gclk11);
let mut battery = pins.battery.init();

// neopixels
let mut neopixel = pins.neopixel.init_spi(
&mut clocks,
// Unfortunately, the SPI driver requires a clock pin, even though it's not used by the
// neopixels.
pins.i2c.scl,
peripherals.sercom2,
&mut peripherals.mclk,
);

let mut delay = Delay::new(core.SYST, &mut clocks);

//todo put this on a .. 10minute, 30min, update timer
loop {
let battery_data = battery.read(&mut adc0);

let mut colors = [
RGB8::default(),
RGB8::default(),
RGB8::default(),
RGB8::default(),
RGB8::default(),
];

if battery_data < 3.6 {
enable_leds(1, &mut colors);
} else if (3.6..3.8).contains(&battery_data) {
enable_leds(2, &mut colors);
} else if (3.8..3.9).contains(&battery_data) {
enable_leds(3, &mut colors);
} else if (3.9..4.0).contains(&battery_data) {
enable_leds(4, &mut colors);
} else {
enable_leds(5, &mut colors);
};

neopixel
.write(brightness(colors.iter().cloned(), 1))
.unwrap();

// Reset the LEDs
delay.delay_ms(10);
}
}

/// Turn on the specified number of LEDs and set the color to red.
fn enable_leds(num_leds: usize, colors: &mut [RGB8]) {
for color in colors.iter_mut().take(num_leds) {
*color = RGB8::from((255, 0, 0));
}
}
88 changes: 88 additions & 0 deletions boards/pygamer/examples/neopixel_adc_light.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
//! Display light sensor reading on the neopixels.
//!
//! Note leds may appear white during debug. Either build for release or add
//! opt-level = 2 to profile.dev in Cargo.toml
#![no_std]
#![no_main]

#[cfg(not(feature = "panic_led"))]
use panic_halt as _;
use pygamer::{entry, hal, pac, Pins};

use hal::adc::Adc;
use hal::prelude::*;
use hal::{clock::GenericClockController, delay::Delay};
use pac::gclk::pchctrl::Genselect::Gclk11;
use pac::{CorePeripherals, Peripherals};
use smart_leds::SmartLedsWrite;
use smart_leds::{
hsv::{hsv2rgb, Hsv},
RGB8,
};

#[entry]
fn main() -> ! {
let mut peripherals = Peripherals::take().unwrap();
let core = CorePeripherals::take().unwrap();
let mut clocks = GenericClockController::with_internal_32kosc(
peripherals.gclk,
&mut peripherals.mclk,
&mut peripherals.osc32kctrl,
&mut peripherals.oscctrl,
&mut peripherals.nvmctrl,
);
let pins = Pins::new(peripherals.port).split();

let mut adc1 = Adc::adc1(peripherals.adc1, &mut peripherals.mclk, &mut clocks, Gclk11);
let mut light = pins.light_pin.into_alternate();

// neopixels
let mut neopixel = pins.neopixel.init_spi(
&mut clocks,
// Unfortunately, the SPI driver requires a clock pin, even though it's not used by the
// neopixels.
pins.i2c.scl,
peripherals.sercom2,
&mut peripherals.mclk,
);

let mut delay = Delay::new(core.SYST, &mut clocks);

const NUM_LEDS: usize = 5;
let mut j: u8 = 0;

loop {
let light_data: u16 = adc1.read(&mut light).unwrap();

let pos: usize = if light_data < 100 {
0
} else if (147..1048).contains(&light_data) {
1
} else if (1048..3048).contains(&light_data) {
2
} else if (3048..3948).contains(&light_data) {
3
} else {
4
};

//finally paint the one led wherever the position is
let _ = neopixel.write((0..NUM_LEDS).map(|i| {
if i == pos {
hsv2rgb(Hsv {
hue: j,
sat: 255,
val: 32,
})
} else {
RGB8::default()
}
}));

//incremement the hue easing
j = j.wrapping_add(1);

delay.delay_ms(10u8);
}
}
115 changes: 115 additions & 0 deletions boards/pygamer/examples/neopixel_button.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
//! Joystick y controls the color of a neopixel while Joystick x moves it
//! left and right around the center neopixel
//! Select and Start control a second neopixel left and right while it is
//! automatically rotating through the color wheel
//! When they overlap, joystick takes precedence
//!
//! Note leds may appear white during debug. Either build for release or add
//! opt-level = 2 to profile.dev in Cargo.toml
#![no_std]
#![no_main]

#[cfg(not(feature = "panic_led"))]
use panic_halt as _;
use pygamer::{self as bsp, entry, hal, pac, pins::Keys, Pins};

use bsp::util::map_from;
use hal::adc::Adc;
use hal::{clock::GenericClockController, delay::Delay};

use pac::gclk::pchctrl::Genselect::Gclk11;
use pac::{CorePeripherals, Peripherals};

use hal::ehal::delay::DelayNs;

use smart_leds::SmartLedsWrite;
use smart_leds::{
hsv::{hsv2rgb, Hsv},
RGB8,
};

#[entry]
fn main() -> ! {
let mut peripherals = Peripherals::take().unwrap();
let core_peripherals = CorePeripherals::take().unwrap();

let mut clocks = GenericClockController::with_internal_32kosc(
peripherals.gclk,
&mut peripherals.mclk,
&mut peripherals.osc32kctrl,
&mut peripherals.oscctrl,
&mut peripherals.nvmctrl,
);

let mut delay = Delay::new(core_peripherals.SYST, &mut clocks);
let pins = Pins::new(peripherals.port).split();

let mut buttons = pins.buttons.init();

let mut adc1 = Adc::adc1(peripherals.adc1, &mut peripherals.mclk, &mut clocks, Gclk11);
let mut joystick = pins.joystick.init();

// neopixels
let mut neopixel = pins.neopixel.init_spi(
&mut clocks,
// Unfortunately, the SPI driver requires a clock pin, even though it's not used by the
// neopixels.
pins.i2c.scl,
peripherals.sercom2,
&mut peripherals.mclk,
);

const NUM_LEDS: usize = 5;
let mut pos_button: usize = 2;
let mut color_button: u8 = 0;
loop {
let (x, y) = joystick.read(&mut adc1);

// map up/down to control rainbow color 0-255
let color_joy = map_from(y as i16, (0, 4095), (0, 255)) as u8;

// map left/right to neopixel position 0-4
// joystick is not quite linear, rests at second pixel
// shifting up by 500 seems to help
let pos_joy = map_from(x as i16 + 500, (0, 4595), (0, 4)) as usize;

for event in buttons.events() {
match event {
Keys::SelectDown => {
pos_button = pos_button.saturating_sub(1);
}
Keys::StartDown => {
if pos_button < 4 {
pos_button += 1;
}
}
_ => {}
}
}

//finally paint the two leds at position, accel priority
let _ = neopixel.write((0..NUM_LEDS).map(|i| {
if i == pos_joy {
hsv2rgb(Hsv {
hue: color_joy,
sat: 255,
val: 32,
})
} else if i == pos_button {
hsv2rgb(Hsv {
hue: color_button,
sat: 255,
val: 32,
})
} else {
RGB8::default()
}
}));

//incremement the hue easing
color_button = color_button.wrapping_add(1);

delay.delay_ms(5);
}
}
102 changes: 102 additions & 0 deletions boards/pygamer/examples/neopixel_easing.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
//! Randomly choose and led and color to breath in and out
//!
//! Note leds may appear white during debug. Either build for release or add
//! opt-level = 2 to profile.dev in Cargo.toml
#![no_std]
#![no_main]

#[cfg(not(feature = "panic_led"))]
use panic_halt as _;
use pygamer::{entry, hal, pac, Pins};

use core::f32::consts::FRAC_PI_2;

use hal::clock::GenericClockController;
use hal::delay::Delay;
use hal::trng::Trng;

use pac::{CorePeripherals, Peripherals};

use hal::ehal::delay::DelayNs;

use micromath::F32Ext;
use smart_leds::SmartLedsWrite;
use smart_leds::{
hsv::{hsv2rgb, Hsv},
RGB8,
};

#[entry]
fn main() -> ! {
let mut peripherals = Peripherals::take().unwrap();
let core = CorePeripherals::take().unwrap();
let mut clocks = GenericClockController::with_internal_32kosc(
peripherals.gclk,
&mut peripherals.mclk,
&mut peripherals.osc32kctrl,
&mut peripherals.oscctrl,
&mut peripherals.nvmctrl,
);

let pins = Pins::new(peripherals.port).split();

// neopixels
let mut neopixel = pins.neopixel.init_spi(
&mut clocks,
// Unfortunately, the SPI driver requires a clock pin, even though it's not used by the
// neopixels.
pins.i2c.scl,
peripherals.sercom2,
&mut peripherals.mclk,
);

let mut delay = Delay::new(core.SYST, &mut clocks);

let trng = Trng::new(&mut peripherals.mclk, peripherals.trng);

const NUM_LEDS: usize = 5;

loop {
let rand = trng.random_u8();
let pos: usize = rand.wrapping_rem(5) as usize; //random led

//slowly enable led
for j in 0..255u8 {
let _ = neopixel.write((0..NUM_LEDS).map(|i| {
if i == pos {
hsv2rgb(Hsv {
hue: rand,
sat: 255,
val: sine_ease_in(j as f32, 0.0, 32.0, 255.0) as u8,
})
} else {
RGB8::default()
}
}));
delay.delay_ms(5);
}

//slowly disable led - note the reverse .rev()
for j in (0..255u8).rev() {
let _ = neopixel.write((0..NUM_LEDS).map(|i| {
if i == pos {
hsv2rgb(Hsv {
hue: rand,
sat: 255,
val: sine_ease_in(j as f32, 0.0, 32.0, 255.0) as u8,
})
} else {
RGB8::default()
}
}));
delay.delay_ms(5);
}
}
}

#[inline]
// current step, where oputput starts, where output ends, last step
fn sine_ease_in(t: f32, b: f32, c: f32, d: f32) -> f32 {
-c * (t / d * FRAC_PI_2).cos() + c + b
}
84 changes: 84 additions & 0 deletions boards/pygamer/examples/neopixel_rainbow.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
//! Rotate all neopixel leds through a rainbow. Uses a luckily placed set of SPI
//! pins as a timer source.
//!
//! Note leds may appear white during debug. Either build for release or add
//! opt-level = 2 to profile.dev in Cargo.toml
#![no_std]
#![no_main]

#[cfg(not(feature = "panic_led"))]
use panic_halt as _;
use pygamer::{entry, hal, pac, Pins};

use hal::{clock::GenericClockController, delay::Delay};

use pac::{CorePeripherals, Peripherals};

use hal::ehal::delay::DelayNs;

use smart_leds::hsv::{hsv2rgb, Hsv};
use smart_leds::SmartLedsWrite;

#[entry]
fn main() -> ! {
let mut peripherals = Peripherals::take().unwrap();
let core = CorePeripherals::take().unwrap();
let mut clocks = GenericClockController::with_internal_32kosc(
peripherals.gclk,
&mut peripherals.mclk,
&mut peripherals.osc32kctrl,
&mut peripherals.oscctrl,
&mut peripherals.nvmctrl,
);
let pins = Pins::new(peripherals.port).split();

// neopixels
let mut neopixel = pins.neopixel.init_spi(
&mut clocks,
// Unfortunately, the SPI driver requires a clock pin, even though it's not used by the
// neopixels.
pins.i2c.scl,
peripherals.sercom2,
&mut peripherals.mclk,
);

let mut delay = Delay::new(core.SYST, &mut clocks);

loop {
for j in 0..255u8 {
let colors = [
// split the color changes across all 5 leds evenly, 255/5=51
// and have them safely wrap over when they go above 255
hsv2rgb(Hsv {
hue: j,
sat: 255,
val: 32,
}),
hsv2rgb(Hsv {
hue: j.wrapping_add(51),
sat: 255,
val: 32,
}),
hsv2rgb(Hsv {
hue: j.wrapping_add(102),
sat: 255,
val: 32,
}),
hsv2rgb(Hsv {
hue: j.wrapping_add(153),
sat: 255,
val: 32,
}),
hsv2rgb(Hsv {
hue: j.wrapping_add(204),
sat: 255,
val: 32,
}),
];

neopixel.write(colors.iter().cloned()).unwrap();
delay.delay_ms(5);
}
}
}
44 changes: 43 additions & 1 deletion boards/pygamer/src/pins.rs
Original file line number Diff line number Diff line change
@@ -150,7 +150,8 @@ pub mod aliases {
name: neopixel
aliases: {
PushPullOutput: NeopixelPin,
Reset: NeopixelReset
Reset: NeopixelReset,
AlternateC: NeopixelPinSpi,
}
},
/// Digital pin 9
@@ -665,6 +666,47 @@ pub struct Neopixel {
pub neopixel: NeopixelReset,
}

/// Pads for the neopixel SPI driver
#[cfg(feature = "neopixel-spi")]
pub type NeopixelSpiPads = spi::Pads<hal::sercom::Sercom2, IoSet1, NoneT, NeopixelPinSpi, Scl>;

/// SPI master neopixel driver
#[cfg(feature = "neopixel-spi")]
pub type NeopixelSpi = spi::PanicOnRead<spi::Spi<spi::Config<NeopixelSpiPads>, spi::Tx>>;

#[cfg(feature = "neopixel-spi")]
impl Neopixel {
/// Setup the SPI driver for neopixel addressable LEDs.
///
/// Unfortunately, the SPI uses SERCOM2, which is also used by the onboard
/// I2C. Therefore the I2C cannot be used simultaneously with the neopixels
/// in SPI mode. The SPI peripheral driver also requires PA12, aka SDA to
/// drive the SCLK signal. Even though it's not used by the LEDs, it's still
/// required to provide a clock pin in order to compile.
pub fn init_spi(
self,
clocks: &mut GenericClockController,
scl: impl hal::gpio::AnyPin<Id = hal::gpio::PA13>,
sercom2: pac::Sercom2,
mclk: &mut pac::Mclk,
) -> ws2812_spi::Ws2812<NeopixelSpi> {
use hal::fugit::RateExtU32;

let gclk0 = clocks.gclk0();
let clock = &clocks.sercom2_core(&gclk0).unwrap();
let pads = spi::Pads::default()
.data_out(self.neopixel)
.sclk(scl.into());
let spi = spi::Config::new(mclk, sercom2, pads, clock.freq())
.spi_mode(spi::MODE_0)
.baud(3.MHz())
.enable()
.into_panic_on_read();

ws2812_spi::Ws2812::new(spi)
}
}

/// SPI pins
pub struct SPI {
pub mosi: SpiMosiReset,

0 comments on commit 275d0e8

Please sign in to comment.