← Kursens material Digital ingång · område 2 Område 2

Område 2 · lektion 8

Digital ingång

Hittills har kortet bara sagt saker. Nu ska det lyssna. En knapp är den enklaste givaren som finns, och ändå gömmer den två fällor som fäller nästan alla första gången: pinnen som inte är kopplad någonstans, och kontakten som studsar.

Mål

Genomgång

En ingång som inte är kopplad någonstans läser skräp

Tänk dig att du gör pinne 2 till ingång och kopplar en knapp mellan pinnen och GND. Trycker du ner knappen ligger pinnen på 0 V, alltså LOW. Det är entydigt. Men när knappen är uppe – vad ligger pinnen på då?

Ingenting. Den hänger löst, och en lös ingång plockar upp störningar från lysrör, mobiltelefoner och din egen hand. digitalRead() levererar då omväxlande HIGH och LOW utan att någon rört knappen. Det kallas att ingången flyter, och symtomet är ett bygge som gör saker helt av sig självt.

Lösningen är ett motstånd som drar pinnen till en känd nivå när inget annat gör det. Dras den uppåt mot 5 V heter det pull-up, nedåt mot GND pull-down. Vi använder pull-up, eftersom kortet redan har ett sådant motstånd inbyggt i varje pinne.

Kopplingen med intern pull-up

Kopplingsschema för en knapp med intern pull-up. Innanför en streckad ruta märkt ATmega328P går en ledning från 5 V ned genom ett motstånd på 20 till 50 kiloohm och vidare till pinne D2. Utanför rutan fortsätter ledningen från D2 till strömbrytaren S1 och därifrån till jord. 5 V 20–50 kΩ ATmega328P D2 S1

Hur mycket ström går det genom motståndet när knappen är nedtryckt? Hela de 5 volten ligger då över det:

I = U / R = 5 V / 20 000 Ω = 0,00025 A = 0,25 mA

I = U / R = 5 V / 50 000 Ω = 0,0001 A = 0,10 mA

Alltså någonstans mellan 0,1 och 0,25 mA. Jämför med de 20 mA en lysdiod vill ha: det här är åttio till tvåhundra gånger mindre, och det är hela poängen. Ett pull-up-motstånd ska hålla en nivå, inte driva något.

digitalRead och INPUT_PULLUP – och inversionen

Två rader gör hela jobbet. Den ena står i setup():

pinMode(KNAPP_PIN, INPUT_PULLUP);

Den andra i loop():

int lage = digitalRead(KNAPP_PIN);

Och nu det som förvirrar alla första gången:

KnappenPinnens spänningdigitalRead() gerI seriemonitorn
Uppe, opåverkad5 V, dragen dit av pull-upenHIGH1
Nedtryckt0 V, kortsluten till GNDLOW0

Nedtryckt är alltså LOW, inte HIGH. Det känns bakvänt, och det är det också – men bara språkligt. Elektriskt är det raka motsatsen till bakvänt: knappen har ett enda jobb, och det är att koppla pinnen till noll. Att den släppta knappen ger 5 V är motståndets förtjänst, inte knappens.

Skriv det i koden, inte i huvudet Skriv if (digitalRead(KNAPP_PIN) == LOW) och sätt en kommentar efter: // LOW betyder nedtryckt. Om ett halvår läser du din egen kod och minns inte inversionen. Kommentaren tar fem sekunder att skriva och sparar tjugo minuters felsökning.

Ett villkor som styr en utgång

Nu sitter hela kedjan ihop: givare in, villkor i mitten, ställdon ut. Här är den med en lysdiod som ställdon, eftersom servot kommer i lektion 9.

const int KNAPP_PIN = 2;
const int LAMPA_PIN = 8;

void setup() {
  pinMode(KNAPP_PIN, INPUT_PULLUP);
  pinMode(LAMPA_PIN, OUTPUT);
}

