Get acquainted with using I2C with the Quark D2000 development board by interfacing an ambient light sensor and an LCD.

Previously, we presented a general overview of the Quark D2000 development board. Subsequently, we explored the use of the board’s GPIO and PWM.

In this project, we will explore using I2C with the D2000 board by interfacing an ambient light sensor and a COG (chip-on-glass) LCD.

Since our last report, a new version of the board’s software interface has been released (ISSM_2016.1.067) and some of the documentation has been updated. Be sure to get the resources linked below:

Software files for all of the programs in the article can be downloaded by clicking the link bar below:


  Quark D2000 Project Source Code FiIes  

Basic I2C Programming

To illustrate the most basic use of the D2000 board’s I2C interface, we can compare the procedure to that used with the ubiquitous Arduino Uno. That is, we will interface a BH1750FVI ambient light sensor, using the module shown below, with each board.


BH1750 Module

The BH1750 ambient light sensor module (left panels) and hookup connections (right panel)


This inexpensive module has been around for a while and is available from several sources (e.g., 1, 2). It’s chosen as an example because the programming to use the board is short and straightforward. Connecting the module is also straightforward (see right panel of the figure above) and it can be used with both 3.3V and 5V systems.

First, connect the board to the UNO and run the included program, BH1750.ino. You should see lux values scrolling on the serial monitor. Looking at the program listing, notice that we first #include wire.h and use the statement wire.begin() in setup(). Then, we call the function initBH1750(), also from setup(). Finally, we read the sensor values within the loop().

To initialize the sensor in initBH1750() we use the statements:

(1) Wire.beginTransmission(BH1750addr);

(2) Wire.write(BHmodedata);

and, then (3) Wire.endTransmission()

The statements; 1) set the address of transmission to the sensor using the variable BH1750addr, 2) cue writing the initialization code in the variable BHmodedata to the sensor, and, 3) actually write the data to the sensor.

In the loop() section, we read the sensor using the statements:

(1)   Wire.requestFrom(BH1750addr, BYTES2READ);

(2)  BHlxdata[0] = (byte);  BHlxdata[1] = (byte);

These statements; 1) send a request to the device to read two bytes of data (the lux value), and 2) read two bytes from the sensor and store them in variables. The program then converts the bytes read to a lux value and prints the value to the serial monitor.

BH1750.c is the analogous program for the D2000 board. After you connect the sensor module, you can compile and run this program by first creating a new project for the D2000 board (make sure you specify QMSI 1.1) in System Studio using an existing template (“Hello World” works fine). Rename it as desired and then copy and paste BH1750.c over the existing code (main.c) and you are ready to Build and Run the program. All of the included D2000 programs will work in this same manner.

To get a sense for how I2C can work on the D2000, we can compare the two programs.

First, instead of including wire.h, we will include qm_i2c.h. This header file and the associated .c file contain the source code for the I2C routines. It is a very good idea to become familiar with these two files if you want to use and understand I2C on the board. You will find them among your installation directories - \IntelSWTools\ISSM_2016.1.067\firmware\bsp\1.1\drivers and IntelSWTools\ISSM_2016.1.067\firmware\bsp\1.1\drivers\include

The function wireBegin() contains the necessary setup calls to use the I2C and the code is listed below.


                    /*  Enable I2C 0 */
	clk_periph_enable(CLK_PERIPH_CLK | CLK_PERIPH_I2C_M0_REGISTER);
	/* set IO pins for SDA and SCL */
	qm_pmux_select(QM_PIN_ID_6, QM_PMUX_FN_2);
	qm_pmux_select(QM_PIN_ID_7, QM_PMUX_FN_2);

/* Configure I2C */
	I2Ccfg.address_mode = QM_I2C_7_BIT;
	I2Ccfg.mode = QM_I2C_MASTER;
	I2Ccfg.speed = QM_I2C_SPEED_STD;

