VCNL4000 - Integrated Proximity and Ambient Light Sensor

VCNL4000 - Integrated Proximity and Ambient Light Sensor © CC BY-ND

The VCNL4000 is a proximity sensor with an integrated ambient light sensor.

  • 6 respects

Components and supplies

About this project

The VCNL4000 is a proximity sensor with an integrated ambient light sensor. It is the industry’s first optical sensor to combine an infrared emitter, PIN photodiode, ambient light sensor and signal processing in a single package with a 16-bit ADC for proximity measurement as well as ambient light measurement.

VCNL4000 is designed specifically for the low height requirements of smart phone, mobile phone, digital camera, and tablet PC applications.Through its standard I2C bus serial digital interface, it allows easy access to a “Proximity Signal” and “Light intensity” measurement without complex calculations or programming (also, this sensor has inside it IIR filter). First, I am going to tell about I2C protocol that this sensor uses to communicate with MCU.

This sensor is easy to use with any microcontroller that has i2c capability. It is 5 volt compliant so you can use it with 3.3V or 5V logic with no risk of damage. There is an onboard 3.3V ultra low dropout regulator so you can power it with 3.3 to 5.0V. However, if you can give it 5.0V that is ideal since the VIN voltage powers the IR LED and the higher the voltage you can give it, the more powerful it is. Also, this sensor is „Digitally Addressable Sensor“ with IIR filter inside which does all the „magic“ for you, so you do not need measure characteristic and linearise it in its working area like for analog sensors do. That means VCNL4000 does not have a analog output, but instead outputs a 16bit digital reading that will be „shown“ on MCU.

An Inter-IC bus is often used to communicate across circuit-board distances. Here’s a primer on the protocol.

Philips originally developed I2C for communication between devices inside of a TV set. Examples of simple I2C-compatible devices found in embedded systems include EEPROMs, thermal sensors, and real-time clocks. I2C is also used as a control interface to signal processing devices that have separate, application-specific data interfaces. For instance, it’s commonly used in multimedia applications, where typical devices include RF tuners, video decoders and encoders, and audio processors. In all, Philips, National Semiconductor, Xicor, Siemens, and other manufacturers offer hundreds of I2C-compatible devices. With I2C you can chain „only“ upto 128 devices/sensors. Using I2C you can set individual address locations for various sensors..

I2C uses just two wires to implement serial communication between one or more master devices and one or more slave devices. Communication is initiated with a START bit (Which is a START condition generated by bringing SDA low while CLK is high) and then is followed with a Control Byte. The control byte consists of the control code, device address, and the read/write bit.

SCL is the clock line. It is used to synchronize all data transfers over the I2C bus. SDA is the data line. The SCL & SDA lines are connected to all devices on the I2C bus. There needs to be a third wire which is just the ground or 0 volts. There may also be a 5volt wire is power is being distributed to the devices. Both SCL and SDA lines are “open drain” drivers. What this means is that the chip can drive its output low, but it cannot drive it high. For the line to be able to go high you must provide pull-up resistors to the 5v supply.

There are both 7- and 8-bit versions of I2C addresses. 7 bits identify the device, and the eighth bit determines if it’s being written to or read from. The Wire library uses 7 bit addresses throughout. If you have a datasheet or sample code that uses 8 bit address, you’ll want to drop the low bit (i.e. shift the value one bit to the right), yielding an address between 0 and 127.

The first thing that will happen is that the master will send out a start sequence. This will alert all the slave devices on the bus that a transaction is starting and they should listen in incase it is for them. Next the master will send out the device address. The slave that matches this address will continue with the transaction, any others will ignore the rest of this transaction and wait for the next. Having addressed the slave device the master must now send out the internal location or register number inside the slave that it wishes to write to or read from. This number is obviously dependant on what the slave actually is and how many internal registers it has. Some very simple devices do not have any, but most do, including all of our modules. Our CMPS03 has 16 locations numbered 0-15. The SRF08 has 36. Having sent the I2C address and the internal register address the master can now send the data byte (or bytes, it doesn’t have to be just one). The master can continue to send data bytes to the slave and these will normally be placed in the following registers because the slave will automatically increment the internal register address after each byte. When the master has finished writing all data to the slave, it sends a stop sequence which completes the transaction. So to write to a slave device:
1. Send a start sequence
2. Send the I2C address of the slave with the R/W bit low (even address)
3. Send the internal register number you want to write to
4. Send the data byte
5. [Optionally, send any further data bytes]
6. Send the stop sequence.

