Veikkaan ettei tähän tule jelppiä mutta ainahan sitä voi yrittää :-)
Kyse siis NT/2000/XP käyttiksestä. (Linuxissa tämän kai voisi ratkaista puukottamamma kerneliä).
Pitäisi saada mahdollisimman helposti oma ohjelman pätkä ajoon jos COM-portin keskeytys aktivoituu. Haluaisin siis varastaa tuon keskeytyksen. Periaatteessa homma on yksinkertainen, kirjoitan vain keskytysvektoritauluun uuden osoitteen mutta kun se ei taida Windowsin tapauksessa mennä noin simppelisti, eli miten se menisi?
Kyse siis NT/2000/XP käyttiksestä. (Linuxissa tämän kai voisi ratkaista puukottamamma kerneliä).
Pitäisi saada mahdollisimman helposti oma ohjelman pätkä ajoon jos COM-portin keskeytys aktivoituu. Haluaisin siis varastaa tuon keskeytyksen. Periaatteessa homma on yksinkertainen, kirjoitan vain keskytysvektoritauluun uuden osoitteen mutta kun se ei taida Windowsin tapauksessa mennä noin simppelisti, eli miten se menisi?
Vastaukset 33
Vanhimmat
Windows NT:ssä on aika tavalla erilaista kuin Win98:ssa tai aikaisemmissa versioissa. NT ei salli ohjelman käsitellä suoraan sarjaportti kuten vielä Win98 tai DOS sallivat. Homma täytyy tehdä käyttiksen kautta.
Jos ihan itse aiot tehdä koodin, paras paikka alkaa on:
http://msdn2.microsoft.com/en-us/library/ms810467.aspx#serial_topic1
Verkosta löytyy valmiitakin ohjelmia, joista voi ladata source-koodin. Näistä sitten voi itse muokata haluamansa tapaisen ohjelman.
Oikeastaan sarjaliikenne ei kiinnosta, vain keskeytyksen saaminen käyttöön ja oman rutiinin kutsuminen tuolla keskeytyksellä.
Sarja tai rinnakkaisportin keskeytys olisi helposti tuotettavissa ulkoisesta härvelistä.
Minulla ei ole Microsoftin kääntäjää tai ddk:ta joita ilman ei kai noita devicedrivereita tms. pysty tekemään. DLL:n eli eräänlaisen virtualilaiteohjaimen kyllä pystyy vanhalla Borlandilaisellakin kai tekemään.
En ole löytänyt yhtään esimerkkiä tai muuta inffoa keskytystaulun käsittelystä tai edes siitä miten device driverit asettavat keskeytysvektorin omaan koodiinsa.
Olisikohan Linux helpompi tie? Sita kai voisi puukottaa oman mielen mukaan kernel tasollakin.
Kuten jo sanoin tuossa edellisessä vastauksessa, NT:ssä sarjaportin käsittely on hankalampaa kuin Win98:ssa. NT ei päästä suoraan kirjoittamaan sarjaporttiin HW tasolla vaan luo virtuaalisen sarjaportin, jos sitä yrittää käsitellä Win98:n tapaan. Siis esim. keskeytysvektoreitavektoria käsittelemällä.
Jos aiot käyttää RS-232 porttia eli sarjaporttia, joudut kuitenkin myös perehtymään portin rekisterien asetuksiin ym. UART:in asetukset on käsitelty seuraavassa linkissä. Useammin jokin ulkoinen signaali tuodaan PC:lle rinnakkaisportin kautta, kuin sarjaportin.
Tässä olisi lyhyt esitys miten Win98:ssa ohjelmoidaan sarjaporttia.
http://www.beyondlogic.org/serial/serial1.htm
Muutama asia, josta kysyit. Esimerkit C-kielellä.
Keskeytysvektorit:
Keskeytysvektorihan sisältää sen ohjelman osoitteen, jota kutsutaan keskeytyksen sattuessa.
Keskeytysvektori luetaan:
oldport1isr = getvect(INTVECT);
missä oldport1isr vanha vektorin arvo (kannattaa tallettaa aluksi)
tyyppimäärittely
void interrupt (*oldport1isr)();
Uusi ohjelman osoite kirjoitetaan vektoriin:
setvect(INTVECT, PORT1INT);
PORT1INT on ohjelma, joka palvelee keskeytyksen.
INTVECT eli keskeytysvektorit:
0B 3 Serial Comms. COM2/COM4
0C 4 Serial Comms. COM1/COM3
muita vektoreita löytyy linkistä ja googlettamalla.
Sitten vaan kirjoittamaan tuota keskeytyspalveluohjelmaa PORT1INT.
Esim.
void interrupt PORT1INT()
{
int c;
do { c = inportb(PORT1 5);
if (c & 1) {
buffer[bufferin] = inportb(PORT1);
bufferin ;
if (bufferin == 1024) bufferin = 0;
}
} while (c & 1);
outportb(0x20,0x20);
}
Ohjelman tyyppinä täytyy olla interrupt, jolloin kääntäjä tietää tehdä tarvittavat toimet (rekisterit pinoon jne.).
Tämä on tyyli, miten kekskeytysvektorien käsittely tehtiin aikoinaan DOS:ssa tai Win98:ssa. NT:ssä tai XP:ssä tämä ei välttämättä toimi.
Verkosta löytyy varmaan satoja esimerkkejä sarjaportin ohjelmoinnista ja keskeytysvektoreiden asetuksista. Hae googlesta esim. sanoilla SERIAL PORT DRIVER.
Tässä hieman NT:n tyylistä:
http://www.codeproject.com/system/SerialPortComm.asp
Eli keskeytysvektorin varaamisen sijaan portti avataan API ohjelmalla:
m_hSerialComm = CreateFile(m_pszPortName,
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISITING,
0,
NULL);
missä m_pszPortName on COM, COM2, COM3 tai COM4.
Tällöin saadaan kahva portille m_hSerialComm, jota tästsä lähtien käytetään portin tunnuksena.
UARTIN rekisterien sijaan konfigurointi tehdään DCB nimiseen taulukkoon (Device Control Block)
DCB dcbConfig;
if(GetCommState(m_hSerialComm, &dcbConfig))
{
dcbConfig.BaudRate = dwBaudRate;
dcbConfig.ByteSize = 8;
dcbConfig.Parity = NOPARITY;
dcbConfig.StopBits = ONESTOPBIT;
dcbConfig.fBinary = TRUE;
dcbConfig.fParity = TRUE;
}
else
//Handle Error Condition
if(!SetCommState(m_hSerialComm, &dcbConfig))
//Handle Error Condition
Lukeminen tapahtuu
std::stringbuf sb;
DWORD dwEventMask;
if(!SetCommMask(m_hSerialComm, EV_RXCHAR))
//Handle Error Condition
if(WaitCommEvent(m_hSerialComm, &dwEventMask, NULL))
{
char szBuf;
DWORD dwIncommingReadSize;
do
{
if(ReadFile(m_hSerialComm, &szBuf, 1, &dwIncommingReadSize, NULL) != 0)
{
if(dwIncommingReadSize > 0)
{
dwSize = dwIncommingReadSize;
sb.sputn(&szBuf, dwIncommingReadSize);
}
}
else
//Handle Error Condition
} while(dwIncommingReadSize > 0);
}
else
//Handle Error Condition
Eli ensin asetetaan maskit keskytyksille. Sitten jäädään odottamaan portin keskeytystä eli EVENT:iä WaitCommEvent() ohjelmaan. Aina kun keskeytys tulee suoritaan ohjelmanpätkä. Tähän kohtaa voit lisätä oman ohjelmasi.
Ehkä klassinen esitys Linux:n sarjaportin ohjelmoinnista.
http://www.faqs.org/docs/Linux-HOWTO/Serial-Programming-HOWTO.html
Ohjelmoinnin voi tehdä Linuxin gcc:llä, jolloin ei erikseen tarvitse hankkia kääntäjää.
Tuosta kernelin puukottamisesta sanoisin, että jos nämä esimerkit eivät tunnu riittävän, voi sitä tietysti harkita. Toisaalta kaikkia UART:in parametreja voi muuttaa myös käyttöjärjestelmän kutsujen kautta, joten miksi mennä puukottamaan kerneliä.
Kiitosta jo heti kärkeen :-)
Minun ei ole tarkoitus välittää dataa sarjaportilla ollenkaan. Tarvin vain tuon keskeytyksen kun on tarkoitus saada oma ohjelman pätkä ajoon tietyin välein. Tuotan ulkoisella oskillaattorilla pulssit.
Koneen oma 10ms timer on aivan liian hidas tähän käyttöön.
Kuinkahan tuo listaamasi mahtaisi toimia jos keskeytyksiä on tarkoitus tulla 100us välein?
Ilmeisesti bufferi on luettava tyhjäksi kuitenkin joka kerta, vai?
Olen pari iltaa tavannut Linux device driver esimerkkejä joita tuntuu löytyvän helposti.
Vaikuttaa helpoimmalta tehdä oma device driveri ja korva sillä joko COM tai PAR portin alkuperäinen.
Linuxiin on vain muuten pieni kynnys. Asia kyllä houkuttaa driveriohjelmoinnin helppouden vuoksi.
Olikos Linuxin Gnome ympäristöön sellaista kääntäjää missä voisi tehdä käyttöliittymän vähän niin kuin visual basicissa tms. vastaavissa missä formiin lätkitään nappeja ja kääntäjäympäristö tuottaa valmiit koodirungot eri hiiri tms. eventtejä varten?
Kannattaa tutustua sarjaliikennettä hoitavan UART piirin toimintaan. Puskuri on luetteva tyhjäksi jos siellä on tavaraa. Lähes aina joutuu lukemaan tai tekemään jotakin jotta saa minkä tahansa väylällä olevan oheispiirin laskemaan keskeytyspyyntö (IRQ) linjansa alas. Jos UART piiri nostaa IRQ linjan ylös vastaanotto puskuriin tulleen merkin seurauksena, niin tällöin UART laskee IRQ linjan alas vasta kun UART:n vastaanotto rekisteri luetaan. Jos UART nostaa IRQ linjan ylös tilarekisterin muuttumisen seurauksena (esim. sarjaportin CTS, DTD linjojen tilaa muutettu), niin tällöin UART vetää IRQ linjan alas vasta kun UART:n tila rekisteri luetaan. Jos UART piiri nostaa IRQ linjan ylös lähetys puskurin tyhjenemisen seurauksena, niin tällöin UART vetää keskeytys linjan alas vasta kun UART:n lähetys rekisteriin kirjoitetaan uusi merkki tai kun sen keskeytys tieto rekisteri luetaan. Jne.. Jos IRQ linjan jättää ylös eikä kyseistä IRQ numeroa ole estetty (PIC (Programmable Intterrupt Controller) piirin avulla) jää prosessori silloin käytännössä jumiin (koska se keskeyttää koko ajan).
Jos pystyt tekemään laitteen joka lähettää tavun sarjaporttiin aina kun haluat keskeytyksen, on merkin vastaanotosta aiheutuva keskeytyksen väli silloin minimissään: 10/SARJALIIKENTEEN_BITTI_NOPEUS. (eli 1/TAVU_NOPEUS). Esimerkiksi 115 200 bps:n nopeudella saisit tehtyä laitteellasi 86 us:n välein/resoluutiolla keskeytyksiä, (tämä tietysti vaatii sen, että tietokoneen: 1) UART piiri on alustettu kyseiselle 115200:n nopeudelle. 2) UART piiri on alustettu siten, ettei käytä sisäistä puskuria vaan nostaa keskeytyspyyntö linjan ylös heti kun yksikin merkki on saapunut. 3) ohjelmasi/keskeytysrutiinisi lukee merkin vastaavalla nopeudella UART:n puskurista pois).
Helpoiten saat sarjaportista keskeytyksen aikaan, jos vain muutat sarjaportin jonkin tila linjan tilaa (kuten CTS, DTD, RI), jolloin et tarvitse laitetta joka lähettää tavuja sarjaväylään. Esim. viet oskillaattorin sarjaportin RI linjaan ja sitten vain SetCommMask/WaitCommEvent käskyjen avulla odottelet EV_RING tapahtumaa (jos käytät Windowsia).
Jos olet tekemässä jotakin kovin tarkkaa aikavälin mittausta, kannattaa pitää mielessä, etteivät Windows ja Linux ole kovin reaaliaikaisia käyttöjärjestelmiä eikä sarjaportin IRQ prioriteetti ei ole kaikkein suurin (jolloin muiden laitteiden tuottamat keskeytykset ajavat sen ohi).
Kuten jo joku tässä ehti todetakin varmaan paras tapa toteuttaa homma, on käyttää jotain modeemin kontrollisignaalia aiheuttamaan keskeytys.
Signaaligeneraattorin kytkemineen Rx linjaan edellyttäisi jonkin verran UARTin tai tässä tapauksessa DCB:n konfigurointia. Ja tietysti bufferin tyhjentämistä sen täytyttyä. Tosin käyttis lukee varsinaisen portin HW puskurin. Kuitenkin myös käyttiksen puskurin täyttymisestä tulee virheilmoitus.
Näille signaaleille on valmiina liittimessä pinni ja johdotus, jos käyttää jotain modeemin ja sarjaportin väliin tarkoitettua kaapelia. Täytyy vain tarkistaa minkänumeroisessa pinnissä on mikin signaali.
Taas lainauksia MS:n knowledge basesta.
http://msdn2.microsoft.com/en-us/library/ms810467.aspx#serial_topic1
Eli ensin valitaan mitä pinniä/johtoa käytetään aiheuttamaan keskeytys.
DWORD dwStoredFlags;
dwStoredFlags = EV_BREAK | EV_CTS | EV_DSR | EV_ERR | EV_RING |\
EV_RLSD | EV_RXCHAR | EV_RXFLAG | EV_TXEMPTY ;
if (!SetCommMask(hComm, dwStoredFlags))
// error setting communications mask
Näistä voi valita jonkin eventin. Vaikka EV_RING.
Tällöin koodi olisi:
DWORD dwCommEvent;
if (!SetCommMask(hComm, EV_RING))
// Error setting communications mask
return FALSE;
if (!WaitCommEvent(hComm, &dwCommEvent, NULL))
// An error occurred waiting for the event.
return FALSE;
else
// Event has occurred.
return TRUE;
Eli aina kun RING-piuha vaihtaa tilaa, tulee sarjaporttiin keskeytys ja ohjelma palaa WaitCommEvent-ohjelman kutsusta. Sitten voi suorittaa oman ohjelmansa ja palata taas odottamaan seuraavaa keskeytystä.
Muita linjoja on esim. CTS ja DTR ja niihin liittyvät eventit.
EV_CTS The CTS (clear-to-send) signal changed state. To get the actual state of the CTS line, GetCommModemStatus should be called.
EV_DSR The DSR (data-set-ready) signal changed state. To get the actual state of the DSR line, GetCommModemStatus should be called.
"Kuinkahan tuo listaamasi mahtaisi toimia jos keskeytyksiä on tarkoitus tulla 100us välein? "
Ehtiikö kone sitten palvelemaan keskeytyksiä 100us:n välein riippuu paljon koneen suorituskyvystä ja kuormituksesta. 115 000 baudia eli n. 10 000 tavua sekunnissa ei ole mikään ongelma PC:lle sarjaliikennenopeutena. Joten jollei PC:tä paljoa kuormita luulisi sen pystyvän palveleen 10 000 keskeytystä sekunnissa.
Itse olen aikoinaan käyttänyt vain gcc:tä, makefilejä ja debuggeria. Kuitenkin nykyään ilmeisesti kaikki IDE:t ovat menossa tuohon drag-and-drop systeemiin.
Nopea vilkaisu googlella kertoi seuraavaa:
Yleisimmät ympäristöt ovat:
http://www.linux.com/base/ldp/howto/Programming-Languages-3.htmlt
Eli GNOME:ssa olisi tuo GTK ja KDE:ssa QT.
Helpoimpana käyttää mainostetaan GNUstepia.
http://wiki.gnustep.org/index.php/Developer_Guides
Kaupallisena tuotteena voisi mainita KYLIX:n, joka on Borlandin Delphiä vastaava tuote Linuxissa.
Jotkut enemmän graafisia käyttöliittymiä Linuxiin tehneet voisivat varmaan kertoa kokemuksistaan.
Linuxin löytyy sellainen kääntäjä missä voisi tehdä käyttöliittymän vähän niin kuin visual basicissa tms. vastaavissa missä formiin lätkitään nappeja ja kääntäjäympäristö tuottaa valmiit koodirungot eri hiiri tms. eventtejä varten (ja joka toimii myös Gnome ympäristössä)?
http://linux.fi/index.php/Lazarus
http://wiki.lazarus.freepascal.org/Main_Page/fi
Jos UART piirisi on kiinni 33 Mhz PCI väylässä (ja väylältä lukuun tarvitaan esim. 2 väylä jaksoa) ja keskeytys rutiinisi lukee UART piiriä 10 kertaa keskeytyksen aikana, syö ne 10 hassua konekielistä käskyä keskeytys rutiinissasi koneen tehosta 100*10000*10*2/33000000 = 0,60 % (jos siis keskeytys tulee 10000 kertaa sekunnissa, eli 100 us välein). Jos koneessasi on 330 Mhz muisti väylä (ja väylältä lukuun tarvitaan esim. 2 väylä jaksoa) ja keskeytys rutiini kirjoittaa muistiin 100 kertaa, syö ne 100 hassua konekielistä käskyä keskeytys rutiinissasi koneen tehosta 100*10000*100*2/330000000 = 0,6 % (tätä tietty prosessorin sisäinen välimuisti voi pienentää). Keskeytys rutiinisi aiheuttama kuormitus on jo näillä tiedoilla 0,6% + 0,6% = 1,2 % koneen tehosta kun sitä kutsutaan 10000 kertaa sekunnissa, (ja sekin voi olla optimaalinen arvio).
Reagointi aika ja kuormitus on täysin kiinni koneesta. Uusissa koneissa UART piiri löytyy usein USB väylän takaa (aivan kuten näppäimistö ja hiirikin), ja USB väylän ohjain piiri on taas kiinni esim. PCI väylässä. Jos tarkoituksesi on tällöin välittää 10 000 keskeytystä USB väylän läpi, niin kuormitus aste on täysin toista luokkaa, puhumattakaan siitä pystyykö USB väylän läpi edes välittämään 10000 keskeytystä. Yleensä USB väylän läpi välitetään useampitavuisia paketteja kerralla joka aiheuttaa yhden keskeytyksen, käyttöjärjestelmä toki voi kääntää nämä niin että sovellus ohjelmalle ne näkyvät kuin sille tulisi yksi merkki kerralla sarjaportista.
Minkälaisia vaatimuksia olet asettanut? Onko tärkeää vain se, että pystyt laskemaan pulssien määrän (jolloin sillä ei ole väliä saako ohjelmasi tiedon yksittäisen pulssin/keskeytyksen tulosta 100 uS vai 2s viiveellä ja siitä onko kyseinen viive vaihtelevan pituinen). Vai onko tärkeää se että saat tiedon pulssien/keskeytysten välistä mahdollisimman tarkasti (jolloin taaskaan sillä ei ole väliä saako ohjelmasi tiedon yksittäisen pulssin tulosta 100 uS vai 1s viiveellä, vaan sillä että tuo viive on vakion pituinen). Vai onko tärkeää se että saat keskeytyksen/tiedon mahdollisimman nopeasti ja tarkasti, eli viiveen pitää olla vakio ja mahdollisimman pieni.
Jos haluat sekä tarkkuutta että nopeutta, niin silloin tuo Windows:n koodin tuskin toimii kovin hyvin tuolla nopeudella, ja linuxissakin joudut menemään laite tasolle. Mikään ei takaa ettei käyttöjärjestelmä vaihda suoritukseen toista sovellusta tuossa heti WaitCommEvent käskyn jälkeen tai että käyttöjärjestelmä antaisi ohjelmallesi heti suorituksen kun se saa sarjaportilta keskeytyksen. Jos riittää vain se että saat tiedon pulssin tulosta mutta viiveellä ja sen muuttuvuudella ei ole haittaa, niin silloin tuo 100 uS ei ole ongelma.
Jos kuitenkin haluat kaapata itse sarjaportin keskeytyksen joudut koodaamaan laitteisto tasolla. Tällöin joudut etsimään sellaisen koneen jossa on UART piiri koneen väylässä kiinni, jos haluat kaapata UART piirin aiheuttaman keskeytyksen. Jos koneessa on sarjaportti USB väylän takana, tällöin luonnollisesti joudut opiskelemaan USB:n ohjausta, koska ainoa keskeytys jota prossessori voi saada, johtuu USB väylästä.
No, kuitenkin jos se ei haittaa, että saat tiedon pulssin tulosta viiveellä ja tuo viive on vaihteleva, niin silloin tuo Windows koodi toimii vallan hyvin, (todennäköisesti silloinkin vaikka se sarjaportti olisi USB väylässä kiinni).
Tässähän tulee kahdeltakin kaverilta inffoa niin, että hengästyttää. KIITOSta vaan molemmille.
Tuo sarjaportin keseytysjuttu on juuri sitä mitä haen, tosin Linuxissa homma voisi mennä vielä helpommin mutta siihen pitää paneutua erikseen.
Löytyyköhän Windossissa sarjaportin keskeytyskuviota vastaavaa tuke myös printteriportin keskeytykselle?
Rinnakkaisportti on kuitenkin se mitä käytän joka tapauksessa I/O avaruuden luomiseen.
Eipä se silti haittaa vaikka sarjaporttia käyttäisi kellokeskeytyksen tuottamiseen.
Ai niin nuo portit on joko emolla tai vain tuota käyttöä varten olevilla PCI-korteilla joten kaippa niiden pitäisi toimia suoraan.
Pieni huojunta käsittelyssä ei haittaa. kunhan se ei mene mahdottomuuksiin. Tehoa ei lopullisessa kohteessa kovin paljon muuhun tarvita. Mitään graaffista käyttöliittymääkään ei tarvitse päivitellä vaan koko kone on pyhitetty ... koneen ohjaamiseen. Voipi olla käännän softan komentorivipohjaiseksi.
Sekä sarja, että rinnakkaisportit on joko emolla tai sitten korttina väylässä joten USB:tä ei tulee. Taidan jopa poistaa sen tuen loppukoneesta.
Tuota "kelloa" käytän askelmoottorin pulsseja generoivan alirutiinin ajamiseen. Maksimi ulostulo nopeus tulee olemaan n. 2-3kHz, eli ehkä neljän keskeytysnaksun välein (400us). Tällöin jos yksittäinen naksun keskeytysajoitus heittää vaikka 50us, lopullisessa ohjauksessa tuo on vain satunnainen 10% heitto joka ei häiritse.
Lopullinen kone on näillä näkymin 700MHz mobile pentium (läppäri). Tuon naksutuksen ja väylien ajamiseen saa kulua puolet koneen ajasta, vaikka epäilen ettei se ihan niin paljon hukkaa.
NT:ssä ja XP.ssähän ongelmana on, ettei sovellusohjelma voi lukea tai kirjoittaa IO-portteihin. Rinnakkaisportin käsittelyyn tarvitaan ns. kernel modessa toimiva device driver tyyppiä oleva ohjelma tai ohjelmakirjasto.
Riippu kääntäjästä, miten tuo divece driver tehdään. Kuitenkin niitä on saatavana verkosta, tarvittaessa jopa source koodeineen. Esimerkkinä Lazaruksen inpout32.dll for Windows ja sen dokumentteja.
http://wiki.lazarus.freepascal.org/Hardware_Access
http://logix4u.net/Legacy_Ports/Parallel_Port/Inpout32.dll_for_Windows_98/2000/NT/XP.html
Nuo ohjelmat on usein tehty ainakin osittain assembly-kielellä, joten niiden muuttaminen saattaa vaatia hieman tavallista enemmän osaamista. Kuitenkin dll:n käyttö pitäisi onnistua, mikäli dokumentaatio on kattavaa. On myös huomattava, ettei tuon IO-dll:n tarvitse olla ohjelmoitu samalla kielellä kuin sovellutuksen.
Nuo tarkkuudet ovat jo sellaisia, että joudut tekemään todennäköisesti silloin itse keskeytys rutiinin, kuten ajattelitkin (tai ainakaan Windows XP puolella). Jos et tarvitse moniajoa tee DOS:lla (vaikka disketiltä käynnistyvä), niin pääset helpommalla. Tällöin ei tarvitse tehdä edes keskeytys rutiinia, opiskella PIC piirin tai edes kovin syvällisesti UART ja LPT portteja hoitavien piirien toimintaa, eikä käyttöjärjestelmä ole sotkemassa moniajolla. Alla esimerkki ohjelmasta:
--- ALKAA ---
/*
Esimerkki
Jakaa sarjaportin RI nastaan tulevan
pulssin taajuuden neljällä, ja antaa
ulostulon rinnakkaisportin
D0-D4 nastaan.
*/
#include
#include
/* Sarja ja rinnakkaisportin perusportit */
#define COM1_BASE 0x3f8
#define LPT1_BASE 0x378
/* Sarjaportin staus rekistrin */
#define COM1_MSR COM1_BASE 0x6
/* Rinnakkaisportin data rekisteri */
#define LPT1_DATA COM1_BASE 0x0
int main(int argc, char *argv[])
{
int laskuri = 0;
int ri_linja = 0;
int ri_linja_vanha = 0;
while(1)
{
ri_linja_vanha = ri_linja;
ri_linja = _inpb(COM1_MSR) & 0x40;
/*
Kun tulee RI tulon nouseva reuna
tehdään toiminto
*/
if (ri_linja && !ri_linja_vanha)
{
laskuri = laskuri 1;
if (laskuri & 0x2)
_outpb(LPT1_DATA, 0x0f);
else
_outpb(LPT1_DATA, 0x00);
}
}
system("PAUSE");
return 0;
}
--- LOPPUU ---
Muutamia linkkejä:
http://www.beyondlogic.org/spp/parallel.htm
http://www.lammertbies.nl/comm/info/serial-uart.html#MSR
http://en.wikipedia.org/wiki/Input/Output_Base_Address
Jos otat ulostulon tietokoneesta, niin kannattaa varmaan käyttää samaa korttia myös lukuun. LPT portissa on tulo nastoja myös, joita voi käyttää. Yllä oleva ohjelma olisi muuten hitaampi jos se käyttäisi COM portin keskeytystä, koska keskeytys tavallaan aina aiheuttaa "sovelluksesta" toiseen siirtymisen.
Olen jo käyttänyt tuota inpout32 driveria menestyksekkäästi Bulder C kanssa joten se puoli on kunnossa.
Vaan mahtaisiko Windowsista löytyä samantapainen keskeytyksen wait-toiminta myös rinnakkaisportille kuin mitä tässä on esitelty sarjaportille?
Linuxissa asia olisi hyvin helppo kun noita valmiita templaatteja todellisiin device drivereihin ja jopa keskeytysketjuihin liittymiseen löytyy runsaasti.
Mutta tuo Windows ympäristön hyvä IDE tuki on plussaa.
Softan pitäisi puuhata muitakin IO juttuja ja vielä ns. pääohjelma asioita samaan aikaan. Keskeytys olisi mielestäni tällöin paljon parempi. Pollaamalla joutuisi koko softan pilkkomaan pieniin osiin joita käydään ajamassa yksi pätkä kerrallaan ja tullaan takaisin katselemaan olisiko ajastuspulssi jo tullut.
Kyllä joku 700MHz koneen pitäisi kyetä vastaamaan keskeytykseen jollain 20-50us vasteella kun siihen pystyi muinoin 6502 (1 MHz) :-)
Mutta pitää kokeilla COM-portin keskeytyksen odotusratkaisua josko olisi riittävän tarkka. Skoopin toinen kanava sisään menevää COM pulssiin ja toinen printteriportin lähtöön. Sitten vaan kuormaa koneeseen ja ihmettelemään miten viive heiluu.
Käytän nyt muun IOn ohjaamiseen printteriportin lähtöjä ja tuloja kellotettuna sarjaliikenteenä (synkroninen) jolloin pääsee rajoittamattomaan IO avaruuteen tosin nopeus hidastuu avaruuden kasvaessa. Jos tuo sarjaportin keskeytys juttu toimii käytännössä, taidan muuttaa printteriportin IOn tavunlevyiseksi (SPP mode) jolla saisi tehtyä niin, että ensin annetaan ulkoisen IOn osoite ja perään data. Vastaavasti luennassa ensi osoite ja sitten luetaan portista tavu. Tällä tekniikalla saisi 256 in ja 256 out tavua helposti. Tosin tuossakin pitää parilla lisä lähtönastalla ohjata ulkoiselle IO:lle:
* ADDR-strobe,
* R/W (ohjaa ulkoisten input piirien output enablea ja valitsee onko input vai outkut piiri),
* IO-piirien-strobe, joka olisi käytännössä chip select.
Eli jotenkin näin
Kirjoitus ulkoiseen IO piiriin (74HC574)
1. nollaa R/W (eli aiotaan kirjoittaa)
2. aseta rinnakkaisportti kirjoitus moodiin
3. kirjoita osoite porttiin
4. aseta ADDR-strobe
5. nollaa ADDR strobe
6. kirjoita data porttiin
7. aseta data strobe
8. nollaa data strobe
Luku ulkoisesta IO-piiristä (74HC245)
1. nollaa R/W (eli aiotaan kirjoittaa)
2. aseta rinnakkaisportti kirjoitus moodiin
3. kirjoita osoite porttiin
4. aseta ADDR-strobe
5. nollaa ADDR strobe
6. aseta rinnakkaisportti input moodiin.
7. aseta R/W (eli aiotaan lukea)
8. aseta data strobe
9. lue portista data
10. nollaa data strobe
11. nollaa R/W (eli aiotaan kirjoittaa)
Tämä viimeinen vain varmuuden vuoksi ettei SW-vikatilanteessa printteriportti ja mikään input-piiri mukiloi toisiaan
Kun ADDR strobe kertoo ulkoiselle systeemille, että portissa on osoite, se lukitaan 74HC574 tai muuhun 8-bitin lukkoon jonka jälkeen on vaikka 3->8 tai 4->16 muunnin ihan sen mukaan kuinka paljon IO-avaruutta tarvitaan.
Printteriportin kehittyneemmät modet toimii hyvin pitkälle samalla tavalla mutta vaativat vähän enemmän älyä ulkopuolen logiikalta.
Näin olen tuuminut :-) Keskeytysten hallinta Windowsissa vaan on ollut täysi mysteeri.
Enpä taida DOSiin mennä. Kun kehitystyökalut on uudemman Windowsin päällä, eikä niissä enää ole DOS tukea. Ja juu moniajo olisi myös tarpeen, se kun helpottaa monia asioita. kullekin erilliselle asialle voi olle oma prosessi/säie.
Jos jaksat niin kerro sitten tulokset odotus ratkaisusta (kuinka monta millisekuntia viivästyi), ja onko viiveellä/huojunnalla oleellista eroa jos ohjelman käynnistää high tai realtime prioriteetilla (Windowsissa). Oletko kasannut tuon elektroniikan koealustalle tai verolevylle, vaiko peräti teettänyt piirilevyn?
Nyt on tehtynä sellainen rauta missä siis käytän sitä synkronoitua sarjaliikennettä (clk, data-out, data-in, stobe-out, strobe-in) jossa kaikki on optoilla erotettuna. Tein luokan jolle anetaan ketjutetujen 8-bitin in ja out piirien määrä jonka jälkeen luokan primitiiveillä voi asettaa/lukea joko byteittäin tai suoraan tietyn bitin tietyssä piirissä. Oikeastaan nuo käsittelyt kohdistuu vain luokan sisällä olevaan puskuriin joka siirretään ulos/sisään erillisellä kutsulla, joka taas voisi tapahtua ihan perinteisellä timerilla vaikka 20ms välein tai tarvittaessa, miten vain. Tuo on luokka on tavallaan sarjapiirien (SIPO ja PISO) IO-API.
Ihan toimiva mutta hidas lopullseen tarpeeseen.
Optoerottimet on kuitenkin protoilukytössä tarpeen. Tuon tein Bebekin koekytkenälevylle. Yhden illan juttu.
Se rinnakkaispohjainen tulee myös varmaan tehtyä protolevylle. Oikeastaan tuosta saisi helposti eräänlaisen väyläratkaisun jossa olisi se 8bit osoite ja 8bit data ynnä kontrollit. verolevy toimisi hyvänä emona ja siihen pystyyn 32 nastaisia euroliittimiä sopivin välein. Tuollainen "emo" itseasiassa onkin jo vanhoista projekteista valmiina. (oli olevinaan Z80 kone joka ei ikinä valmistunut).
Suurin osa in ja out piireistä tullee olemaan suoraan 8bit AD ja DA muuntimia joita voisi pakata euro-kortille niin paljon kuin sopii.
Ajatukset on jossain tämän suuntaisessa
http://www.kolumbus.fi/robman/robotit.htm
Tai tämän suuntaisessa
http://www.youtube.com/watch?v=cY8s5nuGPwI&mode=related&search=
Tässä pieni pätkä koodiprotoa jolla luetaan n-kipaletta 8bit PISO piirien dataa sisään.
kaikkea tuosta ei käy ilmi mutta ehkä jotain.
Pistin perään vielä koodin pätkän mikä odottaa halutun ajan mikrosekunteina. Siis todellakin odottaa loopissa.
Ja tyyli on sitten protoilua :-)
---------------------------------------------
int port_class::transfer_RD_data()
{
int byte_cnt;
BYTE bit_cnt;
// prepare input chips for reading
strobe_RD(); // note output port has value 0 after this
// input chips are now ready for reading
// bytes are sent starting from last one in buffer
for(byte_cnt=size_RD_buffer-1; byte_cnt>=0; byte_cnt--)
{
RD_buffer[byte_cnt]=0;
for(bit_cnt=128; bit_cnt; bit_cnt>>=1)
{
// read input bit
if(get_port_value()&port_data_in)
RD_buffer[byte_cnt]|=bit_cnt;
set_port_value((short)port_clk); // set clock to hi
wait_micro_seconds(opto_delay); // wait until clock is valid after opto
wait_micro_seconds(5); // wait until 4021 has set up value
set_port_value(0); // set clock to lo
wait_micro_seconds(opto_delay); // wait until clock is valid after opto
}
// one byte has been red
}
// all bytes has been red
return(0);
}
-------------------------------------------------------
void port_class::wait_micro_seconds(int delay)
{
unsigned __int64 dd1, dd2, ff;
QueryPerformanceCounter((LARGE_INTEGER*)&dd1); // get time when enterd
// clock speed is calculated every time separately because the clock may
// change due to dynamic CPU clock and voltage control
// this is insied wait 'loop' because the begin time is red before
QueryPerformanceFrequency((LARGE_INTEGER*)&ff); // get clock speed
ff/=1000000; // 1us
ff*=(__int64)delay; // calculate neede delay
dd1 =ff; // define the moment when delay is over
do
{
QueryPerformanceCounter((LARGE_INTEGER*)&dd2);
} while(dd2
Rinnakkasiportin keskeytys PC:ssä on hieman ongelmallinen juttu. Yleensähän sitä ei käytetä. Tai sitten se on ihan muiden laitteiden kuin printerin käytössä. Siis esim. äänikortin tai jonkin muun laitteen. Periaatteessa kuitenkin jokin IRQ7 on varattu rinnakkaisportin keskeytystä varten. Ainakin DOS:ssa oli näin.
Mihinkä sinä sitä keskeytystä tarvitset? Onko tarkoitus syöttää kilokaupalla dataa sekunnissa printeriportin kautta sisään. Tai pitääkö bitin muuttumiseen reagoida millisekunneissa?
Kyseessähän on vain tavun lukemisesta io-portista silloin kun sitä tarvitaan. Miksi siihen keskeytystä laitettaisiin?
Minä en ainakaan koskaan ole käyttänyt keskeytysportissa mitään keskeytystä.
No joo ei byte IO portin kautta tarvitsekaan keskeytyksiä mutta tarvitsen ohjelmalleni n. 8-10kHz ajastuksen jolla ohjaan askelmoottoreita. Koska Windowsissa tuota ei varmasti(?) saa kellokeskeytyksestä, tarkoitus on ottaa se ulkoisesta oskillaattorista.
Sinänsä melkein sama tuonko tuon sisään sarja vai rinnakkaisportin kautta. Sarjaportissa vaan pitää tehdä tasomuutos.
Eli oikeastaan vastaus on kyllä. Tarkoitus on ajaa kilokaupalla askelmoottoripulssidataa ulos kellon ohjaamana :-)
Itselläni ei ole kokemusta, mutta oletko ajatellut ulkoisen keskeytys oskillaattorin sijaan käyttää suoraan jotakin reaaliaika käyttöjärjestelmää? Huomasin vain että alla olevassa artikkelissa RT-Linuxin ominaisuuksia oli testattu juuri tuolla sovellusalueella mihin olet hakemassa ratkaisua, (eli generoimassa pulsseja askelmoottorille). Näyttivät päässeen 100us pulsseissa 15us variaatioon, vaikka samaan aikaan käyttöjärjestelmää kuormitettiin muillakin prosesseilla, levykirjoitusta, verkkokäyttöä, X-Windows, yms.
http://www.soe.ucsc.edu/~sbrandt/courses/Winter00/290S/rtlinux.pdf
http://www.aeolean.com/html/RealTimeLinux/RealTimeLinuxReport-2.0.0.pdf
Googlesta käsitin, että tuon rtlinux kernelin voisi installoida tavallaan vanhan kernelin "päälle", mutta tiedä siitä sitten.
Kyllähän RT käyttis on aina parempi mutta toisaalta koneessa ei tule pyörimään käyttöliitymää, eikä paljon muutakaan.
Suurin kynnys on kuitenkin se, että en ole milloinkaan tehnyt koodia Linuxiin ja uusien työkalujen opettelu Borlandin IDEn jälkeen on kuin raapisi selkää teräsharjalla, erilaista mutta kivuliasta :-)
Kokeilin kuinka hyvin taski pysyy tahdissa Windowsissa, käyttämällä sitä CPU-kelloa mikä on täysin HW pohjainen. Jos taski on korkeammalla prioriteetilla kuin muut taskit, heilunta jää us tasolle, vaikka keskeytyksiä palvellaan. Tuo testi ajoi loopissa viivettä ja vertasi kuinka paljon viiveen jälkeen aika erosi siitä mitä sen piti olla. Tuon viivettä sitten laskettiin yhteen ja lopuksi jaettiin looppien määrällä. Ei kovin hyvä testi mutta kertoi jotain.
Heti jos oli taskeja samalla prioriteetilla reilusti, homma puuroutui.
Kehittelen nyt ensin hardista pidemmälle ja yritän katsella Linuxian koneeseen samalla mutta en ota mitään tavoitetta Linuxin suhteen. En usko, että yhtä helppoa kehitys/debuggausympäristö löytyy kuin mitä Builder C on.
En ole vielä edes aivan varma tarvitaanko suurtaajuus keskeytystä ollenkaan, eli käytänkö askelmoottoreita vai en. Koneen oma 10ms timer-tuki riittäisi paikoittavan DC-servon tekemiseen ihan hyvin. On silti hyvä varautua pahimpaan.
Askelmoottoreissa on muuten pari ikävää puolta, paino tehoon nähden, hinta, hyötysuhde ja tietyissä sovelluksissa nopeus.
Eräs viimeisin ideani DC servoon on tällainen:
Kone ohjaa IOn kautta DA-muunninta joka ohjaa PWM tehoastetta joka syöttä moottoria (ei mitään uutta). Moottorin akselilla on pulssianturi josta pulssit viedään 8 tai 16bit laskuriin jota kone lukee IOn kautta 10ms välein.
Laskurin ei tarvitse olla kuin ylös laskeva koska laskuri laskee vain siihen suuntaan mihin moottori pyörii. Softalla on helppo huomioida tuon ulkoisen laskurin ylipyöriminen. 8bit laskuri toimisi tuolla 10ms lukuperiodilla jos pulssitaajuus ei nouse yli 25kHz. Eli resoluutiota löytyisi kovaankin menoon. 16bit pysyisi mukana 6.5MHz asti.
Paikoitus algoritmi hidastaisi moottoria ennalta lasketussa kohdassa tähän 10ms resoluutio riittää hienosti. Varsinainen paikoitustieto olisi tietysti vain koneessa koska tuo laskuri pyörisi vain ylös päin.
Tämä vaatii vielä jonkin anturin jolla liikkeen nollapiste löydetään, kuten askelmoottorisovelluksissa. Hyvä puolisi olisi, että ei tarvita suurtaajuus keskeytystä. Toinen hyvä puoli on se, ettei tahdista putoamista tarvitse pelätä. Jos vielä mitataan moottorin virtaa, saadaan hyvinkin suuntaa antava kuva momentista.
Koska tuo 10ms timer heiluisi kuin heinämies, todellinen kulunut aika edellisestä timerista luettaisiin CPU-kellosta. Tämä pitää huomioida paikoitusalgoritmissa mutta se ei pitäisi olla ongelma.
Mitä varten muuten yrität tehdä tuon kaiken sarjaportin ja rinnakkaísportin kautta. Hankkisit heti PC:hen lisäkortin, jossa on I/O sekä pulssilaskurit. Tarvittaessa kortti toimisi keskeytysten kautta. Mukana tulisivat myös tarvittavat laitedriverit kaikille käyttöjärjestelmille.
http://www.accesio.com/go.cgi?p=../104/104-iiro_8.html
Esimerkin kortti ei ole halvimmasta päästä. Laitoin vain esimerkin vuoksi. Halvempiakin varmaan löytyy. Tuotesivulle on myös esittelyjä analogiakorteista, joilla on A/D muunnos ym.
Tuo printteriportin käyttö I/O:n tekemiseen on amatöörien kikkailua. Lisäkortti ja tarvittavat laiteohjaimet maksavat n. 200 euroa. Jos kaiken tekee itse, ei varmaan pääse tuntipalkoille.
Niin siis, pulssilaskuri oli analogiakortilla.
http://www.accesio.com/go.cgi?p=../104/104-iiro_8.html
Ja tarkan kellokeskeytyksen voi tehdä ulkoisen oskillaattorin sijasta Watchdog kortilla.
http://www.accesio.com/go.cgi?p=../pci/pci-wdg-csm.html
Valitsee vain sopivan IRQ:n keskeytykselle. Timeri laskee ajan 1 uSec;n tarkkudella.
Joo, kun tuon härpäkkeen pitäisi olla liikkuva niin perinteinen PC ei tule kyseeseen ja aionkin käyttää vanhaa läppäriä jonka näyttö sai siipensä kun kone putosi pöydältä. Rinnakkaisportti on aivan riittävän puhdikas ja ulkoiset piiri vaivaton tapa laajentaa sitä.
Jostain syystä tuo odotusfunktio menee aina erroriin ja jos hakee virhekoodin Getlasterror funktiolla saa arvon 87 joka tarkoitaa... jotain.
No pitää tutkia asiaa jahka kerkiää. Mikkisoftin dokumentointi ei kyllä ansaitse kovin hyvää arvosanaa.
Siis aikomuksena on ohjata moottoreita ja keskekytyksen avulla lukea moottoreiden asennot laskureista ja päätellä ovatko moottorit oikeassa asennossa?
Eikö tuo olisi parempi ulkoistaa raudalle? Eli normi step/dir-ohjain moottorille, ja keskeytys generoidaan vain mikäli moottori ei pääse ajettuun asentoon?
Joudut joka tapauksessa perehtymään sarjaliikenteeseen ja UART piirin toimintaan jos aiot käyttää sitä keskeytyksen lähteenä. ISA väylästä varmaan saisi suoraankin vedettyä halutun IRQ linjan ulos koneesta johdon pätkällä (ehkä vaatii smith triggerin koneen päähän). Alla jotain linkkiä linux puolelle.
http://hegel.ittc.ku.edu/topics/linux/man-pages/man9/request_irq.9.html
http://www.linux-security.cn/ebooks/ulk3-html/0596005652/understandlk-CHP-4-SECT-6.html
http://www.laitetekniikka.com/laitteet/yleistietoa/irq.htm
http://www.helpwithpcs.com/upgrading/change-irq-settings.htm
Helppoa kuin letunpaisto, riippuen vähä kielestä millä luet, jokaista rs portin signaalia on helppo seurata.
kiinni mihin vaan Windowsissa.