• 1
  • 2

Oživujeme tlačidlá #1

. .

 Článok, ktorý poukazuje na fungovanie a zostavenie tlačidiel 1.

Doporučené si preštudovať : Input/Output, D/A set | Dynamická alokácia poľa


Pri detekcií tlačidla sa opierame o zistení logickej úrovne na určitom porte registra. Na základe úrovni (log. 1/0), sme schopní detekovať stisknutie tlačidla a túto akciu ďalej spracovávať.


Spôsob zapojenia tlačidiel môžme realizovať dvoma spôsobmi: odchytávanie log. 1, odchytávanie log. 0.


Odchytávanie log. 1



Pri odchytávaní logickej 1 je tlačidlo zapojené medzi VDD a vstupom MCU. Pri stlačení tlačidla sa impulz (log. 1) privedie na vstup MCU. Rezistor R je zapojený v sérií medzi GND a vstupom MCU, a slúži ako PULL DOWN rezistor.
Rezistor zaisťuje, že stlačením tlačidla bude úroveň logická 1, v inom prípade bude logická 0.
Kondenzátor C je zapojený ako filtračný člen na odfiltrovanie výkivov pri rýchlom stlačení. Kapacita kondenzátora určuje dobu nabitia a tým dobu spomalenej odozvy impulzu na vstup procesora. Čím je väčšia kapacita, tým je dlhšia doba, za ktorú sa impulz dostane na vstup MCU.


Odchytávanie log. 0



Pri odchytávaní logickej 0 je tlačidlo zapojené medzi GND a vstupom MCU.
Rezistor R je zapojený v sérií medzi VDD a vstupom MCU, a slúži ako PULL UP rezistor - zaisťuje, že stlačením tlačidla bude úroveň logická 0, v inom prípade bude logická 1. Kondenzátor C má tú istú funkcionalitu ako v prvom prípade.


* Doba odozvy po zmačknutí tlačidla sa dá riešit aj programovo, avšak pre väčšiu spoľahlivosť sa používa aj
kondenzátor.


Schéma zapojenia




Stiskom zapni

Chceme napísať program, ktorý pri stlačenom tlačidlu rozsvieti LED diodu.
LED bude pripojená prostredníctvom registra PORTB na porte 0 - (RB0). Tlačidlo bude na tom istom registry a na porte 8 - (RB8).
Port RB0 bude ako výstupný a port RB8 ako vstupný. Oba budú digitálne.

Návrh:

main.c

#include "config_diall.h"
#include "header_words.h"

char *PageSys(void)
{
   return "0x21";
}


int main(void)
{
   Init_Port(PORTB,0,"digital");
   Init_Port(PORTB,8,"digital");
   Config_OUT(TRISB,0);
   Config_IN(TRISB,8);
   Bit_Clr(PORTB,0);

   while(1)
          {
             if((Get_Switch(PORTB,8)) !=0)
              {
                 Bit_Set(PORTB,0);
              } else {
                          Bit_Clr(PORTB,0);
                        }
          }
}
 



                                                                        


Stiskom zapni - Stiskom vypni

Teraz chceme, aby sa nám LED po stlačení tlačidla rozsvietila a svietila do doby, kým tlačidlo opäť nestlačíme a LED bude vypnutá do doby ďalšieho stisku.

Zamyslíme sa nad predchadzajúcim príkladom, že všetko beží v neakom cykle while(). Ak stlačíme tlačidlo, podmienka bude na určitú mili-sekundu splnená, ale ako náhle tlačidlo pustíme, podmienka naplnená nebude. Čize potrebujeme premennú identifikujúcu predchodzí stav. A čo v prípade, že tlačidlo zmačkneme a podržíme na 2 sekundy? Pri držaní tlačidla sa vnútorný program procesora neustále v slučke vykonáva. Je jasné, že sa ho pokúsime zbrzdiť, aby pri každom ďalšom zopnutí tlačidla prebiehla práve jedne identifikácia predchodzého stavu.

Návrh:

main.c

#include "config_diall.h"
#include "header_words.h"

char *PageSys(void)
{
   return "0x21";
}

int b=0;

void Switch(void)
{
   if((Get_Switch(PORTB,8)) !=0 )
    {
       while((Get_Switch(PORTB,8)) !=0)
             {
             }
       Delay_ms(12);
       if(b==0)
        {
           b = 1;
         } else
            {
               b = 0;
            }
     }
}

int main(void)
{
   Init_Port(PORTB,0,"digital");
   Init_Port(PORTB,8,"digital");
   Config_OUT(TRISB,0);
   Config_IN(TRISB,8);
   Bit_Clr(PORTB,0);

   while(1)
          {
             Switch();
             if(b==1)
              {
                 Bit_Set(PORTB,0);
              } else
                   {
                      Bit_Clr(PORTB,0);
                   }
          }
}
 



                                                                            



