Artifact Content

Artifact f8eac4565939ae9b6aadd3afbc2e9faec1b282b7dfedf32a75cbb9d392b66d1c:


#!/bin/sh
# the next line restarts using wish \
exec wish "$0" ${1+"$@"}

##
## Name:  Gyroscope Example
## Author:  Gerald W. Lester, 2018
## Copyright 2018, Gerald W. Lester
##
## This program utilizes a BSD copyright, please see the license file in the
## same directory.
##
## This program is loosely based on a C program by the same name from Dexter
## Industries. See
##   https://github.com/DexterInd/DI_LEGO_NXT/blob/master/dIMU/RobotC/Gyro_Example.c
##
## It utilizes the low leve bno055 package.
##

lappend atuo_path [file dirname [info script]] [pwd]
::tcl::tm::path add [file dirname [info script]] [pwd]

package require dimu

##
## These bytes set the full scale range of the gyroscope.
## it is important to define full_scale_range.  Values are:
##      0x00 - 250 dps.  Full scale range.
##      0x10 - 500 dps.  Full scale range.
##      0x30 - 2000 dps.  Full scale range.
##
array set CurrentValue {
    range 2000dps
    digits 5
    decimals 1
    minChange 0.1
    formatStr {%7.1}
}

array set AccelValues {
    range 2000dps
    digits 5
    decimals 1
    minChange 0.1
    formatStr {%7.1}
}

array set MagValues {
    range 2000dps
    digits 5
    decimals 1
    minChange 0.1
    formatStr {%7.1}
}

array set GyroValues {
    digits 5
    decimals 1
    minChange 0.1
    formatStr {%7.1}
}

array set Configuration {
    bus 1
    dIMUaddress 0x28
}

array set UI {
    InLoadCurrentConfiguration false
    operation,map,label {
        configmode      "Configuration Mode"
        acconly         "Accelerometer Only"
        magonly         "Magnetometer Only"
        gyroonly        "Gyroscope Only"
        accmag          "Accelerometer and Magnetometer Only"
        accgyro         "Accelerometer and Gyroscope Only"
        maggyro         "Gyroscope and Magnetometer Only"
        amg             "Accelerometer, Gyroscope and Magnetometer Only"
        imu             "Inertial Measurement Unit"
        compass         "Compass"
        m4g             "Magnet for Gyroscope"
        ndof_fmc_off    "9 Degrees of Freedom -- Fast Magnetometer Calibration Off"
        ndof            "9 Degrees of Freedom -- Fast Magnetometer Calibration On"
    }
    power,map,label     {
        normal          "Normal"
        low             "Low"
        suspend         "Suspend"
    }

    gyrorange,map,label {
        2000dps         "2,000 dps"
        1000dps         "1,000 dps"
        500dps          "500 dps"
        250dps          "250 dps"
        125dps          "125 dps"
    }
    configurationWidgets {
    }
    clocksource,map,label {
        internal        "Internal"
        external        "External"
    }
    temperaturesource,map,label {
        acc     "Accelerometer"
        gyro    "Gyroscope"
    }
    temperatureunits,map,label {
        c   "C"
        f   "F"
    }
    fusionunits,map,label {
        windows   "Windows"
        android   "Android"
    }
    eulunits,map,label {
        deg   "Degrees"
        rad   "Radians"
    }
    remap_x_sign,map,label {
        pos   "Unchanged"
        neg   "Inverse"
    }
    remap_y_sign,map,label {
        pos   "Unchanged"
        neg   "Inverse"
    }
    remap_z_sign,map,label {
        pos   "Unchanged"
        neg   "Inverse"
    }
    remap_x_axis,map,label {
        x   "Unchanged"
        y   "To Y"
        z   "To Z"
    }
    remap_y_axis,map,label {
        x   "To X"
        y   "Unchanged"
        z   "To Z"
    }
    remap_z_axis,map,label {
        x   "To X"
        y   "To Y"
        z   "Unchanged"
    }
}

foreach key [array names UI "*,map,label"] {
    lassign [split $key ","] option
    set maxLabelSize -1
    foreach {symbol label} $UI($key) {
        dict set UI($option,map,symbol) $label $symbol
        if {$maxLabelSize < [string length $label]} {
            set maxLabelSize [string length $label]
        }
    }
    set UI($option,maxLabelSize) $maxLabelSize
}

foreach type {gyroscope accelerometer magnetometer gravity linearAcceleration quaternion prh} {
    set CurrentValue($type,x) {0}
    set CurrentValue($type,y) {0}
    set CurrentValue($type,z) {0}
}

##
## Begin procedure definition section
##
proc ChangeOperationMode {varName index op} {
    global UI
    global Configuration

    set mode [dict get $UI(operation,map,symbol) $Configuration(operation)]

    if {$mode ne "Configuration Mode"} {
        DisableConfigurationWidgets
        EnableTabs $mode
    } else {
        EnableConfigurationWidgets
        DisableTabs
        LoadCurrentConfiguration
    }
}

proc ClockSourceMode {widget varName index op} {
    global Configuration
    global UI

    set mode [dict get $UI(operation,map,symbol) $Configuration(operation)]
    if {$mode eq "Configuration Mode" && $Configuration($index)} {
        $widget configure -state normal
    } else {
        $widget configure -state disabled
    }
}

