st7735_async_low/
commands.rs

1// Copyright 2021 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use crate::command_structs::*;
16use crate::spi::{DcxPin, Read, ReadBits as _, WriteU8, WriteU8s};
17
18/// Commands of ST7735 in their original form, except that the parameters
19/// of each command are typed.
20#[derive(Debug)]
21pub struct Commands<S> { spi: S }
22
23impl<S: DcxPin> Commands<S> {
24    /// Creates a new instance with an spi object.
25    pub fn new(mut spi: S) -> Self {
26        spi.set_dcx_command_mode();
27        Self{spi}
28    }
29}
30
31impl<S> Commands<S> where S: DcxPin,
32                          for<'a> S: WriteU8<'a> + WriteU8s<'a> {
33    /// Sets the column address window as `begin` to `end`, both inclusive.
34    #[inline(always)]
35    pub async fn caset(&mut self, begin: u16, end: u16) {
36        self.command_with_u16_pair(0x2A, begin, end).await;
37    }
38
39    /// Sets the row address window as `begin` to `end`, both inclusive.
40    #[inline(always)]
41    pub async fn raset(&mut self, begin: u16, end: u16) {
42        self.command_with_u16_pair(0x2B, begin, end).await;
43    }
44
45    /// Starts writing memory. The returned object can be used to actually do
46    /// the memory writing.
47    #[inline(always)]
48    pub async fn ramwr(&mut self) -> RamWriter<'_, S> {
49        self.command(0x2C).await;
50        self.spi.set_dcx_data_mode();
51        // `RamWriter::drop()` will restore to command mode.
52        RamWriter{spi: &mut self.spi}
53    }
54
55    /// Starts writing the RGB lookup table (see the ST7735S datasheet
56    /// sec 9.18).
57    ///
58    /// The returned object can be used to actually do the memory
59    /// writing. The user is expected to write exactly 128 bytes, which is
60    /// **not** enforced by the library.
61    ///
62    /// The lookup table is needed when the color mode
63    /// (see [colmod()](Self::colmod))
64    /// is *not* [Colmod::R6G6B6].
65    #[inline(always)]
66    pub async fn rgbset(&mut self) -> RamWriter<'_, S> {
67        self.command(0x2D).await;
68        self.spi.set_dcx_data_mode();
69        // `RamWriter::drop()` will restore to command mode.
70        RamWriter{spi: &mut self.spi}
71    }
72
73    /// Sets the partial area address window as `begin` to `end`, both
74    /// inclusive.
75    #[inline(always)]
76    pub async fn ptlar(&mut self, begin: u16, end: u16) {
77        self.command_with_u16_pair(0x30, begin, end).await;
78    }
79
80    /// Sets the scroll area address windows.
81    #[inline(always)]
82    pub async fn scrlar(&mut self, top: u16, visible: u16, bottom: u16) {
83        let data = [
84            (top >> 8) as u8, (top & 0xFF) as u8,
85            (visible >> 8) as u8, (visible & 0xFF) as u8,
86            (bottom >> 8) as u8, (bottom & 0xFF) as u8,
87        ];
88        self.command_with_u8s(0x33, &data).await;
89    }
90
91    // Performance-critical enough to have its instantiated version.
92    async fn command_with_u16_pair(
93            &mut self, cmd: u8, first: u16, second: u16) {
94        self.command(cmd).await;
95        self.spi.set_dcx_data_mode();
96        let data = [(first >> 8) as u8, (first & 0xFF) as u8,
97                    (second >> 8) as u8, (second & 0xFF) as u8];
98        self.spi.write_u8s(&data).await;
99        self.spi.set_dcx_command_mode();
100    }
101
102    #[inline(always)]
103    async fn command_with_u8s(&mut self, cmd: u8, data: &[u8]) {
104        self.spi.write_u8(cmd).await;
105        self.spi.set_dcx_data_mode();
106        self.spi.write_u8s(data).await;
107        self.spi.set_dcx_command_mode();
108    }
109
110    #[inline(always)]
111    async fn command(&mut self, cmd: u8) {
112        self.spi.write_u8(cmd).await;
113    }
114
115    async fn command_with_u8(&mut self, cmd: u8, data: u8) {
116        self.command(cmd).await;
117        self.spi.set_dcx_data_mode();
118        self.spi.write_u8(data).await;
119        self.spi.set_dcx_command_mode();
120    }
121
122    /// Does nothing.
123    #[inline(always)]
124    pub async fn nop(&mut self) { self.command(0x00).await; }
125    /// Software-resets.
126    #[inline(always)]
127    pub async fn swreset(&mut self) { self.command(0x01).await; }
128    /// Enters the sleep mode.
129    #[inline(always)]
130    pub async fn slpin(&mut self) { self.command(0x10).await; }
131    /// Exits the sleep mode.
132    #[inline(always)]
133    pub async fn slpout(&mut self) { self.command(0x11).await; }
134    /// Enters the partial mode.
135    #[inline(always)]
136    pub async fn ptlon(&mut self) { self.command(0x12).await; }
137    /// Enters the normal mode (i.e., exits the partial mode).
138    #[inline(always)]
139    pub async fn noron(&mut self) { self.command(0x13).await; }
140    /// Disables the inversion mode.
141    #[inline(always)]
142    pub async fn invoff(&mut self) { self.command(0x20).await; }
143    /// Enables the inversion mode.
144    #[inline(always)]
145    pub async fn invon(&mut self) { self.command(0x21).await; }
146    // GAMSET skipped.
147    /// Turns the display/screen off.
148    #[inline(always)]
149    pub async fn dispoff(&mut self) { self.command(0x28).await; }
150    /// Turns the display/screen on.
151    #[inline(always)]
152    pub async fn dispon(&mut self) { self.command(0x29).await; }
153    /// Turns the tear effect line off.
154    #[inline(always)]
155    pub async fn teoff(&mut self) { self.command(0x34).await; }
156    /// Turns the tear effect line on with the given mode.
157    #[inline(always)]
158    pub async fn teon(&mut self, te_mode: bool) {
159        self.command_with_u8(0x35, if te_mode {1} else {0}).await; }
160    /// Sets the MADCTL register.
161    #[inline(always)]
162    pub async fn madctl(&mut self, data: Madctl) {
163        self.command_with_u8(0x36, data.into()).await; }
164    // VSCSAD skipped.
165    /// Turns the idle mode off, i.e., enables the full color mode.
166    #[inline(always)]
167    pub async fn idmoff(&mut self) { self.command(0x38).await; }
168    /// Turns the idle mode on, i.e., enables the 8-color mode.
169    #[inline(always)]
170    pub async fn idmon(&mut self) { self.command(0x39).await; }
171    /// Sets the color mode, i.e., how many bits of the R, G and B components
172    /// have.
173    #[inline(always)]
174    pub async fn colmod(&mut self, data: Colmod) {
175        self.command_with_u8(0x3A, data.into()).await; }
176
177    // Panel functions skipped.
178}
179
180/// A helper RAII object for writing *data* after a *command*.
181#[derive(Debug)]
182pub struct RamWriter<'s, S: DcxPin> { spi: &'s mut S }
183
184impl<'s, S: DcxPin> Drop for RamWriter<'s, S> {
185    fn drop(&mut self) { self.spi.set_dcx_command_mode(); }
186}
187
188impl<'a, 's, S: DcxPin + WriteU8<'a>> WriteU8<'a> for RamWriter<'s, S> {
189    type WriteU8Done = <S as WriteU8<'a>>::WriteU8Done;
190
191    fn write_u8(&'a mut self, data: u8) -> Self::WriteU8Done {
192        self.spi.write_u8(data)
193    }
194}
195
196impl<'a, 's, S: DcxPin + WriteU8s<'a>> WriteU8s<'a> for RamWriter<'s, S> {
197    type WriteU8sDone = <S as WriteU8s<'a>>::WriteU8sDone;
198
199    fn write_u8s(&'a mut self, data: &'a [u8]) -> Self::WriteU8sDone {
200        self.spi.write_u8s(data)
201    }
202}
203
204impl<S> Commands<S> where S: DcxPin,
205                          for<'a> S: WriteU8<'a> + Read<'a> {
206    async fn read_command(&mut self, cmd: u8, num_bits: usize) -> u32 {
207        self.spi.write_u8(cmd).await;
208        let mut r = self.spi.start_reading();
209        r.read_bits(num_bits).await
210    }
211
212    // RD* (except RDDID and RDID*) skipped.
213    // RAMRD skipped.
214
215    /// Reads `ID1`, `ID2` and `ID3` of the screen with a single command.
216    #[inline(always)]
217    pub async fn rddid(&mut self) -> [u8; 3] {
218        let r = self.read_command(0x04, 25).await;
219        [(r >> 16) as u8, (r >> 8 & 0xFF) as u8, (r & 0xFF) as u8]
220    }
221
222    /// Reads `ID1`, i.e., the manufacturer ID. Unless reprogrammed, the value
223    /// should be 0x7C (decimal 124).
224    #[inline(always)]
225    pub async fn rdid1(&mut self) -> u8 {
226        self.read_command(0xDA, 8).await as u8
227    }
228
229    /// Reads `ID2`' i.e., the LCD's "module/driver version ID". The highest
230    /// bit is always 1.
231    #[inline(always)]
232    pub async fn rdid2(&mut self) -> u8 {
233        self.read_command(0xDB, 8).await as u8
234    }
235
236    /// Reads `ID3`, i.e., the LCD's "module/driver ID".
237    #[inline(always)]
238    pub async fn rdid3(&mut self) -> u8 {
239        self.read_command(0xDC, 8).await as u8
240    }
241}
242
243#[cfg(test)]
244mod tests {
245    use mockall::{predicate, Sequence};
246
247    use crate::testing_device::{block_on, MockDevice, MockPlainIO};
248    use super::*;
249
250    macro_rules! test_simple_write_with_name {
251        ($name:tt, $fn:tt $args:tt, code: $code:expr, data: $data:expr) => {
252            #[test]
253            fn $name() {
254                let mut cmds = create_mock();
255                cmds.spi.expect_standard_write_command($code, $data);
256                block_on(cmds.$fn$args);
257            }
258        };
259    }
260    macro_rules! test_simple_write {
261        ($fn:tt $args:tt, code: $code:expr, data: $data:expr) => {
262            test_simple_write_with_name!(
263                $fn, $fn $args, code: $code, data: $data);
264        };
265    }
266
267    test_simple_write!(nop(), code: 0x00, data: &[]);
268    test_simple_write!(swreset(), code: 0x01, data: &[]);
269    test_simple_write!(slpin(), code: 0x10, data: &[]);
270    test_simple_write!(slpout(), code: 0x11, data: &[]);
271    test_simple_write!(ptlon(), code: 0x12, data: &[]);
272    test_simple_write!(noron(), code: 0x13, data: &[]);
273    test_simple_write!(invoff(), code: 0x20, data: &[]);
274    test_simple_write!(invon(), code: 0x21, data: &[]);
275    // GAMSET (26h) skipped.
276    test_simple_write!(dispoff(), code: 0x28, data: &[]);
277    test_simple_write!(dispon(), code: 0x29, data: &[]);
278    test_simple_write!(caset(0x1234, 0x5678), code: 0x2A,
279                       data: &[0x12, 0x34, 0x56, 0x78]);
280    test_simple_write!(raset(0x9876, 0x5432), code: 0x2B,
281                       data: &[0x98, 0x76, 0x54, 0x32]);
282    #[test]
283    fn ramwr() {
284        let mut cmds = create_mock();
285        cmds.spi.expect_standard_write_command(
286            0x2C, &[0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD]);
287        block_on(async {
288            let mut rw = cmds.ramwr().await;
289            rw.write_u8(0x01).await;
290            rw.write_u8s(&[0x23, 0x45]).await;
291            rw.write_u8s(&[]).await;
292            rw.write_u8s(&[0x67, 0x89, 0xAB, 0xCD]).await;
293        });
294    }
295    #[test]
296    fn rgbset() {
297        let mut cmds = create_mock();
298        cmds.spi.expect_standard_write_command(0x2D, &[0x35; 128]);
299        block_on(async {
300            let mut rw = cmds.rgbset().await;
301            rw.write_u8(0x35).await;
302            rw.write_u8s(&[0x35; 27]).await;
303            rw.write_u8s(&[0x35; 50]).await;
304            rw.write_u8s(&[0x35; 50]).await;
305        });
306    }
307    test_simple_write!(ptlar(0x1357, 0x2468), code: 0x30,
308                       data: &[0x13, 0x57, 0x24, 0x68]);
309    test_simple_write!(scrlar(0x2143, 0x3254, 0x4365), code: 0x33,
310                       data: &[0x21, 0x43, 0x32, 0x54, 0x43, 0x65]);
311    test_simple_write!(teoff(), code: 0x34, data: &[]);
312    #[test]
313    fn teon_mode0() {
314        let mut cmds = create_mock();
315        cmds.spi.expect_standard_write_command(0x35, &[0x00]);
316        block_on(cmds.teon(false));
317    }
318    #[test]
319    fn teon_mode1() {
320        let mut cmds = create_mock();
321        cmds.spi.expect_standard_write_command(0x35, &[0x01]);
322        block_on(cmds.teon(true));
323    }
324    #[test]
325    fn madctl_test0() {
326        use crate::command_structs::{
327            Madctl, RowOrder, ColumnOrder, RowColumnSwap, ColorComponentOrder};
328        let mut mctl = Madctl::default();
329        mctl.set_row_address_order(RowOrder::TopToBottom)
330            .set_column_address_order(ColumnOrder::LeftToRight)
331            .set_row_column_swap(RowColumnSwap::Swapped)
332            .set_vertical_refresh_order(RowOrder::BottomToTop)
333            .set_horizontal_refresh_order(ColumnOrder::RightToLeft)
334            .set_rgb_order(ColorComponentOrder::BlueGreenRed);
335
336        let mut cmds = create_mock();
337        cmds.spi.expect_standard_write_command(0x36, &[0xC0]);
338        block_on(cmds.madctl(mctl));
339    }
340    #[test]
341    fn madctl_test1() {
342        use crate::command_structs::{
343            Madctl, RowOrder, ColumnOrder, RowColumnSwap, ColorComponentOrder};
344        let mut mctl = Madctl::default();
345        mctl.set_row_address_order(RowOrder::BottomToTop)
346            .set_column_address_order(ColumnOrder::RightToLeft)
347            .set_row_column_swap(RowColumnSwap::Unswapped)
348            .set_vertical_refresh_order(RowOrder::TopToBottom)
349            .set_horizontal_refresh_order(ColumnOrder::LeftToRight)
350            .set_rgb_order(ColorComponentOrder::RedGreenBlue);
351
352        let mut cmds = create_mock();
353        cmds.spi.expect_standard_write_command(0x36, &[0x3C]);
354        block_on(cmds.madctl(mctl));
355    }
356    // VSCSAD skipped.
357    test_simple_write!(idmoff(), code: 0x38, data: &[]);
358    test_simple_write!(idmon(), code: 0x39, data: &[]);
359    test_simple_write_with_name!(colmod_r4g4b4, colmod(Colmod::R4G4B4),
360                                 code: 0x3A, data: &[0b011]);
361    test_simple_write_with_name!(colmod_r5g6b5, colmod(Colmod::R5G6B5),
362                                 code: 0x3A, data: &[0b101]);
363    test_simple_write_with_name!(colmod_r6g6b6, colmod(Colmod::R6G6B6),
364                                 code: 0x3A, data: &[0b110]);
365
366    // Panel functions skipped.
367
368    impl Commands<MockDevice> {
369        fn mock(&mut self) -> &mut MockPlainIO {
370            self.spi.mock()
371        }
372    }
373
374    fn create_mock() -> Commands<MockDevice> {
375        Commands::new(Default::default())
376    }
377
378    fn set_read_command_expectations(
379            mock: &mut MockPlainIO, code: u8, bits: &str) {
380        let mut seq = Sequence::new();
381        mock.expect_write_command()
382            .with(predicate::eq(code))
383            .times(1)
384            .in_sequence(&mut seq);
385        mock.expect_start_reading()
386            .times(1)
387            .in_sequence(&mut seq);
388        for c in bits.chars() {
389            mock.expect_read_bit()
390                .times(1)
391                .in_sequence(&mut seq)
392                .returning(move || c != '0');
393        }
394        mock.expect_finish_reading()
395            .times(1)
396            .in_sequence(&mut seq);
397    }
398
399    #[test]
400    fn rdid1() {
401        let mut cmds = create_mock();
402        const DATA: u8 = 0b10100110;
403        set_read_command_expectations(
404                cmds.mock(), 0xDA, &std::format!("{:08b}", DATA));
405        let v = block_on(cmds.rdid1());
406        assert_eq!(v, DATA);
407    }
408
409    #[test]
410    fn rdid2() {
411        let mut cmds = create_mock();
412        const DATA: u8 = 0b01010111;
413        set_read_command_expectations(
414                cmds.mock(), 0xDB, &std::format!("{:08b}", DATA));
415        let v = block_on(cmds.rdid2());
416        assert_eq!(v, DATA);
417    }
418
419    #[test]
420    fn rdid3() {
421        let mut cmds = create_mock();
422        const DATA: u8 = 0b01100111;
423        set_read_command_expectations(
424                cmds.mock(), 0xDC, &std::format!("{:08b}", DATA));
425        let v = block_on(cmds.rdid3());
426        assert_eq!(v, DATA);
427    }
428
429    #[test]
430    fn rddid() {
431        let mut cmds = create_mock();
432        const DATA_U32: u32 = 0b0_11110000_11010010_01100001;
433        const DATA_ARR: [u8; 3] = [0b11110000, 0b11010010, 0b01100001];
434        set_read_command_expectations(
435                cmds.mock(), 0x04, &std::format!("{:25b}", DATA_U32));
436        let v = block_on(cmds.rddid());
437        assert_eq!(v, DATA_ARR);
438    }
439
440}  // mod tests