Skip to content

Instantly share code, notes, and snippets.

@cfillion
Last active September 24, 2019 03:12
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cfillion/248cd5140de5c5a2126e5cd85d1b2629 to your computer and use it in GitHub Desktop.
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]
#!/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