CSE 466 Lab 4:
SPI, USB, and Electric Field Sensing Part II


In this lab, you will use the timing calculations you completed in the previous lab to implement a working electric field sensor. You will then use a DLP2232M module to interface your AVR to your PC, where you will calculate the magnitude of the electric field measurement and use the sensor as a virtual knob to control the hue, saturation, or value of a color. You'll display this color in a graphical interface on the PC, and send it back to your AVR to be displayed on the RGB LED.

You will learn the following important concepts:

Suggested Reading and Resources

Helpful Hints

Important Warnings:

General Debugging Strategies

In this lab, you will be working with a system where timing is critical. You can use the JTAG debugger to step through your program, but there is no JTAG interface for the universe that lets you slow down physics. Any breakpoints you set during an e-field measurement will change the timing of your transmit and receive signals and make it impossible to get valid data. Here are a few general debugging strategies that you can use to observe a real-time system without significantly changing the way it operates.

Often, it's incredibly useful to be able to observe the value of a single variable in real time.

Use the JTAG debugger

You can't set breakpoints while collecting samples, but it may be useful to set a breakpoint after you have finished accumulating data for a measurement, so that you can look at the final values of the accumulators. It can be difficult to determine how your sensor operates looking at a single measurement at a time, however.

Use the 7-segment display(s)

You can use the 7-segment display on your board to display a 4-bit value in hex. You could also re-wire a second 7-segment display to be able to see a full 8 bits. You have to be careful with this approach, however, as doing the arithmetic to figure out what the value is in hex, looking up digits in a lookup table, and setting the output pins takes several cycles, so this won't work very well for timing-critical sections of your program.

Use an output port to output binary

Instead of using 7-segment displays, you can use an output port to output an 8-bit binary number. You can view this number by connecting LEDs, or by connecting the pins to the logic analyzer. The advantage to this is that it's fast: you can update the value in one cycle. The downside is that you either have to try to read binary in real time, or spend a bunch of time configuring the logic analyzer.

Use pulse width modulation

You can also use pulse-width modulation to output a value in the form of a duty cycle. You can update the output compare registers as quickly as you can a GPIO port, and can see relative changes in value by watching the brightness of an LED on your breadboard. You can also connect the output to an oscilloscope and use the measurement function to display the duty cycle as a percentage in real time.


Once you've implemented your SPI/USB interface, you can use it to send values to be displayed on your PC.

Part 1: Implementing Your E-Field Sensor

Using the timings you determined in Lab 3, you will now implement a working electric field sensor. The required code consists of some global state information, your interrupt service routines, and a function to coordinate the measurement. Since making measurements will take almost all of the cycles on your microcontroller, you can't run the sensor continuously and still accomplish other tasks.

It is also important that the interrupt service routines be kept as short as possible. You will actually have plenty of cycles between ADC samples to do the accumulation, but it is important that you don't do the accumulation or any sort of calculation in the interrupt service routines. The ADC interrupt has enough time for about one line of C code; trying to do more will make the ADC interrupts start to overlap with the interrupts that are generating the output waveform. You should set a flag in your ADC ISR and watch for this flag to be set from a non-interrupt context, so that you can read the value and do your computation there.

To satisfy both of these requirements, you should write a function that you will call from your main loop that will start up the timer and ADC, make a set of samples, accumulate their values, and then shut everything down so that your microcontroller can perform other functions as well. This function should be broken into three parts: setup, measurement, and cleanup.

The setup portion of your function should perform the following tasks:

As samples are completed by the ADC, your measurement routine should add them to (or subtract them from) the appropriate accumulators. (Make sure that your accumulators are signed 16-bit integers, i.e. int16_t.) Then, once a set number of sets of samples have been accumulated (this number should be a global variable or a constant in your code, referred to as nsamps in this document) your function should move on to the cleanup routine. Sets of samples is important; when you finish you should have added and subtracted the same number of samples from both of your accumulators.

The accumulation process works as follows:

