* Make the `DMA` peripheral API more consistent * Update examples for devices with `PDMA` * Update examples for devices with `GDMA` * Update `CHANGELOG.md` * Update example after rebasing
246 lines
7.6 KiB
Rust
246 lines
7.6 KiB
Rust
//! Drives the 8-bit parallel display on a WT32-SC01 Plus
|
|
//!
|
|
//! This example clears the screen with red and then blue every second.
|
|
//!
|
|
//! Pins used
|
|
//! Backlight GPIO45
|
|
//! Reset GPIO4
|
|
//! CD GPIO0
|
|
//! WR GPIO47
|
|
//! D0 GPIO9
|
|
//! D1 GPIO46
|
|
//! D2 GPIO3
|
|
//! D3 GPIO8
|
|
//! D4 GPIO18
|
|
//! D5 GPIO17
|
|
//! D6 GPIO16
|
|
//! D7 GPIO15
|
|
|
|
#![no_std]
|
|
#![no_main]
|
|
|
|
use esp32s3_hal::{
|
|
clock::ClockControl,
|
|
dma::{Dma, DmaPriority},
|
|
dma_buffers,
|
|
gpio::IO,
|
|
lcd_cam::{
|
|
lcd::i8080::{Config, TxEightBits, I8080},
|
|
LcdCam,
|
|
},
|
|
peripherals::Peripherals,
|
|
prelude::*,
|
|
Delay,
|
|
};
|
|
use esp_backtrace as _;
|
|
use esp_println::println;
|
|
|
|
#[entry]
|
|
fn main() -> ! {
|
|
let peripherals = Peripherals::take();
|
|
let system = peripherals.SYSTEM.split();
|
|
let clocks = ClockControl::boot_defaults(system.clock_control).freeze();
|
|
|
|
let io = IO::new(peripherals.GPIO, peripherals.IO_MUX);
|
|
|
|
let lcd_backlight = io.pins.gpio45;
|
|
let lcd_reset = io.pins.gpio4;
|
|
let lcd_rs = io.pins.gpio0; // Command/Data selection
|
|
let lcd_wr = io.pins.gpio47; // Write clock
|
|
let _lcd_te = io.pins.gpio48; // Frame sync
|
|
|
|
let dma = Dma::new(peripherals.DMA);
|
|
let channel = dma.channel0;
|
|
|
|
let (tx_buffer, mut tx_descriptors, _, mut rx_descriptors) = dma_buffers!(32678, 0);
|
|
|
|
let channel = channel.configure(
|
|
false,
|
|
&mut tx_descriptors,
|
|
&mut rx_descriptors,
|
|
DmaPriority::Priority0,
|
|
);
|
|
|
|
let mut delay = Delay::new(&clocks);
|
|
|
|
let mut backlight = lcd_backlight.into_push_pull_output();
|
|
let mut reset = lcd_reset.into_push_pull_output();
|
|
|
|
let tx_pins = TxEightBits::new(
|
|
io.pins.gpio9,
|
|
io.pins.gpio46,
|
|
io.pins.gpio3,
|
|
io.pins.gpio8,
|
|
io.pins.gpio18,
|
|
io.pins.gpio17,
|
|
io.pins.gpio16,
|
|
io.pins.gpio15,
|
|
);
|
|
|
|
let lcd_cam = LcdCam::new(peripherals.LCD_CAM);
|
|
let mut i8080 = I8080::new(
|
|
lcd_cam.lcd,
|
|
channel.tx,
|
|
tx_pins,
|
|
20u32.MHz(),
|
|
Config::default(),
|
|
&clocks,
|
|
)
|
|
.with_ctrl_pins(lcd_rs, lcd_wr);
|
|
|
|
{
|
|
// https://gist.github.com/sukesh-ak/610508bc84779a26efdcf969bf51a2d1
|
|
// https://github.com/lovyan03/LovyanGFX/blob/302169a6f23e9a2a6451f03311c366d182193831/src/lgfx/v1/panel/Panel_ST7796.hpp#L28
|
|
|
|
reset.set_low().unwrap();
|
|
delay.delay_us(8_000u32);
|
|
reset.set_high().unwrap();
|
|
delay.delay_us(64_000u32);
|
|
|
|
// const CMD_FRMCTR1: u8 = 0xB1;
|
|
// const CMD_FRMCTR2: u8 = 0xB2;
|
|
// const CMD_FRMCTR3: u8 = 0xB3;
|
|
const CMD_INVCTR: u8 = 0xB4;
|
|
const CMD_DFUNCTR: u8 = 0xB6;
|
|
// const CMD_ETMOD: u8 = 0xB7;
|
|
// const CMD_PWCTR1: u8 = 0xC0;
|
|
const CMD_PWCTR2: u8 = 0xC1;
|
|
const CMD_PWCTR3: u8 = 0xC2;
|
|
// const CMD_PWCTR4: u8 = 0xC3;
|
|
// const CMD_PWCTR5: u8 = 0xC4;
|
|
const CMD_VMCTR: u8 = 0xC5;
|
|
const CMD_GMCTRP1: u8 = 0xE0; // Positive Gamma Correction
|
|
const CMD_GMCTRN1: u8 = 0xE1; // Negative Gamma Correction
|
|
const CMD_DOCA: u8 = 0xE8; // Display Output Ctrl Adjust
|
|
const CMD_CSCON: u8 = 0xF0; // Command Set Control
|
|
|
|
i8080.send(CMD_CSCON, 0, &[0xC3]).unwrap(); // Enable extension command 2 part I
|
|
i8080.send(CMD_CSCON, 0, &[0x96]).unwrap(); // Enable extension command 2 part II
|
|
i8080.send(CMD_INVCTR, 0, &[0x01]).unwrap(); // 1-dot inversion
|
|
i8080
|
|
.send(
|
|
CMD_DFUNCTR,
|
|
0,
|
|
&[
|
|
0x80, // Display Function Control //Bypass
|
|
0x22, /* Source Output Scan from S1 to S960, Gate Output scan from G1 to
|
|
* G480, scan cycle=2 */
|
|
0x3B,
|
|
],
|
|
)
|
|
.unwrap(); // LCD Drive Line=8*(59+1)
|
|
i8080
|
|
.send(
|
|
CMD_DOCA,
|
|
0,
|
|
&[
|
|
0x40, 0x8A, 0x00, 0x00, 0x29, // Source eqaulizing period time= 22.5 us
|
|
0x19, // Timing for "Gate start"=25 (Tclk)
|
|
0xA5, // Timing for "Gate End"=37 (Tclk), Gate driver EQ function ON
|
|
0x33,
|
|
],
|
|
)
|
|
.unwrap();
|
|
i8080.send(CMD_PWCTR2, 0, &[0x06]).unwrap(); // Power control2 //VAP(GVDD)=3.85+( vcom+vcom offset), VAN(GVCL)=-3.85+(
|
|
// vcom+vcom offset)
|
|
i8080.send(CMD_PWCTR3, 0, &[0xA7]).unwrap(); // Power control 3 //Source driving current level=low, Gamma driving current
|
|
// level=High
|
|
i8080.send(CMD_VMCTR, 0, &[0x18]).unwrap(); // VCOM Control //VCOM=0.9
|
|
delay.delay_us(120_000u32);
|
|
i8080
|
|
.send(
|
|
CMD_GMCTRP1,
|
|
0,
|
|
&[
|
|
0xF0, 0x09, 0x0B, 0x06, 0x04, 0x15, 0x2F, 0x54, 0x42, 0x3C, 0x17, 0x14, 0x18,
|
|
0x1B,
|
|
],
|
|
)
|
|
.unwrap();
|
|
i8080
|
|
.send(
|
|
CMD_GMCTRN1,
|
|
0,
|
|
&[
|
|
0xE0, 0x09, 0x0B, 0x06, 0x04, 0x03, 0x2B, 0x43, 0x42, 0x3B, 0x16, 0x14, 0x17,
|
|
0x1B,
|
|
],
|
|
)
|
|
.unwrap();
|
|
delay.delay_us(120_000u32);
|
|
i8080.send(CMD_CSCON, 0, &[0x3C]).unwrap(); // Command Set control // Disable extension command 2 partI
|
|
i8080.send(CMD_CSCON, 0, &[0x69]).unwrap(); // Command Set control // Disable
|
|
// extension command 2 partII
|
|
|
|
i8080.send(0x11, 0, &[]).unwrap(); // ExitSleepMode
|
|
delay.delay_us(130_000u32);
|
|
i8080.send(0x38, 0, &[]).unwrap(); // ExitIdleMode
|
|
i8080.send(0x29, 0, &[]).unwrap(); // SetDisplayOn
|
|
|
|
i8080.send(0x21, 0, &[]).unwrap(); // SetInvertMode(ColorInversion::Inverted)
|
|
|
|
// let madctl = SetAddressMode::from(options);
|
|
// i8080.send(madctl)?;
|
|
|
|
i8080.send(0x3A, 0, &[0x55]).unwrap(); // RGB565
|
|
}
|
|
|
|
let width = 320u16;
|
|
let height = 480u16;
|
|
{
|
|
println!("Set addresses");
|
|
|
|
let width_b = width.to_be_bytes();
|
|
let height_b = height.to_be_bytes();
|
|
i8080
|
|
.send(0x2A, 0, &[0, 0, width_b[0], width_b[1]])
|
|
.unwrap(); // CASET
|
|
i8080
|
|
.send(0x2B, 0, &[0, 0, height_b[0], height_b[1]])
|
|
.unwrap(); // PASET
|
|
}
|
|
|
|
println!("Drawing");
|
|
|
|
const RED: u16 = 0b00000_000000_11111;
|
|
const BLUE: u16 = 0b11111_000000_00000;
|
|
|
|
backlight.set_high().unwrap();
|
|
|
|
let total_pixels = width as usize * height as usize;
|
|
let total_bytes = total_pixels * 2;
|
|
|
|
let mut buffer = tx_buffer;
|
|
|
|
for color in [RED, BLUE].iter().cycle() {
|
|
let color = color.to_be_bytes();
|
|
for chunk in buffer.chunks_mut(2) {
|
|
chunk.copy_from_slice(&color);
|
|
}
|
|
|
|
let mut bytes_left_to_write = total_bytes;
|
|
|
|
let transfer = i8080.send_dma(0x2C, 0, buffer).unwrap();
|
|
(buffer, i8080) = transfer.wait().unwrap();
|
|
|
|
bytes_left_to_write -= buffer.len();
|
|
|
|
while bytes_left_to_write >= buffer.len() {
|
|
let transfer = i8080.send_dma(0x3C, 0, buffer).unwrap();
|
|
(buffer, i8080) = transfer.wait().unwrap();
|
|
|
|
bytes_left_to_write -= buffer.len();
|
|
}
|
|
if bytes_left_to_write > 0 {
|
|
let transfer = i8080.send_dma(0x3C, 0, buffer).unwrap();
|
|
(buffer, i8080) = transfer.wait().unwrap();
|
|
}
|
|
|
|
delay.delay_ms(1_000u32);
|
|
}
|
|
|
|
loop {
|
|
delay.delay_ms(1_000u32);
|
|
}
|
|
}
|