Compare commits

...

60 commits

Author SHA1 Message Date
Valentin Trophime 7fa69abd72 avoid some conversion slice to iterator where possible 2024-03-06 20:03:52 +01:00
Valentin Trophime 693f0bdd8f fix semantic versionning 2024-02-13 12:24:24 +01:00
Valentin Trophime 7318f971cf update dependencies 2024-02-13 12:24:24 +01:00
Yuri Iozzelli ac73c3ba40 Merge branch 'chemicstry-master' 2022-10-07 15:17:58 +02:00
chemicstry aa0003d0a4 Bump embedded-hal to 1.0.0-alpha.9 2022-10-07 15:01:55 +03:00
Yuri Iozzelli 32ca78087c
Merge pull request #38 from VersBinarii/readme
Add badges to README
2022-10-06 10:00:10 +02:00
VersBinarii 0cdd4e7c2e Add badges to README 2022-08-26 21:09:24 +02:00
Yuri Iozzelli df5ea50076
Merge pull request #37 from ashutosh-arm/upgrade_embedded_hal_to_alpha
Bumping up embedded-hal version to 1.0.0-alpha.8
2022-06-24 10:12:52 +02:00
Ashutosh Parkhi 00a50a13cc Bumping up embedded-hal version to 1.0.0-alpha.8 2022-06-23 17:20:18 +01:00
Yuri Iozzelli 99f6e63918
Merge pull request #35 from VersBinarii/commands
Add idle mode, brightness and frame rate control
2022-04-24 13:26:02 +02:00
VersBinarii a5e7f6498a Add idle mode, brightness and framerate control 2022-04-23 21:25:22 +02:00
Yuri Iozzelli bfb77647d5
Merge pull request #34 from VersBinarii/more_commands
Add Invert, display and sleep control commands
2022-04-21 16:17:52 +02:00
VersBinarii 1f47f5797e Add Invert, display and sleep control commands 2022-04-21 13:43:06 +02:00
Yuri Iozzelli d896467aec
Merge pull request #33 from VersBinarii/graphics
Add clear_screen function
2022-04-19 07:51:18 +02:00
VersBinarii 6e8c814156 Add clear_screen function 2022-04-18 18:46:30 +02:00
Yuri Iozzelli d933649c19
Merge pull request #32 from VersBinarii/examples
Add RTIC based example for stm32f411 BlackPill
2022-04-18 10:48:01 +02:00
VersBinarii d4e883ce8d Add RTIC based example for stm32f411 BlackPill 2022-04-18 10:30:28 +02:00
Yuri Iozzelli 6e1fab6da7
Merge pull request #31 from VersBinarii/bump_eh_alpha
Bump embedded-hal to alpha7
2022-04-17 10:04:49 +02:00
VersBinarii b0f3b14664 Bump embedded-hal to alpha7 2022-04-16 22:29:50 +02:00
Yuri Iozzelli 8aeefc4abc
Merge pull request #30 from plaes/min-doc-example
Add basic usage example to the index screen
2021-11-19 11:23:14 +01:00
Priit Laes 11661b20b6 Add basic usage example to the index screen 2021-11-18 22:32:37 +02:00
Yuri Iozzelli c3d0d08a3c
Merge pull request #29 from ivmarkov/master
Support for boards that need a non-standard initialization command
2021-08-07 10:31:07 +02:00
imarkov 8a4aee95c2 Support for boards that need a non-standard initialization command 2021-07-14 21:34:55 +03:00
Yuri Iozzelli 19efa445bf Bump version to 0.5.0 2021-06-10 18:40:31 +02:00
Yuri Iozzelli 634558b18e
Merge pull request #28 from yuri91/embedded-graphics-0.7
Embedded graphics 0.7
2021-06-10 18:39:42 +02:00
Yuri Iozzelli e33b10c885 Update embedded-hal version 2021-06-10 18:36:21 +02:00
Yuri Iozzelli ddffec49c6 Remove support for embedded-graphics 0.6 2021-06-10 18:35:09 +02:00
Yuri Iozzelli 7c2e3e27fb
Merge pull request #26 from GrantM11235/e-g-core
Add support for embedded-graphics-core 0.3
2021-05-13 18:14:35 +02:00
Grant Miller 115fec7703 Add support for embedded-graphics-core 0.3 2021-05-12 17:01:59 -05:00
Grant Miller 47d4a07469 Move mod graphics to top and delete extern crate 2021-05-12 15:37:04 -05:00
Grant Miller c93637abfd Rename draw methods
- Rename `draw_iter` to `draw_raw_iter` to avoid conflict with
`embedded_graphics_core::DrawTarget::draw_iter`

