Vince's thoughts

Aller au contenu | Aller au menu | Aller à la recherche

vendredi 22 décembre 2017

18650 Battery charger reverse polarity protection


Having many 18650 battery cells to charge, I bought a few dirt cheap TP4056 modules (0.22 EUR per piece) and discovered that they are "not designed for" reversed polarity cells. In other words, reversed battery = instant magic smoke.

As I know for sure I will insert more batteries in the wrong orientation again sooner or later, I wondered what the best reverse polarity protection would be. This thread addresses this very topic, but at the time of this writing, it ends with:

Q: "Has anyone actually tested this solution in real life?"

A: "Good question..."


So I thought I would test and share the results here.

Note: Sorry for the audio quality of the videos, they are almost straight from my phone and I chose to spend more time on writing rather than on video editing... Also please don't take into account the aspect of the assembled circuits, remember these are quick and dirty tests.

Here are the ideas that were developed in the thread:

1. Simple Diode in series with the cell :

Naive Diode

Big NO-NO !

The naive logic is that the diode only allows the current to flow "in one direction", but this logic is flawed because the battery is not a load (a random circuit) that we want to protect from a reversed voltage source, the battery is a voltage source. In other words, we don't want to protect the charger from a battery with a voltage > 4.2V that would make the current flow from battery to charger, instead we want to protect the charger from a battery with a voltage < 0V (reversed). If the battery is reversed, the current will not be reversed: it will still flow "clockwise", but both the battery and the charger will "push" it the same way, and the diode will happilly let that happen of course. Current will skyrocket as if there was no diode, and magic fume will come again.

Moreover, when the battery is correctly connected, this solution is not good either because even with a very low forward voltage diode (e.g. 0.4V with this shottky diode), that voltage drop in series with the battery will make the TP4056 think the cell has reached its 4.2V when it's really only at 3.8V, and the battery will never get fully charged as it should.

Verdict: That solution is BAD. It doesn't solve the problem and adds other issues.

2. Polyfuse + clamp diode :


The logic here is that we place a (normally reverse-polarized) diode in parallel with the charger's output. If the battery is connected the right way, this diode is blocking and does nothing. But if the battery is reversed, that diode will basically short-circuit it, causing a current surge through the battery. A fuse between the diode and the battery will then blow and "disconnect" the battery, saving both the battery and the charger.

The first problem is that, as explained, a current surge is required for the protection to trigger, so even with a fast fuse, a current surge WILL happen. Moreover, during that surge, the diode will short circuit the battery, but also the charger (effectively, the charger will see the forward voltage drop of the diode). Only a test will tell if the charger survives that short-circuit.

Now if the "fuse" is an actual "single-use" fuse, it will have to be replaced each time a battery is wrongly inserted, and one might wonder if it's worth the hassle, because TP4056 modules are almost as cheap as the fuses themselves :-).

The other option is to use a "Polyfuse" or "Polyswitch", acting as a resettable fuse. Technically, it's a temperature-variable resistor (PTC) which would ideally have a 0-ohm resistance at ambiant temperature, and an infinite resistance above a threshold temperature, that temperature being reached when a given current passes through it.

That option is the exact setup I tested:

As observed, the TP4056 module withstands the short-circuit and is saved by the protection.

Polyfuses are not ideal though: the current that causes the resistance to rise is not a precise value. For example, the 1.1A polyfuse I used for the test is guaranteed to "hold" 1.1A without triggering ("tripping"), and is guaranteed to "trip" at 2.2A (ambiant temperature, still air). More important, the time it takes to trip can be as high as 5 seconds! So although the cell should not blow, it is definitely not designed to be short-circuited for 5 seconds.

Moreover, being a resistor, the polyfuse will also dissipate some power (and cause a voltage drop) when the cell is being charged at high currents. This is not really a big deal because it will just make the charge less efficient. As the battery charges, the current will decrease, and so will the voltage drop on the polyswitch. In the end, we will still get a fully charged cell, even if it takes a bit more time and power than without the polyswitch.

Finally, when the cell is reversed, the polyswitch will continue to limit the current as long as it remains hot, and it will remain hot as long as current is flowing through it. Practically speaking, it means that as long as the cell is left in the wrong orientation, it will get discharged through the diode and polyfuse, with no overdischarge protection... As the battery is probably already empty (why would you want to recharge it otherwise ?), if you don't notice the issue fast enough, the protection will save the TP4056 but kill the cell.

Verdict: OK for temporary protection



We come back to the idea of "preventing the current from flowing" (as in 1) instead of letting it flow and protecting the charger (as in 2), but we do it the right way this time, using a MOSFET as a switch.

When OFF, the mosfet does not let any current through, effectively opening the circuit like a reverse-polarized diode. When ON, it behaves more like a resistor with a very low resistance, causing a voltage drop that decreases as the cell becomes full and current slows down. That is good.

Now, the trick is to have the gate turning the switch On or Off at the right moment. The first thing is to put a resistor from the source to the gate to make sure the MOSFET is OFF "by default", and the second thing is to add a transistor to override that resistor and flip the voltage on the gate when the battery is correctly positioned.

I didn't have P-channel mosfets laying around as most circuits use N-channel ones, so I build the exact mirror of the above circuit (note the NPN transistor becomes a PNP) :

n-channel mosfet 2N7000

And here is the test:

As you can see, the protection works perfectly. There is no current surge when cell is reversed, the charger sees no battery connected and uses no current. On the battery side, the current drained from the cell is also negligible. This circuit is clearly the safest one for distracted or careless people like me.

Verdict: Very effective protection.

Now the MOSFET I randomly selected (2N7000) is not the best as its resistance when ON (RdsOn) is quite high, possibly more than 5 Ohm at Vgs = 4.5V according to the datasheet. In the conditions of the test, the voltage/current ratio gives a more typical value of around 1.5 Ohm, but that means that part of the power used during charging is lost as heat (1.5W at 1Amp), causing the charge to take longer than needed. After screening through my salvaged parts bin, I came across a IRL2203 MOSFET from a dead power supply, which has a RdsOn of only 0.007 Ohm. In other words, the circuit is now:

n-channel mosfet IRL2203

So I tested with that MOSFET and the result is almost perfect :

The protection works as before but now, in normal use, the voltage drop across the mosfet is reduced to a negligible value, So the behaviour is almost perfect.

Verdict: Great solution!

Side-note: although the logic is different, using a N-channel mosfet with a low RdsOn as a switch on the B- connection is also the method used on the "protected" version of the TP4056 module (it's a pity they didn't include the reverse polarity protection while they were at it). That module includes a FS8205A dual mosfet chip with a typical RdsOn of less than 0.03 Ohm.

Now I admit I'm a bit puzzled (and always was) about the additional diode. It is found in many circuits dealing with "reverse voltage protection" as a way to make sure the reverse voltage between base and emitter of the transistor doesn't go below -5V (for 2N2222), which would be "out of spec". It's just a small diode, it doesn't have to carry high current, it's cheap, so not a big deal, but as we're only dealing with a Li-Ion cell, even if reversed when fully charged, it won't ever go below -4.2V.

So I think it could be left out...




... but I hear your disappointment: If you're reading this, you can't be satisfied with a post that ends with "I think...". If you're reading this, it's because you want to see chips burst in flames :-). So I had to do one last test with the following circuit:

n-channel mosfet no diode

And here we go for the test:

Verdict: ... just watch the video :-)

Note: in any case, it may be a good idea to add an indicator when the battery is connected the wrong way. The obvious solution is a LED in series with a resistor, that would light up only when the battery is reversed, but maybe it would be wiser to add an active buzzer in series with a diode. If you build a multi-cell charger, a single buzzer and one diode per cell is sufficient.

If you want to share opinions/ideas/chips/rants/etc., feel free to comment in the original forum thread.

Hope it helps !

mercredi 9 août 2017

Turning a Quick Charge 3.0 charger into a variable voltage power supply

Disclaimer: the following refers to some cheap USB chargers which may not comply with local safety regulations and could pose a hazard. All you do is at your own risk and I cannot be held responsible for use or abuse of those chargers.

1. Introduction

A few months ago, I stumbled upon a Hackaday post presenting the QC2Control library by Timo Engelgeer (Septillion), which makes it very easy to turn a Qualcomm Quick Charge 2.0 compatible USB charger into a 9V or 12V power supply. I used that trick successfully to power a small project that required both 5V and 12V, and its simplicity made me want to dig a bit further into Qualcomm's Quick Charge technology.

As you may know, Quick Charge 3.0 adds the possibility to request any voltage between 3.6V and 12V (with 0.2V steps), and with some QC3 chargers available for less than 4 EUR, throwing in an Arduino clone and a few resistors make a very affordable way to power devices with custom voltages. Of course, for that price, don't expect top notch precision, protection, or power, but it is more than enough for most DIY projects, and the adjustable voltage can compensate voltage loss in long power lines (think wired sensors far from alarm system for example).

So I decided to try and see if QC3 could be controlled similarly to QC2. (Spoiler: it is the case, but required a few adjustments.)

For these tests, I used :

Here's the presentation of the test setup:

2. Test of the different chargers with QC2Control

I thought it was a good idea to start simple and use the QC2Control lib on the 3 chargers, so I uploaded the QC2Control's example sketch to the Arduino and observed the results with each charger in turn. QC3 chargers and battery packs should be backwards compatible, so I used the unmodified circuit and library. Here are the results:

The QC2 charger exhibits a strange behaviour: the QC handshake works and the voltage goes to 12V. It also successfully generates 9V. But requesting 5V then seems to completely reset the charger, with the output voltage dropping to 0V before coming back up to 5V. However, after the reset, the charger has left QC mode and a new handshake would be needed to get it back on track. Most probably it is a problem with the charger... any information is welcome.

