Een 4G-draadloze hotspot van $20 ombouwen tot een tekstapparaat
De motivatie
Ik organiseer mijn spullen niet goed. Ik probeer het wel, maar vaak eindig ik met een hoop spullen op mijn bureau. Van diverse AliExpress-bestellingen en projecten waar ik aan werk, tot aan cadeaus en dingen waar ik geen tijd voor had om een plek voor te vinden.
Precies zo is dit project ontstaan. Op een gegeven moment lagen de volgende zaken op mijn bureau:
- Diverse, hopelijk OpenStick-compatibele modellen, namelijk:
- MF800 – degene die ik uiteindelijk heb gebruikt.
- UZ801 – oké formaat, maar geen batterij of voldoende zichtbare GPIO.
- Een model in USB-stick-formaat dat ik heb weggegooid vanwege andere problemen.
- Het Clicks-toetsenbord voor de iPhone 16 Pro Max (een cadeau van mijn neef – jammer dat ik geen passende iPhone heb 🥲).
- De Adafruit SHARP Memory Display-board.
En natuurlijk combineerde mijn "holbewonersbrein" deze drie. Oké, om eerlijk te zijn, ik was waarschijnlijk beïnvloed door mijn kennis van:
- Beepy door sqfmi.
- Playdate.
De modem
Door de mysterieuze wetten van vraag en aanbod, en de magie van toeleveringsketens, kun je op wonderbaarlijke wijze een 4G-modem met wifi, bluetooth, een display, volledig op batterijen werkend en volledig ontgrendeld bemachtigen voor minder dan $20 inclusief verzendkosten. Dit is uiteraard de hoeksteen van ons project.
Er zijn versies met en zonder display. De versie zonder display vervangt het scherm simpelweg door LED's, hoewel de PCB hetzelfde is. Het display is een GC9107-model, maar het ziet er vreselijk uit, dus die heb ik weggegooid.
Linux kan eenvoudig worden geïnstalleerd, aangedreven door het fantastische OpenStick-project. Het standaardapparaat draait op Android, maar adb is direct toegankelijk – en via adb kun je rechtstreeks naar edl gaan en het apparaat herflashen. Zorg er wel voor dat je alle belangrijke partities opslaat. Er zijn ook pinnen op de PCB die je kunt kortsluiten om direct in edl-modus te komen.
Het extraheren van de actieve device tree uit het draaiende Android-systeem was een goudmijn aan informatie, dus doe dat zeker.
Enkele gidsen of links die ik nuttig vond:
- OpenStick GitHub
- openstick.de
- wvthoog.nl blog (denk ik)
- De kernel die ik als startpunt gebruikte: extowerk.com blog
- De GitHub-repo van dit project kan ook helpen.
De echte pijn met OpenStick begint zodra je bij de drivers en device trees komt, maar daar komen we later op terug.
Het toetsenbord
Er is niet veel te zeggen over het Clicks-toetsenbord. Het voelt heel fijn in gebruik; het enige probleem is dat je er behoorlijk wat geld voor moet neerleggen – wat erger wordt omdat je het vervolgens moet doorzagen 😱.
Wat betreft het protocol is het precies wat ik verwachtte op basis van mijn eerdere ervaringen met MFi-apparaten: gewoon een regulier USB-toetsenbord met een extra propriëtair Apple-eindpunt waarmee de iPhone zich kan autoriseren voordat het toetsenbord wordt toegelaten.
Voor ons apparaat betekent dit dat het gewoon een regulier toetsenbord is.
Bij wijze van zijsprong: er is een Clicks-mobiele app voor configuratie/updates/wat dan ook. Het toetsenbord zelf wordt aangedreven door een CH32V203 of iets soortgelijks. Er zou custom code geflasht kunnen worden, maar ik zie momenteel geen reden om dat te doen. In de toekomst zou ik graag een configuratietool willen hebben.
Het display
Ik vind dat het Sharp-display er geweldig uitziet met het hoge contrast (en dat past goed bij mijn gebruiksscenario van een "dom" apparaat dat alleen voor berichten wordt gebruikt). Verder is er, net als bij het toetsenbord, absoluut niets meer over te zeggen.
Het is een display. Je stuurt commando's. Het toont iets.
De adapter PCB
Eén ding werd me snel duidelijk: ik zou een custom PCB moeten toevoegen. Ik wist niet precies wat de MF800 aan boord had, en ik heb de shield-can nooit geopend – maar ik ben er vrij zeker van dat er geen 5V-booster op boord zit.
Vanwege de fysieke afmetingen, waar we later verder op ingaan, zal de USB-connector worden afgeknipt – dus we hebben ook een manier nodig om dat op te lossen. De uiteindelijke PCB regelt de USB-hostmodus-voeding, het schakelen tussen USB-host/device-modus, de voeding van het display en de signaalniveau-conversie van het display.
Ik heb de PCB besteld inclusief assemblage. Omdat tweezijdige assemblage duur is, heb ik een paar compromissen gesloten om alle componenten aan één zijde te passen. De zijde zonder componenten wordt gebruikt voor de soldeerpunten voor de PCB-naar-PCB-verbindingen.
Alle bestanden zijn te vinden op GitHub, maar kort samengevat bestaat de PCB uit:
- TUSB320 – voor het schakelen van de USB-modus.
- SN74LVC8T245 – voor de level shifting.
- MCP1640 – de 5V-booster.
- TPS22917 (één high, één low) – voor het schakelen tussen VBUS/VBAT.
- USB-connector en FPC-connector voor het display.
- Testpads die worden gebruikt om de adapter PCB met de MF800 te verbinden.
Behuizing 1
De MF800 is een stuk groter dan andere OpenSticks, mede door de batterij die erin moet passen en mede door de slordige constructie.
Om in een Clicks-behuizing te passen, kunnen we hem ofwel verticaal plaatsen en eindigen met een enorme misvorming, of we trimmen de PCB om hem horizontaal te laten passen.
Op de PCB lopen de USB-datalijnen van de connector en pads naar de SOC. Bij het plannen van de snede merkte ik op dat de rechterkant de batterijverbinding doorsnijdt, dus dit moet later worden hersteld.
Het snijden
Het snijden van de PCB ging onverwacht goed. Het apparaat startte direct op en alles leek te werken. Blijkbaar liepen er echt geen cruciale lijnen door die delen van de PCB.
De enige dingen die ik niet testte, waren de USB-verbinding en de 4G-modem. Van de modem was ik 99% zeker dat dat geen probleem zou zijn, omdat er geen reden is om daar lijnen voor te routeren. Wat betreft de USB was ik bang dat ik misschien de via had geraakt.
Bedrading 1
Ik besloot de pads van het oude display te hergebruiken. Ik weet niet meer precies waarom – mogelijk omdat mijn initiële idee was om een rigid-flex PCB te gebruiken en deze te solderen zoals het originele display (waar ik direct tegen besloot na het zien van de prijzen).
Achteraf gezien lijkt dit erg dom. Ik had de gelabelde pads naast de niet-gepopuleerde micro-SD-connector moeten gebruiken. Let wel: ik heb nooit gecontroleerd of deze gedeeld werden met de SIM-kaart.
Aangezien ik het apparaat kon opstarten en de originele Android-device trees had, kon ik extraheren welke pinnen werden gebruikt voor de display SPI. Ik heb deze vervolgens getest met gpioset om er zeker van te zijn dat ik correct zat. Hetzelfde geldt voor de voedingen (hoewel ik die testte door ze uit te schakelen in de device tree en opnieuw op te starten) en de massa's.
Het eerste dat ik bedradde en controleerde waren de voedingen, gevolgd door de USB. Dit is omdat ik dit als een geïsoleerde eenheid kon testen. Ik was hier ook sceptischer over omdat het zowel eigen schakelingen als die onbetrouwbare via betrof.
Natuurlijk werkte het in eerste instantie niet. Na het controleren van de pads en het constateren dat alle voltages in orde waren, zag ik in de dmesg van mijn laptop dat hij probeerde te enumereren – wat betekent dat er iets gebeurde.
Ik merkte ook dat er "high-speed" stond. Dit overviel me een beetje; ik verwachtte niet dat er "high-speed" USB zou worden gebruikt. Mijn eerste gedachte was dat de draden te lang waren. Maar voordat ik ze inkortte, probeerde ik een snelle oplossing – ze strakker om elkaar heen draaien – en het werkte 😲!
Direct na dit succes probeerde ik de andere richting werkend te krijgen. Dit kostte wat tijd. Het blijkt dat niet alle AliExpress-adapters de CC-lijnen correct bedraden. Het toetsenbord werkte overigens direct; het enige probleem was dat ik bang was om dit als eerste te testen, voor het geval er iets onjuist bedraad was.
Bedrading 2
Voordat ik de SPI-lijnen naar het display bedradde, wilde ik controleren of ik mijn device tree correct had geconfigureerd. Ik wist dat de pads correct waren door de eerdere tests, maar er kan hier veel misgaan.
spi@78b9000 {
compatible = "qcom,spi-qup-v2.2.1";
reg = <0x78b9000 0x500>;
interrupts = <0x00 0x63 0x04>;
clocks = <0x13 0x41 0x13 0x36>;
clock-names = "core", "iface";
dmas = <0x6d 0x0c 0x6d 0x0d>;
dma-names = "tx", "rx";
pinctrl-names = "default", "sleep";
pinctrl-0 = <0x84>;
pinctrl-1 = <0x85>;
#address-cells = <0x01>;
#size-cells = <0x00>;
status = "okay";
spidev@0 {
//compatible = "linux,spidev";
compatible = "rohm,dh2228fv";
reg = <0>;
spi-max-frequency = <16000000>;
spi-cs-high;
};
};
Bijvoorbeeld: Qualcomm-drivers zijn onbetrouwbaar en de uitgeschakelde compatible zal de spidev niet daadwerkelijk exporteren, dus we omzeilen dit met de dh2228fv compatibiliteit.
Met behulp van de spi-pipe utility in een loop kon ik voltageveranderingen meten op de MOSI- en CLK-lijnen – wat voor mij genoeg was om te concluderen dat er iets gebeurde. Ik zou dit natuurlijk liever met een scope of een logic analyzer doen, maar die had ik niet bij de hand.
aangemoedigd door het grote succes van de voedingen en USB, sloot ik onvoorzichtig het display aan op de PCB (terwijl het apparaat aan stond 🤦). Er gebeurde direct iets met het display en ik was er zeker van dat ik het kapot had gemaakt. Gelukkig was er niets aan de hand en het display was in orde. (Dit gebeurt eigenlijk elke keer als ik het apparaat aanzet. Ik weet nog niet of ik me zorgen moet maken 😬.)
Nadat ik mezelf had gerustgesteld dat er niets ergs was gebeurd en dat er niets rookte of oververhit raakte, ging ik verder met het proberen het display werkend te krijgen. Na een uur worstelen met AI-gegenereerde Python-code was ik nergens. Er waren meerdere mogelijke faalpunten: level converter, slechte routing, contacten, etc.
Het blijkt, zoals vaak voorkomt, dat de Qualcomm-driver de CS (Chip Select) niet goed (of correct) afhandelt. In ieder geval probeerde ik opnieuw met hetzelfde testscript, maar ditmaal door de CS-pin handmatig te schakelen (via libgpiod) – en dat werkte daadwerkelijk.
Herbedrading
Ik was direct teleurgesteld in alles. Van de "elektrische" draad die ik gebruikte om de voedingen te verbinden tot de onbetrouwbare dunne magneetdraad die ik voor de signalen gebruikte, en de getwiste massa die ik om MOSI en CLK wikkelde (wat niets leek te doen) – dus ik besloot alles nogmaals opnieuw te bedraden.
Deze keer gebruikte ik magneetdraad voor zowel de signalen als de voeding, maar deze was een stuk dikker en behield zijn vorm na het buigen. Ik heb de USB-datalijnen niet opnieuw bedraad, aangezien de pads op de hoofdkaart er erg onbetrouwbaar uitzien.
Display kernel driver
Alle voorgaande tests werden gedaan met simpele Python-scripts, maar de juiste weg vooruit is met een kernel driver. Er zijn vrij veel drivers beschikbaar, maar ik heb gekozen voor de sharp-drm-driver van ardangelo. Mijn redenen voor het willen van een DRM-driver zijn als volgt:
- Het stelt me in staat om een directe output te gebruiken, bijvoorbeeld voor het afspelen van video's (zoals met
mpv). - Dit zou ook met een framebuffer werken, maar dat is in 2026 onbetrouwbaar.
- Ik kan er gemakkelijk X/wayland op draaien.
- Misschien probeer ik voor de grap een desktopomgeving te draaien.
- De driver uitbreiden om partiële updates te doen.
- Nog steeds de framebuffer-interface behouden.
Dit werkte bijna onmiddellijk. Ik moest wel wat experimenteren met de CS en de active-high standaardwaarde. Voor waarschijnlijk de eerste keer in mijn leven had ik geen problemen met het compileren van de kernelmodule en het uitvoeren ervan. De mysterieuze kernel die ik gebruikte was een 6.12.1-msm8916 met ingeschakelde modules. Er was een .config bestand aanwezig, die ik heb overgenomen.
Vervolgens downloadde ik de mainline Linux 6.12.1 kernel in de hoop dat er geen (of geen significante) wijzigingen waren. Dit bleek voldoende te zijn, en na een paar kleine patches aan de driver werkte het ding gewoon.
Hieronder staat hoe de device tree er uiteindelijk uitzag. Let op dat de CS-logica door de display-driver wordt afgehandeld.
spi@78b9000 {
compatible = "qcom,spi-qup-v2.2.1";
reg = <0x78b9000 0x500>;
interrupts = <0x00 0x63 0x04>;
clocks = <0x13 0x41 0x13 0x36>;
clock-names = "core", "iface";
dmas = <0x6d 0x0c 0x6d 0x0d>;
dma-names = "tx", "rx";
pinctrl-names = "default", "sleep";
pinctrl-0 = <0x84>;
pinctrl-1 = <0x85>;
#address-cells = <0x01>;
#size-cells = <0x00>;
status = "okay";
sharp_drm@0 {
compatible = "sharp-drm";
reg = <0>;
spi-max-frequency = <4000000>;
cs-gpios = <0x49 18 1>;
};
};
De standaard Linux console login prompt verscheen.
Het probleem is nu dat de driver de pixelkleur simpelweg afrondt naar zwart onder een bepaalde waarde, en naar wit daarboven (of omgekeerd, afhankelijk van de parameters). Voor tekst (of als je de visuals zelf in je app doet) werkt dit geweldig – maar voor een algemene oplossing waarbij je video's wilt afspelen of afbeeldingen wilt tonen, ziet dit er vreselijk uit. De oplossing hiervoor is om dithering aan de driver toe te voegen.
Ik heb een custom sys-waarde toegevoegd waarmee de gebruiker dithering kan inschakelen en kan kiezen welk algoritme hij wil:
- Atkinson voor video.
- Floyd-Steinberg voor stilstaande beelden.
Alle initiële functionaliteit is intact gelaten. Meer informatie hierover, of over de DRM-driver patches, is te vinden in de GitHub-repo van het project.
Behuizing 2
Nu alles op mijn tafel lag en werkte, en vooral omdat de afmetingen waren vastgesteld en gemeten konden worden, begon ik met het modelleren van de bijna-definitieve behuizing die ik hopelijk in de toetsenbordbehuizing kan plaatsen zonder bang te zijn iets kapot te maken.
Dit was een beetje onzeker, aangezien Apple geen afmetingen geeft van hoe ver de USB-C-connector in de iPhone zit – maar ik heb dit opgelost door standaard Apple USB-C-kabels te meten (die strak aansluiten op het apparaat) en vanaf daar te interpoleren.
Deze print was nog niet bijzonder nuttig, maar diende zijn doel om te bevestigen dat de USB-connector afmetingen (onder andere) correct waren gemeten. Ook printten twee zijwanden niet correct en terwijl ik modelleerde (dit was voordat ik degene ontving die in de foto's zichtbaar is), had ik geen display (behalve degene die vastzat aan de devkit PCB) om van te modelleren, dus de kap ontbreekt.
Dit is wat uiteindelijk de definitieve behuizing werd. Bijna alles paste correct. Eerst maakte ik een snelle test die met Kapton-tape bij elkaar werd gehouden.
Op dit punt verloor ik door persoonlijke omstandigheden tijdelijk toegang tot een groot deel van mijn gereedschap (vooral de 3D-printer). Omdat ik geen keuze had, besloot ik de behuizing met hittebestendige lijm (hot glue) aan elkaar te lijmen in plaats van een definitieve versie te printen die in elkaar klikt.
Ik was ook de aan/uit-knop vergeten, die ondanks dat hij op de PCB zit en werkt, geen uitsparing in de behuizing en geen drukknop kreeg. Dit is opgelost met een klein gaatje en een pinnetje. Erg lelijk, maar het werkt.
Nu het grote moment: de toetsenbordbehuizing inkorten zonder gereedschap. Dat ging ongeveer zo goed als je zou verwachten. Ik heb er wel voor gezorgd dat ik minder wegsneed dan nodig was, zodat ik het kan schuren en er mooi uit kan laten zien zodra ik mijn spullen terug heb. Realistisch gezien ziet het er niet zo slecht uit, maar de randen kunnen wel schoongemaakt worden. De uitstulping van de lijm in de behuizing is een groter probleem. Met de magie van foto's van bovenaf heb ik het grootste deel hiervan voor je verborgen.
Eindresultaat
Ik ben vergeten foto's te maken tijdens de assemblage. Het is precies hetzelfde als voorheen, plus een 4G flex PCB-antenne die ik aan de PCB heb gesoldeerd en onder het display in het bovenste deel van de behuizing heb gelijmd. Er zit nu ook een mini-SIM in de slot.
Batterijconfiguratie
De Android-device trees die ik van het apparaat kopieerde, zijn vooraf gedefinieerd met de batterij- en laderconfiguraties. Deze zijn geavanceerder dan die van de mainline Linux die ik gebruik. Toch verwachtte ik dat het vrij eenvoudig zou zijn om iets bruikbaars werkend te krijgen. De grote vragen waren hier de batterij-informatie en het stroomverbruik van mijn adapter PCB.
Wat de driver op gebruikersniveau echter bood, was alleen de batterijspanning in uV en een vlag of hij aan het opladen was of niet. De eigenlijke batterijlogica zal dus worden overgelaten aan mijn app, aangezien ik niet van plan ben de driver aan te passen alleen maar voor de batterijpercentage-waarde.
Het opladen lijkt prima te werken. Het werkt ook via de USB-passthrough-poort van het toetsenbord, maar helaas alleen wanneer het apparaat is opgestart.
Wat er nog ontbreekt
Slaapmodus is momenteel het grootste onopgeloste probleem. De hoofdreden is het gebrek aan dagelijkse tests van het apparaat, vooral met de modem ingeschakeld – en het gebrek aan een handige aan/uit-knop. Ik ben van plan dit in de nabije toekomst aan te pakken terwijl ik het apparaat begin te gebruiken voor mijn berichtenverkeer. Ik zou in ieder geval graag een snelle bootup/shutdown willen realiseren.
Extra invoedmethoden, zoals een touchscreen of een scrolwiel, zouden waarschijnlijk de beste aanvullende functies zijn. Touch kan vooral met zeer weinig ruimte worden gerealiseerd.
Daarna volgen geluid of vibratie. Zelfs een kleine speaker van ongeveer 8kHz. Er is voldoende ruimte op de PCB voor een versterker, evenals ruimte voor de speaker in de behuizing.
De minder mooie kanten
Voornamelijk het lijmprobleem en de ontbrekende aan/uit-knop, waarvoor beide een nieuwe print nodig is, plus de grillige randen van de toetsenbordbehuizing die moeten worden weggeschuurd.
De batterij wordt vastgehouden door een stukje tape, omdat hij anders uitvalt als hij niet in de behuizing zit. Dit is momenteel geen prioriteit.
De grote onderste rand die de hoogte van de behuizing bepaalt zou ook ingekort kunnen worden, maar dat zou vereisen dat ik een andere batterij zoek met dezelfde 3-pins connector, enzovoort.
De helper PCB schuift omhoog in zijn slot omdat de display FPC-kabel er licht aan trekt en ik vergat tabbladen in de behuizingskap toe te voegen om hem op zijn plaats te houden. Niet ideaal – maar het is alleen een probleem bij het schuiven van de behuizing in de case.
Ten slotte wordt een klein deel van het display bedekt door de behuizing. Ongeveer 1-2 pixels aan alle randen. Dit zal ook worden opgelost met de volgende print.
Al overal beschouwd ben ik erg blij met het apparaat, maar wat meer werk zou wonderen doen voor het uiterlijk. Op foto's ziet het er prima uit – in het echt laat het een beetje te wensen over.
Als je geïnteresseerd bent in het repliceren hiervan of het doen van iets soortgelijks, kun je de meeste zaken vinden in de GitHub-repo van het project.
Toekomst
Ik heb software bewust weggelaten in dit bericht, aangezien dat pas tijdens het gebruik zal worden uitgesleten, en ik geen fan ben van het publiceren van projecten die ik nog niet heb afgerond maar ook niet heb opgegeven.
Tijdens het schrijven hiervan heb ik al een memory leak gevonden in de originele display-driver. Ik heb ook een patch verzonden die dit oplost. Dingen als dit kunnen niet gemakkelijk worden gevonden zonder daadwerkelijke praktijktests.
De tekst is volledig geschreven door mij, een mens. Je kunt me bereiken via veggieprivacy8y op icloud dot com
Groetjes,