Ripetizioni Materie Scientifiche

Documenti analoghi
PIC Set istruzioni. Sintassi Descrizione Microchip Operazione equivalente Effetto su STATUS

Lezione 1 Caratteristiche principali del PIC16C84 L'hardware

DEC PDP8, III Generazione, '65-'75

Architettura hardware

ESERCIZI SUI SISTEMI DI NUMERAZIONE

Gestione Degli INTERRUPT

Fondamenti di Informatica - 1. Prof. B.Buttarazzi A.A. 2011/2012

Pilotare un motore passo-passo, in questo caso il modello della Sanyo le cui caratteristiche principali sono quelle di figura1.

Il processore. Istituzionii di Informatica -- Rossano Gaeta

Componenti e connessioni. Capitolo 3

ELABORAZIONE DELLE INFORMAZIONI (ALGORITMI E LINGUAGGI DI PROGRAMMAZIONE)

Esercizi di verifica del debito formativo:

Le etichette nei programmi. Istruzioni di branch: beq. Istruzioni di branch: bne. Istruzioni di jump: j

Codifica binaria. Rappresentazioni medianti basi diverse

PROCESSOR 16F84A. ;configurazione FUSES: oscillatore XT, WDT disabilitato PWRT abilitato, CP disabilitato config 0x3FF1

Componenti principali. Programma cablato. Architettura di Von Neumann. Programma cablato. Cos e un programma? Componenti e connessioni

Applicazioni dei microcontrollori PIC

LA CODIFICA DELL INFORMAZIONE

Architettura dei computer

Corso di Informatica Generale (C. L. Economia e Commercio) Ing. Valerio Lacagnina Rappresentazione dei numeri relativi

Lanciare MPLAB IDE (Start->Programmi->Mirochip->MPLAB IDE v7.22->mplab IDE):

Componenti principali

Architettura di un calcolatore e linguaggio macchina. Primo modulo Tecniche della programmazione

senza stato una ed una sola

Rappresentazione dell informazione

Il Processore: l unità di controllo

La codifica. dell informazione

Istruzioni macchina. Dove sono gli operandi? Ciclo della CPU. Elementi di un istruzione macchina. Rappresentazione delle istruzioni

Unità aritmetica e logica

MICROCONTROLLORE PIC16F84A

Dall algoritmo al programma

Analogico vs. Digitale. LEZIONE II La codifica binaria. Analogico vs digitale. Analogico. Digitale

Conversione di base. Conversione decimale binario. Si calcolano i resti delle divisioni per due

Lezione 15. L elaboratore Elettronico

Foglio Elettronico Lezione 1

La codifica digitale

1-Rappresentazione dell informazione

Componenti di un processore

Nicola Amoroso. Corso introduttivo sui microcontrollori A. S La programmazione dei PIC.

Lezione 2. Figura 1. Schema del circuito necessario per le prove

Il Processore. Informatica di Base -- R.Gaeta 27

Programma del corso. Elementi di Programmazione. Introduzione agli algoritmi. Rappresentazione delle Informazioni. Architettura del calcolatore

Arithmetic and Logic Unit e moltiplicatore

PUNTATORE LASER AUTOMATICO CON COMANDO VIA ETHERNET

LEZIONE DI MATEMATICA SISTEMI DI NUMERAZIONE. (Prof. Daniele Baldissin)

Il set istruzioni di MIPS Modalità di indirizzamento. Proff. A. Borghese, F. Pedersini

Lezione2: Circuiti Logici

Il linguaggio assembly

Lezione 15 Il Set di Istruzioni (1)

Lezione 4. Figura 1. Schema di una tastiera a matrice di 4x4 tasti

Sistemi di Elaborazione delle Informazioni

FUNZIONI BOOLEANE. Vero Falso

Rappresentazione degli algoritmi

Microcontrollori. L ultima parte del corso prevede un approfondimento sui microprocessori, in particolare sul PIC 16F876.

Aritmetica dei Calcolatori Elettronici

Corso di Architettura (Prof. Scarano) 09/04/2002

I.4 Rappresentazione dell informazione

Problema: dati i voti di tutti gli studenti di una classe determinare il voto medio della classe.

Sistemi di numerazione

Architettura hardware

Rappresentazione dei Dati

Strutture di Controllo

Introduzione alla programmazione

Sistemi Web per il turismo - lezione 3 -

STRUTTURA E LOGICA DI FUNZIONAMENTO DEL COMPUTER

Cap. 2 - Rappresentazione in base 2 dei numeri interi

Cos è un algoritmo. Si dice algoritmo la descrizione di un metodo di soluzione di un problema che sia

La memoria principale

Lezione 6 Introduzione al C++ Mauro Piccolo

La "macchina" da calcolo

Un altro tipo di indirizzamento. L insieme delle istruzioni (3) Istruz. di somma e scelta con operando (2) Istruzioni di somma e scelta con operando

Informatica ALGORITMI E LINGUAGGI DI PROGRAMMAZIONE. Francesco Tura. F. Tura

Capitolo 5 Struttura di base del processore

Lezione 8. Figura 1. Configurazione del registro INTCON

La codifica dei numeri

Lezioni di Informarica. Prof. Giovanni Occhipinti

Il linguaggio macchina

Struttura hw del computer

I sistemi di numerazione e la numerazione binaria

Programmazione strutturata

Istruzioni, algoritmi, linguaggi

Aritmetica dei Calcolatori

Somma di numeri binari

Z80 uc esecuzione di programmi

Appunti di informatica. Lezione 3 anno accademico Mario Verdicchio

1.2f: Operazioni Binarie

Linguaggi di Programmazione

Introduzione al C++ (continua)

ARCHITETTURA DI UN SISTEMA DI ELABORAZIONE

La rappresentazione delle informazioni in un computer. La numerazione binaria

ALGORITMI: PROPRIETÀ FONDAMENTALI

Cosa è? Come lo si usa? Come iniziare? Author: Ing. Sebastiano Giannitto (ITIS M.BARTOLO PACHINO)

Sistemi di Elaborazione delle Informazioni

I.4 Rappresentazione dell informazione - Numeri con segno

Struttura di programmi MAL Elementi lessicali

modificato da andynaz Cambiamenti di base Tecniche Informatiche di Base

Richiami sull architettura del processore MIPS a 32 bit

PROGRAMMAZIONE ASSEMBLER

Università degli Studi di Cassino

ISA Input / Output (I/O) Data register Controller

Transcript:

INTRODUZIONE Questo testo vuole fornire alcune nozioni base riguardo alla programmazione in linguaggio macchina dei PIC, i piccoli ed economici microcontrollori prodotti dalla Microchip. Intanto va fatta subito una distinzione tra microprocessore e microcontrollore. Il primo è un componente universale che ha bisogno di numerosi integrati esterni aggiuntivi per poter funzionare (memoria, oscillatore di clock, periferiche di ingresso/uscita ecc) e può diventare l'elemento di controllo di un computer molto sofisticato. Un microcontrollore invece racchiude tutti questi elementi all'interno di un unico piccolo contenitore, e ha bisogno di pochissimi (o nessuno) componenti esterni per funzionare. La memoria per il programma, quella per i dati di lavoro (RAM), l'oscillatore, il circuito di reset e le diverse periferiche, sono tutte racchiuse in un unico chip. Le sue capacità di calcolo però sono estremamente ridotte, la memoria RAM per esempio è formata da poche centinaia (se non solo decine) di celle, e solitamente non è espandibile in alcun modo. I microprocessori possono essere usati per effettuare elaborazioni molto complesse su grandi quantità di dati, i microcontrollori sono invece adatti per compiti di controllo hardware a basso livello, che non richiedono grandi quantità di memoria, ma prezzo, consumo e dimensioni ridotti, uniti ad una discreta velocità di esecuzione e ad una nutrita serie di periferiche già pronte come timers, convertitori analogico/digitali (ADC), generatori PWM, porte seriali ecc. Tipiche applicazioni di un microcontroller possono essere automatismi, antifurti, strumenti di misura, regolazione luminosità, caricabatterie, trasmettitori/ricevitori codificati ecc. Da qui in avanti si useranno indifferentemente i termini micro, microprocessore, microcontrollore e microcontroller per indicare la stessa cosa, in particolare con microprocessore si intenderà il piccolo microprocessore contenuto all'interno di ogni microcontroller. Cos'è il linguaggio macchina? Ogni microprocessore è progettato e costruito per eseguire determinate operazioni in presenza di determinate sequenze binarie che vengono lette una dopo l'altra da un'apposita area di memoria, detta memoria programma. Queste sequenze binarie sono le istruzioni in linguaggio macchina (L.M.), l'unico direttemente comprensibile dai circuiti del micro. Una tipica istruzione in linguaggio macchina è la sequenza: 00000100000011 Per rendere più facile la vita al programmatore, ad ogni istruzione L.M. è stato dato un nome simbolico detto codice mnemonico. L'insieme dei codici mnemonici prende il nome di linguaggio assembly. Il programma in assembly si scrive con un qualsiasi editor di testo (come il notepad di Windows). Tramite il un programma assemblatore (assembler) si convertono poi le istruzioni assembly in codice macchina direttamente eseguibile. Per ogni istruzione assembly esiste naturalmente una e una sola sequenza binaria in codice macchina, per questo motivo i termini linguaggio macchina e assembly indicano spesso la stessa cosa (a dire il vero è diventato anche di uso comune usare la parola assembler per riferirsi all'assembly e non all'assemblatore, per cui è normale sentir parlare di linguaggio assembler). Perché studiare il linguaggio macchina? I motivi validi sono più di uno. In generale un programma scritto in L.M. è molto compatto, usa poca memoria, è veloce nell'esecuzione, può accedere completamente alle risorse fornite dal micro e ottenere il controllo assoluto delle sue temporizzazioni. Lavora in stretto contatto con l'hardware... una volta si diceva programmare sul nudo metallo, e perciò si comprende in qualche misura anche il suo funzionamento sottostante. Si comprende inoltre quello che c'è sotto ai linguaggi ad alto livello e il modo in cui essi realizzano le loro funzioni complesse partendo dalle istruzioni elementari dell'assembly sottostante. https://twitter.com/profbianco Pag. 1