QC3 chargers should have worked, but as you can see, the test fails with both the cheap one and the Anker one, and it took me some time to find the reason why....

USB power source identification and control

To explain, let's first go back to the basics.

Most of the communication between a portable device (PD) and a USB power source relies on the D+ and D- lines of the USB connection. Of course those USB data lines were not designed for signalling but for data transmission in a differential way: D- going down when D+ goes up (and conversely) and the receiving device subtracting D- from D+, so that if noise or spurious peaks happen during the transfer, they appear on both lines and the subtraction cancels them. But as a charger does not use the data lines, several standards or proprietary protocols were designed using those wires in a primitive way to identify the source or control it.

The first convention is that a charger starts with D+ and D- connected together (shorted), which is incompatible with the differential mode, and can thus be detected. If a PD is QC-compliant, it will set a voltage of around 0.6V on D+ for around 1.25s. If the USB power source is also QC-compliant, it will detect that voltage, remove the short between D+ and D- and connect a 20K pull-down between D- and GND. That is the QC handshake phase.

After that, the USB power source will start monitoring the voltages on D+ and D- and will act according to the detected levels.

A very clear (in my view) definition of levels used by QC is a D+/D- graph inspired by this EOSMEM document, where areas define all QC2 "discrete" output values:

Quadrants QC2 only

So what's the problem ?

Qualcomm does not make Quick Charge specifications available publicly, but after some trial and error, it seems the failure was due to the voltages used for controlling D+ and D-. The original demo by Hugatry and QC2Control by Septillion were inspired by this TI reference guide which is based on the CHY100 chip. The CHY100 specifies voltage thresholds of 0.325V and 2V, and the resistor values were chosen to provide voltages around 1.6V for "low" and 3.3V for "high". While my Quick Charge 2 charger is happy with those voltages, none of the two QC3 chargers worked with a "low" voltage of 1.6V, so I changed the resistors to get the "low" voltage closer to 0.6V (value cited in most of the datasheets), while keeping the "high" voltage at 3.3V or above. Here are the values and corresponding voltages on the D+ side:

Voltage according to resistors

Note: if you want to fiddle with resistor values and compute the corresponding voltages, I made a LibreOffice spreadsheet that does exactly that. Change the values in the yellow and blue cells and check the graph at the bottom...

Graphically, here is a comparison of the voltages obtained with the original resistor values (yellow dots) and with the new values (blue dots). One can see that the blue ones are closer to the official values (intersections of green lines):

Quadrants QC2 + old dots + new dots

Here is the result with the changed values :

With those changes in place, QC2 modes worked with QC3 chargers as expected, so here's the circuit and values I'm using with QC2Control:

Original circuit with new values

3. First test of QC3 mode

QC3 makes use of the area in the upper left corner of the graph that was not used by "discrete" QC2 voltages, and that area (in red below) is known as the "continuous mode":

Quadrants QC3 only

Switching to continuous mode is as easy as selecting a QC2 voltage: after handshake is done, you set D+ to 0.6V and D- to 3.3V and that's it. No matter what the previous settings were, the charger is now in continuous mode (but the output voltage remains the same for now).

Once in continuous mode, you can send pulses "down" or "right" (see arrows) to respectively increment or decrement the output voltage by 0.2V.

Leaving continuous mode and getting back to discrete mode can only be done by returning to the lower "5V" area (note that we're still in QC mode, so no new handshake is required). Consequently, there is no way to get directly from continuous mode to 9V, for example. This is very clear in figure 17 of this datasheet:

State diagram

The original circuit, no matter the resistor values, cannot reach the continuous mode quadrant as the voltage on D- must be around 0.6V when Arduino_DmPin outputs 5V. So my first attempt used a slightly modified version, connecting the lower resistor of the D- divider to a third pin of the Arduino (Arduino_DmGndPin) instead of GND so it can be made floating. That way, D- can be pulled up (to be precise, when the pin of the lower resistor is left "floating", the upper 10K resistor connected to the pin outputting 5V balances with the 15K pull-up Inside the charger, so the actual level on D- is around 3V).

The resulting 3-wire "legacy" circuit is thus:

Legacy circuit

The code required a serious rework, but after a few cycles, I think I can say it is a success :-):

Circuit improvement

That was quite satisfying, but I thought it would be cool to get rid of the need for that third pin, so I modified the circuit again and duplicated the D+ configuration to the D- side: a divider between VCC and GND plus a third resistor to the Arduino "DM" pin. Now,, outputting 5V on Arduino_DmPin, a voltage of 3.3V (or a bit more) can be set on USB D-. Here are the target levels (blue dots):

Quadrants QC3 +new dots

And again, after some coding, I could make it work.

Here is the corresponding 2-wire "recommended" circuit:

Recommended circuit

4. The QC3Control library

To design the Library, I started from the QC2Control project, which is easy to understand and has a clean API, and forked it to add QC3 features. In the end, most of the implementation has been rewritten to work in milliVolt internally, but the API should be fully backwards compatible, with a twist.

You can find the code of QC3control on Github here.

The twist is that while set5V(), set9V() and set set12V() have the exact same behaviour as in QC2Control and request discrete 5V, 9V or 12V mode, **But** setVoltage() now always uses QC3 continuous mode to reach the requested voltage (which is now a 'double' to support decimals). It means that setVoltage() cannot be used with a QC2 chargers anymore contrary to the 3 first functions, but it also guarantees that switching voltage using setVoltage() will always be made in a smooth (monotonic) way. For example, if voltage is now 10V and you call setVoltage(12), a quick ramp will increase voltage by 2V in 10 steps. On the other hand, if voltage is now 10V and you call set12V(), the charger first has to leave continuous mode by switching to 5V (see state diagram above) before requesting discrete 12V, which causes a brutal voltage drop. Performance is not affected in a major way by the ramps because each increment (or decrement) of 200mV only takes 2ms, while the discrete mode change to 5V takes 60mV before requesting the new voltage (this might be slightly reduced but I played it safe). In the end, the choice of mode is left to the programmer using the library.

4 more methods were also added:

  • incrementVoltage() and decrementVoltage() to request a single step of 200mV up of down
  • setMilliVoltage() and getMilliVoltage() are identical to setVoltage() and getVoltage() but the unit is milliVolt and values are thus integers instead of double.

Warning about partially compliant chargers

In my tests, I noticed that the cheap QC3 charger did not go down to 3.6V but instead it stopped at 4V and ignored further "decrement" requests. The fact that setVoltage() now uses relative steps instead of absolute jumps means that such offset can impact the full voltage range if you only use setVoltage().

For example, with such a charger, if right after start you call setVoltage(3.6) then setVoltage(9), the actual voltage will reach 9.4V. That's because setVoltage(3.6) issues 7 decrement requests ((5-3.6)/0.2 = 7), of which the last 2 are ignored, stopping at 4V, then setVoltage(9) issues 27 (=(9-3.6)/0.2) increment requests, which causes the voltage to be increased by 5.4V, to 9.4V.

The same kind of offset could happen if spurious peaks are detected by the charger, so it is advised to limit the use of repeated continuous mode requests to fully validated setups.

Note: Why keep 2 circuits and 2 implementations ?

In other words: why not get rid of the legacy circuit?

The 3-wire "legacy" circuit is kept because it adheres more precisely to the QC specification. Indeed, according to the spec, the handshake should only drive the D+ wire while the D- wire is left floating (high impedance) until the charger has removed the short between D+/D- and activated a 15K pull-down on D-. This high impedance state can be achieved by letting the resistors of the D- divider "unconnected" (or connected to Arduino pins configured as inputs). That is what the library does in "3-wire" configuration.

The 2-wire "recommended" circuit has no way to leave D- floating because the divider is always connected to VCC and GND. During the phase when the charger has D+/D- shorted, having both dividers producing 0.6V gets unnoticed by the charger (because they are shorted anyway). However, when the short is removed, D- is expected to immediately be pulled down to 0V by the charger's internal 20K resistor, but the divider prevents that due to its lower (10K/1.5K) resistor values, and D- remains around 0.6V. The library thus forces D- low after a delay by setting DmPin to 0V, and that gets recognized by both my chargers as an "acknowledge" of the QC handshake.

If your QC3 charger refuses to generate anything other than 5V with the recommended circuit, it may be due to the fact that this "low" pulse does not come right after activation of the 15K pull down. In that case, please try with the legacy "3-wire" circuit, using the 3-parameter constructor, and let me know :-)

5. Demo

OK, so what can we do with a programmable QC3 charger ? Maybe draw something ? How about this:

Hackaday logo

I indend to make a blog post for the making-of of this logo, but in the meantime, here is the proof this was really generated by a QC3 charger :-) :

lundi 5 juin 2017

Introducing the WAHOOcorder

Front panel


What ?

  • A set-top box modified to add Wi-Fi

What's special about it ?

  • Bidirectional interactivity from a browser
  • Advanced features such as user-firendly search interface
  • Bidirectional integration with existing operator website
  • 5 EUR (5 USD) modification

Demo Video

Jump straight to the features to skip the setup presentation.

Note: A French-audio version is also available here.

What is a WAHOOcorder ?

Well, it started its life as a VOOcorder, a Cisco set-top box distributed since 2009 by the Belgian cable operator VOO. The VOOcorder is controlled by an IR remote or via its front panel. It has an integrated cable modem connecting it to the VOO back-end for TV Guide and VOD, but that's about it regarding interactivity, and in particular it has no Wi-Fi. Due to its age, it's now considered legacy and has been superseded by a new set-top box called .évasion, which works with a companion app and website that lets you zap or schedule recordings from your smartphone.