Reading from the Slave
This is a bit more complicated - but not too much. Before reading data from the slave device, you must tell it which of its internal addresses you want to read. So a read of the slave actually starts off by writing to it. This is the same as when you want to write to it: You send the start sequence, the I2C address of the slave with the R/W bit low (even address) and the internal register number you want to write to. Now you send another start sequence (sometimes called a restart) and the I2C address again - this time with the read bit set. You then read as many data bytes as you wish and terminate the transaction with a stop sequence.

Now I am going to implement this on arduino, because of its API simplicity. Arduino uses wire.h library, and this library allows you to communicate with I2C / TWI devices. Arduino Uno uses A4 (SDA), and A5 (SCL) pins for I2C.

First, you should rad VCNL4000 data sheet to see registers which this sensor uses. It is available here. In page 6 there is a simple, understandable flowchart for proximity sensing and this is a start-off for our algorithm implementation.

Wiring the sensor

I already said that VCNL4000 is an I2C device. I2C is a 2-wire serial connection, so you just need to connect the SDA (Data) and SCL (Clock) lines to your Arduino for communication. On most arduinos SDA is on analog pin A4, and SCL is on analog pin A5. On an arduino mega, SDA is digital 20, and SCL is digital 21. The Leonardo’s have their own special connections.

The board needs 3.3v to run, but it also needs 5V for the IR led that it uses to check the proximity.

So to wire this sensor with arduino, connect (the right side is Arduino connectors, left is sensor):

IRA - > 5V

VDD -> 3.3V


SDA -> A4

SCL -> A5


The VCNL4000 has a fix slave address for the host programming and accessing selection. The predefined 7 bit I2C bus address is set to 0010 011 = 13h. The least significant bit (LSB) defines read or write mode. Accordingly the bus address is set to 0010 011x = 26h for write, 27h for read.