Všimnime si funkciu Switch(). V cykle funkcia zistí, či je zopnuté tlačidlo. Následne je použitý druhý cyklus while, ktorý sa vykoná x krát - kým je tlačidlo vkuse zopnuté aby ubrzdil program.
Za cyklom sa čaká 12ms. Pokiaľ by sme stlačili tlačidlo príliš rýchlo, alebo by kontakt zopnutia bol niekoľko milisekundový, dochádzalo by k šumom.
Ako som už hore spomenul, je to sw. ošetrenie možných šumov. Väčším časovým koeficientom sa zvyšuje odozva pri stlačení tlačidla - presne ako pri kondenzátore zapojeného paralelne k tlačidlu.

Nasleduje podmienka ktorou sa zistí aký je predošlý stav pomocnej premennej "b", teda, či bol režim zapnutia/vypnutia a zmení ho na opačný.

Za funkciou Switch() nasleduje podmienka, ktorá na základe aktuálneho stavu režimu rozsvieti alebo zhasne LED.

! Problémom je však vnútorný while funkcie Switch(). Ak by sme mali tlačidlo zopnuté hodinu, hodinu by bol kód zacyklený. Predpokladám však, že pri jednoduchých výtvorov by sa nenašiel nikto taký. V ďalších ukážkach budem problém obchádzať !




! Optimalizácia kódu !

Určite každému blisne hlavou, že niektoré veci sa v oboch kódoch opakujú, premenná "b" je nevhodné deklarovaná a inicializovaná ako verejná a celé tie kódy sú na 3 veci z hľadiska univerzálnosti.
Vezmime v úvahu, že máme 20 tlačidiel..., to by sme boli stratení. Alebo 100 Led a tlačidlom jednu po druhej rozsviecovali. Vo väčšej predstavivosti je to, to isté ako prepínanie vstupných auxov, pridávania/odoberanie volume a kopec ďalšieho.

Optimalizujem kód tak, aby bol čo najuniverzálnejší a čo najrýchlejší.

switchers.h


struct _switch_ {
    struct __attribute__ ((packed)) {
       struct __attribute__ ((packed)) {
          volatile unsigned int *Port;
          volatile unsigned int *Tris;
             }Direct;
       struct __attribute__ ((packed)) {
          int * Pins;
          int pin_size;
          int Pin;
            }Config;
         }Switch;

    struct __attribute__ ((packed)) {
       struct __attribute__ ((packed)) {
          volatile unsigned int *Port;
          volatile unsigned int *Tris;
             }Direct;
       struct __attribute__ ((packed)) {
          int * Pins;
          int pin_size;
          int Pin;
            }Config;
         }Leds;

    struct __attribute__ ((packed)) {
       int i;
       int count;
       bool is_activate;
          }Tmp;
}SW;


void Switch_Config(void)
{
   int switch_array[]={8};
   int led_array[]={0};

   int err,err2 = 564;

   if (*SW.Switch.Direct.Port == PORTA && PORTA != __No__Used__ && TRISA != __No__Used__)
     { SW.Switch.Direct.Tris = uintptr &TRISA; err=0; }
   if (*SW.Switch.Direct.Port == PORTB && PORTB != __No__Used__ && TRISB != __No__Used__)
     { SW.Switch.Direct.Tris = uintptr ⧍ err=0; }
   if (*SW.Switch.Direct.Port == PORTC && PORTC != __No__Used__ && TRISC != __No__Used__)
     { SW.Switch.Direct.Tris = uintptr &TRISC; err=0; }
   if (*SW.Switch.Direct.Port == PORTD && PORTD != __No__Used__ && TRISD != __No__Used__)
     { SW.Switch.Direct.Tris = uintptr &TRISD; err=0; }
   if (*SW.Leds.Direct.Port == PORTA && PORTA != __No__Used__ && TRISA != __No__Used__)
     { SW.Leds.Direct.Tris = uintptr &TRISA; err2=0; }
   if (*SW.Leds.Direct.Port == PORTB && PORTB != __No__Used__ && TRISB != __No__Used__)
     { SW.Leds.Direct.Tris = uintptr ⧍ err2=0; }
   if (*SW.Leds.Direct.Port == PORTC && PORTC != __No__Used__ && TRISC != __No__Used__)
     { SW.Leds.Direct.Tris = uintptr &TRISC; err2=0; }
   if (*SW.Leds.Direct.Port == PORTD && PORTD != __No__Used__ && TRISD != __No__Used__)
     { SW.Leds.Direct.Tris = uintptr &TRISD; err2=0; }

   if(err==0)
    {
       SW.Switch.Config.pin_size = sizeof(switch_array)/sizeof(int);
       SW.Switch.Config.Pins = malloc(SW.Switch.Config.pin_size);

       for (SW.Tmp.i=0; SW.Tmp.i<SW.Switch.Config.pin_size; SW.Tmp.i++)
           {
              SW.Switch.Config.Pins[SW.Tmp.i] = switch_array[SW.Tmp.i];
           }

       for (SW.Tmp.i = 0; SW.Tmp.i<SW.Switch.Config.pin_size; SW.Tmp.i++)
           {
              Init_Port(*SW.Switch.Direct.Port,SW.Switch.Config.Pins[SW.Tmp.i],"digital");
              Config_IN(*SW.Switch.Direct.Tris,SW.Switch.Config.Pins[SW.Tmp.i]);
           }
    }

   if(err2==0)
    {
       SW.Leds.Config.pin_size = sizeof(led_array)/sizeof(int);
       SW.Leds.Config.Pins = malloc(SW.Leds.Config.pin_size);

       for (SW.Tmp.i=0; SW.Tmp.i<SW.Leds.Config.pin_size; SW.Tmp.i++)
           {
              SW.Leds.Config.Pins[SW.Tmp.i] = led_array[SW.Tmp.i];
           }

       for (SW.Tmp.i=0; SW.Tmp.i<SW.Leds.Config.pin_size; SW.Tmp.i++)
           {
              Init_Port(*SW.Leds.Direct.Port,SW.Leds.Config.Pins[SW.Tmp.i],"digital");
              Config_OUT(*SW.Leds.Direct.Tris,SW.Leds.Config.Pins[SW.Tmp.i]);
              Bit_Clr(*SW.Leds.Direct.Port,SW.Leds.Config.Pins[SW.Tmp.i]);
           }
    }

   if(err==0&&err2==0)
    {
       SW.Tmp.count = 0;
       SW.Tmp.is_activate = false;
    }
}