I thought it would be cool to add similar features to the good ol' VOOcorder by giving it Wi-Fi connectivity.

And so the Wireless-Activated Hacked VOOcorder was born, or WAHOOcorder in short.

Small Logo

What's inside.

As I don't have access to the STB firmware code, and I didn't want to perform irreversible modifications, I designed the additional features as a layer around the standard hardware.

Here are the added low level features:

  • Brains: ESP8266. What else ?
  • Control: The VOOcorder can only be controlled using a IR remote
  • User feedback: Apart from the video overlay, the main user feedback is the front panel
  • Monitoring: The VOOcorder also outputs debug information which can be retrieved from a serial port

All these features are presented in a bidirectional Web UI and API, presented both as a stand alone app as well as injected in the official VOO website.

Let's go into the details.


A simple AIThinker ESP-12F module is powered from the set-top box's front USB connector. The 5V is regulated to 3.3V by a AMS1117 followed a 220µF capacitor. Everything is assembled in "dead bug" style on the ESP-12F module, and is located inside the plastic front panel so that the WiFi antenna is out of the shielded case.

Assembly Front

Assembly Back

A small heatsink was later added because the ESP can get hot when enclosed. That heatsink also touches the STB metal case, further dissipating heat. As it is enclosed, the ESP is configured for OTA upload, but to avoid spurious flashing, a (hidden) button has to be pressed for the chip to reset in OTA mode.


By simply sniffing the signal using a version of IRrecvDumpV2 on the IR receiver, I identified a protocol similar (but not identical) to the NEC protocol . In particlar, it is mirrored (high in idle state, then 9000µs low, then 4400µs high, then 600µs low, then 1600 high, ...), and strangely enough, the first byte (address) of each message is not followed by a byte with all the bits inverted ("not address"), but the first two bits are just repeated without being inverted, so if address is 0b10000000 , the next byte should be 0b01111111 but it is 0b10111111...

Anyway, I decided to store the 32 bits as recorded and play them back untouched and it works perfectly. The codes for the remote keys are :

 0x80BFE11E for key 0
 0x80BF49B6 for key 1
 0x80BFC936 for key 2
 0x80BF33CC for key 3
 0x80BF718E for key 4
 0x80BFF10E for key 5
 0x80BF13EC for key 6
 0x80BF51AE for key 7
 0x80BFD12E for key 8
 0x80BF23DC for key 9
 0x80BF3BC4 for key POWER
 0x80BFF30C for key MENU
 0x80BF41BE for key EXIT
 0x80BF01FE for key VOL+
 0x80BF817E for key VOL-
 0x80BF39C6 for key MUTE
 0x80BF31CE for key BACK
 0x80BFBB44 for key FAV
 0x80BFA15E for key CH+
 0x80BF619E for key CH-
 0x80BF11EE for key INFO
 0x80BF53AC for key UP
 0x80BF4BB4 for key DOWN
 0x80BF9966 for key LEFT
 0x80BF837C for key RIGHT
 0x80BF738C for key OK
 0x80BFA35C for key GUIDE
 0x80BF5BA4 for key TV
 0x80BF19E6 for key RADIO
 0x80BFA956 for key HDD
 0x80BF6B94 for key VOD
 0x80BFB34C for key REW
 0x80BF8976 for key PLAY
 0x80BF0BF4 for key FFWD
 0x80BFB14E for key SKIP_BACK
 0x80BFE916 for key STOP
 0x80BFC33C for key PAUSE
 0x80BFC13E for key REC
 0x80BF916E for key RED
 0x80BF21DE for key GREEN
 0x80BF9B64 for key YELLOW
 0x80BF6996 for key BLUE

A GPIO of the ESP is connected in parallel with the IR receiver via a signal diode, and driven low to short the receiver to GND and emulate an incoming signal. This also leaves the original IR remote fully functional.


User feedback.

This was by far the hardest part. The front panel leds are driven by an Elan ePVQ6200 chip for which I couldn't find a datasheet, but I could find one for the ePVP6200 which looks similar. Its pinout revealed that it is controlled via SPI on pins 1 (DOUT), 2 (DIN), 51 (STB) and 52 (CLK), so I added wires to these pins to spy on the protocol.

SPI Pins

Those wires were routed along with the IR "injector" to a connector at the end of the front panel board

Front Panel PCB

A the time of development, the ESP8266 had no documented way yet of using SPI slave, but with the help of me-no-dev's SPI Slave library and explanations, I finally succeeded in decoding the messages sent to the Elan chip to update the leds.

The decoded protocol is as follows:

 msg = <command> <payload>
 where command is:
   0x08: repeated every 200ms, seems to be a heartbeat. 
     payload is 0xFF 0xFF.
   0x54: update a 7-segment digit
     payload = <digit> + <value> + 0x00 + 0x00
       80 for char 1 (left)
       40 for char 2
       C0 for char 3
       20 for char 4 (right)
       0bABCDEFGH with each bit corresponding 
       to one segment, respectively 
   0xA4: update attributes1 
     payload = <value1> <value2>
     value1 = 
       0x70 for high display intensity ? (on)
       0x60 for low display intensity ? (standby)
     value2 =
       0x00 until now
   0x04: update attributes2
     payload = <value1> <value2>
     value1 =
       0x10 for power led red
       0x08 for power led green
       0x00 for power led off
     value2 =
       0xFF until now
   - the messages 54 and A4 always happen in bursts 
     and the bursts are repeated every 3 seconds at least
     (when not in standby), in the following order :
     <digit 1> <digit 2> <digit 3> <digit 4> <digit 1> <digit 2> <digit 3> <digit 4> <attributes1>
   - the messages 04 happen only in response to a key
     press. They are not repeated regularly.

Side-note about using an ESP8266 as SPI slave.

GPIO15 of the ESP must be low for the chip to boot normally, but GPIO15 is also the "Slave Select" PIN. In SPI slave mode, we have no control on that pin which comes from the master and, more often than not, the GPIO15 pin is high upon ESP reset, preventing it from booting. So I used a little trick: a separate ESP pin (GPIO5) acts as a "SS enable", and only activates the actual SS in the setup() function, when the ESP has fully booted. The schema is as follows:

SS Enable schema


The STB has a 3.5mm jack at the back that is used for maintenance and outputs serial debug information. Connecting it to the ESP's serial port was trivial altough I took care to route the signal away from the mains power area of the motherboard.

Serial routing

Here is a video of the complete hardware assembly:


The software consists of 3 parts:

1) Code running on the ESP.

That code was written using the Arduino environment and ESP8266 core (the latest stable version at the time of development was 2.3.0).

It consists of several parts:

  • The driver for the different hardware parts
  • A generic web server for serving static files stored on the internal filesystem (SPIFFS)
  • A specific URL exposing an API to emulate Remote Control keypresses. For example, calling /remote?keys=XT12^ emulates a keypress sequence of the keys eXit, Tv, 1, 2 and up arrow (^)
  • A first websocket server for streaming of front panel led updates
  • A second websocket server for streaming of serial debug logs
  • A real-time parser of the incoming logs to extract interesting information, such as the ID (DVB triplet) of the current channel

I also included the Wifimanager library, OTA, as well as a call to send the DHCP-assigned IP address to my PushBullet notification app via PushingBox (because PushBullet's API can only be accessed using HTTPS while PushingBox allows HTTP).


I encountered several crashes during development, but most were due to the webserver, which only supports one call at a time and has poor performance (although ESP8266 core version 2.4.0 or later should be optimized in that regard). I resorted to storing the CSS and images on an external website, and inlining all the javascript inside the html pages, so that only one page is loaded at a time, and it's now quite stable.

The SPI slave was a challenge, particularly due to the lack of ESP8266 chip documentation. The ESP is incredibly powerful, but SPI slave interfacing was mostly a trial and error thing and I couldn't have made it without Me-no-dev's help. His code is now included in the ESP8266 core, but even then, advanced features rely on register configuration for which documentation is largely missing. In the end, I was lucky that the front panel protocol only used a few messages because the ESP seems (?) to only understand a protocol of the form "command-address-data" (used for memory and some LCD chips), and command can only be one of master_read / master_write / slave_read / slave_write. If you design the protocol, that's no problem, but if you are faced with a SPI transmission that doesn't follow the "command-address-data" format, I frankly doubt the ESP will be able to handle it in all cases.

I also had problems with the serial Rx buffer overflowing when the processor was too busy, but I patched the ESP8266 core with this change (which should be in core 2.4.0 or later) and it solved the issue.

2) HTML pages and javascript code.

There are:

  • Some static pages and links providing navigation between the features
  • A page presenting a picture of the remote control, with links mapped to various areas of the pictures, calling the RC API
  • A page presenting a picture of the front panel and opening a websocket connection to receive led updates. Coupled with a modified version of this Javascript 7-segment library to display changes in the UI.
  • A page presenting a scrolling log of the debug information received from the dedicated websocket
  • A page presenting the information parsed from the debug stream
  • A page with a search form which performs the search on the set-top box by navigating the on-screen keyboard

3) Tampermonkey/Greasemonkey scripts.

