Project from RJM
2004-08-04 Here a simple oscilloscope functionality is presented. By the time this is stripped for the primary purpose of an investigation (event/response delay investigation). Later, further functionality (e.g. axes) will be (re)added.
The application presumes a serial connection to a device providing sample data. Samples cause canvas update just as they arrive.
oscill.tcl provides a basic oscilloscope functionality. When a trace is drawn, the previous trace is removed sample by sample. A small (moving) gap in the trace shows the actual update location. Command line options:
1...n - specifies com port for target device delivering measuring data 0 - oscill.tcl automatically starts msp430dummy.tcl and creates a pipe - (no option) in internal proc provides in timing and simulated measuring data
datadummy.tcl replaces the original measuring frontend. Command line options:
1...n - specifies com port for connection with oscill.tcl via a nullmodem cable. When the PC provides two com ports, both programs can run on the same machine. However, better performance had been observed on separate machines during the phase of optimizing this experimental software.
Here both scripts are inserted:
# oscill.tcl ################################################################################ # oscillograph module ################################################################################ array set oscpar { single 1 samples 400 plotstep 2 timebase 50 persist 0 channels 2 location +1+24 x0 15 } # initialize STRIPPED window proc oscinit {runcmd} { global oscpar # initialize Tk widgets if ![winfo exists .osc] { toplevel .osc wm title .osc "oscillogram" # specify as window not iconizable, '.' is master window wm transient .osc . wm geometry .osc $oscpar(location) wm geometry .osc {} ;# ensure internal widget size appearing canvas .osc.c -width 830 -height 550 -bg white frame .osc.f1 button .osc.run -text "<space> run" -command $runcmd checkbutton .osc.single -text "1 single" -variable oscpar(single) label .osc.tbl -text "2 timebase (ms/div)" set oscpar(tbvalues) {10 20 50 100 200 500 1000} spinbox .osc.tb -values $oscpar(tbvalues) -width 4 \ -command {tb_update %s; .osc.tb selection range 0 end} .osc.tb set $oscpar(timebase) .osc.tb configure -validate key -vcmd {regexp {^[-+]?[.\d]*$} %P} checkbutton .osc.persist -text "9 persistence" -variable oscpar(persist) button .osc.clr -text "0 clr" -command {.osc.c delete curve oldcurve} pack .osc.run .osc.single .osc.tbl .osc.tb \ .osc.persist .osc.clr -side left -fill y -padx 4 -in .osc.f1 pack .osc.c .osc.f1 # basic vertical lines replaces axis system set x $oscpar(x0) for {set n 0} {$n <= 10} {incr n} { .osc.c create line $x 0 $x 512 -fill grey incr x [expr {$oscpar(samples)/5}] } # here define extra osc bindtag for numeric key bindings: active only upon # window focus (not active when entry, spinbox etc. focus) bindtags .osc "wosc [bindtags .osc]" bind wosc <Key-1> {.osc.single toggle} bind wosc <Key-2> {focus .osc.tb} bind wosc <Key-9> {.osc.persist toggle} bind wosc <Key-0> {.osc.clr invoke} bind .osc.tb <Key-Escape> {focus .osc} bind .osc.tb <Key-Return> {tb_update [.osc.tb set]; .osc.tb selection range 0 end} bind wosc <Key-Escape> {destroy .osc} bind wosc <Key-Return> {focus .osc} bind all <Key-space> {.osc.run invoke; break} # let these bindings catch space key to only allow other bindings bind Spinbox <Key-space> {continue} bind Entry <Key-space> {continue} # here filter for .osc.tb: no alpha characters (space allowed, but # captured in a new class binding) bind .osc.tb <KeyPress> { if {[regexp -nocase {[A-Z]} %A]} { break ;# here inhibit further bind processing } } bind wosc <Destroy> {set oscpar(location) [wm geometry .osc]} tb_update $oscpar(timebase) } focus .osc } # argument: a list containing values for each sample, according to the number of channels set xpos 0 proc plotdata {yy} { global oscpar y_expr lcolor # variables with only static nature global objID xpos # CREATE line first, only starting point if {!$xpos} { set xpos $oscpar(x0) if {$oscpar(persist)} { .osc.c itemconfigure curve -fill darkgrey .osc.c addtag oldcurve withtag curve .osc.c dtag curve } else { .osc.c addtag lastcurve withtag curve .osc.c dtag curve .osc.c dchars lastcurve 1 6 ;# create gap in dynamic curve plotting } catch {unset objID} set index 0 foreach y $yy color $lcolor ex $y_expr { set y [expr $ex] if {$color=="-"} { lappend objID 0 } else { lappend objID [.osc.c create line $xpos $y $xpos $y \ -fill $color -tags "object curve curve($index)"] } incr index } } else { # +++++++++++++ the CORE of the line drawing functionality ++++++++++++++ # delete first coordinate pair of last curve(s) .osc.c dchar lastcurve 1 ;# !!! consumes time (check if foraeach & ID would be better than tag) # append additional line pieces to one line object foreach y $yy ex $y_expr id $objID { set y [expr $ex] #set y [expr 400-$y/8.0] .osc.c insert $id end "$xpos $y" } } incr xpos ${oscpar(plotstep)} if {$xpos == [expr {$oscpar(samples)*$oscpar(plotstep)+$oscpar(x0)}]} { set xpos 0 if {!$oscpar(single) } { after idle .osc.run invoke } } } proc tb_update {s} { global oscpar serial set oscpar(timebase) $s .osc.tb config -values $oscpar(tbvalues) .osc.tb set $oscpar(timebase) # prepare sample rate set interval [expr {$s*1000 / ($oscpar(samples)/10)}] if {$interval > 4000} \ {set interval [expr {$interval/1000}]; set unit m} \ {set unit u} if {[info exists serial]} { puts -nonewline $serial "T$unit[binary format S $interval]" flush $serial } # now prepare total scan time representation set unit ms if {$s >= 1000} { set unit s set s [expr {$s/1000}] } .osc.c delete text .osc.c create text 650 525 -text "scan time: [expr {$s*10}] $unit" -tags text return 1 } #################################### # this every uses a repeats limit and a fixed 1ms inner interval (1 ms is a reliable rate) set _count 0 set _rep 0 proc every {ms repeats body} { global _count _rep if {$_rep <= 0} {set _rep $repeats} if {!$_count} {set _count [expr {$ms ? $ms : 1}]} set id [after 1 [info level 0]] ;# use 1 and count for ms ms if {[incr _count -1]} return # timed script execute if 1 $body if {![incr _rep -1]} {after cancel $id} } # this proc is used when an external process (pipe or com port) supplies raw plot data proc plotprocess {} { global serial oscpar # send arming command to target device puts -nonewline $serial "A[binary format S $oscpar(samples)][binary format c $oscpar(channels)]" flush $serial } # this proc is used when this script supplies raw plot data according to timebase setting proc plotdummy {} { global oscpar tp set tp(n) 0 every [expr {$oscpar(timebase)/35}] $oscpar(samples) { global tp set x $tp(n) incr tp(n) # simulate analog values in the range 0-1600 (display range 0-4095, 512 pixel) plotdata "[expr {4*$x}] [expr {4*(400-$x) + 100*rand()}]" #puts -nonewline . } } # input data words (n = number of words, returned in string) # note: this procedure works correct after incompleted $stream # next call through fileevent it will be completed set ser_in_cnt 0 proc ser_in_w {n} { global serial stream ser_in_cnt if {!$ser_in_cnt} {set ser_in_cnt [expr {$n*2}]} append stream [read $serial $ser_in_cnt] set ser_in_cnt [expr {$n*2-[string length $stream]}] if {$ser_in_cnt} return binary scan $stream S$n intval set stream "" return $intval } proc getdata {} { global oscpar if {[set yy [ser_in_w $oscpar(channels)]] != ""} { plotdata $yy } # let next fileevent complete the receive message if not yet complete } ############################################### lappend y_expr {512 - $y/8.0}; lappend lcolor blue lappend y_expr {512 - $y/8.0}; lappend lcolor red if {$argc && [string length $argv] == 1} { # initialize port status if {!$argv} { # when 0, open a pipe: connects to a MSP430 hardware simulator script set serial [open "|wish datadummy.tcl" r+] } else { set serial [open com$argv r+] fconfigure $serial -timeout 1000 -handshake none -pollinterval 4 fconfigure $serial -mode 57600,n,8,1 ;#only necessary to force 8 bit (default is 7) } fconfigure $serial -buffersize 4096 # note: blocking 0 must be used in order to allow event loop processing when # waiting for data fconfigure $serial -blocking 0 -encoding binary -translation binary fileevent $serial readable getdata oscinit plotprocess } else { oscinit plotdummy }
The drawing core of this script can be found just under the phrase "CORE".
Here is the data simulator (next filename must be used)
# datadummy.tcl # dummy MSP430 unit. Uses pipes or serial port to simulate MSP430 target device # may also dummy remotely per serial port (start with port# as argument) #console show if {$argc} { set fid [open com$argv r+] fconfigure $fid -timeout 1000 -handshake none fconfigure $fid -mode 57600,n,8,1 set in $fid set out $fid } else { set in stdin set out stdout } fileevent $in readable "getcommands" # blocking 0 should result in immediate flush return fconfigure $out -blocking 0 -encoding binary -translation binary fconfigure $in -blocking 0 -encoding binary -translation binary pack [text .t] array set param { time 1 unit ms rampstart 0 rampstep 4 } proc getcommands {} { global in out param y sample .t insert end > switch [read $in 1] { A {.t insert end "Arm..." binary scan [read $in 2] S1 param(samples) ;# number of samples read $in 1 ;# command (1 = stop/quit arming) set y $param(rampstart) set sample 0 set ms [expr {$param(unit)=="m" ? $param(time) : $param(time)/1000}] .t insert end " ($ms) $param(samples) samples\n" every $ms $param(samples) sample } I {.t insert end "Channel setup [read $in 2]\n" } T {.t insert end "Timebase set: [set param(unit) [read $in 1]] " binary scan [read $in 2] S1 param(time) .t insert end "$param(time)\n" } R {.t insert end "Ramp set: " binary scan [read $in 2] S1 param(rampstart) binary scan [read $in 2] S1 param(rampend) .t insert end "$param(rampstart) $param(rampend)\n" set param(rampstep) [expr {($param(rampend)-$param(rampstart)) / 400}] } S {.t insert end "Suspend arming\n" } Q {.t insert end "Quit arming\n" } D {.t insert end "Digital Out\n" read $in 1 } C {.t insert end "Custom: \n" read $in 1} B {.t insert end "Firmware Build\n" puts $out "000"; flush $out } } .t see insert } set count 0 set rep 0 proc every {ms repeats body} { global count rep if {!$rep} {set rep $repeats} if {!$count} {set count [expr {$ms ? $ms : 1}]} set id [after 1 [info level 0]] ;# use 1 and count for ms ms if {[incr count -1]} return # timed script execute if 1 $body if {![incr rep -1]} {after cancel $id} } proc sample {} { global out y param sample set x $sample incr sample puts -nonewline $out [binary format S2 [list [expr {int($y)}] [expr {int(4*(400-$x)+100*rand())}]]] flush $out set y [expr {$y + $param(rampstep)}] }