Compare commits
4 Commits
09a920e610
...
256341cd5e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
256341cd5e | ||
|
|
0a1f5f2dfb | ||
|
|
ff8483dbaa | ||
|
|
083263e19e |
@ -35,5 +35,7 @@ embedded-graphics = "0.8.1"
|
|||||||
ili9341 = "0.6.0"
|
ili9341 = "0.6.0"
|
||||||
display-interface-spi = "0.5.0"
|
display-interface-spi = "0.5.0"
|
||||||
|
|
||||||
|
gui = { path="../gui/" }
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
debug = 2
|
debug = 2
|
||||||
@ -2,15 +2,17 @@
|
|||||||
#![no_main]
|
#![no_main]
|
||||||
|
|
||||||
use core::cell::RefCell;
|
use core::cell::RefCell;
|
||||||
|
use defmt::{error, info};
|
||||||
use display_interface_spi::SPIInterface;
|
use display_interface_spi::SPIInterface;
|
||||||
use embassy_embedded_hal::shared_bus::blocking::spi::SpiDevice;
|
use embassy_embedded_hal::shared_bus::blocking::spi::SpiDevice;
|
||||||
use embassy_executor::Spawner;
|
use embassy_executor::Spawner;
|
||||||
use embassy_nrf::{
|
use embassy_nrf::{
|
||||||
bind_interrupts,
|
bind_interrupts,
|
||||||
gpio::{AnyPin, Level, Output, OutputDrive, Pin},
|
gpio::{AnyPin, Input, Level, Output, OutputDrive, Pin},
|
||||||
peripherals, spim,
|
peripherals, spim,
|
||||||
};
|
};
|
||||||
use embassy_sync::blocking_mutex::Mutex;
|
use embassy_sync::blocking_mutex::{raw::ThreadModeRawMutex, Mutex};
|
||||||
|
use embassy_sync::channel::{Channel, Sender};
|
||||||
use embassy_time::{Duration, Timer};
|
use embassy_time::{Duration, Timer};
|
||||||
use ili9341::Ili9341;
|
use ili9341::Ili9341;
|
||||||
use {defmt_rtt as _, panic_probe as _};
|
use {defmt_rtt as _, panic_probe as _};
|
||||||
@ -24,6 +26,7 @@ use embedded_graphics::{
|
|||||||
},
|
},
|
||||||
text::{Alignment, Text},
|
text::{Alignment, Text},
|
||||||
};
|
};
|
||||||
|
use gui::*;
|
||||||
|
|
||||||
// Setup interrupts
|
// Setup interrupts
|
||||||
bind_interrupts!(struct Irqs {
|
bind_interrupts!(struct Irqs {
|
||||||
@ -49,77 +52,100 @@ async fn blink(pin: AnyPin, blink_delay: Duration) -> ! {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a function to run the demo
|
static GUI_CHANNEL: Channel<ThreadModeRawMutex, gui::GuiAction, 8> = Channel::new();
|
||||||
///
|
|
||||||
/// # Arguments
|
|
||||||
/// This function takes in a variable of a generic type `D`.
|
|
||||||
/// With the restriction that the generic type `D` implements
|
|
||||||
/// `DrawTarget`, aka that it is a display.
|
|
||||||
///
|
|
||||||
/// I've also said it needs to use a certain colormode, because
|
|
||||||
/// that is the color mode I use in the demo.
|
|
||||||
///
|
|
||||||
/// While this may seem a little clunky this function now accepts _any_
|
|
||||||
/// display as long as it uses the standard 565 colormode.
|
|
||||||
///
|
|
||||||
/// # Returns
|
|
||||||
/// The function returns a `Result` containing either an empty tuple
|
|
||||||
/// or the error type associated with the generic type `D`.
|
|
||||||
fn display_demo<D>(display: &mut D) -> Result<(), D::Error>
|
|
||||||
where
|
|
||||||
D: DrawTarget<Color = Rgb565>,
|
|
||||||
{
|
|
||||||
display.clear(Rgb565::CSS_DARK_OLIVE_GREEN)?;
|
|
||||||
|
|
||||||
// Create styles used by the drawing operations.
|
#[embassy_executor::task]
|
||||||
let thin_stroke = PrimitiveStyle::with_stroke(Rgb565::WHITE, 1);
|
async fn gui_task(
|
||||||
let thick_stroke = PrimitiveStyle::with_stroke(Rgb565::WHITE, 3);
|
peripheral: embassy_nrf::peripherals::SERIAL3,
|
||||||
let border_stroke = PrimitiveStyleBuilder::new()
|
sck: AnyPin,
|
||||||
.stroke_color(Rgb565::WHITE)
|
miso: AnyPin,
|
||||||
.stroke_width(3)
|
mosi: AnyPin,
|
||||||
.stroke_alignment(StrokeAlignment::Inside)
|
display_reset_pin: AnyPin,
|
||||||
.build();
|
cs_pin: AnyPin,
|
||||||
let fill = PrimitiveStyle::with_fill(Rgb565::WHITE);
|
dc_pin: AnyPin,
|
||||||
let character_style = MonoTextStyle::new(&FONT_10X20, Rgb565::WHITE);
|
) -> ! {
|
||||||
|
///////////////
|
||||||
|
// Setup SPI //
|
||||||
|
///////////////
|
||||||
|
|
||||||
let yoffset = 10;
|
// Create an SPI config with the SPI frequency at 8MHz
|
||||||
|
let mut spim_config = spim::Config::default();
|
||||||
|
spim_config.frequency = spim::Frequency::M8;
|
||||||
|
|
||||||
// Draw a 3px wide outline around the display.
|
// Grab the SPI Master peripheral form the nRF60
|
||||||
display
|
let spim = embassy_nrf::spim::Spim::new(peripheral, Irqs, sck, miso, mosi, spim_config);
|
||||||
.bounding_box()
|
// Use the SPI peripheral to create a shared SPI bus
|
||||||
.into_styled(border_stroke)
|
let spi_bus: Mutex<embassy_sync::blocking_mutex::raw::NoopRawMutex, _> =
|
||||||
.draw(display)?;
|
Mutex::new(RefCell::new(spim));
|
||||||
|
|
||||||
// Draw a triangle.
|
////////////////
|
||||||
Triangle::new(
|
// Setup pins //
|
||||||
Point::new(16, 16 + yoffset),
|
////////////////
|
||||||
Point::new(16 + 16, 16 + yoffset),
|
|
||||||
Point::new(16 + 8, yoffset),
|
// Initialize the CS, DC, and display reset pins as outputs
|
||||||
|
let chip_select = Output::new(cs_pin, Level::High, OutputDrive::Standard);
|
||||||
|
let direction_control = Output::new(dc_pin, Level::High, OutputDrive::Standard);
|
||||||
|
let display_reset = Output::new(display_reset_pin, Level::High, OutputDrive::Standard);
|
||||||
|
|
||||||
|
////////////////////////
|
||||||
|
// Create the display //
|
||||||
|
////////////////////////
|
||||||
|
|
||||||
|
// Use the shared bus to create a SPI Device to use for the display
|
||||||
|
let spi_display = SpiDevice::new(&spi_bus, chip_select);
|
||||||
|
// Create a new SPI display interface
|
||||||
|
let iface = SPIInterface::new(spi_display, direction_control);
|
||||||
|
|
||||||
|
// Some options for the display
|
||||||
|
let display_orientation = ili9341::Orientation::PortraitFlipped;
|
||||||
|
let display_size = ili9341::DisplaySize240x320;
|
||||||
|
|
||||||
|
// Create the display
|
||||||
|
let display = Ili9341::new(
|
||||||
|
iface,
|
||||||
|
display_reset,
|
||||||
|
&mut embassy_time::Delay,
|
||||||
|
display_orientation,
|
||||||
|
display_size,
|
||||||
)
|
)
|
||||||
.into_styled(thin_stroke)
|
.unwrap();
|
||||||
.draw(display)?;
|
|
||||||
|
|
||||||
// Draw a filled square
|
let mut gui = Gui::new(display);
|
||||||
Rectangle::new(Point::new(52, yoffset), Size::new(16, 16))
|
gui.draw().expect("Failed to draw GUI");
|
||||||
.into_styled(fill)
|
|
||||||
.draw(display)?;
|
|
||||||
|
|
||||||
// Draw a circle with a 3px wide stroke.
|
loop {
|
||||||
Circle::new(Point::new(88, yoffset), 17)
|
info!("Waiting for GUI event");
|
||||||
.into_styled(thick_stroke)
|
// Wait to receive a GUI event
|
||||||
.draw(display)?;
|
let gui_action = GUI_CHANNEL.receive().await;
|
||||||
|
// Re-render the GUI
|
||||||
|
info!("Received event, calculating new screen");
|
||||||
|
gui = gui.action(gui_action);
|
||||||
|
|
||||||
// Draw centered text.
|
// Clear the screen
|
||||||
let text = "embedded-graphics";
|
info!("Clearing screen");
|
||||||
Text::with_alignment(
|
gui.fill_black().expect("Failed to clear screen");
|
||||||
text,
|
// Draw the GUI
|
||||||
display.bounding_box().center() + Point::new(0, 15),
|
info!("Drawing screen");
|
||||||
character_style,
|
gui.draw().expect("Failed to draw GUI");
|
||||||
Alignment::Center,
|
}
|
||||||
)
|
}
|
||||||
.draw(display)?;
|
|
||||||
|
|
||||||
Ok(())
|
#[embassy_executor::task(pool_size = 2)]
|
||||||
|
async fn input_controller(
|
||||||
|
control: Sender<'static, ThreadModeRawMutex, gui::GuiAction, 8>,
|
||||||
|
pin: AnyPin,
|
||||||
|
action: gui::GuiAction,
|
||||||
|
) -> ! {
|
||||||
|
let mut button = Input::new(pin, embassy_nrf::gpio::Pull::Up);
|
||||||
|
|
||||||
|
loop {
|
||||||
|
// Wait for the button to be pressed
|
||||||
|
button.wait_for_falling_edge().await;
|
||||||
|
// Send the event
|
||||||
|
if let Err(_e) = control.try_send(action) {
|
||||||
|
error!("Failed to send input action with error");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[embassy_executor::main]
|
#[embassy_executor::main]
|
||||||
@ -137,10 +163,6 @@ async fn main(spawner: Spawner) {
|
|||||||
)
|
)
|
||||||
.expect("Failed to start blink task");
|
.expect("Failed to start blink task");
|
||||||
|
|
||||||
////////////////
|
|
||||||
// Setup pins //
|
|
||||||
////////////////
|
|
||||||
|
|
||||||
let mosi = p.P0_13;
|
let mosi = p.P0_13;
|
||||||
let miso = p.P0_12;
|
let miso = p.P0_12;
|
||||||
let sck = p.P0_30;
|
let sck = p.P0_30;
|
||||||
@ -148,51 +170,38 @@ async fn main(spawner: Spawner) {
|
|||||||
let dc_pin = p.P0_20; // Data / Command pin for the display
|
let dc_pin = p.P0_20; // Data / Command pin for the display
|
||||||
let disp_rst_pin = p.P0_11;
|
let disp_rst_pin = p.P0_11;
|
||||||
|
|
||||||
// Initialize the CS, DC, and display reset pins as outputs
|
// Create the GUI
|
||||||
let chip_select = Output::new(cs_pin, Level::High, OutputDrive::Standard);
|
spawner
|
||||||
let direction_control = Output::new(dc_pin, Level::High, OutputDrive::Standard);
|
.spawn(gui_task(
|
||||||
let display_reset = Output::new(disp_rst_pin, Level::High, OutputDrive::Standard);
|
p.SERIAL3,
|
||||||
|
sck.degrade(),
|
||||||
|
miso.degrade(),
|
||||||
|
mosi.degrade(),
|
||||||
|
disp_rst_pin.degrade(),
|
||||||
|
cs_pin.degrade(),
|
||||||
|
dc_pin.degrade(),
|
||||||
|
))
|
||||||
|
.expect("Failed to spawn GUI task");
|
||||||
|
|
||||||
///////////////
|
// Create the GUI controllers
|
||||||
// Setup SPI //
|
spawner
|
||||||
///////////////
|
.spawn(input_controller(
|
||||||
|
GUI_CHANNEL.sender(),
|
||||||
// Create an SPI config with the SPI frequency at 8MHz
|
p.P0_06.degrade(),
|
||||||
let mut spim_config = spim::Config::default();
|
gui::GuiAction::Down,
|
||||||
spim_config.frequency = spim::Frequency::M8;
|
))
|
||||||
|
.expect("Failed tp spawn DOWN input controller");
|
||||||
// Grab the SPI Master peripheral form the nRF60
|
spawner
|
||||||
let spim = embassy_nrf::spim::Spim::new(p.SERIAL3, Irqs, sck, miso, mosi, spim_config);
|
.spawn(input_controller(
|
||||||
// Use the SPI peripheral to create a shared SPI bus
|
GUI_CHANNEL.sender(),
|
||||||
let spi_bus: Mutex<embassy_sync::blocking_mutex::raw::NoopRawMutex, _> =
|
p.P0_07.degrade(),
|
||||||
Mutex::new(RefCell::new(spim));
|
gui::GuiAction::Select,
|
||||||
|
))
|
||||||
// Use the shared bus to create a SPI Device to use for the display
|
.expect("Failed tp spawn Select input controller");
|
||||||
let spi_display = SpiDevice::new(&spi_bus, chip_select);
|
|
||||||
// Create a new SPI display interface
|
|
||||||
let iface = SPIInterface::new(spi_display, direction_control);
|
|
||||||
|
|
||||||
////////////////////////
|
|
||||||
// Create the display //
|
|
||||||
////////////////////////
|
|
||||||
|
|
||||||
// Some options for the display
|
|
||||||
let display_orientation = ili9341::Orientation::Landscape;
|
|
||||||
let display_size = ili9341::DisplaySize240x320;
|
|
||||||
|
|
||||||
// Create the display
|
|
||||||
let mut display = Ili9341::new(
|
|
||||||
iface,
|
|
||||||
display_reset,
|
|
||||||
&mut embassy_time::Delay,
|
|
||||||
display_orientation,
|
|
||||||
display_size,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// Now loop, re-running the demo every second
|
// Now loop, re-running the demo every second
|
||||||
loop {
|
loop {
|
||||||
display_demo(&mut display).expect("Failed to run display demo");
|
info!("Hello, world!");
|
||||||
Timer::after_secs(1).await;
|
Timer::after_secs(1).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
1
gui
Submodule
1
gui
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit 12afc17f90756988b9011240e6884fa0e3fb48ef
|
||||||
12
simulator/Cargo.toml
Normal file
12
simulator/Cargo.toml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
[package]
|
||||||
|
name = "gui-test"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
embedded-graphics = "0.8.1"
|
||||||
|
embedded-graphics-simulator = "0.7.0"
|
||||||
|
embedded-layout = "0.4.1"
|
||||||
|
heapless = "0.8.0"
|
||||||
|
profont = "0.7.0"
|
||||||
|
gui = { path = "../gui/" }
|
||||||
43
simulator/src/main.rs
Normal file
43
simulator/src/main.rs
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
use embedded_graphics::{geometry::Size, pixelcolor::Rgb565};
|
||||||
|
use embedded_graphics_simulator::{
|
||||||
|
sdl2::Keycode, OutputSettingsBuilder, SimulatorDisplay, SimulatorEvent, Window,
|
||||||
|
};
|
||||||
|
|
||||||
|
use gui::*;
|
||||||
|
|
||||||
|
fn main() -> Result<(), core::convert::Infallible> {
|
||||||
|
let display = SimulatorDisplay::<Rgb565>::new(Size::new(240, 320));
|
||||||
|
|
||||||
|
let output_settings = OutputSettingsBuilder::new().scale(1).build();
|
||||||
|
let mut window = Window::new("Hello World", &output_settings);
|
||||||
|
|
||||||
|
let mut gui = Gui::new(display);
|
||||||
|
|
||||||
|
'running: loop {
|
||||||
|
gui.draw()?;
|
||||||
|
window.update(gui.display_ref());
|
||||||
|
|
||||||
|
for event in window.events() {
|
||||||
|
match event {
|
||||||
|
SimulatorEvent::Quit => break 'running,
|
||||||
|
SimulatorEvent::KeyDown { keycode, .. } => {
|
||||||
|
let action: GuiAction = match keycode {
|
||||||
|
Keycode::Left => GuiAction::Left,
|
||||||
|
Keycode::Right => GuiAction::Right,
|
||||||
|
Keycode::Up => GuiAction::Up,
|
||||||
|
Keycode::Down => GuiAction::Down,
|
||||||
|
Keycode::Return => GuiAction::Select,
|
||||||
|
Keycode::Backspace => GuiAction::Back,
|
||||||
|
_ => continue,
|
||||||
|
};
|
||||||
|
|
||||||
|
gui = gui.action(action);
|
||||||
|
gui.fill_black()?;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user