Een asynchrone unidirectionele datalink van PC naar µC
Artikel over een datalink via 1 draad van LPT naar µC (PIC). Bij dit project is het niet belangrijk of de PIC een USART poort heeft.
Inleiding
Min of meer door toeval heb ik een schakeling gebouwd rond een PIC16F628 microcontroller (µC). Deze schakeling is het 'hart' van een kerstverlichting, waarbij ieder lampje door de µC afzonderlijk aan en uit kan worden gezet. Het leuke is dat er na deze 14 gloeilampjes aangestuurd door de µC nog één poort vrij blijft, die alleen als input kan fungeren. Nu is het natuurlijk verspilling om een poort onbenut te laten dus ik bedacht een wat uitdagender projectje dan een µC die wat gloeilampjes aanstuurt.
Mijn doelstelling van dit project is de kerstverlichting geheel instelbaar te maken (de volgorde waarin lampjes aan- en uitgaan) vanaf de PC, met als randvoorwaarde dat er voor de verbinding slechts één input beschikbaar is op de µC. De data moet in EEPROM of RAM geschreven kunnen worden. RAM om snel te testen zonder EEPROM write cycles te verspillen en EEPROM om een mooi patroon uiteindelijk vast op te slaan in de µC.
In dit artikel zal wat informatie over de hardware en de software (aan µC en aan PC kant) van de verbinding worden gegeven, zodat iemand met wat ervaring op het gebied van µC's zijn/haar projecten ook via één enkel draadje met de PC kan verbinden. Aan de software kant zal niet het hele programma behandeld worden maar slechts enkele fundamentele stukjes. Er zal niet worden ingegaan op het schrijven naar het EEPROM of het uitlezen van de data. Het lijkt me dat dit wel voor zich spreekt.
Copyright © 2004 Tobi Vollebregt
E-mail: (mijn volledige naam zoals hierboven, aan elkaar)@hotmail.com
De hardware
Om iets kunnen te overzenden moet de hardware natuurlijk in orde zijn. Dus met het nodige soldeerwerk wordt de schakeling uitgebreid met een inverterend versterkertje om een parallelle poort signaal van 3,3 naar 5 Volt op te krikken.
Ook een LED wordt toegevoegd om eenvoudig te kunnen zien of er inderdaad een elektrische verbinding is. Met behulp van het programma ParPort, bedoeld om de parallelle poort aan te sturen en/of te monitoren, wordt met succes getest dat de verbinding in orde is: een 1 op de poort laat de LED branden, bij een 0 op de poort staat hij uit. Meten van het signaal op de input pin van de µC laat zien dat dit ook in orde is. Verbazingwekkend eenvoudig eigenlijk om zo de parallelle poort te gebruiken.
Als spanningsbron van de gehele schakeling wordt een PC voeding gebruikt. In het begin een losstaande, maar vanwege het gedoe is de schakeling later gewoon met mijn PC voeding verbonden (dus als de PC aanstaat is de kerstverlichting aan!).
Het stuk aan de RA0:3, 6 en RB0:7 is 13 keer uitgevoerd - één keer voor ieder lampje.
Zoals te zien wordt er geen kristal gebruikt maar een externe weerstand aan OSC1. Er wordt een 68 kΩ weerstand gekozen, deze geeft de µC een kloksnelheid van zo'n 2,45 MHz.
De RA4 pin is op een andere manier met de transistor verbonden dan de rest omdat dit (na uren fout zoeken) een Open Drain output bleek te zijn. Deze kan dus alleen maar wél of niet naar ground trekken maar kan nooit zelf +5 leveren.
Nu de hardware blijkt te werken kunnen we overgaan op de softwarekant van het verhaal, maar niet voordat we de volledige schakeling hebben gezien:
De µC software
Er is slechts één input vrij dus dit betekent een verbinding zonder clocksignaal en zonder feedback van de µC. Omdat er geen clocksignaal is wordt gekozen gekozen voor een soort morse signaal. Kort hoog (aan de parallelle poort) is 0, lang hoog is 1. Bij de input van de PIC moet hoog als laag gelezen worden vanwege de inverterende versterker. De input is dus eigenlijk een active low input in dit geval.
Om een verbinding op te zetten zendt de PC eerst twee start/kalibratie bits. De eerste bit is kort (0) en de tweede lang (1). Dit is een vast gegeven zodat de µC aan de hand hiervan de grens (in tijd) tussen een lang en een kort signaal kan bepalen. Om deze timing te doen wordt TMR1 gebruikt. De timing gebeurd steeds met een lus die er als volgt uitziet. Van tevoren wordt TMR1 gecleared en ge-enabled.
CalibrateShortLoop
; Raise Error1 if TMR1 overflows (time-out).
BTFSC PIR1, TMR1IF
GOTO Error1
; Loop if RA5 is low.
BTFSS PORTA, 5
GOTO CalibrateShortLoop
Na de lus wordt de waarde in TMR1 uitgelezen. Zoals te zien is wordt bij elke fout gejumpt naar een ErrorN label, waar het getal N op PORTA wordt gezet, zodat de fout af te lezen is aan de aan/uit stand van de lampjes (met 14 lampjes kan je toch mooi 214 errorcodes weergeven :-) (er worden er "slechts" 23 gebruikt). Zodra een kort (0) en een lang (1) signaal zijn gemeten wordt het gemiddelde uitgerekend. In assembly valt dit op de volgende manier eenvoudig te doen.
MOVF short_lo, W
ADDWF long_lo, F
MOVF short_hi, W
BTFSC STATUS, C
INCF short_hi, W
ADDWF long_hi, F
RRF long_hi, F
RRF long_lo, F
Zoals wel enigszins logisch is staat de lengte van het korte signaal in short_lo en short_hi en de lengte van het lange signaal in long_lo en long_hi. Het gemiddelde komt uiteindelijk in long_lo en long_hi terecht. Merk op dat de carry van de laatste ADDWF meteen aan de rechterkant het gemiddelde ingeschoven wordt (geschoven omdat delen door 2 hetzelfde is als 1 bit naar rechts schuiven).
Als deze kalibratie gebeurd is dan is er in principe een verbinding. Het kan natuurlijk voorkomen dat het parallelle poort signaal door een ander programma op de PC toevallig achtereenvolgens kort hoog en lang hoog wordt gemaakt. Om te voorkomen dat de uC dan gaat wachten op invoer of corrupte data krijgt wordt er door de PC eerst een 32 bits identifier overgezonden. Met 4 keer het volgende code fragment (steeds een andere letter, in mijn applicatie is de ID "KrSt") worden de ontvangen 32 bits vergeleken met de waarde die gezonden zou moeten zijn.
CALL ReceiveByte
SUBLW 'K'
BTFSS STATUS, Z
GOTO Error6
Zoals te zien is wordt hier ook naar een Error gesprongen als het niet klopt. Wanneer deze verbinding echt gebruikt wordt is het ook mogelijk om alle jumps naar errors te vervangen door code om het binnenkomende signaal verder te negeren. Op die manier kunnen (met verschillende ID's) een willekeurig aantal apparaten aan één pin van de parallelle poort worden aangesloten. En daar zijn er dan weer 8 van ;-).
In het voorgaande stukje assembly wordt de functie ReceiveByte aangeroepen. Deze functie zit vrij simpel in elkaar. In een lus die altijd 8 keer (voor 8 bits per byte) wordt doorlopen (tenzij een time-out optreedt) wordt steeds gewacht tot RA5 naar ground wordt getrokken, waarna de lengte van het signaal wordt gemeten. Deze lengte (in tmr1_hi en tmr1_lo) valt eenvoudig te vergelijken met het gemiddelde in ave_lo en ave_hi.
MOVF ave_hi, W
SUBWF tmr1_hi, W ; tmr1-ave ==> C=0 if tmr1<ave, C=1 if tmr1>=ave.
BTFSS STATUS, Z
GOTO ReceiveByte_jnz
MOVF ave_lo, W
SUBWF tmr1_lo, W
ReceiveByte_jnz
RLF bytebuf, F
Het gehele stukje m.u.v. de laatste regel is een fragment om twee 16 bits waarden te vergelijken. Het resultaat komt terecht in de carry flag en deze wordt door RLF direct in de buffer bytebuf gezet, waarbij voorgaande bits opzij worden geschoven. Na 8 bits is de buffer vol en is er een byte ontvangen!
Omdat dit projectje een eerste experiment met zo'n verbinding was heb ik ook enkele methoden bedacht om te checken of de bytes nog wel goed aankomen nadat de ID goed is gearriveerd. De belangrijkste is het invoeren van een checksum, een waarde die met een bepaalde formule steeds wordt aangepast als functie van elke ontvangen byte en die af en toe vergeleken kan worden met de waarde die het moet zijn (deze wordt dan overgezonden door de PC). Het eenvoudigst is een waarde bij te houden die met:
XORWF bytexor, F
steeds bijgewerkt wordt (waarbij de ontvangen byte in W staat). Om de 8 bytes wordt door de PC een extra byte gezonden die gelijk moet zijn aan bytexor. Is dit niet het geval dan wordt de transmissie verbroken en een errorcode weergegeven.
De PC software
Om de parallelle poort aan te sturen wordt io.dll gebruikt en de functies hiervan aangeroepen in een simpel C++ programma. Voor delays van 5 ms of meer heb ik de Allegro Low Level Game Routines Library gebruikt.
Door wat experimenteren worden de lengtes van wachtlussen gevonden die een snelle dataoverdracht mogelijk maken zonder al teveel fouten. Hierbij blijkt het erg moeilijk te zijn om om te gaan met het Windows besturingssysteem. Doordat dit multitasking is wil het overzenden van data wel eens zomaar mislukken omdat Windows tijdens het zenden een ander programma gaat runnen.
Door langzaam opvoeren van de datasnelheid heb ik als max. 9800 bps gehaald. Gezien de beschikbare geheugencapaciteit van de uC is dit een hoge snelheid: ruim binnen een derde seconde kan het hele RAM en EEPROM volgeschreven worden met ontvangen data. Bij deze snelheid treden, waarschijnlijk dankzij Windows, regelmatig fouten op. Daarom wordt in het uiteindelijke project gewerkt met een snelheid rond de 800 bps.
In de toekomst moet gekeken worden naar betere manieren om dit signaal te timen en naar de juiste functie om de applicatie een hogere prioriteit (realtime of time critical of iets dergelijks) te geven.
Conclusie
Door dit project blijkt hoe eenvoudig (of niet) het kan zijn om een verbinding te establishen tussen een uC en de PC. Eenvoudig omdat de hardware verbazingwekkend simpel is en toch lastig omdat relatief veel code (meer dan bij een signaal met klok) in de uC nodig is om de data te ontvangen. Ook lastig zijn de multitasking eigenschappen van Windows die tijd kritische applicaties lastig maken zonder speciale windows functies te gebruiken om application priority hoger te zetten.
Snelheden van 9800 bps zijn met de gebruikte software en hardware al gehaald. In de praktijk blijken lagere snelheden echter veel beter te werken door de moeilijkheden die Windows veroorzaakt. Worden deze problemen echter opgelost dan behoort 9800 bps zeker tot de mogelijkheden.
Ik hoop dat u enigszins geïnformeerd bent over dit experiment en dat u dit artikel met plezier heeft gelezen. (en dat u de code snippets goed kunt gebruiken :)
Links
- MinGW - de gebruikte C++ compiler
- Allegro - de Allegro Low Level Game Routines Library voor een simpele delay functie.
- ParPort - programma om de parallele poort te testen en/of te monitoren.
- PIC uC tutorial - zonder dit document was ik twee jaar geleden niet aan de uC's gegaan.
- Microchip - de datasheet van de PIC 16F628

