I found a vape pen on the ground already half smashed so I couldn't identify what it was sadly, other than it being pink.

After taking it apart (pliers, force, beer) I looked at the chips under a magnifying glass, and saw it had a SC8F073 MCU^[yeah no idea. Had to look it up. Says it might a UART interface I can dick around with, but not seeing anything], and also had this little ribbon display whose only markings I could see are "12-25 WC294F".

Charlieplexing

It's a 6 pin cable going towards 18 LEDs, so in theory has more than enough (6*5 = 30) configurations to light up each LED.

Turns out because these are what I like to call "Cheap and Cheerful Chinese Chips", they cannot be arsed to put in the required resistors to allow such addressing to take place, so they make use of something called "charlieplexing" which works as follows:

  1. Only a single pair of anode/cathode pins needed to light up an LED (1 high, 1 low, all others in "high impedance" (i.e. disconnected))
  2. Since multiple LEDs might share an overlapping pin, you can't have multiple pairs of pins active at the same time, as this will cause spillover to other LEDs
  3. To show the number "3" , made up of 5 LEDs, you have to cycle through those 5 pairs of pins whilst impeding all others, very very fast to give the illusion of the number "3".

I used a USB to TTL chip from my laptop with Dupont connectors going into an FPC breakout chip (both roughly 5 quid from online retailers), and hooked the display ribbon into it, and got to testing pairs. I got the following (High Low) pair diagram

    |  32  |       65         53
    |  34  |    56    64   43    52
    |  35  |       54         42    
    |  36  |    61    63   45    51
    |  41  |       62         46      26

You might notice:

  • The battery (far left) has multiple pin mappings
  • The percent symbol (far right) has just one, despite the percent one arguably having a large use case by being paired with numbers...
  • There's no common pin grouping for each digit. Digit 1 is a mix of 5/6 high and Digit 2 is a mix of 4/5 high. So I feel like they could have done better.

Anyway after some dicking around, I put this little python "driver" to work and now it can do arbitrary numbers

Code

import json
import time
from pathlib import Path
from typing import Iterable
from gpiozero import InputDevice, OutputDevice


GPIO_PINS = (13, 19, 26, 16, 20, 21)
##FFCB_PINS = (1, 3, 5, 2, 4, 6)       # Physical FFC interface
DISP_PINS = (6, 4, 2, 5, 3, 1)       # Display pin numbers

Mapping = dict[str, dict[str, list[int]]] # Type


class PinMap:
    """Load display segment definitions and map them to display pins."""

    def __init__(self, gpio_pins: Iterable[int] = GPIO_PINS,
                 display_pins: Iterable[int] = DISP_PINS,
                 segment_file: str | Path = "out_segments.json"):
        self.gpio_to_display = dict(zip(gpio_pins, display_pins))
        self.display_to_gpio = {display: gpio
                                for gpio, display in self.gpio_to_display.items()}
        self.segments = self._load_segments(segment_file)

    def _load_segments(self, filename: str | Path) -> Mapping:
        with open(filename, encoding="utf-8") as file:
            raw_segments = json.load(file)

        segments: Mapping = {}

        for name, pairs in raw_segments.items():
            display_name, _, part = name.partition(".")
            part = part or "main"
            display_pairs = [[self.gpio_to_display[gpio] for gpio in pair]
                             for pair in pairs]
            # Preserve the original behaviour: use the first pair.
            segments.setdefault(display_name, {})[part] = display_pairs[0]

        return segments

    def to_gpio_pairs(self, display_pairs: Iterable[tuple[int, int]]) -> list[tuple[int, int]]:
        return [(self.display_to_gpio[anode],
                 self.display_to_gpio[cathode])
                for anode, cathode in display_pairs]


