pimoroni-trackball-driver

A simple driver for the Pimoroni Trackball written with embedded-hal.

The imlpementation is based off the implementations written in Python (https://github.com/pimoroni/trackball-python) and C++ (https://github.com/pimoroni/pimoroni-pico/blob/cb958a7c8a73cdc51873058be4918b893d2c7797/drivers/trackball/trackball.cpp).

Example

#![no_std]
#![no_main]

use bsp::hal::gpio::bank0::{Gpio20, Gpio21, Gpio22};
use bsp::hal::gpio::{FunctionI2C, Interrupt, Pin, PullUpInput};
use bsp::pac::I2C0;
use core::cell::RefCell;
use core::iter::once;
use cortex_m::interrupt::Mutex;
use cortex_m_rt::entry;
use defmt::*;
use defmt_rtt as _;
use embedded_time::duration::Extensions as _;
use embedded_time::rate::Extensions as _;
use panic_probe as _;
use pimoroni_trackball_driver as trackball;
use rp_pico as bsp;
use bsp::hal::clocks::{init_clocks_and_plls, Clock};
use bsp::hal::pac::{self, interrupt};
use bsp::hal::sio::Sio;
use bsp::hal::watchdog::Watchdog;
use bsp::hal::Timer;
use bsp::hal::{self, I2C};
use bsp::XOSC_CRYSTAL_FREQ;
use embedded_hal::timer::CountDown;
use smart_leds::{brightness, RGB8};
use trackball::{Trackball, TrackballBuilder};

const LED_ANIM_TIME_US: u32 = 32_000; // 60Hz
type TrackballWithPins = Trackball<
    I2C<I2C0, (Pin<Gpio20, FunctionI2C>, Pin<Gpio21, FunctionI2C>)>,
    Pin<Gpio22, PullUpInput>,
>;
static TRACKBALL: Mutex<RefCell<Option<TrackballWithPins>>> = Mutex::new(RefCell::new(None));

#[entry]
fn main() -> ! {
    info!("Program start");
    let mut pac = pac::Peripherals::take().unwrap();
    let mut watchdog = Watchdog::new(pac.WATCHDOG);
    let sio = Sio::new(pac.SIO);

    let clocks = init_clocks_and_plls(
        XOSC_CRYSTAL_FREQ,
        pac.XOSC,
        pac.CLOCKS,
        pac.PLL_SYS,
        pac.PLL_USB,
        &mut pac.RESETS,
        &mut watchdog,
    )
    .ok()
    .unwrap();

    let pins = bsp::Pins::new(
        pac.IO_BANK0,
        pac.PADS_BANK0,
        sio.gpio_bank0,
        &mut pac.RESETS,
    );

    let timer = Timer::new(pac.TIMER, &mut pac.RESETS);
    let mut delay = timer.count_down();

    // Configure the i2c peripheral for the trackball.
    let i2c = hal::I2C::i2c0(
        pac.I2C0,
        pins.gpio20.into_mode::<hal::gpio::FunctionI2C>(),
        pins.gpio21.into_mode::<hal::gpio::FunctionI2C>(),
        100.kHz(),
        &mut pac.RESETS,
        clocks.peripheral_clock.freq(),
    );

    // Construct the I2C interface from the peripheral.
    let interface = trackball::I2CInterface::new(i2c);

    // Construct the trackball with the I2C interface and an interrupt pin.
    let mut trackball = TrackballBuilder::<_, Pin<_, _>>::new(interface)
        .interrupt_pin(pins.gpio22.into_pull_up_input())
        .build();

    // Initialize the trackball, setting the interrupt enabled when the input pin triggers low.
    let _ = trackball
        .init(|interrupt_pin| interrupt_pin.set_interrupt_enabled(Interrupt::EdgeLow, true));

    trackball.set_rgbw(0, 0, 0, 0).unwrap();
    let mut n: u8 = 128;

    // Unmask the IO_BANK0 IRQ so that the NVIC interrupt controller
    // will jump to the interrupt function when the interrupt occurs.
    // We do this last so that the interrupt can't go off while
    // it is in the middle of being configured
    unsafe {
        // Also ensure to assign the trackball data for the interrupt before the interrupt handler
        // is enabled.
        cortex_m::interrupt::free(|cs| *TRACKBALL.borrow(cs).borrow_mut() = Some(trackball));
        pac::NVIC::unmask(pac::Interrupt::IO_IRQ_BANK0);
    }

    // Update the LED on the trackball to display a color wheel.
    loop {
        delay.start(LED_ANIM_TIME_US.microseconds());
        let _ = nb::block!(delay.wait());

        let rgb = brightness(once(wheel(n)), 32).next().unwrap();
        n = n.wrapping_add(1);
        cortex_m::interrupt::free(|cs| {
            let mut trackball = TRACKBALL.borrow(cs).borrow_mut();
            let trackball = trackball.as_mut().unwrap();
            trackball.set_rgbw(rgb.r, rgb.g, rgb.b, 0).unwrap();
        });
    }
}

#[interrupt]
fn IO_IRQ_BANK0() {
    let data = cortex_m::interrupt::free(|cs| {
        let mut trackball = TRACKBALL.borrow(cs).borrow_mut();
        let trackball = trackball.as_mut().unwrap();
        // Read the trackball data on interrupt.
        let data = trackball.read().unwrap();
        // Ensure the interrupt is cleared before we continue.
        trackball.interrupt().clear_interrupt(Interrupt::EdgeLow);
        data
    });

    // Log the state of the trackball data for the current state.
    if data.up > 0 || data.down > 0 || data.left > 0 || data.right > 0 || data.switch_changed {
        info!(
            "{} {} {} {} {} {}",
            data.up,
            data.down,
            data.left,
            data.right,
            if data.switch_changed { 1u8 } else { 0 },
            if data.switch_pressed { 1u8 } else { 0 }
        );
    }
}

/// Convert a number from `0..=255` to an RGB color triplet.
///
/// The colours are a transition from red, to green, to blue and back to red.
fn wheel(mut wheel_pos: u8) -> RGB8 {
    wheel_pos = 255 - wheel_pos;
    if wheel_pos < 85 {
        // No green in this sector - red and blue only
        (255 - (wheel_pos * 3), 0, wheel_pos * 3).into()
    } else if wheel_pos < 170 {
        // No red in this sector - green and blue only
        wheel_pos -= 85;
        (0, wheel_pos * 3, 255 - (wheel_pos * 3)).into()
    } else {
        // No blue in this sector - red and green only
        wheel_pos -= 170;
        (wheel_pos * 3, 255 - (wheel_pos * 3), 0).into()
    }
}

License

MIT