VCNL4000 has twelve user accessible 8 bit registers. The register addresses are 80h (register #0) to 8Bh (register #11).

The VCNL4000 contains twelve 8-bit registers for operation control, parameter setup and result buffering. All registers are accessible via I2C communication. Some of the registers like register #2 and register #11 are not intended to use by customer, and we are not going to use all of the registers.

So, lets go coding!

Before you start, you need to know about bit manipulation in C, so read first , its easy, belive me!

First, we need to include arduino wire library where all functions for I2C communication are (begin(),requestFrom(), beginTransmission(), endTransmission(), write(), available(),read(),onReceive(),onRequest())

The code is attached below!

Datasheet VCNL4000


Simple sketch for ArduinoC/C++
#include <Wire.h>
//We need to initialise  the I2C Address of the board,
#define VCNL4000_ADDRESS 0x13 // I2C bus address is set to 0010 011 = //13h –see the datasheet page 9
//Now wee need setup function -> this function loads only once:
void setup(){
  Serial.begin(9600);  // Serial used to print data
  Wire.begin();  // initialize I2C – see arduino reference for wire lib
  initVCNL4000(); //initilize and setup the board -> function
//our loop function    
void loop(){    
  unsigned int ambientValue = readAmbient(); //reading ambient light //data -> it takes about 100ms to read this data
  unsigned int proximityValue = readProximity();

  Serial.print(" | ");

  delay(100);  //Slow down the printing
  //  the readings take about 100ms to execute

//Out initVCNL4000() function looks like this:
void initVCNL4000(){
  byte temp = readVCNLByte(0x81); // Register #1 Product ID Revision //Register - >Register address = 81h. This register contains //information about product ID and product revision.
//Register data value of current revision = 11h -> check datasheet.

  if (temp != 0x11){  // Product ID Should be 0x11
    Serial.print("initVCNL4000 failed to initialize");
    Serial.println(temp, HEX);
    Serial.println("VNCL4000 Connected...");

  /*VNCL400 init params
   Feel free to play with any of these values, but check the datasheet first!*/
// Register #4 Ambient Light Parameter Register
//Register address = 84h.
  writeVCNLByte(0x84, 0x0F);  // Configures ambient light measures - //Single conversion mode, 128 averages (2^7=128, 0x0F=1111(binary))
//Bit 3= Auto offset compensation = Enabled 
/* Register #3 LED Current Setting for Proximity Mode
Register address = 83h. This register is to set the LED current value for proximity measurement.
The value is adjustable in steps of 10 mA from 0 mA to 200 mA.
This register also contains information about the used device fuse program ID. */

  writeVCNLByte(0x83, 15);  /*
R/W bits. IR LED current = Value (dec.) x 10 mA. 
Valid Range = 0 to 20d. 
sets IR current in steps of 10mA 0-200mA //--> 15 decimal= 1111(binary)so current is set to 10x150mA= 150mA

Register #9 Proximity Measurement Signal Frequency 
Register address = 89h */

  writeVCNLByte(0x89, 2);  // Proximity IR test signal freq, 0-3 - //781.25 kHz (DEFAULT)

// Register #10 Proximity Modulator Timing Adjustment // Register address = 8Ah.  
  writeVCNLByte(0x8A, 0x81);  // proximity modulator timing – 81 //hex=129, recommended by Vishay 

//Now we need to write readProximity() function which looks like:
unsigned int readProximity(){
  // readProximity() returns a 16-bit value from the VCNL4000's //proximity data registers

// Register #0 Command Register
//Register address = 80h
//The register #0 is for starting ambient light or proximity measurements. This register contains 2 flag bits for data ready indication.  
  byte temp = readVCNLByte(0x80);
  writeVCNLByte(0x80, temp | 0x08);  /* command the sensor to perform a proximity measure
The command temp | 0x08 is needed because bits 0-2 are N/A (we dont need them, they can be equal to 0, so we are setting third bit (bit 3) to 1, it is Prox. od bit -> R/W bit. Starts a single on-demand measurement for proximity.
Result is available at the end of conversion for reading in the registers #7 (HB) and #8 (LB).)*/

  while(!(readVCNLByte(0x80)&0x20));  // Wait for the proximity data ready bit to be set
//we are setting fifth bit (bit 5) (0x20 hex=100000 binary) in register 0x80 to 1, it is Proximity data ready bit, Read only bit. Value = 1 when proximity measurement data is available in the result registers. This bit will be reset when one of the corresponding result registers (reg #7, reg #8) is read. */
  unsigned int data = readVCNLByte(0x87) << 8; //Shift data from //register 7 (bits 0:7)
  data |= readVCNLByte(0x88); //read data from register 8

  return data;

unsigned int readAmbient(){
  // readAmbient() returns a 16-bit value from the VCNL4000's ambient light data registers
  byte temp = readVCNLByte(0x80);
  writeVCNLByte(0x80, temp | 0x10);  /* command the sensor to perform ambient measure 
-> 0x10 = 10000 binary, set bit 4 from register 0x80 to 1,
als od R/W bit. Starts a single on-demand measurement for ambient light. If averaging is enabled, starts a sequence of readings and stores the averaged result. Result is available at the end of conversion for reading in the registers #5 (HB) and #6 (LB).  */
  while(!(readVCNLByte(0x80)&0x40));  // wait for the als data ready bit to be set als data rdy -> bit 6
  unsigned int data = readVCNLByte(0x85) << 8; //The same as proximity measure
  data |= readVCNLByte(0x86);

  return data;

void writeVCNLByte(byte address, byte data){
  // writeVCNLByte(address, data) writes a single byte of data to address

byte readVCNLByte(byte address){
  // readByte(address) reads a single byte of data from address
  Wire.requestFrom(VCNL4000_ADDRESS, 1);
  byte data =;

  return data;


Similar projects you might like

Ambient Light Sensor with Arduino

by Anas Dalintakam

  • 9 respects

Ambient Light Sensor Using Photo Resistor and LED Lights!

Project tutorial by DCamino

  • 22 respects

Visualising sensor data using Arduino and Processing

Project tutorial by sowmith mandadi

  • 43 respects

Arduino Nano: BH1750 Ambient Light I2C Sensor with Visuino

Project tutorial by Boian Mitov

  • 13 respects

How To Use DS18B20 Water Proof Temperature Sensor

Project showcase by Team IoTBoys

  • 26 respects

Parking Sensor with Arduino

Project showcase by Team VINICIUS LOPES

  • 32 respects
Add projectSign up / Login