Monday, December 12, 2016

Recreating the sum_io

The first step is to launch vivado HLS (2016).

* Helpful Blog Post For Review: Things We Should All Know About Vivado HLS

Create a new project > name the project > Choose a location > Next

Add Files: Here you want to add the file sum_io.c you may need to download this from Mike's files if it is not already saved.
Top Function: sum_io > Next

Add Testbench Files > sum_io.test.c > Next

Solution Configuration:
Solution Name > Solution1
Part Selection > Zedboard > Finish

Project Tab > Run C Simulation

Under Source > Select sum_io.c > Directives Tab > Right Click sum_io > Insert Directive >  Interface > ap_ctrl_none

Run C Simulation

Solution > Run C Synthesis

Solution > Export RTL > Options:VHDL > OK

Now you are ready to launch Vivado (2016)

*Helpful Blog Post Fore Review: Things We Should All Know Vivado

New Project > name the project  > Choose Location > Next

Choose Zedboard Rev C > Next > Finish

Create New Block Design > Name it

Right Click > Add IP > ZYNQ7 Processing System > Run Block Automation

Now we need to add seven GPIOs:

Rt. Click > add IP > AXI GPIO > Run Connection Automation > ✓GPIO > LEDs > Run Connection Automation

Next, insert 3 AXI GPIOs, an Adder/Subtracter and a Constant.

Double click one GPIO1 > IP Configuration > ✓All Outputs > GPIO Width 15 > GPIO Width 32 >
ok



Double click one GPIO2 > IP Configuration > ✓All Outputs > GPIO Width 15 > GPIO Width 32 > ok
Double click one GPIO3 > IP Configuration > ✓All Inputs > GPIO Width 16 > GPIO Width 32 > ok
Double click one AddSub > Basic Tab > Input Width 15 > Output Width 16 > ok

Connect the output of gpio1 to the first input (A) on the add/Sub  and connect the output from gpio2 to the second input (B) on the add/sub. Next connect the output of the adder/sub to the input of gpio3. Finally connect the constant to the bottom input of the add/sub (CE). Now Run Connection Automation. Your block diagram should look like the bottom right corner of the picture below.








Next add three more AXI GPIOs.

Double click one GPIO4 > IP Configuration > ✓All Outputs > GPIO Width 16 > GPIO Width 32 > ok
Double click one GPIO5 > IP Configuration > ✓All Outputs > GPIO Width 16 > GPIO Width 32 > ok
Double click one GPIO6 > IP Configuration > ✓All Inputs > GPIO Width 32 > GPIO Width 32 > ok

Now we need to insert the HLS Repository that we made in the first step.

Project Settings > IP (on left) > Repository Manager > + > Select the path where you saved your HLS file (Ex. Sams_FPGA_projects/Sam_Sum_io/soln1/impl/ip) > Apply  > ok

Right Click > Add IP > sum_io

Now you want to connect the output of gpio4 to the top input of the sum_io and the output of gpio5 to the bottom input. Next connect the output of the sum_io to gpio6.
Run Connection Automation and your block design should be complete.


Now you can save and validate your design. 
Tools > Validate Design

Switch to the sources tab by selecting window > sources. Right click on the top level system > Create HDL Wrapper

Flow Navigator > Generate Bitstream > Open Implemented Design > ok

File > Export > Export Hardware > ✓ Include Bitstream 

File > Launch SDK > ok
Create a new empty application
Expand sumIO > Right Click on src > import > AddComp.c

Now you can program the FPGA. Once the FPGA board is programed
Right click on sumIO > Run As > Launch On Hardware

NOTE: I found that it was useful to run the code using GTKterm. I was unable to get it to work anywhere else.


Your code takes two values and adds them together. The sums are what should be printed out in the terminal. You should get one that says Xilinz Output Adder = 2 and HLS Output Adder = 4. You can change these output values by changing the input values in the code.




Wednesday, July 13, 2016

Data from NASA Lab -- Other Updates


The plot I have attached shows gamma induced events from three sources of radiation (Co-60, Cs-137, and Co-57). 

The distributions of the data contain a lot of noise (on the left side of the plot), because of the trigger level on the oscilloscope. I kept the trigger level at 10mv (any higher and I would lose valuable data) to get a good distribution of points. You'll notice that with the Co-60, everything up until about 0.04 V is noise (and there is a lot of it) and the gamma events are located on the interval between ~0.45 and 0.07 V.

