This is an old revision of the document!
In this task you will complete the implementation of your laser-tag system and test it by connecting the transmitter output (generated by your transmitter state machine) to the Analog-to-Digital Converter (ADC) input. The connection between the transmitter pin (JF-1) and the ADC input are supplied via circuitry that is mounted on the development board to which the ZYBO board is mounted. Though you don't necessarily need to know all of the circuit details, a schematic of the related board circuitry is provided in the Resources Section below. Note: if you don't remember what an ADC is, you can review ADCs by consulting the concept on the web.
You will complete the system in this task by doing the following:
isr_function()
that is contained in isr.c.#define RUNNING_MODE_M3_T3
. Note that these files already exist in your project directory: ~/ecen390.The goal is to completely test your system sans guns, receiver boards and transmitter boards, and to completely verify that everything works correctly. When you connect your receiver and transmitter boards in Milestone 4, things should go pretty smoothly. If things don't work properly when you connect your boards and guns, you will know that the problem lies outside your software – you have already tested it completely. Thus you can focus on finding problems with your boards, cabling, etc. As you can guess, you would not want to debug your signal-processing software in conjunction with guns, receiver and transmitter boards, etc. That would be painful and frustrating, and a recipe for failure.
./check_and_zip.py 390m3-3
Note: For this milestone and in the future, do not use interval timers in your code as these are used for computing run-time statistics for debugging purposes.
The System Organization Diagram - Click the image for a larger view.
The loopback path (red lines) from the transmitter to the receiver is described in more detail on the loopback circuitry page. It is used for testing software without connecting a gun and other circuitry to the system.
The links below provide demonstration videos for shooter and continuous mode, demonstrate how to change compiler optimization settings in the Xilinx SDK, and lead to sites that discuss different sort algorithms etc.
Note that the following files are provided in your ecen390 project directory. Refer to the comments above each function for more details.
You are expected to create and implement the following files. See the provided header files (.h) for a description of each function.
Implement a circular buffer in buffer.c that is dedicated to storing incoming integer data from the ADC. The code is similar to the code needed for queue.c. Why not just define a queue from queue.c and use that? You absolutely cannot under any circumstances use any floating-point data types (double
or float
) in any function that is invoked by an interrupt routine. The isr_function()
is invoked by an interrupt routine, so when the ADC data is read and placed into a buffer, no floating-point types are allowed.
The solution is to create a circular buffer that stores integers of type uint32_t
. Implement a set of functions as follows:
buffer_init()
: this initializes the ADC buffer much like queue_init()
does. It would make sense to have isr_init()
invoke this function.buffer_pushover(uint32_t value)
: this is similar to queue_overwritePush()
with an uint32_t argument instead of a double. If the circular buffer is full, overwrite the oldest value.buffer_pop()
: this is similar to queue_pop()
that returns a uint32_t instead of a double.buffer_elements()
: this is similar to queue_elementCount()
.buffer_size()
: simply returns the capacity of the buffer in elements.
Note: you will have to write most of this ADC buffer code. See ADC Buffer Sketch below for code to get started. You can also refer to your queue.c code for inspiration (but don't copy any double
data types if you cut and paste).
Carefully test your ADC buffer code independent of the detector. You can do this by uncommenting buffer_runTest()
in main.c. It will not be possible to fully test your detector implementation if the buffer is not working properly.
Continue the implementation of isr.c
isr_init()
:tick()
functions).buffer_init()
.isr_function()
:
Use the function: uint32_t interrupts_getAdcData()
. It is declared inside interrupts.h. When invoked, it returns the current value from the ADC. The ADC is programmed to run continuously. As such, you are just grabbing the most recent value from the ADC. Once you get the value, put it into the ADC buffer using buffer_pushover()
. This task must be performed at the 100 kHz rate so place this code inside isr_function()
.
When you invoke detector()
, perform the following steps.
buffer_elements()
for this. Call this amount elementCount
.elementCount
times.interrupts_disableArmInts()
. You must disable interrupts briefly while you pop an element from the ADC buffer. Otherwise, if an interrupt occurs while you are “popping” a value from the buffer, the interrupt routine and your detector()
routine may simultaneously access the ADC buffer and may cause indexIn
, indexOut
, or some other field of the ADC buffer to be miscomputed. This kind of problem can be very difficult to track down because it is hard to reproduce, so it is best to avoid the problem in the first place.buffer_pop()
for this). Place this value in a variable called rawAdcValue
.interrupts_enableArmInts()
.rawAdcValue
to a double
that is between -1.0 and 1.0. Store this value into a variable named scaledAdcValue
. The ADC generates a 12-bit output that ranges from 0 to 4095. 0 would map to -1.0. 4095 maps to 1.0. Values in between 0 and 4095 map linearly to values between -1.0 and 1.0. Note: this is a common source of bugs. Carefully test the code that does this mapping.filter_addNewInput(scaledAdcValue)
. This provides a new input to the FIR filter.filter_addNewInput()
has been called 10 times (decimation). If you have just run the filters and computed power, also do the following:
You will implement the detector in detector.c
. The header file detector.h
is already provided with the laser tag project. Here is an overview of the required functions.
detector_init()
: Initializes the detector module. By default, all frequencies are considered for hits. The function assumes the filter module is initialized previously.detector_setIgnoredFrequencies(bool freqArray[])
: You pass it a preinitialized array of 10 booleans. If freqArray[0] is true, for example, no hits should be registered for this frequency when you run the detector() function.detector(bool interruptsCurrentlyEnabled)
: This runs the entire detector once each time 10 new inputs have been received, including the decimating FIR-filter, all 10 IIR-filters, power computation, and the previously-described hit-detection algorithm. detector()
sets a boolean flag to true if a hit was detected. The interruptsCurrentlyEnabled flag will be set to true if interrupts are enabled and false otherwise.detector_hitDetected()
: Simply returns the boolean flag that was set by detector()
. Example code provided in runningModes.c calls detector()
and then calls detector_hitDetected()
to determine if you have been hit.detector_clear()
: This function simply clears the aforementioned flag.detector_getHitCounts()
: This function simply copies the values from the detector_hitArray into the supplied argument. You provide an array of size 10 as the only argument. After invoking this function, the array will contain the same values as detector_hitArray.detector_runTest()
: When invoked, this function will test the functionality of your detector software. This test function is described below.A sketch is given below as a starting point for an implementation of buffer.c. You will need to finish the implementation.
#include "buffer.h" // This implements a dedicated circular buffer for storing values // from the ADC until they are read and processed by the detector. // The function of the buffer is similar to a queue or FIFO. // Uncomment for debug prints // #define DEBUG #if defined(DEBUG) #include <stdio.h> #include "xil_printf.h" // outbyte #define DPRINTF(...) printf(__VA_ARGS__) #else #define DPRINTF(...) #endif #define BUFFER_SIZE 32768 typedef struct { uint32_t indexIn; // Points to the next open slot. uint32_t indexOut; // Points to the next element to be removed. uint32_t elementCount; // Number of elements in the buffer. buffer_data_t data[BUFFER_SIZE]; // Values are stored here. } buffer_t; volatile static buffer_t buf; // Initialize the buffer to empty. void buffer_init(void) { } // Add a value to the buffer. Overwrite the oldest value if full. void buffer_pushover(buffer_data_t value) { } // Remove a value from the buffer. Return zero if empty. buffer_data_t buffer_pop(void) { } // Return the number of elements in the buffer. uint32_t buffer_elements(void) { } // Return the capacity of the buffer in elements. uint32_t buffer_size(void) { }
Now that you have completed the previous milestones, you have code that can compute the total power that passes through each of your IIR-based bandpass filters. Now what? Here are some considerations.
We will use an algorithm that adjusts the threshold based upon the current power contained in the outputs from all 10 band-pass filters.
The detection algorithm consists of the following steps:
Here is an example of the hit-detection algorithm in operation. Assume the fudge-factor = 5. Let's say that we retrieve the current power values for all 10 frequencies using the previously-implemented function: filter_getCurrentPowerValue(uint16_t filterNumber)
. The retrieved power values for the 10 frequencies for this example are:
After sorting in ascending order, we get the following:
The median value (sorted element #5) is 25 from the band-pass filter for frequency 8. For this example set of data that would mean that you would only detect hits for values that are over the threshold value of 25 (median value) * 5 (fudge-factor) = 125. The band-pass filter for frequency 0 has the maximum power value (150) which is greater than 125 so we would detect a hit.
Let's run the detector again with another set of data. After sorting, the power values from the band-pass filter outputs are as follows:
Our median value (element #5 in sorted order) = 45. We compute the new threshold by multiplying 45 * 5 (fudge-factor) = 225. Our maximum power value (150) is less than our computed threshold (225) so no hit is detected. The reason no hit is detected is because the maximum power is not sufficiently greater than the power contained in the outputs of the other band-pass filters.
Note that these numbers are provided solely for example. The actual numbers you will encounter in your system will be quite different.
A wide range of fudge-factor values will work when using the loop-back circuitry on the board for this task. I have successfully tested systems with fudge factors as high as 10,000. However, such high values won't work when you attach the guns. For this task, you can probably just select a value between 200 and 1,000. If you note false detections during shooter mode, raise the fudge-factor until they are eliminated.
The general idea behind this detection approach is that the threshold tracks the current background noise to some degree. Thus if the frequency channels all have power values that are a little high, the computed threshold also tracks higher. Vice versa, if the frequency channels all have power values that are lower, the computed threshold also tracks lower. In practice this detection strategy has worked quite well, often achieving distances of 100' in daylight.
You will implement this hit-detection algorithm in this task.
To pass off your system to the TAs, you must do the following:
You will write detector_runTest()
and demonstrate its operation to the TAs. Interrupts are not enabled for this test.
detector_runTest()
will test your hit detection algorithm following the steps described below. As a suggestion, organize your hit detection algorithm into a local sub function named hit_detect()
so it can be called by the test. This sub function should also be called by detector()
.
For this test, simply provide power data for your hit detect function by calling filter_setCurrentPowerValue()
for each of the 10 frequencies. Then call your hit detect function, which should retrieve the power values. The isolated test must perform the test upon two sets of data as described below:
filter_setCurrentPowerValue()
) and provide a fudge-factor value that will detect a hit when you invoke hit_detect()
. After calling hit_detect()
, you would invoke detector_hitDetected()
to determine if a hit occurred.hit_detect()
. Use the same fudge-factor as you used for the first set of data. Again, invoke detector_hitDetected()
to determine if a hit occurred.Enable the shooter and continuous mode test functions provided in main.c. Demonstrate your system in both shooter and continuous mode and show that your system runs the same as the videos:
While in continuous mode, allow the graphical display to settle for at least 10 seconds (by not switching frequencies) and then press and hold BTN3 until the program exits and displays the run-time performance values. Similarly, for shooter mode, don't shoot for about 10 seconds before pressing BTN3, again, so the graphics will settle and so that the performance values are more accurate.
Show the performance values to the TAs. In general, these run-time values should be reasonable:
Note that the performance values shown below are “ballpark” figures. Don't worry too much if your statistics vary a fair amount from those shown below.
The picture shown below is for shooter mode.
The picture shown below is for continuous mode.
TAs: please pay attention to the following during pass off and when you are grading source code.