lcd1602_driver/sender/
i2c_sender.rs

1//! # I2C adapter board driver
2//!
3//! This adapter board has a I2C interface to MCU,  
4//! and it's 8 bi-directional pin P7 to P0 is attach to following pin of LCD1602
5//!
6//! P7 -> P0  
7//! DB7/DB6/DB5/DB4/BL/CS(EN)/RW/RS
8//!
9//! Since there are only 4 pin for DB pin, so it only support 4 bit data width command.
10//!
11//! LCD Command is first splitted into 4-bit length command(s), then appending BL/CS(EN)/RW/RS bits to [`I2cCommand`].  
12//! [`I2cCommand`] containers a one or two u8 data, depending on original LCD Command length.  
13//! But [`I2cCommand`] is still not suitable for controlling LCD, one u8 data should be send as ENABLE pin "disable" - "enable" - "disable" sequence,  
14//! to make sure LCD read our command precisely.  
15//! Thus [`I2cCommand`] will further convert to a three u8 data sequence or six u8 data sequence [`I2cRawSeq`] .
16
17use embedded_hal::{
18    delay::DelayNs,
19    i2c::{AddressMode, I2c},
20};
21
22use crate::{
23    command::{Bits, Command, ReadWriteOp, RegisterSelection, State},
24    utils::{BitOps, BitState},
25};
26
27use super::SendCommand;
28
29/// [`I2cSender`] is the I2C interface with an adapter board to drive LCD1602
30pub struct I2cSender<'a, I2cLcd: I2c<A>, A: AddressMode + Clone> {
31    i2c: &'a mut I2cLcd,
32    addr: A,
33    first_command: bool,
34    backlight_state: State,
35}
36
37impl<'a, I2cLcd: I2c<A>, A: AddressMode + Clone> I2cSender<'a, I2cLcd, A> {
38    /// Create a [`I2cSender`] driver
39    pub fn new(i2c: &'a mut I2cLcd, addr: A) -> Self {
40        Self {
41            i2c,
42            addr,
43            first_command: true,
44            backlight_state: State::Off,
45        }
46    }
47}
48
49impl<'a, I2cLcd, A, Delayer> SendCommand<Delayer, true> for I2cSender<'a, I2cLcd, A>
50where
51    I2cLcd: I2c<A>,
52    A: AddressMode + Clone,
53    Delayer: DelayNs,
54{
55    // set backlight should ALWAYS keep ENABLE pin at low voltage state
56    fn set_actual_backlight(&mut self, state: State) {
57        // PCF8574T use weak pull up as high voltage,
58        // thus only ENABLE pin should be strictly set to low voltage.
59        let mut i2c_raw_seq: u8 = 0b1111_1011;
60
61        match state {
62            State::On => i2c_raw_seq.set_bit(3),
63            State::Off => i2c_raw_seq.clear_bit(3),
64        };
65
66        self.i2c.write(self.addr.clone(), &[i2c_raw_seq]).unwrap();
67        self.backlight_state = state;
68    }
69
70    fn get_actual_backlight(&mut self) -> State {
71        let mut buf = [0u8];
72        // just a read is sufficient get backlight state
73        self.i2c.read(self.addr.clone(), &mut buf).unwrap();
74        match buf[0].check_bit(3) {
75            BitState::Clear => State::Off,
76            BitState::Set => State::On,
77        }
78    }
79
80    fn send(&mut self, lcd_command: Command) -> Option<u8> {
81        if self.first_command {
82            assert!(
83                lcd_command.get_data().is_some(),
84                "first command should has some data to write"
85            );
86
87            match lcd_command.get_data().unwrap() {
88                Bits::Bit8(_) => panic!("first command should be 4 bit"),
89
90                Bits::Bit4(_) => {
91                    let i2c_command = I2cCommand::gen_i2c_cmd(lcd_command, self.backlight_state);
92
93                    assert!(
94                        i2c_command.0 == 0b0010_0000 || i2c_command.0 == 0b0010_1000,
95                        "first command should be Function set, and should set to 4 bit mode"
96                    );
97
98                    let I2cRawSeq(len, raw_seq) = i2c_command.into();
99
100                    self.i2c
101                        .write(self.addr.clone(), &raw_seq[0..len as usize])
102                        .unwrap();
103                }
104            }
105
106            self.first_command = false;
107        } else {
108            // if not first command, then all command should have 8 bit length
109            // though we send it as 4 bit per group
110            if let Some(Bits::Bit4(_)) = lcd_command.get_data() {
111                panic!("Only first command is 4 bit long, other command should be 8 bit long")
112            }
113
114            match lcd_command.get_read_write_op() {
115                ReadWriteOp::Write => {
116                    assert!(
117                        lcd_command.get_data().is_some(),
118                        "first command should has some data to write"
119                    );
120
121                    if lcd_command.get_register_selection() == RegisterSelection::Command {
122                        match lcd_command.get_data().unwrap() {
123                            Bits::Bit8(lcd_command_data) => {
124                                if (lcd_command_data >> 4) == 0b0011 {
125                                    panic!("This I2C driver doesn't support 8 bit Data Width Mode")
126                                }
127                            }
128                            _ => unreachable!(),
129                        }
130                    }
131
132                    let i2c_command = I2cCommand::gen_i2c_cmd(lcd_command, self.backlight_state);
133                    let I2cRawSeq(len, raw_seq) = i2c_command.into();
134                    self.i2c
135                        .write(self.addr.clone(), &raw_seq[0..len as usize])
136                        .unwrap();
137                }
138
139                ReadWriteOp::Read => {
140                    let mut concat_buf = [0u8; 2];
141                    let mut buf = [0u8];
142
143                    let i2c_command = I2cCommand::gen_i2c_cmd(lcd_command, self.backlight_state);
144                    let I2cRawSeq(len, raw_seq) = i2c_command.into();
145
146                    assert_eq!(
147                        len, 6,
148                        "Read command will always need to send 6 I2C sequence"
149                    );
150
151                    // The following read-write step is
152                    //
153                    // Send the command ("disable" - "enable", and keep "enable" state), but not sending ending "disable", read the first 4-bit data from I2C.
154                    // Send the ending "disable" of the command, then start to sending the command again, to fetch last 4-bit data from I2C.
155                    // And finally send the "disable" command to end entire seq.
156
157                    self.i2c
158                        .write_read(self.addr.clone(), &raw_seq[0..2], &mut buf)
159                        .unwrap();
160                    concat_buf[0] = buf[0];
161
162                    self.i2c
163                        .write_read(self.addr.clone(), &raw_seq[2..5], &mut buf)
164                        .unwrap();
165                    self.i2c.write(self.addr.clone(), &raw_seq[5..6]).unwrap();
166                    concat_buf[1] = buf[0];
167
168                    // Combine 2 halves of data into a whole one.
169                    return Some((concat_buf[0] & 0b1111_0000) | (concat_buf[1] >> 4));
170                }
171            };
172        }
173
174        None
175    }
176}
177
178// I2cCommand is the command that convert from LCD1602 Command to I2C adapter board command.
179//
180// The first I2C data is always used.(thus a u8)
181// In most time the second I2C data is used, only the first command sent to LCD doesn't have the second part.(thus a Option<u8>)
182struct I2cCommand(u8, Option<u8>);
183
184impl Default for I2cCommand {
185    fn default() -> Self {
186        Self(0, Some(0))
187    }
188}
189
190impl I2cCommand {
191    fn gen_i2c_cmd(lcd_command: Command, backlight_state: State) -> Self {
192        let mut i2c_raw_data = I2cCommand::default();
193        let i2c_raw_data_1_inner = i2c_raw_data.1.as_mut().unwrap();
194
195        if lcd_command.get_register_selection() == RegisterSelection::Data {
196            i2c_raw_data.0.set_bit(0);
197            i2c_raw_data_1_inner.set_bit(0);
198        }
199
200        // will need to set bit of backlight controlling
201        match backlight_state {
202            State::Off => {
203                //i2c_raw_data.0 &= 0b1111_0111;
204                //*i2c_raw_data_1_inner &= 0b1111_0111;
205                i2c_raw_data.0.clear_bit(3);
206                i2c_raw_data_1_inner.clear_bit(3);
207            }
208            State::On => {
209                //i2c_raw_data.0 |= 0b0000_1000;
210                //*i2c_raw_data_1_inner &= 0b0000_1000;
211                i2c_raw_data.0.set_bit(3);
212                i2c_raw_data_1_inner.set_bit(3);
213            }
214        }
215
216        match lcd_command.get_read_write_op() {
217            ReadWriteOp::Write => match lcd_command.get_data() {
218                None => panic!("Write command should have some data to be send"),
219                Some(command_data) => match command_data {
220                    Bits::Bit4(raw_data) => {
221                        assert!(raw_data < (1 << 4), "data is overflow 4 bit");
222                        i2c_raw_data.0 |= raw_data << 4;
223                        i2c_raw_data.1 = None;
224                    }
225                    Bits::Bit8(raw_data) => {
226                        i2c_raw_data.0 |= raw_data & 0b1111_0000;
227                        *i2c_raw_data_1_inner |= (raw_data & 0b0000_1111) << 4
228                    }
229                },
230            },
231            ReadWriteOp::Read => {
232                // A Read will need to send same LCD command twice
233                i2c_raw_data.0.set_bit(1);
234                i2c_raw_data.0 |= 0b1111 << 4; // This actually makes P4~P7 of PCF8574 weak pull up, to read data in
235
236                i2c_raw_data_1_inner.set_bit(1);
237                *i2c_raw_data_1_inner |= 0b1111 << 4; // This actually makes P4~P7 of PCF8574 weak pull up, to read data in
238            }
239        }
240
241        i2c_raw_data
242    }
243}
244
245// This is the actual I2C sequence will be sending by us.
246struct I2cRawSeq(u8, [u8; 6]);
247
248impl From<I2cCommand> for I2cRawSeq {
249    // split each part of I2cCommand into "disable" - "enable" - "disable" seq
250    fn from(i2c_command: I2cCommand) -> Self {
251        let mut seq = [0u8; 6];
252        let mut len = 3;
253
254        let mut disable_0 = i2c_command.0;
255        disable_0.clear_bit(2);
256        let mut enable_0 = disable_0;
257        enable_0.set_bit(2);
258
259        seq[0] = disable_0;
260        seq[1] = enable_0;
261        seq[2] = disable_0;
262
263        if i2c_command.1.is_some() {
264            let mut disable_1 = i2c_command.1.unwrap();
265            disable_1.clear_bit(2);
266            let mut enable_1 = disable_1;
267            enable_1.set_bit(2);
268
269            seq[3] = disable_1;
270            seq[4] = enable_1;
271            seq[5] = disable_1;
272
273            len = 6;
274        }
275
276        Self(len, seq)
277    }
278}