Stopky: Jak ovlivnit reakci na stisk nebo uvolnění tlačítka?

Zdravím.
Zkouším obsluhu stopek pomocí tlačítek. Ty pak zobrazuji na 7segmentový display. Nedaří se mi bohužel zprovozni změnu hodnot při zdvihu tlačitka.
Dotaz: Jak mohu ovlvnit jestli program zareaguje na stisk nebo uvolnění tlačítka?
Něco jsem tu už našel, ale moc jsem z toho nezmoudřel.
Děkuji.

[code]


if ((PIND & (1<<PD6))== 0) start= 0; //start stopek

//========PROBLEM===========
//----nastaveni casu
if(start== 1)
{
if ((PIND & (1<<PD5))== 0) minj++;
/{
_delay_ms(100);
while (! (PIND & (1<<PD5))) minj++;
_delay_ms(100);
}
/
if ((PIND & (1<<PD4))== 0) secj++;
/{
_delay_ms(100);
while (! (PIND & (1<<PD4))) secj++;
_delay_ms(100);
}
/
}
//==========================

if (start== 0)
{
if(count>50)
{
if (mind == 0 && minj == 0 && secd == 0 && secj == 0) break; //stopky konci
if (minj == 0 && secd == 0 && secj == 0)
{
if (mind != 0) minj = 10;
mind–; //zmena desitek minut
}
if (secd == 0 && secj == 0)
{
minj–; //zmena jednotek minut
secd = 6;
}
if (secj == 0)
{
secj=9;
secd–;
} else secj–;
count=0;
}
else count++;
}

}

return 0;
}[/code]

ATMEGA644P, WinAVR-20100110

:arrow_right: administrator: přejmenováno z "Nastavení stopek tlačítky"

Jak myslíš při zdvihu tlačítka, co by to mělo dělat? Běžně se reaguje jen na stisk. Jen je potřeba ošetřovat zákmity. V nejjednodušším případě tak, že se po reakci na stisk přidá prodleva, tím se zabrání opakované reakci při zákmitu. Dokonaleji tak, že se po uvolnění chvíli nereaguje na stisk (opakovaný stisk resetuje čítač prodlevy) a pak se dá tak zajistit rozlišení delší prodlevy na začátku a pak rychlé opakování.

Možná se špatně ptáš, protože v tom kódu Ti jedou stopky jen když držíš tlačítko Start. Vzhledem k tomu, že u funkce Start není vhodné opakování a je potřeba přesnou reakci, tak to je ten druhý případ, kdy je potřeba přesnější obsluha. Např. tady funkce na stisk tlačítka Start, kterou program volá z hlavní smyčky každých 10 ms:

#define BTNHOLD 10 // doba podrzeni tlacitka = 100 ms char StartCitac = 0; char StiskStart(void) { char old = StartCitac; if (StartCitac > 0) StartCitac--; if ((PIND & (1<<PD6))== 0) StartCitac = BTNHOLD; return ((StartCitac > 0) && (old == 0)); }

A v hlavní smyčce pak:

[code]char StopkyBezi = 0;
char Citac10ms = 0;
char SecJ = 0;
char SecD = 0;
char MinJ = 0;
char MinD = 5;
void main ()
{

while (1)
{
if (StiskStart()) StopkyBezi = !StopkyBezi;

	if (StopkyBezi)
	{
		Citac10ms++;
		if (Citac10ms == 100)
		{
			Citac10ms = 0;
			SecJ--;
			if (SecJ < 0)
			{
				SecJ = 9;
				SecD--;
				if (SecD < 0)
				{
					SecD = 5;
					MinJ--;
					if (MinJ < 0)
					{
						MinJ = 9;
						MinD--;
						if (MinD < 0)
						{
							SecJ = SecD = MinJ = MinD = 0;
							StopkyBezi = FALSE;
						}
					}
				}
			}
		}
	}
	else
	{
		// zde podobne obsluha ostatnich tlacitek
		// pri kazde zmene udaje nulovat Citac10ms !
	}

	// zobrazeni casu
	DispTime();

	// prodleva 10 ms
	_delay_ms(10);
}

}
[/code]

Problém je v řádku

      while (! (PIND & (1<<PD5))) minj++;

Proměnná se tady inkrementuje příliš rychle, třeba 100 000x za vteřinu.

Zkus toto:
Při krátkém stisku se hodnota zvětší o 1,
při dlouhém se zvětšuje každých 200 ms.

[code] if(start== 1)
{
if ((PIND & (1<<PD5))== 0)
{
minj++;
_delay_ms(200);
}

  if ((PIND & (1<<PD4))== 0) 
  {
     secj++;
     _delay_ms(200);
  }

  // displej(min,sec)   

}

[/code]

