# furnace control, slightly modified version for lamp oven namespace eval flctrl { } if {![namespace exists lsc]} { source drivers/lsc.tcl } proc stdConfig::flctrl {body} { variable name variable node variable path controller std prop startcmd "s=1" pollperiod 5 5 array set arguments $body # arguments(voltscale) voltage at 100 % # arguments(min_resist) switch off when resistivity is lower than this # arguments(max_resist) switch off when resistivity is bigger than this obj FICTRL -drive wr # @switch_on: set by check_set for switching on control prop @switch_on 0 prop @voltscale $arguments(voltscale) prop @min_resist $arguments(min_resist) prop @max_resist $arguments(max_resist) if {[info proc stdConfig::tdrive] eq ""} { namespace eval :: { source drivers/trun.tcl } } tdrive $name -log 1 hsetprop $path/set check flctrl::check_set kids "fi control ($name)" { node limit par 1000 foreach ch {1 2 3 4} { node t$ch upd kids "settings channel $ch" { node raw rd prop readcmd v$ch prop readfmt v$ch=%g prop update flctrl::update_v prop channel $ch node curve out -text prop width 32 prop model 0 prop check flctrl::curve prop write stdSct::completeUpdate kids "calibration" { hfactory $path/points plain mugger floatvarar 1 } node valid rd -int prop enum 1 prop readcmd s$ch prop readfmt s$ch=%d } } node tref rd prop readcmd tr prop update flctrl::update_ref prop old 0 node tout rd prop readcmd tw prop update flctrl::update_out node toutmax wr prop write flctrl::write_lim prop readcmd wh prop update flctrl::update_out node toutmin wr prop readcmd wl prop write flctrl::write_lim prop update flctrl::update_out node ctrlmode wr prop writecmd s=%d prop readcmd s prop readfmt s=%d prop enum ok,off,illegal_channel,no_sensor,no_waterflow,bad_vacuum,tc_overflow,wall_T_overflow prop update flctrl::update_c node ramp par 20 node smooth par 60 node prop par 0.005 prop help "proportional gain for T slope control" node int par 0.2 prop help "time constant for T slope control" node powerset out default 0 prop check flctrl::check_power prop write stdSct::completeUpdate node power upd node resist upd default 0.025 node maxpower par 150 node maxheater wr prop writecmd m=%.7g prop readcmd m prop readfmt m=%g node output wr prop writecmd o=%.7g prop readcmd o prop readfmt o=%g node manualpower -int par 0 prop enum 1 node ctrlchan wr -int default 1 prop writecmd c=%d prop readcmd c prop readfmt c=%d node interlock_state rd -int prop readcmd is prop readfmt is=%d prop enum ok=0,no_waterflow=1,bad_vacuum=2,no_waterflow_bad_vacuum=3 node interlock_mask wr -int prop readcmd im prop readfmt im=%d prop writecmd im=%d prop enum no_check=0,check_water_only=1,check_vacuum_only=2,check_all=3 node sramp upd node slope upd node v_htr rd prop readcmd U prop readfmt U=%g prop update flctrl::update_offset prop offset 0 node i_htr rd prop readcmd I prop readfmt I=%g prop update flctrl::update_offset prop offset 0 node htr rd prop readcmd h prop readfmt h=%g prop update flctrl::update_power node powerprop upd } } proc flctrl::check_power {} { set p [sct target] if {$p > 0} { set r [hval [sct parent]/resist] set s [sct @voltscale] hset [sct parent]/output [format %.2f [expr 100 * sqrt($p * $r) / $s]] } else { hset [sct parent]/output 0 } } proc flctrl::update_power {} { stdSct::update set v [silent 0 hval [sct parent]/v_htr] set i [silent 0 hval [sct parent]/i_htr] set p [expr $i * $v] updateval [sct parent]/power $p if {$i > 1 && $v > 0.1} { set r [hval [sct parent]/resist] set r1 [expr ($v - 0.005) / double($i + 0.1)] set r2 [expr ($v + 0.005) / double($i - 0.1)] if {$r1 > [sct @max_resist]} { error "resistance $r too high" } if {$r2 < [sct @min_resist]} { error "resistance $r too low" } if {$r < $r1} { set r $r1 # set r [expr $r * 0.9 + $r1 * 0.1] } elseif {$r > $r2} { # set r [expr $r * 0.9 + $r2 * 0.1] set r $r2 } hupdate [sct parent]/resist $r } updateval [sct parent]/power $p return idle } proc flctrl::check_set {} { sct @switch_on 1 if {[sct target] > [hval [sct parent]/limit]} { error "set T > limit ([hval [sct parent]/limit])" } hupdate [sct parent]/target [sct target] if {[string match switched* [hval [sct parent]/status]]} { hupdate [sct parent]/status "" } sct update [sct target] } proc flctrl::cmds {} { set base [sct objectPath] sct cmd_p $base foreach node [hlist $base] { catch { set cmd [hgetpropval $base/$node cmd] sct cmd_$cmd $base/$node lappend query $cmd } } return [join $query " "] } proc flctrl::to_kelvin {ch value} { if {[hvali [sct objectPath]/t$ch/curve] eq ""} { return $value } set tref [hvali [sct objectPath]/tref] set vref [interpolate [hvali [sct objectPath]/t$ch/curve/points] 1 $tref] set v [expr $value + $vref] return [interpolate [hvali [sct objectPath]/t$ch/curve/points] 0 $v extrapolate] } proc flctrl::to_mvolt {ch value} { if {[hvali [sct objectPath]/t$ch/curve] eq ""} { return $value } set tref [hvali [sct objectPath]/tref] set vref [interpolate [hvali [sct objectPath]/t$ch/curve/points] 1 $tref] set vval [interpolate [hvali [sct objectPath]/t$ch/curve/points] 1 $value] return [expr $vval - $vref] } proc flctrl::ctrl_slope {} { if {[sct @switch_on]} { set init "s=0 " } elseif {[hvali [sct parent]/ctrlmode]} { # error state return idle } else { set init "" } set ch [hvali [sct parent]/ctrlchan] if {$ch == 0} { return idle } # alarm when T is increasing by more than 50 K within 5 sec or T > limit + 20 set ta [expr min([hval [sct parent]/t$ch] + 50, [hval [sct parent]/limit] + 20)] set valarm [format %g [to_mvolt $ch $ta]] sct @switch_on 0 #clientput "ctrl talarm=$ta" sct send "${init}a=$valarm c=$ch dx vx sx" return flctrl::ctrl_do } proc flctrl::ctrl_do {} { array set keyval {dx 0 vx 0 sx 0} foreach item [sct result] { lassign [split $item =] key val set keyval($key) $val } set dt $keyval(dx) if {$dt == 0} { # no delta time available (happend on init) return idle } set ch [hvali [sct parent]/ctrlchan] # clientput [array get keyval] set tist [to_kelvin $ch $keyval(vx)] # convert voltage slope to T slope set v1 [to_mvolt $ch [expr $tist - 10]] set v2 [to_mvolt $ch [expr $tist + 10]] set slope [expr $keyval(sx) * 20.0 / ($v2 - $v1)] set tg [hval [sct parent]/target] set ramp [hval [sct parent]/ramp] set out [hval [sct parent]/powerset] set prop [hval [sct parent]/prop] set int [hval [sct parent]/int] updateval [sct parent] $tist set sramp [expr abs($tist - $tg) * 60.0 / [hval [sct parent]/smooth]] if {$sramp > $ramp} { set sramp $ramp } if {$tg < $tist} { set sramp [expr -$sramp] } hupdate [sct parent]/sramp $sramp hupdate [sct parent]/slope $slope set last_dif [silent 0 sct last_dif] set dif [expr $sramp - $slope] # clientput "$dif slope $slope sramp $sramp" sct last_dif $dif set out [expr $out + ($dif * $dt / double($int) + $dif - $last_dif) * $prop] set maxpower [hval [sct parent]/maxpower] set r [hval [sct parent]/resist] set s [sct @voltscale] hset [sct parent]/maxheater [format %.5g [expr sqrt($maxpower * $r) * 100.0 / $s]] if {$out < 0} { if {$dif > 0} { set out 0 } elseif {$out < -$maxpower*10} { set out [expr -$maxpower*10] } } elseif {$out > $maxpower} { if {$dif < 0} { set out $maxpower } elseif {$out > $maxpower * 11} { set out [expr $maxpower * 11] } } # clientput "dif $dif powerset $out" hset [sct parent]/powerset [format %.5g $out] hupdate [sct parent]/powerprop [expr $prop * $dif] return idle } proc flctrl::update_ref {} { if {[hval [sct objectPath]/manualpower] == 0} { [sct controller] queue [sct] slow flctrl::ctrl_slope } set v [lindex [split [sct result] =] 1] set t [format %.1f [expr $v + 273.15]] sct update $t return idle } proc flctrl::update_out {} { set v [lindex [split [sct result] =] 1] sct update [format %.1f [expr $v + 273.15]] return idle } proc flctrl::write_lim {} { sct send [format "[sct readcmd]=%.1f" [expr [sct target] - 273.15]] return read } proc flctrl::update_v {} { stdSct::update set temp_k [to_kelvin [sct channel] [hvali [sct]]] updateval [sct parent] $temp_k if {([hval [sct objectPath]/manualpower] || [hvali [sct objectPath]/ctrlmode]) && [sct channel] == [hval [sct objectPath]/ctrlchan]} { updateval [sct objectPath] $temp_k } return idle } proc flctrl::update_offset {} { lassign [split [sct result] =] name v if {$v > 0} { set v [expr $v + [silent 0 sct offset]] } if {[silent "" sct geterror] ne "powersupply_switched_off"} { sct update $v } return idle } proc flctrl::update_c {} { stdSct::update if {[sct @switch_on]} { return idle } set o [sct objectPath] set old [hvali $o/status] set new [enum_txt [sct]] if {$new ne "ok"} { if {$new != [silent 0 sct oldval]} { clientput "ERROR: $new" sct oldval $new } set interlock_state [expr [hval $o/interlock_state] & [hval $o/interlock_mask]] set summary [list] if {$interlock_state & 1} { lappend summary "no waterflow" } if {$interlock_state & 2} { lappend summary "bad vacuum" } if {[hval $o/tout] > [hval $o/toutmax]} { lappend summary "wall T too high" } elseif {[hval $o/tout] < [hval $o/toutmin]} { lappend summary "missing wall sensor" } set summary [join $summary ", "] if {$summary eq ""} { if {$new eq "off"} { set summary "switched off" } else { set summary "switched off because of $new" } } hupdate [sct objectPath]/status $summary } elseif {$old != ""} { hupdate [sct objectPath]/status "" } return idle } proc flctrl::write {} { set p [expr [sct target]/double([silent 1 sct fact])] sct send "[sct cmd]=$p [flctrl::cmds]" #return flctrl::update return stdSct::complete } proc flctrl::curve {} { if {[sct requested] ne "" && [sct requested] ne "raw"} { lsc::read_curve } }