Se sei già registrato           oppure    

Orario: 03/05/2024 22:46:01  

 

Energia Alternativa ed Energia Fai Da Te > Arduino


Perplessità sul codice di programmazione
FinePagina

mappintosh

Avatar
milliWatt


Gruppo:Utente
Messaggi:12

Stato:



Inviato il: 21/04/2017 19:05:09

Salve a tutti.

Arrivo subito al dunque: sto programmando il chip Atmega328P con l'ide di sviluppo di casa Atmel, Atmel Studio 7 in linguaggio c.
Devo campionare un segnale con una frequenza di 10KHz (100us di periodo); e per fare ciò, ho implementato una variabile uint32_t che viene incrementata di 10us mediante una routine di interrupt sul timer0 in modalità CTC. eseguo quindi un ciclo nel quale eseguo i campionamenti e resto in attesa, mediante ciclo while, che siano trascorsi 100us dopo i quali reiterare il lo stesso.
Qua arriva la perplessità, il seguente è il codice inizialmente scritto:

uint32_t us_timer;
ISR(TIMER0_COMPA_vect)	//routine incremento us_timer														
{
	us_timer += TIMER0_SENSIBILITY;	//incrementa il us_timer											
}
...altro codice...
for (i = 0; i < 200; i++)    //eseguo 200 campionamenti per l'offset														
{
	currTime = us_timer;  //salvo il valore della variabile timer													
	ADMUX &= ~ADC_PIN_MUX;	//azzero bit amux adc												
	ADMUX |= ADC_PIN_A;	//seleziono il pin del seganle A													
	ADCSRA |= (1<<ADSC);	//start conversion												
	ADMUX &= ~ADC_PIN_MUX;	//azzero bit amux adc												
	ADMUX |= ADC_PIN_B;	//seleziono il pin del seganle B (vengono
                                //cambiati a fine conversione)													
	while (ADCSRA & (1<<ADSC));	//attesa fine conversione											
	tempADCH = ADCH;	//salva il valore letto													
	ADCSRA |= (1<<ADSC);	//start conversion												
	while (ADCSRA & (1<<ADSC));	//attesa fine conversione											
	if (tempADCH > vMaxA) vMaxA = tempADCH;	//aggiorna la variabile 
                                                //cernado il valore più alto 								
	if (tempADCH < vMinA) vMinA = tempADCH; //stessa cosa segnale basso
	if (ADCH > vMaxB) vMaxB = ADCH;		//segnale B	
	if (ADCH < vMinB) vMinB = ADCH;	
	while((us_timer - currTime) < 100);//attesa fino a fine del periodo
}


