Atomicita

Ahoj všem,
mam takový menší problém nejspíš s atomicitou.
Ale uvedu do problému, postavil jsem si z nudy takové blikátko, kde mam tři LED a jedno tlačítko. Smyslem je, že počítám stisky tlačítka a rozsvěcuji je podle toho jaký počet stisků odpovídá pro aktivaci jednotlivých LED diod. To vše funguje bez problému, nicméně jsem se rozhodl, že si LEDKy rozblikám a tam je problém, po stisku tlačítka a odpovídající hodnotě stisku se mi ten if nevykoná, nevím proč. Ke zpoždění používám funkci _delay_ms() z knihovny util/delay.h a celý ten kus kódu mam nacpaný v bloku atomic z knihovny util/atomic.h. Zpoždění mezi změnou stavu portu tvořím skrze tu fnci _delay_ms() a tak mne napadlo, jestli si to spolu rozumí dobře? Mne přijde že ne, a nevím proč. A nebo jestli mám špatně nastavený kompilátor avr-gcc? V současné době mám nastaven optimalizaci takto: Optimize (O1). Byl bych rád, kdyby mi někdo vysvětlil kde mam problém, popřípaděm jestli jsem udělal logickou chybu v kódu a přehlížím jí.
níže kód.

#define F_CPU 8000000UL
#include <avr/io.h>
#include <util/delay.h>
//#include <avr/interrupt.h>
#include <util/atomic.h>
#include "header_tiny.h"

volatile uint8_t stav;
volatile uint8_t i;

int main(void)
{
	DDRB |= (1<<PB0)|(1<<PB1)|(1<<PB4); //nastaveni vystupu pro jednotlive zateze
	DDRB &= ~(1<<PB2); //nastaveni vstupu pro tlacitko
	PORTB |= (1<<PB2);
	    
    while (1) 
    {			
		if((PINB&(1<<PB2)) == 0){
			_delay_ms(120);
			if((PINB&(1<<PB2)) == 0){
				stav++;
				
				if(stav == 1){
					
					PORTB |= (1<<PB0);
					
					}else if(stav == 2){
						
					PORTB |= (1<<PB1);
					
					}else if (stav == 3){
						
					PORTB |= (1<<PB4);					
										
					}else if(stav == 4){
						ATOMIC_BLOCK(ATOMIC_FORCEON){
														
									PORTB |= (1<<PB0);
									PORTB |= (1<<PB1);
									PORTB |= (1<<PB4);
									_delay_ms(500);
									PORTB &= ~(1<<PB0);
									PORTB &= ~(1<<PB1);
									PORTB &= ~(1<<PB4);
									_delay_ms(500);					
																											
						}
						
					}else if(stav == 5){
						stav = 0;						
					}						
			}							
		}	
									
    }
	return 0;
}

Nevyznám se vůbec v použitém procesoru, ale zdá se mi, že by to mohlo zlobit, protože proměnné stav nenastavuješ výchozí hodnotu.
Jo a uvažoval bych o tom změnit tento řádek:

}else if(stav == 5){

takto

}else if(stav >= 5){

Případně vynechat if

					}else{
						stav = 0;						
					}						

děkuji za reakci, jdu to zkusit. Jinak jedná s eo řadu AVR attiny 45

Atomic block jen zakáže a pak povolí globální přerušení. delay_ms využívá prodlevu se smyčkou, nepoužívá přerušení, tak si to navzájem nevadí.

Jen je mi ten kód docela záhadou, nepřijde mi že by počítal stisky tlačítka. :hushed: Dokud se tlačítko drží, tak se s prodlevou 120 ms inkrementují LEDky až nakonec zůstanou 500ms svítit, pak zhasnou a 500ms to na nic nereaguje a při dalším držení to jede znovu. … to tak mělo fungovat?

Tak, ako je to napísané, ten atomic je tam úplne zbytočne

LED Ti neblikajú, lebo “blikacia” sekvencia sa dostane k slovu len keď je stlačené tlačidlo na dobu dlhšiu ako tých 120ms. No ale keď Ti to raz blikne a stále držíš tlačidlo, potom sa pri ďalšom prechode zmení hodnota premennej stav a tak to nemôže znovu blikať.

