Start Elkretssimulator


Klocka

RTC eller inte

Det vanligaste är kanske att man använder någon slags extern realtidsklocka, som dessutom tickar på med eget batteri för att hålla tiden även om mikrokontrollens ström är utdragen. Sådana klockar brukar också vara väldigt exakta. Men det går förstås att göra en klocka även utan RTC -krets. En sådan klocka är gjord nedan.

Utmaningar

För att få koden att fungera med 4 stycken 7-segmentdisplayer gör vi som beskrivet här kring 7-segmentdisplayer.

Ett annat problem är att det behövs 3 knappar och de digitala in/ut-gångarna är slut. Men det finns ett antal analoga ingångar och det duger lika bra. Hur man kan använda en analog ingång till ett flertal knappar är beskrivet här.

Koppla upp brädet

Tryck i 4 stycken displayer enligt nedan. Dessa 4 stycken displayer skall sedan i allt väsentligt parallell-kopplas enligt nedan, sånär som den gemensamma katoden.

Om man tar det systematiskt går det att få rätt. Det är tyvärr ofrånkomligt att det blir ett sladdinferno.







Vi behöver också lite knappar. Eftersom de digitala in/ut -gångarna är slut fixar vi detta genom att använda de analoga ingångarna för knapparna.





Ställa klockan - tillståndsmaskin

Det brukar fungera ungefär såhär som nedan och så är det gjort i detta exempel. Alltså för att ställa klockan tänker jag mig såhär:

1. Klicka inställningsknapp
2. Justera timmen med upp/ner -knapparna.
3. Klicka inställningsknapp
4. Justera minutrarna med upp/ner -knapparna.
5. Klicka inställningsknapp

Klart.

Enklaste sättet hantera en sådan här funktionalitet i koden är med en "tillståndsmaskin" där vi vandrar från ett tillstånd till ett annat. Detta hanteras i funktionen buttonPush(int k). Notera hur funktionen vandrar mellan tillstånden STATE_CLOCK, STATE_CHANGE_HOUR samt STATE_CHANGE_MIN för att sedan återgå till STATE_CLOCK som räknar upp klockan.

Räkna upp klockan

Vi sätter ett interrupt som anropar ISR 12019.2 gånger per sekund. Detta gör vi genom att sätta OCR1A = 77 med en prescaler på 1024. Detta ger oss;

Frekvensen = Klockfrekvens / (prescaler x (OCR1A+1))
Frekvensen = 16000000 / (1024 x (OCR1A+1))
Frekvensen = 16000000 / (1024 x (77+1))
Frekvensen = 200.32 per sekund

Räknaren interruptTicks räknar upp vid varje interruptanrop. 200.32 anrop per sekund ger 12019.2 anrop per minut. När denna räknare har nått 12019 så har det alltså gått ganska exakt 1 minut. Inte riktigt exakt, klockan kommer gå fel 1.4 sekunder per dygn. Men det är accepterbart i detta exempel. Den händige fixar snabbt en mer exakt klocka även utan extern realtidsmodul.

Övrigt

Övrigt i koden finns förklarat antingen under 7-segmentdisplayer eller knappar på analog in.

// Beroende på om du anv. gemensam anod (+) eller katod (-) så
// vänder du på nedan. #define _HIGH HIGH ger gemensam katod.
// funktion se http://el.st?klocka

#define _HIGH HIGH
#define _LOW LOW

#define STATE_CHANGE_HOUR 2
#define STATE_CHANGE_MIN 3
#define STATE_CLOCK 4

#define BUTTON_UP 1
#define BUTTON_DOWN 2
#define BUTTON_OK 3

int state = STATE_CLOCK;
 
int pval = 0;
int val = 0;
int keyPressed = 0;
int countNopress = 0;

bool flash = false;
int timeCtr = 0;

int interruptTicks = 0;
int minutes = 0;
int hours = 0;

// pinnar för segmenten a,b,c,d,e,f,g
int segPins[8] = {2,3,4,5,6,7,8,9};    
 
// pinnar för gemensam (common) anod/katod
int comPins[4] = {10,11,12,13};

// siffror som skall visas (innan klocka ställd)
int num[4]={0,0,0,0};

int segLEDs[12][7]=
{
{1,1,1,1,1,1,0},
{0,1,1,0,0,0,0},
{1,1,0,1,1,0,1},
{1,1,1,1,0,0,1},
{0,1,1,0,0,1,1},
{1,0,1,1,0,1,1},
{1,0,1,1,1,1,1},
{1,1,1,0,0,0,0},
{1,1,1,1,1,1,1},
{1,1,1,0,0,1,1},
{0,0,0,0,0,0,0},
{0,0,0,0,0,0,1}
};

