capsules_extra/isl29035.rs
1// Licensed under the Apache License, Version 2.0 or the MIT License.
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3// Copyright Tock Contributors 2022.
4
5//! SyscallDriver for the ISL29035 digital light sensor.
6//!
7//! <http://bit.ly/2rA00cH>
8//!
9//! > The ISL29035 is an integrated ambient and infrared light-to-digital
10//! > converter with I2C (SMBus compatible) Interface. Its advanced self-
11//! > calibrated photodiode array emulates human eye response with excellent IR
12//! > rejection. The on-chip ADC is capable of rejecting 50Hz and 60Hz flicker
13//! > caused by artificial light sources. The Lux range select feature allows
14//! > users to program the Lux range for optimized counts/Lux.
15//!
16//! Usage
17//! -----
18//!
19//! ```rust,ignore
20//! # use kernel::static_init;
21//! # use capsules::virtual_alarm::VirtualMuxAlarm;
22//!
23//! let isl29035_i2c = static_init!(I2CDevice, I2CDevice::new(i2c_bus, 0x44));
24//! let isl29035_virtual_alarm = static_init!(
25//! VirtualMuxAlarm<'static, sam4l::ast::Ast>,
26//! VirtualMuxAlarm::new(mux_alarm));
27//! isl29035_virtual_alarm.setup();
28//!
29//! let isl29035 = static_init!(
30//! capsules::isl29035::Isl29035<'static, VirtualMuxAlarm<'static, sam4l::ast::Ast>>,
31//! capsules::isl29035::Isl29035::new(isl29035_i2c, isl29035_virtual_alarm,
32//! &mut capsules::isl29035::BUF));
33//! isl29035_i2c.set_client(isl29035);
34//! isl29035_virtual_alarm.set_client(isl29035);
35//! ```
36
37use core::cell::Cell;
38use kernel::hil::i2c::{Error, I2CClient, I2CDevice};
39use kernel::hil::sensors::{AmbientLight, AmbientLightClient};
40use kernel::hil::time::{self, ConvertTicks};
41use kernel::utilities::cells::{OptionalCell, TakeCell};
42use kernel::ErrorCode;
43
44/// Recommended buffer length.
45pub const BUF_LEN: usize = 3;
46
47#[derive(Copy, Clone, PartialEq)]
48enum State {
49 Disabled,
50 Enabling,
51 Integrating,
52 ReadingLI,
53 Disabling(usize),
54}
55
56pub struct Isl29035<'a, A: time::Alarm<'a>> {
57 i2c: &'a dyn I2CDevice,
58 alarm: &'a A,
59 state: Cell<State>,
60 buffer: TakeCell<'static, [u8]>,
61 client: OptionalCell<&'a dyn AmbientLightClient>,
62}
63
64impl<'a, A: time::Alarm<'a>> Isl29035<'a, A> {
65 pub fn new(i2c: &'a dyn I2CDevice, alarm: &'a A, buffer: &'static mut [u8]) -> Isl29035<'a, A> {
66 Isl29035 {
67 i2c,
68 alarm,
69 state: Cell::new(State::Disabled),
70 buffer: TakeCell::new(buffer),
71 client: OptionalCell::empty(),
72 }
73 }
74
75 pub fn start_read_lux(&self) -> Result<(), ErrorCode> {
76 if self.state.get() == State::Disabled {
77 self.buffer.take().map_or(Err(ErrorCode::NOMEM), |buf| {
78 self.i2c.enable();
79 buf[0] = 0;
80 // CMD 1 Register:
81 // Interrupt persist for 1 integration cycle (bits 0 & 1)
82 // Measure ALS continuously (buts 5,6 & 7)
83 // Bit 2 is the interrupt bit
84 // Bits 3 & 4 are reserved
85 buf[1] = 0b10100000;
86
87 // CMD 2 Register:
88 // Range 4000 (bits 0, 1)
89 // ADC resolution 8-bit (bits 2,3)
90 // Other bits are reserved
91 buf[2] = 0b00001001;
92
93 if let Err((error, buf)) = self.i2c.write(buf, 3) {
94 self.buffer.replace(buf);
95 self.i2c.disable();
96 Err(error.into())
97 } else {
98 self.state.set(State::Enabling);
99 Ok(())
100 }
101 })
102 } else {
103 Err(ErrorCode::BUSY)
104 }
105 }
106}
107
108impl<'a, A: time::Alarm<'a>> AmbientLight<'a> for Isl29035<'a, A> {
109 fn set_client(&self, client: &'a dyn AmbientLightClient) {
110 self.client.set(client)
111 }
112
113 fn read_light_intensity(&self) -> Result<(), ErrorCode> {
114 self.start_read_lux()
115 }
116}
117
118impl<'a, A: time::Alarm<'a>> time::AlarmClient for Isl29035<'a, A> {
119 fn alarm(&self) {
120 self.buffer.take().map(|buffer| {
121 // Turn on i2c to send commands.
122 self.i2c.enable();
123
124 buffer[0] = 0x02_u8;
125 if let Err((_error, buf)) = self.i2c.write_read(buffer, 1, 2) {
126 self.buffer.replace(buf);
127 self.i2c.disable();
128 self.state.set(State::Disabled);
129 self.client.map(|client| client.callback(0));
130 } else {
131 self.state.set(State::ReadingLI);
132 }
133 });
134 }
135}
136
137impl<'a, A: time::Alarm<'a>> I2CClient for Isl29035<'a, A> {
138 fn command_complete(&self, buffer: &'static mut [u8], status: Result<(), Error>) {
139 if status.is_err() {
140 self.state.set(State::Disabled);
141 self.buffer.replace(buffer);
142 self.client.map(|client| client.callback(0));
143 return;
144 }
145 match self.state.get() {
146 State::Enabling => {
147 // Set a timer to wait for the conversion to be done.
148 // For 8 bits, thats 410 us (per Table 11 in the datasheet).
149 let interval = self.alarm.ticks_from_us(410);
150 self.alarm.set_alarm(self.alarm.now(), interval);
151
152 // Now wait for timer to expire
153 self.buffer.replace(buffer);
154 self.i2c.disable();
155 self.state.set(State::Integrating);
156 }
157 State::ReadingLI => {
158 // During configuration we set the ADC resolution to 8 bits and
159 // the range to 4000.
160 //
161 // Since it's only 8 bits, we ignore the second byte of output.
162 //
163 // For a given Range and n (-bits of ADC resolution):
164 // Lux = Data * (Range / 2^n)
165 let data = buffer[0] as usize; //((buffer[1] as usize) << 8) | buffer[0] as usize;
166 let lux = (data * 4000) >> 8;
167
168 buffer[0] = 0;
169
170 if let Err((_error, buffer)) = self.i2c.write(buffer, 2) {
171 self.state.set(State::Disabled);
172 self.buffer.replace(buffer);
173 self.client.map(|client| client.callback(0));
174 } else {
175 self.state.set(State::Disabling(lux));
176 }
177 }
178 State::Disabling(lux) => {
179 self.i2c.disable();
180 self.state.set(State::Disabled);
181 self.buffer.replace(buffer);
182 self.client.map(|client| client.callback(lux));
183 }
184 _ => {}
185 }
186 }
187}