Quark D2000 I2C Interfacing: Add a Color Sensor and Asynchronous Mode
Use I2C on the Quark D2000 development board to interface a color sensor and get acquainted with using asynchronous mode.
We finish up our project using I2C on the Quark D2000 development board with the addition of a color sensor and an object color identifier program. Finally, we revisit interfacing the BH1750FVI ambient light sensor using asynchronous mode I2C.
Before continuing in this article, consider reading through part one of the article: Quark D2000 I2C Interfacing: Add a Light Sensor and an LCD
Be sure to consult the reference links below:
- Overview of the Quark D2000 development board
- Using GPIO and PWM on the Quark D2000 development board
- Main Quark D2000 documentation page (links to user guides, design notes, schematics, and more)
- Intel Quark Microcontrollers forum
- Intel System Studio forum
- Where to get the Quark D2000 development board (1) (2)
Software files for all of the programs in the article can be downloaded by clicking the link bar below:
Add a Color Sensor
Color sensors have been around for a while and I wanted to try my hand at constructing a color identifier. That is, I wanted to build a circuit and program that would identify the color of an object and display that color on an LCD screen.
Flora color sensor (left panel) and hookup connections (right panel)
The sensor has an infra-red filter and the module has a white illumination LED on board for object illumination. This module is intended for wearables and they also have an alternative module that is more breadboard friendly and lets you control the illumination LED.
Module connection to the D2000 is straightforward and is illustrated on the right side of the picture above. Note from the module’s schematic that it already includes 10K pullup resistors on the I2C lines, so you should exclude the pullup resistors on the LCD display as described.
Before going further, I want to underscore the complexity of sensing color. It’s not a simple matter and some background reading is in order. I specifically suggest two application notes from the chip’s manufacturer: Calculating Color Temperature and Illuminance using the TAOS TCS3414CS Digital Color Sensor and Lux and CCT Calculations using ams Color Sensors.
You may also want to look at two previous projects on the subject: Feel the Rainbow: Sensing Color with an Arduino and Design a Color Sensor with Measurements Displayed via an RGB LED Module.
Additionally, here is an interesting paper detailing a robotic color sorting application. A simple search will turn up much more reference material.
Approach to Color Identification
Illustrated below (left side) is a chromaticity diagram.
Original chromaticity diagram (left) courtesy of ams (Click to enlarge).
Simply put, the diagram is an x y plot of a color space. From this plot, we can map the specific colors (i.e., the name of a group of colors). The right side of the figure is my rendition of such a map. It is admittedly crude and my intention was only to provide a working approximation that had some empirical validity.
The TCS34725, however, does not directly provide us with the chromaticity coordinates (x, y). Instead, we must essentially read the outputs of the ADCs associated with the red, green, blue, and clear light photo-diode sensors. We can then generate the x and y chromaticity coordinates from those values.
The included program, CS_TCS34725A.c will read the module and display the r,g,b,c sensor values on the LCD screen and also display the x and y chromaticity values. The program requires the LCD library as described previously.
The heart of the calculations is given in these code lines:
x = ( -0.14282 * redc ) + ( 1.54924 * greenc ) + ( -0.95641 * bluec );
y = ( -0.32466 * redc ) + ( 1.57837 * greenc ) + ( -0.73191 * bluec );
z = ( -0.68202 * redc ) + ( 0.77073 * greenc ) + ( 0.56332 * bluec );
xcc = x/(x+y+z);
ycc = y/(x+y+z);
The constants, which relate to the spectral response distributions for each of the photo-diode sensors, come directly from the manufacturer application note referenced previously. Although those values are based on the TCS3414CS chip, they also work well with the TCS34725 since the spectral responsivity of the photodiodes is similar.
The program can be used to test the module, adjust the gain and other parameters (see the datasheet), and gain familiarity with working with the module.
A Color Identifier
Once we have chromaticity coordinates and a map identifying color names, we can generate a lookup table to map the coordinates to the names of colors.
The included program, CS_Color_namer.c, ties these procedures together and attempts to identify the color of an object held up to the Flora color sensor and then print the object’s color on the LCD.
The video below demonstrates the color identifier in action. Note that I placed a black paper tube around the sensor to prevent the module’s onboard LED, which is quite bright, from overwhelming the video capture.
I am impressed by the accuracy of this somewhat crude application, although it was not too difficult to find some objects that would be misidentified. If the task was simply to discriminate between a few different colored objects (e.g., is it an orange or a banana?), the approach could likely be fine-tuned to be very accurate—depending, of course, upon the degree of difference between the objects.
Asynchronous I2C Communication
Before concluding, I have to add that, up to now, all of the examples of I2C communication, including the Arduino example in part 1, have used blocking calls. That is, when executing a call to qm_i2c_master_write(), control is not returned to the calling function until execution (i.e., I2C transmission) has completed.
The called function “blocks” the calling function, until the called function completes. This mode of operation is also called synchronous. In many cases, there is absolutely nothing wrong with blocking calls. But, if your code is waiting for the transmission to complete when it could be doing something else important while the transmission is ongoing, they can be inefficient.
The D2000 software system, however, also has an asynchronous I2C mode of operation where a non-blocking call is made. In this case, the call initiates the transfer and then returns to the calling function without waiting for the transmission to complete.
Recall that in part 1 of this project, we interfaced a BH1750 ambient light sensor. The included program BH1750irq.c is functionally equivalent to BH1750.c described previously in part 1, but here we are using non-blocking calls. Transmission is handled via interrupts at the system level, which is why you will see the line qm_irq_request(QM_IRQ_I2C_0, qm_i2c_0_isr), which “registers” the interrupt service for I2C before using this mode.The code fragment below illustrates some of the initialization differences:
/* I2C asynch transfer structure */
static qm_i2c_transfer_t irq_write_xfer;
static qm_i2c_transfer_t irq_read_xfer;
/*I2C asynch structure vars */
static uint8_t id_write=0;
static uint8_t id_read=1;
/* callback function */
static void i2c_cb(void *data, int rc, qm_i2c_status_t status, uint32_t len);
/* call back will set this variable to true */
volatile bool i2cirq_complete=false;
The first difference in the global variables section is where we define two structures for the asynchronous transfers. We also declare a callback function, i2c_cb(). This function will be called by the system software when the I2C transfer has completed.
To detect when a transmission request has completed, we will use the boolean variable i2cirq_complete. It is declared as volatile because it can be manipulated by multiple threads that include the interrupt service routine that will execute the callback function.
Our program sets the variable to “false” and the callback sets it to “true”, telling us that the transfer has completed. Additionally, the callback function will have access to a variable (id_write or id_read) because the variable’s address is passed in the I2C transfer request. That variable can enable additional processing within the callback function, if needed.
The code fragment below illustrates a basic asynchronous I2C read request.
/* wait for I2C to finish if it is not done */
First, we fill in the transfer structure. The .tx and .tx_length elements are NULL and 0, respectively, because this is a read. The .callback element is set to our callback function. The .rx element points to our read buffer and the .rx_length element contains the number of bytes that we want to read. Finally, the .stop element is set to true, to send a stop bit at the end of the read. Then we set i2cirq_complete=false and request the transfer by calling the function, qm_i2c_master_irq_transfer(QM_I2C_0,&irq_read_xfer,BH1750addr).
Control will pass to the next line without waiting for the transfer to complete illustrating the advantage of the asynchronous mode of operation. That is, the program can continue executing more code while the transfer is taking place. In this example, however, we have nothing else to do, so we simply wait for the transfer to complete by checking the value of i2cirq_complete.
You can go through the program code to see the analogous method to perform an I2C write request and you can also request a read and then write with the same request.
Again, inspection of the system files, qm_i2c.h and qm_i2c.c will provide some documentation as well as examination of an example project in the provided System Studio software, \IntelSWTools\ISSM_2016.1.067\firmware\bsp\1.1\examples\i2c, that utilizes synchronous and asynchronous modes of operation, and a DMA asynchronous mode (which I have not discussed).
In my view, the Quark D2000 development board continues to impress. The I2C functions seem to be very well developed, offering high-speed communications and several modes of operation. The availability of the source code within the System Studio is particularly advantageous. Taken together, its low price and substantial power convince me that it is a very good candidate for future projects.
Give this project a try for yourself! Get the BOM.