Linux I/O port programmering mini-HOWTO <author>Av: Riku Saikkonen <tt/<Riku.Saikkonen@hut.fi>/ Översättning till svenska: Sven Wilhelmsson, <tt/<sven.wilhelmsson@swipnet.se>/ </author> <date>v ,28 December 1997 , Översatt: 10 Augusti 1998 </date> <abstract> I detta HOWTO dokument beskrivs hur man programmerar I/O portar samt hur man fördröjer eller mäter korta tidsintervall i <em/user-mode/ Linux program för Intel x86 arkitekturen. </abstract> <toc> <sect>Inledning <p> I detta HOWTO dokument beskrivs hur man når I/O-portar och hur man fördröjer korta tidsintervall i <em/user-mode/ Linux program som körs på Intel x86 arkitekturen. Detta dokumentet är en uppföljare till den mycket lilla <em/IO-Port mini-HOWTO/ från samma författare. This document is Copyright 1995-1997 Riku Saikkonen. See the <url url="http://sunsite.unc.edu/pub/Linux/docs/HOWTO/COPYRIGHT" name="Linux HOWTO copyright"> for details. If you have corrections or something to add, feel free to e-mail me (<tt/Riku.Saikkonen@hut.fi/)... Ändringar från tidigare publicerad version (Mar 30 1997): <itemize> <item>Förklaringar angående <tt/inb_p//outb_p/ och port 0x80. <item>Tagit bort information om <tt/udelay()/, eftersom <tt/nanosleep()/ medger ett renare sätt att göra samma sak. <item>Konverterat till Linuxdoc-SGML, och ändrat om något. <item>Många mindre tillägg och ändringar. </itemize> <sect>Att använda I/O portar i C-program <sect1>Det vanliga sättet <p> Rutiner för att nå I/O-portar finns i<tt>/usr/include/asm/io.h</> (eller <tt>linux/include/asm-i386/io.h</> i 'the kernel source distribution'). Rutinerna där är inline makron, så det räcker att göra <tt>#include <asm/io.h></>, inga ytterligare bibliotek behövs. Beroende på brister i gcc (åtminstone i 2.7.2.3 och lägre) och i egcs (alla versioner), måste du kompilera källkod som använder dessa rutiner med optimeringsflaggan på (<tt/gcc -O1/ eller högre), eller alternativt <tt>#define extern</> till ingenting, (dvs. <tt>#define extern</> på en i övrigt blank rad) innan du gör <tt>#include <asm/io.h></>. Om du vill avlusa, 'debug', kan du använda <tt/gcc -g -O/ (åtminstone med moderna versioner av gcc), även om optimeringen ibland gör att debuggern beter sig lite underligt. Om detta besvärar dig, kan du lägga de rutiner som anropar I/O-portarna i separata filer och kompilera endast dem med optimeringsflaggan på. Innan du anropar en port, måste du ge ditt program tillstånd till detta. Detta gör man genom att anropa <tt/ioperm()/ funktionen (som finns deklarerad i <tt/unistd.h/, och definierad i 'kernel') någonstans i början av ditt program, innan någon I/O-port anropas. Syntaxen är <tt>ioperm(from, num, turn_on)</>, där <tt/from/ är den första portadressen som ska ges tillstånd och <tt/num/ är antalet konsekutiva adresser. Till exempel, <tt/ioperm(0x300, 5, 1)/ ger tillstånd till portarna 0x300 till 0x304 (totalt 5 portar). Det sista argumentet är en bool som specificerar om du vill ge access­tillstånd (true(1)) eller ta bort tillståndet (false(0)). Du kan använda <tt/ioperm()/ upprepade gånger för att ge tillstånd till icke­konsekutiva port­adresser. Se <tt/ioperm(2)/ manualen. <tt/ ioperm()/ anropet kräver att ditt program har root­privilegier. Det krävs att du antingen kör som root, eller gör setuid root. Du kan släppa root­privilegierna så snart du har anropat <tt/ioperm()/. Det är inte nödvändigt att explicit släppa dina accessrättigheter med <tt/ioperm(..., 0)/ mot slutet av ditt program. Detta sker automatiskt när processen avslutas. <tt/Om du gör setuid()/ till en <em/non-root user/ förstörs inte de accessrättigheter som är redan givna av <tt/ioperm()/, men <tt/fork()/ förstör dem (<em/child/ processen får inga rättigheter, men <em/parent/ behåller dem). <tt/ioperm()/ kan endast ge access rättigheter till portarna 0x000 - 0x3ff. För att komma åt högre portadresser, kan man använda <tt/iopl()/, som ger access till <em/alla/ portar på en gång. Använd nivå 3 (dvs <tt/iopl(3)/) för att ge ditt program tillgång till alla portar. (Men var försiktig - att skriva på fel port kan orsaka allehanda otrevliga saker med din dator). Du behöver root­privilegier för att anropa <tt/iopl()/. Se <tt/iopl(2)/ manualen. Sedan, för att komma åt portarna... För att läsa in en byte, (8 bitar) från en port, call <tt/inb(port)/, den returnerar den byte den läser. För att ställa ut, call <tt/outb(value,port)/ (notera parameter­ordningen). För att läsa in 16 bitar från port <tt/x/ och <tt/x+1/ ,en byte från vardera, call inw(x) och för att ställa ut, call <tt/outw(value,x)/. Är du osäker på om du skall använda byte eller word instruktioner, är det troligen <tt/inb()/ och <tt/outb()/ - flertalet apparater konstrueras för bytevis portaccess. Notera att alla portaccesser tar åtminstone cirka en mikrosekund att utföra. För övrigt fungerar makroanropen <tt/inb_p()/, <tt/outb_p()/, <tt/inw_p()/, och <tt/outw_p()/ på samma sätt som ovannämnda, förutom att de lägger till ca en mikrosekund efter varje portaccess. Du kan göra fördröjningen ännu längre, ca 4 mikrosekunder, med <tt/#define REALLY_SLOW_IO/ innan du gör <tt>#include <asm/io.h></>. Dessa makron gör normalt (såvida du inte gör <tt/#define SLOW_IO_BY_JUMPING/, vilket blir mindre noggrant) access till port 0x80 för att skapa delay, så du behöver först ge accessrätt till port 0x80 med <tt/ioperm()/. (Skrivning på port 0x80 påverkar ingenting). För mer flexibla delay-metoder, läs vidare. Det finns sidor till <tt/ioperm(2)/, <tt/iopl(2)/ och ovannämnda makron i någorlunda färska utgåvor av Linux manual. <sect1>En alternativ metod: <tt>/dev/port</> <p> Ett annat sätt att komma åt I/O-portar är <tt/open()/ <tt>/dev/port</> (en 'character device', major number 1, minor 4) för läsning och/eller skrivning (stdio <tt/f*()/ funktionerna har intern buffring, så använd inte dem). Gör sedan <tt/lseek()/ till den aktuella byten i filen (fil position 0 = port 0x00, fil position 1 = 0x01, och så vidare), och <tt/read()/ eller <tt/write()/ en byte eller ett ord till eller från den. Naturligtvis behöver ditt program accessrättigheter till <tt>/dev/port</> för att metoden skall fungera. Denna metod är sannolikt långsammare än den normala metoden enligt ovan, men behöver varken optimerings­flaggan vid kompilering eller <tt/ioperm()/. Det behövs inte heller 'root access', bara du ger 'non-root user' eller 'group' access till <tt>/dev/port</> - låt vara att detta är dumt ur systemsäkerhetssynpunkt, eftersom det är möjligt att skada systemet, kanske till och med vinna 'root access', genom att använda <tt>/dev/port</> för att komma åt hårddisk, nätverkskort, etc. direkt. <sect>Avbrott (IRQs) och DMA <p> Man kan inte använda IRQ eller DMA direkt i en <em/user­mode/ process. Man måste skriva en <em/kernel driver/; se <url url="http://www.redhat.com:8080/HyperNews/get/khg.html" name="The Linux Kernel Hacker's Guide"> Där finns detaljer och <em/kernel/ källkod som exempel. Man kan heller inte stänga av ett avbrott från ett <em/user-mode/ program. <sect>Högupplösande timing <sect1>Fördröjningar <p> Först och främst måste sägas att det inte går att garantera user mode processer exakt kontroll avseende timing eftersom Linux är ett multiprocess system. Din process kan bli utskyfflad under vad som helst mellan 10 millisekunder upp till några sekunder (om belastningen är hög). Detta spelar emellertid ingen roll för flertalet program som använder I/O-portar. För att reducera effekterna, kan du med hjälp av kommandot <em/nice/ ge din process hög prioritet. Se <tt/nice(2)/ manualen eller använd <em/real-time scheduling/ enligt nedan. Om du behöver bättre tidsprecision än vad normala user-mode processer kan ge, så finns vissa förberedelser för 'user-mode real time' support. Linux 2.x kärnor har 'soft real time support', se manualen för <tt/sched_setscheduler(2)/. Det finns en speciell kärna som stöder hård realtid, se <url url="http://luz.cs.nmt.edu/˜rtlinux/"> för ytterligare information om detta. <sect2><tt/sleep()/ och <tt/usleep()/ <p> Låt oss börja med de lätta funktionsanropen. För att fördröja flera sekunder, är det troligtvis bäst att använda <tt/sleep()/. Fördröjningar på 10-tals millisekunder (ca 10 ms verkar vara minimum) görs med <tt/usleep()/. Dessa funktioner frigör CPU för andra processer, så att ingen CPU-tid går förlorad. Se manualerna <tt/sleep(3)/ och <tt/usleep(3)/. Om fördröjningar är på mindre än 50 ms ( beror på din processor och dess belastning), tar det onödigt mycket tid att släppa CPUn, därför att det för Linux <em/scheduler/ (för x86 arkitekturen) vanligtvis tar minst 10-30 millisekunder innan den återger din process kontrollen. Beroende på detta fördröjer <tt/usleep(3)/ något mer än vad du specificerar i dina parametrar, och alltid minst ca 10 ms. <sect2>nanosleep() <p> I 2.0.x serien av Linuxkärnor finns ett nytt systemanrop: <tt/nanosleep()/ (se <tt/nanosleep(2)/ manualen), som möjliggör så korta fördröjningar som ett par mikrosekunder eller mer. Vid fördröjningar på mindre än 2 ms, om (och endast om) din process är satt till <em/soft real time scheduling/ (med <tt/sched_setscheduler()/), använder <tt/nanosleep()/ en vänteloop, i annat fall frigörs CPU på samma sätt som med <tt/usleep()/. Vänteloopen använder <tt/udelay()/ (en intern kernelfunktion som används av många 'kernel drivers'), och loopens längd beräknas med hjälp av BogoMips värdet (det är bara denna sorts hastighet BogoMips värdet mäter noggrant). Se hur det fungerar i <tt>/usr/include/asm/delay.h</> <sect2>Fördröjningar med port I/O <p> Ett annat sätt att fördröja ett fåtal mikrosekunder är att använda port I/O. Läsning eller skrivning på port 0x80 (se ovan hur man gör) tar nästan precis 1 mikrosekund oberoende av processortyp och hastighet. Du kan göra det upprepade gånger om du vill vänta ett antal mikrosekunder. Skrivning på denna port torde inte ha några skadliga sidoeffekter på någon standardmaskin och vissa 'kerneldrivers' använder denna metod. det är på detta sättet <tt/{in|out}[bw]_p()/ normalt gör sin fördröjning. (se <tt/asm/io.h)/. Flertalet port I/O instruktioner i adressområdet 0-0x3ff tar nästan exakt 1 mikrosekund, så om du t.ex. använder parallellporten direkt, gör bara några extra <tt/inb()/ från porten för att skapa fördröjning. <sect2>Att fördröja med assemblerinstruktioner <p> Om man känner till processortyp och klockhastighet, kan man hårdkoda korta fördröjningar med vissa assembler­instruktioner (men kom ihåg, processen kan skyfflas ut när som helst, så fördröjningarna kan ibland bli längre). Tabellen nedan ger några exempel. För en 50MHz processor tar en klockcykel 20 ns. <tscreen><verb> Instruktion i386 klock cykler i486 klock cykler nop 3 1 xchg %ax,%ax 3 3 or %ax,%ax 2 1 mov %ax,%ax 2 1 add %ax,0 2 1 </verb></tscreen> tyvärr känner jag inte till Pentium; förmodligen nära i486. Jag hittar ingen instruktion som tar EN klockcykel i i386. Använd en-cykel instruktioner om du kan, annars kanske <em/pipelinen/ i moderna processortyper förkortar tiden. Instruktionerna <tt/nop/ och <tt/xchg/ i tabellen bör inte ha några sidoeffekter. Övriga modifierar statusregistret, men det bör inte betyda något eftersom gcc detekterar detta. <tt/nop/ är ett bra val. Om du vill använda dem, skriv call <tt/asm("instruktion")/ i ditt program. Syntaxen ge i tabellen ovan. Vill du göra multipla instruktioner i en <tt/asm()/-sats, så separera med semikolon. Till exempel exekveras i satsen <tt/asm(&dquot; nop; nop; nop; nop&dquot;)/ fyra <tt/nop/ instruktioner, som fördröjer fyra klockcykler med i486 eller pentium (eller 12 cykler med i386). <tt/asm()/ översätts av gcc till inline assembler kod, så det blir inget <em/overhead/ med funktionsanrop. Kortare fördröjningar än en klockcykel är inte möjligt med x86 arkitekturen. <sect2>rdtsc() för Pentium <p> Med Pentium kan du erhålla antalet klockcykler som gått sedan senaste uppstart med hjälp av följande C kod: <tscreen><code> extern __inline__ unsigned long long int rdtsc() { unsigned long long int x; __asm__ volatile (".byte 0x0f, 0x31" : "=A" (x)); return x; } </code></tscreen> Du kan polla värdet för hur många cykler som helst. <sect1>Att mäta tid <p> För att mäta tider med en sekunds upplösning, är det nog enklast att använda <tt/time()/. Krävs bättre noggrannhet, ger <tt/gettimeofday()/ cirka en mikrosekunds upplösning (men se ovan angående 'scheduling', utskyffling). För Pentium är <tt/rdtsc/ kod­fragmentet ovan noggrant till en klockcykel. Om din process skall ha en signal efter en viss tid, så använd <tt/setitimer()/ eller <tt/alarm()/. Se manualsidorna. <sect>Andra programmeringsspråk <p> Beskrivningen ovan koncentrerar sig på programmeringsspråket C. Det bör vara tillämpbart även på C++ och Objective C. I assembler får man anropa <tt/ioperm()/ eller <tt/iopl()/ som i C, och därefter kan man använda I/O-port read/write instruktionerna direkt. I andra språk, såvida inte du kan infoga inline assembler eller C kod i ditt program eller använda ovannämnda systemanrop, är det nog enklast att skriva en C källkodsfil med funktionerna för I/O-portaccess och separatkompilera och länka den till övriga delar av ditt program. Eller använda <tt>/dev/port</> enligt ovan. <sect>Några användbara portar <p> Här följer programmeringsinformation om några portar som direkt kan användas för TTL (eller CMOS) digitala kretsar. Om du vill använda dessa eller andra gängse portar för det ändamål de är ägnade (t.ex. för att styra en printer eller modem), skall du troligen använda en befintlig drivrutin (vanligtvis inkluderad i kärnan) i stället för att programmera dessa portar direkt som beskrivs i detta HOWTO. Denna sektion är avsedd för dem som vill ansluta LCD-displayer, stegmotorer, eller annan speciell elektronik till en PC's standardport. Ska du styra en massproducerad produkt som t.ex. scanner (som har funnits på marknaden ett tag), sök efter en befintlig drivrutin. <url url="http://sunsite.unc.edu/pub/Linux/docs/HOWTO/Hardware-HOWTO" name="Hardware-HOWTO"> är ett bra ställe att börja. <url url="http://www.hut.fi/Misc/Electronics/"> är en annan bra källa till information om hur man ansluter utrustning till datorer, och om elektronik i allmänhet. <sect1>Parallellporten <p> Parallellportens basadress (kallad ''<tt/BASE/'' nedan) är 0x3bc för <tt>/dev/lp0</>, 0x378 för <tt>/dev/lp1</>, och 0x278 för <tt>/dev/lp2</>. Ska du styra något som beter sig som en normal printer, se <url url="http://sunsite.unc.edu/pub/Linux/docs/HOWTO/Printing-HOWTO" name="Printing-HOWTO">. Förutom den vanliga output-only moden som beskrivs nedan, finns det en 'extended' bidirektionell mod i flertalet parallellportar. Information om detta och om de nyare ECP/EPP moderna (och om IEEE 1284 standarden allmänt), se <url url="http://www.fapo.com/"> och <url url="http://www.senet.com.au/˜cpeacock/parallel.htm">. Kom ihåg att eftersom du inte kan använda IRQs eller DMA i ett user-mode program, så kommer du nog att behöva skriva en 'kernel-driver' för att använda ECP/EPP. Jag tror att någon håller på att skriva en sådan driver men jag känner inte till några detaljer. Porten <tt/BASE+0/ (Data port) styr data signalerna (D0 till D7 för bitarna 0 to 7, respektive; tillstånden: 0 = låg (0 V), 1 = hög (5 V)). Skrivning på denna port ställer ut data till donet. En läsning returnerar senast skrivna data i standard- eller extended-moden, eller data på stiften från en ansluten apparat i 'extended read mode'. Portarna <tt/BASE+1/ ('Status port') är 'read-only', och returnerar tillståndet på följande insignaler: <itemize> <item>Bits 0 och 1 är reserverade. <item>Bit 2 IRQ status (inget stift, vet inte hur detta fungerar) <item>Bit 3 ERROR (1=hög) <item>Bit 4 SLCT (1=hög) <item>Bit 5 PE (1=hög) <item>Bit 6 ACK (1=hög) <item>Bit 7 -BUSY (0=hög) </itemize> (Vet inte vilka spänningar som motsvarar hög respektive låg.) Porten <tt/BASE+2/ ('Control port') är 'write-only' (läsning returnerar senast inskrivna data), och styr följande status signaler: <itemize> <item>Bit 0 -STROBE (0=hög) <item>Bit 1 AUTO_FD_XT (1=hög) <item>Bit 2 -INIT (0=hög) <item>Bit 3 SLCT_IN (1=hög) <item>Bit 4 aktiverar ('enables') parallell portens IRQ (vilket sker på upp­flanken hos ACK) när den sätts till 1. <item>Bit 5 styr 'extended mode direction' (0 = skriv, 1 = läs), den är 'write-only' (läsning på denna bit returnerar ingenting meningsfullt). <item>Bits 6 och 7 är reserverade. </itemize> (Återigen, är inte säker på vad som är hög och låg.) Pinout (ett 25-pin D-don , hona ) (i=input, o=output): <tscreen><verb> 1io -STROBE, 2io D0, 3io D1, 4io D2, 5io D3, 6io D4, 7io D5, 8io D6, 9io D7, 10i ACK, 11i -BUSY, 12i PE, 13i SLCT, 14o AUTO_FD_XT, 15i ERROR, 16o -INIT, 17o SLCT_IN, 18-25 Ground </verb></tscreen> IBM specifikationen säger att stiften 1, 14, 16, och 17 ('control- outputs') har 'open-collektor' utgångar dragna till 5 V genom ett 4.7 K motstånd (sänker 20 mA, ger 0.55 mA, högnivå utgång 5.0 V minus eventuellt spänningsfall). Övriga stift sänker 24 mA och ger 15 mA, och deras hög­nivå spänning är minst 2.4 V. Lågnivå spänningen är i båda fallen minst 0.5 V. Icke-IBM parallell portar avviker troligen från denna standard. För ytterligare information om detta se <url url="http://www.hut.fi/Misc/Electronics/circuits/lptpower.html">. Slutligen en varning: Var noga med jordningen. Jag har förstört flera parallellportar genom att ansluta dem med datorn igång. Det kan vara ett bra alternativ att använda parallellportar som <em/inte/ sitter på moderkortet för sådana här saker. (Du kan antagligen få en <em/andra/ parallellport med ett billigt standard 'multi-I/O' kort; Stäng bara av de portar du inte behöver, och sätt parallellkortets I/O-adress till en ledig adress. Du behöver inte bekymra dig om parallellportens IRQ, eftersom den vanligtvis inte används.) <sect1>Spelporten (joystick) <p> Spelporten finns på adresserna 0x200-0x207. För att styra normala joystickar finns en <em/kernel-level joystick driver/, se <url url="ftp://sunsite.unc.edu/pub/Linux/kernel/patches/">, filename <tt/joystick-*/. Pinout (ett 25-pin D-don , hona ) (i=input, o=output): <itemize> <item>1,8,9,15: +5 V (kraftmatning) <item>4,5,12: Ground <item>2,7,10,14: Digitala ingångar BA1, BA2, BB1, och BB2, respektive <item>3,6,11,13: ''Analoga'' ingångar AX, AY, BX, och BY, respektive </itemize> +5 V stiften verkar ofta vara direkt anslutna till moderkortets kraftmatning, så de bör klara ganska mycket ström, beroende på moderkort, kraftaggregat och spelport. De digitala ingångarna används till de två joystickarna (joystick A och joystick B, med två knappar vardera) som du kan ansluta till porten. De torde vara normala TTL ingångar, och du kan läsa deras status direkt på statusporten (se nedan). En joystick ger låg (0 V) status när knappen är nedtryckt och eljest hög (5 V från matningen via ett 1K motstånd). De så kallade analoga ingångarna mäter egentligen resistans. Spelportarna har en fyrfaldig one-shot multivibrator (ett 558 chip) anslutet till de fyra ingångarna. På varje ingångsshylsa i kontaktdonet finns ett 2.2K motstånd mellan ingångshylsan och multivibratorns 'open-collector' utgång, och en 0,01 uF 'timing' kondensator mellan multivibratorns utgång och jord. En joystick har en potentiometer för varje axel (X och Y), dragen mellan +5 V och respektive ingångshylsa ( AX och AY för joystick A, eller BX och BY för joystick B). När multivibratorn aktiveras sätts dess utgång hög (5 V) och den inväntar att timing kondensatorn når 3.3 V innan den sänker respektive utgång. På så sätt blir multivibratorns pulslängd proportionell mot potentiometerns resistans (dvs. joystickens position för respektive axel) enligt följande: <quote> R = (t - 24.2) / 0.011, </quote> där R är potentiometerns resistans och t pulsens längd i mikrosekunder. Således, för att läsa dessa analoga ingångar, skall man först aktivera multivibratorn (med en skrivning på porten; se nedan), sedan polla (göra upprepade läsningar) tillståndet för de fyra axlarna tills de går från hög till låg, och på så sätt mäta pulstiden. Denna pollning tar mycket CPU tid och i ett icke­realtids multiprocess system som Linux blir inte resultatet så tillförlitligt eftersom man inte kan polla kontinuerligt, såvida du inte gör en 'kernel-driver' och stänger avbrottsingångar när du pollar (vilket tar ännu mer CPU tid). Om du vet att signalen kommer dröja tiotals millisekunder så kan du anropa usleep() innan du börjar polla för att ge CPU tid till andra processer. Den enda I/O-port som du behöver nå är port 0x201 (de andra portarna beter sig precis likadant eller är inaktiva). En skrivning till porten (spelar ingen roll vad) aktiverar multivibratorn. Läsning från porten returnerar signalernas status: <itemize> <item>Bit 0: AX (status (1=high) of the multivibrator output) <item>Bit 1: AY (status (1=high) of the multivibrator output) <item>Bit 2: BX (status (1=high) of the multivibrator output) <item>Bit 3: BY (status (1=high) of the multivibrator output) <item>Bit 4: BA1 (digital input, 1=high) <item>Bit 5: BA2 (digital input, 1=high) <item>Bit 6: BB1 (digital input, 1=high) <item>Bit 7: BB2 (digital input, 1=high) </itemize> <sect1>Serieporten <p> Om den apparat som du vill kommunicera med stöder något som liknar RS-232 bör du kunna använda en serieport för att tala med den. Linux drivrutin för serieportar bör räcka för nästan alla tänkbara tillämpningar ( du ska inte behöva programmera serieportarna direkt, och skulle så vara måste du skriva en 'kernel driver'); den är mycket flexibel, så att använda icke­standardiserade bit­hastigheter torde inte vara något problem. Se <tt/termios(3)/ manualen, eller seriedriverns källkod, (<tt>linux/drivers/char/serial.c</>), och <url url="http://www.easysw.com/˜mike/serial/index.html"> för mer info om hur man programmerar serieportar på Unix system. <sect>Tips <p> Om du behöver bra analog I/O kan du ansluta ett ADC- och/eller DAC-chip till parallellporten (tips: kraft kan du ta från anslutningsdonet till spelporten eller från ett don till en yttre diskenhet eller använda ett separat kraftaggregat. Har du strömsnåla kretsar kan du ta kraftmatning från parallellporten. Du kan också köpa ett AD/DA-kort (de flesta äldre/långsammare typerna ansluts till I/O-portar. Eller, om det räcker med 1 eller 2 kanaler och måttlig noggrannhet, köp ett billigt ljudkort som har stöd från Linux <em/sound driver/. Ett sådant är också tämligen snabbt. Noggranna analoga apparater störs lätt om jordningen är bristfällig. Om du får problem av detta slag, kan du pröva att isolera din utrustning från datorn med hjälp av optokopplare. (på <em/alla/ signaler mellan datorn och din utrustning. Försök att få matning till optokopplarna från datorn (lediga signaler från porten kan ge tillräckligt med kraft) för bästa isolation från störning. Letar du efter Linux mjukvara för mönsterkortframtagning, så finns det en fri sådan som kallas Pcb. Den torde göra ett bra jobb, åtminstone om inte du gör något alltför komplicerat. Den finn med i många Linux distributioner, och den finns tillgänglig i <url url="ftp://sunsite.unc.edu/pub/Linux/apps/circuits/"> (filename <tt/pcb-*/). <sect>Felsökning <p> <descrip> <tag/Q1./ Jag får <em/segmentation faults/ när jag adresserar portar. <tag/A1./ Antingen har ditt program inte root­privilegier, eller har <tt/ioperm()/ falerat av någon annan orsak. Testa det returnerade värdet från <tt/ioperm()/. Testa också att du verkligen adresserar de portar som du gett tillstånd till med <tt/ioperm()/ (se Q3). Om du använder 'delaying macros' (<tt/inb_p()/, <tt/outb_p()/, osv.), kom ihåg att du då måste anropa <tt/ioperm()/ för att ge accesstillstånd till adress 0x80. <tag/Q2./ Jag kan inte hitta var <tt/in*()/, <tt/out*()/ funktionerna definieras, och gcc klagar över odefinierade referenser. <tag/A2./ Du kompilerade inte med optimerings­flaggan på (<tt/-O1 eller högre/), och därför kunde inte gcc lösa upp de makron som finns i <tt>asm/io.h</>. Eller glömde du kanske <tt>#include <asm/io.h></>. <tag/Q3./ <tt/out*()/ gör ingenting, eller gör något konstigt. <tag/A3./ Kolla ordningen på parametrarna; set skall vara <tt/outb(value, port)/, inte <tt/outb(port, value)/ som förekommer i MS-DOS. <tag/Q4./ Jag vill köra en standard RS-232 device/parallel printer/joystick... <tag/A4./ Då är det nog bäst att använda en befintlig driver (i Linux kernel eller en X-server eller någon annanstans) för detta. Drivrutinerna är vanligtvis mycket flexibla, så att även en icke­standard apparat fungerar vanligtvis med dem. Se info om standard portar ovan efter hänvisningar till dokumentation. </descrip> <sect>Kod exempel <p> Här följer ett enkelt exempel på kod för I/O-port access: <tscreen><code> /* * example.c: very simple example of port I/O * * This code does nothing useful, just a port write, a pause, * and a port read. Compile with 'gcc -O2 -o example example.c', * and run as root with './example'. */ #include <stdio.h> #include <unistd.h> #include <asm/io.h> #define BASEPORT 0x378 /* lp1 */ int main() { /* Get access to the ports */ if (ioperm(BASEPORT, 3, 1)) {perror("ioperm"); exit(1);} /* Set the data signals (D0-7) of the port to all low (0) */ outb(0, BASEPORT); /* Sleep for a while (100 ms) */ usleep(100000); /* Read from the status port (BASE+1) and display the result */ printf("status: %d\n", inb(BASEPORT + 1)); /* We don't need the ports anymore */ if (ioperm(BASEPORT, 3, 0)) {perror("ioperm"); exit(1);} exit(0); } /* end of example.c */ </code></tscreen> <sect>Erkännande <p> Alltför många har bidragit till artikeln för att jag skall kunna räkna upp alla, men tack allesammans. Jag har inte besvarat alla bidrag som jag fått; ledsen för det, men återigen tack för all hjälp. </article>