Naturalmente l'assembly ha anche molti svantaggi, che sono di solito quelli che spingono ad utilizzare linguaggi di livello più alto come il C o il BASIC: i programmi sono più difficili da scrivere, interpretare e correggere, ci si impiega molto più tempo per scriverli, richiedono molte istruzioni anche per effettuare operazioni semplici, ed è molto difficoltoso effettuare calcoli. Ogni micro dispone di un set ben definito di istruzioni elementari, che sono naturalmente dedicate a quel singolo micro, pertanto un programma assembly per Z80 non è compatibile con un programma assembly per PIC o per 8088. In queste pagine si parla dell'assembly dei microcontrollori PIC (famiglie 12F e 16F). Essendo l'assembly strettamente legato all'hardware, le sue istruzioni risentono dei limiti (o delle potenzialità) offerte dall'hardware stesso. Una minima conoscenza dell'hardware è quindi indispensabile. I PIC di cui si parla qui sono micro con architettura a 8 bit, questo significa che possono lavorare direttamente solo con numeri rappresentabili in 8 bit (valori compresi tra 0 e 255). Hanno una memoria per le istruzioni del programma separata dalla memoria dati (RAM). La prima è di tipo flash, riprogrammabile elettricamente almeno 1000 volte con un apposito programmatore. La RAM invece contiene tutte le informazioni di lavoro necessarie durante l'esecuzione del programma, ma a differenza della prima si cancella ogni volta che viene tolta l'alimentazione. In questi micro ogni cella (o locazione) della RAM può essere pensata (ed effettivamente usata) come un registro a 8 bit in cui salvare o da cui leggere i nostri dati. Le locazioni di memoria, sia programma che dati, hanno un indirizzo crescente che parte da 0. In particolare le prime locazioni della RAM prendono il nome di SFR (special function registers), e servono per controllare il funzionamento dell'hardware. In quest'area troviamo per esempio i registri che permettono l'accesso ai piedini (pin) del microcontrollore, che consentono cioè al programma di generare una tensione (livello logico) verso l'esterno per comandare ad esempio dei diodi LED, display, relè, transitor ecc..., oppure di leggerla, per esempio per determinare la posizione di un interruttore o di un un pulsante. Attraverso i registri della sezione SFR si possono anche attivare e usare le diverse periferiche interne, come i timers, il convertitore analogico/digitale (ADC), la memoria EEPROM ecc... Le aree di memoria su cui si può agire da programma sono i registri della memoria dati e il registro accumulatore W, che non fa parte dell'area dati ma è un ulteriore registro hardware specializzato, usato nelle operazioni aritmetico logiche. La RAM è inoltre suddivisa in due o più banchi, un pò come se vi fossero più RAM attivabili una sola alla volta, questa selezione si ottiene impostando alcuni bit specifici (nel registro STATUS). Durante l'esecuzione di un programma è importante sapere sempre quale banco si sta utilizzando. Il registro STATUS è un registro molto importante dei PIC, perché permette di selezionare i banchi RAM e contiene anche i flags, particolari bit di cui parleremo più avanti indispensabili per far funzionare programmi complessi. A differenza di altri micro, i PIC sono microcontrollori di tipo RISC, dispongono cioè di un set ridotto di solamente 35 istruzioni elementari eseguite molto velocemente. Ogni istruzione occupa una sola locazione della memoria programma, e quasi tutte vengono eseguite in 4 cicli di clock. Se un PIC viene cloccato a 4Mhz è in grado di eseguire 1milione di istruzioni al secondo (1 mips) e ogni istruzione dura 1µS (1 microsecondo). Le istruzioni di branch (salto, ramificazione) possono richiedere 8 cicli di clock anzichè 4. Nella terminologia Microchip un gruppo di 4 cicli di clock è detto "ciclo macchina", per cui le istruzioni vengono eseguite in uno o due cicli macchina. Ci sono molti modelli di pic all'interno di una famiglia (architettura), ciascuno con le proprie peculiarità, qualcuno ha più memoria programma, qualche altro ha più periferiche, qualcuno ha https://twitter.com/profbianco Pag. 2