Those scripts are used to modify VOO's "VOOmotion" website on the fly, and to allow it to control the STB. Namely:

  • The home page was hacked to add a "setup" dialog window (e.g. to specify the IP of the Wahoocorder) and modify its VOOmotion logo to WAHOOmotion
  • In the program guide, when selecting details of a program currently on air, a "Watch on Wahoocorder" button is added to allow a direct zapping to that channel
  • In the program guide, when selecting details of a future program, a "Schedule on Wahoocorder" button is added to allow scheduling the record of that program by controlling the "manual recording" screen of the STB (including emulation of keypresses in SMS "multi-tap" style to enter program title)
  • When activating "follow zapping" in the setup dialog, the site hooks to the front panel incoming stream via websocket and pops up the details of the program currently watched on television.

Note that using a browser for both search and manual recording circumvents the tedious process of entering text using a remote with no alphabetic keys. Particularly frustrating is that those two features use a different convention (virtual on-screen keyboard for search, multi-tap for manual recordings). Forget about those as required remote keypresses will be performed for you to fill the fields ;-)


This hack was made just for fun, but I was amazed at what can be performed with an outdated device by adding less than $5 in hardware.

The ESP8266 really deserves its place as the IoT king. I was stunned by the fact it could handle 3 distinct web servers while parsing two message streams without a hitch. If its documentation was more complete, no doubt it would step from IoT king to IoT emperor :-)

samedi 25 février 2017

CH923/CH925/CH926/CH928/JY923/JY925/JY926/JY928 coin acceptor: Features and caveats

I just finished testing this coin acceptor and I thought I'd write about it because it can be tricky and many sample codes I found on the net can lead to errors in coin identification.

What is it ?

The CH92x (also sold as JY92x) is a cheap family of devices aimed at recognizing coins, to be used in vending or arcade machines. The variants differ in the number of kinds of coins they are able to recognize (3, 5, 6 or 8 kinds for CH923/JY923, CH925/JY925, CH926/JY926 or CH928/JY928, respectively). They have variable designs and variable packagings. Here's how I received my CH926:

Packaging-front Packaging-side

How to use it ?


My package didn't include any documentation, but the setup procedure can be found online in several videos (like this one or this one) or in documents, such as the following one:


The procedure is a bit tedious but works seamlessly. It consists of two steps:

1) configure the number of kinds of coins to recognize and, for each of them, How many of samples will be used for learning that kind of coin, the number of Pulses to generate when that kind of coin is detected, and the Filter precision for that kind of coin (1=restrictive, 30=loose).

Do not forget to power off the device after this step

2) feed the coin acceptor with as many samples as you have defined above for each kind of coin. It is advised to use a minimum of 15 coins for each kind to cover all variations. The device accepts up to 30 samples per kind. Of course, it is useless to sample the same coin several times...

Although the documentation above says the working current is 65mA, the peak when detecting a passing coin is well above 100mA, so make sure your power supply is able to provide current peaks. I suggest a 1A power supply.


Once configured, for each recognized coin, the device produces a train of <n> pulses on the "COIN" line, <n> being dependant on the kind of coin as configured in step 1 above. The fourth (grey) wire is labelled "COUNTER" on the device and in some documents, but I could not determine its function. It looks like it's in high impedance all the time...

Many uses on the net show the COIN line directly connected to an Arduino or RPi input. On my model, the COIN line can be in two states : floating or connected to ground, so a pull-up must be connected to it in order to measure voltages. It can be an external pull-up resistor, but it's much simpler to configure the GPIO it's connected to as INPUT_PULLUP of course.
It may be worth checking that it's also the case for you because if your model has an internal pull-up, it could fry your microcontroller if you connect it directly

Using a switch on the back, one can choose the IDLE state of the COIN line to be floating (normally open, NO) or set to GND (normally closed, NC). Most samples on the net use the most intuitive "NC" which makes pulses go up to VCC, but I personally chose NO to avoid wasting power in the pull-up resistor most of the time, which means my pulses are "going down" from VCC to 0V, but that does not really matter.

Each COIN pulse can have a different length according to the other switch at the back. The observed durations are as follows:

Fast 20ms/pulse
Medium 50ms/pulse
Slow 70ms/pulse

Each pulse is followed by a 100ms pause in idle state (no matter the selected speed setting). Here are a few traces for illustration:

- One coin configured as 4 pulses, "Fast" setting:

4 fast pulses

- Zoom on 1 pulse, "Fast" setting:

1 fast pulse

- Zoom on 1 pulse, "Medium" setting:

1 medium pulse

- Zoom on 1 pulse, "Slow" setting:

1 slow pulse

The "quick insertion" problem

Here's an example, when quickly inserting 2 coins in a row, with a pulse count of 4pulse/coin:

2x4 fast pulse

As you can, there is no pause between coins, so there is no way to divide the pulse train for sure.
What it means is that if you give arbitrary values to coins, such as, say, 1,2,3,4,5,6 then 2 coins of value 3 inserted quicky cannot be distinguished from 1 coin of value 6.

This caveat causes a very common mistake in sample programs, and although some claim it can be solved by tuning delays, the trace above proves it's not the case.

For example, in this video, As the author says, he had difficulties differenciate between coins and he relies on a timeout of 200ms to convert counted pulses to actual coins. That works pretty well if you leave time between coin insertions, but otherwise, you'll run into two issues:

  • Issue #1: the program can be fooled by quickly inserting a 50p followed by a 10p, because the train of 4 pulses for 50p will take at least half a second (4 * (20+100)ms) and the 2 pulses for the 10p will be queued next to it, forming a 6-pulse train that will be seen as £2 => seller loses
  • Issue #2: the program can also get stuck by quickly inserting two 50p coins for example, because the train will be 8 pulses long and that case is not handled by the code and those coins will get igored => customer loses

Let's take a look at another very popular example : :

It also relies on a "time-out" of 200ms on the COIN line to count the pulses and determine which coin was inserted, and suffers similar issues:

  • Issue #1: quickly inserting a $1 ("loonie") followed by a $0.25 ("quarter") will result in a train of 15 pulses which will display (and credit) $2 => seller loses
  • Issue #2 is "somewhat" handled by a specific "Unknown coin" case. For example, quickly inserting two $1 coins will cause 20 pulses and make the code fall in the "Unknown coin" case. However, as there is no way to return coins, unless this alerts the store manager, they will be "lost". And even if the store manager gets an alert and looks at the logs, he won't be able to tell which coins were inserted to make 20 pulses: Was it 2*$1 or 4*$0.25 or another combination ?

The easy solution

The only way to solve the "quick insertion" issue is to forget about determining "coins" and focus on "value", which means choosing a number of pulses that is proportional to the face value of the coin. But if you choose a too small unit (e.g. 1 pulse/cent), then a $2 coin will trigger 200 pulses which is both impractical (the train will last for at least 24 seconds !) but also impossible as the device is limited to 50 pulses per kind.

Consequently, with this device, it is impossible to reliably cover a range of coins where the largest value is >= 100 times the smallest one. For example, one will never reliably cover the full EUR or GBP range from 0.01 to 2 EUR/GBP, or even the full USD range from 0.01 to 1 USD..

