# Linear 3D triangulation

I came across this 3D linear triangular method in TheiaSFM:

bool TriangulateNView(const std::vector<Matrix3x4d>& poses,
const std::vector<Vector2d>& points,
Vector4d* triangulated_point)
CHECK_EQ(poses.size(), points.size());

Matrix4d design_matrix = Matrix4d::Zero();
for (int i = 0; i < points.size(); i++) {
const Vector3d norm_point = points[i].homogeneous().normalized();
const Eigen::Matrix cost_term =
poses[i].matrix() -
norm_point * norm_point.transpose() * poses[i].matrix();
design_matrix = design_matrix + cost_term.transpose() * cost_term;
}
*triangulated_point = eigen_solver.eigenvectors().col(0);
return eigen_solver.info() == Eigen::Success;
}


I was aware of the DLT (direct linear transform), but it didn't look like any formulation I've seen before. It's actually pretty neat. Let's say you're trying to find an unknown homogeneous point in 3D, $\mathbf{X} = [X, Y, Z, 1]$. What we have is $N$ poses, $P$, represented as $3\times 4$ matrices and the corresponding 2D coordinates represented as homogeneous points in $\mathbb R^3$. The 2D points are written as $\mathbf{x} = [ x, y, 1]$.

Since we're triangulating the 3D point, and we have homogeneous coordinate (i.e. $\alpha \mathbf{x} \equiv \mathbf{x}$) then for all $i$ we should have:
$\alpha_i \mathbf{x}_i \approx P_i \mathbf X$
given an scale factor $\alpha$.

Now let's pick apart the code above. Let's call design_matrix $D$ and cost_term $C$. On line 12, we have:
$\displaystyle D = \sum_{i=1}^{N} C_i^\top C_i$
And line 15 we’re finding the eigenvector corresponding to the smallest eigenvalue of D (SelfAdjointSolver produces them in a sorted order), i.e.
$\mathbf{X} \approx \displaystyle \underset{\mathbf{v}, |\mathbf{v}|=1}{\text{argmin}}\ \mathbf{v}^\top D \mathbf{v}$

We can rewrite $D = \mathbf{C}^\top\mathbf{C}$ where:
$\mathbf{C} = \left[ \begin{matrix} C_1\\ C_2\\ \vdots\\ C_N\\ \end{matrix}\right]$, which substituting in above gives:
$\mathbf{X} \approx \displaystyle \underset{\mathbf{v}, |\mathbf{v}|=1}{\text{argmin}}\ \|\mathbf{C v}\|_2^2$,
which is of course the right singular vector corresponding to the smallest singular value of $C$. Using eigen decomposition is much more efficient the size is $O(1)$, not $O(N)$, but probably at the penalty of less numerical precision.

Either way we’re trying to find the approximate nullspace of $\mathbf{C}$, which means finding something that’s roughly in the null space of all the $C_i$s. But why?

On lines 8–11, we have:
$C_i = P_i - \mathbf{\hat{x}\hat{x}^\top}P_i$,
and we’re claiming $\mathbf{X}$ is about in the null space. Let’s see what happens when we multiply by it:
$(P_i - \mathbf{\hat{x}\hat{x}^\top}P_i) \mathbf{X} = P_i \mathbf{X} -\mathbf{\hat{x}\hat{x}^\top}P_i \mathbf{X}\\$
Now, substituring in the first equation we have all the way back at the top gives:
$\approx \alpha \mathbf{x} - \alpha\mathbf{\hat{x}\hat{x}^\top x} = \alpha \mathbf{x} - \alpha\mathbf{\hat{x} |x|} = \alpha \mathbf{x} - \alpha\mathbf{x} = 0$
taa daa!

So there you go! If there is no noise, $\mathbf{X}$ is in the right null space of $C_i$ and so the null space of $\mathbf C$ and of course $D$. If there is noise, then it’s closest to being in the null space of all of $C_i$ measured in a least squares sense.

Note though that it’s just an algebraic error, not a reprojection error so it will give slightly oddly scaled results which will give more weight to some points than others. It is however a rather elegant solution.

# Boom! One day your plug goes boom!

Don’t give up in gloom…

I was pushing the plug into a socket on an extension lead and it gave a colossal flash and bang. So here’s the weird things:

1. What the hell.
2. No, seriously, what the hell?
3. The extension lead was fine and appears to still work.
4. The wiring to the wall socket was checked a couple of years ago when the electrician replaced my fuse boxes (including the wooden fusebox with asbestos lined fuse folders!) with a modern consumer unit.
5. For those of you not familiar with British foot stabbersplugs the big pin in the front with the chunk blown out of it is the Earth pin. The plug as you can see is just a simple  figure-8 adapter (Apple used to be really obnoxious and made the nonstandard, but they’ve stopped doing that now). Naturally, being a 2 pin connector, it’s not grounded so that earth pin SHOULD NOT BE CONNECTED TO ANYTHING!
6. The pin on the upper left of the main picture with the really big chunk blown out of it and the black stuff all over it is the neutral.
7. So I say again: WHAT THE HELL?

The event tripped the breaker but not the RCD (and not the 13A fuse in the extension lead either). I guess it’s a race to see which goes first…?

If you look closely, the live pin is not entirely unscathed. There’s definitely a little arc damage on it, though I don’t know if that was from the event or just regular use. You see on the live and neutral, there’s little bits of arc damage on/near the top. This is where the pins contact, so that may be from regular use.

Which made me think: the arc happened when the plug was already substantially inserted (you can see the damage is well down from the top) which means that the wipers were making good contact.

So, in conclusion:

In Part 1 I hacked it to get 99 lives.
In Part 2 I did an excessively thorough analysis of the current limiting.
In Part 4 I found it keeps resetting on life 83.

The light mask comes with an LCD. I’ve always been curious about driving them, but never really taken the time to look. So, I figured I’d get my scope out and have a go. First off, it’s two seven segment displays. Rather handily, Neutrogena have left nice large test points for me. And if you look closely they even saved me the hassle of counting the number of them! There are 8. So, it must be a multiplexed display. Assuming it’s even vaguely symmetric, it’s got to be something like 5×3 or 4×4 (with some dead spots). So, time to break out the scope! First though, I have to solder some wires on:

Except they don’t fit under the LCD. Plus if you connect the power up backwards, then it appears to stop working. Why knew? Fortunately I have one more spare. The failure mode seems to be that one digit is no longer working (the LCD works though—I rubbed some power supply leads across the zebra strip and they all lit up.). Weird.

Um…

Yes OK, so it’s actually working fine (which is weirder) it’s just that it displays “0”, not “00” because it’s made for end users who aren’t expecting everything to be in nicely formatted hex…

So, I’m vaguely aware of some things about LCDs, in no particular order:

• They activate off DC, but that eventually destroys them so you should use AC.
• Driving simple (non multiplexed) LCDs is easy.
• Driving multiplexed ones is harder than you’d expect .
• There’s a threshold voltage below which they don’t turn on.

