1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
#![no_std]
#![doc = include_str!("../README.md")]
/// Preconfigured devices
pub mod devices;
use embedded_hal::blocking::delay::DelayMs;
use embedded_hal::blocking::i2c::Read;
use embedded_hal::blocking::i2c::Write;

/// A struct to integrate with a new IS31FL3743A powered device.
pub struct IS31FL3743<I2C> {
    /// The i2c bus that is used to interact with the device. See implementation below for the
    /// trait methods required.
    pub i2c: I2C,
    /// The 7-bit i2c slave address of the device. By default on most devices this is `0x74`.
    pub address: u8,
    /// Width of the LED matrix
    pub width: u8,
    /// Height of the LED matrix
    pub height: u8,
    /// Method to convert an x,y coordinate pair to a binary address that can be accessed using the
    /// bus.
    pub calc_pixel: fn(x: u8, y: u8) -> u8,
}

impl<I2C, I2cError> IS31FL3743<I2C>
where
    I2C: Write<Error = I2cError>,
    I2C: Read<Error = I2cError>,
{
    /// Fill all pixels of the display at once. The brightness should range from 0 to 255.
    /// brightness slice must have 0xC5 elements
    pub fn fill_matrix(&mut self, brightnesses: &[u8]) -> Result<(), I2cError> {
        // Extend by one, to add address to the beginning
        let mut buf = [0x00; 0xC7];
        buf[0] = 0x00; // set the initial address
        buf[1..=0xC6].copy_from_slice(brightnesses);
        self.bank(Page::Pwm)?;
        self.write(&buf)?;
        Ok(())
    }

    /// Fill the display with a single brightness. The brightness should range from 0 to 255.
    pub fn fill(&mut self, brightness: u8) -> Result<(), I2cError> {
        self.bank(Page::Pwm)?;
        let mut buf = [brightness; 0xC7];
        buf[0] = 0x00; // set the initial address
        self.write(&buf)?;
        Ok(())
    }

    /// Setup the display. Should be called before interacting with the device to ensure proper
    /// functionality. Delay is something that your device's HAL should provide which allows for
    /// the process to sleep for a certain amount of time (in this case 10 MS to perform a reset).
    ///
    /// When you run this function the following steps will occur:
    /// 1. The chip will be told that it's being "reset".
    /// 2. The chip will be put in shutdown mode
    /// 3. The chip will be configured to use the maximum voltage
    /// 4. The chip will be taken out of shutdown mode
    pub fn setup<DEL: DelayMs<u8>>(&mut self, delay: &mut DEL) -> Result<(), Error<I2cError>> {
        self.reset(delay)?;
        self.shutdown(true)?;
        delay.delay_ms(10);
        // maximum current limiting
        self.write_register(Page::Config, addresses::CURRENT_REGISTER, 0xFF)?;
        self.shutdown(false)?;
        Ok(())
    }
    /// Set the brightness at a specific x,y coordinate. Just like the [fill method](Self::fill)
    /// the brightness should range from 0 to 255. If the coordinate is out of range then the
    /// function will return an error of [InvalidLocation](Error::InvalidLocation).
    pub fn pixel(&mut self, x: u8, y: u8, brightness: u8) -> Result<(), Error<I2cError>> {
        if x > self.width {
            return Err(Error::InvalidLocation(x));
        }
        if y > self.height {
            return Err(Error::InvalidLocation(y));
        }
        let pixel = (self.calc_pixel)(x, y);
        self.write_register(Page::Pwm, pixel, brightness)?;
        Ok(())
    }

    /// Change the slave address to a new 7-bit address. Should be configured before calling
    /// [setup](Self::setup) method.
    pub fn set_address(&mut self, address: u8) {
        self.address = address;
    }

    /// Send a reset message to the slave device. Delay is something that your device's HAL should
    /// provide which allows for the process to sleep for a certain amount of time (in this case 10
    /// MS to perform a reset).
    pub fn reset<DEL: DelayMs<u8>>(&mut self, delay: &mut DEL) -> Result<(), I2cError> {
        self.write_register(Page::Config, addresses::RESET_REGISTER, addresses::RESET)?;
        delay.delay_ms(10);
        Ok(())
    }

    /// Set the current available to each LED. 0 is none, 255 is the maximum available
    pub fn set_scaling(&mut self, scale: u8) -> Result<(), I2cError> {
        self.bank(Page::Scale)?;
        let mut buf = [scale; 0xC7];
        buf[0] = 0x00; // set the initial address
        self.write(&buf)?;
        Ok(())
    }

    /// Put the device into software shutdown mode
    pub fn shutdown(&mut self, yes: bool) -> Result<(), I2cError> {
        self.write_register(
            Page::Config,
            addresses::CONFIG_REGISTER,
            if yes { 0 } else { 1 },
        )?;
        Ok(())
    }

    /// How many SW rows to enable
    pub fn sw_enablement(&mut self, setting: SwSetting) -> Result<(), I2cError> {
        let config_register = self.read_register(Page::Config, addresses::CONFIG_REGISTER)?;

        let new_val = (config_register & 0x0F) | (setting as u8) << 4;
        self.write_register(Page::Config, addresses::CONFIG_REGISTER, new_val)?;
        Ok(())
    }

    fn write(&mut self, buf: &[u8]) -> Result<(), I2cError> {
        self.i2c.write(self.address, buf)
    }

    fn write_register(&mut self, bank: Page, register: u8, value: u8) -> Result<(), I2cError> {
        self.bank(bank)?;
        self.write(&[register, value])?;
        Ok(())
    }

    fn read_u8(&mut self, register: u8) -> Result<u8, I2cError> {
        let mut buf = [0x00];
        self.i2c.write(self.address, &[register])?;
        self.i2c.read(self.address, &mut buf)?;
        Ok(buf[0])
    }

    fn read_register(&mut self, bank: Page, register: u8) -> Result<u8, I2cError> {
        self.bank(bank)?;
        let value = self.read_u8(register)?;
        Ok(value)
    }

    fn bank(&mut self, bank: Page) -> Result<(), I2cError> {
        self.unlock()?;
        self.write(&[addresses::PAGE_SELECT_REGISTER, bank as u8])?;
        Ok(())
    }

    fn unlock(&mut self) -> Result<(), I2cError> {
        self.i2c.write(
            self.address,
            &[
                addresses::CONFIG_LOCK_REGISTER,
                addresses::CONFIG_WRITE_ENABLE,
            ],
        )
    }
}

