diff --git a/Cargo.lock b/Cargo.lock index fea926c..f4c0650 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -144,6 +144,15 @@ version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" +[[package]] +name = "euclid" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "667703ececa1ac04d1d40ae0c0fd6401b91d8caccfda3a65458ca8ee5dfedf1c" +dependencies = [ + "num-traits", +] + [[package]] name = "getrandom" version = "0.1.14" @@ -165,6 +174,19 @@ dependencies = [ "lzw", ] +[[package]] +name = "helms-display" +version = "0.1.0" +dependencies = [ + "arrayref", + "euclid", + "image", + "imageproc", + "rppal", + "text_io", + "xml-rs", +] + [[package]] name = "hermit-abi" version = "0.1.13" @@ -493,16 +515,6 @@ dependencies = [ "num_cpus", ] -[[package]] -name = "rpi-rust-test" -version = "0.1.0" -dependencies = [ - "arrayref", - "image", - "imageproc", - "rppal", -] - [[package]] name = "rppal" version = "0.11.3" @@ -555,6 +567,12 @@ dependencies = [ "byteorder", ] +[[package]] +name = "text_io" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cb170b4f47dc48835fbc56259c12d8963e542b05a24be2e3a1f5a6c320fd2d4" + [[package]] name = "tiff" version = "0.4.0" @@ -571,3 +589,9 @@ name = "wasi" version = "0.9.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "xml-rs" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b07db065a5cf61a7e4ba64f29e67db906fb1787316516c4e6e5ff0fea1efcd8a" diff --git a/Cargo.toml b/Cargo.toml index 3074a1f..73b65b5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "rpi-rust-test" +name = "helms-display" version = "0.1.0" authors = ["Jakob Dalsgaard "] edition = "2018" @@ -11,4 +11,7 @@ rppal = "0.11.3" image = "*" imageproc = "*" arrayref = "*" +euclid = "*" +xml-rs = "0.8" +text_io = "0.1.8" diff --git a/forms/boat.svg b/forms/boat.svg new file mode 100644 index 0000000..1a78633 --- /dev/null +++ b/forms/boat.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/forms/compass-rose.svg b/forms/compass-rose.svg new file mode 100644 index 0000000..f2915e5 --- /dev/null +++ b/forms/compass-rose.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/src/forms.rs b/src/forms.rs new file mode 100644 index 0000000..df6a185 --- /dev/null +++ b/src/forms.rs @@ -0,0 +1,128 @@ +use std::cmp; +use euclid::{Angle, Scale, Transform2D, Vector2D}; +use image::{Rgb, RgbImage}; +use imageproc::drawing::draw_line_segment_mut; + +use std::str::FromStr; +use std::f32; +use std::u8; +use std::error::Error; + +use std::fs::File; +use std::io::BufReader; +use xml::reader::{EventReader, XmlEvent}; +use euclid::Point2D; + +use text_io::scan; + +pub enum ModelSpace {} +pub enum ScreenSpace {} + +pub struct Screen { + width: u32, + height: u32, + model_scale: Scale, + pub image : RgbImage, +} + +impl Screen { + pub fn new (width: u32, height: u32) -> Screen { + let image = RgbImage::new(width, height); + let model_scale = Scale::new(cmp::min(width,height) as f32 /1000.0); + Screen { width, height, model_scale, image } + } + + pub fn render (&mut self, form: &Form, angle: f32, x: u16, y: u16) { + let transform : Transform2D = Transform2D::create_rotation(Angle::radians(angle)).post_translate(Vector2D::new(500.0, 500.0)); + let tx_points = form.points.iter().map(|p| transform.transform_point(*p)).collect::>(); + let screen_points = tx_points.iter().map(|p| self.model_scale.transform_point(*p)).collect::>(); + for (indices, weight, (r, g, b)) in &form.lines { + let p0 = screen_points[indices[0] as usize].to_tuple(); + let p1 = screen_points[indices[1] as usize].to_tuple(); + draw_line_segment_mut(&mut self.image, p0, p1, Rgb([*r, *g, *b])); + } + } + + /** + * Clear the underlying RGB image + */ + pub fn clear (&mut self) { + let ptr = self.image.as_mut_ptr(); + unsafe { + for i in 0..self.image.len() { + *ptr.offset(i as isize) = 0; + } + } + } + + pub fn save (&mut self) { + self.image.save("image.png").map_err(|err| println!("Error saving image: {}", err.to_string())).unwrap(); + } + +} + +pub struct Form { + pub points: Vec>, // points in this form, in the space of -500..500 + pub lines: Vec<(Vec, u8, (u8, u8, u8))>, // Vector of line tuples, a line tuple is a vector of line indices and an associated width and rgb color +} + +// load an svg file into a forms object +impl Form { + + + pub fn load (filename: &str) -> Result> { + let file = File::open(filename)?; + let file = BufReader::new(file); + let parser = EventReader::new(file); + let mut points = Vec::new(); // rust compiler will figure out types :-) neat + let mut lines = Vec::new(); + + for e in parser { + match e { + Ok(XmlEvent::StartElement { name, attributes, ..}) => { + if name.local_name == "line" { + // we have a line that should be added to the form + let mut pp = [0.0 as f32; 4]; + let mut width = 1; + let (mut r, mut g, mut b): (u8, u8, u8) = (255,255,255); + for i in attributes { + match i.name.local_name.as_str() { + "x1" => pp[0] = f32::from_str(&i.value)?, + "y1" => pp[1] = f32::from_str(&i.value)?, + "x2" => pp[2] = f32::from_str(&i.value)?, + "y2" => pp[3] = f32::from_str(&i.value)?, + "stroke" => scan!(i.value.bytes() => "rgb({},{},{})", r, g, b), + "stroke-width" => width = u8::from_str(&i.value)?, + _ => println!("unhandled attribute {}", i.name.local_name), + } + } + let points_id = points.len() as u16; + points.push(Point2D::new(pp[0]-500.0, pp[1]-500.0)); + points.push(Point2D::new(pp[2]-500.0, pp[3]-500.0)); + + lines.push((vec![points_id, points_id+1], width, (r,g,b))); + } + }, + Err(e) => { + println!("Error parsing file '{}': {}", filename, e); + break; + }, + _ => {} + } + } + Ok(Form{ points, lines }) + } + +} + +// load the compass rose +pub fn load_compass_rose () -> Form { + let form = Form::load("/root/forms/compass-rose.svg").unwrap(); + form +} + +pub fn load_boat () -> Form { + let form = Form::load("/root/forms/boat.svg").unwrap(); + form +} + diff --git a/src/ilidisplay.rs b/src/ilidisplay.rs index 7ade19d..68fc51b 100644 --- a/src/ilidisplay.rs +++ b/src/ilidisplay.rs @@ -5,8 +5,7 @@ use std::time::Duration; use rppal::gpio::{Gpio, OutputPin}; -use rppal::spi::{Bus, Mode, Segment, SlaveSelect, Spi, Polarity}; -use rppal::system::DeviceInfo; +use rppal::spi::{Bus, Mode, SlaveSelect, Spi}; use image::RgbImage; @@ -29,28 +28,38 @@ impl IliDisplay { pub fn init() -> Result> { - let mut gpio = Gpio::new()?; + let gpio = Gpio::new()?; - let mut dc_pin = gpio.get(GPIO_DC)?.into_output(); - let mut led_pin = gpio.get(GPIO_LED)?.into_output(); - let mut reset_pin = gpio.get(GPIO_RESET)?.into_output(); + let dc_pin = gpio.get(GPIO_DC)?.into_output(); + let led_pin = gpio.get(GPIO_LED)?.into_output(); + let reset_pin = gpio.get(GPIO_RESET)?.into_output(); - let mut spi = Spi::new(Bus::Spi0, SlaveSelect::Ss0, 1_000_000, Mode::Mode0)?; - spi.set_ss_polarity(Polarity::ActiveHigh); + let spi = Spi::new(Bus::Spi0, SlaveSelect::Ss0, 10_000_000, Mode::Mode0)?; return Ok(IliDisplay { spi, dc_pin, reset_pin, led_pin }); } + pub fn print_id (&mut self) { + self.dc_pin.set_low(); + let cmdv = [0x04, 0xFF, 0xFF, 0xFF, 0xFF]; + let mut readv = [0 as u8; 5]; + self.spi.transfer(&mut readv, &cmdv).map_err(|err| println!("SPI transfer error in print_id: {}", err.to_string())).unwrap(); + self.dc_pin.set_high(); + println!("{:#X} {:#X} {:#X} {:#X} {:#X}", readv[0], readv[1], readv[2], readv[3], readv[4]); + } pub fn send_command (&mut self, command: u8, data: &[u8]) { // set DC low for byte one self.dc_pin.set_low(); - self.spi.write(&[command]); + let mut cmdr = [0]; + self.spi.transfer(&mut cmdr, &[command]).map_err(|err| println!("SPI transfer error in send_command (command): {}", err.to_string())).unwrap(); self.dc_pin.set_high(); - if (data.len() > 0) { - self.spi.write(data); + if data.len() > 0 { + let mut full_readv = [0 as u8; 4096]; + // let mut readv = array_ref![full_readv, 0, data.len()]; + self.spi.transfer(&mut full_readv, &data).map_err(|err| println!("SPI transfer error in send_command (data): {}", err.to_string())).unwrap(); } } @@ -61,25 +70,31 @@ impl IliDisplay { self.dc_pin.set_high(); // drop the reset pin to low and set it high again to fully reset the display self.reset_pin.set_high(); - thread::sleep(Duration::from_millis(20)); + thread::sleep(Duration::from_millis(50)); self.reset_pin.set_low(); - thread::sleep(Duration::from_millis(100)); + thread::sleep(Duration::from_millis(50)); self.reset_pin.set_high(); - thread::sleep(Duration::from_millis(20)); + thread::sleep(Duration::from_millis(150)); self.send_command(0x01, &[]); thread::sleep(Duration::from_millis(50)); self.send_command(0x28, &[]); + + // gamma setup self.send_command(0xE0, &[0x00, 0x03, 0x09, 0x08, 0x16, 0x0A, 0x3F, 0x78, 0x4C, 0x09, 0x0A, 0x08, 0x16, 0x1A, 0x0F]); self.send_command(0xE1, &[0x00, 0x16, 0x19, 0x03, 0x0F, 0x05, 0x32, 0x45, 0x46, 0x04, 0x0E, 0x0D, 0x35, 0x37, 0x0F]); + + // self.send_command(0xC0, &[0x17, 0x15]); self.send_command(0xC1, &[0x41]); self.send_command(0x36, &[0xE8]); self.send_command(0x3A, &[0x66]); - self.send_command(0xB0, &[0x80]); + //self.send_command(0xB0, &[0x80]); self.send_command(0xB1, &[0xA0]); self.send_command(0xB4, &[0x02]); - self.send_command(0xB6, &[0x02, 0x02, 0x3B]); + + // Display Function Control + self.send_command(0xB6, &[0x02, 0x02]); // 0x3B self.send_command(0xE9, &[0x00]); self.send_command(0xF7, &[0xA9, 0x51, 0x2C, 0x82]); self.send_command(0x11, &[]); @@ -96,15 +111,48 @@ impl IliDisplay { self.led_pin.set_low(); } + pub fn put_image(&mut self, image: &RgbImage, (x, y): (u32, u32)) -> () { + let (w, h) = image.dimensions(); + let x_extent = (x+w-1) as u16; + let y_extent = (y+h-1) as u16; + self.send_command(0x2A, &[(x >> 8) as u8, (x & 0xFF) as u8, (x_extent >> 8) as u8, (x_extent & 0xFF) as u8]); + self.send_command(0x2B, &[(y >> 8) as u8, (y & 0xFF) as u8, (y_extent >> 8) as u8, (y_extent & 0xFF) as u8]); + self.send_command(0x2C, &[]); + +// let image_data = image.into_raw(); + for c in image.chunks(4096) { + self.spi.write(&c).map_err(|err| println!("SPI error in put_image: {}", err.to_string())).unwrap(); + } +/* + let image_data = image.as_ptr(); + // we can send 4096 byte at a time + let to_send = image_data.len(); + let full_blocks = to_send / 4096; + let mut pointer = 0; + for i in 0..full_blocks { + self.spi.write(&image_data[pointer .. (pointer+4096) ]).map_err(|err| println!("SPI error in put_image (4096): {}", err.to_string())).unwrap(); + pointer = pointer + 4096; + } + let remains = to_send - pointer; + if remains > 0 { + self.spi.write(&image_data[pointer .. (pointer+remains)]).map_err(|err| println!("SPI error in put_imnage (remains): {}", err.to_string())).unwrap(); + } +*/ + } + // send one image to the display pub fn send_image(&mut self, image: RgbImage) -> () { - println!("send image"); // assume 480x320 for both image and screen self.send_command(0x2A, &[0, 0, (480 >> 8) as u8, (480 & 0xFF) as u8]); self.send_command(0x2B, &[0, 0, (320 >> 8) as u8, (320 & 0xFF) as u8]); + self.send_command(0x2C, &[]); let imagedata = image.into_raw(); // let imagedata_ref = array_ref![imagedata, 0, 480*320*3]; - let imagedata_ref = array_ref![imagedata, 0, 4096]; - self.spi.write(imagedata_ref); + // self.spi.write(imagedata_ref); + + for y in 0..320 { + let imagedata_ref = array_ref![imagedata, y*480*3, 480*3]; + self.spi.write(imagedata_ref).map_err(|err| println!("SPI error in send_image: {}", err.to_string())).unwrap(); + } } } diff --git a/src/main.rs b/src/main.rs index b509adb..33bcbe9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,10 +3,7 @@ extern crate arrayref; use std::error::Error; use std::thread; use std::time::Duration; - -use rppal::gpio::Gpio; -use rppal::spi::{Bus, Mode, Segment, SlaveSelect, Spi}; -use rppal::system::DeviceInfo; +use std::f32::consts::PI; use image::{ @@ -17,25 +14,35 @@ use imageproc::rect::Rect; use imageproc::drawing::draw_filled_rect_mut; mod ilidisplay; +mod forms; fn main() -> Result<(), Box> { + let mut screen = forms::Screen::new(200, 200); + let c = forms::load_compass_rose(); + let b = forms::load_boat(); + +/* let mut img = RgbImage::new(480, 320); let thickness = 10; draw_filled_rect_mut(&mut img, Rect::at(0,0).of_size(240, 160), Rgb([255,0,0])); - draw_filled_rect_mut(&mut img, Rect::at(0,160).of_size(240, 160), Rgb([255,0,0])); + draw_filled_rect_mut(&mut img, Rect::at(0,160).of_size(240, 160), Rgb([0, 255, 0])); draw_filled_rect_mut(&mut img, Rect::at(240,0).of_size(240, 160), Rgb([0, 0, 255])); +*/ let mut e = ilidisplay::IliDisplay::init()?; - println!("Before init, sleeping for 5s"); - thread::sleep(Duration::from_millis(5000)); e.init_chip(); - println!("After init, sleeping for 5s"); - thread::sleep(Duration::from_millis(5000)); e.turn_on(); - println!("Before image, screen has now been reset and turned on, sleeping for 5s"); - thread::sleep(Duration::from_millis(5000)); - e.send_image(img); + for i in 0..100 { + let rad = (i as f32) * 2.0*PI / 100.0; + screen.clear(); + screen.render(&c, rad, 500, 500); + screen.render(&b, 0.0, 500, 500); + e.put_image(&(screen.image), ((240-100), (160-100))); + } + + // draw_filled_rect_mut(&mut img, Rect::at(0,0).of_size(480, 320), Rgb([0, 0, 0])); + println!("You should see image now, sleeping for 5s"); thread::sleep(Duration::from_millis(5000)); e.turn_off();