void Switch_Go1(void)
{
   if((Get_Switch(*SW.Switch.Direct.Port,SW.Switch.Config.Pins[0])) !=0 )
     {
        while((Get_Switch(*SW.Switch.Direct.Port,SW.Switch.Config.Pins[0])) !=0)
               {
               }
        Delay_ms(12);
        if(SW.Tmp.is_activate==false)
         {
            SW.Tmp.is_activate = true;
            Bit_Set(*SW.Leds.Direct.Port,SW.Leds.Config.Pins[0]);
          } else
               {
                  SW.Tmp.is_activate = false;
                  Bit_Clr(*SW.Leds.Direct.Port,SW.Leds.Config.Pins[0]);
               }
     }
}


void Switch_Go2(void)
{
    if((Get_Switch(*SW.Switch.Direct.Port,SW.Switch.Config.Pins[0])) !=0 )
      {
         while((Get_Switch(*SW.Switch.Direct.Port,SW.Switch.Config.Pins[0])) !=0)
                {
                }
         Delay_ms(12);
         SW.Tmp.count++;

         if(SW.Tmp.count>=7)
          {
             SW.Tmp.is_activate = true;
             Bit_Set(*SW.Leds.Direct.Port,SW.Leds.Config.Pins[0]);
          } else
               {
                  SW.Tmp.is_activate = false;
                  Bit_Clr(*SW.Leds.Direct.Port,SW.Leds.Config.Pins[0]);
                }
      }
}
 



main.c

#include "config_diall.h"
#include "switchers.h"
#include "header_words.h"

char *PageSys(void)
{
   return "0x21";
}


int main(void)
{
   SW.Switch.Direct.Port = uintptr &PORTB;
   SW.Leds.Direct.Port = uintptr &PORTB;
   Switch_Config();

   while(1)
          {
             Switch_Go2();
          }
}
 



Niektorí zrejme dospejú k záveru, v čom je tento kód univerzálny?! 

Súbor switchers.h : vytvoril som jednu štruktúru uchovavajúcu všetky dáta. Všetky metódy komunikujú prostredníctvom nej.
Umožňuje uložiť množinu vstupov (spínače) a výstupov (LEDs).

V metóde Switch_Config() nastavujeme do pomocných polí čisla portov spínačov a LEDs. Pokiaľ máme len jedno tlačidlo a jednu LED, samozrejme je lepšie uložiť čisla portov do premenných Pin v podštruktúre Config podštruktúrach Leds a Switch.

Ak by sme písali aplikáciu s dotazovaním na debuggovanie a čo najmenším zaťažením MCU pri chybách, použijeme logiku, ktorá na základe názvu registra PORT priradí register TRIS. Pokiaľ zadáme zlý port, vzniknutú chybu môžme v prostredí debuggeru (config_diall.h) ďalej spracovávať. Pri chybách portov nedôjde ku alokovaní a nastavovania portov, čo by zaberalo čas.
V opačnom prípade sa nám alokuje množina vstupov/výstupov, porty sa sinicializujú a nastaví sa ďalšia defaultná konfigurácia.

Môžme si napísať nespčetne mnoho metod na spracovávanie tlačidla a pritom jadro všetkých bude množina štruktur a Switch_Config().
Stiskom zapni - Stiskom vypni aplikujeme na metodu Switch_Go1().
Na metodu Switch_Go2() aplikujeme rozsvietenie LED až po 7. stlačení tlačidla - Zapni po 7 stlačení .


V súbore main.c v metóde main() nastavujeme pomocou referencií označenia pracovných registrov PORTx, pre množinu tlačidiel a LEDs, a celý výtvor metodou Switch_Config() sinicializujeme.

V cykle while beží metoda Switch_Go2().