Zelfgemaakte FS-controller met Arduino Micro
Met de juiste knoppen en hendels voelt het vliegen met Microsoft Flight Simulator 2020 nog realistischer aan. Met een Arduino Micro bouw je snel een geschikte usbcontroller waarmee je een trimwiel, een hendel voor de kleppen en een voor het landingsgestel van een Cessna aan je computer koppelt.
Als je de besturing van een vliegtuigcockpit met fysieke hendels, draaiknoppen en schakelaars nabouwt, haal je de virtuele ervaring van Flight Simulator een beetje dichter naar de werkelijkheid. De simulatie voelt dan authentieker aan en de vliegervaring wordt intenser. Aangezien elke virtueel vliegende piloot zijn eigen favoriete vliegtuig heeft, zal iedereen zijn eigen idee hebben hoe de perfecte besturing er moet uitzien – en de beste manier om dat te realiseren is door zelf je eigen controller te maken. Met een Arduino Micro is dat redelijk eenvoudig en goedkoop. Als de Arduino door het systeem als toetsenbord wordt gezien, wordt er elke keer wanneer er een schakelaar op de zelfgemaakte controller wordt omgeswitcht een toetsaanslag of toetsencombinatie naar je computer gestuurd. Moderne besturingssystemen hebben voor dergelijke Human Interface Devices (HID) geen speciale drivers nodig en bij Flight Simulator en andere games kun je eenvoudig bij de instellingen aangeven wat er moet gebeuren als de Arduino specifieke toetsenaanslagen doorgeeft.
Een microcontroller kan zich alleen als een HID voordoen als hij de usbcommunicatie zelf afhandelt. Als er, zoals bij de Arduino Uno of Nano, voor usb een extra chip op het printplaatje zit, kan er geen HID worden geemuleerd. Op de Arduino Micro, Leonardo en op de onofficiële 3,3Vkloon Pro Micro zit een Atmel MEGA 32U4 vast gesoldeerd die als HID kan werken. We hebben dan ook een Arduino Micro gebruikt waar 16 GPIOpinnen beschikbaar zijn. Dat was voor ons nog niet genoeg, dus hebben we een MCP23017 aangesloten via I2C, wat 16 extra GPIOpinnen oplevert (voor I2C gaan er dan wel twee GPIOpinnen af op de Arduino).
Als je het leuk vindt om onze controller na te bouwen, vind je bij de link aan het eind van dit artikel sjablonen en voorbeelden. Het is niet mogelijk om in dit artikel alle individuele stappen te geven. Aangezien je werkruimte waarschijnlijk anders is uitgerust dan de onze, zul je je eigen manieren moeten vinden om de frontjes uit te knippen en alles te bevestigen. Bovendien is het project sowieso leuker als je onze controller als voorbeeld ziet en je controller aan je eigen wensen en voorkeuren aanpast. Misschien is je favoriete vliegtuig helemaal geen Cessna en moeten de bedieningselementen er ook anders uitzien.
Onze controller maakt gebruik van een draaiencoder. Die registreert wanneer er wordt gedraaid aan het trimwiel, waarmee je de neus van het vliegtuig omhoog of omlaag richt. De schakeling kun je vergelijken met een draaiknop waarmee je het volume harder of zachter zet. De overige bedieningselementen zijn simpele schakelaars, met deels een ander uiterlijk. De hendel voor het uitklappen van het landingsgestel bestaat bijvoorbeeld uit een schuifschakelaar met een lange hendel waar we twee schijven uit kunststof aan bevestigd hebben die we met een 3Dprinter hebben gemaakt. Het resultaat lijkt behoorlijk op de hendel voor het landingsgestel van een Cessna.
Voor de landingskleppen heeft een Cessna een hendel die je in vier posities kunt zetten. Daarvoor hebben we een draaischakelaar ingebouwd die we 90° gedraaid hebben. Daar hebben we ook weer een hendel bevestigd, die een bijzonder kenmerk heeft: om te vermijden dat het landingsgestel in één keer helemaal wordt uitgeklapt, wordt de hendel in trappen bediend. Als je hem omlaag duwt, zet je hem in de eerste positie. Voor een sterkere remwerking kan de piloot de hendel alleen naar de volgende stand zetten, door de hendel eerst naar rechts duwen.
Een trekveer die de hendel naar de as toe trekt zorgt er daarbij voor dat de schakelaar goed in de beveiligde stand blijft zitten. De hendel blijft in een positie hangen dankzij een uitsnede in het gat van de frontplaat. De vluchtsimulatorpiloot moet de hendel dus eerst naar rechts tegen de veerkracht in drukken om stap 2 of 3 te bereiken.
PRINTPLAAT-RECYCLING
De twee draden voor de I2Caansluiting op de MCP23017 en de pulldownweerstanden op alle GPIOpinnen zijn eenvoudig te monteren op een rasterprintplaat. We hadden enige tijd geleden voor een ander project echter boards laten maken om een MCP23017 en verschillende weerstanden op te plaatsen. Omdat je dan altijd een hele stapel boards van een printplaatfabrikant krijgt, lag een aantal ongebruikte platen nog op de redactie. Daar hebben we de Arduino Micro op gesoldeerd. De bestanden voor de layout van de printplaat vind je bij de link op de laatste pagina van dit artikel. Omdat de printplaat echter maar niet helemaal voor dit doel ontworpen is, loont het de moeite niet om die zo ongewijzigd te laten maken. Als je de productie van de controller verder wilt uitbreiden, kun je het KiCADbestand als basis gebruiken.
Met uitzondering van de draaiencoder (de schuifcontacten zijn bijzonder gevoelig voor het zogenaamde bouncen), hebben we alle schakelaars softwarematig gedebouncet, waardoor we geen condensators hoefden te gebruiken. Als je de voorkeur geeft aan een hardwareoplossing, kun je de condensator en weerstandswaarden van onze debouncing voor de Raspberry Pi als basis gebruiken [1].
MECHANICA
Het trimwiel, de landingsgestelhendel en de selectieschakelaar voor de kleppen hebben we elk op een apart frontpaneel van 2 millimeter dik aluminium geschroefd.
Daarmee hebben we modules gemaakt die in willekeurige volgorde kunnen worden gerangschikt en die je indien nodig kunt uitbreiden. Op een vierde, iets bredere aluminium plaat, hebben we tien extra tuimelschakelaars aangebracht die we aan andere functies van het vliegtuig hebben gekoppeld. Zes van de schakelaars zijn redelijk compact en passen in twee rijen van drie. Daaronder hebben we drie grotere tuimelschakelaars geplaatst die we uit oude apparaten hebben gehaald. De wat antieke schakelaars bieden een zeer bevredigend schakelgevoel, omdat ze wat zwaarder zijn en stevig vastklikken als je ze omswitcht. Het hergebruiken van schakelaars bespaart niet alleen geld, maar de verschillende drukpunten zorgen ook voor een haptische variatie en dus meer plezier. Om dezelfde redenen hebben we geen tuimelschakelaar gebruikt als de tiende schakelaar, maar een drukknop, zij het met een hendel om naar beneden te drukken in plaats van een knop om in te drukken. Daarmee lijkt hij wel op de andere schakelaars, maar voelt hij wel anders aan.
Sommige van de schakelaars zijn anders geïnstalleerd dan in hun datasheets gespecificeerd. Zo zitten de draaiencoders en draaischakelaars beiden horizontaal achter het front. De as steekt niet zoals gebruikelijk uit de voorkant van de behuizing, maar de piloot bedient ze via een gekoppelde schijf of hendel. De hendel voor het landingsgestel duwt bijvoorbeeld een schuifschakelaar naar de eindposities. Omdat de hendel langs een cirkelvormige baan beweegt en de schuifschakelaar lineair beweegt, zit er een schoentje over de pen van de schuifschakelaar die de speling opvangt. Bij de draaias van de hendel en de schoen vormen schroeven de draaias.
Om de componenten op zo’n ongewone manier te bevestigen, hebben we verschillende componenten ontworpen en met een 3Dprinter geprint. De CADbestanden van de ontwerpen en kantenklare STL’s voor de 3Dprinter vind je via de link op de laatste pagina. De onderdelen die we gebruikt hebben hadden we nog in een knutseldoos liggen, dus kan het zijn dat je de ontwerpen voor je eigen controller moet aanpassen aan de onderdelen die je hebt. Een inleiding tot OpenSCAD staat in [2], voor een blok op het trimwiel hebben we ook FreeCAD gebruikt.
PROGRAMMACODE
De programmacode voor de firmware voor de controller vind je in de GitHubrepository bij de link. De architectuur is standaard Arduinokost: in setup() configureert de code de vele GPIO’s voor de schakelaars als ingang. De loop()functie pollt de schakelaars en slaat hun gemelde positie tijdelijk op om softwarematig te kunnen debouncen. Door het bouncen is het niet de moeite waard om interrupts te gebruiken. Bovendien heeft de Arduino weinig te berekenen.
De wirebibliotheek verzorgt de communicatie met de MCP23017 via I2C (zie de link op de volgende pagina). De functie i2cDigitalRead (unsigned short pinNo) in i2cMCP.cpp filtert aparte bits uit de bytes die de MCP23017 voor zijn ingangen levert. De code in de loop() in main.cpp gebruikt i2cDigitalRead() en digitalRead() voor de schakelaars die rechtstreeks zijn aangesloten op de Arduino in hetzelfde formaat.
DEBOUNCING VIA SOFTWARE
We hebben geen weerstanden en condensatoren gebruikt voor het debouncen van de schakelaars, maar doen dat met de software. Ter herinnering: bij het bouncen schakelt een schakelaar niet slechts een keer om, maar wisselt snel achter elkaar tussen hoge en lage niveaus tot een stabiel niveau is bereikt, meestal na maximaal 250 milliseconden. Omdat de code de schakelaars om de 25 milliseconden afleest, registreert het bij het schakelen wilde fluctueringen, die zich na een tijdje stabiliseren. De software negeert dat tot er acht keer dezelfde waarde wordt gelezen. Pas dan gaat de software ervan uit dat een schakelaar omgezet is. Dat zorgt voor een vertraging van ongeveer 1/4 seconde – wat snel genoeg is om het te laten lijken alsof de schakelaar direct reageert.
Voor het wachten op stabiele niveaus bevat de code naast de positie ook een historybyte voor elke schakelaar. In C++ ziet dat er voor de eerste schakelaar zo uit:
unsigned char switchA0History = 0; bool switchA0State = false;
De functie debounce() zorgt voor het protocol. Om ervoor te zorgen dat die met elke schakelaar werkt, krijgt hij een pointer naar een historybyte en een pointer naar de status mee, en de zojuist gelezen waarde (de uitvoer van i2cDigitalRead() of digitalRead()). De historybyte gebruikt de functie om 8 waarden als enkele bits op te slaan. Daarom verschuift de functie met een shiftoperatie (<< 1) eerst alle bits een positie naar links. Het oudste bit vervalt en bit 0 is dan een 0. Op die positie (bit 0) slaat de functie dan de gelezen waarde op. C werkt voor booleaanse variabelen intern met integer 0 of 1 weer. Als dat wordt omgezet naar een byte, komen alleen de waarden 0b0000000000 en 0b00000001 voor. Bit 1 tot en met 7 van die byte zijn dus altijd 0. Als de verschoven history wordt berekend met een OR | op bitniveau met de Boolean, kunnen de bits 1 tot en met 7 niet veranderen. Maar omdat de verschoven history gegarandeerd een 0 op positie 0 heeft, schrijft de OR daar alleen de 0 of de 1 uit de Boolean weg.
Met een protocol dat je op die manier toepast, is het eenvoudig om op eenduidige schakelwijzigingen te