1
submitted 1 day ago* (last edited 1 day ago) by [M] to c/electronics_bench@feddit.uk
 
 

DeviceI had this cheap chinese radio MIABOO M20, which had a ton of cool features: portable, bluetooth, aux, and radio. But the speaker wasn't great (mono), and the battery didn't last very long

So I took it apart (beer, force, pliers) with great effort since there's not a single screwhole anywhere on it as it was all glued together

Components:

  • Speaker
  • Battery (no markings, but big and blue cyclinder, looks like a 18650 Li-ion)
  • Main board
    • 2-pin JST speaker connection and another 2-pin JST (2.4mm pitch)
    • SD card slot
    • MCU, unknown 18140
    • 18-pin FPC connector to the LED board
  • LED board
    • 54 Lights:
      • 4x5 grid of different coloured LEDs
      • 4 digits with colon and a dot
      • 4 bottom lights
    • 4 buttons
    • 18-pin FPC connector

LED Board

Front

7 sets of something that appear to feed into everything else. My guess with the 54 LED lights is that it's probably a 7x8 grid, and this is the "7" part.

Some markings on the board:

  • Q are where the transistors are
  • 2A I think are fuses?
  • 104 and 102 are common resistors, with R names etched into the board
  • O are the test contacts on the board -- little circles of metal for me to test with a multimeter

     [O2]             [O14]

       Q5    Q4      Q7     Q6     Q8    Q10     Q9
    | 2A|  | 2A|  | 2A|  | 2A|  | 2A|  | 2A|  | 2A|
    |104|  |104|  |104|  |104|  |104|  |104|  |104|
    |102|  |102|  |102|  |102|  |102|  |102|  |102|
   R41 [O4] [O5]       [O7] R42  [O8]   [O9]   [O10]
   R47      R40   [O6]      R50   R44  R46    R45         [O12]
            R48     R43    [O15]  R51  R52    R53
  [O1] [O3]         R49                       [O18]
                                                       [O13]
                                                
      |  02  04  06  08 10  12  14  16  18|     [O16]         
      |01  03  05  07 09  11  13  15  17  | [O17]          [O11]
      | F P C - C O N N E C T O R - P I N | 

So the 18 pins of the FFC cable (flat flexible cable?) are shown in the above FPC connector.

I used a multimeter in continuity mode to trace each pin to each of the contacts, and wrote down when it beeped. I then changed the numbering of the O contacts in the above schema so that each O number corresponds to an FPC pin (e.g. FPC Pin 18 traces a path to O18 on the board)

Back

  • Back has all the lights, see the video

Investigating the Board

So I hooked the 18-pin 0.5mm pitch ribbon cable into a 0.5mm FPC breakout board (5 quid, ebay), and then stuck each pin into a GPIO on my Raspberry Pi for testing.

Charlieplexing

I've talked about charlieplexing in a previous post, but the main gist is that pairs of pins, 1 high and 1 low is what drives a single LED. I assumed this was the case with this LED board, but when I tested every single pair against every other pair, I got nothing. Not a blip.

Broken lights?

At first I thought I'd somehow botched the board when I was taking it apart, and was worried that the LEDs were simply broken. I didn't know what to do, but after reading a bit I found out that a multimeter is the perfect tool for this because it has something known as Diode Mode which lets you literally put one probe on side of a diode, and another probe on the other side, and then it shoots a little tiny current through it.

At first I thought this was just mumbo jumbo, but thought I would give it a go, and - OH MY GOD IT WORKS! - each LED I tested this way sparkled light at me.

So I knew the board was working! I just have to figure out how to use the pins to get the signals down there.

Actual broken lights

Okay, so in my excitement in this discovery of Diode Mode on a multimeter, I then packed it away and got out my USB-to-TTL chip which also has VCC and Ground contacts.

For some reason I thought I would just double check an LED again using the 5v from the USB and the Ground on an LED, and.... nothing happened. Wondering what happened, I tried the 3v3 from the USB and the Ground on the LED, and.... (-PEW-) I smelled something burning.

I saw a little puff of smoke rising out of a blown LED, and wondering what happened, I stupidly did again on another LED, and another puff of smoking.

Oh dear

So what happened? Well it turns out that circuits have resistors and fuses for a reason, and that if you run an unrestricted current across a sensitive diode, it's probably going to blow up.

🫢

The reason why the multimeter is the better tool here, is because it gives a teeny tiny 1-2mA between the probes, and a USB can give 1000 times amount. Yeah....

Live and learn.

:

Multiplexing the PinsSo charlieplexing from the previous section did not work, meaning that the board probably isn't completely GPIO driven, but might actually have some dedicated pins for power and ground.

Which one's though?

Not really knowing what I was doing, I connected the LED board back to the main board via the FFC ribbon and powered on the main board. A few lights lit up (yes, this would have been an easier test earlier with the lights), and then I started measuring the voltage difference between each of the O pads.

spoiler O Pad voltage matrix

O\O 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
1
2 2.9
3 -0.3 -3.2
4 .14-.17 -2.8 .4
5 .14-.17 -2.8 .4 0
6 .14-.17 -2.8 .4 0 -.002
7 .14-.17 -2.8 .4 0 0 .001
8 .14-.17 -2.8 .4 0 0 .001 -.001
9 .14-.17 -2.8 .4 0 0 .001 -.001 0
10 .14-.17 -2.8 .4 0 0 .001 -.001 0 -.001
11 0.05 -1.2 1.9 1.5 .7 .3 -.05 .3 .3 .3
12 .8-1.1 -1.7-2.0 1.1-1.4 .3-.6 .7-1.0 .3-.7 .4-.6 .7-1.0 .7-1.1 .3-.6 -.3-0
13 .45 -2.4 1.1 .7 .7 .3 .7 .7 .7 .6 0 .4-.7
14 .97 -1.5 1.6 .8 .4 .4 .4 .4 .4 .4 .65 .5-.8 .3
15 .49 -2.4 .3 .3 .3 -.04 .3 .3 .3 .3 .06 0-.4 .74 -.8
16 .5 -2.3 .8 .02 .01 .4 .4 .4 .02 .4 -.3 .1-.4 -.3 -.3 -.8
17 .4 -2.4 .3 -.05 .7 .3 .7 .7 .7 .3 0 .4-.6 0 0 0 .8
18 .3-.7 -2.1--2.5 .6-1.0 -.04 -.05 -.05 -.05 -.05 -.05 -0.04 -.77 .3-.6 -.7 -.8 -.4 .4 0
:::

Great..... what does any of this tell us?

Well there's a pretty high chance that the 2.9V that I got when I put the red probe on 1 and the black probe on 2, gave the highest the positive voltage across the board is the main power line (Pin 1).

There's also a pretty high chance that the -3.2V I got when I put the red probe on 2 and the black probe on 3 is the main ground line (Pin 2). So I think the board operates at roughly 3.3 V, and the first two pins are power pins.

Okay, and does this work?

I moved Pins 1 and 2 in my initial setup on the Raspberry Pi to the 3v3 Pin and to the ground, and yep, things started lighting up!

I assumed that the rest of the lights were charlieplexed, and so I did pair testing on the other 16 pins, and I started getting individual LED's to light up!

Using this information I started to write a little driver and will start using this for my halloween projects!

2
 
 

cross-posted from: https://feddit.uk/post/56576102

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)