Cílem je, aby se hodnota při stisku změnila vždy jen o +1. Měl jsem na mysli stisk tlačítka a ne uvolnění. Omlouvám se. Start mám nastaven na start= 1, když tedy stisknu tlačítko na PD6, vše funguje jak má.
Zkusil jsem hodnoty měnit pomocí uvedeného kódu. Dochází k tomu, že se navýší o jedna a stopky se spustí bez stisku jakéhokoliv tlačtka.
Když nepoužívám změnu hodnot, pak stopky jdou spusti stiskem PD6 až k nule.

while (start == 1) { if ((PIND & (1<<PD5))== 0) { minj++; _delay_ms(500); } if ((PIND & (1<<PD4))== 0) { secj++; _delay_ms(500); } if ((PIND & (1<<PD6))== 0) start= 0; //start stopek }
Celý program je v příloze. Možná nevidím chybu někde jinde.
Děkuji
main.c (1.84 KB)

Jeden z triků na ošetření zákmitů, který používám je následující :

    lds Temp, Stav_Tlacitka ; Temp je registr, Stav_Tlacitka je pozice v RAM
    clc
    sbic PINx, Py ; Brána x, pin y
    sec
    rol Temp
    sts Stav_Tlacitka, Temp

Stav tlačítka se mění až při hodnotách 0x00 nebo 0xFF. Vše ostatní je stav se zákmity.

Na zjištění, zda je tlačítko právě stisknuto, drženo nebo právě uvolněno je možné použít něco v tomto smyslu :

    lds Temp, Stav_Tlacitek
    sts Stav_Tlacitek_Minule, Temp
    rcall Zjisti_Aktualni_Stav ; Tady se na pozici Stav_Tlacitek vrátí aktuální hodnoty.

; Vlastní zpracování stavů - tlačítko je stisknuto při log. 0

    lds Temp, Stav_Tlacitek_Minule
    lds Temp2, Stav_Tlacitek

    sbrc Temp2, 0 ; Pokud je bit0 = 1
    rjmp Nestisk_tlacitka_0 ; Tlačítko není stisknuté
    sbrs Temp, 0 ; Pokud je bit0 = 0
    rjmp Drzeni_tlacitka_0 ; Tlačítko už bylo stisknuté minule
    
    ; Zpracování stisku tlačítka 0
    rjmp Tlacitko_0_konec

Drzeni_tlacitka_0:
    ; Zpracování držení tlačítka 0
    rjmp Tlacitko_0_konec

Nestisk_tlacitka_0:
    sbrc Temp, 0 ; Pokud je bit0 = 1
    rjmp Stisk_tlacitka_0_konec ; Tlačítko už bylo nestisknuté minule

    ; Zpracování puštění tlačítka

Tlacitko_0_konec:

Jen jsem zatím nezpohodlněl natolik, abych začal pro mikrokontrolery používat C-čko.

Dlouho jsem se tomu bránil, ale nakonec mě šéf přesvědčil. S těmi neustálými změnami procesorů není možné stíhat přeučovat se na stále jiné instrukční sady a hlavně - v assembleru je vývoj mnohem pomalejší, pracnější, dá se nasekat více chyb. Proto je optimum - pokud to ničemu nevadí, psát v C a jen v kritických bodech doplňovat assembler (většinou stačí inline). Novější procesory mají už docela dost paměti, tak místo není tak kritické. A většina operací probíhá pomalu, kdy by se optimalizace neprojevila. Dřívější překladače překládaly dost hloupě, ale dnešní (jako gcc nebo Keil) už překládají hodně optimalizovaně.

Tak, já zatím používám jen 8-bitový AVRka, takže si s assemblerem vystačím, krom toho mám vlastních knihoven tolik, že se mi do C-čka ani nechce. Ne, že bych ho už nezkoušel, ale prostě se mi to zdá jako jít s kanónem na vrabce. Jakmile bych sahnul po jiným mcu, kde nemám knihovny napsaný, pak by byl přechod na C-čko pochopitelně na pořadu dne…

DaveG piny portu D máš nastavené jako výstupní, proto Ti při čtení vždy vrací 0. Měl bys mít:
DDRD = 0x0f;

Balů tohle je trochu zrádné. Člověku to pak brání v tom, zkoušet jiné nové věci, přejít na něco jiného, protože má vybudované “to své” a v nových oblastech by to neměl a tak se mu do toho nechce (znám to velmi dobře, např. se mi nechce do Linuxu, protože pod Windows mám moře svých knihoven). Možná bys mohl, jako duchovní rozcvičku, občas zkoušet zabrousit i do neznámých oblastí, např. pomalu přepisovat své knihovny do C a vybudovat si i tam to své zázemí, jinak Tě to bude svazovat a nikdy si nevyzkoušíš třeba ARMy. Když mě v práci nutí dělat v oblastech co neznám, tak sice nadávám, ale nakonec zpětně jsem rád, protože jinak bych ustrnul na svém písečku a nepohnul se dál.

Dík Pando. Vyřešeno.

Ještě by se mělo vyřešit nastavení pull-up na tlačítkách, pokud tam nejsou externí odpory.
A odčítání času mi v simulátoru nefunguje.

Podle datasheetu jsou na portech interní pull-up rezistory. Měl jsi na mysli tohle? V simulátoru jsem to nezkoušel, ale po nahrání do mcu funguje start, nastevení času i odpočet. Jen nějaká možnost přesnějšího čekání než za použítí delay? Jak se eventuelně ošetruje ten pull up?

na portoch su sice pull-up rezistory, ale treba ich nastavit, defaultne su odpojene. napr pull-up na PORTD, pin6 nastavis ako PORTD = 0b01000000;
Cas sa da najpresnejsie kontrolovat Timerom. Timer kontroluje takt procesora, takze vies uplne presne, aky cas ubehol.

[code]int main (void)
{

DDRC = 0xff; // cely port C je vystupni
DDRD = 0x7f; //pouzivam dolni 7 bity portu D

unsigned int secd= 0, secj= 0, mind= 1, minj= 0, count= 0, start= 1;

PORTC = 0;
PORTD = 0;[/code]
Pro port D má být

DDRD = 0b00001111; // PD4 PD5 PD6 vstup(tlačítka), PD3,2,1,0 výstup PORTD = 0b01110000; // PD4 PD5 PD6 pull-up pro tlačítka
Dál je třeba upravit kód pro zobrazení.

...
...
PORTC = gen [mind] ^128;
   PORTD =0b11110111;
   _delay_ms(5);
   PORTD = 0x00;[/code] Tady se posledním příkazem pull-upy zruší.

Kromě toho je zbytečné používat v celém kódu desítky a jednotky, když je potřebujeme jenom pro zobrazení.
Kód by mohl vypadat třeba takto
[code]/*--------------------
obsluha 7segmentoveho displeje - stopky

PORTC..segmenty zobrazovače

PD0,1,2,3...anody (katody) 7-seg
PD4.........tlačítko nastavení sekund
PD5.........tlačítko nastavení minut
PD6.........tlačítko počítání času
----------------------*/

#ifndef F_CPU
#define F_CPU 8000000UL
#endif
#include <avr/io.h>
#include <util/delay.h>


// globální proměnné
unsigned char gen] =        // hodnoty pro 7-seg displej	
{
0b00000001,	//0   abcdefg       
0b01001111,	//1   6543210
0b00010010,	//2   
0b00000110,	//3 
0b01001100,	//4 
0b00100100,	//5 
0b00100000,	//6 
0b00001111,	//7 
0b00000000,	//8 
0b00000100,	//9 
};

volatile signed char sec=1, min, count;

//-------------------------------
int main (void)
{
   // inicializace portů
   DDRC = 0xff;	       // cely port C je vystupni
   PORTC = 0;

   DDRD  = 0b00001111;	// PD4 PD5 PD6 vstup(tlačítka), PPD3,2,1,0 výstup
   PORTD = 0b01110000;   // PD4 PD5 PD6 pull-up pro tlačítka 

//-------------------------------
   while (1)
   {
 
      // nastavení času  
      if(bit_is_set(PIND,6))  
      { 
         if ((PIND & (1<<PD5))== 0)
         {
            min++; 
            _delay_ms(500);
         }

         if ((PIND & (1<<PD4))== 0)
         {
            sec++; 
            _delay_ms(500);
         }
      } 


      // počítání času
      if(bit_is_clear(PIND,6))  
      {
         count++;
         if(count>50)   // každou vteřinu
         {
            count = 0;
            sec--;

            if(sec<0) {sec=59; min--;}
            if(min<0 && sec==59) {break;}
         }
      }

   	// zobrazení
      PORTC = gen[sec%10];   // jednotky sec 
      PORTD = 0b1111101;
      _delay_ms(5);
      PORTD = 0b01110000;

      PORTC = gen[sec/10] +128;  // desítky sec
      PORTD = 0b1111110;
      _delay_ms(5);
      PORTD = 0b01110000;
   
      PORTC = gen[min%10];
      PORTD = 0b11111011;
      _delay_ms(5);
      PORTD = 0b01110000;
   
      PORTC = gen[min/10] +128;
      PORTD =0b11110111;
      _delay_ms(5);
      PORTD = 0b01110000;


   }//while(1)
}//main

Zdroják v C vyjde sice jednodušší, a v této nenáročné aplikaci to má zřejmě opodstatnění, ale - MCU nemá hw děličku a výpočet / 10 je dost náročný, tak tam bych se k této úpravě asi nepřikláněl.

V tomto programu nehraje doba dělení žádnou roli, takže zjednodušení kódu se vyplatí.
Pro přesnější měření je ovšem třeba upravit pomocí simulátoru zpoždění 5 ms v části “zobrazení”.
Pro oscilátor 8 MHz (mega88) mi vyšlo _delay_us(4985).

Schéma + program jednoduchých stopek 99,99s (R5-R8 jsou použity je pro správnou funkci simulátoru):
Reset funguje jen při zastavení stopek.
Stopky.c (2.81 KB)
Stopky.GIF