Skip to content
This repository was archived by the owner on Aug 9, 2022. It is now read-only.

Added SPI peripheral driver #43

Merged
merged 10 commits into from
Sep 11, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ void = { version = "1.0.2", default-features = false }

[dev-dependencies]
panic-halt = "0.2.0"
ili9341 = { version = "0.3.0", features = ["graphics"] }
embedded-graphics = "0.6.2"

[[example]]
name = "alloc"
Expand Down
157 changes: 157 additions & 0 deletions examples/gpio.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
#![no_std]
#![no_main]

use core::{fmt::Write, panic::PanicInfo};

use esp32_hal::{
clock_control::sleep,
dport::Split,
dprintln,
gpio::{Event, Floating, InputPin, Pin, Pull, RTCInputPin},
interrupt::{Interrupt, InterruptLevel},
prelude::*,
serial::{config::Config, Serial},
target,
timer::Timer,
Core,
};

static SERIAL: CriticalSectionSpinLockMutex<
Option<
esp32_hal::serial::Serial<
esp32::UART0,
esp32_hal::gpio::Gpio1<esp32_hal::gpio::Unknown>,
esp32_hal::gpio::Gpio3<esp32_hal::gpio::Unknown>,
>,
>,
> = CriticalSectionSpinLockMutex::new(None);

static GPIO: CriticalSectionSpinLockMutex<
Option<esp32_hal::gpio::Gpio26<esp32_hal::gpio::RTCInput<Floating>>>,
> = CriticalSectionSpinLockMutex::new(None);

#[entry]
fn main() -> ! {
let dp = target::Peripherals::take().unwrap();

let (mut dport, dport_clock_control) = dp.DPORT.split();

let clkcntrl = esp32_hal::clock_control::ClockControl::new(
dp.RTCCNTL,
dp.APB_CTRL,
dport_clock_control,
esp32_hal::clock_control::XTAL_FREQUENCY_AUTO,
)
.unwrap();

let (clkcntrl_config, mut watchdog_rtc) = clkcntrl.freeze().unwrap();
let (_, _, _, mut watchdog0) = Timer::new(dp.TIMG0, clkcntrl_config);
let (_, _, _, mut watchdog1) = Timer::new(dp.TIMG1, clkcntrl_config);

watchdog_rtc.disable();
watchdog0.disable();
watchdog1.disable();

let gpios = dp.GPIO.split();

let mut gpio = gpios.gpio26.into_floating_rtc_input();
gpio.internal_pull_up(true);
gpio.enable_hold(false);
gpio.enable_input(false);
gpio.rtc_enable_input(true);

gpio.listen_with_options(Event::LowLevel, true, false, true, false, false);

// setup serial controller
let mut serial: Serial<_, _, _> = Serial::new(
dp.UART0,
esp32_hal::serial::Pins {
tx: gpios.gpio1,
rx: gpios.gpio3,
cts: None,
rts: None,
},
Config::default().baudrate(115_200.Hz()),
clkcntrl_config,
&mut dport,
)
.unwrap();

writeln!(serial, "\n\nESP32 Started\n\n").unwrap();

(&SERIAL).lock(|val| *val = Some(serial));
(&GPIO).lock(|val| *val = Some(gpio));

interrupt::enable(Interrupt::GPIO_INTR).unwrap();

// Even though the interrupt is called GPIO_NMI is can be routed to any interrupt level.
// Using NMI level (7) is in principle a risk for deadlocks because the
// CriticalSectionSpinLockMutex does not disable the NMI. Therefore using level 5 instead.

// Because the level 5 interrupt clears the interrupt, the regular level 1 handler
// will not be called.
// Comment out the next line to test the level 1 handler
interrupt::enable_with_priority(Core::PRO, Interrupt::GPIO_NMI, InterruptLevel(5)).unwrap();

let mut x = 0;
loop {
x = x + 1;
(&SERIAL, &GPIO).lock(|serial, gpio| {
let serial = serial.as_mut().unwrap();
let gpio = gpio.as_mut().unwrap();
writeln!(
serial,
"Loop: {} {} {} {}",
x,
gpio.is_high().unwrap(),
gpio.is_input_high(),
gpio.rtc_is_input_high()
)
.unwrap();
});

sleep(500.ms());
}
}