dimensioni ridotte (solo 8 pin), altri invece mettono a disposizione più di 30 pin di ingresso/uscita (I/O). Le istruzioni però sono le stesse e funzionano nello stesso modo. Per gli esempi che seguiranno verrà usato soprattutto il PIC16F628, che è molto diffuso ed economico, non ha bisogno di nessun componente esterno per funzionare (a parte un'alimentazione stabilizzata a 5V) e mette a disposizione fino a 15 pin di I/O singolarmente configurabili come ingressi o come uscite, e uno ulteriore utilizzabile solo come ingresso. Lo scopo di queste pagine non è quello quello di descrivere nel dettaglio l'architettura interna di uno o più PIC, o delle periferiche in esso contenute, per queste cose si rimanda sicuramente ai relativi datasheets reperibili sul sito della casa costruttice www.microchip.com. Qui verranno date le spiegazioni strettamente indispensabili per la comprensione degli esempi pratici. Si da per scontato inoltre che si abbia già qualche conoscenza di elettronica, e che si abbia a disposizione un programmatore e i programmi necessari per l'assemblaggio (conversione in codice eseguibile) del programma e per la programmazione del chip. IL LINGUAGGIO MACCHINA Istruzioni, programmi, dati Il linguaggio macchina è quanto di più vicino ci sia all'hardware. Il comportamento di ogni istruzione elementare è realizzato in modo fisico dall'insieme dei circuiti logici del micro, ma si può anche dire che l'insieme delle funzioni logiche realizzabili dal circuito determina quelle che sono le istruzioni elementari utilizzabili. Dal punto di vista fisico ogni istruzione è una sequenza binaria di livelli elettrici in grado di attivare in modi differenti i circuiti interni. Le istruzioni assembler sono solo una forma mnemonica comoda per descrivere le sequenze binarie che danno luogo alle funzioni logiche che vogliamo far eseguire al micro, tra tutte (e solo) quelle che è fisicamente in grado di eseguire. Da questo punto di vista si può dire che l'assembly è in rapporto 1:1 con il linguaggio macchina e con le funzionalità dell'hardware. Un programma non è altro che un'insieme di istruzioni, che vengono eseguite una dopo l'altra producendo il risultato voluto, sia esso un gioco o un processo di controllo industriale. Ma cosa possono fare le istruzioni dei PIC e in generale di ogni altro microprocessore? Fondamentalmente solo poche cose: Assegnare un valore ad un registro Spostare un valore tra registri Effettuare somme e sottrazioni, incrementi e decrementi su un registro Effettuare operazioni logiche AND OR XOR NOT su un registro Effettuare scambi e rotazioni dei bit di un registro Settare o resettare singoli bit di un registro Effettuare dei salti condizionati da un punto all'altro del programma Come si vede la maggior parte dell'attività riguarda la manipolazione elementare dei valori o dei singoli bit dei registri. Come possono delle sequenze di queste poche cose costituire la base per il funzionamento di un robot o di uno strumento di misura? E'più facile vederlo in pratica che spiegarlo... ma si può già intuire che per compiere operazioni di una certa complessità sono richiesti moltissimi di questi "passi elementari". https://twitter.com/profbianco Pag. 3

Si è detto che i registri dei PIC possono contenere valori a 8 bit. Di fatto il considerarli valori (nel senso di numeri) è una nostra convenzione. Dal punto di vista fisico il contenuto dei registri non sono altro che sequenze binarie di bit. Questi bit possono rappresentare qualsiasi cosa o qualsisi tipo di informazione (un numero, un carattere, una parte di un numero più grande ecc...) ed è il programmatore che decide questo al momento in cui scrive il programma. Le istruzioni sono le operazioni elementari che può usare per manipolare i bit delle sue informazioni in modo da arrivare al risultato voluto. In particolare però le istruzioni aritmetiche considerano effettivamente i registri come byte (8 bit) che contengono un valore numerico da 0 a 255 codificato in forma binaria. La figura seguente mostra le tre rappresentazioni numeriche comunemente usate in campo elettronico e "microinformatico". Qui a fianco si vede la codifica del valore 174 nel nostro consueto sistema decimale posizionale che usiamo abitualmente. La cifra di destra è la meno significativa (leggera) ed è chiamata unità, mentre quella di sinistra è la più significativa (pesante) ed è chiamata migliaia. Il valore è dato dalla somma delle diverse cifre moltiplicate per il loro peso, quindi abbiamo una volta 100, 7 volte 10 e 4 volte 1, per un totale di 174. Il peso delle cifre è dato dalle potenze di 10, e quindi il sistema si dice decimale, o in base 10. In binario è la stessa cosa, solo che invece di avere potenze di 10 (1, 10, 100, 1000 ecc) abbiamo potenze di 2 (1,2,4,8 ecc) e le cifre invece di poter assumere valori da 0 a 9 possono essere solo 0 e 1 (da qui il nome bit: binary digit). La cifra meno significativa è chiamata D0 o bit 0 (LSB) e ha peso 1, quella più significativa è chiamata D7 o bit 7 (MSB) ed ha peso 128. Si può calcolare che se tutti i bit fossero a 1 la somma dei loro pesi darebbe esattamente 255, cioè il massimo valore codificabile con 8 bit. https://twitter.com/profbianco Pag. 4

La rappresentazione esadecimale è il terzo caso, ed è molto usata perché una cifra (digit) esadecimale rappresenta esattamente 4 bit binari (un nibble). Con due cifre esa da 00 a FF si rappresenta l'intero range di valori codificabili con 8 bit (1 byte). Anche per l'esadecimale abbiamo la cifra meno significativa a destra e quella più significativa a sinistra. Il peso delle diverse cifre questa volta è dato dalle potenze di 16 (sistema in base 16). le cifre possono assumere tutti i valori compresi tra 0 e 15, e i valori tra 10 e 15 vengono indicati con le lettere dalla A alla F. L'esadecimale è molto usato per rappresentare in modo compatto i valori binari contenuti in memoria e il valore degli indirizzi di memoria, e anche perché permette di passare rapidamente alla notazione binaria. Nell'esempio della figura infatti le due cifre A ed E corrispondono alle due sequenze binarie del 10 e del 14, cioè ai due nibbles 1010 e 1110. La cifra nella posizione delle sedicine rappresenta valori 16 volte più grandi di quelli della cifra delle unità, pertanto 10*16 + 14 = 174. In ogni caso va ricordato che tutte queste rappresentazioni sono solo convenzioni create per facilitarci la lettura dei valori e la scrittura del programma, i circuiti del micro a livello fisico lavorano solo e sempre con livelli binari, cioè assenza o presenza di tensione. CARICAMENTO E SPOSTAMENTO DATI MOVLW n W = n MOVWF reg (reg) = W MOVF reg,d Z d = (reg) SWAPF reg,d d = swap nibbles (reg) CLRF reg Z=1 (reg) = 0 CLRW Z=1 W = 0 Si è visto dal prospetto sui tipi di istruzioni che la manipolazione dei byte (o dei singoli bit) è l'attività principale svolta dai circuiti del micro. La prima importante categoria di istruzioni è composta perciò da quelle che permettono di assegnare valori ben precisi ai registri, e di spostare questi valori da un registro all'altro. Nella tabella qui sopra sono riportate tutte le istruzioni di assegnazione e spostamento. La prima assegna semplicemente il valore n al registro accumulatore W, per esempio l'istruzione: MOVLW 174 fa assumere all'accumulatore W il valore 174 (binario 10101110). Siccome l'assemblatore accetta anche numeri scritti direttamente in binario o in esadecimale è possibile scrivere anche: MOVLW 10101110B MOVLW 0xAE MOVLW 0AEh La seconda istruzione della tabella trasferisce il contenuto dell'accumulatore in una locazione di memoria RAM di indirizzo reg. Il PIC 16F628 nel banco RAM 0 dispone di un'area dati liberamente usabile per memorizzare i propri valori, quest'area parte dall'indirizzo 32 (20h). Quindi se supponiamo che sia attivo il banco 0 e vogliamo trasferire il contenuto dell'accumulatore nella cella (registro) 32 dobbiamo scrivere: https://twitter.com/profbianco Pag. 5

MOVWF 32 Sarebbe molto scomodo però doversi ricordare a memoria gli indirizzi di tutte le celle che ci interessa usare, per facilitare il compito l'assemblatore accetta la definizione di un nome simbolico per i valori e gli indirizzi usati nel programma tramite la "direttiva di compilazione" EQU: PIPPO EQU 32 MOVWF PIPPO In questo modo è possibile assegnare un nome comodo da ricordare ad ogni cella. Va ricordato che ogni PIC ha un'area RAM usbile dal programmatore, però l'indirizzo a cui inizia varia da un modello all'altro, questo è uno dei motivi principali per cui NON è possibile far eseguire ad un PIC un programma scritto per un altro tipo di PIC senza nessuna modifica. Come si può vedere non esiste alcun modo per assegnare in un colpo solo un valore ad un registro, ma occorre sempre passare per l'accumulatore usando quindi due istruzioni. Esperimento: Visualizzare in forma binaria attraverso dei diodi LED il valore dell'accumulatore. Il PIC 16F628 dispone di 16 pin usabili come ingressi/uscite, questi sono raggruppati in due "porte" da 8 bit chiamate PORTA e PORTB mappate nell'area registri SFR, questo significa che è possibile leggere o scrivere su di esse leggendo o scrivendo un valore al loro indirizzo. I pin corrispondenti ai vari bit sono chiamati RA0..RA7 e RB0..RB7. all'accensione tutti i pin sono configurati come ingressi, perciò le prime istruzioni del programma dovranno configurare come uscite i pin che ci interessano. In questo caso renderemo un'uscita l'intera PORTB (scrivendo 0 nel registro di controllo TRISB). A fianco è rappresentato il collegamento pratico di 8 diodi LED alla porta B del 16F628. Tutto il necessario è una sorgente di alimentazione stabilizzata a 5V (ma può andare bene anche una comune pila piatta da 4,5V in quanto il PIC può funzionare da 3 a 5,5V). https://twitter.com/profbianco Pag. 6

Le resistenze limitano la corrente circolante nei diodi a una decina di ma ciascuno, e sono delle comuni 330 ohm 1/4W. Sotto c'è il programma completo con evidenziate le istruzioni viste finora. Il risultato è che i bit caricati nell'accumulatore vengono trasferiti pari pari sui pin RB0..RB7 (bit 0 su RB0 ecc) sotto forma di livelli di tensione, 0V per i bit a zero e +5V per quelli a 1. Risultano quindi accesi i LED corrispondenti ai bit a 1. PROCESSOR 16F628 RADIX DEC INCLUDE "P16F628.INC" CONFIG 11110100010000B ORG 0 BSF STATUS,RP0 ;Attiva banco 1 CLRF TRISB ;Rende PORTB un'uscita BCF STATUS,RP0 ;Ritorna al banco 0 MOVLW 10101110B ;Carica 174 nell'accumulatore MOVWF PORTB ;Mandalo sui pin di uscita SLEEP ;Stop programma END La prima parte del programma è detta header (testata), e contiene informazioni specifiche per l'assemblatore. Il programma vero e proprio è composto solo dalle 6 righe centrali racchiuse tra la testata e l'end finale. Nella testata si indica all'assemblatore il tipo di micro usato, la base di default in cui vanno considerati scritti i numeri, si include un file di definizioni EQU che permette di assegnare automaticamente un nome a tutti i registri di uso comune (come per esempio PORTB, TRISB e STATUS), si definisce la configurazione hardware per il funzionamento del micro (in questo caso per esempio si predispone il funzionamento con clock interno a 4MHz, 16 pin di I/O e WDT disattivato). Org 0 indica l'indirizzo di partenza a cui andranno caricate le istruzioni nella memoria programma (il micro all'accensione inizia ad eseguire le istruzioni partendo dall'indirizzo 0), e l' END finale indica all'assemblatore la fine del testo. Se il programma funziona, appena si fornisce alimentazione i LED devono accendersi coerentemente al valore binario impostato in W, rammentando che la cifra meno significativa (LSB) si trova sul pin RB0, mentre quella più significativa (MSB) su RB7. MOVF reg,d Z d = (reg) La successiva istruzione della tabella permette invece di spostare il contenuto di un registro nell'accumulatore, o... in se stesso! Questa cosa apparentemente strana deriva da una caratteristica di molte istruzioni, che prevedono di specificare la destinazione del risultato (indicato genericamente con d). Il valore d può essere 0 o 1, nel primo caso il risultato viene messo nell'accumulatore W, nel secondo viene messo nel registro stesso chiamato in causa dall'operazione. Per non confondersi nell'indicare 0 o 1 come destinazione, anche questi valori hanno una EQU che ne definisce il nome simbolico W o F. Pertanto è possibile scrivere due forme di questa istruzione: MOVF PIPPO,W ;Carica in W il valore del registro PIPPO MOVF PIPPO,F ;Carica nel registro PIPPO il suo stesso valore https://twitter.com/profbianco Pag. 7

La seconda forma è del tutto inutile? In realtà no, perché questo tipo di spostamento dati coinvolge il flag Z (flag di zero) come indicato nella colonna centrale. Il flag Z è un bit contenuto nel registro STATUS che viene settato (posto a 1) quando il risultato dell'operazione vale 0. Questo spostamento di un registro in se stesso è quindi un modo rapido per capire se il registro contiene il valore 0 senza dover effettuare sottrazioni o altre operazioni. Anche in questo caso si vede che non è possibile spostare direttamente un registro in un altro, ma bisogna sempre passare attraverso l'accumulatore usando due istruzioni. SWAPF reg,d d = swap nibbles (reg) Un gruppo di 4 bit si chiama nibble. L'istruzione SWAPF scambia tra di loro i 4 bit meno significativi di un registro con quelli più significativi (nella rappresentazione esadecimale questo equivale a scambiare tra loro le due cifre esa che rappresentano i due nibbles). Anche in questo caso il risultato può essere rimesso nel registro di partenza oppure in W a seconda del valore che si da al parametro d. Questa operazione non altera i flags e può essere perciò usata vantaggiosamente per leggere (e salvare) il valore del registro STATUS, senza alterarlo come avverrebbe invece con una MOVF. Questo salvataggio è necessario per esempio quando si lavora con gli interrupt. Le seguenti tre istruzioni caricano 147 in PIPPO (una generica cella RAM liberamente utilizzabile a cui è stato dato un nome con una EQU, per esempio la cella 32) e ne effettuano uno swap (scambio) dei nibbles: MOVLW 147 ;Carica 147 nell'accumulatore (W=10101110) MOVWF PIPPO ;Lo mette nel registro PIPPO (PIPPO=10101110) SWAPF PIPPO,F ;Swappa i nibbles di PIPPO (PIPPO=11101010) Esperimento: Per sperimentare quanto detto riguardo a MOVF e SWAPF si può sempre usare il circuito visualizzatore con i LED per vedere il valore assunto dal flag Z in due situazioni diverse. Nel primo caso si carica il valore 147 nella cella PIPPO e si esegue una MOVF di PIPPO in se stesso (per cui Z=0). Poi si usa SWAPF per caricare in W il valore di STATUS senza alterarlo ed infine si scrive il valore di W sulla PORTB, ricordando che i nibbles sono stati invertiti. Il flag Z è il bit 2 del registro STATUS, dopo lo swap (scambio) ce lo dobbiamo aspettare nel bit 6 di W, e quindi sul pin RB6 in uscita. Sono date per scontate la presenza della testata, il settaggio della porta B, la definizione di una cella di nome PIPPO con una EQU, e l'istruzione SLEEP finale per fermare il micro, che rimangono comunque sottointese. MOVLW 147 ;Carica 147 nell'accumulatore MOVWF PIPPO ;Lo mette nel registro PIPPO MOVF PIPPO,F ;Muove il registro PIPPO in se stesso (Z=0) SWAPF STATUS,W ;Swappa STATUS mettendolo in W MOVWF PORTB ;Manda W sui pin di uscita Il secondo programma invece carica 0 in PIPPO, quindi il MOVF deve far settare il flag Z, questa volta il LED su RB6 deve accendersi: MOVLW 0 ;Carica 0 nell'accumulatore MOVWF PIPPO ;Lo mette nel registro PIPPO MOVF PIPPO,F ;Muove il registro PIPPO in se stesso (Z=1) SWAPF STATUS,W ;Swappa STATUS mettendolo in W MOVWF PORTB ;Manda W sui pin di uscita https://twitter.com/profbianco Pag. 8

CLRF reg Z=1 (reg) = 0 CLRW Z=1 W = 0 Le ultime due istruzioni di caricamento e spostamento dati sono le CLRF, che permettono di azzerare i bit di un registro qualsiasi e dell'accumulatore. entrambe queste istruzioni impostano il flag Z a 1. L'esempio precedente poteva perciò esser scritto più sinteticamente: CLRW ;Azzera l'accumulatore MOVWF PIPPO ;Lo mette nel registro PIPPO MOVF PIPPO,F ;Muove il registro PIPPO in se stesso (Z=1) SWAPF STATUS,W ;Swappa STATUS mettendolo in W MOVWF PORTB ;Manda W sui pin di uscita Oppure, ancora meglio: CLRF PIPPO ;Azzera il registro PIPPO MOVF PIPPO,F ;Muove il registro PIPPO in se stesso (Z=1) SWAPF STATUS,W ;Swappa STATUS mettendolo in W MOVWF PORTB ;Manda W sui pin di uscita Riepilogo breve: le istruzioni MOVLW e MOVWF non alterano il flag Z. L'istruzione MOVF invece modifica il flag Z, che risulta settato (s=set=1) se il valore caricato è 0, e resettato (c=clear=0) negli altri casi. Una MOVF può trasferire il valore nella stessa locazione da cui viene letto, il suo valore perciò non cambia, ma, visto che il flag Z viene modificato, è un modo rapido per verificare se contiene zero. L' istruzione SWAPF scambia i nibbles (i 4 bit superiori e i 4 bit inferiori) di un registro, e deposita il risultato nell'accumulatore o nella locazione stessa da cui è stao prelevato. In alcuni casi può essere vantaggioso usarla, oltre che per swappare i nibble, anche come spostamento perché non altera i flags (per esempio è usata per salvare il registro STATUS durante un interrupt). Le istruzioni CLRF e CLRW sono un caricamento diretto del valore 0 in una locazione dati o nell'accumulatore. Entrambe impostano il flag Z a 1. L'unica differenza tra usare una MOVLW 0 e una CLRW è data dal flag Z, che resta invariato nel primo caso, mentre viene settato nel secondo. ISTRUZIONI ARITMETICHE ADDLW n C Z W = W + n ADDWF reg,d C Z d = W + (reg) SUBLW n C Z W = n - W SUBWF reg,d C Z d = (reg) - W INCF reg,d Z d = (reg) + 1 DECF reg,d Z d = (reg) - 1 La seconda grande categoria di istruzioni è quella aritmetica, grazie ad esse il micro ha la possibilità di effettuare dei semplici calcoli o di confrontare dei valori. I PIC delle famiglie 12F e 16F sono in grado di sommare, sottrarre, incrementare e decrementare valori a 8 bit (compresi tra 0 e 255). Come si può vedere dalla tabella tutte le istruzioni di questo gruppo alterano il flag Z, che viene https://twitter.com/profbianco Pag. 9

posto a 1 se il risultato dell'operazione è 0. Le operazioni di somma e sottrazione invece modificano anche il flag C (carry, detto anche flag di prestito/riporto) che è il bit 0 del registro STATUS. Durante una somma il flag C è normalmente a 0, e viene posto a 1 nel caso in cui si verifichi un overflow, nel caso cioè in cui il risultato ecceda il valore 255. Durante la sottrazione invece il flag C viene sempre tenuto a 1, e viene messo a 0 solo se la sottrazione causa un prestito, cioè se il risultato dell'operazione è negativo. Va detto che il valore di un registro non può diventare negativo (come non può aumentare oltre il 255), in questi casi si ha il rollover, un registro arrivato al limite torna cioè all'inizio come se i valori da 0 a 255 fossero disposti in un circolo in cui 0 e 255 sono vicini. Aggiungendo 1 al 255 ritorniamo infatti zero, sottraendo 1 allo 0 otteniamo 255. Il flag C indica l'avvenuto rollover in un senso o nell'altro. Avendo già dimestichezza con le istruzioni di caricamento è semplice capire come funzionano quelle di questo gruppo. La prima somma semplicemente un valore "n" (compreso tra 0 e 255) all'accumulatore W. La seconda invece somma l'accumulatore con un registro, e il risultato viene come sempre posto dove specificato con il parametro d. Le istruzioni di sottrazione sono un pò diverse da quelle di altri tipi di assembly, infatti qui è sempre l'accumulatore ad essere sottratto: SUBLW 15 significa: W = 15 - W SUBWF PIPPO,W significa: W = PIPPO - W SUBWF PIPPO,F significa: PIPPO = PIPPO - W. Le ultime due istruzioni (INCF e DECF) incrementano o decrementano di 1 il valore contenuto nel registro specificato, il risultato viene posto dove specificato con d. Queste istruzioni settano flag Z se il risultato dell'operazione è 0. Se si hanno dei dubbi sui valori che assumono i registri durante queste operazioni è sempre possibile visualizzarli con i LED. Per esempio con il seguente esempio dovremmo ottenere un valore binario di 250+170=420. Per determinare il valore assunto da un registro a causa del rollover è sufficiente sottrarre 256 ai valori che superano il 255 (o aggiungerlo a quelli che scendono sotto lo zero). Nel nostro caso 420-256=164 (10100100): MOVLW 250 ;Carica 250 nell'accumulatore MOVWF PIPPO ;Lo mette nel registro PIPPO MOVLW 170 ;Carica 170 nell'accumulatore ADDWF PIPPO,W ;Lo somma con il valore di PIPPO MOVWF PORTB ;Lo manda sui pin di uscita Inoltre l'operazione setta sicuramente il flag C, per cui visualizzando il registro status (swappato) sui LED si deve trovare il led corrispondente al pin RB4 acceso: MOVLW 250 ;Carica 250 nell'accumulatore MOVWF PIPPO ;Lo mette nel registro PIPPO MOVLW 170 ;Carica 170 nell'accumulatore ADDWF PIPPO,W ;Lo somma con il valore di PIPPO SWAPF STATUS,W ;Carica STATUS swappato su W MOVWF PORTB ;Lo manda sui pin di uscita ISTRUZIONI LOGICHE https://twitter.com/profbianco Pag. 10

ANDLW n Z W = W AND n ANDWF reg,d Z d = W AND (reg) IORLW n Z W = W OR n IORWF reg,d Z d = W OR (reg) XORLW n Z W = W XOR n XORWF reg,d Z d = W XOR (reg) COMF reg,d Z d = NOT (reg) Queste istruzioni sono quelle che forse più assomigliano alle funzioni svolte dai comuni circuiti logici, ed in effetti a livello hardware si comportano proprio come delle semplici porte logiche che operano sui bit dei registri o dell'accumulatore. Tutte le istruzioni a parte l'ultima richiedono due operandi su cui effettuare l'operazione logica. Gli operandi possono essere l'accumulatore e un valore numerico diretto "n", oppure l'accumulatore e un registro, in questo caso naturalmente va specificata la destinazione con il parametro d. Le funzioni logiche vengono applicate tra ogni bit corrispondente dei due operandi, cioè ad esempio tra il bit 0 dell'accumulatore e il bit 0 del registro, tra l'1 dell'accumulatore e l'1 del registro e così via: 100111010 AND 00101100 OR 00010001 XOR 000111000 = 10000010 = 10000001 = ------------- ------------ ------------- 000111000 10101110 10010000 Come si può vedere dalla tabella tutte le istruzioni logiche settano il flag Z nel caso in cui il loro risultato sia 0. Queste istruzioni permettono in modo semplice di settare, resettare o far cambiare di stato uno o più bit di un registro. Nel primo esempio dopo aver caricato 00110011 in PIPPO tramite un'operazione di OR vengono settati i suoi bit 3 e 2 che inizialmente erano a 0: MOVLW 00110011B ;Carica 51 nell'accumulatore MOVWF PIPPO ;Lo mette nel registro PIPPO MOVLW 00001100B ;Carica 12 nell'accumulatore IORWF PIPPO,F ;PIPPO=00111111 Nell'esempio seguente si parte sempre con PIPPO caricato nello stesso modo, ma si effettua poi una AND con 11110000. Il risultato è che tutti i bit corrispondenti agli 0 della AND vengono messi a 0, gli altri rimangono indisturbati. Questa operazione si chiama anche maschera AND, in quanto lascia passare i bit corrispondenti agli 1, mentre azzera tutti gli altri. MOVLW 00110011B ;Carica 51 nell'accumulatore MOVWF PIPPO ;Lo mette nel registro PIPPO MOVLW 11110000B ;Carica 240 nell'accumulatore ANDWF PIPPO,F ;PIPPO=00110000 Nell' ultimo esempio si usa la funzione logica XOR per scambiare lo stato dei bit corrispondenti ai suoi 1 e lasciare indisturbati gli altri: https://twitter.com/profbianco Pag. 11

MOVLW 00110011B ;Carica 51 nell'accumulatore MOVWF PIPPO ;Lo mette nel registro PIPPO MOVLW 10000001B ;Carica 129 nell'accumulatore XORWF PIPPO,F ;PIPPO=10110010 L'ultima istruzione del gruppo,la (COMF) effettua semplicemente una negazione dei livelli logici (NOT), il valore 00110011 diventerebbe 11001100. E'da notare che una COMF è identica ad uno XOR con tutti i bit a 1, ma richiede una sola istruzione mentre un'operazione di XOR ne richiederebbe 2. E'utile spendere qualche altra parola sullo XOR, in quanto permette dei trucchetti non immediatamente evidenti. Questi si basano sul fatto che un doppio XOR con lo stesso valore riporta al valore iniziale. è abbastanza semplice infatti comprendere che se il primo XOR inverte alcuni bit, il secondo li riinverte riportandoli al loro valore originale. Questa caratteristica permette anche di mescolare i bit di due registri e di ricostruirli in posizioni diverse della memoria. Come esempio Immaginiamo di voler scambiare tra di loro il contenuto dell'accumulatore e quello del registro PIPPO. A prima vista servono almeno altri due registri temporanei (chiamaiamoli TEMP1 e TEMP2) in cui depositare i valori iniziali dell'accumulatore e di PIPPO: MOVWF TEMP1 ;Salva accumulatore in TEMP1 MOVF PIPPO,W ;W=PIPPO MOVWF TEMP2 ;Salva PIPPO in TEMP2 MOVF TEMP1,W ;Recupera valore originale di W MOVWF PIPPO ;Lo mette in PIPPO MOVF TEMP2,W ;W = Vecchio valore di PIPPO Sfruttando le caratteristiche dell'operazione logica XOR è possibile evitare l'uso di registri temporanei e ridurre le istruzioni necessarie solamente a 3: XORWF PIPPO,F XORWF PIPPO,W XORWF PIPPO,F La prima non altera il valore di W, ma in PIPPO si viene a trovare il risultato di PIPPO XOR W. La seconda effettua di nuovo uno XOR tra W e PIPPO, il risultato è perciò complessivamente PIPPO XOR W XOR W, cioè il valore inizialmente contenuto in PIPPO, che viene tenuto in W. Infine si effettua un terzo XOR tra PIPPO (che contiene sempre l'iniziale PIPPO XOR W) e W che contiene il valore iniziale di PIPPO, il risultato dell'operazione è complessivamente PIPPO XOR W XOR PIPPO, che è perciò il valore iniziale di W che viene salvato in PIPPO... i due valori hanno così cambiato di posto. Sfruttando lo stesso principio è possibile anche scambiare tra di loro il valore di due registri (chiamiamoli REG1 e REG2) senza usarne altri di appoggio: MOVF REG1,W XORWF REG2,F XORWF REG2,W XORWF REG2,F MOVWF REG1 ROTAZIONI E SET/RESET DI SINGOLI BIT RLF reg,d C d = rlf (reg) https://twitter.com/profbianco Pag. 12

RRF reg,d C d = rrf (reg) BCF reg,b Bit b di (reg) = 0 BSF reg,b Bit b di (reg) = 1 Le istruzioni RLF e RRF ruotano rispettivamente a sinistra o a destra i bit contenuti in un registro. Il risultato è depositato nell'accumulatore o nel registro stesso, la rotazione avviene sempre attraverso il flag C come mostrato nelle due figure seguenti: RLF: RRF: Nella RLF il bit 7 del registro specificato viene spostato nel flag C, mentre il vecchio valore di C rientra nel bit 0 del registro dopo che tutti i bit sono stati spostati di una posizione a sinistra. La RRF funziona nello stesso modo, solo che la rotazione avviene nell'altro senso. L'utilità può non essere evidente, ma queste sono in realtà istruzioni molto potenti, che permettono di risolvere e semplificare numerosi problemi, per esempio legati al controllo di ogni singolo bit di un registro, alla serializzazione dei bit durante una trasmissione o al loro riassemblaggio in ricezione. Inoltre va ricordato che spostare a sinistra o a destra di una posizione i bit di un registro equivale rispettivamente a moltiplicare o dividere per 2 il suo valore numerico. Le ultime due istruzioni permettono di resettare (BCF) o settare (BSF) un qualsiasi bit di un qualsiasi registro lasciando invariati gli altri. Questo permette per esempio di usare i singoli bit di un registro come 8 semplici memorie a due stati, ottenendo così un grande risparmio nell'utilizzo di registri. Un esempio di registro usato in bit mode è lo STATUS, che non viene mai considerato come contenente un valore numerico, ma ogni suo bit ha invece un significato e utilizzo diverso e ben preciso, i flags C e Z sono due di questi bit. Per completezza è perfettamente lecito usare una BCF o BSF sui flag. Queste istruzioni funzionano naturalmente anche sui registri che comandano i pin configurati come uscite, e permettono di alzare o abbassare il livello della tensione in uscita anche di uno solo di essi in modo semplice (è così possibile generare dei segnali, anche delle frequenze audio se si vuole). Il parametro b delle istruzioni BCF e BSF indica il bit su cui agire, il suo valore va da 0 (bit meno significativo) a 7 (bit più significativo). L'esempio seguente genera un impulso positivo della durata di un ciclo macchina dal pin RB0 (con clock di 4MHz l'impulso dura 1µS): BSF PORTB,0 BCF PORTB,0 Importante! Bisogna sempre fare attenzione a non confondersi quando si indica il numero del bit all'interno di un byte. Siccome i bit sono numerati da 0 a 7, il bit 3 non è il terzo, ma il quarto! https://twitter.com/profbianco Pag. 13

CONTROLLO FLUSSO, SALTI, SUBROUTINE BTFSC reg,b Skip se bit b di (reg) = 0 BTFSS reg,b Skip se bit b di (reg) = 1 INCFSZ reg,d d = (reg) + 1 Skip se d = 0 DECFSZ reg,d d = (reg) - 1 Skip se d = 0 GOTO addr Salto all'indirizzo addr CALL addr Chiamata di subroutine Ritorno da subroutine RETLW n Ritorno da subr.con valore in W RETFIE Ritorno da interrupt E ora eccoci ad un insieme corposo e importante di istruzioni e concetti. Fino ad adesso infatti abbiamo visto solo quali sono i modi per impostare e manipolare in modo elementare i nostri dati, scriverne il valore su 8 LED e stop. Se fosse tutto qui un micro servirebbe a ben poco, ciò che manca ancora sono delle istruzioni in grado di far prendere delle decisioni al programma, in modo da effettuare un'operazione o un'altra a seconda del verificarsi di una certa condizione, ed eventualmente ripetere più volte un gruppo di istruzioni, in modo da creare così un processo dinamico che può continuare a leggere gli ingressi e generare gli opportuni segnali sulle uscite. Le istruzioni di questo gruppo si dicono di controllo flusso in quanto permettono effettivamente di alterare il normale flusso lineare di esecuzione, creando delle ramificazioni (branch, salti) a punti (indirizzi) diversi del programma. Usando i salti è possibile strutturare il programma a piacimento (il significato di struttura diverrà più chiaro andando avanti), a tale proposito è utile però ricordare che uno stile di programmazione corretto (che facilita il progetto, la lettura e la manutenzione) prevede l'uso di tre soli tipi fondamentali di struttura, ciascuna con un solo punto di inizio e una sola fine: La struttura sequenziale è quanto abbiamo già visto fino ad ora, è un insieme (o blocco) di istruzioni da eseguire una dopo l'altra. La struttura condizionale prevede la possibilità di porsi una domanda (contenuta nel rombo) a cui si può rispondere con un si o con un no, e di eseguire istruzioni (o blocchi di istruzioni) differenti a seconda dei due casi. La terza struttura permette di ripetere una o più istruzioni finchè la risposta alla domanda vale si (oppure no a seconda delle necessità). Con questa simbologia grafica è possibile rappresentare il funzionamento di un qualsiasi programma https://twitter.com/profbianco Pag. 14

sotto forma di flowchart (diagramma di flusso). Ciascun rettangolo può essere un'istruzione elementare, oppure contenere più istruzioni o anche sottostrutture. Uno dei rettangoli dell'istruzione condizionale può cioè contenere al suo interno altre strutture condizionali e/o iterative, che a loro volta ne possono contenere delle altre... il livello di dettaglio che si vuole rappresentare dipende dalle necessità caso per caso. Ma vediamo allora come un programma può porsi una domanda grazie alle prime due istruzioni della tabella: BTFSS e BTFSC. Queste servono a testare (o controllare) il valore (stato logico 1 o 0) di un qualsiasi bit di un qualsiasi registro, e permettono di saltare (skip) l'esecuzione dell'istruzione successiva se il bit testato si trova nello stato previsto. La prima salta l'istruzione seguente se il bit testato è 1 (alto, set), la seconda se è 0 (basso, clear). Il bit da testare è specificato con il parametro b, e come sempre può andare da 0 per indicare il bit meno significativo, a 7 per quello più significativo. Come si vede in questi due esempi viene testato il flag Z, in fondo i flags sono due bit come tutti gli altri, e diventa evidente la loro grandissima importanza. Infatti è anche grazie al loro valore (che dipende dal risultato delle istruzioni appena eseguite) che il programma può prendere decisioni anche complesse e comportarsi in modi diversi a seconda delle circostanze. Un semplice skip dell'istruzione successiva non fornisce però ancora la flessibilità necessaria per realizzare una completa struttura condizionale equivalente all' IF THEN ELSE (se, allora altrimenti) dei linguaggi ad alto livello. Questa si ottiene con l'aggiunta dell'istruzione GOTO (vai a, salta), che permette di saltare ad un punto qualsiasi del programma da un qualsiasi altro punto. Quando una GOTO è usata insieme ad una istruzione di skip si crea un salto condizionato. E' naturalmente possibile mettere una GOTO in qualsiasi punto del programma, in tal caso questo salto prende il nome di salto incondizionato. E' buona norma però ridurre il più possibile l'utilizzo dei salti perché rendono complicata la lettura e la manutenzione/correzione del programma. Il loro utilizzo andrebbe limitato (se possibile) esclusivamente per la creazione delle strutture fondamentali condizionale e iterativa. Per indicare il punto di arrivo di un salto (GOTO) si usano normalmente delle etichette (label), cioè dei nomi inizianti nella prima colonna del testo. E' evidente che in un programma non devono mai esserci due label uguali, in quanto ciascuna identifica un indirizzo della memoria programma ben preciso. L'esempio seguente mostra come realizzare in assembly la struttura IF THEN ELSE completa. Nell'esempio viene testato il bit 0 del registro DIREZ, se vale 0 viene eseguita l'istruzione RLF LUCE,F (saltando la GOTO ELSE) seguita dal salto a ENDIF, altrimenti viene eseguita l'istruzione RRF LUCE,F. Le istruzioni da eseguire in un caso o nell'altro possono essere anche più di una, a https://twitter.com/profbianco Pag. 15

differenza della skip semplice che al massimo permette di saltare l'esecuzione di una singola istruzione. L' esempio seguente presenta la struttura iterativa, ed è molto importante comprenderlo appieno perché su questo genere di struttura/funzionamento si basa gran parte dell'elaborazione nei casi reali. E' una generica sezione di programma in cui ad un certo punto si carica il valore 4 in un registro della RAM (chiamato come al solito PIPPO). Seguono poi delle istruzioni indicate con dei puntini. Ed infine troviamo altre 4 istruzioni. Le prime due servono a sottrarre 1 dal registro PIPPO. La terza controlla il valore del flag Z che viene settato quando il risultato dell'istruzione precedente vale 0. Se il flag Z è settato viene saltata l'istruzione seguente e il programma prosegue. Altrimenti, se PIPPO dopo la sottrazione non vale 0 e il flag Z non è settato, lo skip non viene effettuato, perciò viene eseguita la GOTO che rimanda indietro al punto indicato dall'etichetta NEXT. In pratica si crea quello che viene chiamato un loop (ciclo, anello) che ripete il blocco di istruzioni interne esattamente 4 volte. Il registro PIPPO funziona da contatore di ciclo. Siccome il suo valore parte da 4 e decrementa ad ogni ciclo, al quarto passaggio raggiunge lo 0 e il loop termina, il programma prosegue cioè con le istruzioni successive... MOVLW 4 MOVWF PIPPO ;W=4 ;PIPPO=W NEXT...... MOVLW 1 ;W=1 SUBWF PIPPO,F ;PIPPO=PIPPO-W BTFSS STATUS,Z ;SE PIPPO=0 ALLORA SKIP (FINE CICLO) GOTO NEXT ;ALTRIMENTI TORNA A NEXT https://twitter.com/profbianco Pag. 16

Abbiamo ormai quasi tutti gli elementi per poter finalmente animare un pò i nostri LED, prima però vediamo qualche altra istruzione. La terza e la quarta istruzione della tabella sono istruzioni potenti, in quanto permettono in un colpo solo di incrementare o decrementare di 1 un registro, ed effettuare automaticamente uno skip dell'istruzione seguente se il risultato dell'operazione vale 0. L'esempio precedente può perciò essere riscritto più succintamente nel seguente modo: MOVLW 4 MOVWF PIPPO ;W=4 ;PIPPO=W NEXT...... DECFSZ PIPPO,F ;DECR.PIPPO, SE=0 ALLORA SKIP (FINE CICLO) GOTO NEXT ;ALTRIMENTI TORNA A NEXT Con i salti (condizionati e non) è già possibile creare qualsiasi flusso elaborativo, tuttavia se c'è bisogno di creare delle suddivisioni logiche tra sezioni di programma che eseguono compiti specifici, oppure ci sono gruppi di istruzioni ripetuti frequentemente in diversi punti del programma, è preferibile ricorrere alle subroutines (o sottoprogrammi). Ogni subroutine può essere così considerata come un modulo a parte, da chiamare quando serve il suo servizio. Le subroutines sono semplicemente porzioni di programma che vengono chiamate con l'istruzione CALL (nello stesso modo di GOTO) ma terminano con l'istruzione che fa ritornare il programma all'istruzione successiva alla CALL di partenza. Questo comportamento è possibile perché ad ogni CALL il PIC salva il contatore di programma (il program counter, che tiene conto dell'indirizzo dell'istruzione in esecuzione) in un'area speciale chiamata stack, e lo riprende quando incontra una. Il PIC 16F628 ha un'area di stack a 8 livelli, è quindi possibile chiamare fino a 8 subroutines una dentro l'altra (nidificate o nested). L'uso delle subroutines è estremamente consigliato, in quanto sono l'unica cosa in grado di tenere in ordine un programma complesso spezzandolo in più moduli, cioè porzioni di codice di dimensioni ridotte che eseguono ciascuna un compito ben specifico, più facile da comprendere e da mettere a punto. Un'altra buona ragione per l' utilizzo dei sottoprogrammi è che all'evenienza possono essere copiati in altri programmi per essere riutilizzati. In gergo una raccolta utile di subroutines riutilizzabili si chiama libreria. https://twitter.com/profbianco Pag. 17

Ci sono altre due forme di istruzione di ritorno da subroutine, la RETLW e la RETFIE. La prima permette il ritorno da una subroutine con un valore specifico caricato nell'accumulatore, come vedremo serve per creare delle tabelle dati nell'area programma, cosa altrimenti impossibile in quanto questa memoria non è accessibile dalle comuni istruzioni che lavorano solo sui registri dalla memoria dati. La RETFIE invece si usa nel caso in cui la subroutine da terminare sia un gestore di interrupt. Esperimento: Ed ora, per mettere in pratica tutto quanto, subroutines, salti condizionati e rotazioni di bit, vogliamo ottenere sui LED un effetto supercar, cioè deve accendersi un LED alla volta scorrendo da destra verso sinistra per poi tornare indietro e così via. Dapprima definiamo le variabili di lavoro (registri) che ci occorrono. Un registro di nome LUCE conterrà il valore a 8 bit da scrivere sulla porta di uscita, uno chiamato DIREZ conterrà 0 o 1 a seconda che la direzione di scorrimento voluta sia verso sinistra o verso destra, ed infine due registri di nome L_CONT e H_CONT serviranno come contatore di ciclo a 16 bit per una subroutine di ritardo. Quest'ultima è necessaria perché la velocità di esecuzione sarebbe altrimenti così rapida da non poter essere notata, in pratica tutti i LED apparirebbero ugualmente illuminati a causa della persistenza dell'immagine sulla nostra retina. Visto che ogni istruzione impiega un certo tempo per essere eseguita è evidente che eseguendo decine di migliaia di volte poche istruzioni all'interno di un loop si possono ottenere ritardi di tempo considerevoli. E' necessario usare 16 bit per il conteggio in quanto usandone solo 8 il massimo ritardo ottenibile sarebbe troppo breve (meno di 2 millisecondi). Per visualizzare un effetto gradevole ci occorre invece un ritardo di diverse decine di ms. Una soluzione poteva essere quella di richiamare la sbroutine di ritardo a 8 bit della durata di 2 ms molte volte di fila, ma questo avrebbe in ogni caso comportato l'uso di un altro ciclo e di un altro registro di conteggio. In questo esempio invece si usano due byte in modo tale da considerarli come un unico registro a 16 bit, con cui realizzare cicli da 1 a 65536 ripetizioni, ed è il primo esempio di creazione via software di un tipo di dato che può assumere valori maggiori di quelli trattabili direttamente dal micro. Un valore a 16 bit è composto infatti da due byte detti alto (H, più significativo) e basso (L, meno significativo). Il valore contenuto in quello alto ha peso 256, una unità nel byte alto vale cioè 256 volte la stessa unità nel byte basso, questo vuol dire che il valore complessivo a 16 bit di due byte H ed L è dato da H*256 + L. Nel nostro caso impostiamo un ciclo di 5320 ripetizioni, la parte alta varrà 20 e quella bassa 200 (infatti 20*256+200=5320). Per decrementare un valore a 16 bit si decrementa prima la parte bassa, se si ha un rollover negativo (da 0 si torna a 255) allora si decrementa anche la parte alta. Quando entrambe le parti contengono zero (basta fare un OR tra di loro e verificare se si setta il flag Z) il ciclo termina. Durante l'esecuzione del programma il valore binario iniziale 00000001 di LUCE viene spostato verso sinistra di una posizione alla volta tramite l'istruzione RLF. L'azzeramento del flag C prima della rotazione fa si che da destra non entri mai un altro bit a 1. Quando il bit a 1 contenuto in LUCE raggiunge la posizione 7 (cioè l'ottava a sinistra, la piùsignificativa) il valore del registro direzione viene messo a 1, in modo tale da abilitare lo spostamento verso destra. Allo stesso modo quando il bit a 1 di LUCE ritorna nella posizione 0 il valore della direzione cambia di nuovo riabilitando lo spostamento verso sinistra come all'inizio. Il programma è senza fine perché il GOTO finale rimanda all'infinito alla posizione MAINLOOP, in totale occupa 33 locazioni di memoria programma delle 2048 disponibili, e utilizza 4 byte di memoria RAM dei 224 disponibili. ; Programma effetto supercar PROCESSOR 16F628 https://twitter.com/profbianco Pag. 18

RADIX DEC INCLUDE "P16F628.INC" CONFIG 11110100010000B LUCE EQU 32 H_CONT EQU 33 L_CONT EQU 34 DIREZ EQU 35 ORG 0 BSF STATUS,RP0 ;Attiva banco 1 CLRF TRISB ;Rende PORTB un'uscita BCF STATUS,RP0 ;Ritorna al banco 0 MOVLW 00000001B MOVWF LUCE ;LUCE=00000001 CLRF DIREZ ;DIREZ=0 MAINLOOP MOVF LUCE,W MOVWF PORTB ;Scrive LUCE sulla PORTB BCF STATUS,C ;Azzera flag C BTFSC DIREZ,0 ;Se DIREZ=0 (sinistra) skip GOTO LAB1 ;altrimenti GOTO LAB1 RLF LUCE,F ;Ruota LUCE verso sinistra GOTO LAB2 ;GOTO fine struttura IF LAB1 RRF LUCE,F ;Ruota LUCE verso destra LAB2 BTFSC LUCE,7 ;Se bit 7 di LUCE è 0 skip INCF DIREZ,F ;altrimenti direzione=destra BTFSC LUCE,0 ;Se bit 0 di luce è 0 skip CLRF DIREZ ;altrimenti direzione=sinistra CALL DELAY ;Richiama subroutine di ritardo GOTO MAINLOOP ;Nuovo ciclo del programma DELAY MOVLW 20 ;Carica 5320 nei 16 bit MOVWF H_CONT ;formati dai due byte MOVLW 200 ;H_CONT e L_CONT MOVWF L_CONT DELAY2 DECF L_CONT,F ;Decrementa parte bassa del contatore COMF L_CONT,W ;Inverte i bit BTFSC STATUS,Z ;Se tutti zero c'è stato rollover DECF H_CONT,F ;allora decrementa parte alta MOVF L_CONT,W ;Carica in W la parte bassa IORWF H_CONT,W ;Mettila in OR con la parte alta BTFSS STATUS,Z ;Se tutto zero skip (fine ciclo) GOTO DELAY2 ;Altrimenti ritorna a DELAY2 END Qui sotto sono riportati i flowchart completi della sezione principale del programma (chiamata anche main) e della subroutine delay. A fianco del disegno sono riportate le istruzioni che compongono le varie parti. Come si può vedere la chiamata alla subroutine si rappresenta con un rettangolo con aggiunte due righe laterali. https://twitter.com/profbianco Pag. 19

https://twitter.com/profbianco Pag. 20

Il programma è completo e funzionante, tuttavia è possibile una piccola ottimizzazione della struttura IF THEN ELSE. Infatti nel caso cui vi sia una sola istruzione da eseguire sotto il THEN e una sola sotto l'else la struttura può essere scritta in modo più compatto risparmiando una istruzione e la necessità di dover definire due etichette a cui saltare, da così: BTFSC DIREZ,0 GOTO LAB1 RLF LUCE,F GOTO LAB2 LAB1 RRF LUCE,F LAB2... a così: BTFSC DIREZ,0 RRF LUCE,F BTFSS DIREZ,0 RLF LUCE,F Il main del nostro programma supercar diventerebbe cioè: BSF STATUS,RP0 ;Attiva banco 1 CLRF TRISB ;Rende PORTB un'uscita BCF STATUS,RP0 ;Ritorna al banco 0 MOVLW 00000001B MOVWF LUCE ;LUCE=00000001 CLRF DIREZ ;DIREZ=0 MAINLOOP MOVF LUCE,W MOVWF PORTB ;Scrive LUCE sulla PORTB BCF STATUS,C ;Azzera flag C BTFSC DIREZ,0 ;Se DIREZ=0 (sinistra) skip RRF LUCE,F ;Ruota LUCE verso destra BTFSS DIREZ,0 ;Se DIREZ=1 (destra) skip RLF LUCE,F ;Ruota LUCE verso sinistra BTFSC LUCE,7 ;Se bit 7 di LUCE è 0 skip INCF DIREZ,F ;altrimenti direzione=destra BTFSC LUCE,0 ;Se bit 0 di luce è 0 skip CLRF DIREZ ;altrimenti direzione=sinistra CALL DELAY ;Richiama subroutine di ritardo GOTO MAINLOOP ;Nuovo ciclo del programma https://twitter.com/profbianco Pag. 21

Lo stesso sistema può essere usato per spostare o meglio copiare un singolo bit di un qualsiasi registro in un altro bit di un qualsiasi altro registro. Nell'esempio seguente copiamo il valore del flag C nel bit 4 del registro PIPPO: BTFSC STATUS,C BSF PIPPO,4 BTFSS STATUS,C BCF PIPPO,4 La sequenza appena vista va bene anche per trasferire il valore di un singolo bit della RAM su un singolo pin di uscita di una porta. Se si lavora invece solo con registri in memoria, come in questo questo caso specifico, è possibile in realtà una ulteriore riduzione di istruzioni. Basta impostare inizialmente il bit di PIPPO ad un valore specifico, per esempio 0, e poi cambiandolo subito dopo SOLO SE il flag C è settato: BCF PIPPO,4 BTFSC STATUS,C https://twitter.com/profbianco Pag. 22

BSF PIPPO,4 Per concludere va ricordato che tutte le istruzioni viste in precedenza duravano sempre un ciclo macchina. Le istruzioni viste in questo capitolo invece possono durare anche 2 cicli macchina. Per la precisione le istruzioni di salto, chiamata e ritorno (GOTO, CALL,, RETLW, RETFIE) richiedono sempre 2 cicli macchina, mentre gli skip ne richiedono 2 solo se la condizione è verificata. ISTRUZIONI DI CONTROLLO SISTEMA CLRWDT SLEEP NOP Azzera watch dog timer Standby mode Nessuna operazione Per completare la lista delle 35 istruzioni eseguibili dai PIC mancano le tre qui sopra. La prima serve per azzerare il contatore WDT, un registro hardware che incrementa automaticamente e, se abilitato, produce un reset del PIC quando arriva all'overflow (rollover). Questo serve in applicazioni critiche in cui è necessario che il PIC riparta automaticamente in caso di blocco accidentale del programma (crash) per esempio a causa di un forte disturbo elettrico. Nel caso in cui il WDT sia abilitato, nel programma si dovrà periodicamente effettuare un CLRWDT prima che il tempo scada. La seconda manda in sleep il micro, che sospende ogni attività ed entra in modalità risparmio di energia. Per uscire dalla condizione di sleep il micro va resettato oppure deve ricevere una richiesta di interrupt nel caso in cui gli interrupt siano stati abilitati. Della tabella rimane ancora la curiosa istruzione NOP. Come indicato nella descrizione questa istruzione non esegue nessuna funzione. E' una follia dei progettisti? Niente affatto, ogni microprocessore dispone di una istruzione NOP, che serve fondamentalmente per far passare del tempo in modo controllato senza influire su alcun altro registro. Come ogni altra istruzione anche una NOP richiede un ciclo macchina per essere eseguita, a 4 MHz un ciclo macchina dura 1 us, pertanto l'inserzione di una NOP in un punto qualsiasi del programma determina un ritardo di 1 us tra la fine dell'istruzione precedente e l'inizio di quella successiva. Ora, 1 us di ritardo è poca cosa, se però una o più NOP vengono racchiuse in un ciclo che le esegue centinaia o migliaia di volte si possono ottenere ritardi precisi di qualsiasi durata. E con questo la lista delle operazioni elementari è terminata, ora si tratta solo di vedere molti esempi su come possono essere combinate assieme per realizzare i compiti più disparati. Chi conosce già l'assembly di altri tipi di microprocessore noterà la mancanza di alcune cose, come un comodo stack su cui salvare i propri registri di lavoro, o l' assenza di registri indice che permettono l'accesso alle celle della ram puntandole da programma con un indirizzo contenuto in un altro registro. In realtà quest'ultima possibilità c'è, ma, come per altre cose in un microcontroller, la sua potenza è ridotta. E' un'operazione comunque possibile che è bene conoscere perché molto importante. https://twitter.com/profbianco Pag. 23

Indirizzamento RAM tramite puntatore Ripetizioni Materie Scientifiche I PIC dispongono di un registro di nome FSR che funziona da indirect memory data pointer. Un valore scritto in questo registro viene considerato come un indirizzo della RAM. Per scrivere o leggere a questo indirizzo così puntato da FSR basta scrivere o leggere nel registro INDF. Un esempio chiarisce meglio il concetto: MOVLW 32 MOVWF FSR ;Punta la cella di indirizzo 32 MOVLW 255 MOVWF INDF ;Ci scrive 255 MOVLW 32 MOVWF FSR ;Punta la cella di indirizzo 32 MOVF INDF,W ;Legge in W il suo contenuto Siccome il valore caricato in FSR può anche essere incrementato o decrementato, è possibile gestire una certa sezione della RAM come un array, cioè una lista di byte raggiungibili con un indice progressivo senza la necessità di doverne definire un nome come si fa solitamente con gli altri registri. Un'area del genere può servire per memorizzare ad esempio dei numeri battuti su una tastiera o dei valori in arrivo da una porta seriale. Una sezione di memoria usata in questo modo viene anche chiamata buffer. Il codice seguente azzera 10 byte a partire dall'indirizzo RAM 40 (usa il registro PIPPO come contatore di ciclo): MOVLW 40 MOVWF FSR ;Punta la cella di indirizzo 40 MOVLW 10 MOVWF PIPPO ;Carica 10 in PIPPO NEXT CLRF INDF ;Azzera la cella RAM puntata da FSR INCF FSR,F ;Incrementa il puntatore DECFSZ PIPPO,F ;Se fatti 10 cicli termina GOTO NEXT ;altrimenti torna a next https://twitter.com/profbianco Pag. 24

RIEPILOGO ISTRUZIONI Ripetizioni Materie Scientifiche Il valore di d può valere 0 o 1 (o, rispettivamente, W o F), reg indica l'indirizzo di un registro dati, addr indica un indirizzo di programma nelle istruzioni GOTO e CALL, b indica un bit all'interno di un byte (0..7), n è un valore costante (literal) a 8 bit (0..255). Opcode operando Cyc. Flags Descrizione ADDWF reg,d 1 Z C d= W + (reg) ADDLW n 1 Z C W = W + n ANDWF reg,d 1 Z d = W AND (reg) ANDLW n 1 Z W = W AND n BCF reg,b 1 Bit b di (reg) = 0 BSF reg,b 1 Bit b di (reg) = 1 BTFSC reg,b 1(2) Skip se bit b di (reg) = 0 BTFSS reg,b 1(2) Skip se bit b di (reg) = 1 CALL addr 2 Chiamata di subroutine CLRF reg 1 Z=1 (reg) = 0 CLRW 1 Z=1 W = 0 CLRWDT 1 Azzera watch dog timer COMF reg,d 1 Z d = NOT (reg) DECF reg,d 1 Z d = (reg) - 1 DECFSZ reg,d 1(2) d = (reg) - 1 Skip se d = 0 GOTO addr 2 Salto all'indirizzo addr INCF reg,d 1 Z d = (reg) + 1 INCFSZ reg,d 1(2) d = (reg) + 1 Skip se d = 0 IORLW n 1 Z W = W OR n IORWF reg,d 1 Z d = W OR (reg) MOVF reg,d 1 Z d = (reg) MOVLW n 1 W = n MOVWF reg 1 (reg) = W NOP 1 Nessuna operazione RETFIE 2 Ritorno da interrupt RETLW n 2 Ritorno da subr. con valore in W 2 Ritorno da subroutine RLF reg,d 1 C d = rlf (reg) RRF reg,d 1 C d = rrf (reg) SLEEP 1 Standby mode SUBLW n 1 Z C W = n - W SUBWF reg,d 1 Z C d = (reg) - W SWAPF reg,d 1 d = swap (reg) XORLW n 1 Z W = W XOR n https://twitter.com/profbianco Pag. 25

COMANDO DI UNO SHIFT REGISTER CD4094 Fino ad ora abbiamo visto come emettere un byte in parallelo sugli 8 pin RB0..RB7, e come creare un'animazione con scritture ripetute. Lo scopo di questo capitolo è quello di mostrare come si possono usare i pin di I/O per generare sequenze arbitrarie di segnali per controllare circuiti esterni. Useremo uno shift register di tipo CD4094 che dispone di 8 uscite per ricreare la stessa animazione supercar. Il PIC dovrà generare gli opportuni segnali per trasferire il dato (registro LUCE) serialmente un bit alla volta verso il 4094, ed infine convalidarlo con un impulso di conferma (strobe). Questo integrato è uno shift register di tipo SIPO (serial input parallel output). Può anche essere usato per ampliare le uscite digitali nel caso in cui quelle del PIC siano insufficienti. Richiede solo 3 segnali di controllo: Dati e Clock per caricare serialmente un byte al suo interno, e Strobe per portarlo sulle sue 8 uscite (Q1..Q8). Come si può vedere dal diagramma temporale più sotto il primo bit che entra finisce (scorre) sull'uscita Q8, l'ultimo sull'uscita Q1. Durante il trasferimento le uscite rimangono stabili, il loro valore viene aggiornato in un colpo solo dando l'impulso di Strobe. Le uscite del 4094 non possono fornire tanta corrente come le uscite di un PIC, i LED risultano perciò meno luminosi. Volendo riprodurre il solito effetto supercar (in cui è acceso un solo LED alla volta) èsufficiente mettere una sola resistenza comune verso massa. Va tenuto conto che al momento dell'accensione i flip flop interni del 4094 contengono valori casuali, per cui i LED per un attimo possono essere accesi casualmente fino a quando non viene effettuata la prima scrittura nel registro. https://twitter.com/profbianco Pag. 26

Per questo esperimento si può usare il seguente collegamento tra un generico PIC (di cui sono indicati i soli pin di I/O usati) e il 4094 (le alimentazioni sono naturalmente sottointese). Si vuole usare lo stesso programma dell'effetto supercar, però scrivendo il valore del registro LUCE sul registro esterno invece che direttamente sui pin della porta B. Questi pin verranno usati invece per generare i segnali di controllo per il 4094. Ci interessa inoltre fare in modo che il bit0 del registro LUCE finisca sull'uscita Q8, e il bit7 sull'uscita Q1. Per fare questo si dovranno inviare in sequenza i bit del registro LUCE dal pin RB1 (ciascuno seguito da un impulso di clock emesso dal pin RB2) partendo dal bit0. Alla fine, dopo 8 impulsi di clock, si conferma il dato con un impulso di strobe emesso dal pin RB0. Si potrebbe pensare di dover riprogettare da capo tutto il programma. Invece, ragionando in modo modulare, basta aggiungere una sola subroutine con funzione di driver e poche altre cose. In particolare occorre definire i nomi di altri due registri dati che servono alla subroutine di scrittura sul 4094, bisogna azzerare le uscite della porta B dopo averla configurata come uscita (per impostare il livello di riposo delle uscite, nel nostro caso vanno messe a 0), e al posto della scrittura diretta sulla porta si deve chiamare la subroutine di trasferimento seriale. ; Programma effetto supercar attraverso shift register PROCESSOR 16F628 RADIX DEC INCLUDE "P16F628.INC" CONFIG 11110100010000B LUCE EQU 32 ;Valore da scrivere sui LED H_CONT EQU 33 ;Parte alta contatore ritardo L_CONT EQU 34 ;Parte bassa contatore ritardo https://twitter.com/profbianco Pag. 27

DIREZ EQU 35 ;Direzione 0=sinistra 1=destra TX_BYTE EQU 36 ;Valore da trasferire serialmente B_CONT EQU 37 ;Contatore dei bit da trasmettere ORG 0 BSF STATUS,RP0 ;Attiva banco 1 CLRF TRISB ;Rende PORTB un'uscita BCF STATUS,RP0 ;Ritorna al banco 0 CLRF PORTB ;Azzera uscite PORTB MOVLW 00000001B MOVWF LUCE ;LUCE=00000001 CLRF DIREZ ;DIREZ=0 MAINLOOP MOVF LUCE,W ;W=LUCE CALL SIPO ;Richiama subroutine SIPO BCF STATUS,C ;Azzera flag C BTFSC DIREZ,0 ;Se DIREZ=0 (sinistra) skip RRF LUCE,F ;Ruota LUCE verso destra BTFSS DIREZ,0 ;Se DIREZ=1 (destra) skip RLF LUCE,F ;Ruota LUCE verso sinistra BTFSC LUCE,7 ;Se bit 7 di LUCE è 0 skip INCF DIREZ,F ;altrimenti direzione=destra BTFSC LUCE,0 ;Se bit 0 di luce è 0 skip CLRF DIREZ ;altrimenti direzione=sinistra CALL DELAY ;Richiama subroutine di ritardo GOTO MAINLOOP ;Nuovo ciclo del programma DELAY MOVLW 20 ;Carica 5320 nei 16 bit MOVWF H_CONT ;formati dai due byte MOVLW 200 ;H_CONT e L_CONT MOVWF L_CONT DELAY2 DECF L_CONT,F ;Decrementa parte bassa del contatore COMF L_CONT,W ;Inverte i bit BTFSC STATUS,Z ;Se tutti zero c'è stato rollover DECF H_CONT,F ;allora decrementa parte alta MOVF L_CONT,W ;Carica in W la parte bassa IORWF H_CONT,W ;Mettila in OR con la parte alta BTFSS STATUS,Z ;Se tutto zero skip (fine ciclo) GOTO DELAY2 ;Altrimenti ritorna a DELAY2 SIPO MOVWF TX_BYTE ;TX_BYTE=W MOVLW 8 ;Carica 8 nel contatore MOVWF B_CONT ;dei bit da trasmettere SIPO2 RRF TX_BYTE,F ;Ruota TX_BYTE a destra nel flag C BTFSC STATUS,C ;Se flag C=0 skip BSF PORTB,1 ;altrimenti dato out=1 BTFSS STATUS,C ;Se flag C=1 skip BCF PORTB,1 ;Altrimenti dato out=0 BSF PORTB,2 ;Clock=1 BCF PORTB,2 ;Clock=0 DECFSZ B_CONT,F ;Decr.contat.bit skip se 0 GOTO SIPO2 ;Prossimo bit da trasmettere BSF PORTB,0 ;Strobe=1 BCF PORTB,0 ;Strobe=0 END https://twitter.com/profbianco Pag. 28

Si è accennato al fatto che il sottoprogramma SIPO può essere considerato l'equivalente di un piccolo driver software, infatti sarebbe possibile usare anche altri tipi di shift register senza alterare minimamente la sezione principale del programma, ma apportando le modifiche necessarie solo al driver. L'unica cosa che deve rimanere inalterata è l'interfaccia, cioè il modo in cui il programma principale comunica il dato da trasmettere al sottoprogramma. Nel nostro caso l'interfaccia è semplicemente il registro accumulatore W, il main deve caricare in W il dato da trasmettere, il sottoprogramma preleva da W il dato. Diventa così ancora più chiaro il concetto di modulo software, inteso come blocco funzionale autonomo che svolge al meglio un certo compito e comunica con il resto del programma attraverso un'interfaccia ben precisa, senza interferire con nessun altro registro o funzione. Un modulo concepito in questo modo può facilmente essere trasportato in un altro programma. Guardando la figura seguente, che è il nostro programma completo, diventa anche più chiaro il concetto di struttura. Si vede come è realizzato con le strutture fondamentali sequenziale, condizionale e iterativa, e si vede inoltre come ogni rettangolo può in realtà anche rappresentare intere sezioni di programma ad un livello di maggiore astrazione. Questo è il caso delle due chiamate a sottoprogramma CALL SIPO e CALL DELAY, che virtualmente racchiudono in due rettangolini i due flowcharts completi visibili sulla loro destra. A loro volta i rettangoli di un sottoprogramma potrebbero rappresentare altri blocchi di istruzioni o sottoprogrammi. Ciò che in ogni caso viene mantenuto è il fatto che ogni struttura (o flowchart) ha un solo punto di inizio e un https://twitter.com/profbianco Pag. 29

solo punto di termine, questo facilita la scrittura e la comprensione del programma, porta a scrivere programmi modulari in modo naturale, e prende il nome di programmazione strutturata. Se non ci fossero le istruzioni CALL e la cosa sarebbe molto meno pulita, si dovrebbero mettere molti GOTO, soprattutto nel caso in cui il sottoprogramma venisse chiamato da più punti differenti del programma principale. In questo caso infatti si dovrebbe usare un ulteriore registro per dire al sottoprogramma chi è il chiamante, in modo da poter effettuare il corretto GOTO di ritorno... Grazie allo stack fortunatamente tutto ciò non serve. In base a quanto detto finora risulta chiaro che l'uso del GOTO, particolarmente libero (e obbligatorio) in assembly, andrebbe limitato alla sola creazione di strutture, e non usato a casaccio, altrimenti si ottengono i cosiddetti programmi spaghetti, in cui è difficile seguire/comprendere/modificare il flusso logico dell'esecuzione (benchè il programma possa funzionare ugualmente). https://twitter.com/profbianco Pag. 30

#DEFINE Sempre nell'ottica di rendere il tutto il più pulito e trasportabile possiamo usare la potente direttiva di assemblaggio #define per dare dei nomi simbolici ad istruzioni o parti di esse. Per esempio possiamo definire i pin utilizzati per comandare il 4094 nel seguente modo: #DEFINE PIN_TX PORTB,1 #DEFINE PIN_CLK PORTB,2 #DEFINE PIN_STRB PORTB,0 E nel programma possiamo usare i nomi simbolici: SIPO MOVWF TX_BYTE ;TX_BYTE=W MOVLW 8 ;Carica 8 nel contatore MOVWF B_CONT ;dei bit da trasmettere SIPO2 RRF TX_BYTE,F ;Ruota TX_BYTE a destra nel flag C BTFSC STATUS,C ;Se flag C=0 skip BSF PIN_TX ;altrimenti dato out=1 BTFSS STATUS,C ;Se flag C=1 skip BCF PIN_TX ;Altrimenti dato out=0 BSF PIN_CLK ;Clock=1 BCF PIN_CLK ;Clock=0 DECFSZ B_CONT,F ;Decr.contat.bit skip se 0 GOTO SIPO2 ;Prossimo bit da trasmettere BSF PIN_STRB ;Strobe=1 BCF PIN_STRB ;Strobe=0 A questo punto il sottoprogramma è indipendente dall'hardware, può usare qualsiasi pin di ingresso/uscita semplicemente modificando la sezione di definizione, e può essere facilmente usato su altri modelli di PIC, o usare anche pin di porte differenti. L'unica cosa a cui bisogna fare attenzione è di non dare nomi già usati nel file include specifico per ogni modello di PIC (in questo caso P16F628.INC). La sezione delle #define può essere messa subito dopo la definizione dei registri dati utilizzati dal programma. https://twitter.com/profbianco Pag. 31

ORG e RES La direttiva di assemblaggio ORG l'abbiamo già vista messa prima della prima istruzione del programma, ed indica l'indirizzo a cui va caricato fisicamente il programma nella memoria flash (tipicamente 0). Ha però anche un secondo utilizzo nel caso venga usata per definire degli indirizzi di memoria dati tramite la direttiva RES. Finora abbiamo dato un nome ai registri di lavoro scrivendone esplicitamente l'indirizzo tramite la direttiva EQU. Nel caso in cui si voglia trasportare un programma su un PIC diverso occorre naturalmente cambiare tutti gli indirizzi dell'area dati e questo è un pòscomodo oltre che fonte di possibili errori. Tramite ORG e RES possiamo invece dichiarare solo l'indirizzo di inizio dell'area, e dire all'assemblatore quanti byte riservare per ogni variabile di lavoro, sarà lui a stabilire l'indirizzo di ogni registro partendo dal valore specificato con ORG: PROCESSOR 16F628 RADIX DEC INCLUDE "P16F628.INC" CONFIG 11110100010000B ORG 32 ;Indirizzo inizio RAM dati LUCE RES 1 ;Valore da scrivere sui LED H_CONT RES 1 ;Parte alta contatore ritardo L_CONT RES 1 ;Parte bassa contatore ritardo DIREZ RES 1 ;Direzione 0=sinistra 1=destra TX_BYTE RES 1 ;Valore da trasferire serialmente B_CONT RES 1 ;Contatore dei bit da trasmettere #DEFINE PIN_TX PORTB,1 ;Pin di trasmissione dato #DEFINE PIN_CLK PORTB,2 ;Pin di trasmissione clock #DEFINE PIN_STRB PORTB,0 ;Pin di trasmissione strobe A questo punto se volessimo trasferire il programma su un PIC16F84 sarebbe sufficiente effettuare le seguenti modifiche: PROCESSOR 16F84 RADIX DEC INCLUDE "P16F84.INC" CONFIG 11111111110001B ORG 12 ;Indirizzo inizio RAM dati LUCE RES 1 ;Valore da scrivere sui LED H_CONT RES 1 ;Parte alta contatore ritardo L_CONT RES 1 ;Parte bassa contatore ritardo DIREZ RES 1 ;Direzione 0=sinistra 1=destra TX_BYTE RES 1 ;Valore da trasferire serialmente B_CONT RES 1 ;Contatore dei bit da trasmettere #DEFINE PIN_TX PORTB,1 ;Pin di trasmissione dato #DEFINE PIN_CLK PORTB,2 ;Pin di trasmissione clock #DEFINE PIN_STRB PORTB,0 ;Pin di trasmissione strobe https://twitter.com/profbianco Pag. 32

CONTEGGIO DA 0 A 9 SU UN DISPLAY Per avvicinarci ad una forma più umana di dialogo con la macchina vogliamo visualizzare su un comune display a 7 segmenti luminosi il valore contenuto in un registro (compreso tra 0 e 9). Questi display contengono 8 LED comandabili con i pin di uscita del PIC, e generalmente vengono identificati con i nomi visibili nella figura a fianco. Per far apparire una cifra occorre naturalmente accendere la giusta combinazione di diodi. Usando un display a catodo comune lo schema è praticamente identico a quello del primo esperimento di visualizzazione, solo che i LED invece di essere separati sono uniti assieme nel contenitore del display, e tutti i catodi sono collegati assieme in un punto di massa comune (da qui il nome di catodo comune). Le 8 resistenze possono anche essere sostituite da una rete resistiva DIL da 330 ohm che ne contiene 8 racchiuse in un unico contenitore a forma di integrato. Possiamo collegare i pin di uscita ai segmenti nell'ordine che ci pare, ma per avere un certo criterio scegliamo di collegare il segmento A al pin RB0, B al pin RB1, per finire con il punto collegato a RB7. E' evidente che per far apparire la cifra 1 dovranno essere accesi i segmenti B e C, e quindi il valore binario scritto sulla porta dovrà essere 00000110. La seguente tabella riporta i codici da scrivere sulla PORTB per ogni valore che si vuole visualizzare. Valore Segmenti Valore Segmenti.GFEDCBA.GFEDCBA ----------------- ----------------- 0 00111111 5 01101101 1 00000110 6 01111101 2 01011011 7 00000111 3 01001111 8 01111111 4 01100110 9 01101111 https://twitter.com/profbianco Pag. 33