Potentiometers and Colors

So so pretty

The questions below are due on Wednesday September 18, 2024; 11:59:00 PM.
 
You are not logged in.

Please Log In for full access to the web site.
Note that this link will take you to an external site (https://shimmer.mit.edu) to authenticate, and then you will be redirected back to this page.

Do not take the MCP3008 ADC and potentiomters out of lab. We need to share them across all students. When you are not working with them, put them back at the front of lab.

You now have a functioning SPI controller that can both send out and read in bits. We want to now use this to communicate with something. We have just the candidate for that: The MCP3008 eight-channel Analog-to-Digital-Converter (ADC). This device is great. It can measure, on command, eight separate analog voltages in the 0 to 3.3V range. It digitizes those measurements, quantizing them into 10 bit values, which is great since we are, after all, working in an digital system. ADC's are one of the great interface devices in existence. The MCP3008, as it turns out, is controllable over SPI so we need to figure out how to do that on this page. We know the SPI language, but now we need to figure out how to have a particular type of conversation in that language.

Download the MCP3008 Datasheet. Have a copy open on your computer since it will be useful throughout this lab!

For motivation, we're going to use this ADC to read the voltages produced by three different potentiometrs hooked up as variable voltage dividers built from potentiometrs. The overall diagram for this will be the following:

The MCP3008 ADC will interface three purely analog potentiometers to the FPGA over an SPI protocol.

If you're unfamiliar with what a potentiometer is, firstly, you should go and take 6.200. As a quick explanation, though, read through the content in the show-hide below.

Voltage dividers are pretty much my favorite circuit in the world. They can be used to explain like 90% of existence.

The canonical voltage divider takes on a format shown below. There is a fixed voltage source (which we'll say is 3.3V for our lab) and two resistors R1 and R2.

Standard voltage divider.

It can be shown using Ohm's Law and Kirchoff's two Laws (Voltage and Current laws) that the voltage v_out, with respect to the ground node, will have a value of:

v_{out} = 3.3\text{V}\cdot\left(\frac{R_2}{R_1+R_2}\right)

The resistors in effect divide the voltage according to the fraction shown above.

A potentiometer is a resistor of a fixed value between two outer legs, but with a "wiper" arm that can tap into somewhere along that resistor throughout its span. The symbol for a potentiometer is the following:

Standard voltage divider.

A "10 KiloOhm Potentiometer" refers to the fact that the potentiometer has a resistance of 10 KiloOhms between its outer two legs. The wiper touches somewhere along that continuous stretch of resistance and in the process forms a sequence of two resistors like shown. Here the variable a ranges from 0 to 1 is a sort of normalized position variable. Because of this, the sum of those two resistors is actually RT*a + RT*(1-a) = RT.

The pot as a voltage divider.

If we drop the potentiomer into the original voltage divider:

Voltage divider.

The equation from earlier turns into the following, where we replace a with an angle \alpha corresponding to the angular turn position in a rotary potentiometer:

v_{out} = 3.3\text{V}\cdot\left(\frac{R_T\alpha}{R_T\alpha+R_T\left(1-\alpha\right)}\right)

or

v_{out} = 3.3\text{V}\cdot\left(\frac{R_T\alpha}{R_T}\right)

or

v_{out} = 3.3\text{V}\cdot\alpha

So boom, we have an angle turned into a variable voltage.

We'll wire things up later on in the page. Let's first focus on setting things up in the FPGA side to talk to our MCP3008 over SPI.

Configuration

First thing we need to do is enable and rename a few pins in our xdc file to allow us to connect up to our ADC. The ADC we're using comes on a little carrier board, and we'll be plugging it into the "PMOD" connector found on the left side of our FPGA board. PMOD stands for "Peripheral Module", and allows for I/O interfacing to modules external to the FPGA. This saves you some wiring (but not all wiring...that's coming later).

Plugging in Board

Plugging in the board.

It connects to six pins (on the top row starting from the PMODA side). Two are 3.3V and Ground (from which it is powered). Those are always on. The other four pins will be used for our SPI communication bus. We need to activate those pins and bring them into our top_level.sv module.

The pins of an FPGA are all located underneath the chip in what is called a ball grid array. The pins are organized and named based on where in this 2D grid of connections they are, with columns being denoted by letters and rows being denoted by numbers. The four pins on our FPGA connected to those four pins on that PMOD connector we're plugging the ADC into are: F14, F15, H13 and H14.

In the default .xdc file we give you, those pins are commented out, and they're also given names that are too generic for our purposes. Go into the .xdc file. Find the four lines (they're right next to one another) that refer to the four pins above, and change them so that they have meaningful names for our use case. Specifically we'll use:

  • F14 for CS
  • F15 for COPI
  • H13 for CIPO
  • H14 for DCLK

A properly edited chunk of the XDC will look like the following:

set_property -dict {PACKAGE_PIN F14 IOSTANDARD LVCMOS33}  [ get_ports "cs" ]
set_property -dict {PACKAGE_PIN F15 IOSTANDARD LVCMOS33}  [ get_ports "copi" ]
set_property -dict {PACKAGE_PIN H13 IOSTANDARD LVCMOS33}  [ get_ports "cipo" ]
set_property -dict {PACKAGE_PIN H14 IOSTANDARD LVCMOS33}  [ get_ports "dclk" ]

Then in your top_level.sv file, add in four new one bit connections corresponding to these values.

  • dclk: should be an output logic and is the data clock output of your SPI controller
  • copi: should be an output logic and is the data output of your SPI controller
  • cipo: should be an input wire and is the data input of your SPI controller
  • cs: should be an output logic and is the chip select line for the SPI bus.

Talking to the MCP3008

Again, if you didn't already, download the MCP3008 Datasheet. Open it up and let's get to studying it.

First, some simple questions about the MCP3008:

The MCP3008 has how many ADC channels?

The MCP3008 has how many bits in an ADC measurement?

We're going to hook up three of the MCP3008 channels up to the three pots. We'll use MCP3008 channels 0, 1, and 2. We'll use one channel for red, one for green, and one for blue. Since we only need 8 bits for each RGB brightness amount, we'll need to use only the top eight bits of the 10 bit measurement.

Now how do we actually get the measurement?

Figure 5-1 of the datasheet is going to be key here. It shows a standard SPI transaction for this chip. Keep in mind this datasheet is written from the perspective of the MCP3008 so:

  • DIN refers to our universal COPI line
  • DOUT refers to our universal CIPO line
  • SCLK is the same as our DCLK (a rose by any other name...)

This diagram shows what the MCP3008 is expecting as far as a conversation goes. If you speak to it correctly (and give it enough time to respond back), you'll get what you want out of it.

How many data clock cycles is a full transaction for the MCP3008?

You'll notice that during a "conversation", the controller and peripheral have certain regions of time/data where they are responsible for setting specific bits and other regions where it doesn't really matter. This is important to know because when you read data back as the controller module, it is your job to know which bits actually are relevant and which are just noise/to be ignored.

Assuming a 17-bit transaction sequence, where the msb is transmitted first, what bits are the responsibility of the controller to set?

Assuming a 17-bit transaction sequence, where the msb is transmitted first, what bits are the responsibility of the peripheral to set?

Now even though the controller and peripheral only really need to set certain ranges of bits, something still needs to go on the line during those "don't care" regions. So when driving the SPI controller with a command message to send, you'll need to send a message of the appropriate size even if you are just padding "junk" in the don't care regions in order to give the peripheral enough clock cycles to send its data back.

In order to read ADC Channel 1 in single-ended mode on the MCP3008 what is a correct 17 bit value to transfer out? Check all that apply?

So as the controller, even though you only need to send a few bits to get a reading, in the context of how we wrote our SPI controller, you'll have to send a message equivalent in size to the full conversation. What you put in the "don't care' regions is totally up to you. Similarly, when you get the message back from the MCP3008, you should remember that only parts of that message are actual legit data...the rest are random bits (could be zero, one, or random).

The Final Push

Do not take the MCP3008 ADC and potentiomters out of lab. We need to share them across all students. When you are not working with them, put them back at the front of lab.

So digesting all of this, we now have some idea of what we need to send to the MCP3008 to make measurements from it.

Our strategy for interacting with the MCP3008 will be as follows:

  • Starting from rest, we will read from a channel on the ADC. We will send the appropriately sized SPI command to read from that channel, and receive the response. The appropriate bits from the response will be extracted, and those bits will be used to set one of the color channels driving our RGB controller.
  • A millisecond later we will read from the next channel on the ADC (if we previously did 0, now we do 1, previously 1? do 2, previously 2? do 0), get the response, and assign it to the next color
  • A millisecond later we will read from the next next channel on the ADC (if we previously did 0, now we do 1, previously 1? do 2, previously 2? do 0), get the response, and assign it to the next color
  • And repeat forever and ever.

On average, therefore a change to a potentiometer's output voltage will be measured by our system within 3 milliseconds of it happening. Even though we're cycling across all three, it'll happen so fast (hopefully we're noticing a pattern here in digital systems)...that the human eye won't notice.

The MCP3008 can run at a SPI clock rate of about 3 MHz or so when powered from 3.3V. So for an SPI clock period, let's target 2 MHz to be safe. That's plenty, plenty fast to do a measurement within a 1 millisecond measure period.

To give some context, ignoring any deadtime between SPI transactions, how many measurements from a single channel can we make from the MCP3008 per second?

We want our ADC readings to control the RGB LED (similar to the lab01, but with potentiometer controls instead of switches), with a channel for each color. We also want to display what we read from the ADC on our seven-segment display. Take the most significant 8 bits from each channel, concatenate it to each other with 4'h0 between consecutive channels, then display the result on the seven-segment display. For example, if the current values of red, green, and blue are 255, 100, and 13, respectively, we'd want to see FF06400d on yoru display. If it was 255, 255, 255, it should be FF0FF0FF, etc...

Thus, as you rotate each potentiometer, you should notice its corresponding color brightening / dimming, and its corresponding 8 bits on the seven-segment display showing the approximate voltage reading.

Eventually, you should hopefully get to this sort of behavior:

To help you get started in this, we wrote some starter code that should be dumped into the end of your current top_level module. This code will not run right away and will require studying on your part to get integrated (there are incomplete lines which you need to figure out, for example). Nevertheless, your answers from above, this code, and your powerful, powerful brain need to get this all working so we have that nice RGB control with seven-segment readout.

Because I am a just and benevolent mentor on digital design journey, I've annoted spots in the code where you will need to be making changes with //MUST CHANGE comments. There may be a couple others in your currently existing top_level that also need to get modified/commented out to avoid collissions.

  //NEW CODE TO ADD IN
  //rgb controller instance (from last week!)

  logic [7:0] red, green, blue; //8 bit channels to hold color values
  assign val_to_display = 32'b0; //MUST CHANGE

  rgb_controller mrgbc (.clk_in(clk_100mhz),
                        .rst_in(btn[0]),
                        .r_in(red),
                        .g_in(green),
                        .b_in(blue),
                        .r_out(rgb0[0]),
                        .g_out(rgb0[1]),
                        .b_out(rgb0[2]));

  parameter ADC_DATA_WIDTH = 2; //MUST CHANGE
  parameter ADC_DATA_CLK_PERIOD = 2; //MUST CHANGE

  parameter ADC_READ_PERIOD = 100_000; //read one channel of ADC every millisec

  //SPI interface controls
  logic [ADC_DATA_WIDTH-1:0] spi_write_data;
  logic [ADC_DATA_WIDTH-1:0] spi_read_data;
  logic spi_trigger;
  logic spi_read_data_valid;

  //built in previous section:
  spi_con
  #(   .DATA_WIDTH(ADC_DATA_WIDTH),
       .DATA_CLK_PERIOD(ADC_DATA_CLK_PERIOD)
   )my_spi_con
   ( .clk_in(clk_100mhz),
     .rst_in(sys_rst),
     .data_in(spi_write_data),
     .trigger_in(spi_trigger),
     .data_out(spi_read_data),
     .data_valid_out(spi_read_data_valid), //high when output data is present.
     .chip_data_out(copi), //(serial dout preferably)
     .chip_data_in(cipo), //(serial din preferably)
     .chip_clk_out(dclk),
     .chip_sel_out(cs)
    );

  //counter from week 1. that will count up and we'll use for triggering
  logic [31:0] select_count;
  counter //include your regular counter from week 1 in source!
  select_counter
  (  .clk_in(clk_100mhz),
     .rst_in(sys_rst),
     .period_in(ADC_READ_PERIOD),
     .count_out(select_count)
  );
  //adc_count used for cycling through measuring ADC channel 0, 1, and 2
  logic [1:0] adc_count;

  always_ff @(posedge clk_100mhz)begin
    if (spi_read_data_valid)begin//if the SPI has received message back:
      case(adc_count) //update appropriate values based on adc_count
        0: //MUST CHANGE
        1: //MUST CHANGE
        2: //MUST CHANGE
        default: //MUST CHANGE
      endcase
    end
    if (select_count=='d1)begin //once a millisecond select_count==1
      case(adc_count) //look ahead:
        0: begin
          spi_write_data <= 0; //MUST CHANGE
        end
        1: begin
          spi_write_data <= 0; //MUST CHANGE
        end
        2: begin
          spi_write_data <= 0; //MUST CHANGE
        end
        default:begin
          spi_write_data <= 0;
        end
      endcase
      spi_trigger <= 1; //trigger spi transaction (channel read based on case above)
      adc_count <= adc_count==2?0:adc_count + 1; //keep cycle to 0, 1, 2, 0..
    end else begin
      spi_trigger <= 0;
    end
  end

Assembly

At some point you're going to want to actually integrate your system together. As already stated, the ADC can be plugged nice and pretty into the PMOD connector like shown:

Plugging in Board

Plugging in the board.

From there you need to wire it up. Here's the schematic again for your convenience.

The MCP3008 ADC will interface three purely analog potentiometers to the FPGA over an SPI protocol.

You should grab some potentiometers, some wire, a breadboard and build this and integrate it with your ADC.

image_of_wiring

What some wiring could look like. Do not rely on this photo for details. Use the schematic. Use the schematic. Use the schematic.

For reference, a breadboard is just an ordered set of metal connectors with grabby things. It'll let you assemble your board without having to hold all the parts together.

How a breadboard works.

The potentiometers should be able to fit right into the board, but remember that not all pins on the pot are interchangeable.

Potentiometer Wiring

Potentiometer Pinout. There are three terminals. Two of them (the outer ones) are interchangeable. The middle one (the wiper arm) is unique. Make sure to wire them up appropriately.

Do not take the MCP3008 ADC and potentiomters out of lab. We need to share them across all students. When you are not working with them, put them back at the front of lab.

Logic Analyzer Debugging

Let's say your systme doesn't work right away (happens to the best of us). This is not a system you want to debug with an LED or by just looking at your stuff...the clock speed is 2 MHz. Hopefully, your SPI module is working fine (why you/we tried to test it on the previous page), but you never know..we can only test so much. You're going to need to read actual signals to make sure things are working correctly. For debugging our system connect the Logic Analyzer that came in your kit. If you haven't already set this device or software (Pulseview) up, go to the documentation page for it.

If you are on a M1/M2/M3 silicon Mac, there is a good chance that this software will not work on your computer, so you should log onto a lab machine and use it there (your kerberos and MIT ID are your login credentials). When logged into the machine, you can launch PulseView from a terminal using pulseview.

Once you have things installed/verified, connect your logic analyzer probes to the COPI, CIPO, DCLK, and CS signals (as well as GND). Set things up to grab 500 samples at 16 MHz on the falling edge trigger of the CS signal. Do some general captures until hopefully you're seeing some signals showing up. Click on the channel that CS is on and set a falling-edge trigger on it. That way when you press run it will only capture the relevant portion of time. For example, with my falling trigger set you should get something similar to below:

wired_zoom_in

The board, interfacing to the potentiometers, with the Logic Analyzer

Coloring may be different depending on what probes you used to hook up to your device.

In the capture below, I am triggering on the falling edge of D3 which corresponds to the CS pin. We can see, very clearly:

  • A clock signal on D0 corresponding to DCLK
  • Some content at the beginning of the transaction on D2 corresponding to the COPI line (the initial read instruction from the FPGA to the MCP3008)
  • Some content towards the end of the transaction on D1 corresponding to the CIPO line (the MCP3008's ADC 10 bit channel measurement response to the FPGA).
  • The CS signal returning high after the completion of the transaction.

SPI Controller waveform 1

Logic analyzer capture of an transaction

for your checkoff make sure you have some logic analyzer captures to show your staff member.

Checkoff 3:
Show us your Potentiometer to RGB Controller working. You should also show us your signals on your logic analyzer. No logic analyzer, no checkoff.