- Rename `draw_raw` to `draw_raw_slice` for consistency
2021-05-12 15:33:38 -05:00
Yuri Iozzelli 510d0c9a03
Merge pull request #25 from GrantM11235/display-error
Use `display_interface::DisplayError`
2021-05-11 20:24:46 +02:00
Grant Miller 535feb236c Loosen trait bounds 2021-05-11 11:53:39 -05:00
Grant Miller be44c24eea Use Result alias 2021-05-11 11:53:39 -05:00
Grant Miller 954d111959 Use display_interface::DisplayError 2021-05-11 11:50:51 -05:00
Yuri Iozzelli d9f35775d6
Merge pull request #24 from GrantM11235/remove-old-interface-trait
Remove old `Interface` trait
2021-05-11 09:17:57 +02:00
Yuri Iozzelli a68c0530ec
Merge pull request #23 from GrantM11235/spi-mode
Move `::spi::MODE` to `::SPI_MODE`
2021-05-11 09:17:11 +02:00
Grant Miller ea9cb59bff Remove old Interface trait 2021-05-10 16:02:56 -05:00
Grant Miller 8884885ca0 Move ::spi::MODE to ::SPI_MODE
We don't need a whole module for just one const
2021-05-10 15:57:26 -05:00
Yuri Iozzelli 2b7ee112fb
Merge pull request #22 from almusil/fix_scrolling
Fix underflow in scrolling
2021-01-21 16:51:51 +01:00
Ales Musil e350936d82 Fix underflow in scrolling
When window was smaller than whole display
scrolling by small step could cause underflow
in the offset computation.
2021-01-21 13:20:30 +01:00
Yuri Iozzelli 509060bb5d
Merge pull request #21 from almusil/do_not_hardcode_size
Do not hardcode display size
2020-11-07 12:46:48 +01:00
Ales Musil b5077900be Do not hardcode display size
ili driver family is pretty generic with support
of multiple display sizes. By having width and size
as parameter this driver can support broader range
of ili displays.
2020-10-30 08:28:47 +01:00
Yuri Iozzelli 64963398c7 Bump version to 0.4.1 2020-10-25 09:51:07 +01:00
Yuri Iozzelli 6adc0191d1
Update readme with scrolling feature 2020-10-21 18:41:37 +02:00
Yuri Iozzelli 81377dfddf
Merge pull request #20 from twitchyliquid64/master
Implement hardware vertical scroll
2020-10-21 18:40:45 +02:00
Tom afbfd98f9d Implement hardware vertical scroll 2020-10-17 00:36:58 -07:00
Yuri Iozzelli e45feea807 Bump version to 0.4.0 2020-09-01 22:19:08 +02:00
Yuri Iozzelli d4afd398f5 Update dependencies 2020-09-01 22:18:05 +02:00
Yuri Iozzelli eff270ab2d Merge branch 'GrantM11235-init-cleanup' 2020-09-01 21:43:05 +02:00
Grant Miller 61531412cf Add comment to pixel format set 2020-08-28 15:35:08 -05:00
Grant Miller 427127fe2d Use set_orientation in initialization 2020-08-28 15:35:08 -05:00
Grant Miller 780a5f0c25 Remove gamma set command, 0x01 is the default 2020-08-28 15:35:08 -05:00
Grant Miller bc1282b0ca Remove extended commands 2020-08-28 15:35:08 -05:00
Grant Miller 2342655799 Simplify reset and reduce delays 2020-08-28 15:35:08 -05:00
Yuri Iozzelli 332aa05bc4
Merge pull request #16 from therealprof/display-interface
Rough first conversion to display-interface
2020-06-06 09:55:03 +02:00
Yuri Iozzelli 0b1ba5c7de
Merge pull request #17 from KarlK90/clear
Implement optimized DrawTarget::clear
2020-06-06 09:54:06 +02:00
Stefan Kerkmann ed2b2eeed4 Improve readability 2020-06-05 20:12:33 +02:00
Stefan Kerkmann 8c45de7e90 Implement optimized DrawTarget::clear 2020-06-05 18:07:45 +02:00
Daniel Egger dc9a389b79 Rough first conversion to display-interface
Tested with SPI display

Signed-off-by: Daniel Egger <daniel@eggers-club.de>
2020-06-01 14:30:03 +02:00
8 changed files with 568 additions and 453 deletions

View file

@ -1,6 +1,8 @@
# Copyright 2022 Arm Limited and/or its affiliates <open-source-office@arm.com>
[package]
name = "ili9341"
version = "0.3.0"
version = "0.6.0"
description = "A platform agnostic driver to interface with the ILI9341 (ald ILI9340C) TFT LCD display"
authors = ["Yuri Iozzelli <y.iozzelli@gmail.com>"]
categories = ["embedded", "hardware-support", "no-std"]
@ -11,12 +13,27 @@ edition = "2018"
[dependencies]
embedded-hal = "0.2.3"
display-interface = "0.5"
embedded-hal = "1.0.0"
[dependencies.embedded-graphics]
[dependencies.embedded-graphics-core]
optional = true
version = "0.6.1"
version = "0.4"
[dev-dependencies]
cortex-m-rtic = "1.0.0"
cortex-m = "0.7.3"
cortex-m-rt = "0.7.0"
defmt-rtt = "0.3.0"
panic-semihosting = "0.6"
[dev-dependencies.stm32f4xx-hal]
version = "0.12.0"
features = ["stm32f411"]
[features]
default = ["graphics"]
graphics = ["embedded-graphics"]
graphics = ["embedded-graphics-core"]
[[example]]
name = "rtic"

View file