And here’s what the waveform for the first 4 pins looks like:

I blame my scope. No way it’s a PEBKAC error. There are like, 4 levels and they go all over the place. It’s crazy.

OK, that is indeed harder than I’d expect. Reading around was a bit boring, confusing and badly written. There’s some information which indicates it’s possible to drive them off only 2 levels. This would make sense: if you want to make a particular row/column pair light up, then you drive those two out of phase. Everything else…

Oh I see.

So, I guess the other rows have to be driven in-phase with the column, and the other columns… hmm. OK, I guess that’s why they have multiple levels. If you have a 5V supply, and you drive the row/column out of phase, the one intersecting segment sees 10v pk-pk. If you drive the other rows and columns with an idle voltage (say 2.5V DC) then the segments see either 0V if neither the row/column is driven or 5V pk-pk if a row/column is driven and the column/row is idle.

Backing up, imagine driving a matrix of light bulbs (not LEDs because I don’t want to get bogged down in Charlieplexing) from switches:

A multiplexed display matrix with one rwo switch and two column switches closed.

Switches are either closed, which on a microcontroller means driving the pin as 1 or 0 depending on which rail the switch is connected to, or open which means tristated. For current driven things, like light emitters, it’s easy: tristated means no current flows. For voltage devices, it simply means “not actively driven”, so something needs to be done with it, i.e. bias it to some neutral voltage.

I have no idea which things are rows and which things are columns on the display. However, I do know that the voltages are 0, 1.6, 3.3 and 5V. I’d hazard a guess that 3v pk-pk (i.e. a neighbouring pair) won’t drive the display but 10V pk-pk will. Not sure about 6.7. Probably?

Well, I’ve got an Arduino and some voltage regulators. For a 5V drive, I can easily get 0, 2.5V and 5V by tristating an output and pulling it to a central value:

Pulling the pins to a 2.5V rail made with a potential divider and a TS358 opamp (costs 6p!)

I used 47k because it’s high ish (won’t load the Arduino or opamp much) and I have plenty of them. Anything in the range of about 1k to several meg would probably work OK.

I could use the 3.3V output as the central value but honestly that seems to be tempting fate and I don’t know if it can sink current. Instead, I’ll use the entertainingly cheap TS358CD op-amp. So, time to cut out the useless remains of the device and wire up the Arduino!

I can still fix this!

Also, the test pads aren’t all they’re cracked up to be: the joints need to be filed down. Even now I’m not sure I’m getting perfect contact between the LCD and the board.

I had to file the tops of the joints down very carefully (totally unnecessary as it transpired).

Anyway I’ve wired up pins 1-8 on the arduino (not 0!) to 1-8 on the LCD, more for my own sanity than anything else. And with a simple program:

void setup(){}

void loop() {

pinMode(1, OUTPUT);
digitalWrite(1, HIGH);
delay(1);
digitalWrite(1, LOW);
delay(1);
pinMode(1, INPUT);
delay(1);
}


I get this scope trace:

This had me going for quite a while. The pin set to input should be getting 2.5V.  But it’s not; it’s being pulled up. I looked up: the internal pullup is 20k. The voltage is consistent with that: 2.5 * 47 / (47 + 20) + 2.5 ≈ 4 ish.

Well, that had me going for a while. I went back and forth with trying to figure out how to turn the pullup off (it’s off by default), turning it on and seeing what happened, plugging and unplugging wires and all that. Turns out that I was using pin1 which on the Arduino is the TX pin for the serial port if you want it to be. That means it has an LED attached which is doing extra pullup effectively to almost exactly the right value.

Ho ho ho.

So looks like I’ll be using pins 2-9 instead and I won’t get to keep my sanity. But at least that works.  Also I realised after a, er, little debugging that the reason the device has screws next to the LCD is so that they push the board against the zebra strip ensuring good contact. I wonder if I should use those screws…

Anyhow, now I think what remains is to so a somewhat exhaustive search over all pairs of wires driving them in opposition, to see what happens when they’re activated.

static const int A=2, B=3;
void setup(){
pinMode(B, OUTPUT);
pinMode(A, OUTPUT);
}

void loop() {
digitalWrite(A, 1);
digitalWrite(B, 0);
delay(1);
digitalWrite(A, 0);
digitalWrite(B, 1);
delay(1);
}


That only took a few minutes: it’s only actually 28 combinations. Here are the results I’ve noted down, along with the matrix that’s implied by the numbers. I’ve written the numbers as pairs indicating the two pins that have been activated:

Oh actually! It’s even better! This proves that not only is 10v pk-pk sufficient to drive the segments (I was sure of this), but 5v pk-pk isn’t, which I wasn’t so sure about. That’s nice: no extra circuitry is required. So, what we have is a 4×4 matrix. What I’m going to do is drive each of the rows in turn, while driving all 4 columns simultaneously.

The mapping is very regular though and we actually have essentially two 4×2 matrices for the two digits. The plan: each digit will be represented by a 7 bit number, one bit for each segment. Then, a 4×2 matrix will be represented as 4 2 bit numbers. The next step is a little tedious and involves designing the segment pattern for each digit and figuring out the mapping of segments to columns for each row. I’ve gone for A-Z (excluding k,m,v,w,x), picking the lowercase letter in general when there’s no other criteria and of course 0-9:

And that’s most of it. All that remains is to write a program which drives the rows in sequence and sets the correct column pattern for each row. Also, I’m going to have a function that maps ASCII to the segment patterns so I can write stuff on the display. My choice of driving is to drive each row low in turn, then repeat with each row high in turn to make it AC. I did try row 1 high then low, then row 2 high then low etc too but it didn’t make much difference. Here’s he code:

void setup(){};