fn handle_gpio_interrupt() {
(&GPIO, &SERIAL).lock(|gpio, serial| {
let gpio = gpio.as_mut().unwrap();
let serial = serial.as_mut().unwrap();

if gpio.is_interrupt_set() || gpio.is_non_maskable_interrupt_set() {
writeln!(
serial,
" Interrupt level: {}, pin state: {}",
xtensa_lx6::interrupt::get_level(),
gpio.is_high().unwrap()
)
.unwrap();

if gpio.is_high().unwrap() {
gpio.listen_with_options(Event::LowLevel, true, false, true, false, false);
} else {
gpio.listen_with_options(Event::HighLevel, true, false, true, false, false);
};
// need to change listen before clearing interrupt, otherwise will fire
// immediately again.
gpio.clear_interrupt();
}
});
}

#[interrupt]
fn GPIO_INTR() {
handle_gpio_interrupt();
}

#[interrupt]
fn GPIO_NMI() {
handle_gpio_interrupt();
}

#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
dprintln!("\n\n*** {:?}", info);
loop {}
}
224 changes: 224 additions & 0 deletions examples/spi.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
//! Example of using the SPI interface using the ESP32 WROVER DEVKIT
//!
//! This examples writes to the display. As this pushes many pixels it is quite slow in debug mode,
//! so please run it in release mode to get an impression of the obtainable speed.

#![no_std]
#![no_main]

use core::{fmt::Write, panic::PanicInfo};

use esp32_hal::{
clock_control::{sleep, ClockControl, XTAL_FREQUENCY_AUTO},
dport::Split,
dprintln,
gpio::{InputPin, OutputPin},
prelude::*,
serial::{self, Serial},
spi::{self, SPI},
target,
timer::Timer,
};

use embedded_hal::blocking::spi::WriteIter;

use ili9341;

use embedded_graphics::{
fonts::{Font12x16, Text},
pixelcolor::Rgb565,
prelude::*,
primitives::{Circle, Rectangle},
style::{PrimitiveStyleBuilder, TextStyle},
};

// Interface for ili9341 driver
// ili9341 uses separate command/data pin, this interface set this pin to the appropriate state
struct SPIInterface<
CMD: embedded_hal::digital::v2::OutputPin,
SCLK: OutputPin,
SDO: OutputPin,
SDI: InputPin + OutputPin,
CS: OutputPin,
> {
spi: SPI<esp32::SPI2, SCLK, SDO, SDI, CS>,
cmd: CMD,
}

impl<
CMD: embedded_hal::digital::v2::OutputPin,
SCLK: OutputPin,
SDO: OutputPin,
SDI: InputPin + OutputPin,
CS: OutputPin,
> ili9341::Interface for SPIInterface<CMD, SCLK, SDO, SDI, CS>
{
type Error = esp32_hal::spi::Error;

fn write(&mut self, command: u8, data: &[u8]) -> Result<(), Self::Error> {
self.cmd
.set_low()
.map_err(|_| esp32_hal::spi::Error::PinError)?;
self.spi.write(&[command])?;
self.cmd
.set_high()
.map_err(|_| esp32_hal::spi::Error::PinError)?;
self.spi.write(data)?;
Ok(())
}

fn write_iter(
&mut self,
command: u8,
data: impl IntoIterator<Item = u16>,
) -> Result<(), Self::Error> {
self.cmd
.set_low()
.map_err(|_| esp32_hal::spi::Error::PinError)?;
self.spi.write(&[command])?;
self.cmd
.set_high()
.map_err(|_| esp32_hal::spi::Error::PinError)?;
self.spi.write_iter(data)?;
Ok(())
}
}