Note that some documents also speak about an "AP mode" which seems to act as a divisor and only outputs one pulse after a threshold of <n> "internal pulses" (as configured with the P-setting) has been reached. I guess the goal is that if, for example, you sell items that are worth 50cents, you will only get a pulse every time 50 cents have been inserted (no matter if it's by 0.50, or 0.20+0.20+0.10, or 5x0.10, etc.). That reduces the number and length of pulse trains you have to handle, but does not fundamentally change the range limitation of course.

The sensitive solution is to cover a reduced range and give one pulse the value of the largest common divisor of all supported coins. For example, this popular Instructable covers 0.05 to 1 USD/GBP and maps 1 pulse to 5 cents. That code is safe and highly recommended.

I personnally chose the 0.10 > 2 EUR range, using 1 pulse for every 10 cents.

The drawback is that large values take a considerable amount of time to be decoded, and during that time, the feedback may be misleading: either you wait until the credit value is "stable" to display the credit (wait for timeout after last pulse to update display, not to count money) or you display a counter with slowly increasing value while pulses are being counted like in this video. The first can be freaky when you insert a big value and don't see any change for a "long" time, the second can be funny or unprofessional, depending on your point of view.

An intermediate solution is to start displaying a "hourglass" (or other animation) as soon as the first pulse comes in, and replace it with the total credit when it is stable. For example, here is a custom animation indicating that counting is taking place:

Here is the corresponding code :

The advanced solutions

The root cause of the issue is that the "pulse interface" is not really suited to the task. If it included a specified timeout between coins, the number of pulses per kind of coin could be greatly reduced. If the train was much faster (shorter pulses, shorter delay between them), then the "counting" phase would go unnoticed. This sluggish interface is particularly disappointing because the coin recognition itself is remarkably fast. Indeed, the 7-segment leds on the side of the device react almost instantly to coin insertion.

Solution 1 - SPI

One clever guy realized that the pulse interface could be dropped completely and replaced by decoding the values sent to the 7-segment controller via SPI. The C code is available but unfortunately, not much information is given regarding required connections.

I thought of doing the same but discoverd there were two other ways.

Solution 2 - Serial

First, my CH926 has a debug port at the bottom.of the board. with a slightly unusual connector (6-pin JST PH with a pitch of 2mm). Following the tracks, one of the pins is directly connected to the serial TX pin of the main chip (a 20-pin STC12C5608AD). The serial is 19200 8-N-1 and the output on this port is 6 + 4 bytes each time a coin is inserted, with a long pause in between.

Serial part 1 Serial part 2

Here are the messages I observed :

Coin type 1st part 2nd part
0.10 EUR 014D-008D-0188 018B-0186
0.10 EUR 014A-008D-0188 018B-0186
0.10 EUR 014D-008C-0188 018D-0186
0.10 EUR 014B-0090-0188 018B-0186
0.10 EUR 014E-0090-0189 018C-0186
0.20 EUR 0147-0063-015F 018C-0186
0.20 EUR 0146-0068-0164 018C-0186
0.20 EUR 0149-0067-0164 018C-0186
0.20 EUR 0147-0063-0162 018D-0186
0.20 EUR 0147-0063-0162 018C-0186
0.50 EUR 013E-004E-00FC 018D-0186
0.50 EUR 0141-0054-00FC 018C-0186
0.50 EUR 0141-0051-00FA 018B-0186
0.50 EUR 0141-004E-00F8 018D-0186
0.50 EUR 013C-0051-00FC 018C-0186
1 EUR 0167-006C-015F 018D-0186
1 EUR 0168-006E-015D 018D-0186
1 EUR 0168-006C-015F 018D-0186
1 EUR 0166-006F-0158 018D-0186
1 EUR 0169-007A-015F 018D-0186
2 EUR 017B-004E-0132 018C-0186
2 EUR 017C-003F-0138 018D-0186
2 EUR 0179-0042-012F 018C-0186
2 EUR 017B-003E-0135 018C-0186
2 EUR 0179-0042-0132 018C-0186
0.05 EUR 0128-000F-0159 018D-0186
0.05 EUR 012C-0015-0159 018D-0186
0.05 EUR 012C-0014-015A 018D-0186
Plastic token 018C-018D-018D 018B-0186
Plastic token 018C-018C-018D 018B-0186
Plastic token 018D-018C-018C 018B-0186

Although I have no idea what the individual values represent, one can notice that they all range between 000Fh and 018Dh, and that they are very similar among coins of the same kind, so those data are probably raw measurements from the sensors sent out for debugging purpose.

I think that with these data, the "coin recognition" algorithm can (and must) be re-implemented externally. Unless you are dissatified with the algorithm in the coin acceptor, this has little use, because although we could recognize more kinds of coins, there is no way to make the device actually "accept" more coins (activate the solenoid to allow the coin in and not reject it). Maybe that can be done by sending messages through the Tx pin which is also available on the connector, but the protocol is unknown... Otherwise, you can drive the solenoid yourself of course, but taking a step back, you are getting closer to building your own coin acceptor...

Solution 3 - Led

It's not the case for all devices of the CH95x family, but mine has a series of 6 leds along its side:

CH926 side

Each time a coin is recognized, the corresponding led flashes briefly, so as a very raw interface, simply reading the values of the leds immediately gives the kind of coin that was just recognized. Technically, it was rather simple to connect to those leds: The case can be opened with simple screws (one of them was under the "Passed QC3" sticker) but I had to unsolder the large coil above (because the coil itself is glued to the coin path) before I could reach the "front" of the PCB. I then soldered 6 wires to the anodes of the leds (the GND is common with the power connector on the back) and hot glued them in place. The setup looks like this:


Basic spying on those wires showed that for each recognized coin, a pulse of 150ms is flashed on the led. Cool. ... but unfortunately, there are some glitches that have to be eliminated.

  • First, upon powering, the pins are in high impedance, then the boot sequence of the CH926 flashes each led in turn for 10ms (here with leds 1-2-3):

Boot sequence

These issues can be solved with pull-down resistors (1K to 10KOhm) and with a delay in the setup() of the Arduino, which should ignore the boot sequence if the power supply is common to the coin acceptor and the Arduino.

  • Second, for an unknown reason, leds seem to randomly flash during 10ms once every minute or so, even when no coin is inserted. Here is a capture of such a "flash":

Led glitch

To solve this issue, the software has to reject all flashes shorter than a given value. I implemented the software using pin change interrupt and here is the result:

Here is the corresponding code :


The CH92x is a very reliable device but it requires careful software design to avoid its pitfalls. If you don't want to modify the led interface, I advise you to stick to a proportional count of pulse with respect to the coin value, and use an animation to give feedback to the user while you count the pulses. If you want the fastest response and have a model that has individual leds for each kind of coin, I think it's the best way to interface it.

Have fun !

lundi 19 décembre 2016

A Dirt Cheap F*** Awesome Interactive Led Table

Space invaders


What ?

  • led coffee table
  • touch-sensitive

What's special about it ?

  • IR detection through tranclucent acrylic
  • 3 devices connected to a Raspberry Pi via a single serial port
  • Java program hacked without modifying the actual code
  • 130 EUR (140 USD)

It's finally time I document this project I completed almost one year ago.

I wanted to play with large led displays for a long time (who doesn't ?), and browsing on AliExpress, I once landed on one of those WS2811/WS2812 addressable led strips for less than 3 EUR/meter, and thought "Wow, that's dirt cheap !".
I bought one meter just to play with it, and was surprised to see how fun it was (easy to control and quite powerful), and I started to think about wiring them in a matrix shape. Most of the libraries already support custom width and height, so that's pretty cool.

A few weeks later, I came across that large led matrix by GreatScott and I thought "F*** Awesome !", and following hours of youtube suggestions, I stumbled upon this video by yohash84 and said to myself "interactivity, mmmhhh".

And so the idea was born: I want a Dirt Cheap, F*** Awesome, Interactive Led Table.

First Things First: Non-interactive Version

After a proof of concept with just my 1m long strip, cells delimited with carboard and a thin tracing paper diffuser, I was rather confident that using a strip was a simple and effective solution, and set a non-interactive table as my first goal.


I started with IKEA's hacker-friendly Lack table. Its honeycomb cardboard structure makes it really easy to get a hollow table.

IKEA_LACK_2016-08-12_1310.png 20151023_171049.jpg 20151023_171709.jpg 20151023_172704.jpg

So first some calculations: I'll be using a strip with a density of 30 led/m, which leads to 3.33x3.33cm per matrix cell. The LACK table is 55x55cm, but the matrix cannot go from edge to edge because each corner of the table contains a bloc of particle wood (see picture above) to which the foot is attached. The frame is about 1cm and blocks are about 4x4cm each but I figured I could chop the inner corners of the blocks to maximise surface, so I settled on a matrix of 14x14 cells (or 46.67x46.67cm). The ~4 cm left around the matrix will be used to hide connections and will help the table remain sturdy. So here's the "final cut":

Final cut

OK, with these numbers, the absolute minimum bill of material is:

  • Lack table: 6 EUR
  • Led strip 7m, 30 led/m (which makes 210. I need 14x14=196): 19 EUR
  • Power Supply : 5V 20A (196 x 3 x 20mA = 12A required for the leds alone): 14 EUR
  • Controller: Arduino Nano clone: 2 EUR
  • Translucent acrylic plate: This is the hardest part to find cheap :-(. I ended up buying it locally but I it's available online for 26.5 EUR, including s/h

The rest (remains of white paint, cardboard, wires, etc.) I had laying around. Less than 70 EUR qualifies for "cheap", and if you can find or recycle a power supply or an acrylic plate, you qualify for "dirt cheap" :-) .

To create cell separations, I used thin corrugated cardboard but quickly found that cutting rectangular noches with regular spacing and depth was a very tedious task, so I piled up all the cardboard strips, stuck them between two old wooden planks and let my circular saw do the job...

20151205_185824.jpg 20151205_185925.jpg

Note that I first painted the cardboard, as well as the inside of the table and 4 small pieces of wood for the 4 inner sides in white. That way, inner reflections will increase brightness and level out the intensity of each cell. I should have taken more care though, because the veneer wood layer of the LACK table is so thin that my masking tape pulled it out at places :-( . I guess you get what you pay for.


I then pasted 14-led segments of the the self-adhesive strips to the bottom of the table, and connected the signal in series from line to line (in a zig-zag fashion), while the power for each segment is distributed from a central point to avoid the voltage drop across the 7-meter strip.

Pasting strip 20151205_191240.jpg

The result with the acrylic plate (protection sheet not fully removed yet) is quite satisfactory :


Arduino Nano

The animation above is simply the output of the XYMatrix example which is part of the FastLed library - highly recommended !

Raspberry Pi

For more complex animations, and to handle the interactive part, small microcontrollers would probably be very limiting, so I switched to a Raspberry Pi as the main controller.

Without any doubt, the king of diy led matrix animation software is Glediator. On the plus side, it's a really neat piece of software which allows highly customised animations and supports many matrix types and configurations, and it's free :-) . It is written in Java, which is a surprising decision for a software where performance is important, but it runs relatively well on the Raspberry Pi. However, the main drawback is that it's not open source...

In that configuration, the Arduino Nano is used as a converter from the Pi's serial signal to the WS2812 protocol. That is required because the Glediator running on Linux can suffer inaccurate timings due to the non-realtime OS, the Java VM garbage collection, etc., while the WS2812 leds requires a very precisely timed data stream.

I had a Raspberry Pi 2 laying around so I used that but probably a Raspberry Pi Zero or an Orange Pi could do the job for less. Anyway, let's say 33 EUR for a RPi 3 to be futureproof.

So here are the steps I took to set up the system:

  • Install VNC on Pi (see here).
  • Free the serial port of the Pi (used for debug and as a shell by default in the distribution) using raspi-config in recent distributions - see here.
  • Install RxTx Java serial lib on Pi using "sudo apt-get install librxtx-java"
  • Unzip Glediator on the Pi and create a startup script in the "dist" folder as follows:

java -Djava.library.path=/usr/lib/jni -jar Glediator_V2.jar

  • For the Arduino Nano, use the Arduino sketch provided on Glediator's website.
  • Start Glediator and configure matrix size (14x14) and pattern (in my case VS_BL for Vertical Snake starting at Bottom Left) "Output" in Glediator protocol in GRB with the serial port on 115200 bauds.

A note about baudrate: Most websites talk about a baud rate of 1000000 or 500000 bps, but with my Raspberry Pi 2, I could not achieve that bitrate at first and could not get anything to work until I reduced speed to 115200 bps as indicated above. However, the Nano (which has a 16MHz clock) is able to go much higher.

After more search, I discovered that the limitation to 115200 was on the Raspberry Pi side. The solution to go beyond 115200 was found on this post. So, to get 1000000 bps, I added the following line to the /boot/config.txt of the Raspberry Pi:
(and reboot of course).

Here is the result. Awesome :-) :

And Now for Something Completely Different: Interactivity!


It seems quite clear that infrared reflection is the cheapest way to detect a presence at small distance. The video by yohash84, already mentioned, is a very good demonstration of how IR can be used. However, there are a few highlights in this project:

  • it has to work through translucent acrylic
  • use 1 IR detector per visible led (yohash84 has 1 detector per cell of 4 or 6 visible leds)
  • favour a circuit simpler than this :-) , if possible (no offense)
  • oh, and keep it dirt cheap of course