For the other two sources, the idea is the same, it is just more difficult to distinguish the noise from gamma events, because the the signal produced by the gammas (multiplied by the SiPM) is much closer to the noise for Co-57 and Cs-137 than it is for Co-60. To get an idea about how much the original energy is amplified by the photo-multiplier, we can look at the Cs-137. If you look up the gamma energy associated with a radiating Cs-137 nucleus, you'll find it to be ~660 keV. And the peak voltage found in the experiment (see green Gaussian) is ~0.03 V. 

The phenomenon shows itself even more so with the Co-57, as the distribution just appears to be one smooth exponential falloff with an increase in pulse height. But, if you look closely, you'll see a second peak just to the right of the noise peak channel (red Gaussian) that represents the gamma induced events. I am convinced of this, because if I try to run the experiment with no source or a very weak one (Am-241 (~60 keV) for example), I am left with a very noisy distribution, which is to be expected. Basically, I don't see the second peak where the Co-57 has another peak.

When I first took a look at this plot from some of Georgia de Nolfo's work I was confused in thinking that the plot was just of the peak heights from one event (one csv). But upon further inspection I learned that it is a plot of a certain number of events (5-,000-10,000) and the associated peaks calculated from the PSD algorithm we are familiar with. With a better understanding of the plot, I could recreate the experiment and use the algorithm I wrote to get the peaks.

It is important to understand the gamma induced event and why we get a pulse. When a gamma ray collides with an electron inside our p-terphenyl, inelastic scattering called Compton scattering occurs. The scattered photon from this reaction will have an energy that is some fraction of the incident energy while the remaining kinetic energy is transferred to the electron. The Compton edge is the maximum possible energy deposited in the detector (electron) occurring at 180 degrees. The range of energies deposited can be determined by the scattering angle of the resulting photon. Looking at the plot below for a 500 keV photon, we can see that the maximum energy deposited in the detector is ~320 keV. 
Once the secondary photon is scattered with some fraction of the incident energy it is detected by the SiPM array. This is where the photoelectric effect takes hold. Because the energy of the gamma ray is less than the initial energy, the effect shifts from Compton to photoelectric inside the photo-multiplier. An electron is ejected from the material and multiplied inside the SiPM (keep in mind that this is a diagram of a PMT which works a little differently than the SiPM):