//Decode uppercase ASCII and numbers into
//a list of segments, stored as a bit pattern.
uint8_t ascii_to_code(uint8_t c)
{
static const uint8_t letters[26] = {
0x77,0x7c,0x39,0x5e,
0x79,0x71,0x6f,0x74,
0x06,0x0e,0x00,0x38,
0x00,0x54,0x5c,0x73,
0x67,0x50,0x6d,0x78,
0x1c,0x00,0x00,0x00,
0x6e,0x5b
};
static const uint8_t numbers[10]={
0x3f,0x06,0x56,0x4f,
0x66,0x6d,0x79,0x07,
0x7f,0x67
};

if(c >= 65 && c = 48 && c >4 | (n&64)>>6;
}
uint8_t r3(uint8_t n){
return (n&16)>>3 | (n&4)>>2;
}
uint8_t r2(uint8_t n){
return (n&8)>>2;
}

//Set the column outputs as driven or inactive
//according to the input bit pattern. Polarity
//determins whether to drive high or low.
void set_columns(uint8_t n, bool polarity)
{
for(uint8_t i=0; i < 4; i++)
if(n&(1<<i))
{
pinMode(i+6, OUTPUT);
digitalWrite(i+6, polarity);
}
else
pinMode(i+6, INPUT);
}

void display_digit(uint8_t left, uint8_t right)
{
//Columns entries for both digits for
//the 4 rows.
uint8_t rows[4];
rows[3] = r5(left)<<2 | r5(right);
rows[2] = r4(left)<<2 | r4(right);
rows[1] = r3(left)<<2 | r3(right);
rows[0] = r2(left)<<2 | r2(right);

//Do positive columns/negative rows first
//then repeat with the polarity inverted
//Activate each row in turn and set the
//column entries for that row.
for(int p=0; p  100){
p++;
q=0;
}
}


There was a lot of fiddling around, most of which did very little. Basically, driving a multiplexed display off 3 levels is pretty marginal. I found that often some digits would ghost on, and others would be very faint. I could increase the contrast by lengthening the amount of time it took to draw the whole display, by driving a row with many cycles then moving on to the next row, but it only got really good when it was far far too slow. I did find putting a slight pause in between driving rows did help. Removing it darkened everything including ghosted on digits, lengthening it lightened everything. The value I got was about the best one I could find.

Here's what it looks like:

It's not perfect, you can see the contrast is a bit poor and so "hELLo" comes out looking a bit like "hECCo". Nonetheless I'm moderately pleased, since it does kind of work. I have to be careful with the choice of symbol though because they're not all that good.

# Neutrogena light mask part 2: down the rabbit hole

In part 1 I hacked the light mask to get 99 lives. In part 3, I play with the LCD. In part 4, I find it annoyingly doesn’t really have 99 lives.

## Final touches

Since it’s more convenient to use the built in timer (for now) than time manually, I added a programming port on the side so I can reprogram it when I need to:

The connections are CS, CK, MOSI, MISO (not required) and GND.

It’s always useful to have IPA or white spirits (effective but smelly) around to wipe off the excess epoxy on the visible bits to get nice looking results. I made the hole with a drill and needle files.

## Further analysis of the circuit

I’m now kind of curious about how accurate my guesses about the functionality were. Also, I have another controller kicking around which is now at 0 uses remaining and has no programming port. So, I pulled off the transistor. First, here’s the  hFE:

The transistor meter doesn’t get a lot of use these days

That’s comfortably in the middle of the 160-300 range expected from the datasheet.The transistor tester has obnoxiously deep holes for the legs…

So, what about the stiffness of the regulator? So, it’s driven by a 1k resistor from a 3.3v source through the base. I’ve got a bag of ST Microelectronics 7833 regulators. They’re meant for regulation not references and the tolerances are quite weak, but in practice the regulation is very good. From 4.65V to 27V, it’s bang on 3.20V output. It was a tiny bit worse at the low end (4.65 to 5.5V), giving out 3.19V when it was scorching due to being connected up backwards, but cold it’s even better. Here’s the test circuit:

With TP0 and TP1, I’ll be able to measure both the voltage across the transistor and the current through it.

### Aside

The resistor is a whacking great 10Ω beast from some old board I found in a junk pile at my old cow-orking space:

Two of the white ceramic rectangles are 10Ω, 10W resistors. The black column in the lower right is a high power 300Ω resistor. Those relays look beautiful and I want to use them for something.

I find having a junk pile of old boards good. Partly I find that despite being able to easily buy the parts I need, I often don’t think of them until I need them at which point I’d in practice have to wait until the next weekend. Having a pile of oddball parts can often yield something useful which works before the weekend expires. Also there’s something satisfying about getting use out of landfill. And then there’s the nostalgia trip: when I was a kid, many of my components were rescued off old junk boards.

There’s a bit of a knack to removing large parts, but it’s much easier now I have a half decent iron (it’s only half decent, but better than a 25W fixed temperature one!). Setting it hot (426C) helps, of course due to the thermal mass. The other trick is to add lots of extra leaded solder. This dilutes the unleaded stuff lowering the melting point and helps thermal coupling of the iron to the joint. Also, having a moderate amount of solder seems to work better with solder suckers. It’s counterintuitive to add solder when you want to remove it 🙂

The resistor is a Dale (Vishay now) 5% 10Ω wirewound ceramic cased one. Worth a couple of quid new. A nice part from a good manufacturer.

### Back to it

Hmm, no that’s not quite going to work. Even at 4V, we’ll push 400mA (the expected value) through the resistor, which may correspond to as little as 4.2V total if the transistor is well saturated. That’s below what the 7833 can regulate; unlike the one on the light mask it’s not LDO (low dropout). Looks like we’ll need two PSUs. Fortunately I have ALL the wall warts. This one (actually a brick not a wart) comes from an old IDE hard disk enclosure of 2008 vintage and gives a good 1.5A at 5 and 12V. Off comes the 5 pin mini DIN and on goes a header suitable for a breadboard or other similar socket:

I always find it’s worth doing things properly: I added a rigid strain relief and insulator using some polycaprolacetone derived plastic. It will stop shorts and annoying breakages.

The modified circuit is now this:

And I also put an ammeter in series with the base. It’s current-ly (heh heh heh) running at 2.49mA. I reckoned 2.7 before, so that’s not a bad guess! I tried a manual voltage ramp using my power supply, but it’s too slow: the transistor heats up a LOT, so the base current increases a lot too. I guess that’s part of the circuit stiffness in a sense, but I want a roughly constant temperature ramp if I can get it.

New circuit time!

I’m using an Arduino for doing the timing. It has a very short duty cycle (0.1%), so instead of dissipating somewhere around 10W, the whole assembly will dissipate 10mW or so. I guess I probably don’t need those power resistors after all. The switching is done using a N channel MOSFET (2N7000) to switch a high side power P channel MOSFET (IRLIB9343). The switch going on causes the 100μF capacitor to charge, rapidly ramping the voltage across the assembly under test, then it goes off, causing it to rapidly ramp down. Given the values, the time constant is around 1ms, so the whole test should be finished in 2-3ms or so (I’m wrong! more like 6-10ms).

The circuit’s been running a while and nothing is warm, and the base current is steady at 2.51mA, which is excellent. This is also where I love having a good 4 channel slope. I’ve set the scope to trigger off the Arduino, giving a very nice trigger. I then set it to averaging mode, to give very clean traces:

Channel 1: V1, Channel 2: Arduino, Channel 3: Vt

Plotting Vt against I=(V1 – Vt)/10 gives:

Yikes!! That’s one hell of a lot of hysteresis and the voltage goes negative??? It is however pretty decently stiff especially on the return leg. There are a few clues knocking around indicating that there’s a problem with my building of the circuit, rather than the hysteresis being due to the device under test. One is that it, well, goes negative. Then there’s a very sharp drop when the switch turns off, far faster than the discharging. And finally, the Arduino voltage takes a very long time to settle back to 0V.

Nonetheless even with this measurement we’re getting 20% regulation of current between 1 and 7V. I think the return leg is probably more accurate since it’s going slower so there are fewer transient effects. That’s more like 10% regulation.

I think the main reason is likely that I’ve been awfully lax with where I’m taking the measurements, see:

Left: the circuit, right: labeled. The power traces are in red and black. Scope probes are labeled in the trace colours (yellow, V1, magenta Vt, cyan Arduino) with ground as grey. The component under test has a white square around it.

The currents are biggish (half an amp or so) and the breadboard trace resistance as well as the contact resistance is nontrivial.You can see there’s a fair bit of stuff between the measurement ground and the transistor, especially the relatively high resistance points between the wires and pins and the breadboard. There’s quite a bit in the way of the bulk storage capacitor too, which explains the slightly wobbly traces too. The solution is to move the measurements right onto the legs of the transistor:

Left: the circuit, right: labeled. The power traces are in red and black. Yellow: V1. Magenta and green are both nominally Vt but green is soldered to the resistor leg and magenta to the transistor leg with no power flowing through the measurement wires. Cyan is the trigger as before. Grey is the scope common and is now soldered to the transistor leg too. There’s also a 1000uF capacitor placed very close to the high current circuit in the centre of the board.

Because of that there’ll be some unknown trace and contact resistance in series with the current sense resistor. I can solve that by making use of the fourth scope probe which will interestingly tell us something about the breadboard’s resistances. If it’s less than 0.5Ω then it’s within the tolerance of the resistor anyway. And while I’m at it, I’ll bump down the delay between measurements to 0.1s (100mW dissipated). That way I can actually get all 1024 measurements in the average without quitting early due to laziness. Here are the very clean looking traces:

One thing I didn’t really note before which you can see is that the decay curve doesn’t look very exponential: it’s actually pretty flat especially at the start: precisely what you”d expect from a constant current sink. The analysis gives:

Well, that’s disappointing. It’s basically the same. There’s less noise (due to more averaging) and it’s slightly smoother, but the hysteresis is still there almost unchanged. I doubt at the moment that it’s a transient effect in the device (e.g. thermal). Suspiciously, the discharge leg which is slower looks better. My current (this is a pun as you will soon see) best guess is that the base drive (see?) is taking a dip, because resistance between the emitter and the ground rail causes the emitter voltage to rise relative to ground, so the base current will drop, maybe until some capacitance charges up?

I’m not sure. The options are to measure the base current (there’s a roughly 2.5V drop across the resistor, which we can measure with the scope), and if there’s a flaw there, then I can stabilize the base current either by changing the drive circuit ground to a different place (e.g. the emitter measurement wire—it’s only a few mA) or sticking a high inductor on the base to resist transient changes in current.

The easiest thing to test is to change the width of the on pulse (and the capacitor) up and down by a factor of 10 to see what if anything changes. That should reveal a lot about the nature of the transient effect.

But first! I can check to see what the board and joint resistances are, using the extra probe that I added. I know the current from the big resistor, and it should be a simply V-vs-I plot…

## Temporal tests

Where was I? (there was around a 3 week gap here, so I’ve kind of forgotten). Oh yes, I was going to try varying the pulse length by a factor of 10 up and down. I’m currently using a 1ms pulse (the shortest available with delay() on the Arduino) for which I need a 100μF capacitor. I’m going to go for 10ms and .1ms as well. So, I have to replace that 1000μF with a 470μF one, since apparently I’ve run out of 1000μF capacitors and that’s my last one. Okey dokey then. Also, during these experiments, I tweak the power supply voltage so that the voltage across the transistor. I also had to bump the time up to 15ms for the slowest setting. By the time I got to the 10μF delay, I had to turn off interrupts and switch to delayMicroseconds(95), in order to get a 100μs delay. I also noticed some gnarly ringing, so I added gate resistors to the MOSFETS and a 100nF capacitor to the timing cap to take the edge off. Even so this is a hell of a fast circuit for a breadboard. Without the gate resistor, the gate of the 2N7000 has a rise time of about 20ns.

I also connected it up wrong, slightly smoked the DUT and burned my finger. Amazingly, it still seems to be in full working order. Ouch ouch ouch ouch. Anyway (ouch) here’s the traces:

No perfect, (ouch) but let’s see how the V/I cures look:

The legend indicates the capacitor used. That corresponds to pulse widths of 15ms, 1ms, 100μs and 10μs.

Well, that’s  lot better!! Those long pulses have awful transient effects, sine the power draw is high. As the pulse shortens that goes away, and the hysteresis cure closes up. I don’t believe that overshoot however on the quickest curve. I suspect I’m getting a different sort of transient effect there.

Also my PSU has now become unstable above 24V out. Naturally I need about 26 for this setup. Whyyyyyyyyyyy???

But that regulation is not bad (5% over quite a wide voltage range), or would be if the circuit actually hit that range. It is serving as a decent upper limit on the current, I guess, using only very cheap components and what was already required for other things (the 3.3V regulator).

## More on transient effects

I’d still like to reduce the size of the hysteresis loop. I speculated that the base current had something to do with it too. The circuit has no star ground and has pretty high currents. Due to the voltage drop along the ground rail, the regulator circuit will probably drift up and down. So, I’ve measured the voltage on either side of the base resistor. Using the scope’s maths functions (through a truly interesting interface), you can see (red channel) what the voltage fluctuation over the base resistor is.

As usual, blue is the trigger, and yellow is the capacitor voltage. Green and purple are measured either side of the base resistor and red is the difference between them. It’s a much smaller scale, so very very noisy. The temporal averaging really helps here.

That’s around a 5% change, which is certainly significant. Inductors resist change in current, so I’m going to stick a big inductor (if I have one) series with the base resistor to stabilise the current. The old PSU board has a transformer on it. I’m going to take that off and see how it looks.

Wretched device was press-fit and solered, so I almost busted it trying to take it out. The transformer has two sides: 1.6Ω/5.4Ω with inductances of  13/35mH, which is somewhat less that I expected. And that made little or no difference. That board also has a whacking great Omron LY4 relay which has a coil of a more respectable 3.5H, with 350Ω nominal resistance. Here’s the result 10th a 100μF timing capacitor:

Same as before, but looks like I got bored waiting for the averaging to finish, so the red trace is noisier.

It’s much better, but not quite as better as it looks because the current is being measured as voltage across 600Ω, not 1kΩ, so it’s more like a 33μA rather than 160μA, which is still a huge improvement. The VI curve is:

uhhhhh.

Yeah, so that’ll teach me for being lazy (no it won’t). Turns out it’s a 120V relay, not the 24V model, so the coil has a resistance of 1.7kΩ, which I would have known if I’d measured it. I have another relay with the specs of 750Ω/1.35H.

Still a 100μF timing capacitor.

Well, that’s a little bit better. As one might expect, it’s better on the outward leg, which is where the majority of the variation was. I strongly suspect I won’t be able to get much better than this without making the circuit on a board that allows substantially better design. For fun, here’s the one with the apparently optimum timescale:

10μF timing capacitor (100μs pulse width)

You know, that’s actually not bad!

Edit: I forgot to include the final circuit diagram, so here it is:

The circuit excluding the 7833 regulator and the power supply decoupling capacitors. Note the inclusion of the major board resistances (RBOARD), and the power resistors with 4 point measurements. I’ve also put the relay in in full.

# Conclusions (i.e. tl;dr)

1. I burned my finger (ouch).
2. The regulation is decidedly OK.
3. The user of the transistor was indeed a clever hack.
4. Measurement is hard.
5. This was a very long post.

# Today’s hack

Let’s say you’re automating a git workflow for a variety of good and bad reasons. Commits are fine, you can just do a:

commit -am 'a message'


and it goes through non interactively. Now let’s say you have a merge and you try:

git merge


It pop open an editor window to let you type a message. There’s no merge -m option to let you provide a message. If you look closely, you’ll see that the editor window already has a message filled in saying it’s a merge. This means if you save without exiting, git sees a message and decides all is OK and it can proceed. So all you have to do is provide an “editor” which quits without saving every time. The program which does nothing except exit successfully is /true so you can simply do:

EDITOR=true git merge


for a non interactive merge.

I don’t know whether to be proud or ashamed, but it works.

# A hidden horror

An Asus Zenbook UX21 that I sometimes maintain has has had one too many trips over the power chord and the power jack has become awfully flaky, so much so that it’s now broken to the point of being unusable. Doubly unfortunate is that the PSU died and the 3rd party one doesn’t have the light that tells you when it’s charging, and the battery is quite bad now so it’s easy for it to run out of power and then it’s really hard to tell if it’s charging or just sitting there.

On the whole, it’s been an excellent laptop but is beginning to feel its age, which makes me a lot less nervous about opening it up to see if it can easily be fixed. The only other non-abuse failing is that some of the screws fell out of the case. This seems a common complaint among Zenbook owners.

Due to the generally excellent build quality it was easy to get apart, just popping off the remaining case screws. the back came off easily:

The laptop is mostly battery!

The motherboard is really tiny and remarkably thin, so care is needed to not flex it. The SSD is upgradable which I didn’t realise and uses a proprietary connector for which adapters exist, fortunately. The heatsinking is decent with a rather dust-clogged fan on the end of a decent heatpipe.

Once inside, it came apart easily. On to the offending part, the power jack. Here it is:

see anything wrong? Weirdly it looks just fine. The last one I replaced on a laptop was smashed all to pieces. That’s good because it’s an oddball part and I’m gland I don’t need to find a replacement. Part of that is due to the barrel coming in through a relatively tight fitting hole in a relatively thick metal case, so there’s quite a lot of mechanical rigidity there.

But look closer:

see the bit at the top where the pin solders to the board? That joint is well dodgy! Turns out that the copper ring around the pin is really thin and once I melted the solder, it turned out that the pin isn’t even long enough, so the end is barely flush with the top of the board. Those two combine to make a really fragile connection.

The fix was easy, just adding some flux, removing the old solder and replacing it with new, quality (leaded) solder made it work perfectly.

So now it works again, and a replacement battery is on the way so it should have a good few years more left in it.

But especially given the overall build that was a remarkably poor job on a part often subject to high stresses.

# Hacking the Neutrogena visible light therapy system to get 99 lives

Edit: this is part 1.
Part 2 here.
Part 3 here.
Part 4 here.

Here’s a thing. It illuminates you with blue and red light which is apparently effective. Possibly because light, especially high energy light causes phototoxicity. Anyway, the device consists of a mask full of LEDs and an “activator” which is a power supply which gives 30 uses before you have to replace it.

They’re clearly going for a printer ink model and the activator is £14 or so which is aimed at the expensive-but-not-outrageously-painful price point. The thing is though it’s extremely wasteful because the batteries are still good1 as is the hardware. Its just burning stuff to make money which is kind of objectionable, so we must deal with it. Or hack it.

By the way after this point I was using the post to make notes as I went along so it’s a bit stream of consciousness.

# Day 1

(note these aren’t full days)

OK, first some disassembly pictures:

The device

The battery cover removed

The back cover removed

The bare board

So far, so good.

The design for manufacture is pretty good: excluding screws there are 5 parts and they slot together with minimal effort. The plastic parts are decently moulded and aimed at a low price point. The draft angles are large and those holes at either end of the battery compartment where the springs are have highly drafted sliding shutoffs.  The board is pretty interesting. It’s a single sided soldering process with a mixture of connection types. There’s SMD for the small stuff and a number of THM parts as well. Note that the THM hasn’t been wave soldered since the gold test pads are not tinned. The joints look very uniform so it’s either top notch hand soldering or a selective soldering machine. Here’s a cool video:

I have nothing to do with that company, but they had a nice video on the technique.

Anyway, the mix is quite unusual because of the extra cost except it allows them several things. Firstly, they can use a stock THM 3.5mm 4 way barrel plug. You can’t see this from the photos, but it’s obvious from the plug. Second, they can use some very cheap battery connectors which are simply bent pieces of spring.

Also note the 100uF electrolytic capacitor. Fujicon is one of the very cheap brands, and given they’ve already got a THM stage, this means they can use the cheapest of the cheap caps. Its hard to find Fujicon retailers, the first one I found sells these caps for 1.195¢ a pop (they blinged it up and went for the pricey 105C one, not the 85C 1.111¢ one). The cheapest 100uF 16V electrolytics on Digikey are 4 cents a pop.

Also, with the cut out they can fit the cap in a very low profile and hey that’s weird, there’s a track going right into the middle of the snapped off tab.

The LCD is connected with your common or garden zebra strip. The final bit is the metal diaphragm switch. This has two lugs which go through the board and are bent in place on the back side, no soldering required:

There appears to be some hand finishing on the back where the crystal is anchored in place with solder:

Now for the components. The 3 terminal THM device has code SS8050 and is a Fairchild NPN transistor rated for 1.5A/2W. Graphs indicate that at 1A with a 100mA base drive, wed expect Vce sat to be about 50mV, giving a equivalent resistance of 50mOhm. This is not that bad since the base drive voltage would be about 0.8V. They’re cheap (0.47¢ from Mouser). Maybe a little inefficient but the LED power draw is high and you only get 30 uses out of the 4 AA batteries, so that’s not a concern.

Before I get to tracing with a multimeter, here’s a rough trace of the board:

At a first glance it looks like the transistor and switch both feed into the little COB blob meaning that’s probably the microcontroller as well as the LCD driver. I’m not sure what the ATMLH640 chip is. My guess is it’s an EEPROM to store the number of remaining uses. Perhaps the super cheap COB chip doesn’t have one because that would require a different process. Web searches don’t reveal anything especially informative.

My guess is the SOT23ish part labeled 7533 is some sort of 3.3V LDO linear regulator. It connects to the main power after the big cap and before the largish ceramic. Also, look at the power: it won’t be connected unless something’s plugged into the jack, since the battery goes into the jack only.

So, what about that small chip, eh?

After a little tracing, here’s the layout:

and it’s connected to the μC via 1k resistors. Interestingly so is the transistor. In that region, the base drop is about 0.6, giving a base current of 2.7mA. That would give a collector current in the region of 0.4 to 0.45A, I reckon. Since there appears to be no other mechanism, I think this may be the current limiting for the LEDs. Note that the datasheet says it’s designed for class B audio amplification so it’s built to run in the active region dissipating power.

Here’s the result of the first bit of tracing with a continuity meter on the socket and using a 4 pin plug I got from Maplin:

Two of the 5 pins connect to one conductor and get bridged only when the plug is in. This appears to be used to connect the battery to the main power, meaning this thing won’t drain any power when the mask isn’t connected. Two(!) of the 4 conductors are unused, so this thing only has power in and power out (no ground) with current limiting on the board. Kinda wonder why they used a weird 4 conductor plug. Maybe obfuscation?

Looking further there’s a B, C and D type transistor. Mine has an isolated D on it as well as a 331. I guess it’s the D type, which has hFE in the range 160 to 300. Kind of a high range for anything approaching reasonable current limiting, but perhaps the actual range is somewhat narrower in practice.

But this is good and implies that there is no DRM in the mask, since there’s no way to communicate from the mask to the uC. Hopefully a simple current supply will do the job, but how much? (more later).

Back to the enigmatic SOIC. A friend on IRC put me on to http://www.smdmark.com. The ATML mark corresponds to Atmel (I guessed that), but ATMLH only matches a few EEPROMS. None of them match the code exactly, but the AT93Cxx series have the correct (unusual) pin layout, or at least a vague match. I can’t find the exact match for the code, so it’s off to the logic analyser to see if the bus is correct.

First some bodgewires and some hot glue to stop them falling off. To get it clean, put a little chip of cold glue in the right place the heat gently to 150 degrees C with an air gun. It’s much cleaner than trying to blob on hot glue:

Now to partially reassemble, but where the heck is the LCD???? Ah buried behind the brass tip cleaner. Anyway they have very kindly provided a very neat hole for precisely this kind of thing:

Given my ordering (1234->GBRW) I hypothesise from the datasheet that:

1. Green = CS
2. Blue = SK
3. Red = DI (that’s MOSI: master out slave in)
4. White= DO (that’s MISO: master in slave out)

Personally I prefer the MOSI/MISO naming since it’s unambiguous. Now, time for the scope! How do you get the blasted thing to trigger? How how how how?? Oh has to be logic triggering and pod input. Apparently selecting pod input and edge triggering isn’t enough. Okey dokey. Here’s the first shot, and of a packet that doesn’t return all zeros:

I think my hypothesis is confirmed! Note how CS goes high then the clock starts. DI is set in advance of the pulses and DO changes on the clock edge. Though it seems glitchy. Look at those weird spikes on the data lines.

Day turns into night and back again.

# Day 2

Huh is the clock really that non uniform or am I reaching the resolution limit given the very long capture window?

So now there’s two choices. I can attack the mask itself and measure the current. Once I know that it’d be easy to build a different driver that provides that current for a fixed time. The other choice is to attack the EEPROM and see if I can figure out what’s going on and reset the counter. I hope the program isn’t stored there, just the data!

Hm well OK! Based on the wiring I can drive it with a current limited source just fine and it appears to work perfectly. That was easy!

So yeah how the heck does the bus decoding work??? I’ve got it to work before. Also the DO pin gives bad noise without a 100k pull down. Nothing in the datasheet about that but it’s not the right datasheet anyway so there’s that! Ho hum got one half working…

So the EEPROM only receives 10 commands so it’s not storing the program for the uC, which is nice. According to the maybe wrong datasheet the commands are:

1. Write enable
4. Write disable
5. Write enable
7. Write disable
8. Write enable
10. Write disable

Well that’s a bit strange but not exactly out of the ordinary for embedded devices!  Anyway fine WHATEVER I’ll decode it by hand. Only a few reads. Here it is along with MISO decoded by the scope and my notes on what it means from a “close” datasheet:

Right well commands (names like EWEN, EWDS are from the datasheet) are 10 bits and reads should be 8 but it clocks 9. A bug? But that means the value at register 0x50 is 1, and this device just happens to have one use left in it. Coincidence? Time to use up the last use, measure the current as best we can and see what changes in the ROM. I just measured my multimeter (90’s era White Gold WG020) as having 66mV burden at 5A. Which is fine. Except then I realised my PSU also measures current and the LEDs will be the  bulk of the draw.

Well it draws 400 mA aaaand it’s down to 0 uses left. Oh hey!! 400mA is exactly what I figured from the datasheet!! whoooo! Oh the hFE thing is small signal gain anyway. The large signal stuff seems pretty well characterized. It’s actually a pretty impressively cheap way of regulating current. The regulator is probably plus minus a few percent, the base resistor, 1%. Being transistor designed for class B audio it’s probably decently narrow in spec so it matches well with it’s complementary PNP (SS8550). Huh. I’m impressed. It’ll probably regulate current to within 10% no problem which is good enough for a non critical task like this. When the battery level gets low the power will drop and there’s no warning but it’s only meant to work for 30 uses so that’s not an issue. A very cheap solution to the problem!

Anyway, looking at the bus now it’s got 0 uses left, at a first glance, both 0x55 and 0x50 have counted down by 1. Not sure what to make of that. The other two numbers don’t change at all.

The AT93C46 datasheet seems close enough. The next step is to try to write to it using my Arduino to reset those registers back to the previous values. Going to have to be a bitbang because of (a) the rather odd bit count and (b) because the D0 pin is not data but an asynchronous write/erase finished signal. OK that explains why the clocking is so messy: it’s bitbanged.

# Day 3

OK, here’s the Arduino sketch:

// Pin 13 has an LED connected on most Arduino boards.
// give it a name:
static const int led = 13;

static const int CS = 4;
static const int CK = 5;
static const int mMOSI = 6;
static const int mMISO = 7;

// the setup routine runs once when you press reset:
void setup() {
// initialize the digital pin as an output.
pinMode(led, OUTPUT);
pinMode(CS, OUTPUT);
pinMode(CK, OUTPUT);
pinMode(mMOSI, OUTPUT);
pinMode(mMISO, INPUT);
Serial.begin(9600);
}

//Write out the N low order bits of data as MSB first
//and return the collected data.
uint32_t write_N_MSB_first(uint32_t data, int n)
{
uint32_t ret = 0;

//First shift everything to the 16th bit
data <<= (32-n);
digitalWrite(mMOSI, LOW); //Make the scope trace look nice
delay(1);

for(int i=0; i < n; i++)
{
digitalWrite(mMOSI, (bool)(data& (uint32_t(1)<<31)));
delay(1);
digitalWrite(CK, HIGH);
delay(1);
digitalWrite(CK, LOW);
delay(1);
ret <<=1;
ret |= r;
data<<=1;
}
digitalWrite(mMOSI, LOW); //Make the scope trace look nice
return ret;
}

// the loop routine runs over and over again forever:
void loop() {
digitalWrite(led, HIGH);   // turn the LED on (HIGH is the voltage level)
delay(100);               // wait for a second
digitalWrite(led, LOW);    // turn the LED off by making the voltage LOW
delay(100);               // wait for a second

digitalWrite(CS,1);
write_N_MSB_first(0x360, 10);
int v = write_N_MSB_first(0, 8);
Serial.write("val =0x");
Serial.print(v, HEX);
Serial.write("\n");

digitalWrite(CS, 0);

}


It's a straightforward bitbang, except you can read/write in variable length chunks, and it prints the read value to the serial port. It looks fine on the scope (the bus decoding works today!) so the next step is to wire it into the device.

The device is a 3.3V one and the Arduino is 5V so we need to shift the levels down. The easiest way is to make a potential divider using an LED and 1k resistor. The LED will drop more than a normal diode: the scope tells me it's dripped about 2V. We don't need to level shift the input since the EEPROM gives a nice clean 3.3V and that's easily enough for the Arduino to register high.  All we need to do is bodge it in (I reckon those 1k resistors on the device will protect things) after switching the device on, so the bus is not busy…

And it WORKS!!! Here’s the scope screenie:

And the Arduino is printing out a steady stream of 0x95, which is the correct value: we got that from earlier analysis. This also confirms that they are writing utterly unnecessary write-enable, write-disable commands and they are clocking in an extra bit at the end too.

I did initially OR both channels together using some diodes so one bus would decode read and write. That of course doesn’t work: for MOSI, the data should be read on the rising edge of the clock, but with MISO, it’s set after the rising edge, and won’t be changed until the next bit-after-rising-edge. It’s safe to read on the falling edge of the clock, so I set up the other decoder to do that.

OK, so now to try a write. Here’s the code:

// Pin 13 has an LED connected on most Arduino boards.
// give it a name:
static const int led = 13;

static const int CS = 4;
static const int CK = 5;
static const int mMOSI = 6;
static const int mMISO = 7;

static const int BUTTON = 2;

// the setup routine runs once when you press reset:
void setup() {
// initialize the digital pin as an output.
pinMode(led, OUTPUT);
pinMode(CS, OUTPUT);
pinMode(CK, OUTPUT);
pinMode(mMOSI, OUTPUT);
pinMode(mMISO, INPUT);
pinMode(BUTTON, INPUT_PULLUP);
Serial.begin(9600);
Serial.write("Booted...\n");
}

//Write out the N low order bits of data as MSB first
//and return the collected data.
uint32_t write_N_MSB_first(uint32_t data, int n)
{
uint32_t ret = 0;

//First shift everything to the 16th bit
data <<= (32-n);
digitalWrite(mMOSI, LOW); //Make the scope trace look nice
delay(1);

for(int i=0; i < n; i++)
{
digitalWrite(mMOSI, (bool)(data& (uint32_t(1)<<31)));
delay(1);
digitalWrite(CK, HIGH);
delay(1);
digitalWrite(CK, LOW);
delay(1);
ret <<=1;
ret |= r;
data<<=1;
}
digitalWrite(mMOSI, LOW); //Make the scope trace look nice
return ret;
}

void ewen()
{
digitalWrite(CS,1);
write_N_MSB_first(0x260, 10);
digitalWrite(CS,0);
delay(1);
}

void ewds()
{
digitalWrite(CS,1);
write_N_MSB_first(0x200, 10);
digitalWrite(CS,0);
delay(1);
}

{
digitalWrite(CS,1);
int v = write_N_MSB_first(0, 8);
digitalWrite(CS, 0);
delay(1);
return v;
}

{
digitalWrite(CS,1);
write_N_MSB_first(val, 8);
digitalWrite(CS,0);
delay(1);
digitalWrite(CS,1);
{
} //Wait for the status to come up
digitalWrite(CS,0);
delay(1);
}

// the loop routine runs over and over again forever:
void loop() {
{
delay(200);
Serial.write("Sequence begin\n");
{}

ewen();
write(0x55, 2);
write(0x50, 1);
ewds();

Serial.write("0x55, 0x50 :=  0x");
Serial.print(v1, HEX);
Serial.write(" 0x");
Serial.print(v2, HEX);
Serial.write("\n");

Serial.write("new 0x55, 0x50 :=  0x");
Serial.print(v3, HEX);
Serial.write(" 0x");
Serial.print(v4, HEX);
Serial.write("\n");
}
else
{
digitalWrite(led, HIGH);   // turn the LED on (HIGH is the voltage level)
delay(100);               // wait for a second
digitalWrite(led, LOW);    // turn the LED off by making the voltage LOW
delay(100);               // wait for a second
}

}


So I've also added a switch too (one of those incredibly bouncy microswitches): it’s connected to ground, and the input pin is set to pullup. When the Arduino detects a zero (a press), it waits 200ms, then waits until the switch is released before starting the write sequence. That way I have plenty of time to set up the scope and triggering etc.

Oddly enough it doesn’t work if you send the wrong instruction for EWEN (write enable). Who knew?

Huh, OK so I successfully wrote (I discovered this after), but my state machine didn’t work for reason that the write finished signal never happens. OK. Maybe I’ll just try waiting 15ms (wrong datasheet says 10 max…). Now it’s in an inconsistent state because it blocked after one attempted write. So the new write function is simply:

void write(uint8_t addr, uint8_t val)
{
digitalWrite(CS,1);
write_N_MSB_first(val, 8);
digitalWrite(CS,0);
delay(15);
}


And the output of the serial monitor is:

Booted...
Sequence begin
0x55, 0x50 :=  0x1 0x0
new 0x55, 0x50 :=  0x2 0x1


That’s promising. Now to disconnect the Arduino and reboot the device…

YESSSS!!!! I WIN!!!!

Yes, I tried using using it, and the LEDs all come on and it goes back down to 0. And I can reset it to 1 again and so on and so forth.

The next step is to get infinite, or at least many lives. I’ll probably try setting it to something intermediate first like 15, then 99, then 255. I wonder if it will work with more than 2 digits or if it will crash. Also, I should probably check what voltage is required to push 400mA through the LEDs: when it’s not limited to 30 uses the batteries will get used up and it would be good to know if it’ll cut out or slowly go dim.

# Day 4

Huh OK, 400mA requires 5.43V.  According to these guys https://www.powerstream.com/AA-tests.htm at a 500mA discharge or under AA cells deadify at  about 1V. At 1V, we’d have a about 4V total which I measure as about 225mA through the mask, so the power is about halved. After that it will drop off very quickly. That link also says a decent battery will get 2Ah ish. The average discharge is very roughly 360mA over that range. That would give about 2000/360 * 60 = 331 minutes of use. Oh LOL!!! The mask lasts for 10 minutes a time so at that rating, the batteries will last about 33 uses or just over the number it claims to give you.

That means the regulation isn’t as good as I  thought before, or more to the point the low voltage condition bites almost instantly. The regulation will decently accurately stop the current from being very high when the battery is fresh but will do little after that.

That’s pretty funny. I’ll bet they chose the power levels based on AA batteries. Well, according to Powerstream above, the expensive batteries pull ahead at a 500mA discharge but are on a par with the cheaper ones at 100mA. So, I can replace the batteries for a grand total of 60p from Maplin, a lot cheaper than £15. It came with “Excell” brand batteries which you can only get with a minimum order of 2000 from Alibaba.

Quality.

I wonder what they cost: no price for these but AA prices seem to vary from 1¢ to 10¢ on Alibaba for bulk buys. Even reputable brands (Panasonic) are only 11p in quantity from Digikey.

So on to 99-UP. Actually come to think of it…

If I was an engineer working on a product (ahem) and there was some spare ROM space, I might be tempted to leave an Easter Egg in there. So let’s do a ROM dump and see. So, all I’m doing is modding the code to simply read 127 bytes from the EEPROM.

Then I got a bunch of:

avrdude: stk500_getsync(): not in sync: resp=0x??


errors. There was a lot of cargo culting on Stack Overflow. the correct answer (not there) was that I accidentally changed the board type setting. Check the github page for the source for the ROM dumper if you’re interested. Its just a combo of read(), sprintf and serial write. And the ROM contents is:

0x00: ff aa aa aa aa aa aa aa aa aa ff ff ff ff ff ff
0x10: aa aa aa aa aa aa aa aa aa aa ff ff ff ff ff ff
0x20: aa aa aa aa aa aa aa aa aa aa ff ff ff ff ff ff
0x30: aa ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
0x40: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
0x50: 01 ff ff ff ff 02 ff ff ff ff ff ff ff ff ff ff
0x60: 95 ff ff ff 34 ff ff ff ff ff ff ff ff ff ff ff
0x70: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff


Well, that’s dull 😦 Oh well.

So, OK, on to infinilives. I still don’t know the significance of 0201 going to 0100. I’m going to try 0209 to see if we get 9 lives. Tried it and now it reads 0 lives. How odd. Let’s try n+1, then, so 0302 and 2n next. So, n+1 first.

Yep! That works!

OK, what about, let’s say, 99, because that’s clearly the most the display can handle. I’d also hypothesise that going higher might go funny, if they do decimal conversion by say %10 and /10, then looking up the segment results in an array. By the way the device won’t boot when the programmer is plugged in, displaying “–” instead of a number. So it’s checking the results of those initial reads and won’t work if they’re corrupted. I just discovered that I only need disconnect the MISO line, rather than everything. This makes experimenting much faster. Right, so, 100,99:

you… uh… huh. huh. HUH.

well well. Looks like they have a straight up lookup table. Maybe because it’s a multiplexed LCD? It doesn’t have enough wires for a static one (8 wires for 14 segments). Anyway, looks like they’re checking bounds which is nice. I’ll bet if I do a ROM dump, I’ll see the 100,99 in there.

0x00: ff aa aa aa aa aa aa aa aa aa ff ff ff ff ff ff
0x10: aa aa aa aa aa aa aa aa aa aa ff ff ff ff ff ff
0x20: aa aa aa aa aa aa aa aa aa aa ff ff ff ff ff ff
0x30: aa ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
0x40: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
0x50: 63 ff ff ff ff 64 ff ff ff ff ff ff ff ff ff ff
0x60: 95 ff ff ff 34 ff ff ff ff ff ff ff ff ff ff ff
0x70: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff

No. Well that’s even more unexpected. First lets’s see what happens with 0,255 and 255,254. The plot thickens!! Oh no, stuuuppppiiidddd! 64,63 in hex is 100,99 in decimal, so hasn’t rewritten the values.

If I upload 0,255 then when I reboot it, I get back to the original 30 uses. Even stranger is the ROM dump:

0x00: ff aa aa aa aa aa aa aa aa aa ff ff ff ff ff ff
0x10: aa aa aa aa aa aa aa aa aa aa ff ff ff ff ff ff
0x20: aa aa aa aa aa aa aa aa aa aa ff ff ff ff ff ff
0x30: aa ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
0x40: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
0x50: ff ff ff ff ff 31 ff ff ff ff ff ff ff ff ff ff
0x60: 95 ff ff ff 34 ff ff ff ff ff ff ff ff ff ff ff
0x70: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff

but after one use, it goes to the expected:

0x50: 29 ff ff ff ff 30 ff ff ff ff ff ff ff ff ff ff

Curious. OK, so what about 255,254? Now we have blank-blank. Disappointing? Well, I tried activating it and it comes on! And the ROM indicates that it’s counted down.So OK, that works. Back to loading up 100,99. It displays 63. If I use it, it counts down to 62?? WTF? The ROM dump indicates it’s still 0x63,0x62. One more use and it displays 61. Then 60. Now it should get interesting… Oh wait I just noticed above, it’s got 0x29, 0x30. The little devil is counting in BCD!!! So, now it’s diplaying 59 and the ROM dump is:

0x50: 59 ff ff ff ff 60 ff ff ff ff ff ff ff ff ff ff

Confirmed! Gosh, I’ve not seen BCD in so long! Let’s try uploading 0x100 0x99. And my hypothesis about the 63 element LUT is clearly wrong. Oh huh 0x100, 0x99 gives 0 on the display.

Yes, 0x100 is 2 bytes, so it would only load up the first 00 which is of course not valid for this setup. Right moving on, I’ll bet its BCD is a bit dodgy, because it happily counted down from 255,255. Maybe 0x9a99 will work.

H4X SUCCESSFUL!

So I wonder…

CONFIRMED! So if you upload 0xfaf9, then it displays blank-9. Because it can’t decode f. OK, so I’ll bet if I load it up with 0xf1f0, it will count down to 0xf0e9 and display blank-9. And sure enough it does. That means the maximum number of uses is the first 99, then the 10s for a9 b9 c9 d9 e9 giving 159 and the remaining 15 giving 174.

But I think I’ll leave it at 99 because it looks cooler.

😎

PS if you’re interested here’s the code

1NOT!!! Actually it uses up most of the batteries, but the hardware is fine