1# Copyright 2020 Cypress Semiconductor Corporation 2# SPDX-License-Identifier: Apache-2.0 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); 5# you may not use this file except in compliance with the License. 6# You may obtain a copy of the License at 7# 8# http://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, 12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13# See the License for the specific language governing permissions and 14# limitations under the License. 15 16# From https://wiki.tcl-lang.org/page/constants 17proc const {name value} { 18 uplevel 1 [list set $name $value] 19 uplevel 1 [list trace var $name w {error constant} ] 20} 21 22const DEFAULT_RESOLUTION 12 23const MIN_SAMPLE_TIME_NS 83.0 24const OTHER_ADC_CLOCKS 2 25 26# Actual sample time is 1/2 clock less than specified 27# for PSoC4 and 1 clock less for PSoC 6. 28# See SAR_v2 SAMPLE_TIME01 register description. 29const ADC_CLOCKS_NOT_SAMPLING 1 30 31const APERTURE_TIMER_COUNT 4 ;# There are four timers in the SAR 32const APERTURE_TIMER_MIN 2 ;# The minimum value for each timer is 2 clocks 33const APERTURE_TIMER_MAX 1023 34 35# ADC clock constants. These are the outer hardware limits. 36# Actual limits depend on the configuration and may be tighter. 37# See AdcClockFreqMin_Hz and AdcClockFreqMax_Hz properties. 38const ADC_CLOCK_MAX_HZ 60000000 39const ADC_CLOCK_MIN_HZ 1000000 40 41const MIN_ADC_CLOCK_DIV 2 42 43const MAX_NUM_CHANNELS 16 44 45const ARG_IDX_HF_CLK_RATE 0 46const ARG_IDX_IS_FIXED_CLOCK 1 47const ARG_IDX_CLK_RATE 2 48const ARG_IDX_MIN_CLK_RATE 3 49const ARG_IDX_MAX_CLK_RATE 4 50const ARG_IDX_IS_SINGLE_SHOT 5 51const ARG_IDX_SAMPLE_RATE 6 52const ARG_IDX_NUM_CHANNELS 7 53const ARG_IDX_CHANNEL_INFO 8 54 55const NUM_ARGS_PER_CHANNEL 2 56 57const SUCCESS 0 58const ERROR_ARG_COUNT 1 59const ERROR_ID 2 60const ERROR_ARG_VALUE 3 61const ERROR_FAIL_SCHEDULING 4 62 63array set SAMPLE_PER_SCAN_MAP { 64 "CY_SAR_AVG_CNT_2" {2} 65 "CY_SAR_AVG_CNT_4" {4} 66 "CY_SAR_AVG_CNT_8" {8} 67 "CY_SAR_AVG_CNT_16" {16} 68 "CY_SAR_AVG_CNT_32" {32} 69 "CY_SAR_AVG_CNT_64" {64} 70 "CY_SAR_AVG_CNT_128" {128} 71 "CY_SAR_AVG_CNT_256" {256} 72} 73 74# Indices for the list-based implementation of the CyTimingInfo structure 75const TIMING_IDEAL_SAMPLE_ADC_CLOCK 0 76const TIMING_SAMPLE_COUNT 1 77const TIMING_IS_ENABLED 2 78const TIMING_CHANNEL_NUM 3 79 80proc construct_timing_info {minAcqAdcClocksNeeded samplesPerScan enabled chanNum} { 81 # Note: the order of this list must match the above indices 82 return [list $minAcqAdcClocksNeeded $samplesPerScan $enabled $chanNum] 83} 84 85# Indices for the list-based implementation of the CySchedChan structure 86# Input properties 87const CHAN_MIN_ACQ_TIME_NS 0 88const CHAN_SAMPLES_PER_SCAN 1 89const CHAN_IS_ENABLED 2 90const CHAN_RESOLUTION 3 91# Output properties 92const CHAN_TIMER 4 93const CHAN_ACHIEVED_ACQ_TIME_NS 5 94const CHAN_ACHIEVED_SAMPLE_TIME_NS 6 95const CHAN_MIN_ACQ_ADC_CLOCKS_NEEDED 7 ;# Minimum number of ADC clocks to acquire the channel 96const CHAN_MIN_CONV_ADC_CLOCKS_NEEDED 8 ;# Minimum number of ADC clocks needed for a conversion 97const CHAN_MIN_TOTAL_ADC_CLOCKS_NEEDED 9 ;# Minimum number of ADC clocks needed for sampling (acquisition + conversion) 98const CHAN_MAPPED_ACQ_ADC_CLOCKS 10 ;# Actual number of ADC clocks to acquire the channel 99const CHAN_MAPPED_TOTAL_ADC_CLOCKS 11 ;# Actual number of ADC clocks needed for sampling (acquisition + conversion) 100 101proc construct_sched_chan {acqTime samples} { 102 # Note: the order of this list must match the above indices 103 return [list [expr {max2($acqTime, $::MIN_SAMPLE_TIME_NS)}] $samples [expr {$samples != 0}] $::DEFAULT_RESOLUTION \ 104 0 0.0 0.0 0 [expr {$::DEFAULT_RESOLUTION + $::OTHER_ADC_CLOCKS}] 0 0 0] 105} 106 107set hfClockRate 0.0 108set isFixedClock false 109set fixedAdcClockRate 0.0 110set minClockRate 0.0 111set maxClockRate 0.0 112set isSingleShot False 113set targetSampleRate 0.0 114set numChannels 0 115set channels {} 116set adcClockDivider 0.0 117 118set aperturesAdcClock [lrepeat $::APERTURE_TIMER_COUNT $::APERTURE_TIMER_MIN] 119set achievedSampleRate 0 120set achievedScanPeriod_us 0.0 121set cost 0 122 123set min_aperture $::APERTURE_TIMER_MAX 124set min_sample_time_sel 0 125set inj_sample_time_sel "" 126set injMinAcqTimeNs 0 127set injSamplesPerScan 0 128set inj_achieved_acq_time 0.0 129set inj_achieved_sample_time 0.0 130 131set channelName stdout 132 133if {[chan names ModusToolbox] eq "ModusToolbox"} { 134 set channelName ModusToolbox 135} 136 137namespace eval tcl { 138 namespace eval mathfunc { 139 proc min2 {arg1 arg2} { 140 if {$arg1 < $arg2} { 141 return $arg1 142 } else { 143 return $arg2 144 } 145 } 146 147 proc max2 {arg1 arg2} { 148 if {$arg1 > $arg2} { 149 return $arg1 150 } else { 151 return $arg2 152 } 153 } 154 } 155} 156 157proc schedule_sar {} { 158 if {$::argc < $::ARG_IDX_NUM_CHANNELS + 1} { 159 error "Not enough base arguments." 160 return $::ERROR_ARG_COUNT 161 } 162 set ::hfClockRate [lindex $::argv $::ARG_IDX_HF_CLK_RATE] 163 set ::isFixedClock [string is true [lindex $::argv $::ARG_IDX_IS_FIXED_CLOCK]] 164 set ::fixedAdcClockRate [lindex $::argv $::ARG_IDX_CLK_RATE] 165 set ::minClockRate [lindex $::argv $::ARG_IDX_MIN_CLK_RATE] 166 set ::maxClockRate [lindex $::argv $::ARG_IDX_MAX_CLK_RATE] 167 set ::isSingleShot [string is true [lindex $::argv $::ARG_IDX_IS_SINGLE_SHOT]] 168 set ::targetSampleRate [lindex $::argv $::ARG_IDX_SAMPLE_RATE] 169 set ::numChannels [lindex $::argv $::ARG_IDX_NUM_CHANNELS] 170 if {![string is double $::hfClockRate] || ![string is double $::fixedAdcClockRate] || 171 ![string is double $::minClockRate] || ![string is double $::maxClockRate] || 172 ![string is double $::targetSampleRate]} { 173 error "Unable to parse argument values: One of $::hfClockRate, $::fixedAdcClockRate, $::minClockRate, $::maxClockRate, $::targetSampleRate 174is not a floating-point number." 175 return $::ERROR_ARG_VALUE 176 } 177 set ::hfClockRate [expr {double($::hfClockRate)}] 178 set ::fixedAdcClockRate [expr {double($::fixedAdcClockRate)}] 179 set ::minClockRate [expr {double($::minClockRate)}] 180 set ::maxClockRate [expr {double($::maxClockRate)}] 181 set ::targetSampleRate [expr {double($::targetSampleRate)}] 182 if {![string is integer $::numChannels]} { 183 error "Unable to parse argument values: $::numChannels is not an integer." 184 return $::ERROR_ARG_VALUE 185 } 186 if {$::argc < $::ARG_IDX_CHANNEL_INFO + $::numChannels * $::NUM_ARGS_PER_CHANNEL + $::NUM_ARGS_PER_CHANNEL} { 187 error "Not enough channel information." 188 return $::ERROR_ARG_COUNT 189 } 190 for {set i 0} {$i < $::numChannels} {incr i} { 191 set minAcqTimeNs [lindex $::argv [expr {$::ARG_IDX_CHANNEL_INFO + $i * $::NUM_ARGS_PER_CHANNEL}]] 192 set samplesPerScan [get_samples_per_scan [lindex $::argv [expr {$::ARG_IDX_CHANNEL_INFO + $i * $::NUM_ARGS_PER_CHANNEL + 1}]]] 193 if {![string is double $minAcqTimeNs]} { 194 error "Unable to parse argument values: $minAcqTimeNs is not a floating-point number." 195 return $::ERROR_ARG_VALUE 196 } 197 set minAcqTimeNs [expr {double($minAcqTimeNs)}] 198 lappend ::channels [construct_sched_chan $minAcqTimeNs $samplesPerScan] 199 } 200 201 set ::injMinAcqTimeNs [lindex $::argv [expr {$::ARG_IDX_CHANNEL_INFO + 16 * $::NUM_ARGS_PER_CHANNEL}]] 202 set ::injSamplesPerScan [get_samples_per_scan [lindex $::argv [expr {$::ARG_IDX_CHANNEL_INFO + 16 * $::NUM_ARGS_PER_CHANNEL + 1}]]] 203 204 set adcClockRate $::fixedAdcClockRate 205 if {!$::isFixedClock} { 206 set ::adcClockDivider [select_adc_clock_divider] 207 set adcClockRate [expr {$::hfClockRate / $::adcClockDivider}] 208 } 209 if {$::isSingleShot} { 210 set ::solution [schedule_adc_one_shot $adcClockRate] 211 } else { 212 set ::solution [schedule_adc_only_with_fixed_adc_clock $adcClockRate] 213 } 214 215 # If scan period <= 0, scheduling did not succeed. 216 set scanPeriodAdcClock [find_mapped_scan_adc_clocks] 217 if {$scanPeriodAdcClock <= 0} { 218 error "Scheduling failed." 219 return $::ERROR_FAIL_SCHEDULING 220 } 221 set ::achievedSampleRate [expr {int(round($adcClockRate / $scanPeriodAdcClock))}] 222 set ::achievedScanPeriod_us [expr {1e6 / $::achievedSampleRate}] 223 for {set chanNum 0} {$chanNum < [llength $::channels]} {incr chanNum} { 224 set adcClocks [expr {[lindex $::channels $chanNum $::CHAN_MAPPED_ACQ_ADC_CLOCKS] - $::ADC_CLOCKS_NOT_SAMPLING}] 225 lset ::channels $chanNum $::CHAN_ACHIEVED_ACQ_TIME_NS [expr {round(1e9 * $adcClocks / $adcClockRate)}] 226 lset ::channels $chanNum $::CHAN_ACHIEVED_SAMPLE_TIME_NS [expr {round(1e9 * [lindex $::channels $chanNum $::CHAN_MAPPED_TOTAL_ADC_CLOCKS] / $adcClockRate)}] 227 } 228 229 for {set sampleTime 0} {$sampleTime < [llength $::aperturesAdcClock]} {incr sampleTime} { 230 set aperture [lindex $::aperturesAdcClock $sampleTime] 231 if {$aperture >= $::APERTURE_TIMER_MIN} { 232 puts $::channelName [format "param:sample_time_%d=%d" $sampleTime $aperture] 233 if {$aperture < $::min_aperture} { 234 set ::min_aperture $aperture 235 set ::min_sample_time_sel $sampleTime 236 } 237 } else { 238 puts $::channelName [format "param:sample_time_%d=%d" $sampleTime $::APERTURE_TIMER_MIN] 239 } 240 } 241 242 puts $::channelName [format "param:inj_aperture=%d" $::min_aperture] 243 244 set injMappedTotalAdcClocks [expr {($::min_aperture + [expr {$::DEFAULT_RESOLUTION + $::OTHER_ADC_CLOCKS}]) * $::injSamplesPerScan}] 245 set ::inj_achieved_acq_time [expr {round(1e9 * [expr {$::min_aperture - $::ADC_CLOCKS_NOT_SAMPLING}] / $adcClockRate)}] 246 set ::inj_achieved_sample_time [expr {round(1e9 * $injMappedTotalAdcClocks / $adcClockRate)}] 247 248 write_output 249 return $::SUCCESS 250} 251 252proc get_samples_per_scan {arg} { 253 if {[info exists ::SAMPLE_PER_SCAN_MAP($arg)]} { 254 return $::SAMPLE_PER_SCAN_MAP($arg) 255 } elseif {[string is integer $arg]} { 256 return $arg 257 } else { 258 error "Unable to parse argument values: $arg is not an integer." 259 exit $::ERROR_ARG_VALUE 260 } 261} 262 263proc write_output {} { 264 puts $::channelName "param:achieved_sample_rate=$::achievedSampleRate" 265 puts $::channelName [format "param:achieved_sample_period=%.2f us" $::achievedScanPeriod_us] 266 puts $::channelName [format "param:adc_clock_divider=%.1f" $::adcClockDivider] 267 268 for {set chanNum 0} {$chanNum < $::MAX_NUM_CHANNELS} {incr chanNum} { 269 set ch_achieved_acq_time 0 270 set ch_achieved_sample_time 0 271 set ch_sample_time_sel "" 272 if {$chanNum < [llength $::channels]} { 273 set ch_achieved_acq_time [expr {int([lindex $::channels $chanNum $::CHAN_ACHIEVED_ACQ_TIME_NS])}] 274 set ch_achieved_sample_time [expr {int([lindex $::channels $chanNum $::CHAN_ACHIEVED_SAMPLE_TIME_NS])}] 275 set ch_sample_time_sel [format "CY_SAR_CHAN_SAMPLE_TIME_%d" [lindex $::channels $chanNum $::CHAN_TIMER]] 276 } 277 puts $::channelName [format "param:ch%d_achieved_acq_time=%d ns" $chanNum $ch_achieved_acq_time] 278 puts $::channelName [format "param:ch%d_achieved_sample_time=%d ns" $chanNum $ch_achieved_sample_time] 279 puts $::channelName [format "param:ch%d_sample_time_sel=%s" $chanNum $ch_sample_time_sel] 280 } 281 282 puts $::channelName [format "param:sample_time_min=%d" $::min_aperture] 283 set inj_sample_time_sel [format "CY_SAR_CHAN_SAMPLE_TIME_%d" $::min_sample_time_sel] 284 puts $::channelName [format "param:inj_sample_time_sel=%s" $inj_sample_time_sel] 285 puts $::channelName [format "param:inj_achieved_acq_time=%d ns" $::inj_achieved_acq_time] 286 puts $::channelName [format "param:inj_achieved_sample_time=%d ns" $::inj_achieved_sample_time] 287} 288 289proc schedule_adc_only_with_fixed_adc_clock {adcClockRate} { 290 # This method must be called to initialize channel sample and conversion ADC clock counts. 291 find_min_scan_adc_clocks $adcClockRate 292 293 # Set up data structures for optimization without padding. 294 set chanTimesNoPad [lsort -dictionary [get_channel_timing_info]] 295 296 set apertureClocksNoPad $::aperturesAdcClock 297 298 # Set up data structures for optimization with padding. 299 set chanTimesPad $chanTimesNoPad 300 set apertureClocksPad $apertureClocksNoPad 301 set padIndex [find_padding_channel $chanTimesPad] 302 if {$padIndex != 0} { 303 set padChan [lindex $::chanTimesPad $padIndex] 304 set ::chanTimesPad [lreplace $::chanTimesPad $padIndex $padIndex] 305 set ::chanTimesPad [linsert $::chanTimesPad 0 $padChan] 306 } 307 308 # Find solution with minimum ADC clocks without padding. 309 optimize_apertures "chanTimesNoPad" 0 [llength $chanTimesNoPad] "apertureClocksNoPad" 0 [llength $apertureClocksNoPad] 310 311 set ::aperturesAdcClock $apertureClocksNoPad 312 set_channel_timers 0 [llength $::aperturesAdcClock] 313 set mappedMinScanAdcClocksNoPad [find_mapped_scan_adc_clocks] 314 315 # Find solution with minimum ADC clocks, reserving one aperture timer for padding. 316 optimize_apertures "chanTimesPad" 1 [llength $chanTimesPad] "apertureClocksPad" 1 [llength $apertureClocksPad] 317 318 lset apertureClocksPad 0 [lindex $chanTimesPad 0 $::TIMING_IDEAL_SAMPLE_ADC_CLOCK] 319 320 set ::aperturesAdcClock $apertureClocksPad 321 set_channel_timers 1 [llength $::aperturesAdcClock] 322 lset ::channels [lindex $chanTimesPad 0 $::TIMING_CHANNEL_NUM] $::CHAN_TIMER 0 323 set mappedMinScanAdcClocksPad [find_mapped_scan_adc_clocks] 324 325 set targetScanAdcClocks [adc_clocks $::targetSampleRate $adcClockRate] 326 327 if {$mappedMinScanAdcClocksPad <= $targetScanAdcClocks} { 328 # What if more than one channel needs to be padded? 329 set padAmount [expr {$targetScanAdcClocks - $mappedMinScanAdcClocksPad}] 330 set samplesPerScan [lindex $::channels [lindex $chanTimesPad 0 $::TIMING_CHANNEL_NUM] $::CHAN_SAMPLES_PER_SCAN] 331 if {$samplesPerScan > 1} { 332 set padAmount [expr {($padAmount + ($samplesPerScan / 2)) / $samplesPerScan}] 333 } 334 lset ::aperturesAdcClock 0 [expr {min2([lindex $::aperturesAdcClock 0] + $padAmount, $::APERTURE_TIMER_MAX)}] 335 } elseif {abs($mappedMinScanAdcClocksNoPad - $targetScanAdcClocks) < abs($mappedMinScanAdcClocksPad - $targetScanAdcClocks)} { 336 # If unpadded timers overshoot less than padded timers, switch to unpadded timers. 337 set ::aperturesAdcClock $apertureClocksNoPad 338 set_channel_timers 0 [llength $::aperturesAdcClock] 339 } 340 # else, stick with padded timers without adding padding. 341} 342 343proc schedule_adc_one_shot {adcClockRate} { 344 # This method must be called to initialize channel sample and conversion ADC clock counts. 345 find_min_scan_adc_clocks $adcClockRate 346 347 # Set up data structures for optimization without padding. 348 set chanTimesNoPad [lsort -dictionary [get_channel_timing_info]] 349 350 # Find solution with minimum ADC clocks without padding. 351 optimize_apertures "chanTimesNoPad" 0 [llength $chanTimesNoPad] "::aperturesAdcClock" 0 [llength $::aperturesAdcClock] 352 353 set_channel_timers 0 [llength $::aperturesAdcClock] 354} 355 356# Find the minimum number of ADC clocks required to scan all channels. 357# Also stores the minimum number of ADC clocks in the channel table. 358# Assumes each channel has its own aperture timer. 359proc find_min_scan_adc_clocks {adcClockRate} { 360 set minScanAdcClocks 0 361 set newChannels {} 362 363 foreach chan $::channels { 364 lset chan $::CHAN_MIN_ACQ_ADC_CLOCKS_NEEDED [aperture_adc_clocks [lindex $chan $::CHAN_MIN_ACQ_TIME_NS] $adcClockRate] 365 if {[lindex $chan $::CHAN_IS_ENABLED]} { 366 lset chan $::CHAN_MIN_TOTAL_ADC_CLOCKS_NEEDED [expr {[lindex $chan $::CHAN_MIN_ACQ_ADC_CLOCKS_NEEDED] + [lindex $chan $::CHAN_MIN_CONV_ADC_CLOCKS_NEEDED] * [lindex $chan $::CHAN_SAMPLES_PER_SCAN]}] 367 } else { 368 lset chan $::CHAN_MIN_TOTAL_ADC_CLOCKS_NEEDED 0 369 } 370 incr minScanAdcClocks [lindex $chan $::CHAN_MIN_TOTAL_ADC_CLOCKS_NEEDED] 371 lappend newChannels $chan 372 } 373 set ::channels $newChannels 374 return $minScanAdcClocks 375} 376 377# Calculate number of ADC clocks required for specified sample time. 378# Number of ADC clocks required to cover sample time 379proc aperture_adc_clocks {apertureNs adcClockRate} { 380 # Convert to ns. 381 set adcClockNs [expr {1.0e9 / $adcClockRate}] 382 383 # Find number of clocks required to cover aperture period. 384 # Down 1/2 ns for rounding (the ceiling function below will adjust this) 385 set apertureClocks [expr {$apertureNs / $adcClockNs + $::ADC_CLOCKS_NOT_SAMPLING - (0.5 / $adcClockNs)}] 386 387 # Convert to integral number of clocks and enforce limits 388 set clocks [expr {int(ceil($apertureClocks))}] 389 390 # Enforce limits. 391 set clocks [expr {min2($clocks, $::APERTURE_TIMER_MAX)}] 392 set clocks [expr {max2($clocks, $::APERTURE_TIMER_MIN)}] 393 394 return $clocks 395} 396 397# Extract timing info for optimization from channel data. 398proc get_channel_timing_info {} { 399 set chanTimings {} 400 401 for {set chanNum 0} {$chanNum < [llength $::channels]} {incr chanNum} { 402 set chan [lindex $::channels $chanNum] 403 lappend chanTimings [construct_timing_info [lindex $chan $::CHAN_MIN_ACQ_ADC_CLOCKS_NEEDED] [lindex $chan $::CHAN_SAMPLES_PER_SCAN] [lindex $chan $::CHAN_IS_ENABLED] $chanNum] 404 } 405 return $chanTimings 406} 407 408# This function (in the original Java code) always returns 0. 409# TODO: Make it do something useful. 410proc compare_padding_merit {chanTimes $bestPadIndex $padIndex} { 411 return 0 412} 413 414# Find best channel in range to use for ADC clock padding. 415# Prefer channels with no averaging or minimum averaging and lowest ideal ADC clock count. 416# The chanTimes list must be sorted. 417proc find_padding_channel {chanTimes} { 418 set bestPadIndex 0 419 for {set padIndex 1} {$padIndex < [llength $chanTimes]} {incr padIndex} { 420 if {[compare_padding_merit $chanTimes $bestPadIndex $padIndex] > 0} { 421 set bestPadIndex $padIndex 422 } 423 } 424 return $bestPadIndex 425} 426 427# Recursive min cost optimizer. 428# 429# "Cost" is the difference between the number of ADC clocks used 430# by aperture timers to cover the given channels and the number of ADC 431# clocks that would be required if each channel had its own timer. 432# 433# The channel timing info list must be sorted. 434# 435# Each recursive call consumes one aperture timer and zero or more 436# channels. The recursion terminates when there are either no timers 437# or no channels left. 438# 439# Returns min cost timer settings in apertureClocks list. 440proc optimize_apertures {chanTimesName chanBase chanTop apertureClocksName apertureBase apertureTop} { 441 upvar $chanTimesName chanTimes ;# "passed by reference", tcl style 442 upvar $apertureClocksName apertureClocks ;# "passed by reference", tcl style 443 if {$chanTop == $chanBase} { 444 # All channels have been processed. Zero out remainder of timer list. 445 for {set index $apertureBase} {$index < $apertureTop} {incr index} { 446 lset apertureClocks $index 0 447 } 448 return 0 449 } 450 if {$apertureBase + 1 == $apertureTop} { 451 # There is only one timer left. Use it to cover all the remaining channels. 452 set apertureTime [lindex $chanTimes [expr {$chanTop - 1}] $::TIMING_IDEAL_SAMPLE_ADC_CLOCK] 453 lset apertureClocks $apertureBase $apertureTime 454 return [calc_aperture_cost $chanTimes $chanBase $chanTop $apertureTime] 455 } 456 457 # Loop over possible timer values making recursive calls. Save the best found. 458 set minCost 9876543210 459 set minClocks $apertureClocks 460 461 set chanNum $chanBase 462 while {$chanNum < $chanTop} { 463 set apertureTime [lindex $chanTimes $chanNum $::TIMING_IDEAL_SAMPLE_ADC_CLOCK] 464 set nextChanNum $chanNum 465 while {$nextChanNum < $chanTop && [lindex $chanTimes $nextChanNum $::TIMING_IDEAL_SAMPLE_ADC_CLOCK] == $apertureTime} { 466 incr nextChanNum 467 } 468 469 set branchClocks $apertureClocks 470 set branchCost [expr {[calc_aperture_cost $chanTimes $chanBase $nextChanNum $apertureTime] 471 + [optimize_apertures "chanTimes" $nextChanNum $chanTop "branchClocks" [expr {$apertureBase + 1}] $apertureTop]}] 472 if {$minCost > $branchCost} { 473 set minCost $branchCost 474 set minClocks [lreplace $branchClocks $apertureBase $apertureBase $apertureTime] 475 } 476 477 set chanNum $nextChanNum 478 } 479 480 for {set index $apertureBase} {$index < $apertureTop} {incr index} { 481 lset apertureClocks $index [lindex $minClocks $index] 482 } 483 return $minCost 484} 485 486# Calculate cost of covering the specified channels with one timer setting. 487# Requires channel ideal sample times to have been calculated. 488proc calc_aperture_cost {chanTimes chanBase chanTop aperture} { 489 set cost 0 490 491 for {set chanNum $chanBase} {$chanNum < $chanTop} {incr chanNum} { 492 set chan [lindex $chanTimes $chanNum] 493 set chanExcess [expr {($aperture - [lindex $chan $::TIMING_IDEAL_SAMPLE_ADC_CLOCK]) * [lindex $chan $::TIMING_SAMPLE_COUNT]}] 494 incr cost $chanExcess 495 } 496 return $cost 497} 498 499# Assign best fit aperture timers to specified channels. 500proc set_channel_timers {timerBase timerTop} { 501 for {set chanNum 0} {$chanNum < [llength $::channels]} {incr chanNum} { 502 for {set timer $timerBase} {$timer < $timerTop} {incr timer} { 503 if {[lindex $::channels $chanNum $::CHAN_MIN_ACQ_ADC_CLOCKS_NEEDED] <= [lindex $::aperturesAdcClock $timer]} { 504 lset ::channels $chanNum $::CHAN_TIMER $timer 505 break 506 } 507 } 508 } 509} 510 511# Find number of ADC clocks required for scan with specified aperture timers. 512proc find_mapped_scan_adc_clocks {} { 513 set mappedScanAdcClocks 0 514 515 for {set chanNum 0} {$chanNum < [llength $::channels]} {incr chanNum} { 516 set mappedAcqAdcClocks [lindex $::aperturesAdcClock [lindex $::channels $chanNum $::CHAN_TIMER]] 517 lset ::channels $chanNum $::CHAN_MAPPED_ACQ_ADC_CLOCKS $mappedAcqAdcClocks 518 lset ::channels $chanNum $::CHAN_MAPPED_TOTAL_ADC_CLOCKS \ 519 [expr {($mappedAcqAdcClocks + [lindex $::channels $chanNum $::CHAN_MIN_CONV_ADC_CLOCKS_NEEDED]) * [lindex $::channels $chanNum $::CHAN_SAMPLES_PER_SCAN]}] 520 521 incr mappedScanAdcClocks [lindex $::channels $chanNum $::CHAN_MAPPED_TOTAL_ADC_CLOCKS] 522 } 523 return $mappedScanAdcClocks 524} 525 526# Find number of ADC clocks required for target scan frequency. 527proc adc_clocks {targetFrequency adcClockRate} { 528 if {$targetFrequency == 0} { 529 return 2147483647 ;# This is the expected behavior of casting Infinity to an int, according to the Java version 530 } 531 return [expr {int(ceil($adcClockRate / $targetFrequency))}] 532} 533 534# Calculate ADC clock divider for internal ADC clocks. 535# Results are stored in the adcClockDivider property. 536proc select_adc_clock_divider {} { 537 if {$::isSingleShot} { 538 set ::adcClockDivider $::MIN_ADC_CLOCK_DIV 539 return 540 } 541 # Find minimum sample time and ADC conversion clock count for scan. 542 set minScan [find_adc_clock_parameters] 543 set minScanSample_ns [lindex $minScan 0] 544 set minScanConvAdcClock [lindex $minScan 1] 545 546 set minScanSampleTime_s [expr {$minScanSample_ns / 1.0e9}] 547 548 # Select a clock divider that will use about 512 padding clock periods to hit the target clock rate. 549 set targetScanPeriod_s [expr {1.0 / $::targetSampleRate}] 550 set allowedConversionTime_s [expr {$targetScanPeriod_s - $minScanSampleTime_s}] 551 set adcClockHz [expr {($minScanConvAdcClock + 512) / $allowedConversionTime_s}] 552 set ::adcClockDivider [expr {int(round($::hfClockRate / $adcClockHz))}] 553 set maxDivider [expr {int(floor($::hfClockRate / $::minClockRate))}] 554 set minDivider [expr {int(ceil($::hfClockRate / $::maxClockRate))}] 555 set ::adcClockDivider [expr {min2($::adcClockDivider, $maxDivider)}] 556 set ::adcClockDivider [expr {max2($::adcClockDivider, $minDivider)}] 557 set ::adcClockDivider [expr {max2($::adcClockDivider, $::MIN_ADC_CLOCK_DIV)}] 558} 559 560proc find_adc_clock_parameters {} { 561 set minScanSampleNs 0 562 set minScanConvAdcClocks 0 563 564 foreach chan $::channels { 565 set minSampleNs [lindex $chan $::CHAN_MIN_ACQ_TIME_NS] 566 set minConvAdcClocks [lindex $chan $::CHAN_MIN_CONV_ADC_CLOCKS_NEEDED] 567 if {[lindex $chan $::CHAN_SAMPLES_PER_SCAN] > 1} { 568 set minSampleNs [expr {$minSampleNs * [lindex $chan $::CHAN_SAMPLES_PER_SCAN]}] 569 set minConvAdcClocks [expr {$minConvAdcClocks * [lindex $chan $::CHAN_SAMPLES_PER_SCAN]}] 570 incr minScanSampleNs $minSampleNs 571 incr minScanConvAdcClocks $minConvAdcClocks 572 } 573 } 574 return [list $minScanSampleNs $minScanConvAdcClocks] 575} 576 577schedule_sar 578