/* set the configuration through the structure and return if failure */
	if (qm_i2c_set_config(QM_I2C_0, &I2Ccfg)) {
		QM_PUTS("Error: I2C_0 config\n");
		return (errno);
	} else {
		return (0);


First, we enable clocking for I2C. Next, we select the desired function for the I/O pins which will be used for the SDA and SCL I2C lines. In this regard, you can download a handy illustration of the identification of each of the board’s I/O pins and their multiplexed functions.

In the global variables section, we defined a structure with the line: qm_i2c_config_t I2Ccfg.

Our next step is to configure parameters of the I2C using this structure. Specifically, we set the addressing mode to 7-bit, the role to master, and the speed to standard. Finally, we set this configuration by calling the system function, qm_i2c_set_config() with our configuration structure as an argument.

The code lines above make use of definitions that appear in qm_i2c.h. For example; QM_I2C_SPEED_STD set the speed to 100 Kbps, whereas QM_I2C_SPEED_FAST sets the speed to 400 Kbps, and QM_I2C_SPEED_FAST_PLUS sets the speed to 1 Mbps.

Our function initBH1750() reads the sensor values. The integral component of the function is a call to the system function qm_i2c_master_write().


                    uint8_t BHmodedata[1] = {0x10}; /* BH1750 initialization code for 1 lux resolution */
	if (qm_i2c_master_write(QM_I2C_0, BH1750addr, BHmodedata,
			sizeof(BHmodedata), true, &I2Cstatus)) {
		return (errno); 	/* error code (errn) */


We defined an array (BHmodedata[]) to hold the data that we want to send and we call the function with arguments for:

  • the I2C number (there is only one I2C interface on the board)
  • the address of the sensor module (BH1750addr as defined in the global variables section)
  • a pointer to the data array (BHmodedata)
  • the number of bytes to send (sizeof(BHmodedata))
  • stop bit specification (true = send a stop bit)
  • the address of a variable to receive the status (&I2Cstatus – defined in the globals section)

Again, see the system file qm_i2c.h for more detailed information on the calls and arguments.

The rest of the program converts the bytes read to a lux value and prints the value to a serial port through System Studio (see the Getting Started guide for instructions on setting up a FTDI serial cable).


Add an I2C LCD

C0220 BiZ LCD

Newhaven NHD-C0220BiZ-FSW-FBW-3V3M LCD


While using an FTDI cable for serial output is convenient, a simple LCD is a necessity for many stand-alone projects. Once we are familiar with basic I2C programming, we can add an LCD without too much difficulty.

I chose a Newhaven NHD-C0220BiZ-FSW-FBW-3V3M available from these sources [12].

The unit is a 3.3 volt, Chip-on-Glass (COG) LCD with a crisp 2 x 20 display. The I2C address is 0x3c (right-shifted 0x78). You will definitely want to get the data sheet for the display as well as the data sheet for the controller, which is an ST7036i.

In addition to the display, only a few other components are required to interface to the D2000 and the schematic below (based on the schematic in the C022BiZ datasheet) shows the entire circuit.


Complete schematic for interfacing the C0220BiZ to the D2000. Click to enlarge.


A couple of points about the schematic are noteworthy. I used 10K pullup resistors on the SDA and SCL lines and the value is recommended by Newhaven (e.g., see here).

I also used jumpers with both pull-up resistors so that a shorting block connector can be positioned to either include (connector on) or exclude (connector off) them from the I2C lines – convenient if you already have pull-up resistors installed somewhere else.

The values for C1 and C2 are recommended in the C022BiZ datasheet and C3 is the usual bypass capacitor. GPIO5 is connected to the reset pin (RST, active low) on the LCD, allowing the software to reset the LCD.


Component Description
BOM for the LCD Interface
R1,R2 10kΩ resistor
C1,C2 1.0 µf capacitor
C3 .1 µf capacitor
J1,J2 2-pole jumper w/connector


Note that the spacing between the LCD interface pins is 2.0 mm, whereas pin spacing for the holes in common breadboards and perfboard is 2.54 mm (0.100 in). There are several ways of resolving the difference and the way that I chose was to use an adaptor board like that pictured below.


Adapter board for mounting the LCD

Adapter board for mounting the LCD


The adaptor was designed for XBee products but it has the capability that we need. First, cut the board right down the middle. This will give you two boards suitable for our purpose. Then, you can solder the LCD leads and also a common 2.54 mm spacing header directly to the board. The latter header is breadboard friendly. The adaptor is available here and there are similar ones, like this one, that also looks like it will work.

Since the LCD has only 8 pins and the adapter accommodates 10 pins, you can use the extra two holes to run leads from the anode and cathode pins for the backlight which are on the side of the LCD.

Once the LCD is wired up and attached to the D2000, it is time to test it out. The included program, C0220BiZdemo.c will do just that. After displaying a simple welcome message, it will cycle through the character set. It is useful to check that you have everything wired and connected correctly.


LCD Library

To facilitate using the LCD, I decided to write a small library that is suitable for general use in a variety of projects. That code is included in the software download as C0220BiZ_Lib.h and C0220BiZ_Lib.c. You can examine those files for the details on each of the library functions that are described briefly below.

  • void LCD_reset(void)  — Hardware reset of the C0220BiZ

  • int LCD_init(void)  — Initialize the C0220BiZ

  • int LCD_clr(void)  — Clear the C022BiZ screen

  • int LCD_home(void)  — Set DDRAM address to 0x00

  • int LCD_display(uint8_t arg) — Turn display on or off

  • int LCD_cursor(uint8_t arg)  — Turn cursor and blink on or off

  • int LCD_write(const void * message) — Write the character string (null terminated) at the current DDRAM location

  • int LCD_writexy(uint8_t x, uint8_t y, const void * message) — Write the character string (null terminated) x (column) and the y (line)

  • int LCD_writedat(uint8_t datum) — Write a single byte to DDRAM (current location)

  • int LCD_writedatxy(uint8_t x, uint8_t y, uint8_t datum) — Write a single byte at x (column) and y (line)

  • int LCD_gotoxy(uint8_t x, uint8_t y) — Set DDRAM address to x (column) and y (line)

  • int LCD_contrast(uint8_t arg) — Set contrast, 0 (low) - 15 (high)

  • int LCD_ICC(uint8_t addr, uint8_t * chrcode, uint8_t length) — Write custom character data to CGRAM, addr=CGRAM character address 0-7, chrcode= the 8 byte character code array, length = number of bytes in array (normally 8)

To include the library in a project, simply copy C0220BiZ_Lib.h and C0220BiZ_Lib.c into the project folder. Subsequently, use the line, #include "C0220BiZ_Lib.h" in your main.c, and the functions will be visible for use.

The included program, LCDdemo.c, demonstrates the library functions. Along with the library files, examination of this code will further explain the use of the functions. The library is somewhat rudimentary and can certainly be extended and embellished.


An example of the LCD’s custom characters capability, from LCDdemo.c


Closing Thoughts

In this project, we introduced using the I2C interface on the the Quark D2000 development board. We used the simple example of a BH1750 ambient light sensor, contrasting its use with that in the familiar Arduino world. From there, we were able to easily interface an LCD and develop a library of routines for its use.

If you come from the Arduino world, you might, at first, find using I2C on the D2000 board to be much different. With a little experience, however, you may see that, at least at a fundamental level, the basic procedures are not so different. In fact, you'll likely find that the D2000's routines offer more flexibility.

In part 2 of this project, we will conclude our exploration of I2C interfacing on the D2000 board and add a color sensor and a program to identify an object's color. We will also touch on using asynchronous I2C by revisiting the BH1750 ambient light sensor.