* Move DMA channels into Peripherals * Initialize DMA in the critical section needed for clock management * Update esp-hal/MIGRATING-0.22.md Co-authored-by: Jesse Braham <jessebraham@users.noreply.github.com> --------- Co-authored-by: Jesse Braham <jessebraham@users.noreply.github.com>
387 lines
12 KiB
Rust
387 lines
12 KiB
Rust
//! Drives the 16-bit parallel RGB display on ESP32-S3-LCD-EV-Board v1.5
|
|
//!
|
|
//! This example fills the screen with every color.
|
|
//!
|
|
//! The following wiring is assumed:
|
|
//! - LCD_VSYNC => GPIO3
|
|
//! - LCD_HSYNC => GPIO46
|
|
//! - LCD_DE => GPIO17
|
|
//! - LCD_PCLK => GPIO9
|
|
//! - LCD_DATA0 => GPIO10
|
|
//! - LCD_DATA1 => GPIO11
|
|
//! - LCD_DATA2 => GPIO12
|
|
//! - LCD_DATA3 => GPIO13
|
|
//! - LCD_DATA4 => GPIO14
|
|
//! - LCD_DATA5 => GPIO21
|
|
//! - LCD_DATA6 => GPIO8
|
|
//! - LCD_DATA7 => GPIO18
|
|
//! - LCD_DATA8 => GPIO45
|
|
//! - LCD_DATA9 => GPIO38
|
|
//! - LCD_DATA10 => GPIO39
|
|
//! - LCD_DATA11 => GPIO40
|
|
//! - LCD_DATA12 => GPIO41
|
|
//! - LCD_DATA13 => GPIO42
|
|
//! - LCD_DATA14 => GPIO2
|
|
//! - LCD_DATA15 => GPIO1
|
|
|
|
//% CHIPS: esp32s3
|
|
|
|
#![no_std]
|
|
#![no_main]
|
|
|
|
use core::iter::{empty, once};
|
|
|
|
use esp_backtrace as _;
|
|
use esp_hal::{
|
|
delay::Delay,
|
|
dma_loop_buffer,
|
|
gpio::{Level, Output},
|
|
i2c,
|
|
i2c::master::I2c,
|
|
lcd_cam::{
|
|
lcd::{
|
|
dpi::{Config, Dpi, Format, FrameTiming},
|
|
ClockMode,
|
|
Phase,
|
|
Polarity,
|
|
},
|
|
LcdCam,
|
|
},
|
|
peripherals::Peripherals,
|
|
prelude::*,
|
|
Blocking,
|
|
};
|
|
use esp_println::println;
|
|
|
|
#[entry]
|
|
fn main() -> ! {
|
|
esp_println::logger::init_logger_from_env();
|
|
|
|
let peripherals: Peripherals = esp_hal::init(esp_hal::Config::default());
|
|
|
|
let i2c = I2c::new(
|
|
peripherals.I2C0,
|
|
i2c::master::Config {
|
|
frequency: 400.kHz(),
|
|
..Default::default()
|
|
},
|
|
)
|
|
.with_sda(peripherals.GPIO47)
|
|
.with_scl(peripherals.GPIO48);
|
|
|
|
let tx_channel = peripherals.DMA_CH2;
|
|
let lcd_cam = LcdCam::new(peripherals.LCD_CAM);
|
|
|
|
let mut expander = Tca9554::new(i2c);
|
|
expander.write_output_reg(0b1111_0011).unwrap();
|
|
expander.write_direction_reg(0b1111_0001).unwrap();
|
|
|
|
let delay = Delay::new();
|
|
|
|
println!("Initialising");
|
|
|
|
let mut write_byte = |b: u8, is_cmd: bool| {
|
|
const SCS_BIT: u8 = 0b0000_0010;
|
|
const SCL_BIT: u8 = 0b0000_0100;
|
|
const SDA_BIT: u8 = 0b0000_1000;
|
|
|
|
let mut output = 0b1111_0001 & !SCS_BIT;
|
|
expander.write_output_reg(output).unwrap();
|
|
|
|
for bit in once(!is_cmd).chain((0..8).map(|i| (b >> i) & 0b1 != 0).rev()) {
|
|
let prev = output;
|
|
if bit {
|
|
output |= SDA_BIT;
|
|
} else {
|
|
output &= !SDA_BIT;
|
|
}
|
|
if prev != output {
|
|
expander.write_output_reg(output).unwrap();
|
|
}
|
|
|
|
output &= !SCL_BIT;
|
|
expander.write_output_reg(output).unwrap();
|
|
|
|
output |= SCL_BIT;
|
|
expander.write_output_reg(output).unwrap();
|
|
}
|
|
|
|
output &= !SCL_BIT;
|
|
expander.write_output_reg(output).unwrap();
|
|
|
|
output &= !SDA_BIT;
|
|
expander.write_output_reg(output).unwrap();
|
|
|
|
output |= SCS_BIT;
|
|
expander.write_output_reg(output).unwrap();
|
|
};
|
|
|
|
let mut vsync_pin = peripherals.GPIO3;
|
|
|
|
let vsync_must_be_high_during_setup = Output::new(&mut vsync_pin, Level::High);
|
|
for &init in INIT_CMDS.iter() {
|
|
match init {
|
|
InitCmd::Cmd(cmd, args) => {
|
|
write_byte(cmd, true);
|
|
for &arg in args {
|
|
write_byte(arg, false);
|
|
}
|
|
}
|
|
InitCmd::Delay(ms) => {
|
|
delay.delay_millis(ms as _);
|
|
}
|
|
}
|
|
}
|
|
drop(vsync_must_be_high_during_setup);
|
|
|
|
let mut dma_buf = dma_loop_buffer!(2 * 16);
|
|
|
|
let mut config = Config::default();
|
|
config.clock_mode = ClockMode {
|
|
polarity: Polarity::IdleLow,
|
|
phase: Phase::ShiftLow,
|
|
};
|
|
config.format = Format {
|
|
enable_2byte_mode: true,
|
|
..Default::default()
|
|
};
|
|
config.timing = FrameTiming {
|
|
horizontal_active_width: 480,
|
|
horizontal_total_width: 520,
|
|
horizontal_blank_front_porch: 10,
|
|
|
|
vertical_active_height: 480,
|
|
vertical_total_height: 510,
|
|
vertical_blank_front_porch: 10,
|
|
|
|
hsync_width: 10,
|
|
vsync_width: 10,
|
|
|
|
hsync_position: 0,
|
|
};
|
|
config.vsync_idle_level = Level::High;
|
|
config.hsync_idle_level = Level::High;
|
|
config.de_idle_level = Level::Low;
|
|
config.disable_black_region = false;
|
|
|
|
let mut dpi = Dpi::new(lcd_cam.lcd, tx_channel, 16.MHz(), config)
|
|
.with_vsync(vsync_pin)
|
|
.with_hsync(peripherals.GPIO46)
|
|
.with_de(peripherals.GPIO17)
|
|
.with_pclk(peripherals.GPIO9)
|
|
// Blue
|
|
.with_data0(peripherals.GPIO10)
|
|
.with_data1(peripherals.GPIO11)
|
|
.with_data2(peripherals.GPIO12)
|
|
.with_data3(peripherals.GPIO13)
|
|
.with_data4(peripherals.GPIO14)
|
|
// Green
|
|
.with_data5(peripherals.GPIO21)
|
|
.with_data6(peripherals.GPIO8)
|
|
.with_data7(peripherals.GPIO18)
|
|
.with_data8(peripherals.GPIO45)
|
|
.with_data9(peripherals.GPIO38)
|
|
.with_data10(peripherals.GPIO39)
|
|
// Red
|
|
.with_data11(peripherals.GPIO40)
|
|
.with_data12(peripherals.GPIO41)
|
|
.with_data13(peripherals.GPIO42)
|
|
.with_data14(peripherals.GPIO2)
|
|
.with_data15(peripherals.GPIO1);
|
|
|
|
const MAX_RED: u16 = (1 << 5) - 1;
|
|
const MAX_GREEN: u16 = (1 << 6) - 1;
|
|
const MAX_BLUE: u16 = (1 << 5) - 1;
|
|
|
|
fn rgb(r: u16, g: u16, b: u16) -> u16 {
|
|
(r << 11) | (g << 5) | (b << 0)
|
|
}
|
|
|
|
let mut colors = empty()
|
|
// Start with red and gradually add green
|
|
.chain((0..=MAX_GREEN).map(|g| rgb(MAX_RED, g, 0)))
|
|
// Then remove the red
|
|
.chain((0..=MAX_RED).rev().map(|r| rgb(r, MAX_GREEN, 0)))
|
|
// Then add blue
|
|
.chain((0..=MAX_BLUE).map(|b| rgb(0, MAX_GREEN, b)))
|
|
// Then remove green
|
|
.chain((0..=MAX_GREEN).rev().map(|g| rgb(0, g, MAX_BLUE)))
|
|
// Then add red
|
|
.chain((0..=MAX_RED).map(|r| rgb(r, 0, MAX_BLUE)))
|
|
// Then remove blue
|
|
.chain((0..=MAX_BLUE).rev().map(|b| rgb(MAX_RED, 0, b)))
|
|
// Once we get we have red, and we can start again.
|
|
.cycle();
|
|
|
|
println!("Rendering");
|
|
loop {
|
|
let transfer = dpi.send(false, dma_buf).map_err(|e| e.0).unwrap();
|
|
(_, dpi, dma_buf) = transfer.wait();
|
|
|
|
if let Some(color) = colors.next() {
|
|
for chunk in dma_buf.chunks_mut(2) {
|
|
chunk.copy_from_slice(&color.to_le_bytes());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
struct Tca9554 {
|
|
i2c: I2c<'static, Blocking>,
|
|
address: u8,
|
|
}
|
|
|
|
impl Tca9554 {
|
|
pub fn new(i2c: I2c<'static, Blocking>) -> Self {
|
|
Self { i2c, address: 0x20 }
|
|
}
|
|
|
|
pub fn write_direction_reg(&mut self, value: u8) -> Result<(), i2c::master::Error> {
|
|
self.i2c.write(self.address, &[0x03, value])
|
|
}
|
|
|
|
pub fn write_output_reg(&mut self, value: u8) -> Result<(), i2c::master::Error> {
|
|
self.i2c.write(self.address, &[0x01, value])
|
|
}
|
|
}
|
|
|
|
#[derive(Copy, Clone)]
|
|
enum InitCmd {
|
|
Cmd(u8, &'static [u8]),
|
|
Delay(u8),
|
|
}
|
|
|
|
const INIT_CMDS: &[InitCmd] = &[
|
|
InitCmd::Cmd(0xf0, &[0x55, 0xaa, 0x52, 0x08, 0x00]),
|
|
InitCmd::Cmd(0xf6, &[0x5a, 0x87]),
|
|
InitCmd::Cmd(0xc1, &[0x3f]),
|
|
InitCmd::Cmd(0xc2, &[0x0e]),
|
|
InitCmd::Cmd(0xc6, &[0xf8]),
|
|
InitCmd::Cmd(0xc9, &[0x10]),
|
|
InitCmd::Cmd(0xcd, &[0x25]),
|
|
InitCmd::Cmd(0xf8, &[0x8a]),
|
|
InitCmd::Cmd(0xac, &[0x45]),
|
|
InitCmd::Cmd(0xa0, &[0xdd]),
|
|
InitCmd::Cmd(0xa7, &[0x47]),
|
|
InitCmd::Cmd(0xfa, &[0x00, 0x00, 0x00, 0x04]),
|
|
InitCmd::Cmd(0x86, &[0x99, 0xa3, 0xa3, 0x51]),
|
|
InitCmd::Cmd(0xa3, &[0xee]),
|
|
InitCmd::Cmd(0xfd, &[0x3c, 0x3]),
|
|
InitCmd::Cmd(0x71, &[0x48]),
|
|
InitCmd::Cmd(0x72, &[0x48]),
|
|
InitCmd::Cmd(0x73, &[0x00, 0x44]),
|
|
InitCmd::Cmd(0x97, &[0xee]),
|
|
InitCmd::Cmd(0x83, &[0x93]),
|
|
InitCmd::Cmd(0x9a, &[0x72]),
|
|
InitCmd::Cmd(0x9b, &[0x5a]),
|
|
InitCmd::Cmd(0x82, &[0x2c, 0x2c]),
|
|
InitCmd::Cmd(0xB1, &[0x10]),
|
|
InitCmd::Cmd(
|
|
0x6d,
|
|
&[
|
|
0x00, 0x1f, 0x19, 0x1a, 0x10, 0x0e, 0x0c, 0x0a, 0x02, 0x07, 0x1e, 0x1e, 0x1e, 0x1e,
|
|
0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x08, 0x01, 0x09, 0x0b, 0x0d, 0x0f,
|
|
0x1a, 0x19, 0x1f, 0x00,
|
|
],
|
|
),
|
|
InitCmd::Cmd(
|
|
0x64,
|
|
&[
|
|
0x38, 0x05, 0x01, 0xdb, 0x03, 0x03, 0x38, 0x04, 0x01, 0xdc, 0x03, 0x03, 0x7a, 0x7a,
|
|
0x7a, 0x7a,
|
|
],
|
|
),
|
|
InitCmd::Cmd(
|
|
0x65,
|
|
&[
|
|
0x38, 0x03, 0x01, 0xdd, 0x03, 0x03, 0x38, 0x02, 0x01, 0xde, 0x03, 0x03, 0x7a, 0x7a,
|
|
0x7a, 0x7a,
|
|
],
|
|
),
|
|
InitCmd::Cmd(
|
|
0x66,
|
|
&[
|
|
0x38, 0x01, 0x01, 0xdf, 0x03, 0x03, 0x38, 0x00, 0x01, 0xe0, 0x03, 0x03, 0x7a, 0x7a,
|
|
0x7a, 0x7a,
|
|
],
|
|
),
|
|
InitCmd::Cmd(
|
|
0x67,
|
|
&[
|
|
0x30, 0x01, 0x01, 0xe1, 0x03, 0x03, 0x30, 0x02, 0x01, 0xe2, 0x03, 0x03, 0x7a, 0x7a,
|
|
0x7a, 0x7a,
|
|
],
|
|
),
|
|
InitCmd::Cmd(
|
|
0x68,
|
|
&[
|
|
0x00, 0x08, 0x15, 0x08, 0x15, 0x7a, 0x7a, 0x08, 0x15, 0x08, 0x15, 0x7a, 0x7a,
|
|
],
|
|
),
|
|
InitCmd::Cmd(0x60, &[0x38, 0x08, 0x7a, 0x7a, 0x38, 0x09, 0x7a, 0x7a]),
|
|
InitCmd::Cmd(0x63, &[0x31, 0xe4, 0x7a, 0x7a, 0x31, 0xe5, 0x7a, 0x7a]),
|
|
InitCmd::Cmd(0x69, &[0x04, 0x22, 0x14, 0x22, 0x14, 0x22, 0x08]),
|
|
InitCmd::Cmd(0x6b, &[0x07]),
|
|
InitCmd::Cmd(0x7a, &[0x08, 0x13]),
|
|
InitCmd::Cmd(0x7b, &[0x08, 0x13]),
|
|
InitCmd::Cmd(
|
|
0xd1,
|
|
&[
|
|
0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35,
|
|
0x00, 0x47, 0x00, 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7,
|
|
0x02, 0x36, 0x02, 0xa6, 0x02, 0xee, 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5,
|
|
0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03, 0xff,
|
|
],
|
|
),
|
|
InitCmd::Cmd(
|
|
0xd2,
|
|
&[
|
|
0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35,
|
|
0x00, 0x47, 0x00, 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7,
|
|
0x02, 0x36, 0x02, 0xa6, 0x02, 0xee, 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5,
|
|
0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03, 0xff,
|
|
],
|
|
),
|
|
InitCmd::Cmd(
|
|
0xd3,
|
|
&[
|
|
0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35,
|
|
0x00, 0x47, 0x00, 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7,
|
|
0x02, 0x36, 0x02, 0xa6, 0x02, 0xee, 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5,
|
|
0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03, 0xff,
|
|
],
|
|
),
|
|
InitCmd::Cmd(
|
|
0xd4,
|
|
&[
|
|
0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35,
|
|
0x00, 0x47, 0x00, 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7,
|
|
0x02, 0x36, 0x02, 0xa6, 0x02, 0xee, 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5,
|
|
0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03, 0xff,
|
|
],
|
|
),
|
|
InitCmd::Cmd(
|
|
0xd5,
|
|
&[
|
|
0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35,
|
|
0x00, 0x47, 0x00, 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7,
|
|
0x02, 0x36, 0x02, 0xa6, 0x02, 0xee, 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5,
|
|
0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03, 0xff,
|
|
],
|
|
),
|
|
InitCmd::Cmd(
|
|
0xd6,
|
|
&[
|
|
0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35,
|
|
0x00, 0x47, 0x00, 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7,
|
|
0x02, 0x36, 0x02, 0xa6, 0x02, 0xee, 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5,
|
|
0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03, 0xff,
|
|
],
|
|
),
|
|
InitCmd::Cmd(0x3A, &[0x66]),
|
|
InitCmd::Cmd(0x11, &[]),
|
|
InitCmd::Delay(120),
|
|
InitCmd::Cmd(0x29, &[]),
|
|
InitCmd::Delay(20),
|
|
];
|