proc ConfigurationChange {varName index op} {
    global UI
    global CurrentValue
    upvar #0 $varName configuration

    if {$UI(InLoadCurrentConfiguration)} {
        return
    }
    set value $configuration($index)
    if  {$index eq "refreshRate"} {
        return
    } elseif {[info exists UI($index,map,symbol)]} {
        set value [dict get $UI($index,map,symbol) $value]
    }
    $CurrentValue(handle) configure -$index $value
}

proc DisableConfigurationWidgets {} {
    global UI

    foreach widget $UI(configurationWidgets) {
        $widget configure -state disabled
    }
}

proc EnableConfigurationWidgets {} {
    global UI
    
    foreach widget $UI(configurationWidgets) {
        if {[winfo class $widget eq "TCombobox"]} {
             $widget configure -state readonly
        } else {
             $widget configure -state normal
        }

    }
}

proc ChangeFormat {varName index op} {
    upvar #0 $varName configuration

    set digits $configuration(digits)
    set decimals $configuration(decimals)
    incr digits 1
    incr digits $decimals
    set configuration(fmtStr) "%${digits}.${decimals}f"
}

proc DisableTabs {} {
    global UI

    set notebook $UI(mainNotebook)
    foreach {tab widget} [array get UI {main,*}] {
        $notebook tab $widget -state disabled
    }

    $notebook tab $UI(main,configuration) -state normal
    $notebook select $UI(main,configuration)
}

proc EnableTabs {mode} {
    global UI

    DisableTabs

    set notebook $UI(mainNotebook)
    switch -exact -- $mode {
        configmode -
        default {
            ##
            ## Do nothing
            ##
        }
        acconly {
            $notebook tab $UI(main,accelerometer) -state normal
        }
        magonly {
            $notebook tab $UI(main,magnetometer) -state normal
        }
        gyroonly {
            $notebook tab $UI(main,gyroscope) -state normal
        }
        accmag {
            $notebook tab $UI(main,accelerometer) -state normal
            $notebook tab $UI(main,magnetometer) -state normal
        }
        accgyro {
            $notebook tab $UI(main,accelerometer) -state normal
            $notebook tab $UI(main,gyroscope) -state normal
        }
        maggyro {
            $notebook tab $UI(main,magnetometer) -state normal
            $notebook tab $UI(main,gyroscope) -state normal
        }
        amg {
            $notebook tab $UI(main,accelerometer) -state normal
            $notebook tab $UI(main,magnetometer) -state normal
            $notebook tab $UI(main,gyroscope) -state normal
        }
        imu -
        compass -
        m4g -
        ndof_fmc_off -
        ndof {
            foreach {tab widget} [array get UI {main,*}] {
                $notebook tab $widget -state normal
            }
        }
    }
}