void loop() {
  if (digitalRead(KNAPP_PIN) == LOW) {   // LOW betyder nedtryckt
    digitalWrite(LAMPA_PIN, HIGH);
  } else {
    digitalWrite(LAMPA_PIN, LOW);
  }
}

Lägg märke till att båda pinnarna får sin pinMode(), och att de får olika. Den ena lyssnar, den andra driver. Glöms den ena bort blir symtomen helt olika, och båda ser ut som kodfel.

Kontaktstuds

En tryckknapp är två metallbitar som slås ihop. De studsar mot varandra innan de får kontakt, precis som en boll mot ett bord, och under den tiden går kontakten till och från många gånger. Det pågår i storleksordningen 1 till 10 millisekunder.

Signalen från en studsande knapp, ritad som en fyrkantvåg över tiden. Signalen ligger på hög nivå, faller till låg, och hoppar sedan tre gånger mellan låg och hög innan den lägger sig på låg nivå. Under hopparna markerar en klammer att de pågår i 1 till 10 millisekunder. HIGH LOW 1–10 ms

Varför spelar några millisekunder roll? Därför att kortet är snabbt. 16 MHz betyder 16 miljoner klockcykler i sekunden, alltså 16 000 cykler per millisekund. Ett varv i en loop() som bara läser en pinne och sätter en utgång tar några mikrosekunder, så under en studs på 5 ms hinner loopen varvas hundratals gånger – och den ser varje uppstuds som en ny knapptryckning. Symtomet är att spaken rycker fram och tillbaka, eller att räknaren hoppar från 3 till 7 på ett tryck.

Den enkla spärren är att läsa om efter en kort paus. Är knappen kvar i samma läge var det ett riktigt tryck och inte en studs:

const int KNAPP_PIN = 2;
const int LAMPA_PIN = 8;

int forra = HIGH;          // knappens läge förra varvet
bool tand = false;         // vad lampan ska vara

void setup() {
  pinMode(KNAPP_PIN, INPUT_PULLUP);
  pinMode(LAMPA_PIN, OUTPUT);
}

void loop() {
  int nu = digitalRead(KNAPP_PIN);

  // Flanken: ögonblicket knappen går från uppe till nedtryckt.
  if (forra == HIGH && nu == LOW) {
    delay(20);                            // vänta ut studsen
    if (digitalRead(KNAPP_PIN) == LOW) {  // ligger den kvar var trycket riktigt
      tand = !tand;
      digitalWrite(LAMPA_PIN, tand);
    }
  }

  forra = nu;
}

Tre saker gör den här versionen annorlunda:

Den här spärren är inte den slutgiltiga delay(20) stoppar allt annat i tjugo millisekunder. Med en knapp och en lampa märks det inte. Med två knappar och något som ska blinka samtidigt gör det systemet trögt och opålitligt. Den riktiga lösningen mäter tid utan att stanna, med millis(), och den hör hemma i område 3. Här nöjer vi oss med att veta vad problemet heter och att spärren ovan duger till bygge 1.

I industrin och i vardagen

I industrin: en nödstoppsknapp på en maskin kopplas alltid med en brytande kontakt, betecknad NC i datablad – den är sluten när allt är normalt, och trycket bryter kretsen. Motsatsen, en slutande kontakt (NO), är den vanliga tryckknappen: öppen i viloläge. Skillnaden är ett ord i ett datablad och avgör om maskinen stannar eller inte. Valet låter bakvänt av samma skäl som pull-upen gör det, och skälet är detsamma: den nivå som betyder "allt är bra" ska vara den som kräver att kretsen är hel. Går en sladd av, lossnar en skruv eller korroderar en kontakt får styrsystemet exakt samma signal som ett nödstopp och stannar maskinen. Ett fel som gör systemet säkrare i stället för farligare kallas felsäkert, och det är den principen som skiljer en industrikoppling från en hobbykoppling. Kretsarna arbetar dessutom oftast på 24 V i stället för våra 5 V, eftersom den högre spänningen tål längre kablar och mer störningar.

I vardagen: tangentbordet du skriver på studsar också. Varje tangent är en mekanisk kontakt med samma studs på några millisekunder, och skulle datorn läsa den rakt av skulle ett tryck på a ge tre eller fyra a:n. Avstudsningen sitter i tangentbordets egen lilla styrkrets, som väntar ut studsen precis som koden ovan gör. Riktningen är den motsatta mot nödstoppet: här är målet inte säkerhet utan att ett tryck ska bli precis ett tecken, hundratals gånger i minuten, utan att någon tänker på det.

Prova själv

  1. Koppla en tryckknapp mellan pinne D2 och GND. Rita schemat först – det är två sladdar, och det tar trettio sekunder.
  2. Skriv en sketch som läser pinnen med INPUT_PULLUP och skriver ut värdet till seriemonitorn två gånger i sekunden. Kontrollera att du får 1 när knappen är uppe och 0 när den är nere.
  3. Ta bort _PULLUP så det bara står INPUT. Ladda upp och titta på monitorn utan att röra knappen. Så ser en flytande ingång ut.
  4. Bygg vidare till lysdiodsexemplet ovan, så att knappen tänder lampan.
  5. Lägg till en räknare som skriver ut hur många gånger knappen tryckts. Tryck tio gånger. Blev det tio? Lägg sedan in spärren mot kontaktstuds och räkna om.

Kontrollfrågor

Varför blir en opåverkad knapp HIGH och inte LOW?

Därför att pull-up-motståndet inuti chippet drar pinnen upp mot 5 V så länge ingenting annat drar den någon annanstans. Knappen kopplar pinnen till GND när den trycks ned, och 0 V vinner över motståndet, eftersom vägen till GND går genom knappens nästan obefintliga resistans medan vägen till 5 V går genom tiotusentals ohm.

Du har glömt INPUT_PULLUP och skrivit INPUT. Vad ser du?

Värdet hoppar mellan 0 och 1 utan att någon rör knappen, och det påverkas av om du håller handen nära. Ingången flyter. Fungerar det ibland och inte andra gånger är det nästan alltid det här.

Om det interna motståndet är 30 kΩ, hur stor ström går genom det när knappen hålls nedtryckt?

I = U / R = 5 / 30 000 = 0,000167 A, alltså ungefär 0,17 mA. Effekten blir P = U · I = 5 · 0,000167 = 0,00083 W, alltså under en milliwatt. Det är därför du kan hålla knappen nedtryckt hur länge du vill utan att något blir varmt.

Din räknare hoppar från 4 till 9 på ett enda tryck. Vad är fel, och vad är det inte?

Det är kontaktstuds. Det är inte ett trasigt kort, en dålig knapp eller ett fel i räknarlogiken. Koden ser varje studs som en ny tryckning eftersom loopen hinner varvas hundratals gånger under de millisekunder studsen pågår. Lägg in flankdetektering och en paus på cirka 20 ms.

Varför räcker det inte att skriva if (digitalRead(KNAPP_PIN) == LOW) om du vill räkna tryckningar?

Därför att villkoret är sant hela tiden knappen hålls nere, inte bara i ögonblicket den trycks. Ett tryck på en halv sekund räknas då tusentals gånger. Du behöver jämföra med förra varvets värde och bara reagera när det ändrats – det är vad flankdetektering betyder.

Kan du använda pinne D2 till både knappen och en lysdiod?

Nej. En pinne är antingen ingång eller utgång, och pinMode() ställer in vilket. Kortet har fjorton digitala pinnar, varav tolv är fria att använda – det finns ingen anledning att tränga ihop sig.

Vanliga fel

Vidare

Kapitlen Villkor och Typer och variabler i språkreferensen hör till den här guiden.

Nästa guide är servot och den mekaniska strukturen. Där byts lysdioden mot något som rör sig, och därmed blir bygget ett mekatroniskt system i stället för en lampa med en knapp.