Cos'è ElectroYou | Login Iscriviti

ElectroYou - la comunità dei professionisti del mondo elettrico

5
voti

T&GV Operating system – Seconda puntata

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 !!!

0

Commenti e note

Inserisci un commento

Inserisci un commento

Per inserire commenti è necessario iscriversi ad ElectroYou. Se sei già iscritto, effettua il login.