/// See the [data sheet](https://lumissil.com/assets/pdf/core/IS31FL3743A_DS.pdf)
/// for more information on registers.
pub mod addresses {
    // In Page 4
    pub const CONFIG_REGISTER: u8 = 0x00;
    pub const CURRENT_REGISTER: u8 = 0x01;
    pub const PULL_UP_REGISTER: u8 = 0x02;
    pub const RESET_REGISTER: u8 = 0x2F;

    pub const PAGE_SELECT_REGISTER: u8 = 0xFD;
    pub const CONFIG_LOCK_REGISTER: u8 = 0xFE;

    pub const CONFIG_WRITE_ENABLE: u8 = 0b1100_0101;
    pub const RESET: u8 = 0xAE;
}

#[derive(Clone, Copy, Debug)]
pub enum Error<I2cError> {
    I2cError(I2cError),
    InvalidLocation(u8),
    InvalidFrame(u8),
}

impl<E> From<E> for Error<E> {
    fn from(error: E) -> Self {
        Error::I2cError(error)
    }
}

#[repr(u8)]
enum Page {
    Pwm = 0x00,
    Scale = 0x01,
    Config = 0x02,
}

#[repr(u8)]
pub enum SwSetting {
    // SW1-SW11 active
    Sw1Sw11 = 0b0000,
    // SW1-SW10 active, SW11 not active
    Sw1Sw10 = 0b0001,
    // SW1-SW7 active, SW10-SW11 not active
    Sw1Sw9 = 0b0010,
    // SW1-SW8 active, SW9-SW11 not active
    Sw1Sw8 = 0b0011,
    // SW1-SW7 active, SW8-SW11 not active
    Sw1Sw7 = 0b0100,
    // SW1-SW6 active, SW7-SW11 not active
    Sw1Sw6 = 0b0101,
    // SW1-SW5 active, SW6-SW11 not active
    Sw1Sw5 = 0b0110,
    // SW1-SW4 active, SW5-SW11 not activee
    Sw1Sw4 = 0b0111,
    // SW1-SW3 active, SW4-SW11 not active
    Sw1Sw3 = 0b1000,
    // SW1-SW2 active, SW3-SW11 not active
    Sw1Sw2 = 0b1001,
    // All CSx pins only act as current sink, no scanning
    NoScan = 0b1010,
}