class CharlieplexDisplay:
    """Drive a Charlieplexed display using gpiozero."""

    def __init__(self, pin_map: PinMap, gpio_pins: Iterable[int] = GPIO_PINS):
        self.pin_map = pin_map
        self.gpio_pins = tuple(gpio_pins)
        self.devices: dict[int, InputDevice | OutputDevice] = {}

    def __enter__(self) -> "CharlieplexDisplay":
        self.high_impedance()
        return self

    def __exit__(self, exc_type, exc_value, traceback) -> None:
        self.close()

    def _close_devices(self) -> None:
        for device in self.devices.values():
            device.close()
        self.devices.clear()

    def high_impedance(self) -> None:
        """Release all GPIO pins and configure them as floating inputs."""
        self._close_devices()
        self.devices = {pin: InputDevice(pin, pull_up=None, active_state=True)
                        for pin in self.gpio_pins}

    def close(self) -> None:
        self._close_devices()

    def drive_pair(self, anode: int, cathode: int) -> None:
        """Drive one LED: anode HIGH, cathode LOW, all others floating."""
        valid_pins = set(self.gpio_pins)
        if anode == cathode:
            raise ValueError("Anode and cathode must be different")

        if anode not in valid_pins or cathode not in valid_pins:
            raise ValueError(
                f"Pins must be in {sorted(valid_pins)}; "
                f"got {anode}, {cathode}"
            )
        self.high_impedance()
        # Replace the two input devices with outputs.
        self.devices[anode].close()
        self.devices[cathode].close()
        self.devices[anode] = OutputDevice(anode, initial_value=False)
        self.devices[cathode] = OutputDevice(cathode, initial_value=False)
        # Set sink first, then source.
        self.devices[cathode].off()
        self.devices[anode].on()

    def light_single_pair(self, anode: int, cathode: int, duration: float = 1) -> bool:
        """Light one LED for duration seconds."""
        try:
            self.drive_pair(anode, cathode)
            time.sleep(duration)
            return True
        except Exception as exc:
            print(f"Error lighting {anode} -> {cathode}: {exc}")
            return False
        finally:
            self.high_impedance()

    def light_pairs_multi(self, display_pairs: Iterable[tuple[int, int]], loops: int,
                          on_time: float, off_time: float) -> bool:
        """Multiplex a sequence of display-pin LED pairs."""
        gpio_pairs=self.pin_map.to_gpio_pairs(display_pairs)
        if not gpio_pairs:
            return False

        try:
            for _ in range(loops):
                for anode, cathode in gpio_pairs:
                    self.light_single_pair(anode, cathode, on_time)
                    time.sleep(off_time)
            return True

        except Exception as exc:
            print(f"Error in display: {exc}")
            return False

        finally:
            self.high_impedance()



def make_number(display: CharlieplexDisplay, leds: dict, value: int, loops: int = 100,
                show_percent: bool = False, show_battery: bool = False) -> bool:
    if not 0 <= value <= 199:
        print("Value must be between 0 and 199")
        return False
    digits = f"{value:3d}"
    pairs: list[tuple[int, int]] = []
    for index, digit in enumerate(digits, start=1):
        pairs.extend(leds[f"dig{index}"][digit])
    if show_percent:pairs.extend(leds["percent"])
    if show_battery:pairs.extend(leds["battery"])
    return display.light_pairs_multi(pairs, loops=loops, on_time=1e-4, off_time=0)

pin_map = PinMap()
dp = pin_map.segments


# Make the LED mappings a bit more legible?
DIGITS = {
    " ": (),                               "0": ("tm", "tr", "tl", "br", "bm", "bl"),
    "1": ("br", "tr"),                     "2": ("tm", "tr", "bl", "bm", "cm"),
    "3": ("tm", "tr", "bm", "br", "cm"),   "4": ("tl", "cm", "tr", "br"),
    "5": ("tm", "br", "bm", "tl", "cm"),   "6": ("tl", "cm", "tm", "br", "bm", "bl"),
    "7": ("tm", "br", "tr"),               "8": ("cm", "tr", "tl", "tm", "br", "bm", "bl"),
    "9": ("cm", "tl", "tm", "tr", "br", "bm"),
}

SEGMENT_NAMES = {
    "tm": "top.middle",   "tr": "top.right",     "tl": "top.left",  "cm": "center.middle",
    "br": "bottom.right", "bm": "bottom.middle", "bl": "bottom.left",
}

def make_digit_map(display: dict) -> dict[str, list[tuple[int, int]]]:
    def segment(name: str) -> tuple[int, int]:
        return display[SEGMENT_NAMES[name]]
    return {digit: [segment(name) for name in names]
            for digit, names in DIGITS.items()}

leds = {
    "battery": [dp["battery"]["main"]],
    "percent": [dp["percent"]["main"]],
    "dig1": {
        " ": [], "0": [], ## Empty arrays we can add in
        "1": [dp["dig1"]["bottom"], dp["dig1"]["top"]],
    },
    "dig2": make_digit_map(dp["dig2"]),
    "dig3": make_digit_map(dp["dig3"]),
}