Other than doing the FPGA stuff with Joe, and working with the hardware in the lab, I have been tasked with comparing both the peak height and pulse shape from two competing algorithms (version 1 and version 2). Since Georgia originally sent the python code to us last summer, the code has made significant advancements in precision. An engineer named Jeff Dumonthier (who wrote the first version as well) updated it and took me through the main differences:

  1. The time of flight is the difference in the beginning of the pulse in the double scatter experiment I described in the other blog. The time of flight for these events is on the order of ~1ns. So precise calculation is needed to determine the exact instant of the CFD time-- the constant fraction discriminator time. Remember that the CFD was calculated as the value that is 25% of the peak voltage. If we can accurately find the time value that the pulse is at 25% of its peak value for both detectors, we can do a simple subtraction and see how long it took a gamma ray/neutron to cause an event in both. The way the DRS is set up, it only saves an event if both detectors are triggered. So the XML file we receive has 1024 time/voltage pairs (as opposed to the 502 pairs from the LeCroy scope) for detector 1, followed directly by the same number of pairs for detector 2. It is important to note that this 25% value is arbitrarily selected and needs (probably) to be optimized to produce the best version of our ToF measurement. The following plot shows the time of flight for a set of 1,000 events. You'll notice two distributions on the plot, the small one corresponding to neutron induced events, and the other to gammas. In this plot the subtraction is reversed between detector 1 and 2, so we can conclude that the neutrons have a longer ToF than gammas (as expected because of their greater weight and lower velocity) and gamma events are more probable than neutrons over the data collection interval. 
  2. Because time is so important now, everything in the new algorithm is calculated through the time vector. So, instead of just calculating which bin (0 to 502) the pulse height and pulse CFD are at, the new code finds both the closest time bin (real value gotten from the DRS) and the actual time value of both values. Realizing that this is sort of confusing I will describe more here:
    1. For one event in one detector, we have 1024 time/voltage pairs. With the LeCroy we had 502 time/voltage pairs. We simply marched up the leading edge of the pulse finding the index of the time/voltage value closest to 25% of the peak. We then chose a precise number of bins coming after that index to calculate both the long and short interval. The algorithm has been updated to instead of simply finding the closest value to the CFD time, it finds the exact time of 25% of the peak by fitting a line to the leading edge. Because part of the leading edge of each pulse is 'very linear' we can take some upper and lower range of the edge (say 15% to 50% of the peak) and save all the time/voltage pairs into a matrix. From there we calculate a slope and intercept. Knowing the exact 25% voltage of the peak we can subsequently calculate the exact 25% CFD time threshold. Then we can get ToF!
    2. Using the 'binning method of inexactness' I was getting a ToF distribution that had a similar shape to the one as above, but the measurements were scattered over a greater range of ToF channels. If you look at the largest number of counts for the ToF above you'll see ~170 events at the peak. With the bins the largest number of counts in the peak bin was ~35 events (withe the same 1,000 event set). 
  3. The overall goal of comparing the old and new versions (and not just going with the later version) is to find a 'lightweight' enough algorithm that produces precise enough results and can be implemented on an FPGA, aka written in such a way it could be written in HDL (or synthesized by HLS for us). The way things are calculated for each algorithm is as follows:
    1. Version 1 Agorithm:
      1. Pre-Filter:
        1. Subtracts out first time element from time vector to normalize/zero-out set
        2. Inverts voltage polarity (LeCroy gives negative vals, DRS gived positive vals)
      2. Pass 1:
        1. Baseline - finds average of constant number of voltage points preceding the leading the edge of the pulse and uses that as a zero such that postY=MAV-baseline.
        2. Performs smoothing function (moving average filter) and finds 4 greatest values of post-smoothing array. Finds average of those four to find pulse height/index.
      3. Pass 2: 
        1. Finds CFD as closest time/voltage value to fraction of the peak.
        2. Performs integration over two regions a fixed distance -- number of bins -- from the CFD index. 
        3. Divides the Long/Short interval to give Pulse Shape.
    2. Version 2 Algorithm:
      1. Pre-Process:
        1. Subtracts out first time element from time vector to normalize/zero-out set
      2. Base and Scale
        1. Baseline - uses all voltage points before a certain time value and computes average.
        2. Subtracts baseline from all voltage values (no smoothing first)
      3. Peak
        1. Fits a parabola to a fixed number of points in a range of points near the max of the pulse. Takes max of parabola to find height.
      4. Leading Edge CFD
        1. Finds CFD time/voltage by fitting a line to range of voltage values along leading edge
      5. PSD
        1. Takes time/voltage pair closest to CFD time threshold and performs integration over two regions of a fixed time.
  4. After running through the comparisons of each algorithm for the baseline voltage, peak, cfd time, and psd calculation I found that Version 1 does a good job of finding the baseline and calculating the peak using smoothing but does not do a very good job of finding the CFD time and finding the ToF. So I adjusted the CFD calculation to manually fit a line between two points, because something like polyfit in python would be too cumbersome to replicate in the FPGA. Here are the comparisons between Version 1 (Mike) and Version 2 (Jeff) for finding the peak and CFD time:
    A typical baseline voltage is -.0022 V. So the difference being ~0.0001 is not large enough to say the binning baseline calculation is useless. Version 1 matches Version 2 pretty closely.

A typical peak voltage is 0.03-0.06 V. So the difference being ~0.0001-0.0002 is not large enough to say the method of smoothing is useless. Version 1 matches Version 2 pretty closely.
Because in both algorithms the CFD voltage is calculated as a certain percentage of the peak, the difference in CFD voltage will be the same as it was for the difference in peak voltage (just a scalar modification).

A typical CFD value is 5.5e-07 so the difference for CFD time for Version 1 and 2 being 2 orders of magnitude smaller shows:  The method of finding a point directly above and below the smoothed curve CFD voltage and fitting a line between those points (to find the CFD time) does as good a job as an un-smoothed range of points 15-60% of the peak with a line fitting those points to find the CFD time. Also now Version 1 has an easier transition to C or HDL.

Instructions to replace the Analog Devices BOOT.BIN with your own

https://docs.google.com/document/d/17Dk7LKYh8bDYon4smHx6lIRhLAoP3ppRFI5bbZ-NWFQ/pub