Your cleanup routine needs to take care of the following tasks:

When your e-field measurement function then returns, the values in your two accumulators should then contain the result of a single measurement.

Implement the measurement function on your microcontroller. Set up your main loop to repeatedly make measurements, and compute the following approximation for the magnitude:

$\left| acc_{inphase} \right| + \left| acc_{quadrature} \right|$

This approximation is fast enough to compute on the microcontroller and will allow you to verify that your sensor is working before moving on. Use the result of the magnitude approximation to set OCR1A, and view the duty cycle of the pulse width modulated waveform on your oscilloscope. Note that this value might be larger than 8 bits; you can set up Timer 1 in Fast PWM mode with more than 8 bits to accommodate this, or you can shift the value to the right until it no longer exceeds 255.

Question 1: How does changing nsamps affect the magnitude of the contrast between a high sensor value (no objects near the sensor) and a low sensor value (your hand very close to the sensor)? Why would it be beneficial to have a large value for nsamps?
Question 2: How does changing nsamps affect the amount of time it takes to make a measurement? You can observe this time by connecting the oscilloscope to the transmit antenna and setting the horizontal scale so that you can see individual "bursts" of the waveform, each corresponding to a measurement. Why would it be beneficial to have a smaller value for nsamps?

Part 2: SPI and USB Communication

In this part of the lab, you will temporarily set aside the electric field sensor and add the DLP2232M module to your breadboard, which will give you the capability of interfacing with your PC over USB. It is recommended that you start a new empty project in AVR Studio to implement this part. Then, in the next section, you will combine the two parts.

WARNING: Please be very careful when inserting and removing the DLP2232M from your board. Its pins are very fragile and will easily break off in your breadboard if you are not careful.

Locate the DLP2232M module in your kit, and place it on your breadboard so that the AD0:3 pins on the DLP2232M are near PB4:7 on your AVR. Refer to Figure 8a in the DLP2232M datasheet and wire the module to be USB bus-powered. Make sure you connect the ground pins on the DLP2232M to your circuit ground, but there should be no connections from Vcc in your circuit to the DLP2232M.

Figure 1. DLP2232M module)

Refer to section 4.3 in the DLP2232M datasheet, which provides a table of functions for the pins on the DLP2232M in its various modes. We will be using the MPSSE mode. Make the following connections from the DLP2232M to your AVR microcontroller:

Testing the Interface and Basic Communication

In this subsection of the lab, you will run a small example program that tests your setup, and will familiarize yourself with the basics of SPI communication. Download the SPI starter code and extract the zip archive. Use AVR Studio to program your AVR with the connection-check.hex file. Then, use the following commands to compile and run the ConnectionCheck Java program:

	javac ConnectionCheck.java
	java ConnectionCheck

The Java program will use the FTDI DLP2232M module to send ten bytes to the microcontroller through the SPI interface, and print the bytes it receives. The connection-check.hex file will simply increment the value of a counter and send its value on each SPI transaction. If you have wired everything correctly, you should see the received bytes being incremented by one on each transaction.

Refer to the section on the SPI module in the ATmega16 datasheet, and in a new AVR Studio project, implement a simple program that does SPI communications. The AVR will be the slave device, and the DLP2232M and PC are the master. You may start by writing a program that does the same thing as connection-check.hex. (You are not required to turn in code for this and may skip it if you want, but it is a useful exercise for making sure that you understand how the SPI module works.)

Setting Up an SPI Communication Protocol

In this subsection, you will design and implement a simple communication protocol with two functions. By sending one command from the PC, you should be able to set the red, green, and blue values of the LED simultaneously. Another command should allow you to read four bytes of data from the microcontroller. These bytes will have arbitrary values for now but will be replaced by the values of your two 16-bit accumulators in Part 3.

On your microcontroller, create a global array called data as follows:

	volatile uint8_t data[4] = {0x01, 0x02, 0x03, 0x04};