Táto časť

if((PINB&(1<<PB2)) == 0){
			_delay_ms(120);
			if((PINB&(1<<PB2)) == 0){
				stav++;

má byť napísaná buď inde, alebo inak.

Můžu se tedy zeptat jak by jsi to přepsal ty? Rád bych se vyhl použití interruptu.
Já jsem víc elektronik a k programování jsem se vrátil po skoro 4 letech, takže se se asi budu, pro někoho, blbě a začátečnicky ptát.

Nepýtaš sa blbo. No nemám čas kód písať a ladiť a kontrolovať.

Preto to napíšem didakticky a bez použitia prerušenia.

  1. cez #define si zadefinuj konštanty
    #define FALSE 0x00
    #define TRUE 0xff
    #define CAS_PRVEHO_STAVU 200
    #define CAS_DRUHEHO_STAVU 400
    #define CAS_TRETIEHO_STAVU 500 // toto bude cas stavu LED pre generovanie blikania s periodou 1s

  2. zadefinuj si premenné
    okrem iného
    uint8_t stav = 0;
    uint8_t stav_mem = 0;
    uint8_t tlacidlo = FALSE;
    uint8_t tlacidlo_mem = FALSE;
    uint16_t casovac_stavu = 0;

  3. nastav si nejaký časovač tak, aby periodicky pretiekol 1x za 1ms

  4. testovaním príslušného bitu zisti, či časovač dopočítal 1ms

  5. Ak sa ešte 1ms nedopočítalo (bit nie je nastavený), znovu skoč na bod 4), ak sa čas dopočítal, skoč na bod 6)

  6. Nezabudni bit pretečenia časovača vynulovať. Väčšinou sa to robí zápisom log.1 na jeho miesto.
    Presný postup si prečítaš v datasheete.

    Tvojou úlohou bude detekovať nábežnú hranu stlačenia tlačítka a to tak, aby si vylúčil zákmity.

    Preto si budeš musieť

    • vytvoriť premennú, ktorá bude interpretovať internú hodnotu stlačenia tlačidla po časovej filtrácii
      algoritmus časovej filtrácie nie je zložitý, no teraz by Ťa iba zbytočne zamotal. Jednoducho daj paralelne k tlačidlu kondenzátor 100nF - 1uF a pre Tvoje aktuálne potreby máš vyfiltrované.

    • vytvoriť premennú, ktorá bude obsahovať predchádzajúcu internú hodnotu, aby si mohol zistiť, že tlačidlo nebolo stlačené a už je.

  7. ak je fyzicke tlacidlo stlacene, potom tlacidlo = TRUE. Inak tlacidlo = FALSE

  8. ak tlacidlo = TRUE a zároveň tlacidlo_mem = FALSE

    • inkrementuj premennú stav.
    • ak je stav >= 4, potom stav = 1
    • LED = ON
      if (stav == 1) casovac_stavu = CAS_PRVEHO_STAVU
      if (stav == 2) casovac_stavu = CAS_DRUHEHO_STAVU
      if (stav == 3) casovac_stavu = CAS_TRETIEHO_STAVU
  9. tlacidlo_mem = tlacidlo;

  10. // pre stav 1 a 2 sa vygeneruje iba jeden impulz rozsvietenia LED
    // pre stav 3 sa bude LED po dopočítaní časovača meniť
    // stláčaním tlačidla počas svietenia LED spôsobí znovunaštartovanie časovača svietenia, takže LED bude stále svietiť
    if (casovac_stavu) {
    casovac_stavu–;
    if (casovac_stavu == 0) {
    zneguj LED; // pre stav 1 a 2 sa LED iba vypne
    if (stav == 3) {
    casovac_stavu = CAS_TRETIEHO_STAVU
    }
    }
    }

  11. skok na bod 4)