#[entry]
fn main() -> ! {
let dp = target::Peripherals::take().expect("Failed to obtain Peripherals");

let (mut dport, dport_clock_control) = dp.DPORT.split();

let clkcntrl = ClockControl::new(
dp.RTCCNTL,
dp.APB_CTRL,
dport_clock_control,
XTAL_FREQUENCY_AUTO,
)
.unwrap();

let (clkcntrl_config, mut watchdog) = clkcntrl.freeze().unwrap();
let (_, _, _, mut watchdog0) = Timer::new(dp.TIMG0, clkcntrl_config);
let (_, _, _, mut watchdog1) = Timer::new(dp.TIMG1, clkcntrl_config);

watchdog.disable();
watchdog0.disable();
watchdog1.disable();

let _lock = clkcntrl_config.lock_cpu_frequency();

let pins = dp.GPIO.split();

let mut serial: Serial<_, _, _> = Serial::new(
dp.UART0,
serial::Pins {
tx: pins.gpio1,
rx: pins.gpio3,
cts: None,
rts: None,
},
serial::config::Config {
baudrate: 115200.Hz(),
..serial::config::Config::default()
},
clkcntrl_config,
&mut dport,
)
.unwrap();

// Official ili9341 spec is 10MHz, but overdrive up to 80MHz actually works.
// 26MHz chosen here: will be 26MHz when using 26MHz crystal, 20MHz when using 40MHz crystal,
// due to integer clock division.
// Faster is no use as the cpu is not keeping up with the embedded_graphics library.
let spi: SPI<_, _, _, _, _> = SPI::<esp32::SPI2, _, _, _, _>::new(
dp.SPI2,
spi::Pins {
sclk: pins.gpio19,
sdo: pins.gpio23,
sdi: Some(pins.gpio25),
cs: Some(pins.gpio22),
},
spi::config::Config {
baudrate: 26.MHz().into(),
bit_order: spi::config::BitOrder::MSBFirst,
data_mode: spi::config::MODE_0,
},
clkcntrl_config,
&mut dport,
)
.unwrap();

let mut gpio_backlight = pins.gpio5.into_push_pull_output();
let mut gpio_reset = pins.gpio18.into_push_pull_output();
let gpio_cmd = pins.gpio21.into_push_pull_output();

gpio_reset.set_low().unwrap();
sleep(100.ms());
gpio_reset.set_high().unwrap();
sleep(100.ms());

gpio_backlight.set_low().unwrap();

let spi_if = SPIInterface { spi, cmd: gpio_cmd };

let mut display =
ili9341::Ili9341::new(spi_if, gpio_reset, &mut esp32_hal::delay::Delay::new()).unwrap();

display
.set_orientation(ili9341::Orientation::Landscape)
.unwrap();

Rectangle::new(Point::new(0, 0), Point::new(320, 240))
.into_styled(
PrimitiveStyleBuilder::new()
.fill_color(Rgb565::WHITE)
.stroke_width(4)
.stroke_color(Rgb565::BLUE)
.build(),
)
.draw(&mut display)
.unwrap();

let rect = Rectangle::new(Point::new(10, 80), Point::new(30, 100)).into_styled(
PrimitiveStyleBuilder::new()
.fill_color(Rgb565::RED)
.stroke_width(1)
.stroke_color(Rgb565::WHITE)
.build(),
);

let circle = Circle::new(Point::new(20, 50), 10).into_styled(
PrimitiveStyleBuilder::new()
.fill_color(Rgb565::GREEN)
.stroke_width(1)
.stroke_color(Rgb565::WHITE)
.build(),
);

Text::new("Hello Rust!", Point::new(20, 16))
.into_styled(TextStyle::new(Font12x16, Rgb565::RED))
.draw(&mut display)
.unwrap();

writeln!(serial, "\n\nESP32 Started\n\n").unwrap();

loop {
for x in (0..280).chain((0..280).rev()) {
rect.translate(Point::new(x, 0)).draw(&mut display).unwrap();
}

for x in (0..280).chain((0..280).rev()) {
circle
.translate(Point::new(x, 0))
.draw(&mut display)
.unwrap();
}
}
}

#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
dprintln!("\n\n*** {:?}", info);
loop {}
}
Loading