Lab Setup

  • LeCroy WaveRunner 2Ghz Oscilloscope: Expensive scope that allows you to insert a USB stick and collect events when the trigger level is met. The data is saved into CSV files and can be processed from a directory. 

  • (Left): Light tight box that allows you to isolate gamma, alpha, beta radiation without disruption from outside light. Cables coming from the box include output cable (to the scope), power cable connected to 27V power supply (potential needed for SiPMs), and 3.3V-5 V cable needed to power the board that the SiPMs are installed on.




    • SiPM array (silicon photo-multiplier). The scintillator crystal is placed atop the array with a radiation source placed directly above that. The whole experiment is then placed in a light-tight box (pictured above). There are two types of SiPMs we are currently testing; the SensL SiPMs requiring 27 V potential and the Hammamatsu SiPMs requiring 54 V potential. The trade-off is that in flight, the SensL's will consume less power but will produce more noise than the Hammamtsu's. They are arranged in an array because the experiment is set up to be a double scatter where the angle of the incident radiation can be determined. 

    • Example of how the double-scatter experiment can be set up. Changing the alignment of the detectors, and the position of the source changes the data we see. 




    •  The second option for digitizing data. The DRS4 chip with evaluation board being fed into the laptop in the background. Data is saved in XML format and can be saved on a USB drive as well.

    • Double scatter instrument connected to DRS.


    • The big picture, my lab setup.



    Wednesday, June 29, 2016

    Vivado HLS from ROM

    This link breaks down how to read in a csv (or other data file type) and gives instructions about how to convert each of the files to header files that can be synthesized by the HLS tool. The problem is, if this is all done by the Zedboard, it would mean that converting to a header file from a csv would have to be executed one by one because the function to do so includes fopen, fscanf, and fclose (which cannot be synthesized by hls because they are too high level.

    https://forums.xilinx.com/t5/High-Level-Synthesis-HLS/HLS-ROM-from-file/td-p/654517

    So from digging the answer might be in one of many places but for now my ideas are the following:


    1. Place the function that will convert the csv to a header file in the testbench function in HLS. 
      1. This function will create the header file that can be read by the "to be synthesized source file" in HLS. The pre-filter, pass1, and pass2 can all be pipelined from there although I doubt that will speed up the process at all. If the idea is to process in parallel, then the part we want to pipeline would be the conversion (more files at once)
      2. Of course one would have to look at the time it takes to receive one sample data set (that 2x502) file that comes from the scope (I will learn later this week or next how exactly this is done and have a better idea of what direction to go). If the time it takes to create one pulse is similar to the time it takes to convert that data into a header file that can be read by HLS code, then we are in good shape because the processor will be able to keep up with the incoming data. However, if the data coming in exceeds the speed of the testbench conversion into a header file then we are in trouble, and this would seem like a good place to begin parallel processing but we are again met with the problem of fopen, fscanf, and fclose not being able to be synthesized. 
        1. So I plan on figuring out the times for each of these processes and figuring out what the best course of action is. The fear is that the data will come in much quicker thatn the processor can process, and we will be in trouble...............BUT that's why I'm thinking of other ways to solve this problem from the beginning!
    2. Extract the fopen, fscanf, and fclose functions from the stdio.h library and see if the lower level code that creates those functions can be synthesized.
      1. It would seem as though, at some level, the code HAS to be able to be synthesized, but you know, nothing ever works correctly, so there could be a third option on this blog by next week. 

    Friday, June 17, 2016

    Digilent PmodENC

    The Digilent Rotary Encoder Module

    This Pmod features a rotary shaft encoder with an integral push button. It also includes a sliding switch that is commonly used as an on/off output.

    A rotary encoder is most commonly used to detect how many "clicks" a knob has been rotated. This is the number in GTKterm labeled 'Encoder Value' and underneath that it records the angle.


    We also created a knob for the top of the rotary shaft in order to increase stability and accuracy when rotating.


    Initially we were hoping that we would be able to use the exact code and process that we use in Emily's previous blog 'Angle Encoder' but unfortunately we came across some problems. We were able to complete all of the steps successfully, however, we had some issues with the outputs. For example, when using the ALPHA encoder we were easily able to recognize a pattern within each click of the LEDs but with the new encoder we were unable to find any sort of pattern at all. The LEDs seemed to be lighting in a random fashion and the Encoder Value was not counting consecutively like it should be. Instead, it would repeat numbers several times and jump around. 

    We checked the Pmod connections to the ZedBoard, which were correct so we know that is not the problem and we also checked to make sure it was receiving power. We measured the output to be about 3.3 volts so that is also not where the issue lies. At this point we think that the problem is most likely somewhere within the code.

    If we had some more time we would try these options provided to us by Mike:

    1. Disconnect the encoder from the PMOD and hook it up to a breadboard. Then use wires to connect back to the PMOD appropriately. I have a feeling that the way the encoder is set up, it has the encoder pins opposite of the ones we are used to. So essentially, the output pins from the encoder would have to be switched (using wires) to connect to JA1 and JA2. 

    2. Leaving the encoder attached to the PMOD, change which input is the MSB. I mean that somewhere in the code, in the interrupt function, you are reading from both encoder pins, then combining the two read values into one. If it says MSB >> LSB, change it to LSB >> MSB. I do not remember the exact structure of the code off the top of my head, so you'll have to do some messing around.

    3. If both above options fail, and you really want this thing to work before you leave tomorrow, get a multimeter out, hook up power to the power pin of the encoder, and see what each encoder pin is spitting out as you rotate the shaft. If it is a different readout then what you get from the other encoders, then you'll have to do some restructuring of the code to get the encoderValue to change when it is supposed to.

    4. BEFORE YOU TRY ANY OF THESE THOUGH, make sure the design is working correctly with the other encoders. This will eliminate a lot of troubleshooting because the issue could lie with the PMOD rather than the enocders or the software.*-+


    Helpful Links:
    https://reference.digilentinc.com/_media/pmod:pmod:pmodENC_rm.pdf


    To Be Continued...

    Wednesday, June 15, 2016

    Digilent Pmod ADC

    The new Digilent Pmod ADC1 has arrived!

    Useful Links:
    http://store.digilentinc.com/pmodad1-two-12-bit-a-d-inputs/
    https://reference.digilentinc.com/_media/pmod:pmod:pmodAD1_rm.pdf
    https://reference.digilentinc.com/_media/pmod:pmod:pmodAD1_sch.pdf

    When connecting the Analog Discovery to the chip, refer to the following link:
    https://reference.digilentinc.com/_media/analog_discovery:analog_discovery_pinout.pdf

    The datasheet for the Pmod explains that the external power provided to the chip must be within 2.35 to 5.25 volts.

    Note: The sampling rate is 1 million samples per second and  will provide 12 bits of information through 16 clock cycles.

    The issue of testing this chip is the software. It appears that the pins of both the Maxim ADC and the Digilent ADC are the same so the hardware and the pmod interface should be the same as well. However, the maxim code only recognizes the maxim chips and the code on the blog from last summer that allowed us to read in a code using a function generator and the Max11205 does not work either. It is possible that there is an error in the code because the computer crashed the last day and it is possible that the final code was not saved.

    Friday, June 10, 2016

    Angle Encoder




    The first thing we want to do is clone the repository:
    1. Create a new folder in zynquser where you want to store your new files: zynquser > Angle_Encoder
    2. In the terminal, type sudo yum install git in order to install github.
    3. Find the directory where you want your files to be save: cd Angle_Encoder
    4. In this file, type git clone https://github.com/mf06engl/Angle_Encoder
    This clones the files that Mike posted on github into the folder you created.


    5. Open Vivado > Open Project > zynquser > Angle_Encoder > Angle_Encoder > Encoder Hardware > Encoder_Hardware.xpr
    (Automatically upgrade to current version if need be).
    6. If your IP blocks need to be upgraded, click the Show IP Status button near the top of the page, select all the blocks and upgrade them. If there are any warnings click OK.
    7. Run synthesis and run implementation.
    8. Generate the bitstream.
    9. File > Export > Export Hardware to Local Project (include bitstream). If a warning comes up asking if you would like to overwrite a previously made hardware click YES.
    10. File > Launch SDK.



    Once you open SDK, you should see the application project, board support package, and the hardware platform.
    The software code should be located under Encoder Software > src


    Next, we need to make sure GTKterm is open in order to read the values that we read from the encoder. 

    1. In the terminal, make sure GTKterm is downloaded. If not, go to  http://dl.fedoraproject.org/pub/epel/6/i386/ and search for  epel-release-6-8.noarch.rpm.
    2.  Once you locate the previous link, download and install the application. 
    3. In the terminal, type sudo yum install gtkterm.
    4. Then type the following: 
                           cd ..
                           cd ..
                           cd dev (in zynquser)
                           ls -l ttyACM0
       If the permission are denied (it does not show crw-rw-rw) then type:  
                           sudo chmod 666 ttyACM0


     To connect the angle encoder, we need to use a breadboard and connect wires from the encoder to the pin outs on the PMOD. Using the previous blog post from Sam, 'Connecting the ALPHA Angle Encoder to the Zedboard,' we can see exactly how it's connected. We use pins JA1 and JA2 connected to Prong 1 and 3 respectively while the 2nd Prong is connected to ground.  

    Once everything is properly connected to the Zedboard, we can program the Zedboard in SDK and launch the hardware to the board. 

    In the GTKterm we can see that it reads an Encoder Value as well as the value of the Angle. We can conclude that each turn either increases or decreases by 7.5 degrees, depending on whether it is turning clockwise or counterclockwise. Each click of a turn on the angle encoder is equivalent to 4 values, so in order to read each single value you must turn the encoder very slowly and carefully.  The Angle Encoder value is the number of clicks that it has turned from its starting position, which should be zero. When it turns clockwise, this value is negative, and when it turns counterclockwise this value is positive.

    The values are shown by the lighting of the LEDs, which only light 4 at a time, having the first 2 bits reading the outputs of Pin 1 and Pin 2 respectively, and the 3rd and 4th bits act as place holders. It acts through a process called 'bit shifting,' where it will read the values through Pin 1 and Pin 2 and then for the next turn it will move the previous measurement to the place holder and read another set of values. 

    For example, let's say the four bits are numbered 4  3  2  1  , and the first read measurement is 01. The 01 would be placed in spots 2 and 1 and would look like the following: 
    0  0  0  1 .  If the next read value is 11, the previous measurement (01) would be shifted from spots 2 and 1 and into spots 4 and 3, while the current measurement replaces it in spots 2 and 1. That would look like the following:  0  1  1  1 . This pattern would continue with every new read value.  

    When it is turned clockwise, the LEDs rotate in the following rotation:
                                    0 1 1 1 
                                    1 1 1 0
                                    1 0 0 0 
                                    0 0 0 1
     When it is turned counterclockwise, the LED's 
                                    1 0 1
                                    1 1 0 1
                                    0 1 0 0
                                    0 0 1 0 





    Part 1: The following video is of the angle encoder being rotated counterclockwise

    https://www.youtube.com/watch?v=quktJql8WBI


    Part 2: The following video is of the angle encoder being rotated clockwise.

    https://www.youtube.com/watch?v=kSy4wF8xKa8&spfreload=5











     

    Thursday, June 9, 2016

    Connecting the ALPHA Angle Encoder to the ZedBoard

    When trying to connect an angle encoder to a ZedBoard it can become confusing because when searching the internet, you can really only find examples of an angle encoder connected to an Arduino. However, after some research and sessions of trial and error, we were able to determine a successful connection option.






    When looking a the encoder, ALPHA should be listed at the top. The side that has three prongs is where we are going to be working. The three prongs at the bottom we will refer to as Prong 1-3, going from left to right. Prongs 1 and 3 are inputs and Prong 2 is ground.

    An example of the specific connections we made when using the angle encoder are, Prong 1 connected to JA1, Prong 2 connected to ground and Prong 3 connected to JA2 (These specific connections JA1 and JA2 may vary depending on the code you are using). We were successful with these connections to the ZedBoard which can be further read about in Emily's Post titled, Angle Encoder.




    Helpful Link:
    http://bildr.org/2012/08/rotary-encoder-arduino/

    Wednesday, June 8, 2016

    Waveform generator outputs

    Using the following setup where the yellow wire is the waveform generator and is connected to Vin of the max11205 chip, and the black wire is ground.

    The waveform generator is sending a constant signal of 3.6 volts. The output is 32767 decimal.

    However, I noticed that the value for the decimal  is the same for signals above 2.5 V. After looking at the datasheet (highlighted below), it is possible that the 2.5V is the maximum voltage for the chip. 


    Then the following outputs would make sense.

    2.5 V    ->        32767
    1.25  V      ->           16489
    0.625 V       ->            8323
    0        ->           160

    I don't understand why zero volts outputs 160 but if you add this value to the rest of the outputs, as you go down the list which is taking half of the previous voltage, then the output is halved as well. 

    This is also why the following line of code from menu.c includes the number 2500mV:

    printf("%d of %d samples = %d decimal.  (%d mV)\r\n",i+1,unNumberSamplesToCapture,nAdcValue,((nAdcValue * 2500)/32768));

    This is just converting the read bit value to a voltage in mV. This is done by simply multiplying by 2500mV and dividing by 32768 just as you would when solving a proportion.