@ -1,5 +1,9 @@
# `ili9341`
[![Crates.io](https://img.shields.io/crates/d/ili9341.svg)](https://crates.io/crates/ili9341)
[![Crates.io](https://img.shields.io/crates/v/ili9341.svg)](https://crates.io/crates/ili9341)
[![Released API docs](https://docs.rs/ili9341/badge.svg)](https://docs.rs/ili9341)
> A platform agnostic driver to interface with the ILI9341 (and ILI9340C) TFT
> LCD display
@ -7,12 +11,12 @@
- Putting pixels on the screen
- Change the screen orientation
- Hardware scrolling
- Compatible with [embedded-graphics](https://docs.rs/embedded-graphics)
## TODO
- [ ] Expose more configuration options
- [ ] Scrolling
- [ ] Read video memory
- [ ] DMA API
- ???

109
examples/rtic.rs Normal file
View file

@ -0,0 +1,109 @@
//! cortex-m-rtic example
//! Tested on BlackPill dev board with stm32f411ceu microcontroller
//! The LCD RESET pin was hard puled to Vcc therefore
//! DummyOutputPin was used as the reset pin
#![no_main]
#![no_std]
#[rtic::app(device = stm32f4xx_hal::pac)]
mod app {
use display_interface_spi::SPIInterface;
use embedded_graphics::{
mono_font::{ascii::FONT_6X10, MonoTextStyle},
pixelcolor::Rgb565,
prelude::*,
text::{Alignment, Text},
};
use embedded_hal::digital::{blocking::OutputPin, ErrorType, PinState};
use ili9341::{DisplaySize240x320, Ili9341, Orientation};
use stm32f4xx_hal::{
prelude::*,
spi::{Mode, NoMiso, Phase, Polarity},
timer::Channel,
};
#[derive(Default)]
pub struct DummyOutputPin;
impl ErrorType for DummyOutputPin {
type Error = ();
}
impl OutputPin for DummyOutputPin {
fn set_low(&mut self) -> Result<(), Self::Error> {
Ok(())
}
fn set_high(&mut self) -> Result<(), Self::Error> {
Ok(())
}
fn set_state(&mut self, _state: PinState) -> Result<(), Self::Error> {
Ok(())
}
}
#[shared]
struct Shared {}
#[local]
struct Local {}
#[init]
fn init(ctx: init::Context) -> (Shared, Local, init::Monotonics) {
let dp = ctx.device;
let rcc = dp.RCC.constrain();
let clocks = rcc.cfgr.use_hse(25.MHz()).sysclk(100.MHz()).freeze();
let gpioa = dp.GPIOA.split();
let gpiob = dp.GPIOB.split();
/*
* The ILI9341 driver
*/
let lcd_clk = gpiob.pb0.into_alternate();
let lcd_miso = NoMiso {};
let lcd_mosi = gpioa.pa10.into_alternate().internal_pull_up(true);
let lcd_dc = gpiob.pb1.into_push_pull_output();
let lcd_cs = gpiob.pb2.into_push_pull_output();
let mode = Mode {
polarity: Polarity::IdleLow,
phase: Phase::CaptureOnFirstTransition,
};
let lcd_spi = dp
.SPI5
.spi((lcd_clk, lcd_miso, lcd_mosi), mode, 2.MHz(), &clocks);
let spi_iface = SPIInterface::new(lcd_spi, lcd_dc, lcd_cs);
let dummy_reset = DummyOutputPin::default();
let mut delay = dp.TIM1.delay_us(&clocks);
let mut lcd = Ili9341::new(
spi_iface,
dummy_reset,
&mut delay,
Orientation::PortraitFlipped,
DisplaySize240x320,
)
.unwrap();
// Create a new character style
let style = MonoTextStyle::new(&FONT_6X10, Rgb565::RED);
// Create a text at position (20, 30) and draw it using the previously defined style
Text::with_alignment(
"First line\nSecond line",
Point::new(20, 30),
style,
Alignment::Center,
)
.draw(&mut lcd)
.unwrap();
(Shared {}, Local {}, init::Monotonics())
}
#[idle(local = [])]
fn idle(cx: idle::Context) -> ! {
loop {
cortex_m::asm::nop();
}
}
}

View file

@ -1,122 +0,0 @@
use crate::{Error, Interface};
use embedded_hal::digital::v2::OutputPin;
/// `Interface` implementation for GPIO interfaces
pub struct Gpio8Interface<'a, DATA, CSX, WRX, RDX, DCX> {
data_pins: &'a mut [DATA; 8],
csx: CSX,
wrx: WRX,
rdx: RDX,
dcx: DCX,
}
impl<'a, CSX, WRX, RDX, DCX, PinE>
Gpio8Interface<'_, &'a mut dyn OutputPin<Error = PinE>, CSX, WRX, RDX, DCX>
where
CSX: OutputPin<Error = PinE>,
WRX: OutputPin<Error = PinE>,
RDX: OutputPin<Error = PinE>,
DCX: OutputPin<Error = PinE>,
{
/// Create a new Gpio8Interface
///
/// Example useage:
///
/// let csx = gpioc.pc2.into_push_pull_output();
/// let wrx = gpiod.pd13.into_push_pull_output();
/// let rdx = gpiod.pd12.into_push_pull_output();
/// let dcx = gpiof.pf7.into_push_pull_output();
///
/// let mut data_pins: [&mut dyn OutputPin<Error = _>; 8] = [
/// &mut gpiod.pd6.into_push_pull_output(),
/// &mut gpiog.pg11.into_push_pull_output(),
/// ...
/// ];
///
/// let if_gpio = ili9341::gpio::Gpio8Interface::new(&mut data_pins, csx, wrx, rdx, dcx);
pub fn new(
data_pins: &'a mut [&'a mut dyn OutputPin<Error = PinE>; 8],
csx: CSX,
wrx: WRX,
rdx: RDX,
dcx: DCX,
) -> Self {
Self {
data_pins,
csx,
wrx,
rdx,
dcx,
}
}
/// Sets the gpio data pins used in the parallel interface
fn set_data_bus(&mut self, data: u8) -> Result<(), Error<PinE, PinE>> {
for (i, d) in self.data_pins.iter_mut().enumerate() {
if ((data >> i) & 0b1) == 0b1 {
d.set_high().map_err(Error::OutputPin)?;
} else {
d.set_low().map_err(Error::OutputPin)?;
}
}
Ok(())
}
}
impl<'a, CSX, WRX, RDX, DCX, PinE> Interface
for Gpio8Interface<'_, &mut dyn OutputPin<Error = PinE>, CSX, WRX, RDX, DCX>
where
CSX: OutputPin<Error = PinE>,
WRX: OutputPin<Error = PinE>,
RDX: OutputPin<Error = PinE>,
DCX: OutputPin<Error = PinE>,
{
type Error = Error<PinE, PinE>;
fn write(&mut self, command: u8, data: &[u8]) -> Result<(), Self::Error> {
self.csx.set_low().map_err(Error::OutputPin)?;
self.rdx.set_high().map_err(Error::OutputPin)?;
self.dcx.set_low().map_err(Error::OutputPin)?;
self.wrx.set_low().map_err(Error::OutputPin)?;
self.set_data_bus(command)?;
self.wrx.set_high().map_err(Error::OutputPin)?;
self.dcx.set_high().map_err(Error::OutputPin)?;
for val in data.iter() {
self.wrx.set_low().map_err(Error::OutputPin)?;
self.set_data_bus(*val)?;
self.wrx.set_high().map_err(Error::OutputPin)?;
}
self.csx.set_high().map_err(Error::OutputPin)?;
Ok(())
}
fn write_iter(
&mut self,
command: u8,
data: impl IntoIterator<Item = u16>,
) -> Result<(), Self::Error> {
self.csx.set_low().map_err(Error::OutputPin)?;
self.rdx.set_high().map_err(Error::OutputPin)?;
self.dcx.set_low().map_err(Error::OutputPin)?;
self.wrx.set_low().map_err(Error::OutputPin)?;
self.set_data_bus(command)?;
self.wrx.set_high().map_err(Error::OutputPin)?;
self.dcx.set_high().map_err(Error::OutputPin)?;
for val in data.into_iter() {
for b in &val.to_be_bytes() {
self.wrx.set_low().map_err(Error::OutputPin)?;
self.set_data_bus(*b)?;
self.wrx.set_high().map_err(Error::OutputPin)?;
}
}
self.csx.set_high().map_err(Error::OutputPin)?;
Ok(())
}
}

View file

@ -1,81 +0,0 @@
use crate::{Ili9341, Interface, OutputPin};
use core::fmt::Debug;
use embedded_graphics::{
drawable::Pixel,
geometry::{Point, Size},
pixelcolor::{
raw::{RawData, RawU16},
Rgb565,
},
primitives::Rectangle,
style::{PrimitiveStyle, Styled},
DrawTarget,
};
impl<IfaceE, PinE, IFACE, RESET> DrawTarget<Rgb565> for Ili9341<IFACE, RESET>
where
IFACE: Interface<Error = IfaceE>,
RESET: OutputPin<Error = PinE>,
IfaceE: Debug,
PinE: Debug,
{
type Error = IFACE::Error;
fn size(&self) -> Size {
Size::new(self.width as u32, self.height as u32)
}
fn draw_pixel(&mut self, pixel: Pixel<Rgb565>) -> Result<(), Self::Error> {
let Pixel(pos, color) = pixel;
if pos.x < 0 || pos.y < 0 || pos.x >= self.width as i32 || pos.y >= self.height as i32 {
return Ok(());
}
self.draw_raw(
pos.x as u16,
pos.y as u16,
pos.x as u16,
pos.y as u16,
&[RawU16::from(color).into_inner()],
)
}
fn draw_rectangle(
&mut self,
item: &Styled<Rectangle, PrimitiveStyle<Rgb565>>,
) -> Result<(), Self::Error> {
let Point { x: x0, y: y0 } = item.primitive.top_left;
let Point { x: x1, y: y1 } = item.primitive.bottom_right;
let w = self.width as i32;
let h = self.height as i32;
if x0 >= w || y0 >= h {
return Ok(());
}
fn clamp(v: i32, max: i32) -> u16 {
if v < 0 {
0
} else if v > max {
max as u16
} else {
v as u16
}
}
let x0 = clamp(x0, w - 1);
let y0 = clamp(y0, h - 1);
let x1 = clamp(x1, w - 1);
let y1 = clamp(y1, h - 1);
self.draw_iter(
x0,
y0,
x1,
y1,
item.into_iter()
.filter(|p| {
let Point { x, y } = p.0;
x >= 0 && y >= 0 && x < w && y < h
})
.map(|p| RawU16::from(p.1).into_inner()),
)
}
}

82
src/graphics_core.rs Normal file
View file

@ -0,0 +1,82 @@
use crate::Ili9341;
use embedded_graphics_core::{
pixelcolor::{raw::RawU16, Rgb565},
prelude::*,
primitives::Rectangle,
};
impl<IFACE, RESET> OriginDimensions for Ili9341<IFACE, RESET> {
fn size(&self) -> Size {
Size::new(self.width() as u32, self.height() as u32)
}
}
impl<IFACE, RESET> DrawTarget for Ili9341<IFACE, RESET>
where
IFACE: display_interface::WriteOnlyDataCommand,
{
type Error = display_interface::DisplayError;
type Color = Rgb565;
fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
where
I: IntoIterator<Item = Pixel<Self::Color>>,
{
for Pixel(point, color) in pixels {
if self.bounding_box().contains(point) {
let x = point.x as u16;
let y = point.y as u16;
let color = RawU16::from(color).into_inner();
self.draw_raw_slice(x, y, x, y, &[color])?;
}
}
Ok(())
}
fn fill_contiguous<I>(&mut self, area: &Rectangle, colors: I) -> Result<(), Self::Error>
where
I: IntoIterator<Item = Self::Color>,
{
let drawable_area = area.intersection(&self.bounding_box());
if let Some(drawable_bottom_right) = drawable_area.bottom_right() {
let x0 = drawable_area.top_left.x as u16;
let y0 = drawable_area.top_left.y as u16;
let x1 = drawable_bottom_right.x as u16;
let y1 = drawable_bottom_right.y as u16;
if area == &drawable_area {
// All pixels are on screen
self.draw_raw_iter(
x0,
y0,
x1,
y1,
area.points()
.zip(colors)
.map(|(_, color)| RawU16::from(color).into_inner()),
)
} else {
// Some pixels are on screen
self.draw_raw_iter(
x0,
y0,
x1,
y1,
area.points()
.zip(colors)
.filter(|(point, _)| drawable_area.contains(*point))
.map(|(_, color)| RawU16::from(color).into_inner()),
)
}
} else {
// No pixels are on screen
Ok(())
}
}
fn clear(&mut self, color: Self::Color) -> Result<(), Self::Error> {
self.clear_screen(RawU16::from(color).into_inner())
}
}

View file

@ -1,55 +1,82 @@
#![no_std]
//! ILI9341 Display Driver
//!
//! ### Usage
//!
//! To control the display you need to set up:
//!
//! * Interface for communicating with display ([display-interface-spi crate] for SPI)
//! * Configuration (reset pin, delay, orientation and size) for display
//!
//! ```ignore
//! let iface = SPIInterface::new(spi, dc, cs);
//!
//! let mut display = Ili9341::new(
//! iface,
//! reset_gpio,
//! &mut delay,
//! Orientation::Landscape,
//! ili9341::DisplaySize240x320,
//! )
//! .unwrap();
//!
//! display.clear(Rgb565::RED).unwrap()
//! ```
//!
//! [display-interface-spi crate]: https://crates.io/crates/display-interface-spi
use embedded_hal::delay::DelayNs;
use embedded_hal::digital::OutputPin;
use display_interface::DataFormat;
use display_interface::WriteOnlyDataCommand;
#[cfg(feature = "graphics")]
extern crate embedded_graphics;
mod graphics_core;
use embedded_hal::blocking::delay::DelayMs;
use embedded_hal::blocking::spi::{Transfer, Write};
use embedded_hal::digital::v2::OutputPin;
pub use embedded_hal::spi::MODE_0 as SPI_MODE;
pub mod spi;
use spi::SpiInterface;
pub use display_interface::DisplayError;
pub mod gpio;
type Result<T = (), E = DisplayError> = core::result::Result<T, E>;
/// Trait representing the interface to the hardware.
/// Trait that defines display size information
pub trait DisplaySize {
/// Width in pixels
const WIDTH: usize;
/// Height in pixels
const HEIGHT: usize;
}
/// Generic display size of 240x320 pixels
pub struct DisplaySize240x320;
impl DisplaySize for DisplaySize240x320 {
const WIDTH: usize = 240;
const HEIGHT: usize = 320;
}
/// Generic display size of 320x480 pixels
pub struct DisplaySize320x480;
impl DisplaySize for DisplaySize320x480 {
const WIDTH: usize = 320;
const HEIGHT: usize = 480;
}
/// For quite a few boards (ESP32-S2-Kaluga-1, M5Stack, M5Core2 and others),
/// the ILI9341 initialization command arguments are slightly different
///
/// Intended to abstract the various buses (SPI, MPU 8/9/16-bit) from the Controller code.
pub trait Interface {
type Error;
/// This trait provides the flexibility for users to define their own
/// initialization command arguments suitable for the particular board they are using
pub trait Mode {
fn mode(&self) -> u8;
/// 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>;
fn is_landscape(&self) -> bool;
}
const WIDTH: usize = 240;
const HEIGHT: usize = 320;
#[derive(Debug)]
pub enum Error<IfaceE, PinE> {
Interface(IfaceE),
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 implementation of the Mode trait from above
/// Should work for most (but not all) boards
pub enum Orientation {
Portrait,
PortraitFlipped,
@ -57,9 +84,32 @@ pub enum Orientation {
LandscapeFlipped,
}
impl Mode for Orientation {
fn mode(&self) -> u8 {
match self {
Self::Portrait => 0x40 | 0x08,
Self::Landscape => 0x20 | 0x08,
Self::PortraitFlipped => 0x80 | 0x08,
Self::LandscapeFlipped => 0x40 | 0x80 | 0x20 | 0x08,
}
}
fn is_landscape(&self) -> bool {
match self {
Self::Landscape | Self::LandscapeFlipped => true,
Self::Portrait | Self::PortraitFlipped => false,
}
}
}
/// Specify state of specific mode of operation
pub enum ModeState {
On,
Off,
}
/// There are two method for drawing to the screen:
/// [draw_raw](struct.Ili9341.html#method.draw_raw) and
/// [draw_iter](struct.Ili9341.html#method.draw_iter).
/// [Ili9341::draw_raw_iter] and [Ili9341::draw_raw_slice]
///
/// In both cases the expected pixel format is rgb565.
///
@ -78,110 +128,91 @@ pub struct Ili9341<IFACE, RESET> {
reset: RESET,
width: usize,
height: usize,
landscape: bool,
}
impl<SpiE, PinE, SPI, CS, DC, RESET> Ili9341<SpiInterface<SPI, CS, DC>, RESET>
impl<IFACE, RESET> Ili9341<IFACE, RESET>
where
SPI: Transfer<u8, Error = SpiE> + Write<u8, Error = SpiE>,
CS: OutputPin<Error = PinE>,
DC: OutputPin<Error = PinE>,
RESET: OutputPin<Error = PinE>,
IFACE: WriteOnlyDataCommand,
RESET: OutputPin,
{
pub fn new_spi<DELAY: DelayMs<u16>>(
spi: SPI,
cs: CS,
dc: DC,
reset: RESET,
delay: &mut DELAY,
) -> 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>>(
pub fn new<DELAY, SIZE, MODE>(
interface: IFACE,
reset: RESET,
delay: &mut DELAY,
) -> Result<Self, Error<IfaceE, PinE>> {
mode: MODE,
_display_size: SIZE,
) -> Result<Self>
where
DELAY: DelayNs,
SIZE: DisplaySize,
MODE: Mode,
{
let mut ili9341 = Ili9341 {
interface,
reset,
width: WIDTH,
height: HEIGHT,
width: SIZE::WIDTH,
height: SIZE::HEIGHT,
landscape: false,
};
ili9341.hard_reset(delay).map_err(Error::OutputPin)?;
ili9341.command(Command::SoftwareReset, &[])?;
delay.delay_ms(200);
// Do hardware reset by holding reset low for at least 10us
ili9341.reset.set_low().map_err(|_| DisplayError::RSError)?;
let _ = delay.delay_ms(1);
// Set high for normal operation
ili9341
.reset
.set_high()
.map_err(|_| DisplayError::RSError)?;
ili9341.command(Command::PowerControlA, &[0x39, 0x2c, 0x00, 0x34, 0x02])?;
ili9341.command(Command::PowerControlB, &[0x00, 0xc1, 0x30])?;
ili9341.command(Command::DriverTimingControlA, &[0x85, 0x00, 0x78])?;
ili9341.command(Command::DriverTimingControlB, &[0x00, 0x00])?;
ili9341.command(Command::PowerOnSequenceControl, &[0x64, 0x03, 0x12, 0x81])?;
ili9341.command(Command::PumpRatioControl, &[0x20])?;
ili9341.command(Command::PowerControl1, &[0x23])?;
ili9341.command(Command::PowerControl2, &[0x10])?;
ili9341.command(Command::VCOMControl1, &[0x3e, 0x28])?;
ili9341.command(Command::VCOMControl2, &[0x86])?;
ili9341.command(Command::MemoryAccessControl, &[0x48])?;
// Wait 5ms after reset before sending commands
// and 120ms before sending Sleep Out
let _ = delay.delay_ms(5);
// Do software reset
ili9341.command(Command::SoftwareReset, &[])?;
// Wait 5ms after reset before sending commands
// and 120ms before sending Sleep Out
let _ = delay.delay_ms(120);
ili9341.set_orientation(mode)?;
// Set pixel format to 16 bits per pixel
ili9341.command(Command::PixelFormatSet, &[0x55])?;
ili9341.command(Command::FrameControlNormal, &[0x00, 0x18])?;
ili9341.command(Command::DisplayFunctionControl, &[0x08, 0x82, 0x27])?;
ili9341.command(Command::Enable3G, &[0x00])?;
ili9341.command(Command::GammaSet, &[0x01])?;
ili9341.command(
Command::PositiveGammaCorrection,
&[
0x0f, 0x31, 0x2b, 0x0c, 0x0e, 0x08, 0x4e, 0xf1, 0x37, 0x07, 0x10, 0x03, 0x0e, 0x09,
0x00,
],
)?;
ili9341.command(
Command::NegativeGammaCorrection,
&[
0x00, 0x0e, 0x14, 0x03, 0x11, 0x07, 0x31, 0xc1, 0x48, 0x08, 0x0f, 0x0c, 0x31, 0x36,
0x0f,
],
)?;
ili9341.command(Command::SleepOut, &[])?;
delay.delay_ms(120);
ili9341.command(Command::DisplayOn, &[])?;
ili9341.sleep_mode(ModeState::Off)?;
// Wait 5ms after Sleep Out before sending commands
let _ = delay.delay_ms(5);
ili9341.display_mode(ModeState::On)?;
Ok(ili9341)
}
}
fn hard_reset<DELAY: DelayMs<u16>>(&mut self, delay: &mut DELAY) -> Result<(), PinE> {
// set high if previously low
self.reset.set_high()?;
delay.delay_ms(200);
// set low for reset
self.reset.set_low()?;
delay.delay_ms(200);
// set high for normal operation
self.reset.set_high()?;
delay.delay_ms(200);
Ok(())
impl<IFACE, RESET> Ili9341<IFACE, RESET>
where
IFACE: WriteOnlyDataCommand,
{
fn command(&mut self, cmd: Command, args: &[u8]) -> Result {
self.interface.send_commands(DataFormat::U8(&[cmd as u8]))?;
self.interface.send_data(DataFormat::U8(args))
}
fn command(&mut self, cmd: Command, args: &[u8]) -> Result<(), IFACE::Error> {
self.interface.write(cmd as u8, args)
fn write_iter<I: IntoIterator<Item = u16>>(&mut self, data: I) -> Result {
self.command(Command::MemoryWrite, &[])?;
use DataFormat::U16BEIter;
self.interface.send_data(U16BEIter(&mut data.into_iter()))
}
fn write_iter<I: IntoIterator<Item = u16>>(&mut self, data: I) -> Result<(), IFACE::Error> {
self.interface.write_iter(Command::MemoryWrite as u8, data)
fn write_slice(&mut self, data: &[u16]) -> Result {
self.command(Command::MemoryWrite, &[])?;
self.interface.send_data(DataFormat::U16(data))
}
fn set_window(&mut self, x0: u16, y0: u16, x1: u16, y1: u16) -> Result<(), IFACE::Error> {
fn set_window(&mut self, x0: u16, y0: u16, x1: u16, y1: u16) -> Result {
self.command(
Command::ColumnAddressSet,
&[
@ -199,8 +230,51 @@ where
(y1 >> 8) as u8,
(y1 & 0xff) as u8,
],
)
}
/// Configures the screen for hardware-accelerated vertical scrolling.
pub fn configure_vertical_scroll(
&mut self,
fixed_top_lines: u16,
fixed_bottom_lines: u16,
) -> Result<Scroller> {
let height = if self.landscape {
self.width
} else {
self.height
} as u16;
let scroll_lines = height as u16 - fixed_top_lines - fixed_bottom_lines;
self.command(
Command::VerticalScrollDefine,
&[
(fixed_top_lines >> 8) as u8,
(fixed_top_lines & 0xff) as u8,
(scroll_lines >> 8) as u8,
(scroll_lines & 0xff) as u8,
(fixed_bottom_lines >> 8) as u8,
(fixed_bottom_lines & 0xff) as u8,
],
)?;
Ok(())
Ok(Scroller::new(fixed_top_lines, fixed_bottom_lines, height))
}
pub fn scroll_vertically(&mut self, scroller: &mut Scroller, num_lines: u16) -> Result {
scroller.top_offset += num_lines;
if scroller.top_offset > (scroller.height - scroller.fixed_bottom_lines) {
scroller.top_offset = scroller.fixed_top_lines
+ (scroller.top_offset + scroller.fixed_bottom_lines - scroller.height)
}
self.command(
Command::VerticalScrollAddr,
&[
(scroller.top_offset >> 8) as u8,
(scroller.top_offset & 0xff) as u8,
],
)
}
/// Draw a rectangle on the screen, represented by top-left corner (x0, y0)
@ -212,14 +286,14 @@ where
///
/// The iterator is useful to avoid wasting memory by holding a buffer for
/// the whole screen when it is not necessary.
pub fn draw_iter<I: IntoIterator<Item = u16>>(
pub fn draw_raw_iter<I: IntoIterator<Item = u16>>(
&mut self,
x0: u16,
y0: u16,
x1: u16,
y1: u16,
data: I,
) -> Result<(), IFACE::Error> {
) -> Result {
self.set_window(x0, y0, x1, y1)?;
self.write_iter(data)
}
@ -233,44 +307,96 @@ where
/// video memory.
///
/// The expected format is rgb565.
pub fn draw_raw(
&mut self,
x0: u16,
y0: u16,
x1: u16,
y1: u16,
data: &[u16],
) -> Result<(), IFACE::Error> {
pub fn draw_raw_slice(&mut self, x0: u16, y0: u16, x1: u16, y1: u16, data: &[u16]) -> Result {
self.set_window(x0, y0, x1, y1)?;
self.write_iter(data.iter().cloned())
self.write_slice(data)
}
/// Change the orientation of the screen
pub fn set_orientation(&mut self, mode: Orientation) -> Result<(), IFACE::Error> {
pub fn set_orientation<MODE>(&mut self, mode: MODE) -> Result
where
MODE: Mode,
{
self.command(Command::MemoryAccessControl, &[mode.mode()])?;
if self.landscape ^ mode.is_landscape() {
core::mem::swap(&mut self.height, &mut self.width);
}
self.landscape = mode.is_landscape();
Ok(())
}
/// Fill entire screen with specfied color u16 value
pub fn clear_screen(&mut self, color: u16) -> Result {
let color = core::iter::repeat(color).take(self.width * self.height);
self.draw_raw_iter(0, 0, self.width as u16, self.height as u16, color)
}
/// Control the screen sleep mode:
pub fn sleep_mode(&mut self, mode: ModeState) -> Result {
match mode {
Orientation::Portrait => {
self.width = WIDTH;
self.height = HEIGHT;
self.command(Command::MemoryAccessControl, &[0x40 | 0x08])
}
Orientation::Landscape => {
self.width = HEIGHT;
self.height = WIDTH;
self.command(Command::MemoryAccessControl, &[0x20 | 0x08])
}
Orientation::PortraitFlipped => {
self.width = WIDTH;
self.height = HEIGHT;
self.command(Command::MemoryAccessControl, &[0x80 | 0x08])
}
Orientation::LandscapeFlipped => {
self.width = HEIGHT;
self.height = WIDTH;
self.command(Command::MemoryAccessControl, &[0x40 | 0x80 | 0x20 | 0x08])
}
ModeState::On => self.command(Command::SleepModeOn, &[]),
ModeState::Off => self.command(Command::SleepModeOff, &[]),
}
}
/// Control the screen display mode
pub fn display_mode(&mut self, mode: ModeState) -> Result {
match mode {
ModeState::On => self.command(Command::DisplayOn, &[]),
ModeState::Off => self.command(Command::DisplayOff, &[]),
}
}
/// Invert the pixel color on screen
pub fn invert_mode(&mut self, mode: ModeState) -> Result {
match mode {
ModeState::On => self.command(Command::InvertOn, &[]),
ModeState::Off => self.command(Command::InvertOff, &[]),
}
}
/// Idle mode reduces the number of colors to 8
pub fn idle_mode(&mut self, mode: ModeState) -> Result {
match mode {
ModeState::On => self.command(Command::IdleModeOn, &[]),
ModeState::Off => self.command(Command::IdleModeOff, &[]),
}
}
/// Set display brightness to the value between 0 and 255
pub fn brightness(&mut self, brightness: u8) -> Result {
self.command(Command::SetBrightness, &[brightness])
}
/// Set adaptive brightness value equal to [AdaptiveBrightness]
pub fn content_adaptive_brightness(&mut self, value: AdaptiveBrightness) -> Result {
self.command(Command::ContentAdaptiveBrightness, &[value as _])
}
/// Configure [FrameRateClockDivision] and [FrameRate] in normal mode
pub fn normal_mode_frame_rate(
&mut self,
clk_div: FrameRateClockDivision,
frame_rate: FrameRate,
) -> Result {
self.command(
Command::NormalModeFrameRate,
&[clk_div as _, frame_rate as _],
)
}
/// Configure [FrameRateClockDivision] and [FrameRate] in idle mode
pub fn idle_mode_frame_rate(
&mut self,
clk_div: FrameRateClockDivision,
frame_rate: FrameRate,
) -> Result {
self.command(Command::IdleModeFrameRate, &[clk_div as _, frame_rate as _])
}
}
impl<IFACE, RESET> Ili9341<IFACE, RESET> {
/// Get the current screen width. It can change based on the current orientation
pub fn width(&self) -> usize {
self.width
@ -282,33 +408,82 @@ where
}
}
#[cfg(feature = "graphics")]
mod graphics;
/// Scroller must be provided in order to scroll the screen. It can only be obtained
/// by configuring the screen for scrolling.
pub struct Scroller {
top_offset: u16,
fixed_bottom_lines: u16,
fixed_top_lines: u16,
height: u16,
}
impl Scroller {
fn new(fixed_top_lines: u16, fixed_bottom_lines: u16, height: u16) -> Scroller {
Scroller {
top_offset: fixed_top_lines,
fixed_top_lines,
fixed_bottom_lines,
height,
}
}
}
/// Available Adaptive Brightness values
pub enum AdaptiveBrightness {
Off = 0x00,
UserInterfaceImage = 0x01,
StillPicture = 0x02,
MovingImage = 0x03,
}
/// Available frame rate in Hz
pub enum FrameRate {
FrameRate119 = 0x10,
FrameRate112 = 0x11,
FrameRate106 = 0x12,
FrameRate100 = 0x13,
FrameRate95 = 0x14,
FrameRate90 = 0x15,
FrameRate86 = 0x16,
FrameRate83 = 0x17,
FrameRate79 = 0x18,
FrameRate76 = 0x19,
FrameRate73 = 0x1a,
FrameRate70 = 0x1b,
FrameRate68 = 0x1c,
FrameRate65 = 0x1d,
FrameRate63 = 0x1e,
FrameRate61 = 0x1f,
}
/// Frame rate clock division
pub enum FrameRateClockDivision {
Fosc = 0x00,
FoscDiv2 = 0x01,
FoscDiv4 = 0x02,
FoscDiv8 = 0x03,
}
#[derive(Clone, Copy)]
enum Command {
SoftwareReset = 0x01,
PowerControlA = 0xcb,
PowerControlB = 0xcf,
DriverTimingControlA = 0xe8,
DriverTimingControlB = 0xea,
PowerOnSequenceControl = 0xed,
PumpRatioControl = 0xf7,
PowerControl1 = 0xc0,
PowerControl2 = 0xc1,
VCOMControl1 = 0xc5,
VCOMControl2 = 0xc7,
MemoryAccessControl = 0x36,
PixelFormatSet = 0x3a,
FrameControlNormal = 0xb1,
DisplayFunctionControl = 0xb6,
Enable3G = 0xf2,
GammaSet = 0x26,
PositiveGammaCorrection = 0xe0,
NegativeGammaCorrection = 0xe1,
SleepOut = 0x11,
SleepModeOn = 0x10,
SleepModeOff = 0x11,
InvertOff = 0x20,
InvertOn = 0x21,
DisplayOff = 0x28,
DisplayOn = 0x29,
ColumnAddressSet = 0x2a,
PageAddressSet = 0x2b,
MemoryWrite = 0x2c,
VerticalScrollDefine = 0x33,
VerticalScrollAddr = 0x37,
IdleModeOff = 0x38,
IdleModeOn = 0x39,
SetBrightness = 0x51,
ContentAdaptiveBrightness = 0x55,
NormalModeFrameRate = 0xb1,
IdleModeFrameRate = 0xb2,
}

View file

@ -1,69 +0,0 @@
use crate::{Error, Interface};
use embedded_hal::blocking::spi;
use embedded_hal::digital::v2::OutputPin;
use embedded_hal::spi::{Mode, Phase, Polarity};
/// 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(())
}
}