Above, the Card Reader (top right) attached to a stock Supercon 2022 Badge (bottom right), next to a stack of attendee-provided programs marked on custom sheets (left). Photo directly after presentation Sunday evening, showing Kenneth's successfully loaded 128-bit counter ("A Candlelit Dinner") still running. Note illuminated scan LEDs, forever awaiting their next program.

The Supercon 2022 Badge Card Reader is a transmissive optical input device designed to facilitate loading hand-marked paper programs onto a stock Supercon 2022 Badge. The Card Reader consists of two arrays of thirteen red LEDs on 0.3" (7.6 mm) centers, harvested from a donor badge, which face each other across a minimal gap set by several layers of cardboard shims and guides. One set of LEDs serve as a single receiver; they are wired in parallel, reverse-biased, and fed into a transimpedance amplifier with 1E6 gain. This useful if somewhat finicky technique can be traced back to Forrest Mims III's various experiments and published circuit diagrams on the subject. The other set of LEDs are Charlieplexed and scanned sequentially, illuminating one bit at a time in rapid succession. All of the LEDs are managed by a Pixelblaze Sensor Expansion Board, chosen for its convenience (as Ben is also known as ElectroMage, Creator of Pixelblaze, and thus had a few extras in his bag) and its 12 bit, 1 Msps ADC.

The printed programming cards store 32 12-bit hand-marked words, providing a 1:1 match to the Supercon 2022 Badge buttons in quantity, function, and physical spacing. Each line also includes a prefilled sync bar which is used to trigger the ADC reads for each row and to serve as an overall word counter for a given scan. In use, cards are fed into the top of the Card Reader and smoothly drawn through by hand. If the sync bar count is not 32 after a brief delay, an error LED flashes and the card must be re-scanned. If the count is correct, the word order is inverted (as the cards are inserted end-first) and added to a buffer. Once the last card is scanned, the badge is placed in UART programming mode, the Card Reader button is pressed (there is only one button, also harvested from a donor badge), and the program is transmitted via the Badge header connector to the waiting RX pin.


We both showed up to Supercon on Friday morning. Soon after receiving our badges at SupplyFrame HQ we agreed to team up for some badge hackery. Ben suggested the idea for a punchcard reader which Zach enthusiastically supported. Early on, we felt that using as many commonly available components as possible was worthwhile (and neither of us had brought a pile of photodiodes), which led to stripping a few dozen red LEDs off Zach's badge using a hot air tool. We ran a quick test, reverse-biasing an LED with a few volts and running it through a 1 M resistor, and then checking the voltage across the resistor using a multimeter under a few conditions: ambient illumination, opaque coverage, paper, and Sharpie-marked paper. The latter two measurements differed by a few dozen millivolts, which seemed promising enough to pursue. We decided to match the button spacing and commenced soldering LEDs to our two supplied expansion prototyping boards:

Above, bare illumination board (top) and sensor board (bottom).

Point-to-point wiring is accomplished using our mutual favorite 34 AWG polyurethane-insulated magnet wire, which is relatively easy to thermally strip using a bit of flux and a well-tinned iron tip. In both cases, the bulk of the interconnect happens on the side opposite the LEDs; this was particularly important for the Charlieplex wiring, which overlaps a few times and would complicate adding guides and light shields down the road. After assembly, we tested the LEDs for continuity (and subsequently reflowed a number of sketchy magnet wire joints) and Charlieplexing logic:

Above, early testing of the illumination board with a marked programming sheet. We committed to the project's success early, soliciting programs such as the one shown above as soon as we had sheets printed on Saturday morning. Risky.

Conveniently, in between days of hacking Ben had access to a laser cutter. Before meeting back up Saturday, he cut a set of simple guides out of 1/16" (1.6 mm) clear acrylic. The design used a pair of spacers to separate the top and bottom guides, each of which had 13 holes for LEDs. Since the clear acrylic would do little to attenuate crosstalk between channels, we colored in the sides of the holes with black Sharpie and glued the structures together. We soldered the Pixelplaze Sensor Expansion Board to the illumination LED board, and also added a pair of extension boards off to one side to hold headers and (eventually) the switch and error LED. Then we temporarily aligned the boards to the guide assembly and secured the parts with clamps for bench testing:

