Maskinorienterad Programmering / Programmering av Inbyggda System

Hemuppgifter C-programmering

Övningsuppgifter kopplade till C-föreläsningarna

På denna sida finner du grundläggande övningsuppgifter i C. Lösningsförslag finns till alla uppgifterna (se nedan). Det finns givetvis ofta ett flertal rätt sätt att lösa en och samma uppgift. Dessa övningsuppgifter är mer orienterade mot vad ni behöver i just den här kursen jämfört med andra grundläggande övningar ni kan finna på nätet. Förhoppningsvis är de även lite roligare. De syftar bl a på att förbereda er för er sista laboration där ni får implementera ett valfritt spel (eller annan dylik app) på labdatorn MD407. Tanken där är framförallt att ni på egen hand skall kommunicera med periferienheter och kunna utnyttja interrupts.

Duktiga spelprogrammerare är ofta mycket attraktiva inom övriga industrin, medan omvänt är mindre självklart. Det är svårt att föreställa sig applikationer med mer diversifierade, mångfaceterade och allomfattande problemområden än spel. Dagens avancerade spel är inte sällan som små minioperativsystem, med en realtidskärna, parallellism, multitasking, high-performance computing, filhantering, nätverk, säkerhet, ljud, grafik och artificiell intelligens. Det mesta av detta omfattar färdigheter som ni tillskansar er i senare kurser. Ni har typiskt heller ännu inte läst om avancerade datastrukturer och algoritmer.

Grundläggande i realtidsspel är dock att de hanterar en mängd överlappande händelseförlopp och har inputs och outputs. Vad övningarna på den här sidan syftar till (förutom att ge er grundkunskaper i C, att adressera minne och manipulera bitar) är att även lära er bemästra hyfsat avancerade händelseflöden som inte nödvändigtvis följer en rak sekventiell ordning (något som ofta saknas i andra kurser och tas för givet att ni kan hantera). Men vi ska starta mjukt.

