mirror of
https://github.com/yuri91/ili9341-rs.git
synced 2024-11-25 08:00:59 +00:00
commit
df4331a8d6
3 changed files with 155 additions and 100 deletions
|
@ -7,6 +7,7 @@ categories = ["embedded", "hardware-support", "no-std"]
|
||||||
keywords = ["embedded-hal-driver", "display", "LCD"]
|
keywords = ["embedded-hal-driver", "display", "LCD"]
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
repository = "https://github.com/yuri91/ili9341-rs"
|
repository = "https://github.com/yuri91/ili9341-rs"
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|
183
src/lib.rs
183
src/lib.rs
|
@ -1,33 +1,51 @@
|
||||||
#![no_std]
|
#![no_std]
|
||||||
|
|
||||||
extern crate embedded_hal as hal;
|
|
||||||
|
|
||||||
#[cfg(feature = "graphics")]
|
#[cfg(feature = "graphics")]
|
||||||
extern crate embedded_graphics;
|
extern crate embedded_graphics;
|
||||||
|
|
||||||
use hal::blocking::delay::DelayMs;
|
use embedded_hal::blocking::delay::DelayMs;
|
||||||
use hal::blocking::spi;
|
use embedded_hal::blocking::spi::{Write, Transfer};
|
||||||
use hal::digital::v2::OutputPin;
|
use embedded_hal::digital::v2::OutputPin;
|
||||||
use hal::spi::{Mode, Phase, Polarity};
|
|
||||||
|
|
||||||
use core::fmt::Debug;
|
use core::fmt::Debug;
|
||||||
use core::iter::IntoIterator;
|
use core::iter::IntoIterator;
|
||||||
|
|
||||||
/// SPI mode
|
pub mod spi;
|
||||||
pub const MODE: Mode = Mode {
|
use spi::SpiInterface;
|
||||||
polarity: Polarity::IdleLow,
|
|
||||||
phase: Phase::CaptureOnFirstTransition,
|
/// Trait representing the interface to the hardware.
|
||||||
};
|
///
|
||||||
|
/// Intended to abstract the various buses (SPI, MPU 8/9/16-bit) from the Controller code.
|
||||||
|
pub trait Interface {
|
||||||
|
type Error;
|
||||||
|
|
||||||
|
/// Sends a command with a sequence of 8-bit arguments
|
||||||
|
///
|
||||||
|
/// Mostly used for sending configuration commands
|
||||||
|
fn write(&mut self, command: u8, data: &[u8]) -> Result<(), Self::Error>;
|
||||||
|
|
||||||
|
/// Sends a command with a sequence of 16-bit data words
|
||||||
|
///
|
||||||
|
/// Mostly used for sending MemoryWrite command and other commands
|
||||||
|
/// with 16-bit arguments
|
||||||
|
fn write_iter(&mut self, command: u8, data: impl IntoIterator<Item = u16>) -> Result<(), Self::Error>;
|
||||||
|
}
|
||||||
|
|
||||||
const WIDTH: usize = 240;
|
const WIDTH: usize = 240;
|
||||||
const HEIGHT: usize = 320;
|
const HEIGHT: usize = 320;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Error<SpiE, PinE> {
|
pub enum Error<IfaceE, PinE> {
|
||||||
Spi(SpiE),
|
Interface(IfaceE),
|
||||||
OutputPin(PinE),
|
OutputPin(PinE),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<IfaceE, PinE> From<IfaceE> for Error<IfaceE, PinE> {
|
||||||
|
fn from(e: IfaceE) -> Self {
|
||||||
|
Error::Interface(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// The default orientation is Portrait
|
/// The default orientation is Portrait
|
||||||
pub enum Orientation {
|
pub enum Orientation {
|
||||||
Portrait,
|
Portrait,
|
||||||
|
@ -52,39 +70,53 @@ pub enum Orientation {
|
||||||
/// - As soon as a pixel is received, an internal counter is incremented,
|
/// - As soon as a pixel is received, an internal counter is incremented,
|
||||||
/// and the next word will fill the next pixel (the adjacent on the right, or
|
/// and the next word will fill the next pixel (the adjacent on the right, or
|
||||||
/// the first of the next row if the row ended)
|
/// the first of the next row if the row ended)
|
||||||
pub struct Ili9341<SPI, CS, DC, RESET> {
|
pub struct Ili9341<IFACE, RESET> {
|
||||||
spi: SPI,
|
interface: IFACE,
|
||||||
cs: CS,
|
|
||||||
dc: DC,
|
|
||||||
reset: RESET,
|
reset: RESET,
|
||||||
width: usize,
|
width: usize,
|
||||||
height: usize,
|
height: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<SpiE, PinE, SPI, CS, DC, RESET> Ili9341<SPI, CS, DC, RESET>
|
impl<SpiE, PinE, SPI, CS, DC, RESET> Ili9341<SpiInterface<SPI, CS, DC>, RESET>
|
||||||
where
|
where
|
||||||
SPI: spi::Transfer<u8, Error = SpiE> + spi::Write<u8, Error = SpiE>,
|
SPI: Transfer<u8, Error = SpiE> + Write<u8, Error = SpiE>,
|
||||||
CS: OutputPin<Error = PinE>,
|
CS: OutputPin<Error = PinE>,
|
||||||
DC: OutputPin<Error = PinE>,
|
DC: OutputPin<Error = PinE>,
|
||||||
RESET: OutputPin<Error = PinE>,
|
RESET: OutputPin<Error = PinE>,
|
||||||
{
|
{
|
||||||
pub fn new<DELAY: DelayMs<u16>>(
|
pub fn new_spi<DELAY: DelayMs<u16>>(
|
||||||
spi: SPI,
|
spi: SPI,
|
||||||
cs: CS,
|
cs: CS,
|
||||||
dc: DC,
|
dc: DC,
|
||||||
reset: RESET,
|
reset: RESET,
|
||||||
delay: &mut DELAY,
|
delay: &mut DELAY,
|
||||||
) -> Result<Self, Error<SpiE, PinE>> {
|
) -> Result<Self, Error<SpiE, PinE>> {
|
||||||
|
let interface = SpiInterface::new(spi, cs, dc);
|
||||||
|
Self::new(interface, reset, delay).map_err(|e| match e {
|
||||||
|
Error::Interface(inner) => inner,
|
||||||
|
Error::OutputPin(inner) => Error::OutputPin(inner),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<IfaceE, PinE, IFACE, RESET> Ili9341<IFACE, RESET>
|
||||||
|
where
|
||||||
|
IFACE: Interface<Error=IfaceE>,
|
||||||
|
RESET: OutputPin<Error = PinE>,
|
||||||
|
{
|
||||||
|
pub fn new<DELAY: DelayMs<u16>>(
|
||||||
|
interface: IFACE,
|
||||||
|
reset: RESET,
|
||||||
|
delay: &mut DELAY,
|
||||||
|
) -> Result<Self, Error<IfaceE, PinE>> {
|
||||||
let mut ili9341 = Ili9341 {
|
let mut ili9341 = Ili9341 {
|
||||||
spi,
|
interface,
|
||||||
cs,
|
|
||||||
dc,
|
|
||||||
reset,
|
reset,
|
||||||
width: WIDTH,
|
width: WIDTH,
|
||||||
height: HEIGHT,
|
height: HEIGHT,
|
||||||
};
|
};
|
||||||
|
|
||||||
ili9341.hard_reset(delay)?;
|
ili9341.hard_reset(delay).map_err(Error::OutputPin)?;
|
||||||
ili9341.command(Command::SoftwareReset, &[])?;
|
ili9341.command(Command::SoftwareReset, &[])?;
|
||||||
delay.delay_ms(200);
|
delay.delay_ms(200);
|
||||||
|
|
||||||
|
@ -128,66 +160,31 @@ where
|
||||||
fn hard_reset<DELAY: DelayMs<u16>>(
|
fn hard_reset<DELAY: DelayMs<u16>>(
|
||||||
&mut self,
|
&mut self,
|
||||||
delay: &mut DELAY,
|
delay: &mut DELAY,
|
||||||
) -> Result<(), Error<SpiE, PinE>> {
|
) -> Result<(), PinE> {
|
||||||
// set high if previously low
|
// set high if previously low
|
||||||
self.reset.set_high().map_err(Error::OutputPin)?;
|
self.reset.set_high()?;
|
||||||
delay.delay_ms(200);
|
delay.delay_ms(200);
|
||||||
// set low for reset
|
// set low for reset
|
||||||
self.reset.set_low().map_err(Error::OutputPin)?;
|
self.reset.set_low()?;
|
||||||
delay.delay_ms(200);
|
delay.delay_ms(200);
|
||||||
// set high for normal operation
|
// set high for normal operation
|
||||||
self.reset.set_high().map_err(Error::OutputPin)?;
|
self.reset.set_high()?;
|
||||||
delay.delay_ms(200);
|
delay.delay_ms(200);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
fn command(&mut self, cmd: Command, args: &[u8]) -> Result<(), Error<SpiE, PinE>> {
|
|
||||||
self.cs.set_low().map_err(Error::OutputPin)?;
|
|
||||||
|
|
||||||
self.dc.set_low().map_err(Error::OutputPin)?;
|
fn command(&mut self, cmd: Command, args: &[u8]) -> Result<(), IFACE::Error> {
|
||||||
self.spi.write(&[cmd as u8]).map_err(Error::Spi)?;
|
self.interface.write(cmd as u8, args)
|
||||||
|
|
||||||
self.dc.set_high().map_err(Error::OutputPin)?;
|
|
||||||
self.spi.write(args).map_err(Error::Spi)?;
|
|
||||||
|
|
||||||
self.cs.set_high().map_err(Error::OutputPin)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_iter<I: IntoIterator<Item = u16>>(
|
fn write_iter<I: IntoIterator<Item = u16>>(
|
||||||
&mut self,
|
&mut self,
|
||||||
data: I,
|
data: I,
|
||||||
) -> Result<(), Error<SpiE, PinE>> {
|
) -> Result<(), IFACE::Error> {
|
||||||
self.cs.set_low().map_err(Error::OutputPin)?;
|
self.interface.write_iter(Command::MemoryWrite as u8, data)
|
||||||
|
|
||||||
self.dc.set_low().map_err(Error::OutputPin)?;
|
|
||||||
self.spi
|
|
||||||
.write(&[Command::MemoryWrite as u8])
|
|
||||||
.map_err(Error::Spi)?;
|
|
||||||
|
|
||||||
self.dc.set_high().map_err(Error::OutputPin)?;
|
|
||||||
for d in data.into_iter() {
|
|
||||||
self.spi
|
|
||||||
.write(&[(d >> 8) as u8, (d & 0xff) as u8])
|
|
||||||
.map_err(Error::Spi)?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.cs.set_high().map_err(Error::OutputPin)?;
|
fn set_window(&mut self, x0: u16, y0: u16, x1: u16, y1: u16) -> Result<(), IFACE::Error> {
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
fn write_raw(&mut self, data: &[u8]) -> Result<(), Error<SpiE, PinE>> {
|
|
||||||
self.cs.set_low().map_err(Error::OutputPin)?;
|
|
||||||
|
|
||||||
self.dc.set_low().map_err(Error::OutputPin)?;
|
|
||||||
self.spi
|
|
||||||
.write(&[Command::MemoryWrite as u8])
|
|
||||||
.map_err(Error::Spi)?;
|
|
||||||
|
|
||||||
self.dc.set_high().map_err(Error::OutputPin)?;
|
|
||||||
self.spi.write(data).map_err(Error::Spi)?;
|
|
||||||
|
|
||||||
self.cs.set_high().map_err(Error::OutputPin)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
fn set_window(&mut self, x0: u16, y0: u16, x1: u16, y1: u16) -> Result<(), Error<SpiE, PinE>> {
|
|
||||||
self.command(
|
self.command(
|
||||||
Command::ColumnAddressSet,
|
Command::ColumnAddressSet,
|
||||||
&[
|
&[
|
||||||
|
@ -208,6 +205,7 @@ where
|
||||||
)?;
|
)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Draw a rectangle on the screen, represented by top-left corner (x0, y0)
|
/// Draw a rectangle on the screen, represented by top-left corner (x0, y0)
|
||||||
/// and bottom-right corner (x1, y1).
|
/// and bottom-right corner (x1, y1).
|
||||||
///
|
///
|
||||||
|
@ -224,33 +222,34 @@ where
|
||||||
x1: u16,
|
x1: u16,
|
||||||
y1: u16,
|
y1: u16,
|
||||||
data: I,
|
data: I,
|
||||||
) -> Result<(), Error<SpiE, PinE>> {
|
) -> Result<(), IFACE::Error> {
|
||||||
self.set_window(x0, y0, x1, y1)?;
|
self.set_window(x0, y0, x1, y1)?;
|
||||||
self.write_iter(data)
|
self.write_iter(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Draw a rectangle on the screen, represented by top-left corner (x0, y0)
|
/// Draw a rectangle on the screen, represented by top-left corner (x0, y0)
|
||||||
/// and bottom-right corner (x1, y1).
|
/// and bottom-right corner (x1, y1).
|
||||||
///
|
///
|
||||||
/// The border is included.
|
/// The border is included.
|
||||||
///
|
///
|
||||||
/// This method accepts a raw buffer of bytes that will be copied to the screen
|
/// This method accepts a raw buffer of words that will be copied to the screen
|
||||||
/// video memory.
|
/// video memory.
|
||||||
///
|
///
|
||||||
/// The expected format is rgb565, and the two bytes for a pixel
|
/// The expected format is rgb565.
|
||||||
/// are in big endian order.
|
|
||||||
pub fn draw_raw(
|
pub fn draw_raw(
|
||||||
&mut self,
|
&mut self,
|
||||||
x0: u16,
|
x0: u16,
|
||||||
y0: u16,
|
y0: u16,
|
||||||
x1: u16,
|
x1: u16,
|
||||||
y1: u16,
|
y1: u16,
|
||||||
data: &[u8],
|
data: &[u16],
|
||||||
) -> Result<(), Error<SpiE, PinE>> {
|
) -> Result<(), IFACE::Error> {
|
||||||
self.set_window(x0, y0, x1, y1)?;
|
self.set_window(x0, y0, x1, y1)?;
|
||||||
self.write_raw(data)
|
self.write_iter(data.iter().cloned())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Change the orientation of the screen
|
/// Change the orientation of the screen
|
||||||
pub fn set_orientation(&mut self, mode: Orientation) -> Result<(), Error<SpiE, PinE>> {
|
pub fn set_orientation(&mut self, mode: Orientation) -> Result<(), IFACE::Error> {
|
||||||
match mode {
|
match mode {
|
||||||
Orientation::Portrait => {
|
Orientation::Portrait => {
|
||||||
self.width = WIDTH;
|
self.width = WIDTH;
|
||||||
|
@ -274,10 +273,12 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the current screen width. It can change based on the current orientation
|
/// Get the current screen width. It can change based on the current orientation
|
||||||
pub fn width(&self) -> usize {
|
pub fn width(&self) -> usize {
|
||||||
self.width
|
self.width
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the current screen heighth. It can change based on the current orientation
|
/// Get the current screen heighth. It can change based on the current orientation
|
||||||
pub fn height(&self) -> usize {
|
pub fn height(&self) -> usize {
|
||||||
self.height
|
self.height
|
||||||
|
@ -290,22 +291,20 @@ use embedded_graphics::drawable;
|
||||||
use embedded_graphics::{drawable::Pixel, pixelcolor::Rgb565, Drawing};
|
use embedded_graphics::{drawable::Pixel, pixelcolor::Rgb565, Drawing};
|
||||||
|
|
||||||
#[cfg(feature = "graphics")]
|
#[cfg(feature = "graphics")]
|
||||||
impl<SpiE, PinE, SPI, CS, DC, RESET> Drawing<Rgb565> for Ili9341<SPI, CS, DC, RESET>
|
impl<IfaceE, PinE, IFACE, RESET> Drawing<Rgb565> for Ili9341<IFACE, RESET>
|
||||||
where
|
where
|
||||||
SPI: spi::Transfer<u8, Error = SpiE> + spi::Write<u8, Error = SpiE>,
|
IFACE: Interface<Error = IfaceE>,
|
||||||
CS: OutputPin<Error = PinE>,
|
|
||||||
DC: OutputPin<Error = PinE>,
|
|
||||||
RESET: OutputPin<Error = PinE>,
|
RESET: OutputPin<Error = PinE>,
|
||||||
SpiE: Debug,
|
IfaceE: Debug,
|
||||||
PinE: Debug,
|
PinE: Debug,
|
||||||
{
|
{
|
||||||
fn draw<T>(&mut self, item_pixels: T)
|
fn draw<T>(&mut self, item_pixels: T)
|
||||||
where
|
where
|
||||||
T: IntoIterator<Item = drawable::Pixel<Rgb565>>,
|
T: IntoIterator<Item = drawable::Pixel<Rgb565>>,
|
||||||
{
|
{
|
||||||
const BUF_SIZE: usize = 64;
|
const BUF_SIZE: usize = 32;
|
||||||
|
|
||||||
let mut row: [u8; BUF_SIZE] = [0; BUF_SIZE];
|
let mut row: [u16; BUF_SIZE] = [0; BUF_SIZE];
|
||||||
let mut i = 0;
|
let mut i = 0;
|
||||||
let mut lasty = 0;
|
let mut lasty = 0;
|
||||||
let mut startx = 0;
|
let mut startx = 0;
|
||||||
|
@ -327,14 +326,8 @@ where
|
||||||
startx = pos.x;
|
startx = pos.x;
|
||||||
}
|
}
|
||||||
// Add pixel color to buffer
|
// Add pixel color to buffer
|
||||||
for b in embedded_graphics::pixelcolor::raw::RawU16::from(color)
|
row[i] = embedded_graphics::pixelcolor::raw::RawU16::from(color).into_inner();
|
||||||
.into_inner()
|
|
||||||
.to_be_bytes()
|
|
||||||
.iter()
|
|
||||||
{
|
|
||||||
row[i] = *b;
|
|
||||||
i += 1;
|
i += 1;
|
||||||
}
|
|
||||||
lasty = pos.y;
|
lasty = pos.y;
|
||||||
endx = pos.x;
|
endx = pos.x;
|
||||||
} else {
|
} else {
|
||||||
|
@ -351,14 +344,8 @@ where
|
||||||
// Start new line of contiguous pixels
|
// Start new line of contiguous pixels
|
||||||
i = 0;
|
i = 0;
|
||||||
startx = pos.x;
|
startx = pos.x;
|
||||||
for b in embedded_graphics::pixelcolor::raw::RawU16::from(color)
|
row[i] = embedded_graphics::pixelcolor::raw::RawU16::from(color).into_inner();
|
||||||
.into_inner()
|
|
||||||
.to_be_bytes()
|
|
||||||
.iter()
|
|
||||||
{
|
|
||||||
row[i] = *b;
|
|
||||||
i += 1;
|
i += 1;
|
||||||
}
|
|
||||||
lasty = pos.y;
|
lasty = pos.y;
|
||||||
endx = pos.x;
|
endx = pos.x;
|
||||||
}
|
}
|
||||||
|
|
67
src/spi.rs
Normal file
67
src/spi.rs
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
use embedded_hal::blocking::spi;
|
||||||
|
use embedded_hal::spi::{Mode, Phase, Polarity};
|
||||||
|
use embedded_hal::digital::v2::OutputPin;
|
||||||
|
use crate::{Interface, Error};
|
||||||
|
|
||||||
|
/// SPI mode
|
||||||
|
pub const MODE: Mode = Mode {
|
||||||
|
polarity: Polarity::IdleLow,
|
||||||
|
phase: Phase::CaptureOnFirstTransition,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// `Interface` implementation for SPI interfaces
|
||||||
|
pub struct SpiInterface<SPI, CS, DC> {
|
||||||
|
spi: SPI,
|
||||||
|
cs: CS,
|
||||||
|
dc: DC,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SPI, CS, DC, SpiE, PinE> SpiInterface<SPI, CS, DC>
|
||||||
|
where SPI: spi::Transfer<u8, Error = SpiE> + spi::Write<u8, Error = SpiE>,
|
||||||
|
CS: OutputPin<Error = PinE>,
|
||||||
|
DC: OutputPin<Error = PinE>,
|
||||||
|
{
|
||||||
|
pub fn new(spi: SPI, cs: CS, dc: DC) -> Self {
|
||||||
|
Self {
|
||||||
|
spi,
|
||||||
|
cs,
|
||||||
|
dc,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SPI, CS, DC, SpiE, PinE> Interface for SpiInterface<SPI, CS, DC>
|
||||||
|
where SPI: spi::Transfer<u8, Error = SpiE> + spi::Write<u8, Error = SpiE>,
|
||||||
|
CS: OutputPin<Error = PinE>,
|
||||||
|
DC: OutputPin<Error = PinE>,
|
||||||
|
{
|
||||||
|
type Error = Error<SpiE, PinE>;
|
||||||
|
|
||||||
|
fn write(&mut self, command: u8, data: &[u8]) -> Result<(), Self::Error> {
|
||||||
|
self.cs.set_low().map_err(Error::OutputPin)?;
|
||||||
|
|
||||||
|
self.dc.set_low().map_err(Error::OutputPin)?;
|
||||||
|
self.spi.write(&[command]).map_err(Error::Interface)?;
|
||||||
|
|
||||||
|
self.dc.set_high().map_err(Error::OutputPin)?;
|
||||||
|
self.spi.write(data).map_err(Error::Interface)?;
|
||||||
|
|
||||||
|
self.cs.set_high().map_err(Error::OutputPin)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_iter(&mut self, command: u8, data: impl IntoIterator<Item=u16>) -> Result<(), Self::Error> {
|
||||||
|
self.cs.set_low().map_err(Error::OutputPin)?;
|
||||||
|
|
||||||
|
self.dc.set_low().map_err(Error::OutputPin)?;
|
||||||
|
self.spi.write(&[command]).map_err(Error::Interface)?;
|
||||||
|
|
||||||
|
self.dc.set_high().map_err(Error::OutputPin)?;
|
||||||
|
for w in data.into_iter() {
|
||||||
|
self.spi.write(&w.to_be_bytes()).map_err(Error::Interface)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.cs.set_high().map_err(Error::OutputPin)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue