This was a fun yet initially challenging experiment, to find a convenient way to read in data at a reasonably high speed on a BeagleBone Black. This photo shows the results from a mobile, showing a couple of sampled waveforms (100kHz and 1MHz sinewaves in this case).
This was another capture of the same signals on a PC (this is an older picture with a x10 probe so the amplitude is a little low in the photo - it should fill the screen).
What does it do?
In its current state, it grabs analog data from an ADC, and dumps it into memory on the BBB, ready to be displayed or further processed. It could be used for gathering analog information from sensors, CCDs or other data acquisition use-cases. To be reasonably useful, the desire was for it to support 20Mbytes/sec of data or more. It does achieve this, but it is for further study to find higher speed methods.
How does it work?
A few different ways were considered. The initial approach was using an FTDI device (USB interface). However the method described here just feeds data directly into the on-chip 200MHz PRU that is part of the Beaglebone’s AM3359 chip. Other methods are possible too.
It was also desired to have an external clock, so that the data could be sampled at a determinable jitter, so that it could be useful for frequency analysis, or maybe Software Defined Radio (SDR).
The overall approach that was taken is outlined here.
The analog signal was amplified and fed into a high-speed ADC (A parallel bus ADC is needed in order to achieve high throughput).
A pre-built amp+ADC board was used from KNJN (note: in my opinion it is not a good choice, because it is closed source; there are no circuit diagrams for it so it is hard to modify it, the datasheet is sparse and also it is expensive; better to construct one up manually).
A linux application (called adc_app in the diagram) was used to kick off the PRU code which reads in data and dumps to shared memory. Once complete, the adc_app stores it in a .csv format file.
I wanted to try out Node.js ever since a recent blog post, so some very basic code is used to create a HTTP server. The real-time comms between the browser and web server is achieved using Socket.IO which is a way of passing arbitrary data.
A photo of the overall system:
A bit more detail:
The underside was more untidy!
As mentioned, a ready-built amplifier and ADC board was used. The on-board oscillator was disabled, so that an external one could be fed in. I needed a clock of 20MHz or less, but I only had a 32MHz oscillator at hand and didn't want to wait (and the local Maplin store doesn’t sell any 3.3V-compatible logic to divide by two!) and I’m not entirely sure how long the shared memory write takes, and I did experience lost samples with the 32MHz oscillator. I plan on trying frequencies in the range 14MHz-20MHz to find the upper limit for missing no samples; for further study!
Note that some ADCs will have specific requirements related to the clock and duty cycle.
The ADC on this particular board was ADC08200 but an ADC08100 or ADC08060 could have been used at lower cost).
These were extremely important for two reasons. One reason is that the pins to the BBB that I wished to use need to be isolated during power-up, because they are used for selecting the boot method. If there was any unusual level on the pins upon power-up then the BBB will not boot from the eMMC. So, a tri-state buffer is needed.
The other reason is that there is a fair bit of capacitance and it is highly likely that the ADC may not be able to directly drive the pins at high speed. I actually came across this problem while trying to connect a camera to the BBB. I struggled for days without realising that the camera could not support the load. So, the buffers are likely to be essential for most designs using the pins that were selected. I used a 74LVC244A device as a buffer.
Note that the clock also needs a buffer, unless significant jitter is acceptable. No tri-state is required here, so I used a MC74VHC1GT50.
The PRU code uses shared memory for communication. I designated a single byte of shared memory to be used for commands. When run, the PRU sits in a loop waiting for the command to instruct it to begin the data capture. PRU GPI mode is used which allows inputs to be read at the processor speed of 200MHz with no varying latency. Just a few instructions are needed to populate the data into shared memory. No attempt was made to pack the data, and 32-bits are used to store the 8-bit sample. This is not such a bad idea, because in future the ADC could be swapped out to a (say) 10-bit ADC with no code change on the PRU.
There are two PRUs in the AM3359. There are a total of 12 PRU GPI capable pins available that are connected to PRU 1 which are brought out to port 8 on the BBB. So, this means that realistically 10 or 11-bit is about the limit for high-speed parallel ADCs connected in this manner. Still, at (say) 16MHz clock, this would equate to 20Mbytes/sec of data for a 10-bit ADC.
EDIT: See comments section - on PRU0, all 16 pins are in theory available).
The pins used are shared with the HDMI interface; it proved necessary to disable the HDMI interface by recompiling the device tree file in the /boot folder (EDIT: See Brian's comment below for a better method to disable HDMI). Since I wished to display the data using a web browser, I have no issue with losing the HDMI.
Once the data has been captured (2000 samples in this example), the command byte is acknowledged, so that the Linux hosted application can know that the PRU has completed. The PRU now sits and waits for a new instruction from the Linux hosted application.
These were the pins used. They were used as D[0..7], CLK and a *EN signal for controlling the octal tri-state buffer.
Linux hosted application
The adc_app program is very simple (C code); it downloads the assembled code into PRU1 and executes it. The resultant data in shared memory is dumped to a text file and then the program exits.
The Node.js application creates a HTTP server (no need for apache!) and a Socket.IO connection. This sits and waits for a connection from any web browser. Once it receives a connection, it will send a handshake and then wait for a ‘capture’ command from the web browser. It then calls the adc_app program. Once complete, it opens the file of captured data and transmits it over the Socket.IO connection line-by-line. This is very inefficient, but it is proof-of-concept code that could really be optimised.
The web page served up contains some small bits of code to handle the Socket.IO connection and send a ‘capture’ request when a button is clicked and to display the received data with a canvas element and pixel manipulation.
In conclusion, it is possible to read analog data with low jitter at fairly high speeds without any external FIFO or logic (beyond a simple buffer IC), while continuing to run Linux applications such as a web server. It is also nice that a web-based UI can be rapidly created using Node.js.
Note: It is still for further study how long captures can be sustained and read off by the Linux application without any data loss. If it can be sustained, then it could be useful for SDR applications just about, although a higher speed (and better ADC) would be preferable.
Note2: The waveforms used to test out the system were generated by the same BBB using a low-cost ‘direct digital synthesis’ (DDS) board. That’s a subject for a later date.
Using the code
Disable the HDMI as mentioned in the comments.
You may need to install Socket.IO. Type this to install it:
npm install socket.io
Create a development folder and then the attached code can be unzipped to (say) /home/root/development/adc.
make clean make cp BB-BONE-HSADC-00A0.dtbo /lib/firmware/ source install_hsadc_cape.sh node index.js
Then, navigate to http://xx.xx.xx.xx:8081/index.html
If you want to make subsequent changes, there is a bug in the makefile, and you will need to issue 'make clean' before typing 'make' whenever any change is made in the C code.
If you just want to reassemble the PRU code, type 'make pru' (no need for 'make clean').
adc_v1.zip 7.1 KB