Basically, that last point made me choose the cheapest emitting ang receiving IR diodes I could find, which are these (1.92EUR/100pc) and these (2.27EUR/100pc). As I need 200 of each, that adds 8.5EUR to the bill.

Of course, at that price point, one shouldn't expect to find a datasheet, but adding a resistor in series with a reverse-polarized receiving diode (more exactly a photodiode, or according to the measures probably a phototransistor, but let's just call it receiving diode for simplicity :-)) and measuring the voltage drop gives an image of the incoming IR. The basic circtuit is thus:

Basic schema

The hardest point was to achieve reliable IR detection through translucent acrylic. My preliminary tests showed that many parameters influence the measured IR level:

  • direct illumination: the risk is that the receiving led gets blinded by direct flow from the emitting led
  • external lighting: natural (sun) or artificial (TL) lighting generate much IR, with the unfortunate drawback that it acts "against" the expected IR reflection (the IR flow is reduced by objects that cast a shadow while the reflection against these objects should increase the flow)
  • internal reflections: this is the obvious enemy when putting a plastic plate in front of the leds, and particularly as it is translucent and not transparent
  • component dispersion and building imprecisions

All these elements made accurate detection a real challenge. Here are the strategies I put in place to solve it:

  • limit direct illumination by putting small tubes around the leds. These are made from black cocktail straws (add 2 EUR for 2x40pcs of 12cm, which is enough for 196x2x2cm).
  • eliminate influence of external IR sources by using a differencial mode: measure IR illumination when IR source is off (Ioff) and when IR is source is on (Ion), and consider that reflection is Ion-Ioff. This is not really accurate because the observed voltage drop is not proportional with the illumination, but it gives satisfying results nonetheless
  • reduce the "mirror" effect of the acrylic plate by not putting emitting and receiving diodes at opposite corners, but on two adjacent corners of the bottom, and make them aim at the center of the top surface. Diffusing objects such as paper or hand should reflect much IR while mirror reflection of the emitted IR light against the top plate should fall in the opposite corner and not on the receiving led (see geometric model below). Also, a calibration phase is applied to measure the received light when nothing is on the table vs when a full white surface (paper) is on layed upon the table.
  • compensate difference in component dispersion and led positions by making the calibration independant per cell.

Here is a geometric model of one cell:


As calibration must be made cell per cell, and with both low and high thresholds, using comparators and potentiometers was out of question. Calibration is performed in software at the very end (on the Raspberry Pi) and all measurements are achieved by the Arduino Nano using A/D inputs (note that it is a separate Arduino Nano from the one driving the led strip, which has very strict timing constraints. Here is how I came up with the final circuit, step by step:

Let's start with just one cell :

1 cell

OK, now as we don't have 196 A/D input, we have to multiplex several cells per input, like so:

2 cells A

But if the anodes of receiving diodes are all connected to GND, then the measure will combine all cells, so we use a GPIO per "column" instead of GND. When low, it acts as GND and "selects" one column (one cell in this case):

2 cells B 1

However, the "non-selected" columns will cause a problem. If we set their GPIOs to VCC, it will "short circuit" the A/D through the (now forward polarized) IR receiving diode. If we just leave them in high impedance (turning them to inputs), there is still a path that would "leak" through the IR emitter led. Both issues are illustrated below:

2 cells issue 1 2 cells issue 2

The solution is to add a diode in series with each IR receiver, polarized the opposite way, like so:

2 leds C

We need to be able to switch the emitting led on or off and compare the received IR intensity. To do so, as the "GND" of each cell is common to the emitting and receiving leds, the anode of the emitting led also has to be connected to a GPIO instead of VCC. When low, nothing will be lit, but when high, one emitting led (the one that has an both active "VCC" and active "GND") will turn on:

2 leds D

Now we can duplicate this structure for each row of the matrix. The simple solution using a single GPIO for all leds (at the cost of a transistor to increase the current) looks like this:

4 cells A

In that configuration, all leds of the same column having both VCC and GND active at the same time light up together, resulting in cross-cell illumination. Switching to one "VCC" GPIO per row avoids this issue by only activating one cell at a time. So the final model is the following:

4 cells B

Here is a video showing the proof-of-concept of this circuit in action:

Note that after more testing, in the final version, I slightly changed the resistor values and settled on 330 ohm for limiting resistors on the IR emitters and 38.3K (because that's the one slightly lower than 47K I had in stock) in series with the IR receivers.

Now we need to expand that to 14 rows by 14 cells, but of course, the number of A/D inputs is limited (8 per Arduino Nano) so two Nanos are used to perform measurements on all 14 lines. For the GPIOs, each Nano drives the "programmable VCC" for 7 rows. For "GNDs" however, we don't have enough I/Os for controlling each of them. The solution is to add an I/O expander controlled by the Nanos with fewer pins. I selected the MCP23017, which can be found for less than 1 EUR incuding s/h (add 5 EUR to the bill, 2 per Nano and 1 for the expander).

Now on to the real thing: Get the soldering iron and wire stripper!
To bend led legs precisely and assemble each of the 14 columns, I made a small jig reusing one of the planks that helped cut the notches in cardboard separators. One stripped wire for the "GND" is stretched between two screws and the angles and positions for the leds are drawn on the wood. One leg of each led (the correct one :-) ) is then bent and soldered to the wire. When all are soldered, the other led legs are bent up by the same angle and the diode preventing forward current through the IR receivers (see above) is soldered too:

Mounting animation One mounted row 4 lines

Then when all 14 columns are ready, two wires have to run perpendicularly for each line (one for "VCC" and one for measurements). Unfortunately these will cross the RGB led strips, so to avoid all risk of short circuit, they are only partially stripped, making a beautiful (and extremely boring to make) dotted pattern (grey and orange are for the 7 rows linked to the first Nano, brown and purple are for the second one) :

partially stripped wires soldering in progress fully soldered

... and with straws:

Cut straws Swtraws

Pretty cool, huh :-) ?