# display = CharlieplexDisplay(pin_map)
# print(leds["dig2"]["3"])
# print(display.light_pairs_multi(leds["dig2"]["3"], 100, 1e-3, 1e-3))

with CharlieplexDisplay(pin_map) as display:
    value = 199 ## the literal max value
    while value > 0:
        make_number(display, leds, value,
                    loops=3, show_percent=True, show_battery=False)
        value -= 1
    ## And then add the battery display at zero
    make_number(display, leds, 0,
                loops=300, show_percent=False, show_battery=True)

all 30 comments

sorted by: hot top controversial new old
[–] 38 points 4 days ago (7 children)

Checking online I'm often struck at how motivated others are with their free time, while I twiddle my thumbs or stare at myself in the mirror while I work out, others are disassembling electronics and shit just for funzies.

  • source
  • hideshow 7 child comments
  • [–] 8 points 4 days ago (1 child)

    I somehow only have the motivation for physical stuff. Im sorta like the stereotypical gifted kid, could do amazing stuff if i could motivate myself, instead i do have motivation just for like working out, sports, going out with people. The moment i have to do something that requires sitting down and focusing i just instantly transition to wasting time on my phone without even noticing sometimes.

  • source
  • parent
  • hideshow 1 child comment
  • [–] 6 points 4 days ago* (1 child)

    I have a cousin, smart guy, but seemingly incurious. Spends most of his free time watching TV or solving sudokus. What a waste.

    And here I am, pissed off that I have to sleep because I have more projects on the go than I can complete in this lifetime.

  • source
  • parent
  • hideshow 1 child comment
  • [–] 3 points 4 days ago

    Personally, it helps me so much to make things that are useful for me or others, even in the smallest way. Sometimes even just thinking in a 'I might learn something new' is enough.
    I also have a project tracker on NocoDB, and just seeing 'the number go up' makes me feel good for what was accomplished. Like a no drinking tracker but in reverse?

  • source
  • parent
  • [–] [S] 36 points 4 days ago* (4 children)

    I should add that I like the idea of having a little display like this hanging off a raspberry Pi or something to show, I dunno, progress of a download or something... I'm just not a fan of the display having to update multiple times per second to show the same damn number.

    Ideally: I would just set a few pins high and few pins low to show a single number, and not have to change those pins until I want to change the number.

    Slowly, I'm beginning to understand why people go for more hobby-friendly LCD displays which can persist an image without having to constantly redraw

  • source
  • hideshow 4 child comments
  • [–] [S] 16 points 4 days ago* (3 children)

    Okay, after thinking about this more carefully I realise that most displays are likely charlieplexed and what I want is probably unreasonable.

    Each of the main digits has 11 desired states^["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", " "], 2 states for the first digit, 2 states for battery, and 2 states for the percent symbol

    That's a total of 11 * 11 * 2 * 2 *2 = 968 individual states that I want to represent in a fixed way.

    If I had a 10-pin cable then this would be no issue (2^10^ = 1024 states), but such a display would also need an extra controller to handle the multiple LEDs.

    This way, with just 6-pins oscillating over all 18 LEDs and making use of the refresh rate of the human eye, greatly simplifies the logic and minimizes the number of pins.

    Actually, this is probably how all displays work....

  • source
  • parent
  • hideshow 3 child comments
  • [–] 5 points 4 days ago (2 children)

    For data transfer to the display pixels, almost definitely every display uses some sort of multiplexing, but most display boards include a driver chip that does the multiplexing for you. There are a couple of persistent display techs (only send data when you want to change what is displayed) like e-ink, but there's also a LCD version that I think Sharp just released, that doesn't have e ink's slow update rate or persistence tendencies. Both are much more expensive than your $free.99 display.

    Very cool that you got it working! I also found a disposed vape and ripped out the charging board with a display like that to monitor/charge some spare 18650 batteries I harvested. I hate that the standard vape is now disposable, Lotta good stuff in there!

  • source
  • parent
  • hideshow 2 child comments
  • [–] [S] 2 points 4 days ago* (last edited 4 days ago) (1 child)

    Yeah I think I was expecting something a bit more refined in what data I was sending over the wire. Like "100101” would give me a persistent 37, but I guess I gotta face the reality of how these things actually work... or go for e-ink screens like you say

    This was my first go at reverse engineering something like this, so I had a blast and learned loads. Especially how microphones and vape pens go hand-in-hand hahaha

    Would love to see your battery monitoring setup!

    Also, I'm still quite confused by what putting a pin into a High-Z state actually means. Before this, I genuinely thought a pin was either 1 or 0

  • source
  • parent
  • hideshow 1 child comment
  • [–] 4 points 3 days ago (1 child)

    That's something! I haven't seen a module quite like that in my street lithium finds, but I'll be sure to check some of the specimen I've collected for that chip. It's really a shame all these resources are damned to waste, especially considering I've collected something like 80AH of capacity, factoring for the success rate I've had recovering the cells so far

  • source
  • hideshow 1 child comment
  • [–] 13 points 4 days ago (1 child)

    I did not understand a single word you said. But it's still cool and it looks cool too. Carry on you crazy person!

  • source
  • hideshow 1 child comment
  • [–] 6 points 4 days ago* (1 child)

    "Disposable" vapes are crazy to me, specifically the ones that are not refillable in any way. People buy them, spend extra money for the electronics, and then just throw them in the normal landfill trash. Lithium batteries shouldn't be thrown in the trash, ever, but these almost always get thrown into the trash or littered.

    I don't understand why people don't just get the vapes that have the cartridge seperate from the device, the type that you screw on. After two or so purchases of the "disposable" vapes, you'd have been able to pay for a cheap cartridge-based device and then save money from there instead.

    Anyway, rant over. Cool little project! I'm always for repurposing ewaste :D

  • source
  • hideshow 1 child comment
  • [–] 6 points 4 days ago (2 children)

    Awesome!! How does one get into doing this kind of thing?

  • source
  • hideshow 2 child comments
  • [–] 6 points 4 days ago (1 child)

    https://learn.adafruit.com/

    Microcontroller programming + some basic electronics knowledge can do a lot. There's lots of guides and whole projects with step-by-step instructions, pictures, videos, etc.

    Look into something like LED control. You can examine projects like (WLED)[https://github.com/wled/WLED] if you want more complexity.

    Not super expensive, the ESP32s are around $5-7 and a breadboard/wires are similarly cheap. A multimeter is useful, a bit more expensive but in the 10s of dollars depending on quality.

    Everything is low voltage/current so it's pretty safe.

    Toss in a 3D printer once you're comfortable for a top tier home mad scientist lab.

    Other than that, Google, textbooks from Ana's Archive, AI can all be useful in some situations.

  • source
  • parent
  • hideshow 1 child comment
  • [–] 5 points 4 days ago
    [–] 5 points 4 days ago (3 children)

    Excuse me, sir, this is dull_mens_club, not awesome_mens_club. I'll have to ask you to leave.

    JK! This is awesome! I love recovering e-waste, though usually I just collect the li-ion cells from wayside vapes. Regarding how this thing is driven, I'm continually saddened that display manufacturers don't bother with the tiny amount of extra SRAM needed to support a persistent image.

    For one project of mine in the distant past -- using a device similar to an Alpha Smart -- the display did have its own memory but the board designers couldn't be bothered to run the MISO line from the display to the CPU. So even though I could write to the display memory, I still had to keep a full copy of the rendered graphics in CPU memory, which was significant. All this made worse because the display chip itself supported a reasonable set of manipulations, like scrolling and shifting the display. But alas, none of it could be used effectively.

  • source
  • hideshow 3 child comments
  • [–] [S] 4 points 4 days ago (2 children)

    Chalk another mark towards what were they thinking when they put this wonder together?

    Also I think we really need an electronics community on the lemmyverse. I tried looking for one but found nothing, and just know that I want to share more in this direction

  • source
  • parent
  • hideshow 2 child comments
  • [–] 4 points 4 days ago* (1 child)

    The closest I'm familiar with is !askelectronics@discuss.tchncs.de , but it's more about questions and answers, rather than general discussion. But then again, questions are a good way to start a discussion.

    I will also post some of my stuff to !imadethis@lemmy.zip , but that's for a complete build and not so much about the electronics journey that preceded it.

  • source
  • parent
  • hideshow 1 child comment
  • [–] 4 points 4 days ago (1 child)
  • But can it play Doom?

  • source