Skip to content

Commit c6c5f6d

Browse files
oestrichfhunleth
authored andcommitted
Add a device sim for the VCNL4040 proximity sensor
- Setting config and logging what changed - Set the proximity/ambient light/white light and fetch from the data registers - Return the chip id Datasheet link https://www.vishay.com/docs/84274/vcnl4040.pdf
1 parent e02c23d commit c6c5f6d

2 files changed

Lines changed: 265 additions & 0 deletions

File tree

Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
defmodule CircuitsSim.Device.VCNL4040 do
2+
@moduledoc """
3+
VCNL4040 ambient light and proximity sensor
4+
5+
Typically found at 0x60
6+
See the [datasheet](https://www.vishay.com/docs/84274/vcnl4040.pdf)
7+
Many configuration options aren't implemented.
8+
9+
Call `set_proximity/3`, `set_ambient_light/3`, and `set_white_light/3` to
10+
change the state of the sensor.
11+
"""
12+
alias CircuitsSim.I2C.I2CDevice
13+
alias CircuitsSim.I2C.I2CServer
14+
15+
defstruct als_config: 0x0000,
16+
als_low_threshold: 0x0000,
17+
als_high_threshold: 0x0000,
18+
ps_low_threshold: 0x0000,
19+
ps_high_threshold: 0x0000,
20+
ps_config_1: 0x0000,
21+
ps_config_2: 0x0000,
22+
ps_cancellation_level: 0x0000,
23+
proximity_raw: 0,
24+
ambient_light_raw: 0,
25+
white_light_raw: 0
26+
27+
@type t() :: %__MODULE__{
28+
als_config: 0..0xFFFF,
29+
als_low_threshold: 0..0xFFFF,
30+
als_high_threshold: 0..0xFFFF,
31+
ps_low_threshold: 0..0xFFFF,
32+
ps_high_threshold: 0..0xFFFF,
33+
ps_config_1: 0..0xFFFF,
34+
ps_config_2: 0..0xFFFF,
35+
ps_cancellation_level: 0..0xFFFF,
36+
proximity_raw: 0..0xFFFF,
37+
ambient_light_raw: 0..0xFFFF,
38+
white_light_raw: 0..0xFFFF
39+
}
40+
41+
@spec child_spec(keyword()) :: Supervisor.child_spec()
42+
def child_spec(args) do
43+
device = __MODULE__.new(Keyword.get(args, :state, []))
44+
I2CServer.child_spec_helper(device, args)
45+
end
46+
47+
@spec new(keyword) :: t()
48+
def new(options \\ []) do
49+
struct!(__MODULE__, options)
50+
end
51+
52+
@spec set_proximity(String.t(), Circuits.I2C.address(), number()) :: :ok
53+
def set_proximity(bus_name, address, value) do
54+
I2CServer.send_message(bus_name, address, {:set_proximity, value})
55+
end
56+
57+
@spec set_ambient_light(String.t(), Circuits.I2C.address(), number()) :: :ok
58+
def set_ambient_light(bus_name, address, value) do
59+
I2CServer.send_message(bus_name, address, {:set_ambient_light, value})
60+
end
61+
62+
@spec set_white_light(String.t(), Circuits.I2C.address(), number()) :: :ok
63+
def set_white_light(bus_name, address, value) do
64+
I2CServer.send_message(bus_name, address, {:set_white_light, value})
65+
end
66+
67+
defimpl I2CDevice do
68+
require Logger
69+
70+
# read/write
71+
@als_config_register 0x00
72+
@als_high_threshold_register 0x01
73+
@als_low_threshold_register 0x02
74+
@ps_config_register_1 0x03
75+
@ps_config_register_2 0x04
76+
@ps_cancellation_register 0x05
77+
@ps_low_threshold_register 0x06
78+
@ps_high_threshold_register 0x07
79+
80+
# read only
81+
@ps_data_register 0x08
82+
@als_data_register 0x09
83+
@white_data_register 0x0A
84+
@als_ps_interrupt_flag_register 0x0B
85+
@cmd_device_id 0x0C
86+
87+
@impl I2CDevice
88+
def read(state, count) do
89+
{:binary.copy(<<0>>, count), state}
90+
end
91+
92+
@impl I2CDevice
93+
# second byte is reserved and should always be 0b0000_0000
94+
def write(state, <<@als_config_register, config, 0>>) do
95+
<<time::2, 0::2, interrupt_persistence::2, interrupt::1, power::1>> = <<config>>
96+
97+
config = %{
98+
integration_time: time,
99+
interrupt_persistence: interrupt_persistence,
100+
interrupt_enable: interrupt,
101+
power_enable: power
102+
}
103+
104+
Logger.debug("[VCNL4040] Ambient Light Sensor Config - #{inspect(config)}")
105+
106+
%{state | als_config: config}
107+
end
108+
109+
def write(state, <<@als_high_threshold_register, high::little-16>>) do
110+
Logger.debug("[VCNL4040] Ambient Light Sensor High Threshold - #{high}")
111+
%{state | als_high_threshold: high}
112+
end
113+
114+
def write(state, <<@als_low_threshold_register, low::little-16>>) do
115+
Logger.debug("[VCNL4040] Ambient Light Sensor Low Threshold - #{low}")
116+
%{state | als_low_threshold: low}
117+
end
118+
119+
def write(state, <<@ps_config_register_1, low, high>>) do
120+
<<duty::2, interrupt_persistence::2, time::3, power::1>> = <<low>>
121+
<<0::4, output_bits::1, 0::1, interrupt::2>> = <<high>>
122+
123+
config = %{
124+
duty_ratio: duty,
125+
interrupt_persistence: interrupt_persistence,
126+
interrupt_enabled: interrupt,
127+
integration_time: time,
128+
output_bits: output_bits,
129+
power_enable: power
130+
}
131+
132+
Logger.debug("[VCNL4040] Proximity Sensor Config - #{inspect(config)}")
133+
134+
%{state | ps_config_1: config}
135+
end
136+
137+
def write(state, <<@ps_config_register_2, low, high>>) do
138+
<<0::1, proximity_mps::2, smart_persistence::1, af::1, af_trigger::1, 0::1, sunlight::1>> =
139+
<<low>>
140+
141+
<<white_channel::1, proximity::1, 0::3, led::3>> = <<high>>
142+
143+
config = %{
144+
proximity_mps: proximity_mps,
145+
smart_persistence: smart_persistence,
146+
active_force_mode: af,
147+
active_force_trigger: af_trigger,
148+
sunlight_cancel_enabled: sunlight,
149+
white_channel_enabled: white_channel,
150+
proximity_mode: proximity,
151+
led_current: led
152+
}
153+
154+
Logger.debug("[VCNL4040] Proximity Sensor Config 2 - #{inspect(config)}")
155+
156+
%{state | ps_config_2: config}
157+
end
158+
159+
def write(state, <<@ps_cancellation_register, level::little-16>>) do
160+
Logger.debug("[VCNL4040] Proximity Sensor Cancellation Level - #{level}")
161+
%{state | ps_cancellation_level: level}
162+
end
163+
164+
def write(state, <<@ps_low_threshold_register, low::little-16>>) do
165+
Logger.debug("[VCNL4040] Proximity Sensor Low Threshold - #{low}")
166+
%{state | ps_low_threshold: low}
167+
end
168+
169+
def write(state, <<@ps_high_threshold_register, high::little-16>>) do
170+
Logger.debug("[VCNL4040] Proximity Sensor High Threshold - #{high}")
171+
%{state | ps_high_threshold: high}
172+
end
173+
174+
def write(state, data) do
175+
Logger.debug("[VCNL4040] Unknown write - #{inspect(data)}")
176+
state
177+
end
178+
179+
@impl I2CDevice
180+
def write_read(state, <<@ps_data_register>>, 2) do
181+
{<<state.proximity_raw::little-16>>, state}
182+
end
183+
184+
def write_read(state, <<@als_data_register>>, 2) do
185+
{<<state.ambient_light_raw::little-16>>, state}
186+
end
187+
188+
def write_read(state, <<@white_data_register>>, 2) do
189+
{<<state.white_light_raw::little-16>>, state}
190+
end
191+
192+
def write_read(state, <<@als_ps_interrupt_flag_register>>, 2) do
193+
{<<0x00, 0x00>>, state}
194+
end
195+
196+
def write_read(state, <<@cmd_device_id>>, 2) do
197+
{<<0x86, 0x01>>, state}
198+
end
199+
200+
def write_read(state, _value, read_count) do
201+
{:binary.copy(<<0>>, read_count), state}
202+
end
203+
204+
@impl I2CDevice
205+
def render(state) do
206+
"""
207+
Ambient light sensor output
208+
209+
Proximity: #{state.proximity_raw}
210+
Ambient Light: #{state.ambient_light_raw}
211+
White Light: #{state.white_light_raw}
212+
"""
213+
end
214+
215+
@impl I2CDevice
216+
def handle_message(state, {:set_proximity, value}) do
217+
{:ok, %{state | proximity_raw: value}}
218+
end
219+
220+
def handle_message(state, {:set_ambient_light, value}) do
221+
{:ok, %{state | ambient_light_raw: value}}
222+
end
223+
224+
def handle_message(state, {:set_white_light, value}) do
225+
{:ok, %{state | white_light_raw: value}}
226+
end
227+
end
228+
end
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
defmodule CircuitsSim.Device.VCNL4040Test do
2+
use ExUnit.Case
3+
4+
alias CircuitsSim.I2C.I2CServer
5+
alias CircuitsSim.Device.VCNL4040, as: VCNL4040Sim
6+
7+
@i2c_address 0x60
8+
@ps_data 0x08
9+
@als_data 0x09
10+
@wl_data 0x0A
11+
12+
test "rendering the sensor", %{test: test_name} do
13+
i2c_bus = to_string(test_name)
14+
start_supervised!({VCNL4040Sim, bus_name: i2c_bus, address: @i2c_address})
15+
16+
rendered = I2CServer.render(i2c_bus, @i2c_address)
17+
18+
assert rendered ==
19+
"Ambient light sensor output\n\nProximity: 0\nAmbient Light: 0\nWhite Light: 0\n"
20+
end
21+
22+
test "supports VCNL4040 package", %{test: test_name} do
23+
i2c_bus = to_string(test_name)
24+
start_supervised!({VCNL4040Sim, bus_name: i2c_bus, address: @i2c_address})
25+
26+
VCNL4040Sim.set_proximity(i2c_bus, @i2c_address, 10)
27+
VCNL4040Sim.set_ambient_light(i2c_bus, @i2c_address, 50)
28+
VCNL4040Sim.set_white_light(i2c_bus, @i2c_address, 40)
29+
30+
assert {:ok, <<10::little-16>>} = I2CServer.write_read(i2c_bus, @i2c_address, <<@ps_data>>, 2)
31+
32+
assert {:ok, <<50::little-16>>} =
33+
I2CServer.write_read(i2c_bus, @i2c_address, <<@als_data>>, 2)
34+
35+
assert {:ok, <<40::little-16>>} = I2CServer.write_read(i2c_bus, @i2c_address, <<@wl_data>>, 2)
36+
end
37+
end

0 commit comments

Comments
 (0)