The Ultra96 is an FPGA dev board built to showcase Xilinx's Zynq UltraScale+ MPSoC chip at a price point available to the hobbyist community. Users have two options for connecting to Ultra96. The first is through a webserver via integrated wireless access point capability, which a user can access from a browser on a computer connected to the same wireless network. The other option is the provided PetaLinux desktop environment which can be viewed on the integrated Mini DisplayPort video output, making it more akin to the traditional user experience of a Raspberry Pi. For this project I will be opting for the first option as it means I can leave my Ultra96 safely on the shelf, safe from my cat's war path through my apartment while I work from my couch.
In my previous Ultra96 project, I walked through how to create the base image with Wi-Fi connectivity and startup webpage. I'm starting this project from the ending point of that project.
Once the Ultra96 has been booted and connected to the desired Wi-Fi network, the home page of the Ultra96's webserver can be brought up at the dynamically assigned IP address in a browser window:
If you choose to hardcode your wireless network's info into the wpa_supplicant.conf file in the root home folder of the Ultra96, it will connect automatically at boot so you can skip the process of reconnecting to Wi-Fi using the generated peer to peer network and web app after every boot. You can then open a terminal window and just SSH into Ultra96 with the assigned IP address (my Ultra96 was assigned 192.168.1.188 on my network, which I found using the arp -a command). Even though most of this project takes place within the web application, there are still a couple things where access to the Ultra96's command line is necessary.
arp -a
ssh root@192.168.1.188
To start developing a new web page application within the Ultra96's webserver, the 'Custom Content' page provides the necessary tools. There are three components needed for adding a custom web page to the Ultra96's webserver:
1 - The application project (written in Python)2 - The web front end (written in HTML)3 - The web back end (written in Python)
Given the extra processing power that the Zynq MPSoC on the Ultra96 provides, I thought it would be interesting to see how I could put it to use in one of my favorite project application areas: software defined radio. While there are many components that go into the complete design of an SDR (more than I'd like to cover in one project) I decided to start with one of the most basic applications used in SDR and RF testing, the spectrogram. Commonly referred to as a waterfall graph, a spectrogram displays all frequencies present in a specified spectrum as they vary with respect to time over a specified time period.
This is a very useful tool as it gives a visual representation of the physical signals present in the specified spectrum. For purposes of proving in the web application in this project, I decided to hardcode two simple signals to be displayed on the spectrogram and leave the task of interfacing with an external RF signal for a future project.
As the first step to create the application after clicking on the Custom Content tab, select the 'Create Project' and the following blank text editor will appear:
Add the following code for the spectrogram:
# Spectrogram in Python
import matplotlib.pyplot as plt
import numpy as np
# Fixing random state for reproducibility
np.random.seed(19680801)
dt = 0.0005
t = np.arange(0.0, 20.0, dt)
s1 = np.sin(2 * np.pi * 100 * t)
s2 = 2 * np.sin(2 * np.pi * 400 * t)
# create a transient "chirp"
s2[t <= 10] = s2[12 <= t] = 0
# add some noise into the mix
nse = 0.01 * np.random.random(size=len(t))
x = s1 + s2 + nse # the signal
NFFT = 1024 # length of the windowing segments
Fs = int(1.0 / dt) # sampling frequency
fig, (ax1, ax2) = plt.subplots(nrows=2)
ax1.plot(t, x)
Pxx, freqs, bins, im = ax2.specgram(x, NFFT=NFFT, Fs=Fs, noverlap=900)
# The `specgram` method returns 4 objects:
# - Pxx: the periodogram
# - freqs: the frequency vector
# - bins: centers of the time bins
# - im: matplotlib.image.AxesImage instance representing data in the plot
plt.show()
#save the plot to an image file to display in the webapp
plt.savefig('/usr/share/ultra96-startup-pages/webapp/static/images/spectrogram_chirp.png')
The code generates a 100Hz sine wave that is present for the entire 20-second interval the spectrogram is measuring over a 0Hz to 1kHz spectrum, and a momentary "chirp" from 10 - 12 seconds with a 400Hz sine wave. Then using the specgram function from the Matplotlib module library, plots out the spectrogram and saves the plot to an image file that the web app can then display to the user. Notice that the output path specified for the image file is within the file structure of the Ultra96 webserver.
After adding the custom code, click 'Save File' and specify a filename. Be sure to add the .py extension as the text editor in the webserver does not do so automatically in the custom project tab.
This is one of the steps where the command line is needed. The Matplotlib package is not in the standard local Python library, so it along Cython and numpy need to be installed using the Python3.5 package manager, pip3 (Matplotlib is only compatible with Python3.5 and above so be sure you using pip3 vs pip):
pip3 install Cython
pip3 install numpy
pip3 install matplotlib
After the necessary Python packages are installed, I verified the spectrogram Python script ran as expected by executing it from the command line of the Ultra96 and verifying an image file was output to the appropriate location.
python3 /usr/share/ultra96-startup-pages/webapp/templates/CustomContent/custom/spectrogram_chirp.py
Once the spectrogram project file is verified to be working, the web page in the Ultra96 webserver needs to be created to display the output plot image. An HTML front end is needed as well as a backend in Python since the webserver itself is Python based.
From the Custom Content page of the webserver, click the 'Edit Webapp' button to bring up the Webapp Reload page.
Click 'Create Front End' to bring up the HTML text editor with a basic page template already populated.
Add the following HTML code:
{% extends "Default/default.html" %}
{% block content %}
<div class="page-header">
<h1 class="display-4"><b>{% block title %}Ultra96 spectrogram{% endblock %}</b></h1>
</div>
<!-- Start adding your code below here -->
<h1>Spectrogram - Transient Chirp Example</h1>
<meta http-equiv="explore" content="B" />
<img src="{{ url_for('static', filename='images/spectrogram_chirp.png') }}" alt = "Spectrogram Plot" />
<!-- Stop adding your code here -->
{% endblock %}
When specifying the file path for the image, the Ultra96 webserver seems to be hardcoded to look in the /usr/share/ultra96-startup-pages/webapp/static/ directory, so only the file path within that directory should be passed to the img src function (I discovered this after using the inspection tools in my browser when at first the image wouldn't come up in the web page at first with the full file path specified).
The HTML text editor will automatically append.html to the file name so type the desired filename when prompted after clicking 'Save File'.
For the corresponding back end file for the web page, the 'Create Back End' button on the Webapp Reload page will bring up a Python text editor with the basic template auto-populated that when called by the Flask server (the Python library the Ultra96's webserver uses) will pull up the corresponding front end HTML file.
Custom back end code: The only editing the template needs for this project is to simply replace all of the CHANGE_ME's to match the filename given to the front end we just created:
@app.route("/spectrogram.html", methods=["GET", "POST"])
def spectrogram():
if request.method == "POST":
return render_template("CustomContent/custom_front_end/spectrogram.html")
else:
return render_template("CustomContent/custom_front_end/spectrogram.html")
After editing, save the back end file using the same name as the corresponding front end file. Again, the back end text editor will automatically append the.py extension to the filename when saving.
Returning to the Webapp Reload page, you'll see the front end and back end files matched to each other in the table of available web pages. To be able to access this new custom web page, it needs to be added to the webserver. This is accomplished by checking the box in the 'Include' column then clicking the 'Reload Webapp' button. You only have to reload the web app the first time you create a custom web page or if you make changes to the back end file.
To reload the webserver, the board has to be power cycled which done automatically after clicking the 'Reload Webapp' button. Once the Ultra96 has power cycled, reconnect to the webserver via a browser and navigate back to the Custom Content page.
The spectrogram web page will now appear as an option. Click the 'View' button to bring it up:
In future projects, I will be revisiting this custom web page to expand on it such that it can run the project Python script over and over with different inputs and dynamically load new images of the spectrogram plot. This will just be a matter of expanding on the custom front and back end files generated in this project.
Troubleshooting:I found an interesting error in the Flask-based webserver file webserver.py of the Ultra96, when I first generated a blank test webpage (the CHANGEME web page you saw in earlier screenshots) I got a 404 Not Found error when trying to view it. Flask works by specifying all URLs and function calls prior to actually running the webserver, and when you select 'Include' for your custom web page and click 'Reload Webapp' it adds your custom backend code to the webserver so it then knows what the URL to it is prior to running the webserver.
Upon tracing through webserver.py (found at /usr/share/ultra96-startup-pages/webapp/webserver.py in the Ultra96 file structure) I noticed that my custom back end code had been autopopulated after a call to run the webserver was made. This meant the webserver was never actually passed the URL to my custom webpage.
The code to pass my custom webpage URLs is shown with the first purple bracket in the screen shot, and the second call to run the webserver after that is shown with the second purple bracket. The first call to run the webserver is circled in purple and must be commented out or deleted. Once you make this edit, the board needs to be power-cycled. Also, I found that I have to re-do this after every time I click 'Reload Webapp' if I add a new custom web page.
I'm not sure if I somehow got an old version of webserver.py when I built my image or what happened, so I don't think this will be something everyone needs to do if you started with an image on the Ultra96 different than mine. But it took me long enough to track this down that I though it was worth noting here.
Comments