From 6f12a41cc6fc61d62bfd862f454e63b0652823fc Mon Sep 17 00:00:00 2001
From: jaseg <git@jaseg.net>
Date: Mon, 12 Jun 2017 13:03:18 +0200
Subject: Add resistor calculation script

---
 fw/main.c                    |  44 +++++++--------
 hw/chibi/chibi_2024/rcalc.py | 127 +++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 149 insertions(+), 22 deletions(-)
 create mode 100644 hw/chibi/chibi_2024/rcalc.py

diff --git a/fw/main.c b/fw/main.c
index 6113a10..2e7b56e 100644
--- a/fw/main.c
+++ b/fw/main.c
@@ -74,32 +74,32 @@ int main(void) {
     SPI1->CR2 &= ~SPI_CR2_DS_Msk;
     SPI1->CR2 |= LL_SPI_DATAWIDTH_16BIT;
     /* FIXME maybe try w/o BIDI */
-    SPI1->CR1 = SPI_CR1_BIDIMODE | SPI_CR1_BIDIOE | SPI_CR1_SSM | SPI_CR1_SSI | SPI_CR1_SPE | (1<<SPI_CR1_BR_Pos) | SPI_CR1_MSTR | SPI_CR1_CPOL | SPI_CR1_CPHA;
+    SPI1->CR1 = SPI_CR1_BIDIMODE | SPI_CR1_BIDIOE | SPI_CR1_SSM | SPI_CR1_SSI | SPI_CR1_SPE | (0<<SPI_CR1_BR_Pos) | SPI_CR1_MSTR | SPI_CR1_CPOL | SPI_CR1_CPHA;
 
-    int i = 0;
-    int val = 0x5555;
+    int val = 0xffff;
     GPIOA->BSRR = GPIO_BSRR_BR_6;
+    int j = 0;
+    int bval = 0x4000;
     while (42) {
-        if (i == 8) {
-            i = 0;
-            val = ~val;
+        for (int i=0; i<8; i++) {
+            spi_send(val);
+            spi_send(val);
+            strobe_leds();
+            spi_send(0x0200 | bval | (0xff^(1<<i)));
+            strobe_aux();
+            for(int i=0; i<10; i++)
+                tick();
+            //j++;
+            if (j == 1000) {
+                j = 0;
+                if (bval == 0x4000)
+                    bval = 0x8000;
+                else if (bval == 0x8000)
+                    bval = 0x0000;
+                else
+                    bval = 0x4000;
+            }
         }
-        spi_send((i&1 ? 0xfa00 : 0xf000) | (0xff^(1<<i)));
-        i++; // 1100'0100"0000'0000
-        strobe_aux();
-
-        spi_send(val);
-        spi_send(val);
-        strobe_leds();
-        /*if (i == 32) {
-            i = 0;
-        }
-        i++;
-        spi_send((i&16) ? 0 : (1<<i));
-        spi_send((i&16) ? (1<<(i&15)) : 0);
-        strobe_leds();
-        */
-        LL_mDelay(200);
     }
 }
 
diff --git a/hw/chibi/chibi_2024/rcalc.py b/hw/chibi/chibi_2024/rcalc.py
new file mode 100644
index 0000000..3bba342
--- /dev/null
+++ b/hw/chibi/chibi_2024/rcalc.py
@@ -0,0 +1,127 @@
+#!/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')
+
-- 
cgit