Približne takto nejako. Časovanie LED a vyhodnocovanie tlačidla bude prebiehať z pohľadu užívateľa “paralelne” a to aj bez použitia interruptu.
Keď sa bojíš použiť časovač pre periodické generovanie 1ms času, tak potom dočasne kľudne miesto bodov 4, 5 a 6 použi delay o dĺžke 1ms. Perióda bude síce trvať dlhšie o vykonanie celého programu, no nepredpokladám, že pri takte 8MHz by program trval dlhšie ako 10-50us.

děkuji :slight_smile:

Tak jsem tam někde udělal chybu a nebo blbě pochopil ten popis. Dole je kód, je to psané pro attiny45, jiné mcu nepoužívám než attiny25-85.

/*
 * blikac_V1.c
 *
 * Created: 30.07.2021 23:52:45
 * 
 * Vypocet zpozdeni pro fci user_delay(): x=8000000/64
 * preddelicka nastavena na 64
 *	T=1/x = 8us
 * y=8*255 = 0.002ms 
 * Timer=1/y = 1/0.002 = 500
 * delay = 500*0.002 = 1ms
 *
 *
 */ 

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

#define FALSE 0
#define TRUE 1
#define prvni_stav 200
#define druhy_stav 400
#define treti_stav 500

#define button (bit_is_set(PINB,2))

volatile uint8_t stav = 0;
volatile uint8_t stav_memory = 0;
volatile uint8_t tlacitko_1 = FALSE;
volatile uint8_t tlacitko_1_memory = FALSE;
volatile uint16_t stav_casovace = 0;

void setup_MCU(){
	DDRB |= (1<<PB0)|(1<<PB1)|(1<<PB4); //nastaveni vystupu
	DDRB &= ~(1<<PB2); //nastaveni vstupu
	PORTB |= (1<<PB2); //nastaveni pull-up pro vstup
	TCCR0A = 0x00;
	TCCR0B = 0x00;
	TCCR0B |= (1<<CS00)|(1<<CS01);
	TCNT0 = 0;
}

void user_delay(){
	uint8_t i = 0;
	while(i <= 8){
		while ((TIFR & (1<<TOV0)) == 0);
		TIFR |= (1<<TOV0);
		i++;
		
		stav_casovace = i;	
	}
}



int main(void)
{
	setup_MCU();
   
    while (1) 
    {
		
		if(button == 1){
			tlacitko_1 = TRUE;
		}else{
			tlacitko_1 = FALSE;
		}
		
		if ((tlacitko_1 == TRUE) & (tlacitko_1_memory == FALSE))
		{
			stav++;
			
			if(stav >= 4){
				
				stav = 1;
			}
			
			if (stav_casovace)
			{
				stav_casovace--;
			}
			
			if (stav_casovace == 0)
			{
				PORTB &= ~(1<<PB0);
				PORTB &= ~(1<<PB1);
				PORTB &= ~(1<<PB4);
			}
						
			if (stav == 1)
			{
				stav_casovace = prvni_stav;				
				
				PORTB |= (1<<PB0)|(1<<PB1)|(1<<PB4);
			}
			
			if (stav == 2)
			{
				stav_casovace = druhy_stav;
				
				PORTB |= (1<<PB0);
				PORTB &= ~(1<<PB1);
				PORTB |= (1<<PB4);
			}
			
			if(stav == 3)
			{
				stav_casovace = treti_stav;
				
				PORTB ^= (1<<PB0);
				PORTB ^= (1<<PB1);
				PORTB ^= (1<<PB4);				
			}
			
			tlacitko_1_memory = tlacitko_1;			
		}		
    }
}

Presne ako píšeš. Nepochopil si to dobre.

Čo konkrétne očakávaš od premennej stav_casovace?
Funkciu:

void user_delay(){
	uint8_t i = 0;
	while(i <= 8){
		while ((TIFR & (1<<TOV0)) == 0);
		TIFR |= (1<<TOV0);
		i++;
		
		stav_casovace = i;	
	}
}

Nikde nevoláš. Je teda úplne zbytočná a nič nerobí.

Skús to, ako som Ti písal predtým.

Hneď za