Controlling the GPIOs is easy, but there are two issues :

  • synchronization: the I/O expander for GNDs can only be driven by one I2C master, but two Arduino Nanos will be cycling the VCCs, and they have to perform the measurements in sync.
  • communication: the Raspberry Pi has a single serial port (and I'd rather have the data coming from a single source anyway). Note that to double the scan frequency, I wanted to run the two Nanos in parallel, so the two halves of the table are scanned simultaneously. The cross illumination between cells 7 rows apart is minimal.

Both issues were solved at once by multiplexing the Tx signals and having the Nanos "spy" on each other to speak in turn. One of Nanos is the "master" and sends its data first. The other Nano is the "slave", it listens to the master and then sends its data in turn. During that phase, the master listens to the slave, and when it's done sending, the next row is processed. When all rows have been processed, the loop restarts. The multiplexing of the Tx is done using a two diodes and a pull-up to 3.3V (remember the RPi logic levels are not 5V tolerant), creating an "or" gate, as follows:

Nano logic

For simplicity, both Arduino Nanos are identical both in software and hardware, except that pin 2 selects the behaviour (master or slave) depending if it's high or low. Each of them has 4 connectors :

  • a 3-pin header to the Raspberry Pi (GND, 5V, Tx) - any can be used. The Tx just requires a pull-up to 3.3V on the RPi side
  • a 4-pin header to the I/O expander (GND, 5V, SDA, SCL) - any can be used
  • a 4-pin header to the each other (GND, 5V, Rx, Tx). The cable crosses Rx and Tx
  • a 2x7-pin header to the led matrix (7x "VCC" and 7x measure)

Here are the schematic and a few pictures (with the Nano removed first, and then fully integrated):

Nano schema

Nano top Nano bottoms Nano integrated

The I/O expander is on its own perfboard, but it's really straightforward, with its 4-pin connector and a 2x7-pin header to the 14 "GND" lines. Here's what it looks like:

IO Expander IO Expander connected

As you can see, I used flat cables with 2x7 pin connectors to distribute signals for rows and columns. Add 4 EURs for those.

So here is the fully cabled IR matrix:

And here is the general schema:

Block Schema


Arduino Nanos

Each Nano has an outer loop that counts to 14 and sets one of the "GND GPIO" low in turn, while the other 13 are high. Inside this loop, the nano basically performs 2x7 measurements in a loop which goes:

  • measure row <n> (ambiant level)
  • set "VCC GPIO" high on row <n>
  • measure row <n> again (reflection level)
  • set "VCC GPIO" low on row <n>


  • it compute the delta (reflection - ambiant)
  • if the Nano is master, it sends a "start of message" byte, then the column number (0-13), then the 7 deltas, then listens and waits until it gets a "end of message" emitted by the slave
  • if the Nano is slave, it listens until it gets a "start of message", counts the 7 deltas emitted the master, then sends its own 7 delta plus a "end of message" byte

Then the Nano starts again with GND active on the next column.

One interesting thing to note is that the only sync between the two Nanos is the serial message. Even though one of them actually drives the I/O expander, the fact that they run the same code guarantees a high precision in timings as they run in parallel. The only time when code differs (the sending phase), I added a delay (measured by experience) on the slave to accomodate the time it takes the master to receive and detect the "end of message" byte.

Fun fact: one might wonder how we can be sure the Nanos are kept in sync, particularly regarding column numbers. Indeed, in case there is a temporary loss of connection, or one of the Nanos restarts, or after any condition happens that requires a resync, we have no guarantee that the outer loop will be at the same column number in both Nanos. Surprisingly, that doesn't really matter as long as the master is the one controlling the I/O expander. Indeed the only thing that matters is that the number sent by the master in the message is the number of the actual column powered by the expander. As the slave does not write the column number in the message, if it doesn't drive the expander either, the column number the slave "thinks" it is on does not matter : it just performs 2 measurements per row and returns their difference.

One more thing regarding the Arduino Nano: the Library to control the I/O expander was found in this thread.

Here is the sketch I used.

Raspberry Pi

In Glediator, a Generator is the primary class that renders contents. Generators can then be combined together to create Scenes, which can then in turn be combined to create PlayLists.

The basic idea is to create a Generator that takes the serial input from the Nanos, calibrates their values, and then renders them as a greyscale level on a matrix. After that, one can for example combine that matrix with a Rainbow Generator with a "multiply" operation, so that output is dark where no obstacle is "seen" by the IR, and rainbow-colored where an obstacle is detected.

Unfortunately, Glediator is not open source as I said, and has seen no evolution in the last two years. However, The great advantage of Java for hackers is that it is easy to change the behaviour of a Java program by overriding classes. So I quicky analyzed Glediator's classes to see what could be done, and I came up with a way to add new "generators" (much like extensions are added to browsers) by just giving replacement classes higher priority than the original ones in the classpath, and leaving the original Glediator distribution untouched.

I tried contacting the authors - SolderLed - several times to propose them to include those changes but got no reply. So I guess contributions are not welcome, or they just leave the project as abandonware :-(. In that case I hope SolderLed won't mind if I share the changes here.

For those interested, here is the code I sent them as a proof-of-concept. It contains the original Glediator v2.0.3 + a patch that overrides 4 Glediator classes to recognize "pluggable" generators + two new sample Generators: the first is a dummy "Uniform" color generator which is similar to the "Black" generator but with any color, and the second one is a "Game of Life", just because... you know.

Here's a demo with those two :

Next step: The Glediator generator reflecting exactly the values received from the IR matrix as a greyscale value (ok, there is mirroring mistake which was fixed later on). As you can see, it worked pretty well first time, but note that this is the ideal case, without white cardboard separators and translucent acrylic. Keep an eye on the screen at the right:

Note how the different cells have different values when nothing is in front of the leds. This is partly due to component dispersion, but also to the imprécisions in my build regarding led orientation. If they emitter and receiver are more or less "in front" of each other, the quantity of received IR can greatly vary, hence the calibration phase. The difference is made even less visible once the separators, and more importantly the translucent acrilyc plate are in place, so we need to amplify that difference, and that is the goal of the calibration phase too.

That calibration is thus done by determining low and high thresholds for each cell, and performing a linear interpolation in between, as follows:

Calibration logic

So the generator is first put in calibration mode, where it records the extreme "high" and "low" values observed for each cell, and stores them in two arrays. When exiting calibration mode, all incoming values will be "scaled" according to those extreme values. To check how it behaves, I developed the following UI:

With this approach, results are much better, even with the full separators and top plate:

However, this greyscale version doesn't give good results when combined with other effects using a "multiply" operation, because calibration is made with white paper laying flat on the table, which produces much higher reflection than average skin waved at the surface, resulting in a very dim image. As a first measure, a "threshold percentage" was added to convert this greyscale to binary ("black or white") values. Of course, while this percentage is a general setting, the actual value of the threshold varies from cell to cell according to the calibration. For example, a 30% threshold would be computed as follows on two cells:

Calibration with threshold

This is now much better:

As a last improvement, I replaced the unique threshold by a narrow linear interpolation from a low threshold to a high threshold. For example between 20 and 40%:

Calibration with 2 thresholds

And the results are really cool :-) :

Final bill

  • Non-interactive version (see above): 70 EUR
  • Raspberry Pi 3: 33 EUR
  • IR leds: 8.5 EUR
  • Straws: 2 EUR
  • 2 more Arduino Nano clones: 4 EUR
  • MCP23017 I/O expander: 1 EUR
  • Flat cables with 2x7 pin connectors: 4 EUR
  • Cables, soldering, resistors, perfboard, etc.: say 5-10 EUR

Total: 130 EUR (140 USD), or even less if you can recycle a power supply, a Raspberry Pi or a translucent acrylic plate.

For all the stuff there is in that table, I really think that's dirt cheap.

Final demo

pixel matrix + interactivity = ?

Tetris of course.

I say "Fucking awesome !"

Thanks for reading :-)

vendredi 23 octobre 2015

Tous les titres se ressemblent...

Enfin, certains plus que d'autres :

Pour référence : X Ambassadors "Renegades" (2015) vs Gerald de Palmas "Tomber" (2002), écrit par JJ Goldman.

dimanche 18 octobre 2015

Arduino Nano bootloader flashing jig

I'm a huge fan of cheap Arduino Nano clones that you can find for less than 2 USD (shipping included) on AliExpress and the likes.

They use an obscure CH340 USB/Serial controller instead of the expensive FTDI one of official Arduinos, but since the FTDIgate, I'm happy to steer clear from FTDI alotgether. The driver is in Chinese so you are greeted with weird messages upon installation, but apart from that, it works flawlessly.

What is great with the Nano design is that, as it includes a serial/USB chip (just like the big Arduino Uno), you don't need any specific hardware to (re)program it using the Arduino IDE. However, it is much smaller and cheaper, so it's the perfect candidate for small "set-up and forget" projects.

For those unfamiliar with the Nano, here is what it looks like : Nano Clone

Probably for economical reasons, the cheapest clones don't have a bootloader burned in, so this has to be done once and for all (nothing complicated with a small USB ASP programmer and the Arduino IDE).

For the same reason, and probably to avoid damages when shipping, those also don't come with the headers soldered, which is a good thing, because you're free to keep them bare and include them in small enclosures, or to populate the headers if you prefer. Nano clone unsoldered

However, as you can see in the first picture, the programming header through which you can burn the bootloaded (the group of 6 pins at the back) makes the whole thing much taller than needed, and it's a bit silly to solder the headers, burn the bootloader, and unsolder them...

So I thought the solution could be to burn it through a set of temporary connections known as pogo pins :

Pogo pin

So I ordered a pack of those.

The result is not very professional but hey, it works ;-)

Here are a few pictures:

Pogo rig 1

Pogo rig 2

Pogo rig 3

Notice the nice "clip" that guarantees a sufficient pressure so that the contact on the pogo pins is firm and stable.

Still many things to learn (in particular, this one lacks a way to force alignment of the board on the pogo pins), but the next one will be better ;-)

Enjoy !

mercredi 22 avril 2015

125kHz RFID reader comparison - HZ-1050 vs RDM6300

I needed a cheap EM4100 125kHz RFID reader and came across the RDM6300 and the HZ-1050. As I couldn't find a face to face comparison of those 2 readers, I bought one of each and did the comparison myself. So here we go...

Lire la suite...

samedi 17 décembre 2011

Binary stepwise division explained

Trying to write an emulator, I had a hard time finding back how stepwise division was implemented. I think I now have it, so I'm going to share it here in the hope it can be useful to others.

Binary stepwise division is exactly like performing a division by hand, so let't remember how it works.

Say you want to divide 135 (dividend) by 12 (divisor). The operations you do are like :

  • how many times does 12 fit in 13 ? Answer : 1. So 1) note "1" as the first digit of the answer and 2) subtract 1x12 from 13, that is 1, and bring the next digit from the original divident (5) next to the 1. In other words, you must now divide 15 by 12
  • how many times does 12 fit in 15 ? Answer : 1 So 1) note "1" as the second digit of the answer and 2) subtract 1x12 from 15, that is 3, and there is nothing left to bring next to it. We're done.

So we have the quotient : 11 (the two 1's found at each previous step), and we have the remainder : 3 (found at the end of the last step).

In fact, if we were working in a universe of 3-digit numbers, we would write the divisor as 012, the divident as 135, the remainder as 003 and the quotient as 011. Where does the 0 before the 11 come from ? Well, it comes from a first step I skipped above, that should be written like :

  • how many times does 12 fit in 1 ? Answer : 0. So 1) note "0" as the very first digit of the answer and 2) don't change anything to 1 but bring the next digit from the original divident (3) next to the 1. In other words, you must now divide 13 by 12

So we always have to perform as many steps as the width of our numbers, 3 in the above case. That's when we know we're done.