Modify your SPI interrupt handler to allow you to read all four bytes of this array by sending a single message from the PC. You will need to implement a multi-byte communication protocol. When designing your protocol, keep in mind that since SPI sends and receives at the same time, you will likely have several bytes where you don't care about the value you are receiving or sending.

Add another command to your protocol that allows you to read the four bytes in this array from the PC. Later, we will replace the four bytes in this array with the two 16-bit accumulator values from your sensor. You will need to implement some state in your SPI ISR to keep track of what command is active and which byte to send next.

Question 3: If the SPI bit clock is running at 100 kHz, how many cycles on your AVR do you have between receiving the last bit of one byte of a multi-byte message and the first bit of the next byte? How many cycles do you have between the beginning of one byte and the beginning of the next?

The SPI interrupt will occur once the last bit of a byte is received. Considering your answer to part 1 of question 3, it is important that you update the SPDR as soon as possible in the SPI interrupt to ensure that it is ready by the next negative edge of the SPI clock. When the SPI interrupt occurs, you should already know what data needs to go into SPDR so you can put it there as the first thing in your ISR. Then, you have more time before the next SPI interrupt occurs (which you calculated as part 2 of question 3) to look at the byte you received and decide what to do next time.

To make this easier, there is an important note about SPDR in the ATmega16 datasheet. This register is single-buffered in the transmit direction, but double-buffered in the receive direction. This means that you can write a new value to SPDR at the very beginning of your ISR, even before you have read the value that has been received! You can then read SPDR any time before the next SPI interrupt to get the byte that was received. If this seems illogical, consider the SPDR that you write to to be a completely different variable than the SPDR that you read from.

Once you have implemented your code on the AVR, modify ConnectionCheck.java to add a method that reads the four data bytes from your AVR. Refer to the communicateNoProtocol() method to see how communication with the DLP2232M takes place. The MPSSE on the DLP2232M works by sending command bytes that are described in FTDI Application Note 108. Three commands are sent: the first (0x80) configures the pins on the DLP2232M to make SS low. The next (0x35) tells the module to send and receive a set number of bytes. The last (0x80 again) then sets the SS pin high again. These commands and their arguments are placed into a buffer which is sent to the FTDI DLP2232M module with the ftdi.write() method. As the commands are executed, the response bytes from the AVR are stored in a buffer inside the FTDI chip. The Java program reads this buffer with ftdi.read().

A Note About Unsigned Types in Java

Java does not have any unsigned integer types, which unfortunately adds a little bit of complexity to implementing a communication protocol one byte at a time. The arrays that we pass to ftdi.read() and ftdi.write() are of type byte[], which Java will treat as signed. You'll need to do a little bit of extra work to make Java do the right thing.

If you have an 8-bit unsigned value that you want to store into a byte, you simply cast it to byte as follows:

	byte b = (byte)0xFF;

If you want to get the value of a byte as an unsigned value, you have cast it to a larger type, such as int and perform a bitwise and with 0xFF:

	int value = (0xFF & (int)b);

If you don't do this, then Java will treat all bytes as though they were signed values: 0xFF will equal -1 instead of 255, for example.

Now, add another command to your communication protocol that allows you to set the red, green, and blue components of the RGB LED. Add another function to ConnectionCheck.java that allows you to set the color of the LED using this command. This should be easier than the previous task, since you don't need to read back any data from your AVR.

Question 4: In your report, describe your communication protocol, and explain why you chose to implement it the way that you did. Using your notes, someone else should be able to write a program that interfaced with your breadboard without having to see your code.

Part 3: Interfacing Your Sensor with a PC

You've now written almost all of the code required to implement the final AVR project for this class, and it's time to put the pieces together. In this part, you will combine the your SPI code with your sensor code, and add your two functions for communicating with your board to an application that allows you to select a color using the sensor as a virtual knob, displays this color on the screen, and sets the LED on your board to the same color.

Merge your SPI code into your sensor project, and check to make sure that your sensor still works. As long as you are not sending any SPI messages from the PC, the performance of your sensor should not be affected.

