Help wanted - Oscilloscope!
MattMatz
Posts: 87
For those of you that have played with the graphing feature in BlocklyProp - it's been my personal pet project since its inception, and I really want to make it something special.
Here's what's on my wish list - making a basic oscilloscope. I've tested the ability of the graphing engine in a web browser to redraw 1000 samples every 5 ms, which is more than fast enough on the computer/browser end (see example here).
There is also a simple library written for the ADC on the Propeller Activity Board (ADC124S021) that can collect ~100 ksps for 4 channels... Not super high frequency, but enough for classroom/learning/basic use.
Here's what I would need - some Propeller C code that can use this library (or a library of your own design) to send bursts of samples via the terminal. Ideally, you would set things like the trigger (none/rising/falling & threshold?), sample duration, and sample frequency (ie. sample for 2ms every 25 ms...)
The terminal in BlocklyProp can handle the full ASCII character set, so some sort of encoding scheme could be developed to transmit the samples to the computer very efficiently, and I can build all the the pieces for the browser, including blocks, settings, user interface, buttons, etc.
Anyone up for the challenge?
Here's what's on my wish list - making a basic oscilloscope. I've tested the ability of the graphing engine in a web browser to redraw 1000 samples every 5 ms, which is more than fast enough on the computer/browser end (see example here).
There is also a simple library written for the ADC on the Propeller Activity Board (ADC124S021) that can collect ~100 ksps for 4 channels... Not super high frequency, but enough for classroom/learning/basic use.
Here's what I would need - some Propeller C code that can use this library (or a library of your own design) to send bursts of samples via the terminal. Ideally, you would set things like the trigger (none/rising/falling & threshold?), sample duration, and sample frequency (ie. sample for 2ms every 25 ms...)
The terminal in BlocklyProp can handle the full ASCII character set, so some sort of encoding scheme could be developed to transmit the samples to the computer very efficiently, and I can build all the the pieces for the browser, including blocks, settings, user interface, buttons, etc.
Anyone up for the challenge?
Comments
Not super fast, but most every PC has a Sound card.
https://github.com/rbehm-ibb/PropScope/tree/master/prop
and this in Python,
https://github.com/rosco-pc/OpenScope/tree/master/app
jmg - thanks! I'll see what I can glean from those repos. The UI part is actually the easy part for me as I'm more of a Javascript guy than an embedded C/C++ guy.
I'm hoping that what I'm asking is simple - just getting lots a data from the ADC to the terminal as fast as 115200 bps will allow (since I'm not too keen on messing with the default terminal speed in BlocklyProp - that's a whole different can of worms). The UI/JavaScript end might even be able to determine things like the trigger/etc from raw data (although that's probably best handled on the Prop).
hehe, "lots a data" and 115200 bps are something of a contradiction.
To stream you would need binary, and 12b needs 2 bytes, so that's 5760 dots-per-second update. (that's about 1/8 the speed of a 'sound card scope' sample rate)
If you pace conversions at or below that speed, Prop is just a bridge (SPI-UART), but that's modest speeds - however it is quite simple, and may be enough for your demos.
You could do a music tuner with quite good precision, as the Prop crystal sets the timebase.
The ADC chip can manage 200k updates, ( and there are 500k and 1M siblings, for modest price changes)
Anything > 5760 needs single shot capture/send, and there Prop does need to manage trigger. It could grab ~100ms of one chan, or ~25ms each of 4 chans at 200k, but needs ~3.5 seconds to send those ~20k samples to the PC.
Also useful could be a Logic-Analyser type capture. Prop could send 4 Logic channels streamed, at 17280 samples per second.
Enough to show a servo working, but somewhat coarse.
A time-stamp Logic Analyser would get to something under 1us edge precision, for a low average edge rate (eg useful for Servo, 9600 baud comms )
My thought is that the scope would grab 2000 (or maybe only 1000) samples, then go into sending mode (to the PC/terminal), and when that's done, grab the next 1000-2000 samples (or at least pace things that way simultaneously across cogs). Our eyes can't perceive things much faster than that anyway.
Whether the scale of the viewing window is 5ms or .05ms, you'd be looking at 1000-2000 samples. Do forgive me - since this is only at the very early stages of an idea, I haven't done the math on what's possible or not yet.
Additionally, I'm okay limiting it to two inputs (analog), and maybe supporting 4 down the road in a logic analyzer mode.
I think you're right in that the Prop would have to handle the triggers.
The nice thing is that the graphing engine draws lines as SVG paths (bezier curves), so it "fills in the blanks", which might be advantageous, too.
I would be quite careful with that - can it also show the sampling dots ?
A problem with polished smooth curves is, it can give the illusion of precision, when none exists....
Snapshot and send gives the highest sps (short term), but the continual send mode 'feels' more like an analog scope, in that you can vary volume / amplitude and get instant change.
Continual send should be the easiest to get working initially.
Prop could grab up to 20k samples, but that's then a quite long send-time, so a user choice of samples-per-packet-burst lets them trade off speed against X-axis reach. 1~2k may be a usage trade off.
I'd suggest you also look at the .RAW file that LTSpice uses.
That's a simple N-point, floating point number X=Time,Y=amplitude file, that you can load into LTSpice (et al) and measure/zoom.
The topic was build a simple scope to demonstrate with blocky prop how a scope works.
I would like to try and write that library.
I have the original Activity Board and did not realize that there was not a library for that ADC.
Have not had to use it so did not see a need to write a library for it.
I think the Activity Board WX uses the same ADC.
Mike
Exactly! Simple is the goal. There is a library (I linked it in the original post), but it's only just the SPI driver for the onboard ADC - we'd need something that collects, does some minimal processing, and transmits them via serial. It does need to be fast, but I don't think we need we need high refresh rates.
Keep me posted on what you are able to accomplish - If you're up for it, let's make this happen!
Thanks!
Matt
By all means use the cogs .
The need to capture, then read, is more related to the quite slow serial port rule of 115200.
Looking at the ADC, it has a simple fixed 16 clock frame, with 2 index bits shifted in, and 12 ADC bits shifted out.
Interleaving an ADC read with UART TX in a single COG, looks doable at a glance, for a 20 bit-slot total 2 byte frame. ie Very simple, fixed rate, bridge streaming.
A full burst read at ADC peak speeds of 16 Clks at 200k (+?) would need 2 COGS as you say.
Playback at that speed would need ~ 4MBd, which starts to thin out the candidate UARTS. eg CP2102N can be set to 4MBd, but may need HW handshake enabled to hit that reliably
Possibly a round 100k sps could support over a 2MBd continual stream link ?
jsfiddle.net/8tq7xvmg/73/
180 x 2 channels samples every 65 ms happens to be what can be squeezed into 115200 bps.
The axes can be scaled however we want. Since the ADC chip on the ActivityBoard can do 200 ksps, that means that the X axis could zoom down to ~1 ms. Even at 100 ksps, a zoom down to 2ms isn't bad - enough to show servo pulses or medium-frequency audio.
Would definately want an edge trigger capability, as well as letting the prop prep all of the data, since there wouldn't be any wiggle room in the sending of that data.
That's quite nifty. I like the 'curves + dots'
I'm not sure the Prop needs to prep all the data, and the more I look at an interleaved bridge code, the faster that can get.
ie You might pack this into one COG, with no RAM cost, which means it becomes rather more a clip-on useful item for debug, as well as education.
For this, you run the UART and SPI at the same time, and one UART bit is sent, for every SPI bit R/W.
A little bit of 'housekeeping footwork is needed in the middle', when you have SPI bits arriving and no TX slot yet, but that's a primed FIFO to sort.
Most bits are 4~5 lines, one bit is 6~7 lines, unless I swap the lower nibble bit order, in which case that drops to 4~5 lines.
Bit reversal you would do via a table anyway, so nibble order is manged when that table loads.
Hmm, thinking some more, I think the ADC will tolerate a paused clock during read, in which case this gets simpler, as no mini-FIFO is needed.
Do you have any FT2232H / FT232H modules there, to do some real Serial crunch tests ?
ie What baud rate can your javascript keep up with ?
Does your 'redraw 1000 samples every 5 ms' include a serial buffer read ? - that's ~200ksps, which is quite decent.
So was that 'redraw 1000 samples every 5 ms' a flat-out rate ?
3 of the streaming 20 bit-slots have spare time, so they can poll a RXD for Start/Stop type commands for simple pc-side channel/mode/go on the streaming. (you would send a double 0x00,0x00 to force a stream break )
Why not ? Useful to know just where the ceiling is, on Baud + javascript.
Really? -O0? Why not -Os? Cmm I get, but it's not like anyone's looking at the generated assembly for Blockly apps, right?
C-wrapper around C++ is quite easy. For instance:
With that in mind, what would you think if I wrote it using PropWare classes? That'd be easier for me of course because I know those classes inside and out, but it would entail dropping a bunch of new code into Simple (such as my own UART object, and probably the Printer, and others). I'm guessing that's not ideal. But... if that doesn't scare you off, I'm probably up for it! However... I do worry about that -O0 flag. No idea what kind of code space the PropWare classes will consume with that flag.
The -O0 flag is because for some time, we couldn't figure out how to get BlocklyProp to generate "volatile" for variables shared across cogs. We have figured it out since, but have not done the testing required to implement the change (which would then allow other optimization settings to be used). It's on the list, but it's priority is medium...
Since I don't know anything about PropWare classes, ????
I'll try to do some experimenting with the baud rate, and 1) see if I can change it on the fly within BlocklyProp, and 2) see how fast it can go.
The PropWare classes are a collection that I wrote with the intention of providing a complete HAL, much like Simple and libpropeller. However, unlike Simple, I used a few more complex language features than Simple (as well as C++ in general) to achieve better performance and higher code reusability, including inline assembly + fcache, interfaces/polymorphism, and templates. Usage of PropWare definitely requires a more mature grasp of C++ - it was never my intention to entice grade school children - but what you get is pretty cool. One of my favorite demos in PropWare is the one for my "Runnable" class: https://david.zemon.name/PropWare/api-develop/Runnable_Demo_8cpp-example.xhtml showing off the Runnable class for executing code in separate cogs, the Printer interface, the synchronous printer interface which lets you print from multiple cogs without requiring a dedicated cog for the UART driver as well as blinking an LED.
Doxygen docs: https://david.zemon.name/PropWare/api-develop/namespacePropWare.xhtml
Source code: https://github.com/parallaxinc/PropWare/tree/develop/PropWare
So, it's a vastly different approach. On the one hand, the PropWare-ness would be completely hidden from the user in this "black box" that is Simple. I'd write a bunch of C++ code, get the job done, and then when all is said and done and tested, I'd wrap it with some C, copy/paste all the necessary files in Simple, test it one last time from SimpleIDE instead of PropWare's build system, and call it done. The user of said library would never know how different it is.
But does Parallax want a bunch of code in their repository (which Parallax will ultimately be responsible for maintaining/supporting) that follows such a vastly different philosophy and coding style?
Here are some test results, COM ports at likely baud rates. Uses FT2232H (HS-USB) as the 'emulate P1' TX test generator, because that can stream without gaps.
Config with 2 Terminals, sending large files (up to 500k Bytes) , and look for issues..
The Prop+ADC124S021 can manage 200ksps, (assumes 80MHz), but the common UARTs are the weak links...
The best performing FS-USB uart is the EXAR XR21B1420, which can cope with an impressive 8MBd continual, but they are less common and a slight price premium.
The CP2102N is next best, with more baud choices than FTDI, (2Mbd, 2.181818MBd, 2.4MBd, 2.666666MBd 3MBd, 3.428571MBd, 4MBd) but it just coughs at 4MBd continual 8.N.1.
Works ok at 4MBd with 1 added stop bit plus mark parity, or is testing ok at 3.428571 MBd
for an exact sample speed of (20.00MHz/117) = 170940.1709 sps
FTDI parts seem to manage 3MBd, tho hard to gauge margins as they have no other choices close.
FT231X for example, could do exact sample speed of 20.00MHz/134 = 149253.731 sps
I tried to gauge PC loading, and with large files and two terminals active (doing 4MBd one each way), it's hard to nudge over 10% CPU here, on a good desktop PC.
Faster baud should be easy enough to set, as the drivers do all the work. Be interesting to see what javascript can handle.
A useful feature of the hard-bridge coding, is you know the exact sample rate as that is set by the Prop Crystal, so should be within a few ppm.
It would be nice to stream at max rates for 2~5 seconds, so close to 1MByte buffer. 'Scope like' Display zoom can be by skip of 2.5.10.20.50.100 samples,
If you can support a > 1 second max, you can use GPS 1pps systems to calibrate the Prop Xtal, and turn this into a quite useful frequency counter.
A good example of a good Frequency counter, using low sample rates, is http://www.daqarta.com/
1) I took a peek at the code in BlocklyProp - and the baud rate doesn't look like it would be too hard to change, so let's say we can set that to anything we want within reason. I'll try to do some testing to see how fast we can go before we start losing data, because I don't think calculating a checksum is worth it, and then report back what I learn when I'm able to do this...
1b) FYI - serial communication between the USB port and the browser is base64 encoded. Doesn't affect the prop, but there is a speed penalty of 75%. It's this way because upper ASCII (and some command ASCII bytes) would cause things to really flip out...
2) Let's stick to the hardware on the Propeller Activity Board. That means that we have the ADC124S021, FTDI FT231X, and 5 Mhz crystal (16xPLL/80Mhz clock) to work with.
3) Let's define what the data being sent to the browser looks like (open to suggestion, but here's some initial thoughts.
a) 5 byte packets:
byte 1: LSB [0-5] channel 1 low [6-7] 0b00
byte 2: LSB [0-5] channel 1 high [6-7] 0b00
byte 3: LSB [0-5] channel 2 low [6-7] 0b01
byte 4: LSB [0-5] channel 2 high [6-7] 0b01
byte 5: LSB [0-5] time, in microseconds (or 0.5 us or 2 us or 5 us increments?), since last measurement [6-7] 0b10, except FIRST measurement in a burst (tells the computer to refresh start collecting new) is 0b11.
4) Let's define a command set that can be sent to the Prop at any time - REALLY open to suggestions here...
a) command packet size?
i) trigger (none, ch 1 rising, ch 1 falling, ch 2 rising, ch 2 falling, others?)
ii) trigger voltage?
iii) sampling frequency?
iv) one shot or continuous?
5) Not too worried about precision...any "measuring" (min/max/avg/frequency/etc.) could probably be done on the computer/browser side, but again, open to ideas.
Thoughts?
Not sure how that maps onto javascript, but USB drivers and readers usually specify some buffer size, so I'd expect full speeds are possible, with the right buffer eizes.
Yes, the 3MBd rate above with a dual-byte (16b SPI to 20b UART) encoding maps on FT231X : Gives exact sample speed of 20.00MHz/134 = 149253.731 sps
Hmm. that's more of an issue - both from a raw speed cost, and a peak reading rate loss.
Exact Base64 would not stream at 3~4MBd, but the encoding used below might just be good enough, if used with care ?
My current mapping looks like this - it has expansion room for two types of Logic Analyzer / Digital streaming too, but that could be command based.
ADC, stream, Binary packed First bit tagged, a/b = 00 for AnalogCH0, 01 For AnalogNextCH, 10 for packed digital(3x4b nibbles), 11 for timestamp digital
[0.a.b.ADC11:7] - always sent paired a.b are tag bits, and a. can be 1, which is nearly base64, (if you ignore the bit reversal issue)
[1.ADC6:0]
It's encoded like this bit-order because the ADC has a few clocks preamble and then delivers data MSB first.
Usually this would feed through a fast re-map table lookup, to swap the bit order & shift/add, which can (hopefully) be done before any escape testing ?
The PC knows how many samples in the stream eg 1000~1000000 ?
To keep the prop side simplest, right now the command variables are just
* A SampleCount (in 20.00MHz/134 units), (may as well be 32b )
* Channels to scan (ADC can do 1,2,3,4)
* A possible live Break from the PC (in case the sample count is too large, or perhaps a stream forever to disk may be possible ? Numeric limit of 32b at 3MBd is ~ 8 hours.)
because it can stream at a good enough speed, the PC can manage Trigger/Trigger voltage/ sampling frequency (by skip)
The sample count means the PC can know exactly how many bytes to store as samples, and bypass the base64 handler.
(of course, assumes no lost bytes, but there is a unique end-of-frame possible, that could serve as a double check )
ie Request N Readings. repeat read until EOF, and check N were stored.
Precision comes for free, with packed transmit, as the timestamp is built in
The bridge code has an exact, deterministic time base, that is Crystal Accurate, and the Crystal can be GPS calibrated, so single digit ppm (or better) looks achievable.
Might as well use/ show off the full abilities of the Prop
This is storing quite a lot of state in the messaging system. Are you worried about the PC-side of the world loosing track of things, or bytes coming in out-of-order? I'm not sure why you need the high bits in every single byte, especially the final one for time, where you've only left yourself with the ability to count up to 63 microseconds, which is almost 16kHz. As for the last byte, the time between each sample - the PC is the one setting the sample rate, why can't the PC keep track of that? No need to repeat the same number over and over again.
What about this simpler idea:
byte 1: Low byte of channel 1
byte 2: [0-3] high bits for channel 1, [4-6] Channel for this and the previous byte, [7] Unused?
Repeat these two bytes for each channel currently enabled
To tweak your suggestion slightly:
bit [7] could mark the first byte in a burst.
jmg -
the base64 thing happens between the BlocklyProp-client and the browser. More exactly, USB data comes into the BlocklyProp-client RAW, then gets base64 encoded, and sent to the browser via a Websocket. When the browser gets it, it is then base64 unencoded. This is because certain ASCII chars would break the Websocket. When we move to an offline version, we won't need the Websocket because the client and browser will be within the same app, so at that point the base64 encoding goes away. Unfortunately, there isn't really a way to conditionally use encoding or not - it's sort of an all or nothing thing. That being said - some speed tests will prove out what's possible.
Both - I was mistaken in thinking the packets should be timestamped - if the prop sends at regular intervals, that should be sufficient. So I like David's simple packet suggestion.
I do suspect that we will want to Prop to manage the trigger...but again, open to what's possible, and letting testing prove out what's best.
I'll start cooking up some browser HTML/Javascript code to do some testing with this.
Thanks!
If it would help at all, you're welcome to use and modify my SpinScope HTML/Javascript:
https://forums.parallax.com/discussion/157982/spinscope-a-virtual-oscilloscope-for-the-propeller/p1
My original code doesn't display analog, but Heater modified it so that it could (same thread).
-Phil