Eseguendo il codice sul chip si bloccava, precisamente nel ciclo ultimo di attesa fine periodo (Ho controllato inserendo l'accensione di alcuni led nella routine).
La prima considerazione che ho fatto è stata in merito alla possibilità che la routine di interrupt per la gestione della variabile us_timer non funzionasse.
La prima prova che ho fatto è stata inserire un paio di righe aggiuntive per inviare il valore della suddetta variabile via UART e la variabile veniva incrementata correttamente, almeno fino all'inizio del ciclo.
La seconda è stata controllare l'evolversi del sistema inviando di volta in volta il valore della variabile alla fine di ogni ciclo di campionamento ma prima del while di attesa e MAGIA! il codice ha iniziato a girare inviandomi i valori della variabile.
E qui ho dedotto che essendo la stringa inviata tramite UART di una 30 di char, ed essendo la velocità di 115200 per inviarla servivan almeno 260us; quel tanto che bastava alla variabile us_timer per superare abbondantemente
i 100 di fine campionamento. Di fatto entrando nel ciclo while, questo veniva direttamente saltato.


pointer = data;
pointer += sprintf (pointer, "ustimer= %lu currTime+100= %lu", us_timer, 
                                                          currTime+100);
sendString(data, pointer - data);
while((us_timer - currTime) < 100);  //attesa fino a fine del periodo


La Terza prova è stata inviare la differenza tra us_timer e currTime (4 char almeno 34us) all'interno del blocco while di attesa fine periodo; per garantire che questo venisse reiterato almeno un paio di volte ho alzato il periodo di campionamento a 200us

while((us_timer - currTime) < 200)  //attesa fino a fine del periodo
{
     pointer = data;
     pointer += sprintf (pointer, "%lu\n", us_timer - currTime);
     sendString(data, pointer - data);
}

Anche il codice ha funzionato inviandomi circa 3 valori per ciclo(circa perchè non li ho contati, ma ho apprezzato l'aumento del numero di righe inviatomi).
L'ultima prova che ho fatto è stata togliere le righe di invio della stringa e implementare un semplice ritardo di 1us

while((us_timer - currTime) < 100) _delay_us (1);

e il codice funziona egregiamente ma non riesco ancora a capire il motivo per il quale

while((us_timer - currTime) < 100);

si bloccasse; come se il continuo controllo della variabile impedisse alla routine di interrupt di incrementarla.
Per le poche conoscenze che ho in materia, questo non mi sembra un comportamento logico; e son alqualto preoccupato nel programma finale è prevista un'ulteriore routine di interrupt per gestire i dati provenienti dalla UART, e non vorrei che anche questa freezzasse in alcuni momenti.
Non so, qualcuno ha qualche consiglio? Faccine/giveup.gif
Vi ringrazio anticipatamente
Saluti Salati Saliti

 

calcola
GigaWatt


Gruppo:Utente
Messaggi:4435

Stato:



Inviato il: 22/04/2017 06:11:55

Credo che hai già individuato il problema, occorre considerare un ritardo di 1us per dare il tempo di scrivere nella sdram, bisogna tenere conto delle latenze d'accesso alla memoria, che adc esterno usi?



Modificato da calcola - 22/04/2017, 06:21:30


---------------
Impara l'arte e mettila da parte
14 pannelli da 100w, inverter kemapower 3kw, regolatore morningstar tristar ts60, banco batterie n.1 di 12 elementi 2v 480Ah C5 corazzate per trazione pesante, banco batterie n.2 di 400Ah in C5 formato da 24 elementi 2V 200Ah corazzate al gel per fotovoltaico in due serie da 12 elementi, centralina di gestione impianto autoprodotta.

 

mappintosh

Avatar
milliWatt


Gruppo:Utente
Messaggi:12

Stato:



Inviato il: 22/04/2017 07:59:46

Grazie per la risposta, in realtà ni
essendo una routine di interrupt ha la priorità rispetto a quella normale.
Ho scritto una altro programmino per testare le funzionalità del timer in CTC. un semplice ciclo di attesa per la pressione di un tasto, un ritardo di 500ms e il toggle di un led. nell'inframezzo ho salvato su tre variabili il valore di us_timer per poi inviarmeli sul pc. e cronometro alla mano ho verificato l'andamentento del sistema.
Praticamente, non so di quanto con esattezza ma, intorno ai 30s avevo già un ritardo apprezzabile. oltre al fatto che se tenevo premuto il tasto avevo un blink del led di un periodo di quasi 2 secondi.
Ho provato a cambiare prima canale di output compare match e il timer si bloccava e poi a cambiare il timer ma il risultato era lo stesso.
Ho spulciato tra le librarie dell'ide e ho notato che la funzione delay era scritta in modo che venisse reiterato un ciclo vuoto per un numero di volte tale da fare trascorrere il tempo richiesto.
Detto ciò ho deciso di escludere tale libreria e di scrivere da me tale funzioni per scongiurare l'eventuale conflitto con la routine di gestione della uart presente poi nel proramma finale.
Per quanto riguarda la gestione degli eventuali periodi di campionamento, semplicemente resetto il timer all'inizio del campionamento ed entro in ciclo alla fine aspettando sia trascorso il tempo rimanente a completare il periodo.
é un po' come gettare la spugna sinceramente
BTW uso l'ADC interno del chip.

 

mappintosh

Avatar
milliWatt


Gruppo:Utente
Messaggi:12

Stato:



Inviato il: 22/04/2017 08:03:19

aggiungo che più abbassavo la sensibiltà di us_timer più' il sistema era stabile; ma comunque alla lunga si sfasava.
addirittura con risoluzioni troppo alte avevo appunto il doppio del periodo per il blink del led e la metà del valore del tempo trescorso in us.



Modificato da mappintosh - 22/04/2017, 08:19:04
 

calcola
GigaWatt


Gruppo:Utente
Messaggi:4435

Stato:



Inviato il: 22/04/2017 21:22:11

Continuo a ritenere che dipende dalle latenze d'accesso alla memoria, vai troppo al limite e chiedi troppo al povero atmega328.



---------------
Impara l'arte e mettila da parte
14 pannelli da 100w, inverter kemapower 3kw, regolatore morningstar tristar ts60, banco batterie n.1 di 12 elementi 2v 480Ah C5 corazzate per trazione pesante, banco batterie n.2 di 400Ah in C5 formato da 24 elementi 2V 200Ah corazzate al gel per fotovoltaico in due serie da 12 elementi, centralina di gestione impianto autoprodotta.

 

mappintosh

Avatar
milliWatt


Gruppo:Utente
Messaggi:12

Stato:



Inviato il: 02/05/2017 20:46:38

Volevo pubblicare le righe di codice che mi han permesso di risolvere, o almeno aggirare, il problema.
Ricapitolando, implementando una routine di interrupt per la gestione di una variabile contatrice per i us trascorsi, assieme alle funzioni "_us_delay" e "_ms_delay" della libreria util/delay per gestire i ritardi interni, si andava ad accumulare uno sfasamento temporale proporzionale alla sensibilità della variabile contatrice us; con 10us avevo sfasamenti apprezzabili dopo alcuni minuti per la campionatura, e un ritardo aggiuntivo del ~100% per le funzioni, mentre per una sensibilità di 2us gli sfasamenti diventavano subito apprezzabili per la campionatura, e le funzioni implementavano un ritardo del ~700%.

Ho deciso quindi di fare riferimento direttamente ai registri del timer1 a 16bit per tenere traccia delle varie latenze.
la libreria util/delay e le relative funzioni sono state tolte e i ritardi gestiti con apposite routine, le quali anch'esse manipolano i registri del suddetto timer
Ma veniamo al codice:


#define	TIMER1_PRESCALER_MASK ((1<<CS12) | (1<<CS11) | (1<<CS10)) 
 //identifica i bit del prescaler
#define	TIMER1_PRESCALER_8 (1<<CS11) //prescaler per Fosc\8
void microDelay (uint16_t time_us); //prototipo funzione ritardo microsecondi
 void milliDelay (uint16_t time_ms); //prototipo funzione ritardo microsecondi
 
<b>[...main..]</b>
TCCR1A = 0; //normal mode del timer 1
TCCR1B = TIMER1_PRESCALER_8; //abilito timer 1 settando il prescaler

max_v = 0;
min_v = 255;
for (i = 0; i < 200; i++) //eseguo 200 campionamenti per l'offset (200+100=0.02s)
{
TCNT1 = 0; //azzero il timer
ADCSRA |= (1<<ADSC); //start conversion
while (ADCSRA & (1<<ADSC)); //attesa fine conversione
if (ADCH < (0xFF - MAX_AMP) || ADCH > MAX_AMP) return -1; //ritorna -1 se il valore è fuori range (0.5 - 4.5)
if (ADCH > max_v) max_v = ADCH;	//aggiorna la variabile cernado il valore più alto
if (ADCH < min_v) min_v = ADCH;	//aggiorna la variabile cernado il valore più basso
 while (TCNT1 < 200); //cicla fino alla fine del periodo (100us * 0.5)
}

<b>[...funzioni...]</b>

void microDelay (uint16_t time_us)
{
TCNT1 = 0; //azzero il timer1
time_us /= 0.5;	//ottengo il valore del ritardo nella risoluzione del timer1 (1/16/8)us
while (TCNT1 < time_us); //attendo fino al raggiungimento del numero da parte del timer
}

void milliDelay (uint16_t time_ms)
{
double temp_time; //variabile di appoggio per calcolo ritardo su risoluzione timer1
TCCR1B ^= TIMER1_PRESCALER_MASK; //seleziona la risoluzione a 64us
TCNT1 = 0; //azzero il timer1
temp_time = (time_ms / 64.0) * 1000.0 + 0.5; //converto il valore del ritardo nella risoluzione del timer1 
time_ms = (int) temp_time; //ottengo il valore intero di riferimento per il timer
while (TCNT1 <  time_ms); //attendo fino al raggiungimento del numero da parte del timer
TCCR1B ^= TIMER1_PRESCALER_MASK; //reimposto la risoluzione a 0.5us
}


per la funzione microDelay il ritardo massimo è dato da (2^16-1)*0.5= 32767.5us
mentre per milliDelay (2^16-1)*64=4169240us ~4169ms; volendo implementando un ciclo si può tranquillamente estendere il valore massimo per entrambe le funzioni a piacere.

Saluti a tutti.

 
 InizioPagina
 

Versione Mobile!

Home page       TOP100-SOLAR      Home page forum