Set up your main loop so that when your E-field measurement function completes, it stores the values of the two accumulators into the data array that your SPI interrupt will read and send back to the PC. You will need to split the 16-bit accumulators into their individual bytes to store them in the data array.

Now you must decide how events will be timed in your program. In order to get a usable sensor measurement, it is important that no interrupts other than the timer and ADC interrupts happen while the samples are being collected. You can't, however, turn off the SPI interrupt, since the PC may decide to try to communicate at any time. SPI doesn't provide a mechanism to delay communication; if the PC tries to communicate and the SPI interrupt is turned off, the AVR will miss the command and the data that the PC receives will be invalid.

There are two ways to structure your program to avoid this problem. One way is to set up your PC application so that it reads the sensor value, sets the sensor color, and then waits a certain amount of time before doing so again. In this case, after the color has been set, you can perform another measurement without needing to worry about it getting interrupted. The advantage to this approach is that it's relatively straightforward to implement. The disadvantage is that the value the PC receives from the sensor will be somewhat delayed, especially if the PC has not read from the sensor in a long time.

Another approach is to continuously make sensor readings when your AVR is idle, so that there will always be a very recent measurement available for the PC to read. If you do this, you need to consider two things that might happen. First, an SPI interrupt may occur during the middle of a sensor measurement. If this happens, the timing will be affected and the measurement must be aborted and the data discarded because they are invalid. Second, you must make sure not to update the data array that your SPI interrupt is reading in the middle of a read. You don't want the PC to read some bytes from one measurement and the rest of the bytes from another measurement. In order to make this work, you will need to add several status flags that your ISR sets and your measurement function checks and acts upon accordingly.

As long as your finished product works in a reasonable manner, you may implement your program in another way if you think you have a better solution. You will be asked to describe your implementation in Question 5.

On the PC side, two Java files have been provided. ColorWindow.java displays a standard Java color picker widget, instantiates SensorInterface, and periodically queries this instance to get the current sensor value and to send back a color. This class is already complete, but you should look at the code to see what it does. SensorInterface.java is structured just like ConnectionCheck.java, except that the methods are now non-static so that it can be instantiated. It has two methods, readSensor and setLEDColor that you will need to implement.

Copy your methods over from ConnectionCheck.java to the methods in SensorInterface.java. In your readSensor function, you should re-assemble the two bytes of each of the accumulators back into ints, and compute the exact magnitude of the sensor value with the following equation:

This is the value that readSensor should return.

You should now be able to run java ColorWindow. The program will display the color chooser (you may have to click the HSB tab to switch to the hue/saturation/brightness interface) and the hue of the color that it displays should be controlled by the distance of your hand above the sensor. There are also two buttons in the interface to allow you to set the minimum and maximum sensor values that it will consider. You should click the minimum button with your hand close to the sensor, and the maximum button with your hand far away from the sensor so that your software knows the range of the sensor values to consider in determining the hue angle.

Question 5: Describe how you chose to structure the timing of events between the PC and your sensor to ensure that SPI interrupts do not affect the accuracy of your measurements. Explain your reasoning behind your decisions.


In the past four labs, you've learned the basics of programming a small, 8-bit microcontroller to perform a variety of functions. You now know how to interface a microcontroller with a variety of sensors, how to implement a sensor by creating and demodulating signals, and how to actuate output devices like a 7-segment display or RGB LED. This week, you've finished implementing a system with hard real time constraints, and added communication with another device, in this case, your PC. You should now have a good understanding of how to solve design problems using small microcontrollers.

Next week, we will move to the iMote2 platform, which uses a 32-bit processor and runs embedded Linux.

Question 6: Please take a moment to provide some feedback on the first four labs. This question is completely optional, will not affect your grade in any way, and is completely open-ended. Here are a few topics you may wish to address:

You may include your feedback in your report for this lab, or you and your partner can individually send your feedback to cse466-tas@cs. Alternatively, you may also use the anonymous feedback form.

Lab 4 Deliverables