st7789_rs/
lib.rs

1use std::{fs::File, cmp::min, thread::sleep, time::Duration};
2
3use color::Color;
4use image::{DynamicImage, ImageBuffer, imageops::overlay};
5use imageproc::{drawing::{draw_text_mut, draw_filled_rect_mut}, rect::Rect};
6use rppal::gpio::{Gpio, OutputPin};
7use rusttype::{Scale, Font};
8use spidev::{Spidev, SpidevOptions, SpiModeFlags, SpidevTransfer};
9
10use conv::bytes_from_img;
11use text::Text;
12
13pub mod conv;
14pub mod text;
15pub mod color;
16
17struct Consts {}
18
19#[allow(dead_code)]
20impl Consts {
21  const BG_SPI_CS_BACK: u8 = 0;
22  const BG_SPI_CS_FRONT: u8 = 1;
23
24  const SPI_CLOCK_HZ: u32 = 60_000_000;
25
26  const ST7789_NOP: u8 = 0x00;
27  const ST7789_SWRESET: u8 = 0x01;
28  const ST7789_RDDID: u8 = 0x04;
29  const ST7789_RDDST: u8 = 0x09;
30
31  const ST7789_SLPIN: u8 = 0x10;
32  const ST7789_SLPOUT: u8 = 0x11;
33  const ST7789_PTLON: u8 = 0x12;
34  const ST7789_NORON: u8 = 0x13;
35
36  const ST7789_INVOFF: u8 = 0x20;
37  const ST7789_INVON: u8 = 0x21;
38  const ST7789_DISPOFF: u8 = 0x28;
39  const ST7789_DISPON: u8 = 0x29;
40
41  const ST7789_CASET: u8 = 0x2A;
42  const ST7789_RASET: u8 = 0x2B;
43  const ST7789_RAMWR: u8 = 0x2C;
44  const ST7789_RAMRD: u8 = 0x2E;
45
46  const ST7789_PTLAR: u8 = 0x30;
47  const ST7789_MADCTL: u8 = 0x36;
48  const ST7789_COLMOD: u8 = 0x3A;
49
50  const ST7789_FRMCTR1: u8 = 0xB1;
51  const ST7789_FRMCTR2: u8 = 0xB2;
52  const ST7789_FRMCTR3: u8 = 0xB3;
53  const ST7789_INVCTR: u8 = 0xB4;
54  const ST7789_DISSET5: u8 = 0xB6;
55
56  const ST7789_GCTRL: u8 = 0xB7;
57  const ST7789_GTADJ: u8 = 0xB8;
58  const ST7789_VCOMS: u8 = 0xBB;
59
60  const ST7789_LCMCTRL: u8 = 0xC0;
61  const ST7789_IDSET: u8 = 0xC1;
62  const ST7789_VDVVRHEN: u8 = 0xC2;
63  const ST7789_VRHS: u8 = 0xC3;
64  const ST7789_VDVS: u8 = 0xC4;
65  const ST7789_VMCTR1: u8 = 0xC5;
66  const ST7789_FRCTRL2: u8 = 0xC6;
67  const ST7789_CABCCTRL: u8 = 0xC7;
68
69  const ST7789_RDID1: u8 = 0xDA;
70  const ST7789_RDID2: u8 = 0xDB;
71  const ST7789_RDID3: u8 = 0xDC;
72  const ST7789_RDID4: u8 = 0xDD;
73
74  const ST7789_GMCTRP1: u8 = 0xE0;
75  const ST7789_GMCTRN1: u8 = 0xE1;
76
77  const ST7789_PWCTR6: u8 = 0xFC;
78}
79
80
81pub enum DataType {
82  Data,
83  Command
84}
85
86#[allow(dead_code)]
87pub struct ST7789 {
88  gpio: Gpio,
89  spi: Spidev,
90
91  cs: OutputPin,
92  dc: OutputPin,
93  bl: OutputPin,
94  rst: Option<OutputPin>, // optional
95
96  width: i16, // dimensions
97  height: i16, // ^
98  rotation: f32, // optional w/ default (0)
99  offset_x: i16, // optional w/ default (0)
100  offset_y: i16, // optional w/ default (0)
101
102  invert: bool,
103
104  display_buffer: DynamicImage,
105
106  chunk_size: Option<usize>
107}
108
109impl ST7789 {
110  pub fn new(
111    spi_no: u8, 
112    dev_no: u8,
113    cs_no: u8,
114    dc_no: u8,
115    bl_no: u8,
116    speed_hz: u32
117  ) -> Self {
118    let mut spi = Spidev::new(File::open(format!("/dev/spidev{}.{}", spi_no, dev_no)).expect("COULDN'T OPEN SPI FILE!"));
119    let gpio = Gpio::new().unwrap();
120    
121    // pins
122    let cs = gpio.get(cs_no).unwrap().into_output();
123    let dc = gpio.get(dc_no).unwrap().into_output();
124    let bl = gpio.get(bl_no).unwrap().into_output();
125
126    // options
127    let opts = SpidevOptions::new()
128      .bits_per_word(0)
129      .max_speed_hz(speed_hz)
130      .lsb_first(false)
131      .mode(SpiModeFlags::SPI_MODE_0)
132      .build();
133
134    spi.configure(&opts).expect(
135      "Couldn't configure SPI! \
136      \nThis is likely because SPI is not set\
137      \nup on your pi."
138    );
139  
140    return Self {
141      gpio: gpio,
142      spi: spi,
143      //pwm: pwm,
144
145      cs: cs,
146      dc: dc,
147      bl: bl,
148      rst: None,
149
150      width: 320,
151      height: 170,
152      rotation: 0_f32,
153      offset_x: 0,
154      offset_y: 0,
155
156      invert: true,
157      display_buffer: DynamicImage::ImageRgb8(ImageBuffer::new(320, 170)),
158
159      chunk_size: None
160    }
161  }
162
163  pub fn draw_image(&mut self, img: &DynamicImage, pos_x: i16, pos_y: i16) {
164    overlay(&mut self.display_buffer, img, pos_x as i64, pos_y as i64);
165  }
166
167  pub fn draw_text(&mut self, text: &str, font: &Font, pos_x: i16, pos_y: i16, color: &Color, size: f32) {
168    draw_text_mut(
169      &mut self.display_buffer, 
170      color.get_rgba(), 
171      pos_x as i32, pos_y as i32, 
172      Scale::uniform(size), 
173      font, 
174      text
175    );
176  }
177
178  pub fn draw_text_obj(&mut self, text: &Text, pos_x: i16, pos_y: i16) {
179   self.draw_text( 
180      text.content.as_str(),
181      &text.font,
182      pos_x, pos_y,
183      &text.color,
184      text.size
185    );
186  }
187
188  pub fn draw_rect(&mut self, rect: Rect, color: &Color) {
189    draw_filled_rect_mut(
190      &mut self.display_buffer,
191      rect,
192      color.get_rgba()
193    );
194  }
195
196  pub fn draw_clear(&mut self, color: &Color) {
197    draw_filled_rect_mut(
198      &mut self.display_buffer,
199      Rect::at(0, 0)
200        .of_size(self.width as u32, self.height as u32), 
201      color.get_rgba()
202    );
203  }
204
205  pub fn display(&mut self) {
206    self.set_window(None, None, None, None);
207
208    let bytes_vec = bytes_from_img(&self.display_buffer);
209    let bytes = bytes_vec.as_slice();
210
211    self.send_cmd(Consts::ST7789_RAMWR);
212    self.send_datas(bytes);
213  }
214
215  pub fn set_window(&mut self, x1_o: Option<i16>, y1_o: Option<i16>, x2_o: Option<i16>, y2_o: Option<i16>) {
216    let mut x1 = match x1_o { Some(x) => x, None => 0 };
217    let mut y1 = match y1_o { Some(y) => y, None => 0 };
218    let mut x2 = match x2_o { Some(x) => x-1, None => self.width-1 };
219    let mut y2 = match y2_o { Some(y) => y-1, None => self.height-1 };
220
221    x1 += self.offset_x;
222    y1 += self.offset_y;
223    x2 += self.offset_x;
224    y2 += self.offset_y;
225 
226    // column addr set
227    self.send_cmd(Consts::ST7789_CASET);
228    self.send_data((x1 >> 8) as u8); // x start
229    self.send_data((x1 & 0xff) as u8); // ^ other half
230    self.send_data((x2 >> 8) as u8); // x end
231    self.send_data((x2 & 0xff) as u8); // ^ other half
232
233    // row addr set
234    self.send_cmd(Consts::ST7789_RASET);
235    self.send_data((y1 >> 8) as u8); // x start
236    self.send_data((y1 & 0xff) as u8); // ^ other half
237    self.send_data((y2 >> 8) as u8); // x end
238    self.send_data((y2 & 0xff) as u8); // ^ other half
239
240  }
241
242  pub fn clear(&mut self, col: u16) {
243    let color_half = col.to_be_bytes();
244    let num_pixels = self.width as i32 * self.height as i32;
245    let mut pix_vec: Vec<u8> = Vec::new();
246
247    self.set_window(None, None, None, None);
248
249    for _ in 0..num_pixels {
250      pix_vec.push(color_half[0]);
251      pix_vec.push(color_half[1]);
252    }
253
254    self.send_cmd(Consts::ST7789_RAMWR);
255    self.send_datas(pix_vec.as_slice());
256  }
257
258  // TODO: Doesn't work as of now
259  pub fn reset(&mut self) {
260    match &mut self.rst {
261      Some(p) => {
262        p.set_high();
263        sleep(Duration::from_millis(10));
264        p.set_low();
265        sleep(Duration::from_millis(10));
266        p.set_high();
267        sleep(Duration::from_millis(10));
268      }
269      None => {
270        println!("Reset pin not set.");
271      }
272    }
273  }
274
275  pub fn init(&mut self) {
276    self.bl.set_pwm_frequency(1000.0, 1.0).expect(
277      "Couldn't set PWM frequency! \
278      \nThis is likely because your PWM outputs \
279      \nare disabled."
280    );
281    self.reset();
282
283    self.send_cmd(Consts::ST7789_SWRESET);
284    sleep(Duration::from_millis(150));
285
286    // mem access data ctrl
287    // format: <bit> - <description> - <0>/<1>
288    // from least to most significant:
289    // 0, 1: - ignored
290    // 2 - LCD refresh     - l-r / r-l
291    // 3 - color           - rgb / bgr
292    // 4 - line addr order - t-b / b-t
293    // 5 - page/col order  - normal / reversed
294    // 6 - col addr order  - l-r / r-l
295    // 7 - page addr order - t-b / b-t
296  
297    self.send_cmd(Consts::ST7789_MADCTL);
298    self.send_data(0x70);
299
300    self.send_cmd(Consts::ST7789_FRMCTR2); // frame ctrl, normal mode
301    self.send_data(0x0C);
302    self.send_data(0x0C);
303    self.send_data(0x00);
304    self.send_data(0x33);
305    self.send_data(0x33);
306
307    self.send_cmd(Consts::ST7789_COLMOD); // color mode
308    self.send_data(0x05); // rgb565
309
310    self.send_cmd(Consts::ST7789_GCTRL); // gate ctrl
311    self.send_data(0x35);
312
313    self.send_cmd(Consts::ST7789_VCOMS); 
314    self.send_data(0x13);
315
316    self.send_cmd(Consts::ST7789_LCMCTRL); // power ctrl
317    self.send_data(0x2C);
318
319    self.send_cmd(Consts::ST7789_VDVVRHEN); // power ctrl
320    self.send_data(0x01);
321
322    self.send_cmd(Consts::ST7789_VRHS); // power ctrl
323    self.send_data(0x0B);
324
325    self.send_cmd(Consts::ST7789_VDVS); // power ctrl
326    self.send_data(0x20);
327
328    self.send_cmd(0xd0); // power ctrl
329    self.send_data(0xa4);
330    self.send_data(0xa1);
331
332    self.send_cmd(Consts::ST7789_FRCTRL2); // framerate ctrl
333    self.send_data(0x0f);
334
335    self.send_cmd(Consts::ST7789_GMCTRP1); // gamma +
336    self.send_data(0x00);
337    self.send_data(0x03);
338    self.send_data(0x07);
339    self.send_data(0x08);
340    self.send_data(0x07);
341    self.send_data(0x15);
342    self.send_data(0x2A);
343    self.send_data(0x44);
344    self.send_data(0x42);
345    self.send_data(0x0A);
346    self.send_data(0x17);
347    self.send_data(0x18);
348    self.send_data(0x25);
349    self.send_data(0x27);
350
351    self.send_cmd(Consts::ST7789_GMCTRN1); // gamma -
352    self.send_data(0x00);
353    self.send_data(0x03);
354    self.send_data(0x08);
355    self.send_data(0x07);
356    self.send_data(0x07);
357    self.send_data(0x23);
358    self.send_data(0x2A);
359    self.send_data(0x43);
360    self.send_data(0x42);
361    self.send_data(0x09);
362    self.send_data(0x18);
363    self.send_data(0x17);
364    self.send_data(0x25);
365    self.send_data(0x27);
366
367    self.send_cmd(match self.invert {
368        true => Consts::ST7789_INVON,
369        false => Consts::ST7789_INVOFF
370    });
371
372    self.send_cmd(Consts::ST7789_SLPOUT);
373
374    self.send_cmd(Consts::ST7789_DISPON);
375  }
376
377  pub fn cleanup(&mut self) {
378    match &mut self.rst {
379      Some(pin) => {
380        pin.set_high();
381        self.dc.set_low();
382        sleep(Duration::from_millis(1));
383        self.bl.set_high();
384      },
385      None => return
386    }
387  }
388
389  pub fn send_data(&mut self, data: u8) {
390    self.send_datas(&[data]);
391  }
392
393  pub fn send_cmd(&mut self, data: u8) {
394    self.send_cmds(&[data]);
395  }
396
397  pub fn send_datas(&mut self, data: &[u8]) {
398    self.send(data, DataType::Data);
399  }
400
401  pub fn send_cmds(&mut self, data: &[u8]) {
402    self.send(data, DataType::Command);
403  }
404
405  pub fn send(&mut self, data: &[u8], data_type: DataType) {
406    // set data mode (high for data, low for command)
407    match data_type {
408      DataType::Command => self.dc.set_low(),
409      DataType::Data => self.dc.set_high()
410    };
411
412    // get chunk size
413    let step = match self.chunk_size {
414      Some(s) => s,
415      None => 4096
416    };
417
418    // send the data <step> bytes at a time
419    for start in (0..data.len()).step_by(step) {
420      let end = min(start + step, data.len());
421      let mut slice = SpidevTransfer::write(&data[start..end]);
422
423      match self.spi.transfer(&mut slice) {
424        Ok(_) => (),
425        Err(_) => println!("Error sending command/data starting with {:#04x}", data[start]),
426      }
427    }
428  }
429
430  pub fn with_reset(mut self, rst_no: u8) -> Self {
431    self.rst =Some(self.gpio.get(rst_no).unwrap().into_output());
432
433    return self;
434  }
435
436  pub fn with_dimensions(mut self, width: i16, height: i16) -> Self {
437    self.width = width;
438    self.height = height;
439
440    return self;
441  }
442
443  pub fn with_rotation(mut self, rot: f32) -> Self {
444    self.rotation = rot;
445
446    return self;
447  }
448
449  pub fn with_offset(mut self, off_x: i16, off_y: i16) -> Self {
450    self.offset_x = off_x;
451    self.offset_y = off_y;
452
453    return self;
454  }
455
456  pub fn with_chunk_size(mut self, size: usize) -> Self {
457    self.chunk_size = Some(size);
458
459    return self;
460  }
461
462  pub fn width(&self) -> i16 {
463    return self.width;
464  }
465
466  pub fn height(&self) -> i16 {
467    return self.height;
468  }
469}