|
|
|
@ -0,0 +1,237 @@
|
|
|
|
|
#![no_main]
|
|
|
|
|
#![no_std]
|
|
|
|
|
|
|
|
|
|
use embedded_hal::{delay::DelayNs, digital::OutputPin, spi::SpiBus};
|
|
|
|
|
use fugit::RateExtU32;
|
|
|
|
|
use panic_halt as _;
|
|
|
|
|
use rp_pico::{
|
|
|
|
|
entry,
|
|
|
|
|
hal::{
|
|
|
|
|
clocks::init_clocks_and_plls,
|
|
|
|
|
gpio::{FunctionSio, Pin, PinId, PullDown, SioOutput},
|
|
|
|
|
spi::{Enabled, Spi, SpiDevice, ValidSpiPinout},
|
|
|
|
|
Clock, Sio, Timer, Watchdog,
|
|
|
|
|
},
|
|
|
|
|
pac, Pins,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const XOSC_CRYSTAL_FREQ: u32 = 12_000_000; // MHz, https://forums.raspberrypi.com/viewtopic.php?t=356764
|
|
|
|
|
|
|
|
|
|
const ROWS: usize = 320;
|
|
|
|
|
const COLUMNS: usize = 240;
|
|
|
|
|
const FRAMEBUF: usize = ROWS * COLUMNS * 3;
|
|
|
|
|
|
|
|
|
|
struct Step {
|
|
|
|
|
param_cnt: usize,
|
|
|
|
|
command: u8,
|
|
|
|
|
params: [u8; 4],
|
|
|
|
|
delay: Option<u32>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Step {
|
|
|
|
|
fn send_command<D, Pinout, P>(
|
|
|
|
|
&self,
|
|
|
|
|
spi: &mut Spi<Enabled, D, Pinout, 8>,
|
|
|
|
|
data_command: &mut Pin<P, FunctionSio<SioOutput>, PullDown>,
|
|
|
|
|
) where
|
|
|
|
|
D: SpiDevice,
|
|
|
|
|
Pinout: ValidSpiPinout<D>,
|
|
|
|
|
P: PinId,
|
|
|
|
|
{
|
|
|
|
|
let _ = data_command.set_low();
|
|
|
|
|
let _ = spi.write(&[self.command]);
|
|
|
|
|
if self.param_cnt > 0 {
|
|
|
|
|
let _ = data_command.set_high();
|
|
|
|
|
let _ = spi.write(&self.params[0..self.param_cnt]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const NOP: u8 = 0x00;
|
|
|
|
|
|
|
|
|
|
const SWRESET: Step = Step {
|
|
|
|
|
param_cnt: 0,
|
|
|
|
|
command: 0x01,
|
|
|
|
|
params: [0, 0, 0, 0],
|
|
|
|
|
delay: Some(150),
|
|
|
|
|
};
|
|
|
|
|
const SLPOUT: Step = Step {
|
|
|
|
|
param_cnt: 0,
|
|
|
|
|
command: 0x11,
|
|
|
|
|
params: [0, 0, 0, 0],
|
|
|
|
|
delay: Some(10),
|
|
|
|
|
};
|
|
|
|
|
const COLMOD: u8 = 0x3a;
|
|
|
|
|
const MADCTL: Step = Step {
|
|
|
|
|
param_cnt: 1,
|
|
|
|
|
command: 0x36,
|
|
|
|
|
params: [0x08, 0, 0, 0],
|
|
|
|
|
delay: None,
|
|
|
|
|
};
|
|
|
|
|
const CASET: u8 = 0x2a;
|
|
|
|
|
const RASET: u8 = 0x2b;
|
|
|
|
|
const INVON: Step = Step {
|
|
|
|
|
param_cnt: 0,
|
|
|
|
|
command: 0x21,
|
|
|
|
|
params: [0, 0, 0, 0],
|
|
|
|
|
delay: Some(10),
|
|
|
|
|
};
|
|
|
|
|
const NORON: Step = Step {
|
|
|
|
|
param_cnt: 0,
|
|
|
|
|
command: 0x13,
|
|
|
|
|
params: [0, 0, 0, 0],
|
|
|
|
|
delay: Some(10),
|
|
|
|
|
};
|
|
|
|
|
const DISPON: Step = Step {
|
|
|
|
|
param_cnt: 0,
|
|
|
|
|
command: 0x29,
|
|
|
|
|
params: [0, 0, 0, 0],
|
|
|
|
|
delay: Some(10),
|
|
|
|
|
};
|
|
|
|
|
const RAMWR: u8 = 0x2c;
|
|
|
|
|
|
|
|
|
|
// Adafruit setup instructions
|
|
|
|
|
// SWRESET (0x01), 150ms delay
|
|
|
|
|
// SLPOUT (0x11), 10ms delay
|
|
|
|
|
// COLMOD (0x3a) 0x55 (65K RGB, 16bit/pixel), 10ms delay
|
|
|
|
|
// MADCTL (0x36) 0x00,
|
|
|
|
|
// memory data access control, RGB
|
|
|
|
|
// CASET 0x00, 0, 0, 170,
|
|
|
|
|
// column address set, 4 parameters
|
|
|
|
|
// 0x00, 0x00 indicates xstart is 0
|
|
|
|
|
// 0x00, 170 indicates xend is 170
|
|
|
|
|
// RASET 0x00, 0, 320 >> 8, 320 & 0xFF,
|
|
|
|
|
// row address set, 4 parameters
|
|
|
|
|
// 0x00, 0x00 indicates ystart is 0
|
|
|
|
|
// 3230 >> 8, 320 & 0xff indicates that 320 is the last y address
|
|
|
|
|
// INVON, 10ms delay
|
|
|
|
|
// invert the display
|
|
|
|
|
// NORON, 10ms delay
|
|
|
|
|
// normal display mode
|
|
|
|
|
// DISPON, 10ms delay
|
|
|
|
|
// turn the display on
|
|
|
|
|
|
|
|
|
|
const SETUP_PROGRAM: [Step; 9] = [
|
|
|
|
|
SWRESET,
|
|
|
|
|
SLPOUT,
|
|
|
|
|
Step {
|
|
|
|
|
param_cnt: 1,
|
|
|
|
|
command: COLMOD,
|
|
|
|
|
params: [0x66, 0, 0, 0],
|
|
|
|
|
delay: Some(10),
|
|
|
|
|
},
|
|
|
|
|
MADCTL,
|
|
|
|
|
Step {
|
|
|
|
|
param_cnt: 4,
|
|
|
|
|
command: CASET,
|
|
|
|
|
params: [0, 0, 0, 240],
|
|
|
|
|
delay: None,
|
|
|
|
|
},
|
|
|
|
|
Step {
|
|
|
|
|
param_cnt: 4,
|
|
|
|
|
command: RASET,
|
|
|
|
|
params: [0, 0, (320 >> 8) as u8, (320 & 0xff) as u8],
|
|
|
|
|
delay: None,
|
|
|
|
|
},
|
|
|
|
|
INVON,
|
|
|
|
|
NORON,
|
|
|
|
|
DISPON,
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
#[entry]
|
|
|
|
|
unsafe fn main() -> ! {
|
|
|
|
|
// rp_pico::pac::Peripherals is a reference to physical hardware defined on the Pico.
|
|
|
|
|
let mut peripherals = pac::Peripherals::take().unwrap();
|
|
|
|
|
|
|
|
|
|
// SIO inidcates "Single Cycle IO". I don't know what this means, but it could mean that this
|
|
|
|
|
// is a class of IO operations that can be run in a single clock cycle, such as switching a
|
|
|
|
|
// GPIO pin on or off.
|
|
|
|
|
let sio = Sio::new(peripherals.SIO);
|
|
|
|
|
|
|
|
|
|
// Many of the following systems require a watchdog. I do not know what this does, either, but
|
|
|
|
|
// it may be some failsafe software that will reset operations if the watchdog detects a lack
|
|
|
|
|
// of activity.
|
|
|
|
|
let mut watchdog = Watchdog::new(peripherals.WATCHDOG);
|
|
|
|
|
|
|
|
|
|
// Here we grab the GPIO pins in bank 0.
|
|
|
|
|
let pins = Pins::new(
|
|
|
|
|
peripherals.IO_BANK0,
|
|
|
|
|
peripherals.PADS_BANK0,
|
|
|
|
|
sio.gpio_bank0,
|
|
|
|
|
&mut peripherals.RESETS,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Initialize an abstraction of the clock system with a batch of standard hardware clocks.
|
|
|
|
|
let clocks = init_clocks_and_plls(
|
|
|
|
|
XOSC_CRYSTAL_FREQ,
|
|
|
|
|
peripherals.XOSC,
|
|
|
|
|
peripherals.CLOCKS,
|
|
|
|
|
peripherals.PLL_SYS,
|
|
|
|
|
peripherals.PLL_USB,
|
|
|
|
|
&mut peripherals.RESETS,
|
|
|
|
|
&mut watchdog,
|
|
|
|
|
)
|
|
|
|
|
.ok()
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
|
|
// An abstraction for a timer which we can use to delay the code.
|
|
|
|
|
let mut timer = Timer::new(peripherals.TIMER, &mut peripherals.RESETS, &clocks);
|
|
|
|
|
|
|
|
|
|
// Grab the clock and data pins for SPI1. For Clock pins and for Data pins, there are only two
|
|
|
|
|
// pins each on the Pico which can function for SPI1.
|
|
|
|
|
let spi_clk = pins.gpio2.into_function();
|
|
|
|
|
let spi_sdo = pins.gpio3.into_function();
|
|
|
|
|
// let spi_sdi = pins.gpio4.into_function();
|
|
|
|
|
// Chip select 1 means the chip is not enabled
|
|
|
|
|
let mut board_select = pins.gpio13.into_function();
|
|
|
|
|
let mut data_command = pins.gpio15.into_function();
|
|
|
|
|
let mut reset = pins.gpio14.into_function();
|
|
|
|
|
|
|
|
|
|
let _ = reset.set_low();
|
|
|
|
|
let _ = board_select.set_high();
|
|
|
|
|
let _ = data_command.set_high();
|
|
|
|
|
|
|
|
|
|
// Now, create the SPI function abstraction for SPI1 with spi_clk and spi_sdo.
|
|
|
|
|
let mut spi = Spi::<_, _, _, 8>::new(peripherals.SPI0, (spi_sdo, spi_clk)).init(
|
|
|
|
|
&mut peripherals.RESETS,
|
|
|
|
|
// The SPI system uses the peripheral clock
|
|
|
|
|
clocks.peripheral_clock.freq(),
|
|
|
|
|
// Transmit data at a rate of 1Mbit.
|
|
|
|
|
32_u32.MHz(),
|
|
|
|
|
// Run with SPI Mode 1. This means that the clock line should start high and that data will
|
|
|
|
|
// be sampled starting at the first falling edge.
|
|
|
|
|
embedded_hal::spi::MODE_3,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
let _ = reset.set_high();
|
|
|
|
|
timer.delay_ms(10);
|
|
|
|
|
let _ = board_select.set_low();
|
|
|
|
|
timer.delay_ms(10);
|
|
|
|
|
for step in SETUP_PROGRAM {
|
|
|
|
|
step.send_command(&mut spi, &mut data_command);
|
|
|
|
|
if let Some(delay) = step.delay {
|
|
|
|
|
timer.delay_ms(delay);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
timer.delay_ms(1000);
|
|
|
|
|
|
|
|
|
|
let mut bitmap: [u8; FRAMEBUF] = [0; FRAMEBUF];
|
|
|
|
|
|
|
|
|
|
let mut i = 0;
|
|
|
|
|
loop {
|
|
|
|
|
let _ = board_select.set_low();
|
|
|
|
|
let _ = data_command.set_low();
|
|
|
|
|
let _ = spi.write(&[RAMWR]);
|
|
|
|
|
let _ = data_command.set_high();
|
|
|
|
|
let _ = spi.write(&bitmap);
|
|
|
|
|
let _ = board_select.set_high();
|
|
|
|
|
|
|
|
|
|
let color = i << 2;
|
|
|
|
|
bitmap = [color; FRAMEBUF];
|
|
|
|
|
|
|
|
|
|
i = if i >= 64 { 0 } else { i + 1 };
|
|
|
|
|
|
|
|
|
|
timer.delay_ms(10);
|
|
|
|
|
}
|
|
|
|
|
}
|