Logic level parsing Rigol oscilloscope CSV data with F#
Posted on January 12, 2015
I was recently debugging a SPI communication but had no logic analyser. Fortunately my oscilloscope (rigol DS1052e) can export a capture as CSV (I assume that most digital oscilloscopes can export to CSV too).
Parsing the CSV is trivial, getting a logic level from it is also fairly easy. I realise that this is terribly quick & dirty but it worked 100% for what I needed. Here is the high level process
- Ignore timings - SPI is clocked so actual timing is irrelevant for this analysis
- Parse X and Y channels
- Convert clock channel voltage into logic level 1 or 0
- Discard all rows apart from rows where the clock goes high
- Convert data channel to logic level
- Group into bytes
- Display hex value
As I said, very simple but it worked well and I was able to see exactly what was going on. Obviously this only works for synchronous (clocked) protocols like SPI/I2C but that is all I needed.
Here is the full F# script (also see the gist)
open System
open System.IO
open System.Text
open System.Globalization
open System.Text.RegularExpressions
let ttlTrue = 2.3
let getLines (fname:string) = seq{
use stream = new StreamReader( fname )
while not stream.EndOfStream do
yield stream.ReadLine()
}
let parseVoltage (s:string) =
try
System.Double.Parse( (s.Replace( ".", "," )), NumberStyles.Float )
with
| e ->
printfn "%s - %s" s (e.ToString())
0.0
let filterOnClockGoingHigh (s:seq<int * int>) = seq {
let e = s.GetEnumerator()
let last = ref 0
while e.MoveNext() do
if !last <> (snd e.Current) then
last := (snd e.Current)
if !last = 1 then
yield e.Current
}
//from http://fssnip.net/6A
let groupWhen f (input:seq<_>) = seq {
use en = input.GetEnumerator()
let running = ref true
// Generate a group starting with the current element. Stops generating
// when it founds element such that 'f en.Current' is 'true'
let rec group() =
[ yield en.Current
if en.MoveNext() then
if not (f en.Current) then yield! group()
else running := false ]
if en.MoveNext() then
// While there are still elements, start a new group
while running.Value do
yield group() |> Seq.ofList }
let toNum (g:int seq) =
let l = List.ofSeq g
(if l.Length > 0 && l.[0] = 1 then 128uy else 0uy) +
(if l.Length > 1 && l.[1] = 1 then 64uy else 0uy) +
(if l.Length > 2 && l.[2] = 1 then 32uy else 0uy) +
(if l.Length > 3 && l.[3] = 1 then 16uy else 0uy) +
(if l.Length > 4 && l.[4] = 1 then 8uy else 0uy) +
(if l.Length > 5 && l.[5] = 1 then 4uy else 0uy) +
(if l.Length > 6 && l.[6] = 1 then 2uy else 0uy) +
(if l.Length > 7 && l.[7] = 1 then 1uy else 0uy)
getLines fsi.CommandLineArgs.[1]
//Skip headers
|> Seq.skip 2
//Split CSV
|> Seq.map (fun l -> l.Split( [|','|] ))
//Ignore empty lines
|> Seq.filter (fun f -> f.Length >= 3)
//Parse the voltages
|> Seq.map (fun f -> (parseVoltage f.[1], parseVoltage f.[2]) )
//Clock from voltage to logic level
|> Seq.map (fun (data,clock) -> ((if data > ttlTrue then 1 else 0), (if clock > ttlTrue then 1 else 0)))
//Get data - clock rising edge
|> filterOnClockGoingHigh
//remove the clock data
|> Seq.map (fun f -> fst f)
//Add a count to each item
|> Seq.mapi (fun i v -> (i,v))
//group into chunks of 8
|> groupWhen (fun (i,v) -> i % 8 = 0)
//get only the second item
|> Seq.map (Seq.map snd)
//convert each group to a number
|> Seq.map (fun x -> toNum x)
|> Seq.iter (fun x -> printfn "%02x %c" x (if (x >= 0x20uy) && (x <= 0x7euy) then char x else ' ') )