Problemi con PIC (seguito)

Jun 28, 2006 26 Replies

Ciao a tutti, dopo aver terminato con i vari problemi hardware relativi alla programmazione del PIC16F84A, volevo chiedere un parere sulla parte software. Devo pilotare 4 display a 7 segmenti con un numero che prendo da un convertitore A/D. Per farlo ho usato tutti i pin di PORTB. I primi 4 (Rb0-Rb3) sono per il valore da visualizzare e gli altri 4 sono per la selezione del display. Ho usato un decoder per display esterno per risparmiare linee di I/O. Questa è la funzione che ho scritto per far visualizzare un numero sul display. Nei commenti ho scritto decine, unità ecc. perchè sul mio display le prime due cifre a sx le considero parte intera e le altre parte decimale. La funzione verrà sempre chiamata con un numero a 4 cifre intero. Ho provato a chiamarla dal main in questo modo: visualizza(1234); ed ha funzionato ma, chiamandola con questo: visualizza(2609); già non và più... mi viene fuori un altro numero... Secondo voi sbaglio nel ragionamento? Come devo fare?



Ecco la funzione:



void visualizza(int num) { char a, i=0, dis=0b11110000, resto; //decine a = num / 1000; resto = num % 1000; PORTB = a; PORTB = PORTB | (dis - (16


  1. ma il parametro num è di tipo INT! cioè nel range [0..0xff] e quindi a 4 cifre non ci arrivi! o prima piazzi una #typedef per portare il tipo int a 16 bit (ma te lo sconsiglio vivamente) oppure usi variabili di tipo int16 o LONG (non so che compilatore usi)
  2. il fatto di usare "/" e "%" non ricordo essere così immediato cioè se tu hai un numero di 2 cifre allora con num/10 ottieni le decine e con num%10 le unità... ma questo non vale per numeri a 3 o più cifre!!! devi prima "eliminare" una cifra alla volta e solo alla fine (quando te ne restano 2) puoi usare il %
  3. usando "/" esegui calcoli che per il micro sono una montagna... prova ad ottimizzare un attimo e usare degli shift è importante che questa funzione sia velocissima dal momento che vuoi pilotare in multiplexing, no? per far questo ti serve chiamarla un certo numero FISSO di volte al secondo (50 volte/secondo ad esempio per avere un refresh a 50hz) e quindi ti devi appoggiare da un interrupt... ecc... se la chiami dal main() non va bene almeno che il micro non debba fare solo quello... ti cambiererbbe la luminosità a "piacere"

secondo me era + logico usare degli NPN... emettitore a massa e collettore al comune del display

-ice-

Io uso il Pic C Lite. Mi pareva di ricordare che gli INT siano a 4 byte, non ad un byte come i CHAR. Comunque provo e vi farò sapere.

Scusa: Num da convertire: 2609

2609 / 1000 = 2 (resto 609) 609 / 100 = 6 (resto 9) 9 / 10 = 0 (resto 9 -> unità)

dove sbaglio?

A questo ci avevo pensato, ma come faccio a dividere per 1000 shiftando?? Al massimo posso dividere per 1024...

Il pic deve praticamente fare solo quello, oltre ad altre due cose:

-preleva il dato dall'adc

-lo converte

-visualizza queste sono le azioni che deve fare.

L'ho fatto per dargli uno zero come comando.

Ciao Brunix

Brunix ha scritto:

ad

Beh, puoi fare come ti ho suggerito: prendi la cifra iniziale e sottrai

1000 fino a che il numero rimane positivo, quello che resta =E8 per forza inferiore a 1000 e costituisce il resto della divisione. Prendi il resto di cui sopra e fai la stessa cosa con 100 e poi ripeti il tutto con 10. Non =E8 il massimo dell'eleganza, ma se il tutto =E8 ben scritto, si evita di fare una vera divisione in binario e si guadagna in termini di memoria e di rapidit=E0 di esecuzione. Ho scritto una routine per dividere due numeri in assembler PIC e non =E8 proprio comodissimo.

Ah, anch'io propenderei per dei transistor NPN.

Bhe, visto che non ci sono problemi di performance, perchè riscrivere una cosa che esiste già nel compilatore? Se il compilatore è fatto bene avrà già la sua bella routine di divisione ottimizzata e debuggata, riscrivendola otterresti la stessa cosa, con però in più l'onere di debuggare quello che hai scritto.

Ci sono un po' di imprecisioni in quello che dici; la larghezza del tipo "int" non è universale e dipende dal tipo di piattaforma su cui il sorgente C viene compilato. In ogni caso, non mi è mai capitato di vedere piattaforme in cui un compilatore C usa interi ad 8bit. Di solito il "taglio" più comune è 16bit. Inoltre non esiste nessuna direttiva #typedef che permette di "allungare" i bit usati da un tipo di data. La keyword typedef è usata per definire tipi personalizzati, dando nomi a piacere a tipi già esistenti. Es. se scrivo; typedef int int64;

Dico semplicemente al compilatore che quando trova la stringa "int64" deve considerarla come se fosse un tipo "int". Il fatto che l'abbia chiamata "int64" non significa assolutamente che il dato è a 64bit, potevo dargli qualsiasi altro nome di fantasia e il risultato non sarebbe cambiato.

Vedi sotto altra risposta data a Darwin, se non ci sono problemi di performance è sempre sconsigliato "reinventarsi la ruota"

Concordo

Secondo me l'errore sta nel fatto che ogni volta che fai PORTB = a; sovrascrivi anche i bit che multiplexano i display, io la funziona la farei così, partendo dai centesimi:

#define DATAMASK 0xF0 // 11110000 in binario

void visualizza(int num) { int i; unsigned char portb_buf;

for (i = 0; i < 4; i++) { /* Azzera il buffer del PORTB */ portb_buf = 0;

/* Copiaci il numero attuale */ portb_buf |= num % 10; num /= 10;

/* Accendi il display corrispondente*/ portb_buf |= ((~(1

no, dipende dalla macchina su cui deve girare il codice compilato il codice per pic lo compili su un pc e quindi un int sarebbe a 32 bit ma il compilatore pic-c interpreta int come 8 bit

In ogni caso, non mi è mai capitato di

i pic sono quasi tutti micro ad 8 bit, non vedo perchè usare come tipo base un intero a 16-bit riporto direttamente dal manuale "By default the compiler treats SHORT as one bit, INT as 8 bits, and LONG as 16 bits. The traditional C convention is to have INT defined as the most efficient size for the target processor. This is why it is 8 bits on the PIC®."

per le cpu-cisc sicuramente... per i pic, che sono mcu-risc, direi 8-bit

si, ho sbagliato direttiva... però si fa! così: #type int=16

ok

non si tratta di reinventare la ruota... guarda cosa genera il compilatore quando usi una divisione con "/" però in effetti se la tempistica non è critica forse non ne vale la pena

ciao!

Sono stato frettoloso a rispondere, per "piattaforma" intendevo dire microprocessore target + eventuale sistema operativo, per fare un'esempio sui pc gli int possono essere da 16 a 64 bit a seconda dei casi.

Non conosco il compilatore usato perchè Brunix non lo dice, parlavo di C così come è definito dallo standard C99, per la mia esperienza posso dirti che ho trovato diversi microcontrollori ad 8bit (avr, alcuni motorola) in cui gli int erano a 16bit lo stesso. Comunque mi fido di quello che dici, io parlo in modo generico.

Nuovamente, io parlo di C standard, questa direttiva funzionerà sicuramente ma è un'estensione del compilatore, non è certo portabile.

Se non si tratta di casi particolari in genere il codice compilato è migliore di quello che un programmatore può riscrivere. Non perchè il compilatore è più bravo ma perchè chi ha scritto il compilatore si è già posto il problema ed ha già avuto modo di debuggarlo ed ottimizzarlo. I compilatori più evoluti sono addirittura in grado di riconoscere quando per esempio la divisione può essere sostituita con uno shift.

Io sconsiglio sempre queste ottimizzazioni a meno che non siano davvero necessarie, perchè si corre il rischio di sottovalutare il costo necessario e sopravvalutare i benefici ottenuti.

Ciao :)

Francesco Sacchi ha scritto:

ne

Naturalmente. Esiste pero' il fatto che si sta parlando di un PIC

16F84, che =E8 una macchina con risorse di calcolo piuttosto esigue. Naturalmente, posso dire delle imprecisioni anche perch=E9 io per questo motivo preferisco usare l'assembler. La prima cosa che scrissi in assembler PIC =E8 stato proprio un programma che esegue la divisione non perch=E9 non sapessi dell'esistenza delle ottime librerie ASM e compilatori C per questo microcontrollore, ma per la valenza didattica che quest'esercizio aveva. Feci lo stesso con il 6502.

Ma il C per i PIC =E8 rigorosamente lo standard ANSI, oppure vi sono degli adattamenti?

Ciao ragazzi, nella funzione qui sopra ho sbagliato a mettere la variabile resto come char, perchè ad esempio provando a scrivere 2609, il resto della divisione 2609 / 1000 è 609, che eccede gli 8 bit delle variabili char. Poi ho risolto anche gli altri problemi che avevo nelle altre funzioni. Il sistema è ora più o meno funzionante. Devo correggere un problema che mi fa "ballare" la misura. Devo infatti, prima di leggere il valore convertito, fare in modo che il convertitore smetta di lavorare e blocchi l'uscita. Credo di essere in grado di farlo. Al prossimo aggiornamento. Ciao Brunix

Continuo questa interessante discussione.

Francesco Sacchi ha scritto:

Ho programmato un 68HC11 in cui la gestione di numeri a 16 bit era decisamente pi=F9 comoda che su un PIC, a causa dei registri 16bit. Il PIC =E8 un RISC a 8 bit con architettura Harvard, una macchina molto essenziale, ma con poche istruzioni (35, mi sembra) che il pi=F9 delle volte vengono eseguite in un solo ciclo di clock. Il 68HC11 aveva anche una bella istruzione di divisione hardware a 16 bit, che pero' richiedeva parecchi cicli per essere eseguita. Tutto questo per dire che non mi stupisce affatto che il C dei PIC consideri gli interi ad 8 bit mentre su un Motorola 68HC11 (parlo di questo, perch=E9 lo conosco) potrebbero essere benissimo a 16 bit.

Stiamo sempre parlando di microcontrollori molto piccoli (anche se non per questo limitati come possibilit=E0)... Non so inoltre se il C dei PIC sia standard ANSI.

=E8 gi=E0

Si, ma come dicevo sopra, stiamo parlando di un PIC, ove ogni word risparmiata puo' contare. Non penserei minimamente di scrivere niente in assembler per altri processori, ma per il momento preferisco l'assembler per i PIC pi=F9 piccoli. Inoltre, in casi come questi il programmatore (che anche usa il C) deve conoscere bene quali sono le risorse della macchina. Fare una divisione con un PIC della serie 16 richiede un po' di codice ed un bel po' di cicli macchina. Per quanto il compilatore faccia un lavoro eccellente, non bisogna utilizzare alla leggera la barra di divisione. E' solo un esempio, ovviamente, naturalmente non voglio dire che bisogna conoscere tutto alla perfezione o che il C sia abbandonare (tutt'altro!), tuttavia in molti casi si possono scegliere degli algoritmi alternativi ed =E8 un ottimo esercizio il valutarne l'effettiva validit=E0.

Ciao a tutti, non sono ancora riuscito a risolvere il problema della misura che "balla"... Praticamente, prendendo in esempio 5 misure in successione, è possibile che il circuito scriva in successione 28.88, 28.88, 27.75,

29.15, 28.88, 29.45... Questo è un bel problema, e non so come risolverlo. Dunque, nel mio sistema ho usato un adc esterno (adc0801) montato in configurazione free-running (continuo). Tramite il pic, prima di leggere la misura, blocco la conversione. Una volta visualizzato il numero, faccio continuare le conversioni. Ho già previsto un ritardo tra la visualizzazione dei numeri. Questo funziona, ma il problema è quello dei valori delle misure. Dunque, come sensore di temperatura ho usato un LM35 e come tensione di riferimento quella di un diodo al silicio (0.7 V). La precisione della conversione è quindi (0.7*2/256 = 0,00546875). Dato che il sensore dà 10mV/C, la precisione in gradi è di 0,546875... Per avere il valore convertito faccio: (Valore binario ottenuto dall'adc)*0,546875 Secondo me il problema è proprio la precisione, che è bassa. Ecco come pensavo di risolvere il problema: Amplifico il segnale del sensore circa 10 volte (es 20°C(200mV)=2V) poi riduco il range di misurazione a 0-:-50 °C. Imposto la tensione di riferimento a 2,5V. La precisione diventa (5/256 = 0.0195 V), in gradi: 50/256 = 0.1953°C. Ho fatto qualche errore? Che ne pensate??? Ciao Brunix

Brunix ha scritto:

C'=E8 del rumore all'ingresso dell'ADC? Hai disaccoppiato bene le alimentazioni? Utilizza un filtro prima del convertitore; le variazioni di temperatura sono lente: filtra con un RC banale e con frequenza di taglio di 1Hz. Se no, qualunque impulso anche brevissimo ti perturber=E0 la misura (in altri termini, migliorerai il rapporto segnale/rumore). Dal lato software, puoi anche prendere qualcosa tipo 8 misure e farne la media. Sono sicuro tuttavia che un bel filtro all'ingresso del ADC migliorer=E0 considerevolmente la situazione.

Ehm... lo sai no che la tensione di ginocchio di un diodo non =E8 molto precisa e che dipende, fra l'altro, proprio dalla temperatura? Esistono dei sistemi per generare tensioni di riferimento ben pi=F9 precise.

Ho risolto aumentando la precisione, come avevo detto nel post precedente, con un fattore di amplificazione di 5. Adesso i valori sono più precisi e non "ballano" eccessivamente. Aggiungerò comunque un filtro passa basso RC.

Si lo so, infatti lo ho eliminato ed ho messo un trimmer con un amplificatore a guadagno unitario, che servirà a tarare lo strumento. Ciao Brunix

esatto

è giusto che sia così perchè questi micro hanno i registri interni a 16 bit per cui il tipo più performante (int) è intero 16-bit se vogliamo è un po' come 8086 che ha datapath interno e bus dati di larghezza diversa

non è portabile tra pic e pc, questo no! ma tra pic e pic anche di famiglie diverse lo è

sicuro... solo che non ho presente compilatori per pic che diano la possibilità di decidere come ottimizzare... si può scegliere un livello da 0 a 9 (ccs) ma non voci singole tipo "optimize floating point with L/R shift" ad esempio in una versione vecchia del ccs con ottimizzazione maggiore di 5 veniva fuori un bug che sbagliava qualcosa quando si usava (esempio) X=3... ero riuscito a risolvere proprio portando gli int a 16 bit (e sprecando il doppio della ram)

nella situazione attuale a Brunix può anche andare bene così (/) però basta aggiungere nel main() una funzione asimmetrica in termini di tempo (esempio: rilevare la pressione di un tasto con debounce) che la luminosità dei display cambia da sola a singhiozzo personalmente gestisco sempre il multiplexing con interrupt e quindi la funzione di refresh deve essere molto veloce (>>,

Darwin ha scritto:

A questo non so risponderti, perchè non conosco il compilatore usato per i pic, comunque avendo visto altri compilatori commerciali (iar, codevision, ecc..) posso dirti che in genere forniscono delle "estensioni" che servono a legare il programmatore al prodotto.

Nel codice postato da Brunix, ad esempio, ha fatto uso di un numero indicato in base binaria.

Sebbene possa risultare molto comodo, queste comodità non sono portabili, quando dovremo cambiare processore o compilatore saremo costretti a rivedere e ridebuggare il codice.

Francesco Sacchi ha scritto:

Sono d'accordo con te quando si tratti di scrivere un programma su calcolatori da tavolo (un Macintosh o un PC). Il contesto qui =E8 diverso, anche per il fatto che il programma che puo' girare su un

16F84 non puo' essere per forza di cose molto lungo. Inoltre, sebbene esista un certo grado di compatibilit=E0 all'interno della famiglia dei PIC, la necessit=E0 di riadattare cose tipo la gestione delle porte di I/O vanificherebbe i vantaggi dell'usare un C rigorosamente standard. Non parliamo poi se si tratta di passare ad una famiglia diversa. Normalmente, in un microcontrollore il codice scritto =E8 a basso livello (accesso diretto alle porte, pi=F9 o meno semplici operazioni logiche basate sul loro stato). Dipende ovviamente dal contesto (e dal "calibro" del microcontrollore), ma qui stiamo parlando di una macchinetta piuttosto piccolina.

ice ha scritto:

Nono, erano micro a 8bit completamente simili ai PIC, con ALU interna a

8 bit, l'unica differenza è che forse avevano un po' più di ram a disposizione.

La portabilità è un concetto generale, se usi lo stesso compilatore sulla stessa famiglia di micro mi meraviglierei che non lo fosse :) Ma se cambi compilatore o microcontrollore? E' questo quello a cui mi riferivo. Scrivere codice portabile paga nel lungo periodo, quando, per esempio, tra 1 anno ti ritrovi già scritto e funzionante un driver per un sensore o un controllo pid pur lavorando con piattaforme diverse.

Purtroppo questo è un problema molto comune dei compilatori commerciali, costano molto ma presentano a volte dei grossi bug che verranno sistemati quando (e se) gli pare dalla casa produttrice, magari facendoti pagare "l'assistenza" o costringendoti per rimuovere il bug a comprare un aggiornamento...

Come tu stesso hai detto, hai risolto il problema progettando meglio il sistema (uso degli interrupt). Anche ottimizzando bene, senza interrupt, se aggiungi altre cose da fare nel loop principale la luminosità varia per forza.

Il modo corretto di visualizzare i numeri sul display sarebbe stato quello di calcolarsi i 4 dati da inviare nel programma principale e lasciare all'interrupt il solo compito di prendere e copiare in uscita i dati. In questo modo, a meno che non tu debba fare calcoli trascendentali, non c'è quasi mai bisogno di ottimizzazioni. Anche se la divisione prendesse 2000 cicli macchina, con un clock a

4MHz, essa viene eseguita in 500uS, che sarebbero trascurabili rispetto alla frequenza di refresh (qualche centinaio di Hz al massimo).

Sì è una pratica comune :)

Queste cose dovrebbero essere riconosciute dal compilatore ed ottimizzate automaticamente, questo mi fa ricordare perchè ho poca fiducia nei compilatori commerciali :)

Darwin ha scritto:

Non sono del tutto d'accordo, di lavoro scrivo prevalentemente firmware per piattaforme embedded e posso dirti che nel giro di qualche hanno ho cambiato 3-4 cpu diverse e 2-3 compilatori. Non ho mai lavorato con i PIC perchè hanno poca RAM/ROM per le mie applicazioni, ma in quest'ottica scrivere codice portabile è prioritario. E posso assicurarti che con un po' di sforzo è possibile rendere portabile anche i driver di basso livello, basta lasciarsi un layer di astrazione dall'hardware.

La fatica fatta paga, per esempio abbiamo un driver del timer unico per tutti i microcontrollori. Quando dobbiamo usare un nuovo micro/compilatore, invece di riscrivere tutto adattiamo e rendiamo più generico quello che abbiamo già. In questo modo le nuove applicazioni vengono sviluppate sempre più velocemente e con meno bug.

Addirittura, per le applicazioni più difficili da debuggare, è possibile "emularle" i driver in modo che il codice si compili su un PC. Questo dà un'enorme vantaggio, in quanto consente di provare l'applicazione quando la scheda non esiste ancora.

Se il codice non fosse stato portabile tutto questo non sarebbe stato possibile.

Join the Discussion

Have something to add? Share your thoughts — no account required.

Didn't find your answer?

Ask the community — no account required