Above, the top and bottom LED boards are temporarily clamped around an acrylic guide assembly for testing.

We figured we wouldn't be able to directly drive the STM32F030's ADC using the paltry current generated by the LEDs (remember, millivolts over a megaohm), so we tried repurposing the convenient audio amplifier which is also present on the Pixelblaze Sensor Expansion Board. We poked at this for a bit without encouraging results; eventually, we built a dedicated transimpedance amplifier using an extra LM358LV which we found in one of Digi-Key's parts bins:

Above, a bodgy transimpedance amplifier based on one side of an LM358LV, mounted to the back of the sensor LED board.

Of all of the parts of the project, the analog front end probably resulted in the most hair-pulling and I-need-to-take-a-break-ing. We had a few great helpers here who filled in the critical missing pieces we'd missed (since we were just learning about transimpedance amplifiers); most notably, the single-ended LM358LV really needs to be biased to the midpoint, which we did using a pair of 10k resistors across the voltage rails. Keen observers will notice that tired eyes misread one of the resistors in the above image; thankfully this was caught (always check your voltages!) before it caused another multi-hour delay.

After many revisions to the analog bits, we were finally seeing encouraging values from the microcontroller. When we pressed the two boards together tightly across a test sheet with alternating marks, we read 300-500 ADC count differentials; more than enough to play with thresholds and maybe include automatic gain control to compensate for ambient light! Unfortunately, our jubilation was short-lived; when we re-installed the acrylic spacer and tested again with paper, our signal range dropped by an order of magnitude, an effect we attributed to being on the wrong end of the inverse square law (as in, the spacing was far too great). We slept on the problem, and on Sunday returned with some 0.6 mm cardboard sheets with a few laser-cut holes at proper spacing. After a bit of tinkering and re-gluing, we ended up securing a perforated mask to each LED board with an additional single shim on the infeed side to clear the LEDs, then separated the two halves with a pair of cardboard spacers. 

Above: Zach was able to get incredibly good alignment and and extremely small gap between illumination LED and sensor LED, just large enough for a paper to slide through.

As Zach was paper-crafting, Ben wrote a test program which watched sync pulses and filled a buffer with bits that fell below an experimentally-derived threshold. At 2:20 PM on Sunday, with just over three hours to go before the badge ceremony, we pulled our first perfect test sheet:

Above, the first perfect test scan. Left, the marked test sheet (note the generous square markings) loaded into the assembled LED board sandwich; right, the resulting buffer captured by the STM32F030, not yet reversed but numerically correct.

It turned out we weren't quite done yet, as the scan speed and accuracy was fairly low. Initially the firmware switched LEDs and started an ADC at the same time, relying on generously configured ADC sample times to allow the analog frontend to settle. This was switched to a toggle, allowing a much larger phase between LED and ADC sampling. Meanwhile we removed the 100pF filter capacitor on the transimpedance amplifier circuit. This increased risk of oscillation, but the analog was much more responsive to quickly changing sensor conditions - and thus we could increase scanning speed considerably. This also seemed to increase the accuracy as well, probably reducing ghosting effects from previous bits. 

The next few hours were a frantic circus of mechanical adjustments, firmware programming, card testing, and rapid iteration. More can certainly be said about this time. But finally, at 5:27 PM, right as we were being encouraged to clean up for the badge ceremony, we scanned and successfully loaded our first program. Dan Young happened to be there to capture the moment:

Above, with three minutes to spare, we successfully scanned and loaded our first program.

Theory of Operation

Program data is written onto a paper with 32 words by using a sharpie to fill in a circle/dot in a column/position that correlates with the badge instruction bits. A 13th column acts as a "sync" bit with a narrow dark bar that is slightly smaller in height than the program circles.

13 Charlieplexed LEDs are lit in sequence, one at a time, shining light onto a parallel array of sensor LEDs. Dark sensor LEDs do not contribute much at all to the input and they are fairly insensitive to ambient light, so the illuminating LED effectively selects which bit is being checked. When light shines through the paper, the sensor LED on the opposite side is lit up as well. Sharpie (or the printed sync bit) effectively blocks the light, and filled dots will not illuminate the sensor LED.

