aboutsummaryrefslogtreecommitdiff
path: root/hw/chibi/chibi_2024/rcalc.py
blob: 2c5473db96d96ccc19ddf39c14b76186fff9fd53 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
#!/usr/bin/env python3
"""
    MBI5026 current set resistor calculations

    The MBI5026's output current is set by a current set via a single resistor
    connected to its R_ext pin.

    To get a larger inter-frame dynamic range Megumin can switch between
    four different current ranges. The ratio between one current range and the
    next smaller one is r=1:8 (eq. -lg(r)=3 bit). This means at b=12bit BCM range we
    get a minimum of

        bmin = b+lg(r) = 8bit @ r=1:16, b=12bit
        
    worst-case in the intermediate ranges using a static current setting.
    Megumin uses BC847 small-signal NPN transistors to switch between three
    current ranges:

                        ┌─────────┐
                        │ MBI5026 │
                        │         │     
                        │    Rext─┼──┬──┤R1├───────────GND
                        │         │  │                 
                        └─────────┘  ├──┤R2├──┤BC847├──GND

                                     ├──┤R3├──┤BC847├──GND

                                     └──┤R4├──┤BC847├──GND

    The transistors are used to select either or none of {R2, R3, R4}. This means
    the R_ext pin sees either R1, R1||R2, R1||R3 or R1||R4. We don't do a full
    R-2R or similar DAC configuration as we only have to maintain the ratio r
    between ranges.

    Megumin's smallest BCM period is tb=250ns resulting in a base BCM rate of
    4MHz minus control overhead. This results in a BCM period and frame rate of

        Tm = tb*(2**b) = 1.024ms @ tb=250ns, b=12bit. 
        fm = 1/Tm ≈ 1kHz
    
    Now, if we want to modulate the display at a current range in between two
    of the preset ranges, we can switch between both ranges with a ratio of
    sqrt(r)=1:4 and still get a frame rate of
    
        f = fm/sqrt(r) = 250Hz @ fm=1kHz, r=1:16
        
    Normalized to the larger of the two ranges (here r1=1) we get the following
    equation for the ratio of the resulting modulated range:

        r_im1    = sqrt(r)*r1 = 0.25               @ r=1:16, r1=1
        r_im_tot = r_im1 + (1-sqrt(r))*r2 = 0.297  @ r2=r*r1

    Including the 2 bit gained by inter-frame modulation this results in the
    following basic ranges at framerate f=250Hz with a slight mid-range
    discontinuity at the mixed ranges:

         Range max │ Total bits
        ───────────┼──────────────────────
             1.000 │ 14
             0.297 | 16 (14 at mid-range)
             0.250 | 14

    The resistances of the resistors R1, R2, R3, R4 used are calculated in this
    script.
"""

prefixes = {' ': 1, 'k': 1e3, 'M': 1e6, 'm': 1e-3, 'μ': 1e-6, 'n': 1e-9}
def format_unit(val):
    for prefix, magnitude in prefixes.items():
        if 1.0 <= val/magnitude < 1000.0:
            return val/magnitude, prefix
    else:
        if val<1:
            return val/10e-9, 'n'
        else:
            return val/10e6, 'M'

def print_var(name, val, unit, **kwargs):
    scaled, prefix = format_unit(val)
    print('{} = {: >7.3f}{}{}'.format(name, scaled, prefix, unit), **kwargs)

r = 1/16
stages = 3
mod_r = 1/8
I_max_led = 0.01
n_boards = 20
n_digits_per_board = 8*4
n_leds = n_boards*n_digits_per_board*8
V_fw = 1.9 # V

print('r = 1:{:.0f}'.format(1/r))

I_min_led = I_max_led*(r**(stages-1)) # A
I_max_mod = I_max_led/mod_r
I_min_mod = I_min_led/mod_r
print_var('I_max_led', I_max_led, 'A')
print_var('I_max_mod', I_max_mod, 'A')
print_var('I_min_mod', I_min_mod, 'A')
if (I_max_mod > 0.09):
    print('\033[91mError: The MBI5026 has a maximum output current of 90mA!\033[0m')

Vrext = 1.26 # V
# Iout = 15 * Vrext/Rext | acc. to MBI5026 datasheet
R1 = 15*Vrext/I_min_mod
Itot_1 = n_leds * mod_r * I_min_mod
Ptot_1 = Itot_1 * V_fw
print_var('R1', R1, 'Ω', end='\t')
print_var('I1', I_min_mod, 'A', end='\t')
print_var('Itot_1', Itot_1, 'A', end='\t')
print_var('Ptot_1', Ptot_1, 'W')

for i in range(stages-2, -1, -1):
    # Rpar = 15*Vrext/(I_max_mod*r)
    # R1||R2 = 1/(1/R1 + 1/R2) =!= Rpar = 15*Vrext/(I_max_mod*r)
    # ⇒ 1/R1 + 1/R2 = 1/(15*Vrext/(I_max_mod*r))
    # ⇒ 1/R2 = 1/(15*Vrext/(I_max_mod*r)) - 1/R1
    # ⇒ R2 = 1/((I_max_mod*r)/(15*Vrext) - 1/R1)
    In = I_max_mod*(r**i)
    Rn = 1/(In/(15*Vrext) - 1/R1)
    Itot_n = n_leds * mod_r * In
    Ptot_n = Itot_n * V_fw
    scaled, prefix = format_unit(Rn)
    print_var('R{}'.format(stages-i), Rn, 'Ω', end='\t')
    print_var('I{}'.format(stages-i), In, 'A', end='\t')
    print_var('Itot_{}'.format(stages-i), Itot_n, 'A', end='\t')
    print_var('Ptot_{}'.format(stages-i), Ptot_n, 'W')

l = [ (1,     [1/2,    1/4,    1/8,    1/16,
               1/32,   1/64,   1/128,  1/256,
               1/512,  1/1024, 1/2048, 1/4096]),
      (1/16,  [1/2,    1/4,    1/8,    1/16,
               1/32,   1/64,   1/128,  1/256,
               1/512,  1/1024, 1/2048, 1/4096]),
      (1/256, [1/2,    1/4,    1/8,    1/16,
               1/32,   1/64,   1/128,  1/256,
               1/512,  1/1024, 1/2048, 1/4096])
    ]
for v, ls in l:
    for e in ls:
        print('{:> 12.10f} {:.0f}'.format(e*v, 0.5/(e*v)))

print('\033[93m---\033[0m')
l = [ (1/2**0,  [1/2,    1/4,    1/8,    1/16,
                 1/32,   1/64,   1/128,  1/256,
                 1/512,  1/1024, 1/2048, 1/4096]),
      (1/2**7,  [1/32,   1/64,   1/128,  1/256,
                 1/512,  1/1024, 1/2048, 1/4096]),
      (1/2**14, [1/32,   1/64,   1/128,  1/256,
                 1/512,  1/1024, 1/2048, 1/4096])
    ]
for v, ls in l:
    for e in ls:
        print('{:> 5.0f} {:> 12.10f} {:.0f}'.format(0.5/e, e*v, 0.5/(e*v)))
plain = sum(l[0][1])
optimized = sum([e for v, ls in l for e in ls])
overhead_percent = (optimized/plain-1)*100
print(plain, optimized, overhead_percent)