while(1)
{

vlož

_delay_ms(1)

to Ti zabezpečí, že sa celá slučka spraví 1x za cca 1ms. Nie je to síce exaktné, ale aby si sa posunul ďalej, tak na to to bude stačiť.

táto časť

if (stav_casovace)
			{
				stav_casovace--;
			}

nesmie bežať len vtedy, keď je nábežná hrana tlačítka, ale po každej milisekunde. Takže Ti tam predtým chýba uzatváracia zátvorka pre príkaz

if ((tlacitko_1 == TRUE) & (tlacitko_1_memory == FALSE))
		{
			stav++;
			
			if(stav >= 4){
				
				stav = 1;
			}
        }

Ďalej tam máš zase chybu v tom, že ten stav_casovace sa musí testovať na nulovú hodnotu iba raz po jeho dopočítaní

takže normálne by to celé mohlo napríklad vyzerať nejako takto:

/*
 * blikac_V1.c
 *
 * Created: 30.07.2021 23:52:45
 * 
 * Vypocet zpozdeni pro fci user_delay(): x=8000000/64
 * preddelicka nastavena na 64
 *	T=1/x = 8us
 * y=8*255 = 0.002ms 
 * Timer=1/y = 1/0.002 = 500
 * delay = 500*0.002 = 1ms
 *
 *
 */ 

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

#define FALSE 0
#define TRUE 1
#define PRVY_STAV 200  // je dobrym zvykom rozlisovat medzi konstatntami a premennymi vo velkosti  pismen, Ale v principe je to jedno
#define druhy_stav 400
#define treti_stav 500

#define button (bit_is_set(PINB,2))

volatile uint8_t stav = 0;
volatile uint8_t stav_memory = 0;
volatile uint8_t tlacitko_1 = FALSE;
volatile uint8_t tlacitko_1_memory = FALSE;
volatile uint16_t stav_casovace = 0;

void setup_MCU(){
	DDRB |= (1<<PB0)|(1<<PB1)|(1<<PB4); //nastaveni vystupu
	DDRB &= ~(1<<PB2); //nastaveni vstupu
	PORTB |= (1<<PB2); //nastaveni pull-up pro vstup
	TCCR0A = 0x00;
	TCCR0B = 0x00;
	TCCR0B |= (1<<CS00)|(1<<CS01);
	TCNT0 = 0;
}


int main(void)
{
	setup_MCU();
   
    while (1) 
    {
        _delay_ms(1)		

		if(button == 1) tlacitko_1 = TRUE;
		else tlacitko_1 = FALSE;
		
		if ((tlacitko_1 == TRUE) & (tlacitko_1_memory == FALSE))
		{
			stav++;
			if(stav >= 4) stav = 1;

            // stav_casovace nastavujeme len pri nabeznej hrane tlacitka. Iba ak stav == 3, iba vtedy znovu prednastavime hodnotu aj po dopocitani do nuly.
     
			if (stav == 1)
			{
				stav_casovace = PRVY_STAV;				
				PORTB |= (1<<PB0)|(1<<PB1)|(1<<PB4);
			}
			
			else if (stav == 2)
			{
				stav_casovace = druhy_stav;
                PORTB |= (1<<PB0)|(1<<PB4);
                PORTB &= ~(1<<PB1);
			}
			
			else if(stav == 3)
			{
               stav_casovace = treti_stav;
               // Tu sa bude iba SET- ovat
               // XOR- ovat sa bude az po dopocitani casovaca
               PORTB |= (1<<PB0)|(1<<PB1)|(1<<PB4);
			}
         }			
         tlacitko_1_memory = tlacitko_1; // dame to tu, aby sme na to na konci programu nezabudli


         if (stav_casovace)
         {
	       stav_casovace--;
           if (stav_casovace == 0) {
              if (stav == 3) 
              {
                 // nastavenim casovaca tato slucka prebehne znovu a znovu.
                 // na rozdiel od toho, ak sa stav == 1 alebo 2
                 stav_casovace = treti_stav;
                 PORTB ^= (1<<PB0);
                 PORTB ^= (1<<PB1);
                 PORTB ^= (1<<PB4);				
              }
              // ak to nie je treti stav, tak sa LED-ky zhasnu
              else {
                 PORTB &= ~(1<<PB0);
                 PORTB &= ~(1<<PB1);
                 PORTB &= ~(1<<PB4);
              }
           }
        }
    }
}

Inak tri štvrtiny času písania príspevku som zabil vymazávaním tabelátorov a formátovaním textu do kódu. Tak jednak neviem, či mi presne sedia zátvroky, ale už na to nemám trpezlivosť. Najlepšie je písať kód bez použitia tabelátorov, ale s obyčajnými medzerami.
Snáď som pomohol.

Klidně se “začátečnicky a blbě” ptej. Aspoň je vidět snaha. Na druhé straně vyhýbat se za každou cenu přerušením není úplně dobře. V lecčems velmi usnadní práci a složitě to vypadá jen na první pohled.

Druhá věc je použití delay. To je něco, čemu by měl každý programátor vyhnout jako čert kříži. Ještě tak do časů do pár milisekund, ale cokoliv delšího řešit pomocí časovače a flagů. Uvědom si, že 1 ms znamená při těchto hodinách (8 MHz) až 8000 instrukcí. Pak se tu objevují dotazy typu “ono mi to nereaguje na tlačítka” a přitom je program plný delayů.

S těmi delay to i chápu, že jsou problémové, Když jsem kdysi psal SW tak jsem si jim také vyhýbal, ale jak píšu, neviděl jsem MCU cca 3 -4 roky, dělám dávno v jiné elektrobranži a teď jsem se k tomu vrátil spíš jako ke koníčku, i když uvidíme dál. Ten delay co mam dole pro blikání nahradím časovačem tmr1, spíš mi jde o to, jak rozumně vyřešit uvolnění tlačítka a napsat si to jako knihovnu pro budoucí použití i u více tlačítek. Jestli by jsi mi mohl dát nějaké vodítko?
Děkuji

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

#define BUTTON_PRESS (!(PINB&(1<<PB2)))

#define LED_1_ON PORTB |= (1<<PB0)
#define LED_2_ON PORTB |= (1<<PB1)
#define LED_3_ON PORTB |= (1<<PB4)

#define LED_1_OFF PORTB &= ~(1<<PB0)
#define LED_2_OFF PORTB &= ~(1<<PB1)
#define LED_3_OFF PORTB &= ~(1<<PB4)

#define  LED_1_TOGGLE PORTB ^= (1<<PB0)
#define  LED_2_TOGGLE PORTB ^= (1<<PB1)
#define  LED_3_TOGGLE PORTB ^= (1<<PB4)

volatile uint8_t count;

void setup_MCU(){
	DDRB |= (1<<PB0)|(1<<PB1)|(1<<PB4); //nastaveni vystupu
	DDRB &= ~(1<<PB2); //nastaveni vstupu
	PORTB |= (1<<PB2); //nastaveni pull-up pro vstup
	TCCR0A = 0x00;
	TCCR0B = 0x00;
	TCCR0B |= (1<<CS00)|(1<<CS02);
	TCCR0A |= (1<<WGM01);
	OCR0A = 94;
	TCNT0 = 0;		
}

void debounce_delay(){
	uint8_t i = 0;
	
	while(i <=1){
		while ((TIFR & (1<<OCF0A)) == 0);
		TIFR |= (1<<OCF0A);
		i++;
	}
}

int main(void){
	
	setup_MCU();
	
	while (1)
	{				
		if(BUTTON_PRESS){
			debounce_delay();
			if(BUTTON_PRESS){
				count++;
				debounce_delay();						
				
				if (count == 1)
				{				
					LED_1_ON;
					LED_2_OFF;
					LED_3_OFF;					
					
				}else if(count == 2){
					
					LED_1_OFF;
					LED_2_ON;
					LED_3_OFF;				
					
				} else 	if(count == 3){
					
					LED_1_OFF;
					LED_2_OFF;
					LED_3_ON;				
					
				}else if(count == 4){
					
					LED_1_ON;
					LED_2_ON;
					LED_3_ON;										
					
					} else if(count == 5){
					
					LED_1_OFF;
					LED_2_OFF;
					LED_3_OFF;					
					//count = 0;
																				
					}else if(count == 6){
						
						while(((count >= 6) && (!BUTTON_PRESS))){
							LED_1_ON;
							LED_2_ON;
							LED_3_ON;
							_delay_ms(150);
							LED_1_OFF;
							LED_2_OFF;
							LED_3_OFF;
							_delay_ms(150);
						}						
					}else if(count == 8){
						
						LED_1_OFF;
						LED_2_OFF;
						LED_3_OFF;
						count = 0;
					}			
			}
		}
	}
}

Ako bolo povedané.
Bez použitia prerušenia, napríklad podľa priloženého vývojového diagramu.

S využitím časovača, ktorý nastaví príslušný bitík 1x za 1ms si spravíš jednoduchý systém “reálneho času” s nepreemptívnym multitaskingom. Jediná podmienka plynulého chodu je, že všetky úlohy sa musia stihnúť vykonať do behu 8000 inštrukcií. Program sa samozrejme dá doplniť o test, či sa všetko stihlo do 8000 inštrukcií. Prídeš na to ako?

Vzhľadom k tomu, že Tvoj procesor má maximálne 4096 inštrukcií (8192B) , tak to stihneš určite. Ak by nie, tak si základný časový krok časovača nastav na 10ms. Aj to je celkom slušná plynulá šupa pre multitasking viacerých nezávislých úloh, bežiacich na ATtiny85.

Jedinou podmienkou je, aby si jednotlivé úlohy iba otestovali či majú bežať alebo nie. Ak nie, hneď treba ísť na ďalšiu úlohu. Ak áno, tak sa má spraviť iba funkčná činnosť. K funkčnej činnosti však žiadne delay nepatrí.

Ak si potrebuješ odpočítať nejaký čas, nastav do novej premennej číslo v ms a každým novým prechodom ho dekrementuj. Činnosť spravíš, až keď sw časovač dosiahne hodnotu nula.
Kľudne použi aj príkazy for a while. Len si v teoretickej analýze skontroluj, či tieto slučny budú reálne trvať rozumnú dobu.

Napríklad sw komunikáciu cez I2C (bit bang) si spravíš tak, že zmena CLK bude iba 1x za 1ms. Dosiahneš síce “iba” 500Bd rýchlosť, ale to rozhodne žiadnemu I2C zariadeniu nevadí.

Tlačítka dělám tak, že je kontroluju v přerušení (většinou) každých 10ms. Řeším tam i debounce. Následně zapisuju do proměnných stisknuté tlačítko, dlouhý stisk tlačítka, autorepeat, uvolněné tlačítko, náběžnou hranu a sestupnou hranu. Netvrdím, že všechno najednou, ale v programu pak už jenom kontroluju tyhle proměnné a ne přímo vstupy procesoru.

Dlouhé procesy nebo tam, kde jsou delší prodlevy, rozfázovávám.

Malý příklad :

ISR( timer s periodou 10 ms )
{
    if (LedDelayTimer) LedDelayTimer--;
    .
    .
    .
}

void BlikaniLedkou ( void )
{
    static unsigned char Krok = 0;

    switch (Krok)
    {
        case 0: LED = 1;
                LedDelayTimer = 50; // 50x10 ms = 500 ms
                Krok++;
                break;
        case 1: if (!(LedDelayTimer)) Krok++;
                break;
        case 2: LED = 0;
                LedDelayTimer = 50; // 50x10 ms = 500 ms
                Krok++;
                break;
        case 3: if (!(LedDelayTimer)) Krok = 0;
                break;
        default:Krok = 0;
                break;
    }
}

int main(void)
{
    .
    .
    .
    .

    sei();

    set_sleep_mode(SLEEP_MODE_IDLE);
    sleep_enable();
    
    while (1) 
    {

        sleep_cpu();
        BlikaniLedkou();
        .
        .
        .
    }
}

Ty jo, děkuji!
Tohle je dobré pro moje studium!!
Děkuji