Last active
September 24, 2019 03:12
-
-
Save cfillion/248cd5140de5c5a2126e5cd85d1b2629 to your computer and use it in GitHub Desktop.
Calculate valid output current ranges for given R-EXT values [TLC5916/TLC5917 8-Channel Constant-Current LED Sink Driver]
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/env ruby | |
# | |
# TLC5916/TLC5917 8-Channel Constant-Current LED Sink Driver | |
# Calculate valid output current ranges for given R-EXT values | |
# | |
# Run with no arguments to print all valid ranges for common E12 and E96 values | |
# See ./TLC591x_R-EXT.rb --help for usage instructions and supported options. | |
# | |
# Example 1: ./TLC591x_R-EXT.rb 360 720 | |
# Rext= 360Ω Imin= 4.375mA Imax= 52.090mA IΔ= 47.715mA | |
# CM=0 HC=0 Imin= 4.375mA Imax= 8.682mA IΔ= 4.307mA | |
# CM=0 HC=1 Imin= 8.750mA Imax= 17.363mA IΔ= 8.613mA | |
# CM=1 HC=0 Imin= 13.125mA Imax= 26.045mA IΔ= 12.920mA | |
# CM=1 HC=1 Imin= 26.250mA Imax= 52.090mA IΔ= 25.840mA | |
# Rext= 720Ω Imin= 4.375mA Imax= 26.045mA IΔ= 21.670mA | |
# CM=0 HC=1 Imin= 4.375mA Imax= 8.682mA IΔ= 4.307mA | |
# CM=1 HC=0 Imin= 6.562mA Imax= 13.022mA IΔ= 6.460mA | |
# CM=1 HC=1 Imin= 13.125mA Imax= 26.045mA IΔ= 12.920mA | |
# | |
# Example 2: ./TLC591x_R-EXT.rb --config=1,0,111000 442 | |
# Rext= 442Ω Iout= 20.044mA | |
require 'optparse' | |
require 'ostruct' | |
ESeries = Struct.new :name, :values | |
ResultGroup = Struct.new :r_ext, :i_min, :i_max, :i_delta, :subresults | |
Result = Struct.new :r_ext, :cm, :hc, :i_min, :i_max, :i_delta | |
CHIP_IOUT_RANGE = 0.003..0.120 | |
E12 = ESeries.new 'E12', [ | |
1.0, 1.1, 1.2, 1.3, 1.5, 1.6, 1.8, 2.0, 2.2, 2.4, 2.7, 3.0, 3.3, 3.6, 3.9, | |
4.3, 4.7, 5.1, 5.6, 6.2, 6.8, 7.5, 8.2, 9.1 | |
] | |
E96 = ESeries.new 'E96', [ | |
1.00, 1.02, 1.05, 1.07, 1.10, 1.13, 1.15, 1.18, 1.21, 1.24, 1.27, 1.30, 1.33, | |
1.37, 1.40, 1.43, 1.47, 1.50, 1.54, 1.58, 1.62, 1.65, 1.69, 1.74, 1.78, 1.82, | |
1.87, 1.91, 1.96, 2.00, 2.05, 2.10, 2.15, 2.21, 2.26, 2.32, 2.37, 2.43, 2.49, | |
2.55, 2.61, 2.67, 2.74, 2.80, 2.87, 2.94, 3.01, 3.09, 3.16, 3.24, 3.32, 3.40, | |
3.48, 3.57, 3.65, 3.74, 3.83, 3.92, 4.02, 4.12, 4.22, 4.32, 4.42, 4.53, 4.64, | |
4.75, 4.87, 4.99, 5.11, 5.23, 5.36, 5.49, 5.62, 5.76, 5.90, 6.04, 6.19, 6.34, | |
6.49, 6.65, 6.81, 6.98, 7.15, 7.32, 7.50, 7.68, 7.87, 8.06, 8.25, 8.45, 8.66, | |
8.87, 9.09, 9.31, 9.53, 9.76 | |
] | |
TOP_N = 10 | |
DECADES = 4 | |
options = OpenStruct.new | |
options.min_range = CHIP_IOUT_RANGE | |
options.max_range = CHIP_IOUT_RANGE | |
options.overlap_only = false | |
OptionParser.new do |opts| | |
def parse_config(str) | |
parts = str.split(',').map {|v| v.to_i 2 } | |
raise OptionParser::ParseError unless parts.size == 3 | |
config = {cm: parts[0], hc: parts[1], cc: parts[2]} | |
raise OptionParser::InvalidOption unless (0..1) === config[:cm] | |
raise OptionParser::InvalidOption unless (0..1) === config[:hc] | |
raise OptionParser::InvalidOption unless (0..0x40) === config[:cc] | |
config | |
end | |
def parse_range(str) | |
parts = str.split('..').map &:to_f | |
raise OptionParser::ParseError unless parts.size == 2 | |
range = Range.new *parts | |
raise OptionParser::InvalidOption unless | |
CHIP_IOUT_RANGE === range.min && CHIP_IOUT_RANGE === range.max | |
range | |
end | |
opts.banner = "Usage: ./TLC5916_R-EXT.rb [options] [resistance...]" | |
opts.on "--config=CM,HC,CC[0-5]", | |
'Calculate Iout for the given resitance values' do |val| | |
options.config = parse_config val | |
end | |
opts.on "--min-range=#{options.min_range}", 'Desired range for Imin' do |val| | |
options.min_range = parse_range val | |
end | |
opts.on "--max-range=#{options.max_range}", 'Desired range for Imax' do |val| | |
options.max_range = parse_range val | |
end | |
opts.on "--overlap-only", 'Only output overlapping ranges' do | |
options.overlap_only = true | |
end | |
end.parse! | |
# from page 21 and 23 of the datasheet | |
def current(r_ext, cm: 1, hc: 1, cc: 0b111111) | |
vg = (1 + hc) * (1 + cc/64.0) / 4 | |
v_rext = 1.26 * vg | |
i_ref = v_rext / r_ext | |
cg = vg * 3**(cm-1) | |
i_out = i_ref * 15 * 3**(cm-1) | |
i_out | |
end | |
def calculate(r_ext, cm: 1, hc: 1) | |
i_min = current r_ext, cm: cm, hc: hc, cc: 0b000000 | |
i_max = current r_ext, cm: cm, hc: hc, cc: 0b111111 | |
i_delta = i_max - i_min | |
Result.new r_ext, cm, hc, i_min, i_max, i_delta | |
end | |
def print(result) | |
hash = result.to_h | |
puts [ | |
["Rext=%#{DECADES}sΩ", :r_ext], | |
['CM=%d', :cm], | |
['HC=%d', :hc], | |
['Imin=%7.3fmA', :i_min, lambda {|v| v * 1000 }], | |
['Imax=%7.3fmA', :i_max, lambda {|v| v * 1000 }], | |
['IΔ=%7.3fmA', :i_delta, lambda {|v| v * 1000 }], | |
].map {|fmt, field, func| | |
value = hash[field] | |
if value | |
value = func[value] if func | |
fmt % value | |
else | |
placeholder = fmt % 0 | |
next "\x20" * placeholder.size | |
end | |
}.join "\x20" * 2 | |
hash[:subresults]&.each {|result| print result } | |
result | |
end | |
def calculate_all(r_ext, options) | |
min_range, max_range = options.min_range, options.max_range | |
full_range = min_range.min..max_range.max | |
last_max = nil | |
results = [0, 1].repeated_permutation(2).collect {|cm, hc| | |
result = calculate r_ext, cm: cm, hc: hc | |
next unless full_range === result.i_min && full_range === result.i_max | |
next unless min_range === result.i_min || max_range === result.i_max | |
next if options.overlap_only && last_max && result.i_min > last_max | |
last_max = result.i_max | |
result | |
}.compact | |
i_min = results.min_by {|result| result.i_min }&.i_min | |
i_max = results.max_by {|result| result.i_max }&.i_max | |
return unless i_min && i_max && min_range === i_min && max_range === i_max | |
if results.size == 1 | |
results.first | |
else | |
results.each {|result| result.r_ext = nil } | |
ResultGroup.new r_ext, i_min, i_max, i_max - i_min, results | |
end | |
end | |
# def resistors(steps, decades) | |
# decades.times do |decade| | |
# steps.times do |step| | |
# r = 10.0 ** (step.to_f / steps) * 10 | |
# yield r.round.to_i * 10**(decade-1) | |
# end | |
# end | |
# end | |
def resistors(series, decades) | |
return enum_for :resistors, series, decades unless block_given? | |
series = series.uniq.sort | |
decades.times do |decade| | |
series.each {|r| | |
r = r * 10**decade | |
r = r.round if decade > 0 | |
yield r | |
} | |
end | |
end | |
if ARGV.size > 0 | |
ARGV.map {|r_ext| | |
r_ext = r_ext.to_f.round 2 | |
r_ext = r_ext.to_i if r_ext >= 10 | |
r_ext | |
}.sort.each {|r_ext| | |
if options.config | |
result = current r_ext, **options.config | |
puts "Rext=%#{DECADES}sΩ Iout=%7.3fmA" % [r_ext, result * 1000] | |
next | |
end | |
result = calculate_all r_ext, options | |
if result | |
print result | |
else | |
warn "Rext=%#{DECADES}sΩ out of range" % r_ext | |
end | |
} | |
exit | |
elsif options.config | |
warn "--config cannot be used without specifing at least one resistance value" | |
exit | |
end | |
puts "TLC5916/TLC5917 output current ranges" | |
puts options.each_pair.map {|k, v| "#{k}=#{v}" }.join "\x20" | |
results = [E12, E96].collect do |series| | |
puts | |
puts "#{series.name} resistors:" | |
results = resistors(series.values, DECADES).collect do |r_ext| | |
results = calculate_all r_ext, options | |
print results if results | |
results | |
end.compact | |
end.flatten(1).uniq {|result| result.r_ext } | |
puts | |
puts "Top #{TOP_N} lowest Imin:" | |
results.min_by(TOP_N) {|status| status.i_min }.each {|status| print status } | |
puts | |
puts "Top #{TOP_N} highest Imax:" | |
results.max_by(TOP_N) {|status| status.i_max }.each {|status| print status } | |
puts | |
puts "Top #{TOP_N} best IΔ:" | |
results.max_by(TOP_N) {|status| status.i_delta }.each {|status| print status } | |
puts | |
puts "EOF" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment