I2C Lommeregner

Fra HTX Arduino
Version fra 13. mar. 2015, 16:24 af htx_>Bar htx_>Bar (Udestående udvikling)
(forskel) ← Ældre version | Nuværende version (forskel) | Nyere version → (forskel)
Spring til navigation Spring til søgning
Lommeregner lavet med I2C Moduler

Lommeregneren er opbygget od fra et I2C tastatur og et I2C Display hvor begge er koblet på en Arduino ved hjælp af en I2C Bus.

Selve lommeregner logikken er lavet i Arduinoen.

Systemet

Lommeregneren er lavet med logikken i en Arduino, der fungerer som I2C master, og henter oplysninger nede i I2C tastaturet og sender de relevante informationer til I2C displayet.

Lommeregneren kan betragtes ud fra følgende blokdiagram:
Blokdiagram over lommeregneren
Blokdiagram over lommeregneren

I2C Displayet er et færdigt købt modul, mens I2C tastaturet er et modul der er konstrueret på Holstebro HTX.

Lommeregneren er et rent software projekt, som illustrerer hvilke muligheder man har med I2C Moduler

Software ide

Ideen er baseret på at Arduinoen hele tiden looper og tjekker om der er kommet indtastninger på tastaturet.

Når der kommer en indtastning, så samles den op og samles til tal eller der reageres på den regneart man ønsker.

Når der trykkes = så beregnes og der gøres klar til et nyt regnestykke.

Lommeregneren er ikke lavet til at tage højde for regnearternes hieraki, men regner tallene sammen som de tastes ind.

Detaljer i softwaren

Hele softwaren bliver beskrevet i de forskellige funktionsblokke det er lavet i

Definitioner og Initialisering af lommeregneren

I starten inkluderes Wire modulet, så man kan kommunikere ved hjælp af I2C-protokollen, og rutinerne til I2C displayet.

Der laves en initialisering af I2C displayet og angives hvilken adresse I2C tastaturet arbejder på.

#include <Wire.h>
#include <LiquidCrystal_I2C.h>

LiquidCrystal_I2C lcd(0x27,16,2);  // set the LCD address to 0x20 for a 16 chars and 2 line display

// Adressen på I2C tastaturet
#define I2C_Address 0x23

Herefter erklæres de variabler der anvendes og der hvor det er nødvendigt sættes den standard værdi de skal have.

// Variabler til programmet
byte error;
byte tast;
float tal1 = 0.0;
float tal2 = 0.0;
char tegn;
char ch;
boolean get1 = true;
boolean komma = false;
float kommafaktor = 0.1;
boolean mellem = false;
boolean start = true;

I Arduinis setup() laves initialisering af seriel port, der anvendes til test, initialisering af LCD'en og der udskrives en hjælpeskærm til den serielle port.

void setup()
{
  Wire.begin();  // Start I2C kommunikationen

  Serial.begin(9600);
  delay(100);
  lcd.init();                      // initialize the lcd 
  // Print a message to the LCD.
  lcd.backlight();
  lcd.print("Lommeregner");
  help();  
}

Hoved-loopet i lommeregneren

I hoved-loopet tjekkes der på om der kommer noget ind fra den serielle port, og de relevante rutiner kaldes.

Her er det kun hjælp man kan få vist igen, og man kan slå beep på tastaturet til og fra.

void loop()
{
  // Reager på de forskellige indtastninger brugeren 
  // laver på PC'en
  if (Serial.available() > 0) {
    ch = Serial.read();
    if ((ch == 'h') | (ch == 'H')) {
      help();
    }
    if ((ch == 'b') | (ch == 'B')) {
      error = Serial.parseInt();
      setBeep(error);
    }
  }

Den sidste del af loopet kalder en rutine der tjekker om der er nogen taster i bufferen. Der holdes en pause, men den har faktisk ingen betydning, blot at den ikke belaser I2C tastaturet så meget.

  getTastBuf();  // Håndterer indtastninger på I2C tastaturet
  
  delay(100); 
}

Servicering af I2C tastaturet

Starten af tjekket på I2C tastaturet spørger om hvor mange karakterer der er i bufferen, og hvis der ingen er, så sker der ikke noget.

Hvis der er en eller flere taster i bufferen, så hentes den.

void getTastBuf() {
  Wire.beginTransmission(I2C_Address);
  Wire.write(2);    // Kommando der undersøger om der er taster klar
  error = Wire.endTransmission();

  if (error == 0)
  {
    delayMicroseconds(100);
    Wire.requestFrom(I2C_Address, 1);
    if (Wire.available() == 1) {
      error = Wire.read();
      if (error >= 1) {  // Hvis der er taster klar
        Wire.beginTransmission(I2C_Address);
        Wire.write(1);   // Kommando der henter tasten
        error = Wire.endTransmission();
      
        if (error == 0) {
          delayMicroseconds(100);
          Wire.requestFrom(I2C_Address, 1);
          if (Wire.available() == 1) {

Håndtering af tal

Det første der sker når man har modtagert tasten er at der tjekkes om det er en start-situation, og hvis det er, så slettes displayet, så der er plads til et nyt regnestykke.

Herefter tjekkes om det er et ciffer der indtastes, og det tilføjes i enten første eller andet tal.

Der håndteres også at der kan tastes komma i tallene.

            tast = Wire.read();  // Hent den aktuelle tast
            if (start) {
              lcd.clear();
              start = false;
            }
            if (tast < 10) {     // Hvis det er en taltast
              lcd.print(tast);
              if (get1) {        // Hvis det er første tal der hentes
                if (komma) {
                  tal1 = tal1 + tast * kommafaktor;
                  kommafaktor *= 0.1;
                } else {
                  tal1 = tal1 * 10 + tast;
                }
              } else {          // Hvis tallet er et efterfølgende
                if (komma) {
                  tal1 = tal1 + tast * kommafaktor;
                  kommafaktor *= 0.1;
                } else {
                  tal2 = tal2 * 10 + tast;
                }
              }
            } else if (tast == 10) {  // Tasten . (komma)
              if (! komma) {
                lcd.print('.');
              }
              komma = true;

Selve beregningerne

Hvis der tastes = så gøres klar til afslutningen og beregningen. I testudskriften til den serielle port tages der højde for om der har været mellemregninger.

Der beregnes så ud fra hvilken regneart der er brugt sidst.

Tilsidst skrives resultatet ud både på den serielle testport og til I2C displayet og der gøres klar til næste regnestykke.

            } else if (tast == 11) {  // Tasten = (beregn og slet)
              get1 = true;
              komma = false;
              kommafaktor = 0.1;
              if (! mellem) {
                Serial.print(tal1);
              }
              mellem = false;
              if (tegn == '+') {      // Beregn ud fra regnearten
                tal1 = tal1 + tal2;
              } else if (tegn == '-') {
                tal1 = tal1 - tal2;
              } else if (tegn == '*') {
                tal1 = tal1 * tal2;
              } else if (tegn == '/') {
                tal1 = tal1 / tal2;
              }
              lcd.print("=");
              lcd.setCursor(0,1);
              lcd.print(tal1);          // Udskriv resultatet
              Serial.print(tegn);
              Serial.print(tal2);
              Serial.print("=");
              Serial.println(tal1);
              start = true;
              tal1 = 0.0;
              tal2 = 0.0;

Regnearterne håndteres i den følgende kode, hvor der også tages højde for om det er første gang der er tastet et regnetegn, eller om der skal foretages en mellemregning.

            } else if (tast == 15) {    // Tasten +
              if (! get1) {
                mellemregn();
              }
              tegn = '+';
              komma = false;
              kommafaktor = 0.1;
              lcd.print(tegn);
              get1 = false;
            } else if (tast == 14) {    // Tasten -
              if (! get1) {
                mellemregn();
              }
              tegn = '-';
              komma = false;
              kommafaktor = 0.1;
              lcd.print(tegn);
              get1 = false;
            } else if (tast == 13) {    // Tasten *
              if (! get1) {
                mellemregn();
              }
              tegn = '*';
              komma = false;
              kommafaktor = 0.1;
              lcd.print(tegn);
              get1 = false;
            } else if (tast == 12) {    // Tasten /
              if (! get1) {
                mellemregn();
              }
              tegn = '/';
              komma = false;
              kommafaktor = 0.1;
              lcd.print(tegn);
              get1 = false;
            }

Mellemregningen håndteres af følgende rutine:

void mellemregn() {      // Rutine der håndterer når 2. regnetegn tastes
  if (!mellem) {
    Serial.print(tal1);
  }
  if (tegn == '+') {
    tal1 = tal1 + tal2;
  } else if (tegn == '-') {
    tal1 = tal1 - tal2;
  } else if (tegn == '*') {
    tal1 = tal1 * tal2;
  } else if (tegn == '/') {
    tal1 = tal1 / tal2;
  }
  Serial.print(tegn);
  Serial.print(tal2);
  tal2 = 0;
  mellem = true;
}

Muligheden for at slå Beep fra

Det er muligt at slå beep fra via den serielle kommunikation ved at skrive B 0 til den serielle port, og det kan slås til igen ved at skrive B 1. Dette udføres af den viste rutine:

void setBeep(byte mode) {    // Til at slå beep til og fra
  Wire.beginTransmission(I2C_Address);
  Wire.write(4);
  Wire.write(mode);
  error = Wire.endTransmission();
  if (error == 0) {
    Serial.print("Beep mode set to: ");
    Serial.println(mode);
  }
}

Hjælpeskærm

For at hjælpe med brugen kan man få en hjælpeskærm via den serielle port.

// Hjælpeskærm der udskrives på den serielle konsol
void help() {
  Serial.println("Regnemaskine med I2C tastatur og I2C Display");
  Serial.println("--------------------------------");
  Serial.println("H\tDisplays this help-screen");
  Serial.println("B n\tSet Beep mode to n");
  Serial.println("");
  Serial.println("1  2  3  +");
  Serial.println("4  5  6  -");
  Serial.println("7  8  9  *");
  Serial.println(".  0  =  /");
  Serial.println("");
}

Test af softwaren

Softwaren er blevet udviklet løbende til først at kunne acceptere tal og herefter at kunne regne med en indtastning af regnearterne.

Derefter blev der lagt på, så man kan indtaste flere regnearter i samme regnestykke - dog ikke så regnemaskinen overholder regnearternes hieraki.

Der blev lavet så man kan indtaste kommatal.

Endelig blev resultatet flyttet ned i anden linje så der er bedre plads i displayet, og der blev lavet så resultatet forbliver i displayet indtil der tastes et nyt regnestykke.

Udestående udvikling

Regnemaskinen overholder ikke regnearternes hieraki. Det vil kræve at der ikke kun arbejdes med to tal internt, men at der etableres en form for stack til at gemme det der ikke er beregnet endnu, eller at man placerer alle tal i en stack og så udfører beregningen efterfølgende.

Der tages heller ikke højde for at man kan skrive uden for displayet - den skriver ude i en del af displayets hukommelse der ikke vises på dette display. Dette kan måske løses ved at lade den øverste linje scrolle eller ved at skrive ned på næste linje. Begge dele vil kræve en del omstrukturerring af indtastningen.

Endelig er der ikke nogen håndtering af flere decimaler til resultatet - det kan ganske givet lade sig gøre at angive flere decimaler, men der er ikke afklaret hvordan der skal bestemmes hvor mange decimaler der skal vises.

Tal-håndteringen fungerer heller ikke optimalt når tallene bliver store - der ser ud til at være en ikke nærmere analyseret begrænsning i Float-formatet, så det ikke arbejder over 4294967296, hvilket ikke burde være en begrænsning for float.