int button(int v)
{
  if(v>450&&v<550) return BUTTON_UP;
  if(v>600&&v<700) return BUTTON_DOWN;
  if(v>850&&v<950) return  BUTTON_OK;
  return 0;
}

void showXY(int x, int y)
{
  num[3] = round(x/10);
  num[2] = x-(num[3]*10);
  num[1] = round(y/10);
  num[0] = y-(num[1]*10);
}

int buttonPush(int k)
{
  interruptTicks = 0;
  switch(state)
  {
    case STATE_CHANGE_HOUR:
      if(k == BUTTON_UP)
      {
        hours++;
        if(hours>23)
          hours=0;        
        showXY(hours,minutes);
      }
      if(k == BUTTON_DOWN)
      {        
        hours--;  
        if(hours<1)
          hours=23;      
        showXY(hours,minutes);    
      }
      if(k == BUTTON_OK)
      {
        num[0] = 11;
        num[1] = 11;
        state = STATE_CHANGE_MIN;
      }
      break;

    case STATE_CHANGE_MIN:
      if(k == BUTTON_UP)
      {
        minutes++;
        if(minutes>59)
          minutes=0;      
        showXY(hours,minutes);  
      }
      if(k == BUTTON_DOWN)
      {
        minutes--;  
        if(minutes<1)
          minutes=59;      
        showXY(hours,minutes);  
      }
     
      if(k == BUTTON_OK)
      { 
        flash = false;
        state = STATE_CLOCK;
      }      
      break;

    case STATE_CLOCK:
      if(k == BUTTON_OK)
      {         
        state = STATE_CHANGE_HOUR;
        num[2]=11;
        num[3]=11;
        flash=true;
      }
    break;
  }
}

void setup() 
{
  for (int i=0; i<8;i++)
    pinMode(segPins[i],OUTPUT);
    
  for (int i=0; i < 4;i++)
    pinMode(comPins[i],OUTPUT);  

  // Frekvensen = Klockfrekvens / (prescaler x (OCR1A+1))
  // Frekvensen = 16000000 / (1024 x (OCR1A+1))
  // Frekvensen = 16000000 / (1024 x (77+1))
  // Frekvensen = 200.32 per sekund
  // Eller 12019.2 per minut. Om vi räknar med 12019 per
  // minut så blir felet 1.4 sekunder per dygn.
 
  cli();
  TCCR1A = 0;
  TCCR1B = 0;
  TCNT1  = 0;
  OCR1A =  77;
  TCCR1B |= (1 << WGM12);
  TCCR1B |= (1 << CS12) | (1 << CS10); // prescaler=1024
  TIMSK1 |= (1 << OCIE1A);
  sei();       
}

ISR(TIMER1_COMPA_vect)
{
  interruptTicks++;
  if(interruptTicks>12018)
  {
    interruptTicks = 0;
    minutes ++;    
    showXY(hours,minutes);
  }
  if(minutes>59)
  {
    minutes = 0;
    hours++;    
    showXY(hours,minutes);
  }
  if(hours>23)
  {
    hours=0;
    showXY(hours,minutes);
  }
  
  clear();

  if(flash && timeCtr > 150)
  {
    digit(0,10);
    digit(1,10);
    digit(2,10);
    digit(3,10);    
  }
  else
  {
    digit(0,num[0]);
    digit(1,num[1]);
    digit(2,num[2]);
    digit(3,num[3]);
  }

  timeCtr++;
  if(timeCtr > 300)
    timeCtr = 0;

  pval = button(analogRead(0));
    
  if(pval == 0)
  {
    countNopress++;
    if(countNopress > 1)
      val = 0;    
  }
  else
  {
    if(val != pval)
    {
      val = pval;
      countNopress = 0;      
      buttonPush(val);
    }
  }
}

void digit(int d, int num)
{
  for (int d=0; d < 4;d++)
    digitalWrite(comPins[d], _HIGH);

  digitalWrite(comPins[d], _LOW);
  for (int i=0; i<7; i++)
    digitalWrite(segPins[i],segLEDs[num][i]?_HIGH:_LOW);

  delay(50);
}

void clear()
{
  for (int d=0; d < 4;d++)
  {
    digitalWrite(comPins[d], _HIGH);
      for (int i=0; i<7; i++)
        digitalWrite(segPins[i],_LOW);     
  }
}

void loop() 
{
  
}