This can be seen in this video:

The scanner continuously scans the row across all 13 bits, interpreting the data as the paper is manually pulled through the reader. It does not need to be pulled through at any particular speed, which is necessary for a hand-fed scanner system, and can scan quite quickly at around 384 row scans per second.

As the paper is pulled through the reader, the rightmost "sync" bit triggers data acquisition. While in this state, the bit sensors are read and checked against a threshold. Any dark bits cause a program bit to be set. Bits are "sticky" in that they only need one dark reading to be considered set while the sync bit is detected. A single row may be checked hundreds of times while the paper moves across the sensor area. 

Firmware & Processor

The firmware can be found here:

It runs on the STM32F030F4P6 on a hacked Pixelblaze Sensor Expansion Board, info for the board can be found here:

The STM32F030F4P6 is a low end 32-bit ARM Cortex M0 with 4K of RAM, 16K of flash, and runs at 48MHz. It's fairly small compared to most MCUs these days, but something of a beast compared to the 4-bit computer the badge emulates. It also has a very nice ADC that can do 12-bit conversions at 1M samples/sec. 

The scanner works by running a timer (TIM1) at 10KHz, which fires an interrupt. The interrupt toggles between activating the next LED, and starting an ADC sample, so 5K samples/sec are taken which works out to about 384 row scans per second. 

ADC conversions are automatically stored in a buffer via DMA, and copied to a working array when all 13 bits are scanned.

The main loop checks for new data, and proceeds to process it in a state machine.

State WAIT_FOR_PAGE checks for a page scan or button press. If the button is pressed with a buffered program, it is sent via UART to the badge. PAGE_ERROR state is a special case of this, and behaves the same but also blinks the page read error status LED. Both states fall through to the WAIT_FOR_SYNC state.

WAIT_FOR_SYNC constantly checks for the sync bit to go low, below a threshold, switching to IN_SYNC state. If no sync bit is found after a timeout and 32 rows have been scanned, they are reversed (since we scan bottom up) and stored in the program buffer. if anything but exactly 32 rows have been scanned, PAGE_ERROR state becomes active.

During the IN_SYNC state the program word bits are checked against a threshold and ORed with the current word. This can happen many times, ORing every set bit it sees during the sync bit. This improves dot recognition and requires less precise alignment. If the sync bit goes high the assembled word is counted. If more than 32 words were scanned, it switches to PAGE_ERROR state, otherwise back to WAIT_FOR_SYNC state. If the sync bit stays low for too long a timeout triggers PAGE_ERROR state.

When the program is to be transmitted, trailing blank instruction words are trimmed. This seems to match the behavior of the badge as well during manual program entry. So while the reader always scans full pages of 32 words, only ones that have been used are actually sent based on the last non-empty row. 

The leds.c file has a setLed() function that handles the charlieplexing based on a GPIO pairing table, using the least significant bit to determine direction. It seems to be pretty quick, and was a lot less typing than a giant switch case statement. 


A few pictures of the finished badge:

Above: Front view of Card Reader as installed on a stock badge, showing front badge expansion board with illumination LEDs, Pixelblaze Sensor Expansion Board, white expansion-expansion board for button and LED, and cardboard feed ramp.

Above: Rear view of Card Reader as installed on a stock badge, showing rear badge expansion board with sense LEDs, transimpedance amplifier, and white expansion-expansion board for STM32 programming header. Also the back of the feed ramp.

Above: close-up of Pixelblaze Sensor Expansion Board co-processor, showing wiring detail.

We ended up winning the Expansion Connector category. Here is the ceremony video with our live demo, cut to the appropriate timestamp; we also urge readers to watch the ceremony from the start, as there are many other fantastic badge hacks to see.

Thank you to everyone who supported us in this effort: Michael, Morgan, Debra, Evan, Tom, Kenneth, James, and the flock of anonymous (please comment if we missed you!) friends who kindly wrote programs for the badge. Your support means the world to us.