Kapitel 1 Grunderna i stegkodsprogrammering Följande bilaga innehåller grunderna i stegkodsprogrammering i den form som används under kursen. Vi kommer att kort diskutera olika datatyper, villkor, operationer och strukturer som främst används i programvaran RSLogix. En del av det diskuterade materialet är även tillämpligt i andra programmeringsspråk. Vi skiljer på operationer och villkor när vi programmerar. Vanliga villkor kan exempelvis vara IF-satser och matematiska villkor (såsom olikhet, likhet, osv.). Operationer kan vara multiplikation, addition eller manipulation av enskilda bitar. Vi gör även skillnad på register- och bitvisa operationer/villkor. Den första typen förändrar/kontrollerar talet som lagras i hela registret och den andra typen förändrar/kontrollerar bara värdet av en bit 1. Slutligen kan vi placera operationer och villkor i strukturer är att göra mer avancerade program. Vi kommer nu i tur och ordning redogöra för dessa begrepp i mer detalj och även ge exempel på hur de kan användas. 1.1 Datatyper och variabler Datatyper kallas de olika typer av data som man kan spara i variabler och liknande. I de flesta programmeringsspråk idag är dessa av typen double vilket är stora flyttal (decimaltal). Inom PLC-programmering är det dock mer vanligt med typen integers (heltal) av en relativt begränsad storlek. Vanligt är att varje integer (i vårt fall) kan innehålla maximalt 16 bitar (varav en teckenbit). En annan typ av data är flaggor eller booleanska variabler, dessa kan antingen vara 0 (falskt) eller 1 (sant). Varje integer kan således innehåller 16 flaggor. 1 Kom ihåg att ett register Nx:y innehåller 16 bitar. Vi når hela registret om vi använder den tidigare hänvisningen och vi kan nå bit z genom att använda oss av adressen: Nx:y/z där z = 0, 1,..., 15 och 15 betecknar registrets teckenbit. 1
2 KAPITEL 1. GRUNDERNA I STEGKODSPROGRAMMERING Variabler av heltalstyp lagras som ett register under en datafil. En datafil benämner man som Nx där x är något heltal. Ett exempel på detta är det första registret i datafilen N9 som betecknas N9:0. Om vi istället vill använda registret för att lagra flaggor kan vi använda följande beteckning Nx:y/z där y är något register och z är en bit i registret. I N9:0 kan vi därför lagra flaggor med namnen N9:0/0 upp till N9:0/15 (som också är teckenbiten). Egentligen lagras heltalen i varje register som ett binärt tal. Detta innebär att ett 16-bitars heltal egentligen är 16 platser som vardera kan innehålla 0 eller 1. Vi kan därför se ett heltal som 16 lådor där varje låda också kan vara en flagga. Vi kan undersöka innehållet i en låda genom att använda /-operatorn. Om vi använder oss av operationer som påverkar hela registret kommer vi antagligen att påverka alla bitar/lådor som finns på den adressen. Därför kommer vi göra skillnad på register- och bitvisa operationer. Det är även viktigt att endast använda ett register till enbart flaggor (där varje bit/låda representerar en enskild sak) eller för att presentera ett heltal (där alla bitar/lådor används för att representera en sak). 1.2 Villkor Vi fortsätter med några speciella villkor som finns att tillämpa i stegkodsprogrammering. Några exempel på dessa villkor finns i tabell 1.1 nedan. Det finns två Tabell 1.1: Vanliga villkor i stegkodsprogrammering Typ Kod Symbol Förklaring x:x/x Bitvis XIC x:x/x Testa för hög bit XIO Testa för låg bit Registervis EQU Testa = GEQ Testa GRT Testa > LEQ Testa LES Testa < Special TON Timer för nedklockning RES RES Nollställ timer enkla vanliga bitvisa villkor, nämligen testa för hög bit (ettsatt eller sann) och för låg bit (nollsatt eller falsk). Vi får passera genom dessa villkor om registret som villkoret pekar på faktiskt är ettsatt respektive nollsatt. Denna typ av villkor har alltså ett argument, det register som man vill undersöka om det är ettställt eller nollställt. För att använda oss av dessa jämförelser behöver vi bara använda
1.2. VILLKOR 3 respektive grind ur tabell 1.1 och byta ut argumentet x:x/x till rätt bitadress. Vi förändrar dessa bitar genom de två operation som diskuteras i det kommande avsnittet om operationer. Stegkodningsspråket som vi kommer att nyttja på kursen innehåller även matematiska villkor. Dessa är exempelvis att testa för likhet och olikhet. I tabellen ser ni några exempel på dessa uttryck som har två argument. Vanligtvis fungerar dessa block att om villkoret A B är uppfyllt får vi passera. kan exempelvis vara likhet (=) eller större än eller lika med ( ). I dessa block skriver vi således in argument på två rader där den första raden är källregister A och den andra är källregister B. Vi har därför två argument i denna typ av villkor. Vi avslutar diskussionen om dessa enklare villkoren med två exempel som visas nedan. I det första exemplet är villkoret att 5000 N9:1 och om detta är uppfyllt får vi passera villkoret. Lägg här märke till att vi kan använda oss av både register och tal i vår jämförelse. 5000 här kan bytas ut till vilket heltal som helst eller ett register, som vi snart kommer att se. I det andra exemplet är villkoret att talen som lagras i register N9:0 och N9:1 ska vara lika. Om talen är lika får vi passera villkoret och då utförs det som står på höger sida av samma rad. Även här är det tillåtet att byta ut ett av registren till ett heltal. Genom att använda förgreningar och placera dessa villkor i rad kan vi vidare skapa de flesta logiska satser. Detta kommer vi att diskutera i ett senare avsnitt. 1.2.1 Timers En sista typ av villkor är specialvillkor som bland annat innefattar timer och räknare. Timers byggs upp av ett block som ettsätter en bit när en visst uppräkning är slutförd. Som användare kan man ställa in hur ofta man ska räkna upp räknaren, alltså sätta i = i + 1, och vilket som är målvärdet i end. När man sedan når målvärdet med räknaren, alltså då i = i end ettsätter man biten DN annars (när i i end ) så är istället biten EN ettsatt. Ett timerblock ser i praktiken ut som följer:
4 KAPITEL 1. GRUNDERNA I STEGKODSPROGRAMMERING TON Timer Time Base Preset Accum Tn:0 t i end i start Tn:0/EN Tn:0/DN där Tn:0 är blockets namn, jämför PID-blocket i laboration 1. Time base indikerar hur lång tid som det ska ta innan man räknar upp variabeln Accum, alltså sätter i = i + 1. Time base beskriver denna tid i sekunder, så om man skriver 0.01 i denna variabel räknar man upp i hundra gånger per sekund, eller i 100 Hz. Preset är målvärdet som man önskar och Accum är det nuvarande värdet på i. Bitarna Tn:0/EN samt Tn:0/DN är ettsatta när vi har inte har nått respektive när vi har nått vårt målvärde. Vi avslutar detta med ett exempel där vi har döpt vår timer till T4:0: TON Timer T4:0 Time Base 0.01 Preset 1000 Accum 0 T4:0/EN T4:0/DN T4:0/DN (stuff) Blah... T4:0/DN T4:0 RES här har vi även lagt till ett villkor som kontrollerar om T4:0/DN är ettsatt. När detta villkor är uppfyllt kommer det som står i rutan stuff att utföras samt så kommer timern att nollställas av RES vilket betyder att Accum nollsätts, alltså i sätts till noll och uppräkningen påbörjas igen. I detta exempel har vi time base 0.01 sekunder (vi kommer alltid att använda denna time base, eftersom att vår PLC arbetar i 100 Hz) och preset är 1000, det kommer således att ta 0, 01 1000 = 10 sekunder innan vi ettsätter T4:0/DN. Därmed kommer vi att göra det som står i stuff -rutan vart tioende sekund. Lägg även märke till att vi kan använda oss av flaggan T4:0/EN för att utföra något annat när vi inte har nått målvärdet. Exempelvis kan detta vara en påfyllnadsprocess där påfyllnad sker sålänge som T4:0/DN är ettsatt och när T4:0/EN ettsätts som byts vagnen som ska fyllas på ut.
1.3. OPERATIONER 5 1.3 Operationer Operationer används för att förändra data som lagras i olika register. Detta kan vara att sätta registret till ett visst värde eller till något matematiskt uttryck som är funktion av andra register. I tabell 1.2 presenterar vi några vanliga operationer Tabell 1.2: Vanliga operationer stegkodsprogrammering Typ Kod Symbol Förklaring x:x/x Bitvis OTU U x:x/x Nollställa bit OTL L Ettställa bit Registervis MOV Kopiera ADD Addition SUB Subtraktion MUL Multiplikation DIV Division för både bitar och register. Bitvisa operationer har bara ett argument, nämligen den bit som man vill sätta till noll eller till ett. Vill vi således nollsätta biten som vi hittar på adressen S:5/0 (överspillsindikatorn) byter vi ut x:x/x operationen ovan och får följande: Registervisa operationer har två eller tre argument beroende på vilken typ vi använder oss av. Matematiska operationer har ofta tre argument och följer konventionen att A B = C där är exempelvis addition eller multiplikation. Sådana typ av block har alltså tre rader med register, två som kallas källregister (A och B) samt ett målregister C. Ett exempel på detta är: där A =N9:10, B =N9:11 och C =N9:12. På samma sätt kan vi även skapa block för multiplikation, division och subtraktion. MOV-blocket har endast två argument: källa och mål. Det flyttar således det som finns i källregistret till målregistret. Lägg märke till att vi även här kan byta ut ett av registren till något heltal om vi så vill. Detta kommer vi se exempel på i nästa avsnitt där vi behandlar strukturer i stegkodsprogrammering.
6 KAPITEL 1. GRUNDERNA I STEGKODSPROGRAMMERING 1.4 Strukturer Man kan slutligen placera operationer och villkor i strukturer för att skapa avancerade program. Den grundläggande strukuren i stegkodsprogrammering består av olika steg. Varje steg är placerad i en rung eller rad. Ett program exekveras sekvensiellt uppifrån och ned i detta språk. Detta innebär att raderna utförs en och en i den ordning som de står i programmet. Varje rad delas in i en villkors- och operationsdel. Villkor placeras till vänster på raden och operationer till höger. Det är tillåtet att placera flera villkor efter varandra (seriekoppling) men endast en operation tillåts på vardera rad. Ett exempel på en enkel rad med flera villkor är som följer: N7:0/1 S:5/0 N9:10/15 MOV 32767 N9:12 där vi flyttar talet 32767 till registret N9:12 om följande tre villkor är uppfyllda samtidigt: (i) biten N7:0/1 är ettsatt, (ii) biten S:5/0 (överspillsindikatorn) är ettsatt, samt (iii) biten N9:10/15 (teckenbiten) är nollsatt (positivt tal). I detta exempel kontrollerar vi alltså bland annat om vi har ett överspill (vi försöker lagra ett större tal än som får plats i registret) samt om talet är positivt. När detta är uppfyllt skriver vi det största positiva talet vi kan till registret N9:12. Det föregående exemplet var av en struktur med villkor i serie som motsvarar ett logiskt och. Alltså att alla villkor samtidigt måste vara uppfyllda för att något ska hända. En annan vanlig konstruktion är logiskt eller där det räcker med att ett villkor är uppfyllt. Denna typ av struktur motsvarar en parallellkoppling, där man lägger en förgrening så att villkoren ligger över varandra. Ett exempel på detta är: där operationen (som är utelämnad ur figuren) utförs om minst ett av villkoren är uppfyllt, alltså om N9:0 är större än N9:1 eller om N9:2 är större än N9:1. Vi kan även kombinera denna struktur med ett villkor som är kopplat i serie:
1.4. STRUKTURER 7 nu kräver vi även att N9:5/4 är ettsatt för att vi ska multiplicera N9:1 och N9:2 och lagra resultatet i N9:3. På detta sätt kan vi skapa stora komplicerade villkor där vi kombinerar jämförelser med logiskt och samt eller. Tidigare nämnde vi begränsningen med att endast en operation får utföras per rad. Detta är ofta begränsande och skulle leda till att vi måste skapa många rader med samma villkor men olika operationer. Vanligtvis vill vi ha så få rader som möjligt där vi försöker att återanvända (modularisera) så mycket av vår kod som möjligt. Detta brukar man betrakta som god programmeringssed. För att undvika begränsningen kan vi lägga ett antal operationer parallellt med hjälp av en förgrening. Detta leder till strukturer som liknar de som vi introducerade för logiskt eller. Som vanligt beskrivs detta enklast med två exempel. I det första exemplet lägger vi ihop två tal från registren N9:10 och N9:11 samt sparar resultatet i N9:12. Samtidigt så nollsätter vi biten S5:/0 som indikerar överspill (den ettsätts då vi försöker lagra ett större tal än möjligt i ett register). ADD N9:10 N9:11 N9:12 S:5/0 U Det andra exemplet är en multiplikation och omskalning av svaret. Vi kommer först att utföra divisionen och sedan multiplikationen. Kom ihåg att vi läser programmet uppifrån och ned. Först skalar (dividerar) vi om registret N10:13 med 128 och sparar resultatet i N10:14. Sedan utför vi multiplikationen mellan talen i registren N10:14 och N10:2 och lagrar detta i registret N10:14.
8 KAPITEL 1. GRUNDERNA I STEGKODSPROGRAMMERING DIV N10:13 128 N10:14 MUL N10:14 N10:2 N10:14 Här ser vi ett exempel på att vi återanvänder ett register. Först läser vi ett tal från ett register genomför någon matematisk operation och slutligen skriver det till samma register. Detta är en tillåten operation och det enda vi behöver tänka på är i vilken ordning saker utförs: läsa register, utföra beräkning, skriva till register. Vi ser också att det är det skalade talet som vi utför multiplikationen med, vi kan därför göra matematiska operationer i sekvens genom att bygga parallella strukturer. Slutligen borde vi även nämna att man kan skapa nestlade strukturer som är mer avancerade än de som vi har gjort här. I sådana strukturer kan man ha parallella rader (fortfarande som tillhör samma huvudrad) där men både utför operationer och villkor. Detta kommer dock att vara utanför denna kurs och är därmed inte intressanta för våra syften. 1.5 Praktisk programmering Vi avslutar detta dokument men en kort diskussion om hur man praktiskt går tillväga för att programmera ett program. Vi passar även på att nämna några saker som kan vara bra att komma ihåg och hur man skriver ett snyggt program. Som med de flesta saker blir man enbart duktig på programmering om man övar på att att skriva program. Programmering är till lika stor del en praktisk kunskap som en teoretisk. I princip har vi beskrivit all teori som krävs i detta kapitel för att skapa relativt komplicerade program. Det som inte har diskuterats är implementering, felsökning och andra praktiska aspekter. Dessa lär man sig bäst när man sitter vid datorn och provar själv. Ett bra sätt att strukturera sin programmering är att utgå från problemet och bryta ned det i delproblem. Sedan löser man varje delproblem för sig för att sedan plocka samman allt till en lösning av det ursprungliga problemet. För att underlätta detta är det bäst att börja med att skriva en pseudokod där man med enbart ord skriver ned vad som ska hända. Här identifierar man även de olika delar som programmet och problemet består av. Sedan försöker man hitta lösningar till vardera delproblem. Detta innebär att svara på frågor som vart man hittar den informationen man behöver, vilka villkor
1.5. PRAKTISK PROGRAMMERING 9 och vilka operationer som är nödvändiga. Tillslut bygger man upp en struktur som löser det problem man är ute efter. När man sedan har tänkt genom detta försöker man hitta grupper av villkor och operationer som återkommer. Detta kallas att modularisera sin kod och då försöker man återanvända så mycket av den som möjligt. Ska man exempelvis utföra samma beräkning på ett antal rader kanske man istället kan kombinera villkoren i en parallell struktur så att man kan slå ihop flera rungs till en. Detta kan även innebära att man skapar subrutiner där man genomför delberäkningar. Slutligen ska man alltid kommentera sin kod, både för sin egen del och för andras. Ute i industrin arbetar man ofta flera stycken med samma program och då behöver man beskriva vad man har gjort. Ni kanske även måste gå tillbaka i kod som ni skrev för flera år sedan. Att man då har kommenterat kort vad varje grupp av rader gör, kan spara mycket tid och möda. Tillsammans med detta tillhör även att döpa variabler till vettiga saker, detta hjälper både i felsökning och i dokumentation.