Files
sea/tcl/drivers/ccu.tcl
2022-08-18 15:04:28 +02:00

1162 lines
28 KiB
Tcl

namespace eval ccu {
variable bits
variable pcnt 0
# bits to be kept / bits to be set
array set bits {
pulsOn {0xfe 1}
pulsOff {0xfe 0}
n2On {0xfc 1}
n2Off {0xfc 2}
heOn {0xfa 1}
heOff {0xfa 4}
nvOpen {0xf6 9}
nvClose {0xf6 1}
nvOff {0x0e 0xc0}
weak {0xcf 0x20}
normal {0xcf 0x10}
strong {0xcf 0x30}
micro {0x3f 0xc0}
short {0x3f 0x80}
long {0x3f 0x00}
}
}
# general ccu initialization
proc stdConfig::ccuInit {} {
variable node
variable ctrl
variable name
if {[controller std "0x0d" 2]} {
controllerDesc "CCU cryostat control unit"
prop resolution 26.214
prop dout 0x06
prop readA0 0
prop readA1 0
prop readA2 0
prop readA3 0
prop readA4 0
prop readA5 0
prop readA6 0
prop readA7 0
prop starttime 0
prop n2off ccu::valveoff n2
prop n2on ccu::valveon n2
prop heoff ccu::valveoff he
prop heon ccu::valveon he
prop beforefixed 0
set node /sics/$ctrl/tasks
poll 1
prop start ccu::start
prop read ccu::handler
}
set node /sics/$ctrl
}
# automatic n2 fill with two pt sensors
proc stdConfig::n2_ccu {{title ""} {type n2cryo}} {
variable name
ccuInit
# A0: upper sensor, A1: lower sensor
prop readA0 1
prop readA1 1
prop n2path /$name
if {$type eq "trap"} {
prop warn_trap_empty 1
set vis false
} else {
prop warn_no_auto 1
set vis true
}
obj n2ccu out -int
default -1
prop write ccu::write
prop enum watching=0,filling=1,inactive=4
prop visible $vis
prop label "filling state"
if {$title eq ""} {
set title "LN2 fill settings"
}
kids $title {
node threshold par 90
prop help {[K] a sensor below this value is immersed}
prop visible $vis
node limit par 240
prop label "warm limit"
prop help {[K] above this value the cryo is considered as warm}
prop visible $vis
node tmo1 par 300
prop label "max. tube cooling time"
prop help {[sec]}
prop visible $vis
node tmo2 par 1800
prop label "max. fill time"
prop help {[sec]}
prop visible $vis
node upper upd
node lower upd
}
return "LN2 fill with CCU"
}
# automatic fill with level reading from external source
proc stdConfig::ccuExtInit {type {readlevel 0}} {
variable node
variable ctrl
ccuInit
obj fillccu out -int
default -1
prop write ccu::writeExt $type
prop enum watching=0,filling=1,inactive=2
prop label "filling state"
if {$type eq "n2"} {
set text LN2
} else {
set text LHe
}
kids "$text fill settings" {
node readlevel -text par $readlevel
prop visible false
node lowlevel par 10
node highlevel par 100
node smooth upd
prop fmt %.7g
default -1
node minfillminutes par 3.0
node maxfillminutes par 30.0
node minholdhours par 12.0
node maxholdhours par 120.0
node tolerance par 20.0
node badreadingminutes par 30.0
node tubecoolingminutes par 3.0
if {$type eq "he"} {
node vessellimit par 10.0
node vext upd
}
}
set node /sics/$ctrl
}
proc stdConfig::n2_ext_ccu {} {
variable name
ccuExtInit n2 "lev n2"
prop n2extpath /$name
prop n2fill "ccu::fillExt n2"
return "LN2 fill with CCU and external level meter"
}
proc stdConfig::he_ext_ccu {{readlevel lev}} {
variable name
ccuExtInit he $readlevel
prop heextpath /$name
prop hefill "ccu::fillExt he"
return "LHe fill with CCU and external level meter"
}
proc stdConfig::he_ccu {} {
variable name
ccuInit
# A3: he sensor
prop readA3 1
prop hepath /$name
# voltage divider 56.2 kOhm + 200 kOhm, max. 10 V
# full is ~ 2.2 V
prop full [expr 56.2 / 256.2 * 10]
prop empty 0
obj heccu upd
return "He reading from AMI135/136 with CCU"
}
proc stdConfig::he9_ccu {} {
variable name
ccuInit
# A6: he sensor (potentiometer channel)
prop readA6 2
prop hepath /$name
# on this level meter, full is 0, empty is 0.5 V
prop full 0
prop empty 0.5
obj heccu upd
return "He reading from old MA09 with CCU"
}
proc stdConfig::n2cool_ccu {} {
variable name
variable node
variable ctrl
ccuInit
obj fillccu out -int
default -1
prop write ccu::writePrecool
prop enum watching=0,filling=1,inactive=2
prop label "precool filling state"
return "LN2 precool with CCU"
}
proc stdConfig::nv_ccu {{vtype VTI}} {
variable name
ccuInit
# A2: pressure sensor
prop readA2 1
prop nvpath /$name
obj nvccu out -int
default 0
prop control ccu::control
prop write ccu::writeNv
if {$vtype != "VTI"} {
# lambda point refrigerator
prop enum "off=5,fixed=0,controlling=1,close=3,open=4"
prop vtype $vtype
} else {
prop enum "automatic=2,fixed=0,controlling=1,close=3,open=4"
prop vtype $vtype
}
poll 0.5 progress ticker
prop ticker ccu::ticker
kids "$vtype needle valve settings" {
node set out
default 2
prop write ccu::writeSet
prop label "flow set"
node flowmax par 20
prop label "flow maximum"
node prop par 200
node int par 10
node minpulse par 100
prop label "minimum pulse length:"
prop enum "micro=1,short=10,long=100"
prop help "micro: 1 ms, short: 10 ms, long: 100 ms"
node tolerance par 0.05
node speed upd
node flow upd
node pos upd
default 20
kids "position estimation parameters" {
node norm par 20
node max par 60
}
node autoflow -none
kids "autoflow control parameters" {
flow::make
}
}
return "$vtype needle valve control with CCU"
}
proc stdConfig::nvread_ccu {} {
variable name
ccuInit
# A2: pressure sensor
prop readA2 1
prop nvpath /$name
prop readonly 1
obj nvccu rd -int
prop read ccu::flowtask
prop enum "readonly=0"
kids "pressure reading" {
node flow upd
}
}
proc ccu::flowtask {} {
sct update 0
namespace eval :: {
flowtask
}
return idle
}
proc ccu::start {} {
# set all digital ports to output push-pull and all outputs inactive
# remark: bit 1 and 2 are inactive High (hex 06)
sct send ":10000000030600FF00FF0006.."
return ccu::setADCrate
}
proc ccu::setADCrate {} {
# set adc rate to 8
sct send ":10000D0001020008.."
return ccu::getidn
}
proc ccu::getidn {} {
sct send ":0300030001.."
return stdSct::completeStart
}
proc ccu::handler {} {
variable last_nv 0
variable last_n2 0
set now [clock seconds]
if {$now/5 != $last_n2/5} {
set last_n2 $now
# read all used channels
set chanlist {0 1 2 3 4 5 6 7}
} elseif {[sct readA2]} {
set last_nv $now
# read only used channels for needle valve control
set chanlist {2 6 7}
}
set min 8
set max -1
foreach chan $chanlist {
if {[sct readA$chan]} {
if {$chan > $max} {
set max $chan
}
if {$chan < $min} {
set min $chan
}
}
}
if {[silent 0 sct hefill] ne 0} {
[sct controllerName] queue [sct heextpath] read hefill
}
if {[silent 0 sct n2fill] ne 0} {
[sct controllerName] queue [sct n2extpath] read n2fill
}
if {$min > $max} {
return idle
}
set n [expr $max - $min + 1]
sct firstChan $min
sct lastChan $max
sct nChan $n
sct send [format ":04%4.4x%4.4x.." $min $n]
return ccu::update
}
proc ccu::smooth {var band old} {
upvar $var val
#clientput "$val $band $old"
set weight [expr 0.05 + pow(($val - $old) / $band, 2)]
#clientput "w $weight"
if {$weight < 1.0} {
set val [expr $old + ($val - $old) * $weight]
}
#clientput "$weight $val"
}
proc ccu::convertPt2T {sensor raw} {
set millivolt [expr $raw / [sct resolution]]
if {$millivolt < 30} {
hupdate [sct n2path]/status "LN2 sensor unplugged"
hsetprop [sct n2path]/$sensor geterror "LN2 sensor unplugged"
return 0
}
if {$millivolt < 150 || $millivolt > 1500} {
hupdate [sct n2path]/status "illegal LN2 sensor reading: $millivolt mV"
hsetprop [sct n2path]/$sensor geterror "illegal LN2 sensor reading: $millivolt mV"
return 0
}
set t [expr 68 * (5000.0 / $millivolt - 1.0) * 0.25 + 26.8]
catch { ccu::smooth t 5.0 [hvali [sct n2path]/$sensor] }
hupdate [sct n2path]/$sensor $t
hdelprop [sct n2path]/$sensor geterror
return $t
}
proc ccu::nvstatus {} {
set status ""
set vtype [hgetpropval [sct nvpath] vtype]
if {[silent -63 hvali [sct nvpath]/flow] < -50} {
append status "$vtype needle valve pressure sensor not connected\n"
} elseif {[silent -1 hvali [sct nvpath]] == -1} {
append status "$vtype needle valve fixed\n"
}
set pos [silent 20 hvali [sct nvpath]/pos]
if {$pos >= [hvali [sct nvpath]/pos/max]} {
append status "$vtype needle valve probably open\n"
}
if {$pos <= 0} {
append status "$vtype needle valve probably closed\n"
}
hupdate [sct nvpath]/status $status
}
proc ccu::update {} {
set fmt [format ":04%2.2X%s" [expr 2 * [sct nChan]] [string repeat %4x [sct nChan]]]
set vars [lrange {a0 a1 a2 a3 a4 a5 a6 a7} [sct firstChan] [sct lastChan]]
if {[eval scan [sct result] $fmt $vars] != [sct nChan]} {
error "bad response to '[sct send]': '[sct result]'"
}
if {[sct readA2]} {
# reading 0..65535, voltage 0..2500 mV with a 100 Ohm resistor: 0..25 mA
# 4..20 mA is 0..250 mbar
set flow [expr ($a2 / 65535.0 * 25 - 4) / 16.0 * 250]
catch { ccu::smooth flow 0.5 [silent 0 hvali [sct nvpath]/flow] }
hupdate [sct nvpath]/flow $flow
if {$flow < -50} {
hsetprop [sct nvpath]/flow geterror "pressure sensor not connected"
switch [silent 0 hvali [sct nvpath]] {
1 - 2 {
hset [sct nvpath] 0
hsetprop [sct nvpath]/flow geterror "pressure sensor not connected"
}
}
} else {
hdelprop [sct nvpath]/flow geterror
}
if {[silent 0 sct readonly] == 0} {
ccu::nvstatus
[sct controllerName] queue [sct nvpath] read control
}
}
if {[sct readA6] == 2} {
# He reading on chan 6 (for MA09)
# reading 65535 is 2.5 V, output is in percent
set val [expr 100.*(2.5*$a6/65535.-[sct empty])/([sct full]-[sct empty])]
hupdate [sct hepath] $val
} elseif {[sct readA3]} {
# He reading on chan 3 (for AMI 135/136 level meter)
# reading 65535 is 2.5 V, output is in percent
set val [expr 100.*(2.5*$a3/65535.-[sct empty])/([sct full]-[sct empty])]
if {$val < -1.0} {
set val -1.0
} elseif {$val > 101.0} {
set val 101.0
}
hupdate [sct hepath] $val
}
if {[sct firstChan] == 0} {
# automatic n2 fill with 2 point sensor
set upper [convertPt2T upper $a0]
set lower [convertPt2T lower $a1]
set lim [hvali [sct n2path]/limit]
set thr [hvali [sct n2path]/threshold]
set state [hvali [sct n2path]]
if {$upper > $lim || $lower > $lim} {
if {$state < 3} {
hupdate [sct n2path]/status "sensor warm"
hupdate [sct n2path] 4
hsetprop [sct n2path] enum watching=0,fill=3,inactive (sensor warm)=4
return n2off
} elseif {$state == 4} {
hupdate [sct n2path]/status "sensor warm"
hupdate [sct n2path] $state
return idle
}
} elseif {[string equal "sensor warm" [result hvali [sct n2path]/status]]} {
hupdate [sct n2path]/status ""
}
if {$upper < $thr && $lower > $thr} {
if {$state < 4} {
hupdate [sct n2path]/status "sensor upside down"
hupdate [sct n2path] 4
hsetprop [sct n2path] enum watching=0,fill=3,inactive (sensor upside down)=4
return n2off
} else {
hupdate [sct n2path] $state
return idle
}
} elseif {[string equal "sensor upside down" [result hvali [sct n2path]/status]]} {
hupdate [sct n2path]/status ""
}
if {[silent 0 sct warn_trap_empty]} {
set now [clock seconds]
if {$lower > $thr} {
if {[silent 0 sct empty_time] == 0} {
sct empty_time $now
}
if {$now > [sct empty_time] + 600} {
set hr [expr 20 - ($now - [sct empty_time]) / 3600]
if {$hr <= 0} {
set txt "the trap gets empty soon - please fill immediately"
} else {
set txt "the trap gets empty soon - please fill within $hr h"
}
} else {
set txt ""
}
} else {
set txt ""
sct empty_time 0
}
if {$txt ne [silent 0 hvali [sct n2path]/status]} {
hupdate [sct n2path]/status $txt
}
}
switch -- $state {
-1 { # initializing
hupdate [sct n2path] 4
hsetprop [sct n2path] enum watching=0,fill=1,inactive=4
if {[silent 0 sct warn_no_auto]} {
hupdate [sct n2path]/status "automatic LN2 filling off"
}
return n2off
}
0 { # watching
if {$lower > $thr} {
clientput "start N2 fill"
hupdate [sct n2path] 1
hsetprop [sct n2path] enum watching=0,fill (starting)=1,inactive=4
sct starttime [clock seconds]
return n2on
}
}
1 { # filling until lower or upper is cold
if {[clock seconds] > [sct starttime] + [hvali [sct n2path]/tmo1]} {
if {$lower < $thr} {
hupdate [sct n2path] 2
hsetprop [sct n2path] enum watching=0,fill=2,inactive=4
return n2on
}
clientput "stop N2 fill (lower sensor not cold quick enough)"
hupdate [sct n2path]/status "lower sensor not cold quick enough"
hupdate [sct n2path] 4
hsetprop [sct n2path] enum watching=0,fill=3,inactive (lower sensor not cold quick enough)=4
return n2off
} else {
if {$upper < $thr} {
clientput "finished N2 fill"
hupdate [sct n2path] 0
hsetprop [sct n2path] enum watching=0,fill=1,inactive=4
return n2off
}
}
sct update $state
return n2on
}
2 - 3 { # filling until upper is cold
if {[clock seconds] > [sct starttime] + [hvali [sct n2path]/tmo2]} {
clientput "stop N2 fill (fill timeout)"
hupdate [sct n2path]/status "fill timeout"
hupdate [sct n2path] 4
hsetprop [sct n2path] enum watching=0,filling=2,inactive (fill timeout)=4
return n2off
} elseif {$upper < $thr} {
clientput "finished N2 fill"
hupdate [sct n2path] 0
hsetprop [sct n2path] enum watching=0,fill=1,inactive=4
return n2off
}
hupdate [sct n2path] $state
return n2on
}
}
hupdate [sct n2path] $state
}
return idle
}
proc ccu::output args {
variable bits
variable pcnt
set dout [sct dout]
foreach a $args {
set b $bits($a)
set dout [expr $dout & [lindex $b 0] | [lindex $b 1]]
}
sct dout $dout
sct send [format ":10000200010200%2.2x.." $dout]
if {[silent 0 result pulsedebug]} {
if {[string equal pulsOn $args]} {
incr pcnt
} elseif {[string equal pulsOff $args]} {
} else {
clientput "$args ($pcnt)"
set pcnt 0
}
clientput "[format "%2.2x" $dout]"
}
}
proc ccu::valveoff {type} {
ccu::output ${type}Off
return stdSct::complete
}
proc ccu::valveon {type} {
ccu::output ${type}On
return ccu::pulse
}
proc ccu::pulse {} {
ccu::output pulsOff
return stdSct::complete
}
proc ccu::write {} {
hupdate [sct n2path]/status ""
switch -- [sct target] {
# watching
0 {
sct update [sct target]
sct enum watching=0,fill=3,inactive=4
return n2off
}
# inactive
4 {
sct update 4
sct enum watching=0,fill=3,inactive=4
hupdate [sct]/status "automatic LN2 filling off"
return n2off
}
1 - 2 - 3 {
sct starttime [clock seconds]
if {[hvali [sct]] < 3} {
sct update 1
sct enum watching=0,fill=1,inactive=4
} else {
sct update 3
sct enum watching=0,fill=3,inactive=4
}
return idle
}
}
return idle
}
# --- automatic fill with level reading from external source ---
proc calcsmooth {level minspeed maxspeed} {
if {![string is double $level]} {
return 999.9
}
set now [clock seconds]
set delta [expr $now - [silent $now sct lasttime]]
sct lasttime $now
# use property for better resolution
set smooth [hvali [sct]/smooth]
if {$smooth < 0} {
set smooth $level
}
set gs [expr $smooth + $delta * $maxspeed]
if {$level > $gs} {
set smooth $gs
} else {
set gs [expr $smooth + $delta * $minspeed]
if {$level < $gs} {
set smooth $gs
} else {
set smooth $level
}
}
set badtime [silent 0 sct badtime]
if {abs($smooth - $level) > [hvali [sct]/tolerance]} {
if {$badtime < [hvali [sct]/badreadingminutes] * 60 * 2} {
sct badtime [expr $badtime + $delta]
}
} else {
if {$badtime > $delta} {
sct badtime [expr $badtime - $delta]
} else {
sct badtime 0
}
}
hupdate [sct]/smooth $smooth
return $smooth
}
proc ccu::fillExt {type} {
set level [silent invalid result [hvali [sct]/readlevel]]
if {! [string is double $level]} {
hupdate [sct ${type}extpath]/status "illegal $type level reading"
return idle
}
set state [hvali [sct]]
set now [clock seconds]
set lowlevel [hvali [sct]/lowlevel]
if {$lowlevel < 0.0} {
hupdate [sct]/lowlevel 0.0
}
if {$lowlevel > 90.0} {
hupdate [sct]/lowlevel 90.0
}
set highlevel [hvali [sct]/highlevel]
if {$highlevel > 100.0} {
hupdate [sct]/highlevel 100.0
}
if {$highlevel < $lowlevel + 10.0} {
hupdate [sct]/highlevel [expr $lowlevel + 10.0]
}
switch -- $state {
-1 { # initializing
hupdate [sct]/status "automatic $type filling off"
hset [sct] 2
sct lasttime 0
return ${type}off
}
0 { # watching
set minspeed [expr - 100.0 / [hvali [sct]/minholdhours] / 3600.]
set maxspeed [expr - 100.0 / [hvali [sct]/maxholdhours] / 3600.]
set s [calcsmooth $level $minspeed $maxspeed]
if {$s < $lowlevel} {
if {[sct badtime] > [hvali [sct]/badreadingminutes] * 60} {
hupdate [sct]/status "automatic $type filling off - continuos bad reading"
hset [sct] 2
return ${type}off
}
# start fill
hset [sct] 1
clientput "$type level low - start fill"
}
return ${type}off
}
1 {
set vcmd [silent 0 sct vessel_cmd]
set now [clock seconds]
set s [hvali [sct]/smooth]
if {$s < [sct minlevel]} {
sct minlevel $s
}
set vessel 999
set maxspeed [expr 100.0 / [hvali [sct]/minfillminutes] / 60.]
if {[sct tubecooling] && $now < [sct startfill] + [hvali [sct]/tubecoolingminutes] * 60 && $s < [sct minlevel] + 5} {
# allow fill tube to get cold
set minspeed [expr - 100.0 / [hvali [sct]/maxfillminutes] / 60.]
} else {
if {[sct tubecooling]} {
sct tubecooling 0
if {$vcmd ne "0"} {
sct vstart_level [expr [result $vcmd]]
# start time - 2 minutes
sct vstart_time [expr $now - 120]
sct vlast_time $now
hupdate [sct]/vext [sct vstart_level]
}
}
set minspeed [expr + 100.0 / [hvali [sct]/maxfillminutes] / 60.]
if {$vcmd ne "0"} {
set dt [expr $now - [sct vlast_time]]
if {$dt > 0} {
set slope [expr ([hvali [sct]/vext] - [sct vstart_level])/([sct vlast_time] - [sct vstart_time])]
} else {
set slope 0
}
set vessel [result $vcmd]
set vext [expr [hvali [sct]/vext] + $slope * $dt]
if {$vext < $vessel} {
if {$vext < $vessel - 2.0} {
set errcnt [silent 0 sct errcnt]
incr errcnt
if {$errcnt > 3} {
hupdate [sct]/status "not enough progress on $type vessel - automatic ${type} filling off ($vext < $vessel - 2)"
hset [sct] 2
return ${type}off
}
sct errcnt $errcnt
} else {
set vessel $vext
}
}
hupdate [sct]/vext $vessel
sct vlast_time $now
}
}
if {$vessel < [silent 0 hvali [sct]/vessellimit]} {
hupdate [sct]/status "$type vessel empty - automatic ${type} filling off"
hset [sct] 2
return ${type}off
}
if {[calcsmooth $level $minspeed $maxspeed] <= $highlevel} {
# continue filling
return ${type}on
}
clientput "$type level high - stop fill"
hset [sct] 0
return ${type}off
}
2 {
set minspeed [expr - 100.0 / [hvali [sct]/minholdhours] / 3600.]
set maxspeed [expr + 100.0 / [hvali [sct]/minfillminutes] / 60.]
calcsmooth $level $minspeed $maxspeed
}
}
return idle
}
proc ccu::writeExt {type} {
sct update [sct target]
hupdate [sct]/smooth [silent 0 result [hvali [sct]/readlevel]]
switch -- [sct target] {
# watching
0 {
catch {logsetup [sct]/vext clear}
hupdate [sct]/status ""
eval [silent "expr 0" sct slow_cmd]
return ${type}off
}
# inactive
2 {
catch {logsetup [sct]/vext clear}
eval [silent "expr 0" sct slow_cmd]
if {[hvali [sct]/status] eq ""} {
hupdate [sct]/status "automatic ${type} filling off"
}
clientput "ERROR: [hvali [sct]/status]"
return ${type}off
}
# fill
1 {
hupdate [sct]/status ""
set vcmd [silent 0 sct vessel_cmd]
if {$vcmd ne "0"} {
set vlev [result $vcmd]
if {$vlev == 60} {
hupdate [sct]/status "vessel level meter not connected - automatic ${type} filling off"
clientput [hvali [sct]/status]
sct update 2
return ${type}off
}
if {$vlev < [hvali [sct]/vessellimit] + 3.0} {
hupdate [sct]/status "vessel not full enough - automatic ${type} filling off"
clientput [hvali [sct]/status]
sct update 2
return ${type}off
}
}
sct minlevel 100
sct errcnt 0
sct startfill [clock seconds]
sct tubecooling 1
eval [silent "expr 0" sct fast_cmd]
return ${type}on
}
}
return idle
}
proc ccu::writePrecool {} {
hupdate [sct]/status ""
sct update [sct target]
switch -- [sct target] {
# watching
0 {
return n2off
}
# inactive
2 {
hupdate [sct]/status "LN2 precooling off"
return n2off
}
1 {
return n2on
}
}
return idle
}
proc ccu::writeNv {} {
if {[sct target] != 5} {
eval [silent expr sct switchgraph] 1
}
hupdate [sct] [sct target]
switch -- [sct target] {
5 { # off
eval [silent expr sct switchgraph] 0
hupdate [sct]/speed 0
ccu::output nvOff
return stdSct::complete
}
0 { # fixed
hupdate [sct]/speed 0
ccu::output nvOff
return stdSct::complete
}
1 - 2 { # controlled or automatic:
sct beforefixed [sct target]
ccu::output nvOff
return stdSct::complete
}
3 { # close
if {[hvali [sct]/pos] < [hvali [sct]/pos/norm]} {
hupdate [sct]/pos [hvali [sct]/pos/norm]
}
}
4 { # open
if {[hvali [sct]/pos] > [hvali [sct]/pos/norm]} {
hupdate [sct]/pos [hvali [sct]/pos/norm]
}
}
}
return idle
}
proc ccu::writeSet {} {
switch -- [hvali [sct objectPath]] {
0 - 3 - 4 - 5 {
hset [sct objectPath] [sct beforefixed]
}
}
sct update [sct target]
return idle
}
proc ccu::adjustpos {delta} {
set pos [hvali [sct]/pos]
set normpos [hvali [sct]/pos/norm]
if {$pos < $normpos} {
if {$delta < 0} {
if {$pos <= 0} {
set pos 0
} else {
set pos [expr $pos + $delta * 0.2]
}
} else {
set pos [expr $pos + $delta]
}
} else {
if {$delta > 0} {
set pos [expr $pos + $delta * 0.5]
} else {
set pos [expr $pos + $delta]
}
}
if {$pos > [hvali [sct]/pos/max]} {
set pos [hvali [sct]/pos/max]
}
hupdate [sct]/pos $pos
}
proc ccu::trans args {
# transform all variables listed in the args
foreach var $args {
upvar $var val
if {$val > 4.0} {
set val [expr 3.3 + ($val - 4.0) * 0.1]
} elseif {$val > 3.5} {
set val [expr 3.2 + ($val - 3.5) * 0.2]
} elseif {$val > 3.0} {
set val [expr 3 + ($val - 3) * 0.4]
}
}
}
# polling script for nv control
proc ccu::control {} {
switch -- [hvali [sct]] {
0 - -1 - 5 { # fixed or off
if {[sct beforefixed] == 0} {
sct beforefixed 2
}
logsetup [sct]/set clear
set speed 0
return idle
}
3 { # closing
set soll -1
set speed -1000
}
4 { # opening
set soll -1
set speed 1000
}
1 { # controlled
set soll [hvali [sct]/set]
# the following line is for graphic (using flowtarget as nv_set curve)
catch {hupdate [sct]/autoflow/flowtarget $soll}
}
2 { # automatic
flow::task /nv/autoflow [hvali [sct]/set] [hvali [sct]/flowmax]
set soll [hvali [sct]/autoflow/flowset]
}
default {
return idle
}
}
if {$soll >= 0} {
# case 1 and 2: pid control
set ist [hvali [sct]/flow]
set tol [hvali [sct]/tolerance]
if {$soll > $ist + $tol} {
set soll [expr $soll - $tol]
} elseif {$soll < $ist - $tol} {
set soll [expr $soll + $tol]
} else {
set soll $ist
}
# transfer function taking into account heavy nonlinearities of needle valve
ccu::trans ist soll
set prop [hvali [sct]/prop]
set int [hvali [sct]/int]
set diff [expr $soll - $ist]
set lastdiff [silent $diff sct lastdiff]
sct lastdiff $diff
# carry is used for finetuning when speed is below 1
set carry [silent 0 sct carry]
set speed [expr $prop * ($diff - $lastdiff) + $int * $diff + $carry]
} else {
logsetup [sct]/set clear
}
set long 100
set short 10
set micro 1
set minpulse [hvali [sct]/minpulse]
if {$speed > 700} {
set speed 700
} elseif {$speed < -700} {
set speed -700
}
# clientput $speed
set normpos [hvali [sct]/pos/norm]
set maxpos [hvali [sct]/pos/max]
set pos [hvali [sct]/pos]
if {$speed < 0} {
set dir nvClose
set plen [expr -$speed]
set force normal
} else {
set dir nvOpen
set plen $speed
if {$pos < $normpos} {
set force strong
} else {
set force normal
}
}
sct pulser ccu::pulser
if {$plen >= $long * 0.9} {
set length long
sct utime endtime
set p [expr int($plen / $long)]
if {$p < 1} {
set p 1
}
sct pulsecnt $p
sct endtime [expr [sct endtime] + $plen * 0.001]
sct plen $plen
if {$plen == 700} {
# a pulse of more than 700 ms is extended to a 1000 ms hardware pulse
set plen 1000
sct pulser ccu::pulsend
} else {
sct pulser ccu::timer
}
} elseif {$plen >= $short * 0.9 && $minpulse <= 10} {
set length short
set p [expr int($plen / $short)]
if {$p > 5} {
set p 5
} elseif {$p < 1} {
set p 1
}
sct pulsecnt $p
set plen [expr $p * $short]
} elseif {$plen >= $micro * 0.9 && $minpulse <= 1} {
set length micro
set force strong
set p [expr int($plen / $micro)]
if {$p > 5} {
set p 5
} elseif {$p < 1} {
set p 1
}
sct pulsecnt $p
set plen [expr $p * $micro]
} else {
sct carry $speed
hupdate [sct]/speed 0
return idle
}
sct carry 0
if {$speed < 0} {
set speed -$plen
} else {
set speed $plen
}
hupdate [sct]/speed $speed
ccu::output $dir $force $length
ccu::adjustpos [expr $speed * 0.001]
return pulser
}
proc ccu::timer {} {
sct utime nowtime
if {[sct nowtime] > [sct endtime]} {
ccu::output nvOff
return stdSct::complete
}
ccu::output pulsOff
return ccu::timer
}
proc ccu::pulser {} {
set p [sct pulsecnt]
incr p -1
sct pulsecnt $p
if {$p <= 0} {
ccu::output nvOff
return stdSct::complete
}
ccu::output pulsOff
return ccu::pulser2
}
proc ccu::pulser2 {} {
ccu::output pulsOn
return ccu::pulser
}
proc ccu::ticker {} {
if {([sct dout] & 0x30) == 0} {
# do not tick when motor is off
return idle
}
ccu::output pulsOn
return ccu::pulsend
}
proc ccu::pulsend {} {
ccu::output pulsOff
return stdSct::complete
}
proc ccu::unpoll {} {
return unpoll
}