proc ReadGyro {} {
    global CurrentValue
    global GyroValues

    ##
    ## Read the gyroscope
    ##
    set currentReadings [$CurrentValue(handle) readGyro]
    set formatStr $GyroValues(formatStr)
    set x [format $formatStr [dict get $currentReadings x]]
    set y [format $formatStr [dict get $currentReadings z]]
    set z [format $formatStr [dict get $currentReadings z]]
    set minChange $GyroValues(minChange
    if {abs($x - $CurrentValue(gyroscope,x)) > $minChange ||
        abs($y - $CurrentValue(gyroscope,y)) > $minChange ||
        abs($z - $CurrentValue(gyroscope,z)) > $minChange
    } {
        LogValues $x $y $z
        
        ##
        ## Only change if delta exceed, otherwise a slow drift will never get
        ## logged.
        ##
        set CurrentValue(gyroscope,x) $x
        set CurrentValue(gyroscope,y) $y
        set CurrentValue(gyroscope,z) $z
    }

    return
}
proc ReadTemperature {} {
    set ::GeneralValues(temperature) [format {%5.1f} [dict get [$::CurrentValue(handle) readTemperature] temperature]]
    return
}

proc RunSelfTest {} {
    $::CurrentValue(handle) runSelfTest
}

proc LogValues {type} {
    variable WidgetData
    variable CurrentValue

    #if {[info exists CurrentValue(logId)]} {
    #    after cancel $CurrentValue(logId)
    #}
    #set CurrentValue(logId) [after [expr {$CurrentValue(freq) * 100}] LogValues]

    set timeStamp [clock format [clock seconds]]
    set x $CurrentValue($type,x)
    set y $CurrentValue($type,y)
    set z $CurrentValue($type,z)
    $WidgetData($type,history) insert {} end -values [list $timeStamp $x $y $z]
}

proc StartReading {} {
    global CurrentValue

    .main.controls.buttons.start configure -state disabled
    .main.controls.buttons.stop configure -state normal
    StartGyro $CurrentValue(handle) $range
    ReadGyro

}

proc StopReading {} {
    global CurrentValue

    if {[info exists CurrentValue(afterId)]} {
        after cancel $CurrentValue(afterId)
    }
    if {[info exists CurrentValue(logId)]} {
        after cancel $CurrentValue(logId)
    }
    
    .main.controls.buttons.start configure -state normal 
    .main.controls.buttons.stop configure -state disabled
}
proc LoadCurrentConfiguration {} {
    global UI
    global Configuration
    global CurrentValue
    
    set UI(InLoadCurrentConfiguration) true
    foreach optionValue [$CurrentValue(handle) configure] {
        lassign $optionValue option default value
        ##
        ## Remove the leading -
        ##
        set index [string range $option 1 end]
        if {[info exists UI($index,map,label)]} {
            try {
                set value [dict get $UI($index,map,label) $value]
            } on error {errMsg errInfo} {
                puts "Error: $errMsg"
                puts "Error on $index for '$value'"
            }
        }
        set Configuration($index) $value
    }
    set UI(InLoadCurrentConfiguration) false
}

proc SaveConfigurationToFile {} {
    global Configuration

    set fn [tk_getSaveFile \
                -confirmoverwrite yes \
                -defaultextension .cfg \
                -initialdir [file normalize ~] \
                -parent . \
                -title "Save Configuration" \
    ]
    if {$fn ne {}} {
        set ofd [open $fn w]
        try {
            puts $ofd [array get Configuration]
        } finally {
            close $ofd
        }
    }
    
}

proc LoadConfigurationFromFile {} {
    global Configuration
    global UI

    set fn [tk_getOpenFile \
                -defaultextension .cfg \
                -initialdir [file normalize ~] \
                -multiple no \
                -parent . \
                -title "Restore Configuration" \
    ]

    set ifd [open $fn r]
    set tempDict [read $ifd]
    close $ifd

    ##
    ## Save desired operation mode
    ##
    set newMode [dict get $tempDict operation]
    
    ##
    ## Remove readonly options
    ##
    foreach option {bus dIMUaddress operation
                    uniqueID softwareRev bootloaderRev
                    mcuSelfTest gyroSelfTest magSelfTest accelSelfTest
                    sysCalStatus accCalStatus gyrCalStatus magCalStatus
                    sysStatus sysError clockStatus} {
        dict unset tempDict $option
    }
    
    ##
    ## Load the new configuration
    ##
    set Configuration(operation) "Configuration Mode"
    array set Configuration $tempDict
    set Configuration(operation) $newMode
    
}

proc ResetInterrupts {} {
    $::CurrentValue(handle) reset interrupts
}

proc ResetSystem {} {
    $::CurrentValue(handle) reset system
}


##
## GUI Creation Routines
##
proc RemoveClassBindings {widget} {
    bindtags $widget [lreplace [bindtags $widget] 1 1]
    return;
}
proc CreateGui {} {
    global UI

    wm title . "dIMU Gyroscope Example"

    menu .menuBar
    . configure -menu .menuBar
    menu .menuBar.configuration
    .menuBar add cascade \
        -menu .menuBar.configuration \
        -label "Configuration"
    .menuBar.configuration add command \
        -command SaveConfigurationToFile \
        -label "Save Configuration..."
    .menuBar.configuration add command \
        -command LoadConfigurationFromFile \
        -label "Restore Configuration..."
    .menuBar.configuration add separator
    .menuBar.configuration add command \
        -command LoadCurrentConfiguration \
        -label "Load from Device"

    set UI(mainNotebook) [ttk::notebook .main]
    grid configure .main -sticky nsew
    grid columnconfigure . .main -weight 1
    grid rowconfigure . .main -weight 1
    
    set UI(main,configuration) [ttk::frame .main.configuration]
    .main add .main.configuration -text {Configuration}
    set UI(main,gyroscope) [ttk::frame .main.gyroscope]
    .main add .main.gyroscope -text {Gyroscope}
    set UI(main,accelerometer) [ttk::frame .main.accelerometer]
    .main add .main.accelerometer -text {Accelerometer}
    set UI(main,magnetometer) [ttk::frame .main.magnetometer]
    .main add .main.magnetometer -text {Magnetometer}
    set UI(main,gravity) [ttk::frame .main.gravity]
    .main add .main.gravity -text {Gravity}
    set UI(main,linearAcceleration) [ttk::frame .main.linearAcceleration]
    .main add .main.linearAcceleration -text {Linear Acceleration}
    set UI(main,quaternion) [ttk::frame .main.quaternion]
    .main add .main.quaternion -text {Quaternion}
    set UI(main,prh) [ttk::frame .main.prh]
    .main add .main.prh -text {Pitch, Roll and Heading}

    CreateConfigurationFrame .main.configuration
    CreateGyroFrame .main.gyroscope
    CreateAccelerometerFrame .main.accelerometer
    CreateMagnetometerFrame .main.magnetometer
    CreateGravityFrame .main.gravity
    CreateLinearAccelerationFrame .main.linearAcceleration
    CreateQuaternionFrame .main.quaternion
    CreatePitchRollHeadingFrame .main.prh
}

proc CreateGeneralConfigFrame {general} {
    ##
    ## General Configuration group
    ##
    ttk::label $general.busLbl \
        -text {I2C Bus:}
    ttk::combobox $general.busCMB \
        -textvariable ::Configuration(bus) \
        -values [list 1] \
        -state disabled
    ttk::label $general.addrLbl \
        -text {iDMU Address:}
    ttk::entry $general.addrEnt \
        -width 5 \
        -textvariable ::Configuration(dIMUaddress) \
        -state readonly
    ttk::label $general.uuidLbl \
        -text {Unique ID:}
    ttk::entry $general.uuidEnt \
        -textvariable ::Configuration(uniqueID) \
        -state readonly
    ttk::label $general.softwareRevLbl \
        -text {Software Rev.:}
    ttk::entry $general.softwareRevEnt \
        -textvariable ::Configuration(softwareRev) \
        -state readonly
    ttk::label $general.bootloaderRevLbl \
        -text {Bootloader Rev.:}
    ttk::entry $general.bootloaderRevEnt \
        -textvariable ::Configuration(bootloaderRev) \
        -state readonly
    ttk::label $general.rateLbl1 \
        -text {Refresh Rate:}
    ttk::spinbox $general.rateEnt \
        -from 50.0 \
        -to 500.0 \
        -increment 10.0 \
        -format {%3.0f} \
        -wrap no \
        -command [format {set ::Configuration(refreshRate) [expr {entier([%1$s get])}]}  $general.rateEnt]
    ttk::label $general.rateLbl2 \
        -text { in microseconds}
    ttk::label $general.operLbl1 \
        -text {Operational Mode}
    ttk::combobox $general.operCMB \
        -textvariable ::Configuration(operation) \
        -values [dict keys $::UI(operation,map,symbol)] \
        -width $::UI(operation,maxLabelSize) \
        -state readonly
    ttk::label $general.pwrLbl1 \
        -text {Power Mode}
    ttk::combobox $general.pwrCMB \
        -textvariable ::Configuration(power) \
        -values [dict keys $::UI(power,map,symbol)] \
        -width $::UI(power,maxLabelSize) \
        -state readonly
    lappend UI(configurationWidgets) $general.pwrCMB
    ttk::labelframe $general.selfTest \
        -text {Self Test Results}
    ttk::checkbutton $general.selfTest.mcuVal \
        -text {Microcontroler} \
        -variable ::Configuration(mcuSelfTest)
    ttk::checkbutton $general.selfTest.accVal \
        -text {Accelerometer} \
        -variable ::Configuration(accelSelfTest)
    ttk::checkbutton $general.selfTest.gyroVal \
        -text {Gyroscope} \
        -variable ::Configuration(gyroSelfTest)
    ttk::checkbutton $general.selfTest.magVal \
        -text {Magnetometer} \
        -variable ::Configuration(magSelfTest)
    foreach widget {mcuVal accVal gyroVal magVal} {
        RemoveClassBindings $general.selfTest.$widget
    }
    ttk::frame $general.selfTest.btn
    ttk::button $general.selfTest.btn.runSelfTest \
        -text {Run Self Tests} \
        -command RunSelfTest
    lappend UI(configurationWidgets) $general.selfTest.runSelfTest
    ttk::label $general.calibrationLbl \
        -text {Calibration Status:}
    ttk::entry $general.calibrationVal \
        -textvariable ::Configuration(sysCalStatus) \
        -state readonly
    ttk::label $general.sysStatusLbl \
        -text {System Status:}
    ttk::entry $general.sysStatusVal \
        -textvariable ::Configuration(sysStatus) \
        -state readonly
    ttk::label $general.sysErrorLbl \
        -text {System Error:}
    ttk::entry $general.sysErrorVal \
        -textvariable ::Configuration(sysError) \
        -state readonly
    ttk::checkbutton $general.clockStatusVal \
        -text {Clock Configurable} \
        -variable ::Configuration(clockStatus)
    RemoveClassBindings $general.clockStatusVal 
    ttk::label $general.clockSourceLbl \
        -text {Clock Source}
    ttk::combobox $general.clockSourceVal \
        -textvariable ::Configuration(clocksource) \
        -values [dict keys $::UI(clocksource,map,symbol)] \
        -width $::UI(clocksource,maxLabelSize) 
    trace add variable ::Configuration(clockStatus) write [list ClockSourceMode $general.clockSourceVal]
    after 1 [list ClockSourceMode $general.clockSourceVal Configuration clockStatus write]
    ttk::labelframe $general.temp \
        -text {Temperature}
    ttk::entry $general.temp.value \
        -justify right \
        -width 6 \
        -textvariable ::GeneralValues(temperature) \
        -state readonly
    bind $general.temp.value <Double-Button-1> ReadTemperature
    ttk::combobox $general.temp.unitsCMB \
        -textvariable :::Configuration(temperatureunits) \
        -values [dict keys $::UI(temperatureunits,map,symbol)] \
        -width $::UI(temperatureunits,maxLabelSize)
    ttk::label $general.temp.sourceLbl \
        -text {from}
    ttk::combobox $general.temp.sourceCMB \
        -textvariable :::Configuration(temperaturesource) \
        -values [dict keys $::UI(temperaturesource,map,symbol)] \
        -width $::UI(temperaturesource,maxLabelSize) 
    lappend UI(configurationWidgets)  $general.temp.unitsCMB  $general.temp.sourceCMB
    ttk::label $general.fusionLbl \
        -text {Fusion Unit Type:}
    ttk::combobox $general.fusionCMB \
        -textvariable :::Configuration(fusionunits) \
        -values [dict keys $::UI(fusionunits,map,symbol)] \
        -width $::UI(fusionunits,maxLabelSize) 
    ttk::label $general.eulUnitsLbl \
        -text {Fusion Unit Type:}
    ttk::combobox $general.eulUnitsCMB \
        -textvariable :::Configuration(eulunits) \
        -values [dict keys $::UI(eulunits,map,symbol)] \
        -width $::UI(eulunits,maxLabelSize)
    lappend UI(configurationWidgets) $general.fusionCMB $general.eulUnitsCMB
    ttk::frame $general.remap
    ttk::label $general.remap.xLbl -text {X} -anchor c
    ttk::label $general.remap.yLbl -text {Y} -anchor c
    ttk::label $general.remap.zLbl -text {Z} -anchor c
    ttk::label $general.remap.signLbl -text {Remap Sign}
    ttk::label $general.remap.axisLbl -text {Remap Axis}
    ttk::combobox $general.remap.xSignCMB \
        -textvariable :::Configuration(remap_x_sign) \
        -values [dict keys $::UI(remap_x_sign,map,symbol)] \
        -width $::UI(remap_x_sign,maxLabelSize)
    ttk::combobox $general.remap.ySignCMB \
        -textvariable :::Configuration(remap_y_sign) \
        -values [dict keys $::UI(remap_y_sign,map,symbol)] \
        -width $::UI(remap_y_sign,maxLabelSize)
    ttk::combobox $general.remap.zSignCMB \
        -textvariable :::Configuration(remap_x_sign) \
        -values [dict keys $::UI(remap_z_sign,map,symbol)] \
        -width $::UI(remap_z_sign,maxLabelSize)
    ttk::combobox $general.remap.xAxisCMB \
        -textvariable :::Configuration(remap_x_axis) \
        -values [dict keys $::UI(remap_x_axis,map,symbol)] \
        -width $::UI(remap_x_axis,maxLabelSize)
    ttk::combobox $general.remap.yAxisCMB \
        -textvariable :::Configuration(remap_y_axis) \
        -values [dict keys $::UI(remap_y_axis,map,symbol)] \
        -width $::UI(remap_y_axis,maxLabelSize)
    ttk::combobox $general.remap.zAxisCMB \
        -textvariable :::Configuration(remap_x_axis) \
        -values [dict keys $::UI(remap_z_axis,map,symbol)] \
        -width $::UI(remap_z_axis,maxLabelSize)
    lappend UI(configurationWidgets) $general.remap.xSignCMB $general.remap.ySignCMB $general.remap.zSignCMB
    lappend UI(configurationWidgets) $general.remap.xAxisCMB $general.remap.yAxisCMB $general.remap.zAxisCMB
    ttk::frame $general.buttons
    ttk::button $general.buttons.resetInterrupt \
        -text "Reset Interrupts" \
        -command ResetInterrupts
    ttk::button $general.buttons.resetSystem\
        -text "Reset System" \
        -command ResetSystem

    grid configure $general.busLbl $general.busCMB $general.addrLbl $general.addrEnt -padx 2 -pady 2 -sticky ew
    grid configure $general.uuidLbl $general.uuidEnt - - -padx 2 -pady 2 -sticky ew
    grid configure $general.softwareRevLbl $general.softwareRevEnt $general.bootloaderRevLbl $general.bootloaderRevEnt -padx 2 -pady 2 -sticky ew
    grid configure $general.sysStatusLbl $general.sysStatusVal $general.sysErrorLbl $general.sysErrorVal -padx 2 -pady 2 -sticky ew
    grid configure $general.calibrationLbl $general.calibrationVal x x -padx 2 -pady 2 -sticky ew
    grid configure $general.clockStatusVal - $general.clockSourceLbl $general.clockSourceVal -padx 2 -pady 2 -sticky ew
    grid configure $general.selfTest - - - -padx 2 -pady 2 -sticky ew
    grid configure $general.rateLbl1 $general.rateEnt $general.rateLbl2 x -padx 2 -pady 2 -sticky ew
    grid configure $general.operLbl1 $general.operCMB $general.pwrLbl1 $general.pwrCMB  -padx 2 -pady 2 -sticky ew
    grid configure $general.temp - - - -padx 2 -pady 2 -sticky ew
    grid configure $general.fusionLbl $general.fusionCMB $general.eulUnitsLbl $general.eulUnitsCMB -padx 2 -pady 2 -sticky ew
    grid configure $general.remap - - - -padx 2 -pady 2 -sticky ew
    grid configure $general.buttons - - - -padx 2 -pady 2 -sticky ew

    grid columnconfigure $general {1 3} -weight 1

    grid configure x $general.selfTest.mcuVal x $general.selfTest.accVal x $general.selfTest.gyroVal x $general.selfTest.magVal x -padx 2 -pady 2 -sticky ew
    grid configure $general.selfTest.btn -columnspan 9 -sticky nsew
    grid configure x $general.selfTest.btn.runSelfTest x -sticky ew
    grid columnconfigure $general.selfTest.btn {0 3} -weight 1
    grid columnconfigure $general.selfTest {0 2 4 6 8} -weight 1
    grid columnconfigure $general.selfTest {1 3 5 7} -uniform selftests
    
    grid configure $general.temp.value $general.temp.unitsCMB $general.temp.sourceLbl $general.temp.sourceCMB -padx 2 -pady 2
    
    grid configure x $general.remap.xLbl $general.remap.yLbl $general.remap.zLbl -padx 2 -pady 2 -sticky ew    
    grid configure $general.remap.signLbl $general.remap.xSignCMB $general.remap.ySignCMB $general.remap.zSignCMB -padx 2 -pady 2 -sticky ew     
    grid configure $general.remap.axisLbl $general.remap.xAxisCMB $general.remap.yAxisCMB $general.remap.zAxisCMB -padx 2 -pady 2 -sticky ew
    
    grid configure x $general.buttons.resetInterrupt x $general.buttons.resetSystem x -sticky ew
    grid columnconfigure $general.buttons {0 2 4} -weight 1
    grid columnconfigure $general.buttons {1 3} -uniform buttons

    $general.rateEnt set 50.0

}

proc CreateAccelConfigFrame {accel} {
    ##
    ## Accelerometer group
    ##
    ttk::label $accel.rangeLbl \
        -text {Range:}
    ttk::combobox $accel.rangeCMB \
        -textvariable ::Configuration(accelrange) \
        -values [list 250dps 500dps 2000dps] \
        -state readonly
    lappend UI(configurationWidgets) $accel.rangeCMB
    ttk::label $accel.numDigitsLbl \
        -text {Digits before decimal}
    ttk::combobox $accel.numDigitsCMB \
        -textvariable ::AccelValues(digits) \
        -values [list 3 4] \
        -state readonly
    ttk::label $accel.numDecimalsLbl \
        -text {Digits after decimal}
    ttk::combobox $accel.numDecimalsCMB \
        -textvariable ::AccelValues(decimals) \
        -values [list 1 2 3 4] \
        -state readonly
    ttk::label $accel.minChangeLbl1 \
        -text {Ignore less than}
    ttk::spinbox $accel.minChangeEnt \
        -from 0.1 \
        -to 200.0 \
        -increment 0.1 \
        -format {%5.1f} \
        -wrap no \
        -textvariable ::AccelValues(minChange)
    ttk::label $accel.minChangeLbl2 \
        -text {dps change}
    trace add variable ::AccelValues(digits) write ChangeFormat
    trace add variable ::AccelValues(decimals) write ChangeFormat
    # Add ACC units
    # Add Self Test status
    # Add Calibration status
    # Add Unit Select
    # Add offsets
    # Add radius
    # Add Acc No Motion Interrupt Status
    # Add Acc Any Motion Interrupt Status
    # Add Acc High G Interrupt Status
    # Add Acc Any Motion Threashold
    # Add Acc Any Motion Duration
    # Add Acc No Motion Threashold
    # Add Acc Any / No Motion X, Y and Z axis enable
    # Add Acc High G X, Y and Z axis enable
    # Add Acc High G Threashold
    # Add Acc High G Duration
    # Add Acc Power Mode
    # Add Acc Bandwidth
    # Add Acc Sleep Mode
    # Add Acc Sleep Duration
    grid configure $accel.rangeLbl $accel.rangeCMB x x -padx 2 -pady 2 -sticky ew
    grid configure $accel.numDigitsLbl $accel.numDigitsCMB x x -padx 2 -pady 2 -sticky ew
    grid configure $accel.numDecimalsLbl $accel.numDecimalsCMB x x -padx 2 -pady 2 -sticky ew
    grid configure $accel.minChangeLbl1 $accel.minChangeEnt $accel.minChangeLbl2 x -padx 2 -pady 2 -sticky ew
    grid columnconfigure $accel 3 -weight 1

    $accel.minChangeEnt set $::AccelValues(minChange)

}

proc CreateGyroConfigFrame {gyro} {
    ##
    ## Gyrometer
    ##
    ttk::label $gyro.rangeLbl \
        -text {Range:}
    ttk::combobox $gyro.rangeCMB \
        -textvariable ::Configuration(gyrorange) \
        -values [list 250dps 500dps 2000dps] \
        -state readonly
    lappend UI(configurationWidgets) $gyro.rangeCMB
    ttk::label $gyro.numDigitsLbl \
        -text {Digits before decimal}
    ttk::combobox $gyro.numDigitsCMB \
        -textvariable ::GyroValues(digits) \
        -values [list 3 4] \
        -state readonly
    ttk::label $gyro.numDecimalsLbl \
        -text {Digits after decimal}
    ttk::combobox $gyro.numDecimalsCMB \
        -textvariable ::GyroValues(decimals) \
        -values [list 1 2 3 4] \
        -state readonly
    ttk::label $gyro.minChangeLbl1 \
        -text {Ignore less than}
    ttk::spinbox $gyro.minChangeEnt \
        -from 0.1 \
        -to 200.0 \
        -increment 0.1 \
        -format {%5.1f} \
        -wrap no \
        -textvariable ::GyroValues(minChange)
    ttk::label $gyro.minChangeLbl2 \
        -text {dps change}
    trace add variable ::GyroValues(digits) write ChangeFormat
    trace add variable ::GyroValues(decimals) write ChangeFormat
    # Add Self Test status
    # Add Calibration status
    # Add Unit Select
    # Add offsets
    # Add Gyro High Rate Interrupt Status
    # Add Gyro Any Motion Interrupt Status
    # Add Gyro Any Motion Threashold
    # Add Gyro Any Motion Duration
    # Add Gyro Any Motion X, Y and Z axis enable
    # Add Gyro High Rate X, Y and Z axis enable
    # Add Gyro High Rate Threashold
    # Add Gyro High Rate Duration
    # Add Gyro Power Mode
    # Add Gyro Bandwidth
    # Add Gyro Sleep Mode
    # Add Gyro Sleep Duration
    grid configure $gyro.rangeLbl $gyro.rangeCMB x x -padx 2 -sticky ew
    grid configure $gyro.numDigitsLbl $gyro.numDigitsCMB x x -padx 2 -sticky ew
    grid configure $gyro.numDecimalsLbl $gyro.numDecimalsCMB x x -padx 2 -sticky ew
    grid configure $gyro.minChangeLbl1 $gyro.minChangeEnt $gyro.minChangeLbl2 x -padx 2 -sticky ew
    grid columnconfigure $gyro 3 -weight 1

    $gyro.minChangeEnt set $::CurrentValue(minChange)

}

proc CreateMagConfigFrame {mag} {
    ##
    ## Magnetometer group
    ##
    ttk::label $mag.rangeLbl \
        -text {Range:}
    ttk::combobox $mag.rangeCMB \
        -textvariable ::Configuration(magrange) \
        -values [list 250dps 500dps 2000dps] \
        -state readonly
    lappend UI(configurationWidgets) $mag.rangeCMB
    ttk::label $mag.numDigitsLbl \
        -text {Digits before decimal}
    ttk::combobox $mag.numDigitsCMB \
        -textvariable ::MagValues(digits) \
        -values [list 3 4] \
        -state readonly
    ttk::label $mag.numDecimalsLbl \
        -text {Digits after decimal}
    ttk::combobox $mag.numDecimalsCMB \
        -textvariable ::MagValues(decimals) \
        -values [list 1 2 3 4] \
        -state readonly
    ttk::label $mag.minChangeLbl1 \
        -text {Ignore less than}
    ttk::spinbox $mag.minChangeEnt \
        -from 0.1 \
        -to 200.0 \
        -increment 0.1 \
        -format {%5.1f} \
        -wrap no \
        -textvariable ::MagValues(minChange)
    ttk::label $mag.minChangeLbl2 \
        -text {dps change}
    trace add variable ::MagValues(digits) write ChangeFormat
    trace add variable ::MagValues(decimals) write ChangeFormat
    # Add Self Test status
    # Add Calibration status
    # Add Unit Select
    # Add offsets
    # Add radius
    # Add Mag Power Mode
    # Add Mag Operation Mode
    # Add Mag Data Output Rate
    grid configure $mag.rangeLbl $mag.rangeCMB x x -padx 2 -pady 2 -sticky ew
    grid configure $mag.numDigitsLbl $mag.numDigitsCMB x x -padx 2 -pady 2 -sticky ew
    grid configure $mag.numDecimalsLbl $mag.numDecimalsCMB x x -padx 2 -pady 2 -sticky ew
    grid configure $mag.minChangeLbl1 $mag.minChangeEnt $mag.minChangeLbl2 x -padx 2 -pady 2 -sticky ew
    grid columnconfigure $mag 3 -weight 1

    $mag.minChangeEnt set $::MagValues(minChange)

}

proc CreateControlConfigFrame {control} {
    ##
    ## Buttons Group
    ##
    ttk::button $control.start \
        -text {Start} \
        -width 6 \
        -command StartReading
    ttk::button $control.stop \
        -state disabled \
        -text {Stop} \
        -width 6 \
        -command StopReading
    grid configure x $control.start x  $control.stop x  -sticky ew
    grid columnconfigure $control {0 2 4} -weight 1

}

proc CreateConfigurationFrame {w} {

    set nb $w.nb
    set general $w.nb.gen
    set accel $w.nb.acc
    set gyro $w.nb.gyro
    set mag $w.nb.mag
    set control $w.controls

    ttk::notebook $nb

    ttk::frame $general
    $nb add $general \
        -text "General"
    ttk::frame $accel
    $nb add $accel \
        -text "Accelerometer"
    ttk::frame $gyro
    $nb add $gyro \
        -text "Gyrometer"
    ttk::frame $mag
    $nb add $mag \
        -text "Magnetometer"
    ttk::frame $control


    grid configure $nb -sticky nsew -pady 2 -padx 4 -ipady 4
    grid configure $control -sticky ew -pady 4
    grid columnconfigure $w $nb -weight 1
    grid rowconfigure $w $nb -weight 1

    CreateGeneralConfigFrame $general
    CreateAccelConfigFrame $accel
    CreateGyroConfigFrame $gyro
    CreateMagConfigFrame $mag
    CreateControlConfigFrame $control
        
}

proc CreateGyroFrame {w} {
    ttk::labelframe $w.readings \
        -text {Current Readings}
    ttk::labelframe $w.history \
        -text {Change History}
    grid configure $w.readings -sticky ew
    grid configure $w.history -sticky nsew
    grid rowconfigure $w $w.history -weight 1
    grid columnconfigure $w $w.history -weight 1
    
    CreateReadingsFrame $w.readings gyroscope
    CreateHistoryFrame $w.history gyroscope
    
    grid columnconfigure $w 6 -weight 1
}

proc CreateAccelerometerFrame {w} {

    ttk::labelframe $w.readings \
        -text {Current Readings}
    ttk::labelframe $w.history \
        -text {Change History}
    grid configure $w.readings -sticky ew
    grid configure $w.history -sticky nsew
    grid rowconfigure $w $w.history -weight 1
    grid columnconfigure $w $w.history -weight 1

    CreateReadingsFrame $w.readings accelerometer
    CreateHistoryFrame $w.history accelerometer

    grid columnconfigure $w 6 -weight 1
}

proc CreateMagnetometerFrame {w} {

    ttk::labelframe $w.readings \
        -text {Current Readings}
    ttk::labelframe $w.history \
        -text {Change History}
    grid configure $w.readings -sticky ew
    grid configure $w.history -sticky nsew
    grid rowconfigure $w $w.history -weight 1
    grid columnconfigure $w $w.history -weight 1

    CreateReadingsFrame $w.readings magnetometer
    CreateHistoryFrame $w.history magnetometer

    grid columnconfigure $w 6 -weight 1
}

proc CreateGravityFrame {w} {

    ttk::labelframe $w.readings \
        -text {Current Readings}
    ttk::labelframe $w.history \
        -text {Change History}
    grid configure $w.readings -sticky ew
    grid configure $w.history -sticky nsew
    grid rowconfigure $w $w.history -weight 1
    grid columnconfigure $w $w.history -weight 1

    CreateReadingsFrame $w.readings gravity
    CreateHistoryFrame $w.history gravity

    grid columnconfigure $w 6 -weight 1
}

proc CreateLinearAccelerationFrame {w} {

    ttk::labelframe $w.readings \
        -text {Current Readings}
    ttk::labelframe $w.history \
        -text {Change History}
    grid configure $w.readings -sticky ew
    grid configure $w.history -sticky nsew
    grid rowconfigure $w $w.history -weight 1
    grid columnconfigure $w $w.history -weight 1

    CreateReadingsFrame $w.readings linearAcceleration
    CreateHistoryFrame $w.history linearAcceleration

    grid columnconfigure $w 6 -weight 1
}

proc CreateQuaternionFrame {w} {

    ttk::labelframe $w.readings \
        -text {Current Readings}
    ttk::labelframe $w.history \
        -text {Change History}
    grid configure $w.readings -sticky ew
    grid configure $w.history -sticky nsew
    grid rowconfigure $w $w.history -weight 1
    grid columnconfigure $w $w.history -weight 1

    CreateReadingsFrame $w.readings quaternion
    CreateHistoryFrame $w.history quaternion

    grid columnconfigure $w 6 -weight 1
}

proc CreatePitchRollHeadingFrame {w} {

    ttk::labelframe $w.readings \
        -text {Current Readings}
    ttk::labelframe $w.history \
        -text {Change History}
    grid configure $w.readings -sticky ew
    grid configure $w.history -sticky nsew
    grid rowconfigure $w $w.history -weight 1
    grid columnconfigure $w $w.history -weight 1

    CreateReadingsFrame $w.readings prh {roll pitch heading} {"Roll:" "Pitch:" "Heading:"}
    CreateHistoryFrame $w.history prh {timestamp roll pitch heading} {"Timestamp" "Roll" "Pitch" "Heading"}

    grid columnconfigure $w 6 -weight 1
}

proc CreateHistoryFrame {w type {columns {timestamp xaxis yaxis zaxis}} {labels {"Time Stamp" "X Axis" "Y Axis" "Z Axis"}}} {
    variable WidgetData

    ttk::treeview $w.data \
        -xscrollcommand [list $w.hsb set] \
        -yscrollcommand [list $w.vsb set] \
        -columns $columns \
        -displaycolumns  $columns \
        -show headings \
        -selectmode none
    ttk::scrollbar $w.vsb \
        -orient vertical \
        -command [list $w.data yview]
    ttk::scrollbar $w.hsb \
        -orient horizontal \
        -command [list $w.data xview]
    ttk::frame $w.buttons
    ttk::button $w.buttons.reset \
        -text {Clear Log} \
        -width 10 \
        -command [format {%1$s delete [%1$s children {}]; LogValues %2$s} $w.data $type]
    set WidgetData($type,history) $w.data
    
    foreach column $columns heading $labels {
        $w.data column $column \
            -anchor w \
            -stretch yes
        $w.data heading $column \
            -anchor center \
            -text $heading
    }
    $w.data column timestamp \
            -anchor e

    grid configure $w.data $w.vsb -sticky nsew
    grid configure $w.hsb -sticky ew
    grid configure $w.buttons - -sticky ew
    grid columnconfigure $w $w.data -weight 1
    grid rowconfigure $w $w.data -weight 1
    
    grid configure x $w.buttons.reset x -sticky ew
    grid columnconfigure $w.buttons {0 2} -weight 1
}

proc CreateReadingsFrame {w type {columns {x y z}} {labels {"X:" "Y:" "Z:"}}} {
    lassign $columns x y z
    lassign $labels xl yl zl
    
    ttk::label $w.xLabel \
        -text $xl
    ttk::entry $w.xValue \
        -textvariable ::CurrentValue($type,$x) \
        -state readonly
    ttk::label $w.yLabel \
        -text $yl
    ttk::entry $w.yValue \
        -textvariable ::CurrentValue($type,$y) \
        -state readonly
    ttk::label $w.zLabel \
        -text $zl
    ttk::entry $w.zValue \
        -textvariable ::CurrentValue($type,$z) \
        -state readonly
    grid configure $w.xLabel $w.xValue $w.yLabel $w.yValue $w.zLabel $w.zValue x -sticky ew -padx 2 -pady 1
    grid columnconfigure $w [list $w.xLabel $w.yLabel $w.zLabel] -uniform labels -weight 0
    grid columnconfigure $w [list $w.xValue $w.yValue $w.zValue] -uniform values -weight 0
}


##
## Main Body
##
set CurrentValue(handle) [dimu new]
LoadCurrentConfiguration
ReadTemperature
CreateGui
trace add variable Configuration write ConfigurationChange
trace add variable Configuration(operation) write ChangeOperationMode
ChangeOperationMode Configuration operation write

##
## Go into the event loop
##