Hur som helst finns här andra grundläggande övningar för den som skulle vilja ha:
  • http://www.learn-c.org
  • http://www.tutorialspoint.com/cprogramming/
  • http://www.cprogramming.com/tutorial.html
  • Om man skall programmera stora projekt (större än i den här kursen) och under längre tid så vill man troligen använda en annan IDE än CodeLite, som t ex. Visual Studio (MS Windows) eller XCode (MacOS) - främst pga smidigare debugstöd. Men dessa fungerar för närvarande endast under respektive operativsystem - oavsett vad respektive företag anser och skriver på nätet.
          CodeLite är däremot utmärkt för kortare program (som i den här kursen) och ger i princip en identisk utvecklingsmiljö oavsett operativsystem (Windows, Linux, Mac). Däremot skiljer det emellanåt vilka include-filer resp. libraries som man behöver använda eftersom operativsystemen skiljer sig åt.

    Installera utvecklingsmiljön CodeLite





















    Skapa nytt workspace och project




    Slå på C99 för ditt projekt



    Build and run

    Om du sitter i en datasal på Chalmers
    Oftast fungerar den installerade CodeLite-miljön automatiskt. Får du problem med att GCC eller CrossGCC inte finns som alternativ när du skapar projekt, eller att inget händer när du kompilerar, titta på följande beskrivning om inställningar i CodeLite för sökvägar till GCC samt CrossGCC i datasal.

    Om du installerar på egen dator
  • Installera CodeLite från sidan Resurser på kurshemsidan. (Installera inte någon de senare versioner som finns på CodeLites hemsida, ty vi har patchat CodeLite för bättre debuggstöd mot MD407-plattformen som används i era laborationer längre fram.)
  • Du kan med fördel även installera ETERM, SimServer, samt GCC ARM (ingår redan för Windows i vår patchade CodeLite), så detta är avklarat inför uppgifterna i Arbetsboken samt Labbarna. Just nu behöver vi dock endast CodeLite. För hemuppgifterna kommer vi nämligen att till en början endast kompilera C-program mot din dators inbyggda processor (Intel / AMD). Senare kommer vi använda en korskompilator mot ARM-processorn på MD407-datorn.
  • För Mac OS måste du även installera XCode om du inte redan gjort det. Detta för att få en standardinstallation av GCC för Intel-processorn. Linuxanvändare behöver också se till att gcc finns installerat.
  • Skapa ett tomt workspace och ett nytt projekt. Projektet skall vara av typen "Console -> simple executable (gcc)".
          För Mac OS väljer du compiler: GCC och Debugger: LLDB.
          För Windows väljer du compiler: GCC och Debugger: GNU gdb.
    Försök helst utan hjälp, men behöver du instruktioner så finns det här.
  • Vi vill gärna använda C99. Slå på detta genom att högerklicka på projektet och välja Settings->Compiler->"C Compiler Options" och klicka i "Enable ANSI C99 features". Försök helst utan hjälp, men behöver du instruktioner så finns det här. Observera att du behöver slå på C99 för varje projekt du skapar.
  • Kompilera och testkör programmet. Behöver du instruktioner finns det här.
  •  

    C - Föreläsning 1.

    Uppg. 1
    Gör en for-loop som skriver ut "I am an awesome C programmer" 10 gånger.
    #include <stdio.h> int main() { for(int i=0; i<10; i++) { printf("I am an awesome C programmer\n"); } return 0; }
    Uppg. 2
    Skapa en sträng och skriv ut den baklänges.
    #include <stdio.h> #include <string.h> int main() { char str[] = "I am a great programmer"; for(int i=strlen(str)-1; i>=0; i--) { printf("%c", str[i]); } return 0; }
    Tips: Googla på "length of a string in c" så finner du att funktionen strlen() returnerar längden av en sträng. Googla även på "strlen header" så ser du att strlen() är definierad i string.h vilken behöver inkluderas och är en systemheaderfil.

    Kort om #include-direktivet:
    #include <file>
    This variant is used for system header files. It searches for a file named file in a standard list of system directories.
    #include "file" This variant is used for header files of your own program. It searches for a file named file first in the directory containing the current file and then in the system directories.





    Nu ska vi ta en titt på vilka funktioner som faktiskt finns fördefinierade i C...

    I den här kursen använder vi ANSI C99 och GNU-kompilatorn GCC (på Windows använder vi Min-GW som är en portning av GCC för Windows). Andra C-kompilatorer som finns är t.ex. LLVM (clang), Visual C++ samt GCC-ARM-kompilatorn som vi använder senare som korskompilator för labdatorn.

    C Quick Reference Guide

    En översikt över C-språket samt de funktioner som ingår i standardbiblioteken (och resp. headerfil som behöver inkluderas). C Reference Card (ANSI)
    Den som ändå skulle vilja ha en fullständig GNU C referensmanual finner detta t ex här: GNU C Reference Manual.

    C standard libraries

    Noggrannare beskrivning av funktionerna i standardbiblioteken finner ni här: http://en.cppreference.com/w/c/header
    Tanken är att dessa funktioner skall vara identiska för alla ANSI-C-kompilatorer och operativsystem. Skillnader kan dock ändå finnas, som t ex för ARM-kompilatorn. T ex så saknas operativsystem för MD407 och därmed har vi inte (enkelt) tillgång till alla standardbibliotek.

    C system include files

    Observera att utöver standardbiblioteken finns även systembibliotek. Dessa skiljer ofta markant mellan operativsystem. Var ligger systemheaderfilerna?
    • MacOS X Yosemite: Öppna en terminal och skriv gcc -xc - -v -E . Se här.
    • Windows: högerklicka på #include <systemfilnamnet.h> och välj goto declaration.
    • MD407 + ARM-korskompilatorn: Eftersom operativsystem saknas för MD407 så saknar vi systemheaderfiler.
    För mer komplexa program måste man därför tyvärr anpassa programkoden för varje plattform som man kompilerar mot. Av denna anledning existerar det bibliotek som abstraherar bort skillnaderna (som t ex wxWidgets, SDL (som bl a används av Valve) mfl.).

    C - Föreläsning 1. forts...

    Uppg. 3
    Bitwise OR används typiskt för att ett-ställa bitar. Bitwise AND används typiskt för att nollställa bitar. Bitwise XOR används typiskt för att invertera bitar. Du måste vara bekväm med att översätta fram och tillbaks mellan decimala, hexadecimala och binära talsystemet. (Oktala talsystemet används sällan nu för tiden.) Du måste behärska AND, OR, XOR, <<, >> och ~ (not).
    Ni måste kunna tända o släcka givna bitar i bytes, shorts och ints. Allt detta kommer på tentan.
    1. Skapa en funktion som tar en byte som input och printar ut dess bitar med 1:or och 0:or. T ex 00001001 för byte = 9. Gör även en funktion för shorts (16-bits) och en för ints (32-bits). Testa.
      #include <stdio.h> #include <string.h> void printBinary(unsigned char byte) { for(int i=7; i>=0; i--) { int bit = (byte >> i) & 0x01; printf("%d", bit); } } int main() { unsigned char byte = 128; printBinary(byte); return 0; }

    2. Gör en funktion som tar en 8 tecken lång sträng av 1:or och 0:or och printar ut den som ett decimaltal. Ex: char str[] = "00001001" ska ge utskriften 9 om funktionen anropas med strängen.
      #include <stdio.h> #include <string.h> void printDecimal(char str[]) { unsigned char byte = 0; for(int i=0; i<8; i++) { if(str[i] == '1') byte |= (1<<(7-i)); } printf("%d", byte); } int main() { char str[] = "00001001"; printDecimal(str); return 0; }
    Använd dina två funktioner i de föregående uppgifterna för att verifiera dina lösningar nedan.
    1. Skapa en unsigned char c = 128;. Ettställ bit 1, 3 och 5. Antag bitarna numreras 0-7 (1-8 är också vanligt). Verifiera och skriv ut decimaltalet.
      #include <stdio.h> int main() { unsigned char byte = 128; printBinary(byte); printf("\n"); byte |= (1<<1 | 1<<3 | 1<<5); printBinary(byte); printf("\n"); printf("%d\n", byte); return 0; }
    2. För att skriva ut ett tal hexadecimalt kan du använda printf("%x\n", tal);. Hexadecimaltal anges i C med prefixet 0x. Konvertera följande tal, med penna och papper, till respektive talsystem (decimalt, hexadecimalt samt binärt). Använd dina funktioner för att verifiera svaren:
            97, 0x97, 10010010
    3. Skapa en variabel signed char x = 27;. Invertera alla bitarna och addera 1. Testkör. Vad hände? Varför?
      x ^= 0xff; x++;
    4. Skapa ett program som ger följande utskrift - se videon nedan.
      Gör det genom att skifta bitar. Skapa en variabel unsigned short a = 0x8000; där du skiftar ner bitarna varje iteration. Skapa på motsvarande vis en variabel där du skiftar upp bitarna varje iteration. OR:a ihop de två variablerna inför varje utskrift.
            För att vid varje iteration få en blank terminal med texten längst ned, skapa en funktion som varje iteration rensar terminalfönstret alldeles innan du skriver ut texten. Detta kan göras genom att t ex skriva ut c:a 80 st "\n".
            En sak till... För att dina iterationer skall komma i ett lagom tempo så kan du använda operativsystemets sleep-funktion efter varje iteration. Har du skapat en yttre evig loop så gör anropet till sleep (som återför kontrollen till operativsystemet) även att programmet kan vara enklare att avbryta när du stänger console-fönstret.
      Windows: Använd Sleep(millisekunder) tillsammans med #include <windows.h>

      Mac OS: Använd usleep(mikrosekunder) tillsammans med #include <unistd.h>
      #include <stdio.h> #include <string.h> #include <unistd.h> //(eller <windows.h>) void printBinary(unsigned short tal) { for(int i=15; i>=0; i--) { int bit = (tal >> i) & 0x01; printf("%d", bit); } } void clearscreen() { for(int i=0; i<80; i++) printf("\n"); } int main() { while (1) { unsigned short a = 0x8000, b = 0x0001; for(int i=0; i<15; i++) { clearscreen(); a >>= 1; b <<= 1; printBinary(a | b); printf("\n"); // without the sleep, the eternal loop may // also cause the program to be harder to // terminate. usleep(250000); // för Windows: Sleep(250); } } return 0; }
    5. Gör nu ditt eget animerade favoritbitmönster
    6. Lägg till så att texten scrollar omväxlande till höger/vänster så att den ser ut att studsa mot terminalkanterna. (Om du kan, lägg även till så att bitmönstret animeras hälften så fort som texten scrollar). Se videon nedan.
    #include <stdio.h> #include <string.h> #include <stdlib.h> // för abs() #include <unistd.h> //(eller <windows.h>) ... void printSpaces(int n) { for(int i=0; i<n; i++) printf(" "); } int main() { unsigned short a = 1, b = 0x8000; int scrollWidth = 50; int loopIter = 0; while (1) { clearscreen(); // Hantera horisontell bouncing scroll // låt scrolliter loopa runt från 0 t.om. 99; int scrollIter = loopIter % (2*scrollWidth); int pos = 35 + scrollWidth - abs(scrollWidth - scrollIter); printSpaces(pos); // hantera bit-scroll if((loopIter % 2) != 0) { // uppdatera bitmönstret endast varannan iteration. a = (a <= 1) ? 0x8000 : a >> 1; b = (b == 0x8000) ? 0x0001 : b << 1; } printBinary(a | b); printf("\n"); usleep(33333); loopIter++; } return 0; }

    Uppg. 4
    1. Om du tittar på lösningen ovan så ser du principen för att lägga till sinsemellan oberoende händelseförlopp. Gör nu så att texten även studsar i y-led. Se videon nedan:
    ... #include <math.h> //för fabs(), sin() ... // Hantera horisontell bouncing scroll ... // hantera bit-scroll ... // Hantera vertikal studs float t = (loopIter % 41) / 40.0f; // t: 0 -> 40 float y = 10.3f*( fabs(2*(3*t*t - 2*t*t*t) -1)); // ser bättre ut än // en ren t^2-kurva for(int i=0; i<y; i++) printf("\n");