To implement that logic more easily, we'll use a buffer B to store the current number to be divided. Instead of starting with the first digit in the buffer, we start with an empty buffer (B=0) and an empty answer (A=0) and each step will first load the next digit in the buffer. So we'll rewrite the above operations as :

  • load one more digit from the dividend (1) into the buffer : B=1. How many times does 12 fit in 1 ? Answer : 0. So note "0" as the 1st digit of A and leave 1 in B : B=1
  • load one more digit from the dividend (3) into the buffer : B = 13. How many times does 12 fit in 13 ? Answer : 1. So note "1" as the 2nd digit of A and store the difference (1) in B : B=1
  • load one more digit from the dividend (5) into the buffer : B = 15. How many times does 12 fit in 15 ? Answer : 1 So note "1" as the 3rd digit of A and store the difference (3) in B : B = 3

Quotient A=011, Remainder B = 003.

The steps now look exactly identical :

  1. Load one more digit from the dividend into the buffer
  2. See if the divisor fits into the buffer.
  3. If so, note how many times it fits as the next digit of A and store the remainder into B. If not, note 0 as the next digit of A and leave B unchanged

OK. Now imagine we do the same with binary numbers on 8-digit (8-bit) : We want to divide 10000111 (=135 in decimal) by 1100 (=12 in decimal) . The operations we do are : Start with B = 0 and A = 0.

  1. load one more digit from the dividend (1) into the buffer : B = 1. How many times does 1100 fit in 1 ? Answer : 0. So note "0" as the 1st digit of A and leave 1 in B : B = 1.
  2. load one more digit from the dividend (0) into the buffer : B = 10. How many times does 1100 fit in 10 ? Answer : 0. So note "0" as the 2nd digit of A and leave 10 in B : B = 10.
  3. load one more digit from the dividend (0) into the buffer : B = 100. How many times does 1100 fit in 100 ? Answer : 0. So note "0" as the 3rd digit of A and leave 100 in B : B = 100.
  4. load one more digit from the dividend (0) into the buffer : B = 1000. How many times does 1100 fit in 1000 ? Answer : 0. So note "0" as the 4th digit of A and leave 1000 in B : B = 1000.
  5. load one more digit from the dividend (0) into the buffer : B = 10000. How many times does 1100 fit in 10000 ? Answer : 1 (take care, we're in binary. In binary it can just be 0 or 1 in any case). So note "1" as the 5th digit of A and store the difference (100) into B : B = 100.
  6. load one more digit from the dividend (1) into the buffer : B = 1001. How many times does 1100 fit in 1001 ? Answer : 0. So note "0" as the 6th digit of A and leave 1001 in B : B = 1001.
  7. load one more digit from the dividend (1) into the buffer : B = 10011. How many times does 1100 fit in 10011 ? Answer : 1. So note "1" as the 7th digit of A and store the difference (111) into B : B = 111.
  8. load one more digit from the dividend (1) into the buffer : B =1111. How many times does 1100 fit in 1111 ? Answer : 1. So note "1" as the 8th digit of A and store the difference (11) into B : B : 11.

Quotient : A=00001011 (=11 in decimal). Remainder B = 11 (=3 in decimal).

Good news is : we have the same correct answer in binary.

Summary : to perform a binary stepwise division on <n> bits, we just have to perform the same operation <n> times, the operation being formalized as :

  1. Load one more digit from the dividend into the buffer
  2. Compare the current "buffer" with the divisor and see if it fits
  3. If so, note 1 as the next digit of A and store the remainder into B. If not, note 0 as the next digit of A and leave B unchanged

A smart implementation in a CPU is to use a register R of 2x<n> bits, the lower half RL being loaded with the original dividend before starting and higher half RH being used as our B buffer. In our example, the 16-bit register R would start with 00000000 10000111 The step (1) then becomes a simple left shift by 1 of this register. (2) is a simple comparison : RH - divisor. According to the Carry (or Borrow) flag set by this comparison, we have two cases : (3a) if C is 0 (no borrow), set the next digit of A to 1 and set RH to RH - divisor. (3b) else set the next digit of A to 0 (no change to RH)

Smarter yet, each digit of A, as it is calculated, can be fed as the last digit of a register that would be shifted left by 1 at each turn. Wait, we just have such a register : that's RL. Each time it gets shifted left to feed RH, it leaves the last digit of RL free to be used as the next digit of A.

So in the end, we'll have the anwser A built bit by bit in RL, and the remainder in RH. Great !

If you're only interested in unsigned divisions, you can stop now. That's all there is to it.

In real life, however, we sometimes want to use signed numbers. In that case, the logic is similar, except that things like "fits in" and "compare" can take another meaning. So what this involves mainly is :

  • a first step to set some flags according to signs of the dividend and divisor
  • the same <n> operations we just described above (but taking into account the flags above to decide if it "fits")
  • a few correction steps once the division is complete.

dimanche 26 août 2007

Méfiez-vous des petits caractères

A nouveau deux photos que j'avais dans mes cartons depuis quelques temps. Il n'y a pas que dans les contrats d'assurance que les petits caractères réservent des surprises. Les polices en taille 5 de l'industrie alimentaire laissent aussi quelquefois perplexe...

Lire la suite...

samedi 18 août 2007

Panneaux insolites

En clin d'oeil, deux panneaux routiers qui laissent perplexe... Le premier à Bruxelles, à deux pas de la RTBF, avertit que l'on croise un tram à ... 1m. Pas d'illusion d'optique, le tram passe bien 1m derrière le panneau. A quand "Tram à 50cm" ? Tram à 1m Le second est en Bretagne, à Saint Colombier, et laisse aussi assez circonspect. Pas de montage ni de truc non plus, le portique enjambe bien cet énorme roc... 2m maximum Voir les lieux exacts sur Google Maps.

vendredi 29 juin 2007

How to use Ant like 'make' for generic conversions

While I appreciate Ant for automating java projects build, I still often find cases where I go "jeez, it would have taken two lines in a Makefile and I can't see how to do that with Ant"

Last case I came across: I have a bunch of JPG files and I want to make GIF thumbnails from them. ImageMagick does the job pretty well, but I want to automate the operation so that only newly added or changed JPG are reprocessed at each build.

Lire la suite...

jeudi 15 mars 2007

Plagiat ? Hommage ? Hasard ?

L'autre jour, sur Pure FM, deux titres l'un à la suite de l'autre : "Don't Listen To The Radio" puis "Relax - Take It Easy". Et deux fois de suite, je me suis dit "Ca me rappelle un truc, ça..." Ben oui, en effet, c'est non seulement ressemblant mais carrément plagié (involontairement peut-être...)

Lire la suite...

vendredi 2 mars 2007

Drivers VGA et manque de visibilité

Franchement, la manière dont les drivers déclarent les capacités du matériel qu'ils gèrent est vraiment obscure. Et en matière de vidéo, trouver la bonne combinaison driver/player revient le plus souvent à de l'essai-erreur.

Lire la suite...

lundi 19 février 2007

Ca, c'est de la HD

La TV numérique, c'est un beau concept, mais à c'est aussi un sacré paradoxe. Le problème, c'est le facteur qualité/quantité réglable : 6 canaux nickel ou 12 canaux potables par transportstream ? Quand on est distributeur, on est vite tenté de privilégier le nombre plutôt que la qualité. Ben oui, ça claque plus de dire "100 chaînes en qualité numérique" que "50 chaînes en excellente qualité numérique", que voulez-vous... Le problème, c'est qu'on ne peut pas jouer comme ça en HD...

Lire la suite...

samedi 17 février 2007

P965 et JMicron : rollback

Suite de ce post.

Bon, les résultats des drivers JMicron étaient atroces. Après pas mal de recherches, je suis tombé sur des posts qui conseillent de basculer le JMicron en mode RAID dans le BIOS.

Essayé. Ca démarre, l'utilitaire RAID accepte de s'ouvrir et est même présent à côté de l'horloge, mais bon, on ne peut pas y voir grand chose à part le disque dur... Pas d'amélioration du côté lecture, par contre :-(

Tentative suivante : désinstaller le driver JMicron tout en laissant le BIOS sur RAID. OK, mais au reboot, il dit qu'il y a un nouveau matériel (contrôleur RAID) et cherche le driver. Introuvable, soit... Résultat : plus de disque dur :-(. Paaaas bon.

Enfin, retour à la case départ : passage du BIOS en mode IDE.

Tout redémarre, et plus de souci côté accès disque (ouf). Dans le gestionnaire de périphérique, le DVD est maintenant en ... UDMA4 !

Ah ben ça, alors... Pas encore pu faire d'essai de gravure, mais c'est quand même spécial...

Re-à suivre donc...

vendredi 16 février 2007

Hanoï est là

Sans hésiter, procurez vous le DVD. Non seulement un concert étonnant (en tout cas la partie philharmonique) mais surtout cette ambiance décalée... Le CD et le DVD sont référencés sur la discographie...

jeudi 15 février 2007

Cartes mères P965 pour Core 2 Duo - Attention

J'ai rencontré quelques problèmes de performance, il semble que je ne sois pas le seul... Le fait que le chipset P965 n'ait aucun support pour le parallel ATA impose pour l'instant aux fabricants de recourir à d'autres chips controleurs PATA, mais manifestement, c'est loin d'être parfait dans l'état actuel des drivers

Lire la suite...

Nostalgie informatique

Le week-end dernier, j'ai déterré les reliques de mes premiers contacts avec l'informatique, j'ai nommé le ZX Spectrum. Que de souvenirs, que de nostalgie...

Lire la suite...

jeudi 8 février 2007

La fin des DRM ?

Steve Jobs (Apple) propose dans une lettre ouverte la suppression pure et simple des DRM comme la meilleure voie pour ouvrir la compatibilité fichiers / lecteurs dans le marché de la vente de musique en ligne.

Lire la suite...

- page 1 de 2