Consentitemi una piccola divagazione incentrata sulla mia megalomania:
se e' vero che tutto il software che andremo a descrivere serve per interfacciare l'operatore (il sottoscritto o un ospite) a tutti i dispositivi che ci sono su questa ferrovia, controllandone il funzionamento logico ed evitando che l'operatore commetta degli errori che possono danneggiare dei modelli dal costo non trascurabile, posso definirlo un “Sistema Operativo”?
Io dico di si
Indice |
Implementazioni diverse
Adesso cominciamo a vedere come si devono/possono concatenare nella corretta sequenza, tra di loro, tutte le funzioni che devono essere sviluppate su questo PIC perche' la T&GV Railroad possa espletare al meglio il proprio servizio (???).
Bene, cominciamo:
- dobbiamo gestire i due PWM, quali sono i punti salienti che li riguardano?
- frequenza dell'onda quadra
- numero di “gradini” della regolazione
- poi abbiamo il tastierino, qui i problemi quali sono ?
- “lettura” del tasto premuto
- filtraggio dei rimbalzi
- c'e' il display, ma qui le cose sono un po' piu' semplici: dobbiamo semplicemente “dirgli di mostrarci” qualcosa.
- abbiamo la matrice degli output, ma anche qui siamo messi come con il display, salvo una cosa: qualche output e' previsto sia pilotato per un certo tempo minimo, ma non tanto di più, gli altri riescono a lavorare con impulsi più brevi, anche se accettano delle mezz'ore
- abbiamo bisogno di “leggere” la posizione dei potenziometri per impostare la larghezza dell'impulso dei PWM
Se escludo l'ultimo “task”, gli altri fanno TUTTI riferimento al TEMPO, quindi mi serve un “clock” comune a tutti, un sottomultiplo temporale del tempo piu' breve di tutti, per avere tempo tra un “TICK” e l'altro, di elaborare le informazioni che devo spostare qua e la, dalla tastiera al display, ad esempio.
Bello, vero ?
Calcolo dei tempi
Io ho cominciato a ragionare dal PWM, in questo modo:
la frequenza “ideale” per i motori dei trenini e'... circa 50 Hz: una volta ( e parlo dei “miei” modelli che sono piuttosto datati ) si raddrizzava la tensione in uscita da un trasformatore a presa variabile (che costituiva il regolatore di velocita') e la si portava (attraverso un invertitore di polarita') alle rotaie (i piu' sofisticati inserivano un disgiuntore termico).
Quindi dobbiamo avere un fronte di salita ogni 20 msec. Bene, ecco il clock ! No, non e' questo.
Il fronte di salita arriva ogni 20 msec, ma QUANTO deve essere LUNGO il tempo “ON” ?
Deve essere 20 msec per andare a tutta velocita'. Bene, e se voglio rallentare?
Ecco il “busillis”: di quanto vogliamo rallentare/accelerare, o meglio: da ZERO al massimo, quanti passi vogliamo realizzare?
Ho pensato a 64 passi: mi ricordo che un trasformatore Fleischmann degli anni 60 aveva 5 (CINQUE) passi e a me bastavano; averne 64, in confronto, è come avere una regolazione continua;
in forza di ciò il tempo di clock dovrà essere di 331,2 microsecondi. (AAAARRRGHHH! Che razza di numero è ? ).
È un sessantaquattresimo del periodo completo del PWM, ma chi mi da' questo tempo esatto ?
Nessuno, dovremo accontentarci di qualcosa di simile andando a programmare il TMR0:
;------------ ; ; INIT HARDWARE DI TMR0 ; BSF BANK1 BCF OPTION_REG,T0CS ;clock dal quarzo int. BCF OPTION_REG,T0SE ;non conta, ma = 0 BCF OPTION_REG,PSA ;prescaler x TMR0 BCF OPTION_REG,PS2 ;impost. prescaler BCF OPTION_REG,PS1 ;per dividere x 4 BSF OPTION_REG,PS0 ; BCF BANK1 MOVLW TZERO_CY ; carica contatore MOVWF TMR0 ; per ciclicità BCF INTCON,T0IF ;clear ORA interrupt BSF INTCON,T0IE ; abilita interrupt TMR0
- ------------
dove
TZERO_CY EQU .49
Con queste impostazioni, quarzo a 10 Mhz, viene fuori che la frequenza dei PWM è di circa 50 Hz; misurata con il frequenzimetro, mostra di essere intorno ai 46 Hz.
Le istruzioni appena viste servono allo start del sistema, quando si accende l'ambaradan, poi, ad ogni completamento del ciclo, vedremo quando, la coppia “MOVLW”+”MOVWF” (quelle con il commento “carica contatore per ciclicità”) deve essere rieseguita, pena risultati per lo meno strani.
Riassumiamo dicendo: dovremo scrivere una sequenza di inizializzazione in seguito alla quale, ogni poco più di 331 µsec, TMR0 genera un interrupt; tra un interrupt e l'altro il PIC riesce ad eseguire più di 800 istruzioni: possiamo essere sicuri che non serviranno mai tutte-tutte.
A cosa serve una temporizzazione del genere ?
Serve a sincronizzare TUTTO:
dobbiamo, almeno allo start del sistema, inizializzare il display rispettando i SUOI tempi, e 331 x 3 fa quasi 1msec., diciamo pure che è 1 msec e, all'atto pratico, il mio display (con il “solito” 44780) funziona benissimo con questo clock-base.
Una volta generato un clock da 1 msec, posso andare a generarmi tutti quelli che mi serviranno: qualunque numero intero è multiplo di UNO ! ( su questa asserzione, qualche studente della "Normale" potrebbe costruire una tesi di laurea da 110, lode e bacio accademico ... o no ? )
Infatti mi servono 50 msec per il tastierino, poi 500 msec per il reset iniziale del display, un'altra volta mezzo secondo per azionare gli scambi, 0,1 sec (per sicurezza, anche se basterebbe meno...) per far commutare i relé, e così via.
Conversione A/D
Abbiamo, quindi, un primo interrupt ciclico, continuo, ma abbiamo la necessità di gestirne un altro, l'A/D converter: devo leggere la posizione di due potenziometri, bene, potrei lanciare il processo di conversione A/D e aspettare tenendo d'occhio con l'istruzione “BTFSC” il bit 2 di ADCON0, ma quanto tempo dovrei aspettare ? E quante altre cose potrei fare, usando quel tempo?
Allora, tenendo d'occhio la tabella seguente (Microchip DS30292C.pdf-page 117 )
REGISTERS/BITS ASSOCIATED WITH A/D Name Bit7 Bit6 Bit5 Bit4 Bit3 Bit2 Bit1 Bit 0 0Bh,8Bh, 10Bh,18Bh: INTCON GIE PEIE T0IE INTE RBIE T0IF INTF RBIF 0Ch : PIR1 PSPIF ADIF RCIF TXIF SSPIF CCP1IF TMR2IF TMR1IF 8Ch : PIE1 PSPIE ADIE RCIE TXIE SSPIE CCP1IE TMR2IE TMR1IE 1Eh : ADRESH <---------A/D Result Register High Byte----------> 9Eh : ADRESL <---------A/D Result Register Low Byte ----------> 1Fh : ADCON0 ADCS1 ADCS0 CHS2 CHS1 CHS0 GO/DONE — ADON 9Fh : ADCON1 ADFM – – – PCFG3 PCFG2 PCFG1 PCFG0 85h : TRISA — — PORTA Data Direction Register 05h : PORTA — — PORTA Data Latch when written: PORTA pins when read
Tab. “pro-A/D converter”, Nota: ho tralasciato il PORT-E perché non l'ho usato con A/D
vado a preparare la seguente inizializzazione:
; ; INIT A/D CONVERTER ; MOVLW .2 MOVWF AD_CONT ; INIT NUMERO CANALI A/D GESTITI MOVLW B'11000001' ;=ON SU "SOLO ADRESH", CANALE 0, A/D "ON" MOVWF ADCON0 BSF BANK1 ; EQUIVALE A "BANKSEL ADCON1" MOVLW B'01000101' MOVWF ADCON1 BSF PIE1,ADIE BCF BANK1 ; EQUIVALE A "BANKSEL INTCON" BSF INTCON,PEIE ; ABILITA INTRPT PERIFERICHE
in cui preparo la “periferica-A/D” a generare interrupts (quell'iniziale 2 → AD_CONT è riferito ai 2 canali che uso); come si vede, imposto=1 PIE1 e ADIE: abilitazione generale del gruppo-1 delle periferiche e dell'A/D in particolare a generare interrupts.
Questo non mi esime dal gestire il bit GO/DONE, ma mi evita di doverne controllare il reset a conversione avvenuta: si alzerà il bit ADIF e di conseguenza si genererà l'interrupt.
Ad ogni interrupt cambierà la programmazione dell'acquisizione dei valori analogici: una volta sarà un canale, la volta dopo l'altro e così sempre, alternativamente il canale 0 e il canale 1: uno per ciascun potenziometro.
Bene, con questo abbiamo esaurito la questione-interrupt.
Ogni cosa a suo tempo
Tutto qui quello che doveva essere un “Operating System” ?
Per la questione-interrupt, si, ma un sistema operativo non può far perno solo sugli interrupt, deve, soprattutto, gestire con ordine gli eventi sia che accadano esternamente e si debbano onorare in un certo ordine logico, sia debbano essere generati, sempre secondo il corretto ordine, dall'interno verso l'esterno, quindi:
Ogni 50 msec andremo a vedere cosa succede al keypad: 50 msec sono un tempo più che sufficiente a filtrare i rimbalzi dei contatti, e mi eviterò un bel po' di gruppi RC di “spianamento”.
Questi 50 msec me li fornisce la temporizzazione del TMR0, come avevamo già visto.
L'unico momento in cui “faccio il poltrone” e attendo lo scorrere del tempo, è quando aspetto i clock di temporizzazione per il display.
Questo è veramente un tempo in cui non devo fare null'altro.
I motivi:
i PWM vengono gestiti dal TMRO, nella sua stessa routine, quindi non devo occuparmene.
i potenziometri... come sopra, e alla temporizzazione si aggiunge il “loro-proprio” interrupt
se scrivo qualcosa (sul display) non posso accettare caratteri in input del keypad e
viceversa: prima leggo il keypad, poi scrivo sul display
C'è qualcosa, però che manca:
* la routine di interrupt dei PWM necessita del livello di duty-cycle da impostare
- il dato deve essere prodotto dall'A/D
Questo è un collegamento indispensabile: un dato prodotto da una routine di interrupt viene usato da un'altra routine, e cio' che le lega è solo il valore scritto in questo dato, null'altro, in quanto, temporalmente e come origine hardware, sono fratelli siamesi quanto un eschimese e un watusso, coetanei ed entrambi primogeniti nonché, purtroppo, orfani dalla nascita.
Mi trovo in un guaio simile quando devo temporizzare il display.
Bene, uso delle locazioni per dati “unidirezionali”: all'inizializzazione del sistema, chi ha bisogno di leggere quel dato scrive nella sua locazione di memoria un valore sicuramente inconsistente, da riconoscersi come “nessuno ha scritto nulla” (può essere zero o un valore negativo, ad esempio), poi, durante il normale processo, lo va continuamente a leggere: finché ci trova il valore iniziale lo ignora e non esegue alcun compito (se è una routine uscirà con un RETURN senza far null'altro), quando il valore è diverso lo preleva e lo gestisce, scrive il valore “scemo” al suo posto e conclude il lavoro. Un lavoro “opposto” deve essere fatto dalla parte di programma che “genera” il valore da gestire: se nella locazione c'è scritto il “valore scemo”, tutto ok, il valore precedentemente scritto era stato letto e usato, altrimenti: errore, allarme o quant'altro sia indispensabile fare.
Un altro sistema, simile ma più semplice e utilizzabile per temporizzarsi, è quello di inizializzare un contatore, ad esempio al valore 3.
La routine di interrupt del TMR0 controlla il valore del contatore: se è =0 lo ignora, altrimenti lo decrementa.
Il programma, dopo che lo ha inizializzato, “tiene d'occhio” quel contatore: finché resta maggiore di zero, torna a “controllarlo”, quando diventa zero vuol dire che è scaduto il tempo che doveva passare ( … spegni il fuoco prima che vada in ebollizione: la minestra è calda ! … )
Allora è la gestione degli interrupt che costituisce la vera e propria spina dorsale del SISTEMA OPERATIVO, dettando i tempi che devono essere rispettati; in “TeGV” le cose sono impostate così:
Clock principale: 0,331 msec
............primi clock derivati: 50 msec
......................................1 msec entrambi “a comando”, non continui
Credo di capire qual è la domanda:
“Se mi serve una temporizzazione di, diciamo, 30 msec, come faccio ad ottenerla ?”
Ecco la mia risposta nella routine “generica” di gestione delle temporizzazioni:
;----------------------------------------------- ; Delay = N x 0.001 seconds ; Clock da TMR0: ; Si inizializza C_1MS con il valore di T_1MS ; si controlla C_1MS: quando diventa ZERO e' passato 1 msec. ; Delay_50_ms ; ENTRY X 30 MSEC MOVLW .50 MOVWF d3 GOTO INI_WAIT ; Delay_30_ms ; ENTRY X 30 MSEC MOVLW .30 MOVWF d3 GOTO INI_WAIT ; Delay_5_ms ; ENTRY X 5 MSEC MOVLW .5 MOVWF d3 GOTO INI_WAIT ; Delay_1_ms ; ENTRY X 1 MSEC MOVLW .1 MOVWF d3 ; INI_WAIT MOVLW T_1MS MOVWF C_1MS ; T_1_WAIT CLRW IORWF C_1MS,W ;SCADUTO TIMER BTFSS ZERO ; 1 MSEC ? GOTO T_1_WAIT ; NON ANCORA DECFSZ d3,F ; SCADUTO TIMER SPEC. ? GOTO INI_WAIT ; NON ANCORA RETURN ; ;-----------------------------------------------
Ad ogni interrupt, se si tratta del TMR0, ecco cosa succede:
; CLRW IORWF C50_MS,W ; CONTATORE 50 MSEC BTFSC ZERO ; ATTIVO ? GOTO NO_T1 ; NO DECFSZ C50_MS,F ; SI, DECREMENTA GOTO NO_T1 ; NON SCADUTO MOVLW T1_C0 ; SCADUTO MOVWF C50_MS ; REINIT CALL SCANKBD ; KEYPAD CLRW ; TEMPO AZIONAMENTO IORWF CONTAZ,W ; MAGNETI BTFSS ZERO ; "ON" ? DECF CONTAZ,F ; SI, DECREMENTA NO_T1 CLRW ;CONTATORE 1 MSEC IORWF C_1MS,W ; X DISPLAY ATTIVO ? BTFSS ZERO ; "ON" ? DECF C_1MS,F ; SI, DECREMENTA ;
Si possono vedere anche un paio di cose “speciali”, sempre gestite dal TMR0: il filtraggio dei rimbalzi per la tastiera e una temporizzazione “dedicata” all'azionamento dei dispositivi bistabili (il contatore “C50_MS” abbinato a “SCANKBD” e il contatore “CONTAZ” che sembra messo lì non si sa per cosa (temporizza l'azionamento dei bistabili); nella parte “in fondo” c'è la gestione del temporizzatore da 1 msec.
Questa routine "multi-entry" non è costruita con lo scopo "fire & forget", cioé "lancia e dimentica, allo scadere scatta qualcosa", ma con lo scopo di attendere PROPRIO il tempo richiesto senza far ALTRO ma consentire agli interrupt, e quindi all'A/D e ai PWM, di continuare a fare il proprio lavoro.
Così ho risolto il problema dei tempi.
Se tu dai una cosa a me...
Come ho realizzato la generazione dei segnali PWM?
Con tre contatori:
un contatore comune che ad ogni inizio-ciclo vale 64 e altri due che, sempre ad ogni inizio-ciclo, possono valere da 0 a 63.
Da dove prendo questi numeri ?
Il primo lo avevamo detto: sono i passi in cui posso dividere il duty-cycle e l'ho deciso prima di cominciare a farmi venire i mal di testa per tutto il resto.
Gli altri due me li prendo dal convertitore A/D che legge i due potenziometri.
Si, ok, ma l' A/D genera un numero a 10 bit, come Microchip dice...
E' vero, ma: avevamo detto che 64 “gradini” di regolazione sono sufficienti e poi: ho impostato l' A/D dell' 877 a darmi gli otto bit più significativi su ADRESH e gli ultimi due su ADRESL; questi ultimi li ignoro e poi il contenuto di ADRESH lo divido per quattro (shift di due bit a destra con perdita del loro contenuto) così sono anche stra-sicuro di non avere “indecisioni”, o “vibrazioni del valore acquisito”; in pratica, se elimino i bit meno significativi, le “indecisioni” dei potenziometri sono del tutto tagliate fuori.
Ok, adesso che ho il valore “pre-elaborato”, lo vado a scrivere nella loazione READ_VAL_1 o READ_VAL_2, a seconda se ho appena letto il potenziometro “1” o il “2”.
Nella routine di interrupt del TMR0, nella parte dedicata alla gestione dei PWM e al pilotaggio dei relativi circuiti esterni, avverrà la lettura di tali valori e la trascrizione nei due “contatori-a-scadenza” di cui, prima, mancava la descrizione e che vanno a completare il terzetto citato all'inizio; non sto a riportare qui la relativa parte di listato perché, necessariamente, comprende anche la logica di sincronizzazione “anti-corto-circuito” necessaria per far transitare un treno dalla parte alimentata dal PWM-1 a quella controllata dal PWM-2, il transito su uno dei due lati del “triangolo” sotto la montagna: durante questa fase “critica” il PWM-2, quello che alimenta il tratto sul ponte, quello in salita e la stazione di Green Valley viene “ignorato” e i due stadi di potenza vengono pilotati “all'unisono” dal PWM-1. Quando viene tolta alimentazione alla tratta di passaggio, i due PWM tornano indipendenti.
Nella stessa parte di routine vengono anche letti gli invertitori di polarità per realizzare, oltre all'inversione di marcia, anche la funzione “arresto di emergenza”; se ho commesso qualche errore di instradamento o chissà cos'altro e mi accorgo che il treno rischia di fare o subire danni, basta che inverta la marcia “al volo”: la logica del software non inverte la polarità lasciando invariato il duty-cycle, ma ignora la lettura del potenziometro e tiene importato forzatamente ZERO sul contatore fino a che non legge ZERO anche sul valore proveniente dal potenziometro.
Stessa cosa all'avviamento dell'intero sistema: se i valori letti sono maggiori di zero si impongono = 0 finché non viene riportata a zero la manopola, poi si riparte.
Questo impedisce avviamenti indesiderati e inopportuni.
Per realizzare questa funzione, vado a controllare lo stato del commutatore in questo modo:
E' su “destra”?
….si, anche su “sinistra”?
…...........Si, errore, blocca tutto, il commutatore è rotto in corto circuito
….....si, è su “destra” e basta, vai pure avanti
….non è su “destra”, è su “sinistra” ?
…...........Si, ok, vai pure avanti
…...........No, né “destra”, né “sinistra”, ferma tutto, c'è “inversione-al-volo”
Ci sarebbe un problema: quanto impiego a spostare un commutatore da destra a sinistra ?
Ovvero: con la risoluzione di quei 331,2 microsecondi tra un interrupt TMR0 e l'altro riesco a individuare quella “posizione al centro” in cui non ho né “destra” né “sinistra” ?
Innanzitutto ho impiegato dei commutatori a zero centrale (una possibilità in più per l'operatore) e poi ho fatto parecchie prove di velocità: non sono MAI riuscito a “fregarlo”, mi ha sempre fermato il motore ad ogni inversione, per quanto veloce fossi stato a spostare la levetta. O sono lento io o è sufficientemente veloce lui.
Per ora direi che basta.
Per le prossime volte rimangono ancora da vedere la gestione del keypad a 16 tasti e il display, ma credo che, con tutto quello che “gira” in rete già bell'e pronto, se dovessi farvi perdere più di cinque minuti potrei passare per uno che vuole brevettare l'acqua tiepida, quindi vi dico che, a parte la temporizzazione già citata, la tastierina ve la potete cercare da soli dove meglio credete. Per il display è la stessa cosa: per fortuna, ciò che è stato scritto … è stato scritto in rete, altrimenti sai quanti alberi da tagliare ?
La prossima volta, invece, vorrei parlarvi della logica del triangolo; se andate a riprendere un attimo la seconda figura dell'articolo precedente, guardate un attimo il triangolo di rotaie che si vede al centro e provate a pensare a questi casi:
un treno passa sullo scambio 3, gira a sinistra per passare sullo scambio 4: la tratta 3 deve essere alimentata, ma come fa il software a stabilire le condizioni per consentirlo o meno ?
un treno passa sullo scambio 3, gira a destra per passare sullo scambio 2: la tratta 2 deve essere alimentata, ma …
un treno proviene da sinistra, sullo scambio 4, deve attraversare la tratta 3 per superare lo scambio 3; la tratta 3 deve essere alimentata, ma...
un treno proviene da destra, sullo scambio 2, deve attraversare la tratta 2 per superare lo scambio 3; la tratta 2 deve essere alimentata, ma...
Vi anticipo solo che c'è di mezzo la “lettura” dei potenziometri (vista...), la “lettura” degli invertitori di polarità (vista...), la “conoscenza” della posizione degli scambi da 1 a 5 della figura vista e , come ciliegina sulla torta, l'uso di “tabelle decisionali”.
Per intanto: qualcuno vuole divertirsi a soffrire tentando di leggere il listato del programma completo ?
Sono nuovo di questo ambiente di discussione e al momento non saprei come fare a rendere disponibile in download il file (estensione .asm ma, ovviamente, TESTO SECCO, come fosse .txt) quindi basta che qualcuno mi spieghi come si fa o che mi faccia avere il suo indirizzo e-mail: sarà mia premura spedirglielo (.zip o no) dietro compenso: la promessa di un caffè SE e QUANDO potremo incontrarci... e mi attendo dei commenti, anche negativi: DISCUTIAMONE !!!
Alla prossima !!!