capsules_extra/tsl2561.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 Taos TSL2561 light sensor.
6//!
7//! <http://www.digikey.com/product-detail/en/ams-taos-usa-inc/TSL2561FN/TSL2561-FNCT-ND/3095298>
8//!
9//! > The TSL2560 and TSL2561 are light-to-digital converters that transform
10//! > light intensity to a digital signal output capable of direct I2C
11//! > interface. Each device combines one broadband photodiode (visible plus
12//! > infrared) and one infrared-responding photodiode on a single CMOS
13//! > integrated circuit capable of providing a near-photopic response over an
14//! > effective 20-bit dynamic range (16-bit resolution). Two integrating ADCs
15//! > convert the photodiode currents to a digital output that represents the
16//! > irradiance measured on each channel. This digital output can be input to a
17//! > microprocessor where illuminance (ambient light level) in lux is derived
18//! > using an empirical formula to approximate the human eye response.
19
20use core::cell::Cell;
21
22use kernel::grant::{AllowRoCount, AllowRwCount, Grant, UpcallCount};
23use kernel::hil::gpio;
24use kernel::hil::i2c;
25use kernel::syscall::{CommandReturn, SyscallDriver};
26use kernel::utilities::cells::{OptionalCell, TakeCell};
27use kernel::{ErrorCode, ProcessId};
28
29/// Syscall driver number.
30use capsules_core::driver;
31pub const DRIVER_NUM: usize = driver::NUM::Tsl2561 as usize;
32
33// Buffer to use for I2C messages
34pub const BUFFER_LENGTH: usize = 4;
35
36/// Command register defines
37const COMMAND_REG: u8 = 0x80;
38const WORD_PROTOCOL: u8 = 0x20;
39
40/// Control_Reg defines
41const POWER_ON: u8 = 0x03;
42const POWER_OFF: u8 = 0x00;
43
44/// Timing_Reg defines
45const INTEGRATE_TIME_101_MS: u8 = 0x01;
46const LOW_GAIN_MODE: u8 = 0x00;
47
48// Interrupt_Control_Reg defines
49const INTERRUPT_CONTROL_LEVEL: u8 = 0x10;
50const INTERRUPT_ON_ADC_DONE: u8 = 0x0;
51
52// ADC counts to Lux value conversion copied from TSL2561 manual
53// −−−−------------------------------
54// Value scaling factors
55// −−−−−−−−−−−−−−−-------------------
56const LUX_SCALE: u16 = 14; // scale by 2^14
57const RATIO_SCALE: u16 = 9; // scale ratio by 2^9
58
59// −−−−−−−−−−−−−−−−−−−−−−−-----------
60// Integration time scaling factors
61// −−−−−−−−−−−−−−−−−−−−−−−−−−−−------
62const CH_SCALE: u16 = 10; // scale channel values by 2^10
63#[allow(dead_code)]
64const CHSCALE_TINT0: u16 = 0x7517; // 322/11 * 2^CH_SCALE
65const CHSCALE_TINT1: u16 = 0x0fe7; // 322/81 * 2^CH_SCALE
66
67// −−−−−−−−−−−−−−−−−−−−−−−−−−−−------
68// T, FN, and CL Package coefficients
69// −−−−−−−−−−−−−−−−−−−−−−−−−−−−------
70// For Ch1/Ch0=0.00 to 0.50
71// Lux/Ch0=0.0304−0.062*((Ch1/Ch0)^1.4)
72// piecewise approximation
73// For Ch1/Ch0=0.00 to 0.125:
74// Lux/Ch0=0.0304−0.0272*(Ch1/Ch0)
75//
76// For Ch1/Ch0=0.125 to 0.250:
77// Lux/Ch0=0.0325−0.0440*(Ch1/Ch0)
78//
79// For Ch1/Ch0=0.250 to 0.375:
80// Lux/Ch0=0.0351−0.0544*(Ch1/Ch0)
81//
82// For Ch1/Ch0=0.375 to 0.50:
83// Lux/Ch0=0.0381−0.0624*(Ch1/Ch0)
84//
85// For Ch1/Ch0=0.50 to 0.61:
86// Lux/Ch0=0.0224−0.031*(Ch1/Ch0)
87//
88// For Ch1/Ch0=0.61 to 0.80:
89// Lux/Ch0=0.0128−0.0153*(Ch1/Ch0)
90//
91// For Ch1/Ch0=0.80 to 1.30:
92// Lux/Ch0=0.00146−0.00112*(Ch1/Ch0)
93//
94// For Ch1/Ch0>1.3:
95// Lux/Ch0=0
96// −−−−−−−−−−−−−−−−−−−−−−−−−−−−------
97const K1T: usize = 0x0040; // 0.125 * 2^RATIO_SCALE
98const B1T: usize = 0x01f2; // 0.0304 * 2^LUX_SCALE
99const M1T: usize = 0x01be; // 0.0272 * 2^LUX_SCALE
100const K2T: usize = 0x0080; // 0.250 * 2^RATIO_SCALE
101const B2T: usize = 0x0214; // 0.0325 * 2^LUX_SCALE
102const M2T: usize = 0x02d1; // 0.0440 * 2^LUX_SCALE
103const K3T: usize = 0x00c0; // 0.375 * 2^RATIO_SCALE
104const B3T: usize = 0x023f; // 0.0351 * 2^LUX_SCALE
105const M3T: usize = 0x037b; // 0.0544 * 2^LUX_SCALE
106const K4T: usize = 0x0100; // 0.50 * 2^RATIO_SCALE
107const B4T: usize = 0x0270; // 0.0381 * 2^LUX_SCALE
108const M4T: usize = 0x03fe; // 0.0624 * 2^LUX_SCALE
109const K5T: usize = 0x0138; // 0.61 * 2^RATIO_SCALE
110const B5T: usize = 0x016f; // 0.0224 * 2^LUX_SCALE
111const M5T: usize = 0x01fc; // 0.0310 * 2^LUX_SCALE
112const K6T: usize = 0x019a; // 0.80 * 2^RATIO_SCALE
113const B6T: usize = 0x00d2; // 0.0128 * 2^LUX_SCALE
114const M6T: usize = 0x00fb; // 0.0153 * 2^LUX_SCALE
115const K7T: usize = 0x029a; // 1.3 * 2^RATIO_SCALE
116const B7T: usize = 0x0018; // 0.00146 * 2^LUX_SCALE
117const M7T: usize = 0x0012; // 0.00112 * 2^LUX_SCALE
118const K8T: usize = 0x029a; // 1.3 * 2^RATIO_SCALE
119const B8T: usize = 0x0000; // 0.000 * 2^LUX_SCALE
120const M8T: usize = 0x0000; // 0.000 * 2^LUX_SCALE
121
122// −−−−−−−−−−−−−−−−−−−−−−−−−−−−------
123// CS package coefficients
124// −−−−−−−−−−−−−−−−−−−−−−−−−−−−------
125// For 0 <= Ch1/Ch0 <= 0.52
126// Lux/Ch0 = 0.0315−0.0593*((Ch1/Ch0)^1.4)
127// piecewise approximation
128// For 0 <= Ch1/Ch0 <= 0.13
129// Lux/Ch0 = 0.0315−0.0262*(Ch1/Ch0)
130// For 0.13 <= Ch1/Ch0 <= 0.26
131// Lux/Ch0 = 0.0337−0.0430*(Ch1/Ch0)
132// For 0.26 <= Ch1/Ch0 <= 0.39
133// Lux/Ch0 = 0.0363−0.0529*(Ch1/Ch0)
134// For 0.39 <= Ch1/Ch0 <= 0.52
135// Lux/Ch0 = 0.0392−0.0605*(Ch1/Ch0)
136// For 0.52 < Ch1/Ch0 <= 0.65
137// Lux/Ch0 = 0.0229−0.0291*(Ch1/Ch0)
138// For 0.65 < Ch1/Ch0 <= 0.80
139// Lux/Ch0 = 0.00157−0.00180*(Ch1/Ch0)
140// For 0.80 < Ch1/Ch0 <= 1.30
141// Lux/Ch0 = 0.00338−0.00260*(Ch1/Ch0)
142// For Ch1/Ch0 > 1.30
143// Lux = 0
144// −−−−−−−−−−−−−−−−−−−−−−−−−−−−------
145// const K1C: usize = 0x0043; // 0.130 * 2^RATIO_SCALE
146// const B1C: usize = 0x0204; // 0.0315 * 2^LUX_SCALE
147// const M1C: usize = 0x01ad; // 0.0262 * 2^LUX_SCALE
148// const K2C: usize = 0x0085; // 0.260 * 2^RATIO_SCALE
149// const B2C: usize = 0x0228; // 0.0337 * 2^LUX_SCALE
150// const M2C: usize = 0x02c1; // 0.0430 * 2^LUX_SCALE
151// const K3C: usize = 0x00c8; // 0.390 * 2^RATIO_SCALE
152// const B3C: usize = 0x0253; // 0.0363 * 2^LUX_SCALE
153// const M3C: usize = 0x0363; // 0.0529 * 2^LUX_SCALE
154// const K4C: usize = 0x010a; // 0.520 * 2^RATIO_SCALE
155// const B4C: usize = 0x0282; // 0.0392 * 2^LUX_SCALE
156// const M4C: usize = 0x03df; // 0.0605 * 2^LUX_SCALE
157// const K5C: usize = 0x014d; // 0.65 * 2^RATIO_SCALE
158// const B5C: usize = 0x0177; // 0.0229 * 2^LUX_SCALE
159// const M5C: usize = 0x01dd; // 0.0291 * 2^LUX_SCALE
160// const K6C: usize = 0x019a; // 0.80 * 2^RATIO_SCALE
161// const B6C: usize = 0x0101; // 0.0157 * 2^LUX_SCALE
162// const M6C: usize = 0x0127; // 0.0180 * 2^LUX_SCALE
163// const K7C: usize = 0x029a; // 1.3 * 2^RATIO_SCALE
164// const B7C: usize = 0x0037; // 0.00338 * 2^LUX_SCALE
165// const M7C: usize = 0x002b; // 0.00260 * 2^LUX_SCALE
166// const K8C: usize = 0x029a; // 1.3 * 2^RATIO_SCALE
167// const B8C: usize = 0x0000; // 0.000 * 2^LUX_SCALE
168// const M8C: usize = 0x0000; // 0.000 * 2^LUX_SCALE
169
170#[allow(dead_code)]
171enum Registers {
172 Control = 0x00,
173 Timing = 0x01,
174 ThresholdLowLow = 0x02,
175 ThresholdLowHigh = 0x03,
176 ThresholdHighLow = 0x04,
177 ThresholdHighHigh = 0x05,
178 Interrupt = 0x06,
179 Id = 0x0a,
180 Data0Low = 0x0c,
181 Data0High = 0x0d,
182 Data1Low = 0x0e,
183 Data1High = 0x0f,
184}
185
186#[derive(Clone, Copy, PartialEq)]
187enum State {
188 Idle,
189
190 /// Read the Id register.
191 SelectId,
192 ReadingId,
193
194 /// Process of taking a light measurement.
195 TakeMeasurementTurnOn,
196 TakeMeasurementConfigMeasurement,
197 TakeMeasurementReset1,
198 TakeMeasurementReset2,
199
200 /// Read the ADC registers.
201 ReadMeasurement1,
202 ReadMeasurement2,
203 ReadMeasurement3,
204 /// Calculate light and call the callback with the value.
205 GotMeasurement,
206
207 /// Disable I2C and release buffer
208 Done,
209}
210
211#[derive(Default)]
212pub struct App {}
213
214pub struct TSL2561<'a, I: i2c::I2CDevice> {
215 i2c: &'a I,
216 interrupt_pin: &'a dyn gpio::InterruptPin<'a>,
217 state: Cell<State>,
218 buffer: TakeCell<'static, [u8]>,
219 apps: Grant<App, UpcallCount<1>, AllowRoCount<0>, AllowRwCount<0>>,
220 owning_process: OptionalCell<ProcessId>,
221}
222
223impl<'a, I: i2c::I2CDevice> TSL2561<'a, I> {
224 pub fn new(
225 i2c: &'a I,
226 interrupt_pin: &'a dyn gpio::InterruptPin<'a>,
227 buffer: &'static mut [u8],
228 apps: Grant<App, UpcallCount<1>, AllowRoCount<0>, AllowRwCount<0>>,
229 ) -> Self {
230 // setup and return struct
231 Self {
232 i2c,
233 interrupt_pin,
234 state: Cell::new(State::Idle),
235 buffer: TakeCell::new(buffer),
236 apps,
237 owning_process: OptionalCell::empty(),
238 }
239 }
240
241 pub fn read_id(&self) {
242 self.buffer.take().map(|buffer| {
243 // turn on i2c to send commands
244 self.i2c.enable();
245
246 buffer[0] = Registers::Id as u8 | COMMAND_REG;
247 // buffer[0] = Registers::Id as u8;
248 // TODO verify errors
249 let _ = self.i2c.write(buffer, 1);
250 self.state.set(State::SelectId);
251 });
252 }
253
254 pub fn take_measurement(&self) {
255 // Need pull up on interrupt pin
256 self.interrupt_pin.make_input();
257 self.interrupt_pin
258 .enable_interrupts(gpio::InterruptEdge::FallingEdge);
259
260 self.buffer.take().map(|buf| {
261 // Turn on i2c to send commands
262 self.i2c.enable();
263
264 buf[0] = Registers::Control as u8 | COMMAND_REG;
265 buf[1] = POWER_ON;
266 // TODO verify errors
267 let _ = self.i2c.write(buf, 2);
268 self.state.set(State::TakeMeasurementTurnOn);
269 });
270 }
271
272 fn calculate_lux(&self, chan0: u16, chan1: u16) -> usize {
273 // First, scale the channel values depending on the gain and integration
274 // time. 16X, 402mS is nominal. Scale if integration time is NOT 402 msec.
275 // let mut ch_scale = CHSCALE_TINT0 as usize; // 13.7ms
276 let mut ch_scale = CHSCALE_TINT1 as usize; // 101ms
277 // let mut ch_scale: usize = 1 << CH_SCALE; // Default
278
279 // Scale if gain is NOT 16X
280 ch_scale <<= 4; // scale 1X to 16X
281
282 // scale the channel values
283 let channel0 = (chan0 as usize * ch_scale) >> CH_SCALE;
284 let channel1 = (chan1 as usize * ch_scale) >> CH_SCALE;
285
286 // Find the ratio of the channel values (Channel1/Channel0).
287 // Protect against divide by zero.
288 let mut ratio1 = 0;
289 if channel0 != 0 {
290 ratio1 = (channel1 << (RATIO_SCALE + 1)) / channel0;
291 }
292
293 // round the ratio value
294 let ratio = (ratio1 + 1) >> 1;
295
296 // is ratio <= eachBreak ?
297 let mut b = 0;
298 let mut m = 0;
299 // T, FN, and CL package
300 if ratio <= K1T {
301 b = B1T;
302 m = M1T;
303 } else if ratio <= K2T {
304 b = B2T;
305 m = M2T;
306 } else if ratio <= K3T {
307 b = B3T;
308 m = M3T;
309 } else if ratio <= K4T {
310 b = B4T;
311 m = M4T;
312 } else if ratio <= K5T {
313 b = B5T;
314 m = M5T;
315 } else if ratio <= K6T {
316 b = B6T;
317 m = M6T;
318 } else if ratio <= K7T {
319 b = B7T;
320 m = M7T;
321 } else if ratio > K8T {
322 b = B8T;
323 m = M8T;
324 }
325 // CS package
326 // if ratio <= K1C {
327 // b=B1C; m=M1C;
328 // } else if ratio <= K2C {
329 // b=B2C; m=M2C;
330 // } else if ratio <= K3C {
331 // b=B3C; m=M3C;
332 // } else if ratio <= K4C {
333 // b=B4C; m=M4C;
334 // } else if ratio <= K5C {
335 // b=B5C; m=M5C;
336 // } else if ratio <= K6C {
337 // b=B6C; m=M6C;
338 // } else if ratio <= K7C {
339 // b=B7C; m=M7C;
340 // } else if ratio > K8C {
341 // b=B8C; m=M8C;
342 // }
343
344 // Calculate actual lux value
345 let mut val = ((channel0 * b) as isize) - ((channel1 * m) as isize);
346
347 // Do not allow negative lux value
348 if val < 0 {
349 val = 0;
350 }
351
352 // round lsb (2^(LUX_SCALE−1))
353 // val += (1 << (LUX_SCALE−1));
354 val += 1 << (LUX_SCALE - 1);
355
356 // strip off fractional portion and return lux
357 let lux = val >> LUX_SCALE;
358
359 lux as usize
360 }
361}
362
363impl<I: i2c::I2CDevice> i2c::I2CClient for TSL2561<'_, I> {
364 fn command_complete(&self, buffer: &'static mut [u8], _status: Result<(), i2c::Error>) {
365 match self.state.get() {
366 State::SelectId => {
367 // TODO verify errors
368 let _ = self.i2c.read(buffer, 1);
369 self.state.set(State::ReadingId);
370 }
371 State::ReadingId => {
372 self.buffer.replace(buffer);
373 self.i2c.disable();
374 self.state.set(State::Idle);
375 }
376 State::TakeMeasurementTurnOn => {
377 buffer[0] = Registers::Timing as u8 | COMMAND_REG;
378 buffer[1] = INTEGRATE_TIME_101_MS | LOW_GAIN_MODE;
379 // TODO verify errors
380 let _ = self.i2c.write(buffer, 2);
381 self.state.set(State::TakeMeasurementConfigMeasurement);
382 }
383 State::TakeMeasurementConfigMeasurement => {
384 buffer[0] = Registers::Interrupt as u8 | COMMAND_REG;
385 buffer[1] = INTERRUPT_CONTROL_LEVEL | INTERRUPT_ON_ADC_DONE;
386 // TODO verify errors
387 let _ = self.i2c.write(buffer, 2);
388 self.state.set(State::TakeMeasurementReset1);
389 }
390 State::TakeMeasurementReset1 => {
391 buffer[0] = Registers::Control as u8 | COMMAND_REG;
392 buffer[1] = POWER_OFF;
393 // TODO verify errors
394 let _ = self.i2c.write(buffer, 2);
395 self.state.set(State::TakeMeasurementReset2);
396 }
397 State::TakeMeasurementReset2 => {
398 buffer[0] = Registers::Control as u8 | COMMAND_REG;
399 buffer[1] = POWER_ON;
400 // TODO verify errors
401 let _ = self.i2c.write(buffer, 2);
402 self.state.set(State::Done);
403 }
404 State::ReadMeasurement1 => {
405 // TODO verify errors
406 let _ = self.i2c.read(buffer, 2);
407 self.state.set(State::ReadMeasurement2);
408 }
409 State::ReadMeasurement2 => {
410 // Store the previous readings in the buffer where they
411 // won't get overwritten.
412 buffer[2] = buffer[0];
413 buffer[3] = buffer[1];
414 buffer[0] = Registers::Data0Low as u8 | COMMAND_REG | WORD_PROTOCOL;
415 // TODO verify errors
416 let _ = self.i2c.write(buffer, 2);
417 self.state.set(State::ReadMeasurement3);
418 }
419 State::ReadMeasurement3 => {
420 // TODO verify errors
421 let _ = self.i2c.read(buffer, 2);
422 self.state.set(State::GotMeasurement);
423 }
424 State::GotMeasurement => {
425 let chan0 = ((buffer[1] as u16) << 8) | (buffer[0] as u16);
426 let chan1 = ((buffer[3] as u16) << 8) | (buffer[2] as u16);
427
428 let lux = self.calculate_lux(chan0, chan1);
429
430 self.owning_process.map(|pid| {
431 let _ = self.apps.enter(pid, |_, upcalls| {
432 upcalls.schedule_upcall(0, (0, lux, 0)).ok();
433 });
434 });
435
436 buffer[0] = Registers::Control as u8 | COMMAND_REG;
437 buffer[1] = POWER_OFF;
438 // TODO verify errors
439 let _ = self.i2c.write(buffer, 2);
440 self.interrupt_pin.disable_interrupts();
441 self.state.set(State::Done);
442 }
443 State::Done => {
444 self.buffer.replace(buffer);
445 self.i2c.disable();
446 self.state.set(State::Idle);
447 }
448 _ => {}
449 }
450 }
451}
452
453impl<I: i2c::I2CDevice> gpio::Client for TSL2561<'_, I> {
454 fn fired(&self) {
455 self.buffer.take().map(|buffer| {
456 // turn on i2c to send commands
457 self.i2c.enable();
458
459 // Read the first of the ADC registers.
460 buffer[0] = Registers::Data1Low as u8 | COMMAND_REG | WORD_PROTOCOL;
461 // TODO verify errors
462 let _ = self.i2c.write(buffer, 1);
463 self.state.set(State::ReadMeasurement1);
464 });
465 }
466}
467
468impl<I: i2c::I2CDevice> SyscallDriver for TSL2561<'_, I> {
469 fn command(
470 &self,
471 command_num: usize,
472 _: usize,
473 _: usize,
474 process_id: ProcessId,
475 ) -> CommandReturn {
476 if command_num == 0 {
477 // Handle this first as it should be returned
478 // unconditionally
479 return CommandReturn::success();
480 }
481 // Check if this non-virtualized driver is already in use by
482 // some (alive) process
483 let match_or_empty_or_nonexistant = self.owning_process.map_or(true, |current_process| {
484 self.apps
485 .enter(current_process, |_, _| current_process == process_id)
486 .unwrap_or(true)
487 });
488 if match_or_empty_or_nonexistant {
489 self.owning_process.set(process_id);
490 } else {
491 return CommandReturn::failure(ErrorCode::NOMEM);
492 }
493 match command_num {
494 // Take a measurement
495 1 => {
496 self.take_measurement();
497 CommandReturn::success()
498 }
499 // default
500 _ => CommandReturn::failure(ErrorCode::NOSUPPORT),
501 }
502 }
503
504 fn allocate_grant(&self, processid: ProcessId) -> Result<(), kernel::process::Error> {
505 self.apps.enter(processid, |_, _| {})
506 }
507}