├── .gitignore ├── .vscode └── settings.json ├── README.md └── img ├── RBIF.JPG ├── RBIF.jpg ├── adc.JPG ├── adc.jpg ├── adif.png ├── capture.JPG ├── capture.jpg ├── input_output.jpg ├── instruction_set.png ├── microcontrollori.jpg ├── microcontrolloriadc2.jpg ├── microcontrolloricapturesonar.jpg ├── neumann-harvard-architecture.png ├── port_diagram.png ├── pwm.JPG ├── pwm.jpg ├── timer0.JPG └── timer0.jpg /.gitignore: -------------------------------------------------------------------------------- 1 | workspace* -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "ADCON", 4 | "ADDLW", 5 | "ADFM", 6 | "ADIF", 7 | "ADON", 8 | "ADRES", 9 | "ADRESH", 10 | "ADRESL", 11 | "AIDF", 12 | "ANDWF", 13 | "ANSEL", 14 | "ANSELC", 15 | "BANKSEL", 16 | "BOREN", 17 | "BTFSS", 18 | "CCPL", 19 | "CCPR", 20 | "CCPTMRS", 21 | "CCPXCON", 22 | "CCPXH", 23 | "CCPXIF", 24 | "CCPXL", 25 | "CLEARF", 26 | "CLEARW", 27 | "CLRF", 28 | "COMF", 29 | "DECFSZ", 30 | "EASYPIC", 31 | "FOSC", 32 | "Fmin", 33 | "INCF", 34 | "INDF", 35 | "INTCON", 36 | "IOCB", 37 | "IORWF", 38 | "L", 39 | "L'enable", 40 | "LATB", 41 | "LATC", 42 | "LATD", 43 | "LCD", 44 | "L’uscita", 45 | "MCRL", 46 | "MOVF", 47 | "MOVFW", 48 | "MOVLW", 49 | "MOVWF", 50 | "MPASM", 51 | "MYPORT", 52 | "Mikro", 53 | "PCLATH", 54 | "PCLATU", 55 | "PEIE", 56 | "PIEX", 57 | "PIRX", 58 | "PORTB", 59 | "PORTC", 60 | "PORTD", 61 | "PWRT", 62 | "PWRTE", 63 | "RBIE", 64 | "RBIF", 65 | "RCON", 66 | "SUBFW", 67 | "SUBLW", 68 | "SUBWF", 69 | "SWAPF", 70 | "Squareroot", 71 | "TRISC", 72 | "TRISD", 73 | "TRISE", 74 | "TXCON", 75 | "Tacqt", 76 | "Tacquisition", 77 | "Tmax", 78 | "UDATA", 79 | "VECT", 80 | "WDTE", 81 | "XORWF", 82 | "addwf", 83 | "aiuto", 84 | "all", 85 | "all'assembler", 86 | "all'interrupt", 87 | "all'overflow", 88 | "all’esame", 89 | "altro", 90 | "andlw", 91 | "asmpic", 92 | "complementati", 93 | "d", 94 | "dall'ADC", 95 | "dall'ALU", 96 | "dall'instruction", 97 | "dall'interrupt", 98 | "datasheet", 99 | "dell", 100 | "dell'ADC", 101 | "dell'LCD", 102 | "dell'interrupt", 103 | "dell’LCD", 104 | "dell’impulso", 105 | "dell’interrupt", 106 | "d’ingresso", 107 | "endianness", 108 | "esame", 109 | "importante", 110 | "impulso", 111 | "ingresso", 112 | "interrupt", 113 | "iorlw", 114 | "l", 115 | "l'ALU", 116 | "l'IDE", 117 | "l'IOCB", 118 | "l'LCD", 119 | "l'acquisition", 120 | "l'array", 121 | "l'assembler", 122 | "l'assembly", 123 | "l'interrupt", 124 | "l'opcode", 125 | "l'overflow", 126 | "l’LCD", 127 | "l’aiuto", 128 | "l’altro", 129 | "l’importante", 130 | "l’interrupt", 131 | "microcontrollore", 132 | "microcontrollori", 133 | "microcontrolloriadc", 134 | "microcontrolloricapturesonar", 135 | "nell'A", 136 | "nell'IDE", 137 | "nell'ISR", 138 | "nell'array", 139 | "nell'instruction", 140 | "nell'opcode", 141 | "postscaler", 142 | "prescaler", 143 | "retlw", 144 | "sbit", 145 | "strcat", 146 | "strcpy", 147 | "sull", 148 | "sull'LCD", 149 | "sull’LCD", 150 | "uscita", 151 | "xorlw" 152 | ], 153 | "cSpell.ignoreWords": [ 154 | "clockati", 155 | "dall", 156 | "desempio", 157 | "fsrindf", 158 | "ladc", 159 | "nellaa", 160 | "nput", 161 | "sullutilizzo", 162 | "utput" 163 | ] 164 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pic notes 2 | 3 | Appunti del corso di microcontrollori, Politecnico di Milano, 2020-2021. 4 | 5 | Documento originariamente creato da [Squareroot7](https://github.com/Squareroot7/pic-notes) 6 | 7 | Indice creato con [github-markdown-toc](https://github.com/ekalinin/github-markdown-toc) 8 | 9 | Table of Contents 10 | 11 | ================= 12 | 13 | * [Pic notes](#pic-notes) 14 | * [Numeri binari](#numeri-binari) 15 | * [Endianness](#endianness) 16 | * [Complementi](#complementi) 17 | * [Architettura](#architettura) 18 | * [Architettura di Von Neumann](#architettura-di-von-neumann) 19 | * [Architettura di Harvard](#architettura-di-harvard) 20 | * [Differenze tra le due](#differenze-tra-le-due) 21 | * [ASSEMBLY](#assembly) 22 | * [Operazioni eseguibili dal PIC16](#operazioni-eseguibili-dal-pic16) 23 | * [Memoria e la suddivisione in banchi](#memoria-e-la-suddivisione-in-banchi) 24 | * [Registri importanti](#registri-importanti) 25 | * [Quali istruzioni affliggono lo STATUS register?](#quali-istruzioni-affliggono-lo-status-register) 26 | * [Il program counter (PC)](#il-program-counter-pc) 27 | * [Struttura del codice](#struttura-del-codice) 28 | * [Salti e return in assembly](#salti-e-return-in-assembly) 29 | * [Direttive asm](#direttive-asm) 30 | * [Differenze tra pseudo-istruzioni, macro, direttive](#differenze-tra-pseudo-istruzioni-macro-direttive) 31 | * [Scrivere programmi in pseudo codice](#scrivere-programmi-in-pseudo-codice) 32 | * [Cosa deve essere salvato durante una IRQ (Interrupt request routine)](#cosa-deve-essere-salvato-durante-una-irq-interrupt-request-routine) 33 | * [Stringa di configurazione della CPU](#stringa-di-configurazione-della-cpu) 34 | * [Loop infinito](#loop-infinito) 35 | * [Indirizzamento diretto e indiretto (come utilizzare FSR e INDF)](#indirizzamento-diretto-e-indiretto-come-utilizzare-fsr-e-indf) 36 | * [Codice d'esempio sull'utilizzo di cicli con FSR/INDF](#codice-desempio-sullutilizzo-di-cicli-con-fsrindf) 37 | * [Velocità di esecuzione delle istruzioni](#velocità-di-esecuzione-delle-istruzioni) 38 | * [Esercitazione 1](#esercitazione-1) 39 | * [Perché vengono usati dei condensatori?](#perché-vengono-usati-dei-condensatori) 40 | * [Brown OUT](#brown-out) 41 | * [Latch UP](#latch-up) 42 | * [Oscillatori](#oscillatori) 43 | * [Selezione del banco di memoria](#selezione-del-banco-di-memoria) 44 | * [Interazione con le porte](#interazione-con-le-porte) 45 | * [Esercitazione 2](#esercitazione-2) 46 | * [Interazione con i registri](#interazione-con-i-registri) 47 | * [Esercitazione 3](#esercitazione-3) 48 | * [Esercitazione 4](#esercitazione-4) 49 | * [Esercitazione 5](#esercitazione-5) 50 | * [Indirizzamento indiretto](#indirizzamento-indiretto) 51 | * [TIMER2](#timer2) 52 | * [ADC](#adc) 53 | * [Lezione 1](#lezione-1) 54 | * [GPIO](#gpio) 55 | * [BISOGNA USARE PORT O LAT IN INPUT O OUTPUT?](#bisogna-usare-port-o-lat-in-input-o-output) 56 | * [EASYPIC BOARD SETUP](#easypic-board-setup) 57 | * [Lezione 2](#lezione-2) 58 | * [INTERRUPT](#interrupt) 59 | * [Mismatch nel caso di IOCB](#mismatch-nel-caso-di-iocb) 60 | * [Lezione 3](#lezione-3) 61 | * [TIMER0](#timer0) 62 | * [TIMER1 modalità 16Bit vs 2x8](#timer1-modalità-16bit-vs-2x8) 63 | * [Modalità 2x8](#modalità-2x8) 64 | * [Quando allora questa modalità è comoda da usare?](#quando-allora-questa-modalità-è-comoda-da-usare) 65 | * [Modalità 1x16](#modalità-1x16) 66 | * [LCD](#lcd) 67 | * [Istruzioni per manipolare le stringhe](#istruzioni-per-manipolare-le-stringhe) 68 | * [Lezione 6 - ADC](#lezione-6---adc) 69 | * [Lezione 7 - PWM](#lezione-7---pwm) 70 | * [Argomenti non visti nell'A.A. 2020/2021](#argomenti-non-visti-nellaa-20202021) 71 | * [Lezione 4 - CCP (Sonar)](#lezione-4---ccp-sonar) 72 | * [Cosa succede se TIMER1 va in overflow più volte durante una misura di CCP?](#cosa-succede-se-timer1-va-in-overflow-più-volte-durante-una-misura-di-ccp) 73 | * [Appunti sulle conversioni sonar modalità capture e sonar letto con l'ADC](#appunti-sulle-conversioni-sonar-modalità-capture-e-sonar-letto-con-ladc) 74 | * [SONAR in modalità CAPTURE](#sonar-in-modalità-capture) 75 | * [SONAR letto con ADC](#sonar-letto-con-adc) 76 | 77 | ## Numeri binari 78 | 79 | ### Endianness 80 | 81 | La *Endianness* (ordine dei byte in Italiano) indica in che modo vengono salvati i bit all'interno della memoria di un sistema: 82 | 83 | * **Big endian**: i bit più significativi (MSB) sono scritti **per primi** 84 | * **Small endian**: i bit meno significativi (LSB) sono scritti **per primi** 85 | 86 | La maggior parte dei processori, tra cui i PIC usati in questo corso, usano una architettura di tipo **big endian**. 87 | 88 | ### Complementi 89 | 90 | Sono il metodo più diffuso per rappresentare numeri con segno in informatica. Il segno della somma di due numeri complementati verrà determinato in modo automatico, senza dover prendere particolari accorgimenti. 91 | 92 | * **Complemento ad uno**: si invertono i bit della parola. **Problema:** ci sono due rappresentazioni per il numero 0 (+0 e -0) 93 | * **Complemento a due**: si invertono i bit della parola e poi si somma 1, ignorando il bit di *carry*. **La rappresentazione di 0** è univoca. 94 | 95 | Con `N` bit è possibile rappresentare tutti i numeri da `-(2 ^ (N - 1))` a `2 ^ (N - 1) - 1`. 96 | 97 | Per ottenere l'opposto di un numero in complemento a due, è sufficiente invertire tutti i bit ed aggiungere 1. 98 | 99 | Per ottenere rapidamente il complemento a due di un numero, si procede come segue: 100 | 101 | 1. Partendo da destra (dal LSB), si ricopiano tutti gli `0` 102 | 1. Si procede copiando il primo `1` 103 | 1. Da qua in poi, si invertono tutti i bit fino ad arrivare al MSB 104 | 105 | ## Architettura 106 | 107 | ### Architettura di Von Neumann 108 | 109 | Nell'architettura di Von Neumann, il programma e le istruzioni sono salvate nella stessa memoria. Essa verrà condivisa tra le due entità. 110 | 111 | È stata inventata da *John Von Neumann* nel 1945. 112 | 113 | ### Architettura di Harvard 114 | 115 | A differenza della struttura appena vista, la memoria e le istruzioni sono salvate in parti diverse della memoria. Esistono dei collegamenti (*bus*) che dovranno collegare le varie periferiche che afferiscono a queste due entità. 116 | 117 | Il nome deriva dal computer *Harvard Mark I*, primo sistema a farne uso, costruito nel 1944 nell omonima università. 118 | 119 | ### Differenze tra le due 120 | 121 | | Von Neumann | Harvard | 122 | |-------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------| 123 | | La stessa memoria è usata per dati e istruzioni | Dati e istruzioni sono salvate in memorie diverse | 124 | | Viene usato solo un bus per trasferire sia dati che istruzioni | Vengono usati bus dedicati per i dati e per le istruzioni | 125 | | Servono due colpi di clock per eseguire ogni istruzione, perché la CPU non può accedere contemporaneamente alle due sezioni di memoria (istruzioni e dati) | Ogni istruzione richiede un singolo colpo di clock perché la CPU può accedere a istruzioni e dati contemporaneamente | 126 | | Più usata in personal computer | Più usata in microcontrollori | 127 | 128 | ![confronto tra architetture](img/neumann-harvard-architecture.png) 129 | 130 | ## ASSEMBLY 131 | 132 | ![instruction-set](img/instruction_set.png) 133 | 134 | istruzioni ASSEMBLY del PIC16 135 | 136 | ### Operazioni eseguibili dal PIC16 137 | 138 | A causa della semplicità del microcontrollore, potranno solo essere effettuate operazioni: 139 | 140 | * Algebriche: 141 | * Somma 142 | * Sottrazione 143 | * Shift left/right (quindi moltiplicazione/divisione di un registro per una potenza di due) 144 | * Incremento/decremento di 1 145 | * Complemento 146 | * Logiche: 147 | * AND 148 | * OR 149 | * XOR 150 | * Di salto: 151 | * Incondizionato 152 | * Condizionato 153 | * Ritorno da subroutine 154 | 155 | Non sarà quindi possibile eseguire nativamente operazioni quali moltiplicazioni, sottrazioni, confronti. Bisognerà infatti costruire delle *routine* per poter effettuare questo tipo di operazioni. 156 | 157 | ### Memoria e la suddivisione in banchi 158 | 159 | La memoria del microcontrollore è costituita da dei banchi di memoria in cui si trovano tutti i registri disponibili. Per una questione di indirizzamento, non viene utilizzata un'unica pagina di memoria piena di registri, ma si utilizzano 4 banchi separati. 160 | 161 | Infatti, a causa della limitatezza nella dimensione della memoria (8 bit), non sarebbe possibile accedere a più di 2^8 (256) indirizzi memoria. Dividendo la memoria in banchi da 256 indirizzi e selezionando, all'occorrenza, il banco di memoria necessario tramite alterazione di un altro registro, è possibile lavorare con memorie più ampie (nel caso del PIC16, 1024 indirizzi). 162 | 163 | Come si può vedere, questi banchi contengono dei registri ripetuti più volte. Infatti questi sono i registri più comuni, ed è molto comodo non cambiare banco ogni qual volta si vuole eseguire un'operazione con uno di questi. 164 | 165 | ### Registri importanti 166 | 167 | Alcuni dei registri importanti in un PIC sono: 168 | 169 | * `PROGRAM COUNTER`*(PC)* è il registro che tiene traccia dell'indirizzo dell'istruzione corrente. È a sua volta composto da due registri, `PCL` e `PCH` Normalmente incrementa di 1 ad ogni istruzione, ma può essere manipolato dalle istruzioni di branch. 170 | * `PCL` contiene i bit da 0 7. È mappato in memoria ed è accessibile in lettura o in scrittura 171 | * `PCH` contiene i bit da 8 a 12. Non è accessibile direttamente in lettura o scrittura e deve essere manipolato tramite un meccanismo che coinvolge il registro `PCLATH` 172 | * `PCLATH` è il registro che fornisce i bit necessari all'istruzione di branch per effettuare il salto. I bit `<4:3>` vengono utilizzati nel `PCH` quando viene chiamata una istruzione `GOTO`. 173 | * `W` *(working register)* registro usato come operando nelle operazioni della ALU, sia come sorgente che come destinazione. Può anche essere manipolato direttamente poiché è mappato nella RAM. 174 | * `FSR` contiene l'indirizzo di memoria a cui punta il registro `INDF`. È mappato nella RAM. 175 | * `INDF` è un registro virtuale che punta all'indirizzo contenuto in `FSR`. Può essere manipolato come un normale registro e viene usato per l'indirizzamento indiretto. 176 | * `OPTION` contiene vari bit di controllo per configurare: 177 | * il *pull-up* della porta B (bit 7) 178 | * l'*edge* dell'interrupt sulla porta B (*rising* o *falling*, bit 6) 179 | * la sorgente del clock del TIMER 0 (transizione sul pin RA4/T0CKI o clock interno, bit 5) 180 | * l'*edge* dell'interrupt sul pin RA4/T0CKI B (*rising* o *falling*, bit 4) 181 | * il *prescaler* del TIMER 0 (bit 3) 182 | * il *postscaler* del WATCHDOG (bit 2-0) 183 | * `STATUS` è il registro che può essere definito come il più importante del microcontrollore. Esso contiene i bit usati per: 184 | * l'indirizzamento indiretto (bit 7, `IRP`) 185 | * l'indirizzamento diretto (bit 6-5, `RP1:RP0`) 186 | * segnalare il *TIME OUT* (bit 4) 187 | * segnalare il *POWER DOWN* (bit 3) 188 | * segnalare se l'ultima operazione eseguita dalla ALU (aritmetica o logica) ha risultato zero (bit 2, `Z`) 189 | * segnalare se c'è stato un carry **dal quarto bit in giù (quindi nel secondo nibble)** nell'ultima operazione eseguita dalla ALU (bit 1, `DC`) 190 | * segnalare se c'è stato un carry nell'ultima operazione eseguita dalla ALU (bit 0, `C`) 191 | 192 | #### Quali istruzioni affliggono lo STATUS register? 193 | 194 | * Tutte le operazioni di *addizione* (`ADDWF`, `ADDLW`), *sottrazione* (`SUBWF`, `SUBLW`) affliggono i bit `C` (carry) `DC` (digit carry) `Z` (zero) 195 | * Le *rotate left* e *rotate right* (`RLF`, `RRF`) affliggono il bit `C` (carry) perché hanno bisogno di 1 bit da salvare per lo shift 196 | 197 | * Tutte le operazioni di *incremento* e *decremento* non condizionali affliggono il bit `Z` 198 | * Le operazioni logiche `ANDWF`, `CLEARF`, `CLEARW`, `COMF`, `IORWF`, `MOVF`, `XORWF`, `ANDLW`, `IORLW`, `XORLW` affliggono il bit `Z` 199 | 200 | * Tutte le operazioni rimanenti non affliggono lo `STATUS` (per esempio, lo swap dei nibbles) 201 | 202 | #### Il program counter (PC) 203 | 204 | Il program counter `PC` è un registro che tiene traccia dell'indirizzo di memoria dell'istruzione correntemente eseguita. Ha 13 bit di larghezza: 205 | 206 | * I primi 8 (gli LSB) provengono dal registro `PCL`, leggibile e scrivibile 207 | * Gli altri 3 (i MSB) provengono dal registro `PCLATH`. 208 | 209 | Una istruzione `GOTO` avviene tramite somma di un offset al registro `PCL`. 210 | 211 | Il `PC` è aggiunto alla stack (PUSH) tramite una call ad una subroutine o da un interrupt, mentre viene recuperato dalla stessa (POP) nell'eventualità che vengano chiamate le istruzioni `RETURN` (ritorno da subroutine), `RETLW` (ritorno da tabella), o `RETFIE` (ritorno da interrupt routine). 212 | 213 | Il `PCLATH` non è coinvolto da queste operazioni e quindi non viene mai aggiunto alla stack. 214 | 215 | ### Struttura del codice 216 | 217 | ### Salti e return in assembly 218 | 219 | * `GOTO XXX`: salto assoluto. Aggiorna il PC ad un'etichetta designata. La dimensione dell'indirizzo dell'etichetta è di 11 bit. È sensibile al paging, infatti il bit 12 e 13 vengono letti da `PCLATH`. Quindi sarà necessario usare la direttiva `BANKSEL` oppure modificare il registro `PCH`. Nei PIC18 il PC è a 21 bit assoluti e il `GOTO` è a 20bit; quindi può spostare l'esecuzione ovunque in 2M di memoria (in due cicli), ma c'è il problema del paging con `PCLATU` (non più H) 220 | * `BRA XXX`: salto relativo. Ci si può spostare di massimo ±1023 posizioni dall'indirizzo di partenza. Essendo un salto condizionale partendo dal valore del PC corrente, non è affetto dal paging (non è disponibile nei PIC16). 221 | * `RETFIE`: specifica il return dall'interrupt (quindi sposta quello che c'era nel Top Of Stack nel `PC`) e riattiva il general interrupt `GIE` (che è stato disattivato all'ingresso della routine dell'interrupt). 222 | * `CALL`: va all'etichetta, esattamente come il goto, quindi è affetto da paging (sarà necessario quindi usare la direttiva `BANKSEL`). Salva nello stack il `PC` corrispondente alla posizione di chiamata. Appena la routine chiamata dal `CALL` termina, il `PC` viene riportato all'indirizzo chiamante. 223 | 224 | ### Direttive asm 225 | 226 | * `#include`: stesso utilizzo del C++. Esempio: `#include ` 227 | * `UDATA`: dichiara l'inizio di una sezione di dati non inizializzati. Per riservare lo spazio in questa sezione bisogna utilizzare la direttiva `res`. L'utilizzo è `LABEL res #byte`. Se non vengono specificate label e indirizzo, (es `VARIABLES_IN_BANK udata 0x20`) é sufficiente usare la direttiva `.udata` e il linker provvederà in modo autonomo. 228 | * `BANKSEL XXX`: Serve per selezionare automaticamente il bank in base all'etichetta desiderata. Quindi se si vuole selezionare il registro *TRISB* senza sapere in che banco ci si trova, è sufficiente scrivere `BANKSEL TRISB`. Questo permette di poter riutilizzare *(con le dovute precauzioni della scelta del micro)* lo stesso codice su più micro diversi senza dovermi preoccupare del Bank da selezionare. 229 | * `CODE XXXX`: (indirizzo opzionale) significa che il linker può piazzare il codice nella program memory all'indirizzo specifico XXXX segnalato dall'utente, oppure viene lasciata libera la scelta dell'indirizzo al linker se il parametro non viene specificato. 230 | * `END`: specifica all'assembler che questa è la fine del file asm. Ogni file asm deve necessariamente finire con la direttiva `END`. Se così non fosse, l'assembler continuerebbe a cercare in tutta la memoria istruzioni da eseguire. 231 | * `EQU`: analogo del define del C++. Esempio: `MYPORT equ PORTD` (etichetta eq nome_indirizzo_in_RAM). 232 | 233 | #### Differenze tra pseudo-istruzioni, macro, direttive 234 | 235 | * **Direttiva**: una direttiva assembly è un comando utilizzato a livello software che compare nel source code ma non è direttamente traducibile come opcode. Di conseguenza una direttiva non compare nell'instruction set del datasheet del PIC ma nella User's guide del MPASM™ Assembler. 236 | * **Pseudo-istruzione**: istruzione asm scritta con parole diverse in modo tale da agevolare la memorizzazione. Per esempio, nel PIC16 , c'è `MOVWF`, mentre la sua “complementare” dovrebbe essere `MOVFW`. Nelle istruzioni del datasheet però esiste solo `MOVF, w`. L'assemblatore via software permette di tradurre direttamente `MOVF, w` tramite la pseudo-istruzione `MOVFW`, in modo tale da avere meno confusione nel codice e migliore memorizzazione. 237 | * **Macro**: può essere considerata come “un'istruzione” a livello di sviluppo software, ma in realtà è **un gruppo di istruzioni unificate sotto un unico nome**. A differenza delle funzioni in linguaggio C, la macro è una sostituzione **in-line**. Questo significa che non viene effettuata una call, non viene cambiato il PC, non viene allocato spazio nello stack. Il contenuto della macro viene semplicemente inserito in quel punto del programma. Per maggiori informazioni, leggere la User's guide del MPASM™ Assembler. 238 | 239 | ### Scrivere programmi in pseudo codice 240 | 241 | Un processo alla base della scrittura di algoritmi per i neofiti della programmazione è lo sviluppo iniziale in pseudo-codice. Pur essendo all'apparenza *"troppo base e scontato"*, questo è un ottimo metodo sia per formalizzare l'algoritmo, sia in fase di debug per ricontrollare codice! 242 | 243 | Per esempio, si consideri il caso seguente. 244 | 245 | Si vogliono comparare due variabili `B` e `A`, ponendo come condizione vera quando `B` è maggiore di `A`. In linguaggio C il problema risulterebbe triviale e risolvibile con una sola istruzione> `if (B > A)`. 246 | Tuttavia, nelle istruzioni del PIC16 non è contemplato il confronto diretto tra due registri, come visto prima. 247 | 248 | Alla luce di queste considerazioni, si osservi lo pseudo codice seguente: 249 | 250 | ``` pseudocode 251 | prendi risultato 252 | carica B su risultato 253 | sottrai A a risultato 254 | ... 255 | se risultato è negativo 256 | ->B è minore di A 257 | ... 258 | se risultato è positivo 259 | ->B è maggiore di A 260 | ``` 261 | 262 | L'ultimo passo rimanente sarà convertire questo listato in un linguaggio che possa essere compreso dal compilatore e tradotto in codice macchina, l'assembly. Ci sono due criticità non emerse fin'ora: 263 | 264 | 1. È necessario avere un'ulteriore variabile contenente il risultato 265 | 266 | * Sarà necessario usare working register (`W`) 267 | 268 | 1. È necessario controllare il segno del risultato" 269 | 270 | * Il registro `STATUS` contiene il bit carry `C`, che verrà portato a 1 nel caso in cui l'operazione restituisca una risultato negativo 271 | 272 | Sarà dunque possibile scrivere il codice: 273 | 274 | ``` asm 275 | MOVFW B; sposto B nel working register 276 | SUBFW A; sottraggo A al working register 277 | BANKSEL STATUS; vado a selezionare il bank in cui sta STATUS 278 | BTFSS STATUS, c; Se B-A<0 ALLORA salta la prossima istruzione 279 | GOTO B_MAGGIORE; se il bit test non non salta allora B è maggiore 280 | GOTO B_MINORE; se il bit test salta allora B è minore 281 | ``` 282 | 283 | In generale, *è consigliato ragionare cercando di dividere ogni passo in problemi più piccoli, sino ad arrivare ad una soluzione facilmente implementabile in Assembly.* 284 | 285 | È estremamente consigliato affrontare questo processo soprattutto nel momento in cui ci si affaccia alla programmazione in Assembly. Man mano che si prosegue sarà sempre più naturale pensare direttamente alla soluzione senza dover passare da step intermedi. 286 | 287 | ### Cosa deve essere salvato durante una IRQ (Interrupt request routine) 288 | 289 | Devono essere salvati: 290 | 291 | * Registro *W* 292 | * Registro *FSR* 293 | * Registro *PCLATH* 294 | * Registro *STATUS* 295 | 296 | in un registro ad uso generico nel banco attualmente selezionato. 297 | 298 | Esempio di codice: 299 | 300 | ``` asm 301 | MOVWF W_TEMP; Copia il contenuto di W nel registro TEMP, nel banco 1 o 0 302 | SWAPF STATUS, W; Salva il registro status in W 303 | CLRF STATUS; indipendentemente dalla pagina, cancella IRP, RP1, RP0 nel banco 0 304 | MOVWF STATUS_TEMP; Salva il registro status nel registro STATUS TEMP nel banco 0 305 | 306 | MOVF PCLATH, W; Necessario nel caso si usi il banco 1, 2 o 3 307 | MOVWF PCLATH_TEMP; Salva PCLATH in W 308 | CLRF PCLATH; Pagina zero, indipendentemente dalla pagina attuale 309 | 310 | BCF STATUS, IRP; Ritorna al banco 0 311 | MOVF FSR, W; Copia FSR in W 312 | MOVWF FSR_TEMP; Copia FSR da W a FSR_TEMP 313 | 314 | : ; abbiamo salvato tutto quanto 315 | : ; qua viene scritta la ISR 316 | : ; dopo la routine di interrupt andiamo a ricaricare i salvataggi 317 | 318 | MOVF PCLATH_TEMP, W; Recupera PCLATH 319 | MOVWF PCLATH; Muove W in PCLATH 320 | SWAPF STATUS_TEMP, W; Scambia il registro STATUS_TEMP con W 321 | ; (resetta il banco allo stato originario) 322 | 323 | MOVWF STATUS; Sposta W nel registro STATUS 324 | 325 | SWAPF W_TEMP, F; Inverte W_TEMP con F 326 | SWAPF W_TEMP, W; Inverte W_TEMP con W 327 | ``` 328 | 329 | ### Stringa di configurazione della CPU 330 | 331 | * `_CONFIG`: parola inizio configurazione 332 | * `_FOSC_XT`: selezione oscillatore (XT: cristallo) 333 | * `_WDTE_OFF`: watch dog (inattivo) 334 | * `_PWRTE_ON`: delay per alimentazione stabile uC all'accensione (acceso) 335 | * `_CP_OFF`: code protection (inattivo) 336 | * `_BOREN_ON`: brown out detect (attivo). Se la tensione di alimentazione scende al di sotto di una determinata soglia, si verifica ciò che viene chiamato *brown out*. Per evitare l'esecuzione di istruzioni errate all'interno del microcontrollore, il *circuito brown out* resetta e lo tiene inattivo per tutta la durata di questo evento. 337 | 338 | ### Loop infinito 339 | 340 | Per scrivere correttamente un programma su un microcontrollore, bisogna considerare che non esiste un modo di avviare a piacimento le sotto funzioni. Quindi, ogni programma dovrà contenere un loop infinito, all'interno del quale verranno scritte le istruzioni che caratterizzeranno il comportamento del microcontrollore stesso. 341 | 342 | Questo codice contiene l'inizio del file asm che deve essere sempre inserito: 343 | 344 | ``` asm 345 | _CONFIG _FOSC_XT & _PWRTE_ON & _CP_OFF & _BOREN_ON 346 | GENERATOR 347 | RES_VECT CODE 0x0000; processor reset vector 348 | GOTO START; go to beginning of program 349 | 350 | ; TODO ADD INTERRUPTS HERE IF USED 351 | 352 | MAIN_PROG CODE; let linker place main program 353 | START 354 | GOTO $ ; loop forever 355 | END 356 | ``` 357 | 358 | ### Indirizzamento diretto e indiretto (come utilizzare FSR e INDF) 359 | 360 | 1. **Diretto**: l'indirizzo della RAM è direttamente contenuto nell'opcode, quindi l'indirizzo viene dall'*instruction register* (che contiene l'opcode appena ricavato dal `PC`). 361 | 1. **Indiretto**: l'indirizzo della RAM è già contenuto nel `FSR` (File Select Register), di conseguenza l'opcode non deve contenere direttamente l'indirizzo nell'ISR ma il suo `indice`, esattamente come avverrebbe con un puntatore. 362 | 363 | L'indirizzamento indiretto può essere utilizzato per non dover riscrivere più volte lo stesso codice per indirizzi diversi. Ad esempio, al posto di `clear indirizzo1; clear indirizzo2; clear indirizzo3;`, basta incrementare il `FSR` e ripetere la stessa routine il numero necessario di volte. 364 | 365 | * Poiché solo i primi 8 bit (gli LSB) sono indirizzati dal `FSR`, per indirizzare locazioni di memoria successive bisogna fare uso del bit `IRP` del registro `STATUS`. 366 | * Il bit `IRP`, unitamente al bit 8 del `FSR` funzioneranno quindi da bank selector (come `RP1` ed `RP0`). 367 | * Con `IRP = 0` si selezioneranno i banchi 0 ed 1, con `IRP = 1` si selezioneranno i banchi 2 e 3. 368 | 369 | #### Codice d'esempio sull'utilizzo di cicli con FSR/INDF 370 | 371 | Esempio di uso con indirizzamento diretto 372 | 373 | ``` asm 374 | CLRF 20h; cancella i dati all'indirizzo 20h 375 | CLRF 21h; cancella i dati all'indirizzo 21h 376 | : 377 | : 378 | CLRF 30h; cancella i dati all'indirizzo 30h 379 | ``` 380 | 381 | Come è possibile ottimizzare questa lunga sezione di codice? Tramite l'utilizzo dei registri `FSR` e `INDF` sfruttando quindi l'indirizzamento indiretto. 382 | 383 | ``` asm 384 | MOVLW 0x20; Carico l'indirizzo di partenza in W 385 | MOVWF FSR; lo sposto nel FSR 386 | NEXT 387 | EXT CLRF INDF; pulisco il registro puntato utilizzando INDF 388 | INCF FSR, f; incremento FSR 389 | BTFSS FSR, 5; il bit 5 è a 1? Allora sono passato a 3xh (in cui la x sta per una cifra qualsiasi) quindi nel nostro caso 30h 390 | GOTO NEXT; se non siamo ancora a 30h continua il loop - nota che questa istruzione non viene eseguita se la precedente istruzione è vera 391 | ``` 392 | 393 | ### Velocità di esecuzione delle istruzioni 394 | 395 | A causa dell'architettura del microcontrollore, una esecuzione richiede 8 colpi di clock per essere eseguita dall'inizio alla fine. Infatti, ognuna di essere sarà scomposta nella fase di *fetch* e in quella di *execute*, ciascuna richiedente 4 colpi di clock per essere completata. 396 | 397 | Grazie alla struttura a pipeline, tuttavia, ogni 4 colpi di clock verrà eseguita una istruzione (tranne per le operazioni di salto che ne richiederanno 8). In contemporanea verranno eseguite la fase di *execute* di una funzione con la fase di *fetch* della successiva. 398 | 399 | Poiché le istruzioni di branch possono dare origine a dei *data faults* (ovvero possono richiedere alla cpu delle informazioni che non sono ancora state processate dalla fase di *execute*), viene forzata una istruzione `NOP` in seguito alle stesse, obbligando un *flush* della memoria. 400 | 401 | ## Esercitazione 1 402 | 403 | ### Perché vengono usati dei condensatori? 404 | 405 | In un circuito ideale, le connessioni tra componenti avvengono tramite cavi che non presentano alcuna opposizione al passaggio di corrente. Nella realtà, tutta via, ciò è ovviamente falso. 406 | 407 | Infatti, in ogni collegamento si vengono a creare degli effetti *induttivi* causati da componenti parassiti che risiedono all'interno degli stessi dispositivi. Per cercare in qualche modo di limitare questi disturbi nefasti, si impiegano dei condensatori. 408 | Essi infatti smussano la curva della tensione, soprattutto durante le commutazioni. Ricordando infatti che, per un induttore, maggiore è la variazione di tensione ai suoi capi, maggiore sarà la corrente da lui iniettata nel circuito, si giunge facilmente alla conclusione che riducendo gli sbalzi di tensione si ridurranno anche le correnti parassite. 409 | 410 | Introdurre un condensatore in parallelo al microcontrollore ridurrà anche il *ripple* al suo ingresso. 411 | 412 | ### Brown OUT 413 | 414 | Causato da un *glitch* nell'alimentazione, normalmente di breve durata, che temporaneamente oscilla attorno al suo valore di riferimento fino al di sotto della tensione di alimentazione del microcontrollore. Questo meccanismo può dare origine a malfunzionamenti nel software (reset del microcontrollore, modifica del contenuto della memoria). 415 | 416 | Si innesca quindi il processo di *brown out reset (BOR)*, con i seguenti effetti: 417 | 418 | * Viene attivato il timer `PWRT` (power up timer) 419 | * Il programma sul PIC viene resettato, ricominciando dall'indirizzo puntato dal *reset vector* 420 | * Viene sospesa la funzione `SLEEP` per tutta la durata del *BOR* 421 | * Vengono re inizializzati i registri speciali (`SFR`, *special function register*), cioè 422 | 423 | * `STATUS` 424 | * `OPTION_REG` 425 | * `PCL` e `PCLATH` 426 | * `INTCON` 427 | 428 | Quindi il microcontrollore viene effettivamente riportato allo stato iniziale, immediatamente successivo all'accesione. 429 | 430 | ### Latch UP 431 | 432 | Situazione che si verifica quando abbastanza corrente scorre attraverso il bulk di un transistor *cMOS*, causando una caduta di tensione sufficiente da attivare uno dei *BJT* parassiti che si vanno a creare a cavallo delle zone *n+* e *p+*. È anche causata, seppur più raramente, da scariche elettrostatiche (ESD) o da radiazione ionizzante. 433 | 434 | ### Oscillatori 435 | 436 | Tipi di oscillatori esterni: 437 | 438 | * `LP` - low power 439 | * `XT` - crystal resonator - necessita di due condensatori collegati in parallelo 440 | * `HS` - high speed crystal resonator 441 | * `RC` - resistor capacitor - sfrutta un circuito *RC* passivo collegato al pin `OSC1` 442 | 443 | Vengono collegati tra i pin `OSC1` ed `OSC2` del microcontrollore. 444 | 445 | ### Selezione del banco di memoria 446 | 447 | Per accedere ad uno dei quattro banchi di memoria del microcontrollore, bisogna agire sui bit 6 e 7 (`RP0` e `RP1`) del registro `STATUS`. 448 | 449 | ### Interazione con le porte 450 | 451 | Per predisporre lo stato della porta (*INPUT* o *OUTPUT*), bisogna prima agire sul registro `TRISx` con le istruzioni apposite (rispettivamente `BSF` e `BCF`) il relativo pin. Impostando ad 1 il valore del bit la porta funzionerà in input, mentre impostandolo a 0 la porta funzionerà in output. *Regola mnemonica:* 1 **I**nput, 0 **O**utput. 452 | 453 | Successivamente si agisce sull'uscita effettiva della porta, sempre usando le due istruzioni (`BSF` e `BCF`), sul registro `PORTx` sul pin coinvolto nell'istruzione. 454 | 455 | Prima di tutto, sarà necessario verificare di essere sul banco di memoria corretto. 456 | 457 | * I registri `PORTx` sono sul banco 0, quindi `STATUS,RP0 = 0`, `STATUS,RP1 = 0` 458 | * I registri `TRISx` sono sul banco 1, quindi `STATUS,RP0 = 1`, `STATUS,RP1 = 0` 459 | 460 | Esempio di codice che accende un led collegato al pin `RB0`: 461 | 462 | ``` asm 463 | BSF STATUS,RP0 ; sposta sul banco 1 di memoria (contenente) il registro TRISB 464 | BCF STATUS,RP1 ; 465 | BCF TRISB,0 ; setta il pin 0 della porta B come output 466 | BCF STATUS,RP0 ; torna al banco 1 di memoria, contenente il registro PORTB 467 | BCF STATUS,RP1 ; 468 | BSF PORTB,0 ; porta ad 1 (HIGH) il l'uscita sul pin 469 | ``` 470 | 471 | ## Esercitazione 2 472 | 473 | ### Interazione con i registri 474 | 475 | **Nota** con `f` e `d` si indicano due registri generici (quelli indicati come *general purpose*) 476 | 477 | Operazioni con i registri: 478 | 479 | * Per caricare un valore nel registro `W` (il *working register*) si usa l'istruzione `MOVLW k` (il valore k verrà messo nel registro `W`). 480 | * Per spostare un valore dal registro `W` ad un qualsiasi altro registro, si usa l'istruzione `MOVWF f` (il valore contenuto nel registro W verrà spostato all'interno del registro f). 481 | * Per cancellare il contenuto del registro `f`, si usa l'istruzione `CLRF f` 482 | 483 | Istruzioni usate: 484 | 485 | * `MOVF f,d` copia il contenuto del registro f nel registro d. Questa istruzione influenza il bit `Z` del registro `STATUS`. Difatti, se il risultato è zero, il bit verrà settato ad 1 (e viceversa). 486 | * `SWAPF f,d` inverte i due nibble di d e li salva in f. 487 | * `ANDWF f,d` effettua l'operazione di and bit a bit tra il registro W ed il registro f e salva il risultato nel registro d. Se questo risultato è zero, il bit `Z` del registro `STATUS` viene portato ad 1. 488 | * `ADDWF f,d` somma il contenuto di f al contenuto di w e salva il risultato in d. Ciò influenzerà i bit `Z`, `DC`, `C` del registro `STATUS`. 489 | * `DECFSZ f,d` decrementa f e salta l'istruzione successiva se f diventa uguale a 0. d conterrà il valore f-1. 490 | 491 | ## Esercitazione 3 492 | 493 | La direttiva `BANKSEL label` permette di selezionare il banco di memoria in cui l'etichetta *label* è definita. Non è possibile eseguire operazioni su *label* e deve essere stata definita in precedenza. 494 | 495 | Per attivare l'interrupt bisogna prima abilitare la `IRQ` sulla porta desiderata settando i bit `RxIE` e `GIE` (che influenzano la `IRQ` rispettivamente sulla porta X e in modo generale) presenti nel registro `INTCON` della memoria. 496 | 497 | Esempio: 498 | 499 | ``` asm 500 | BSF INTCON,RBIE ; attiva la IRQ sulla porta B 501 | BSF INTCON,GIR ; attiva la IRQ su tutto il dispositivo 502 | 503 | ``` 504 | 505 | All'interno dell'interrupt routine bisognerà disattivare la `IRQ` su tutto il microcontrollore in modo da evitare l'interruzione della stessa a seguito di un altro interrupt. Inoltre, bisogna sempre leggere il valore sulla porta che ha scatenato l'interrupt in modo da poterla disabilitare e resettare il bit `RBIF`. 506 | Infine, per ritornare alla routine principale si usa l'istruzione `RETFIE`. 507 | 508 | Per abilitare i pull-up interni (nelle porte supportate) bisogna abbassare il pin 7 del registro `OPTION_REG` nel banco 1. 509 | 510 | ## Esercitazione 4 511 | 512 | Durante la routine di interrupt bisogna salvare tutti quei registri che potrebbero venire modificati all'interno della stessa. In generale, possono essere i registri: 513 | 514 | * `W`, working register 515 | * `STATUS`, il registro di stato 516 | * `FSR`, il registro di indirizzamento indiretto 517 | 518 | **Nota:** disattivare sempre la `IRQ` all'inzio della routine e di riattivarla alla fine. 519 | 520 | ## Esercitazione 5 521 | 522 | ### Indirizzamento indiretto 523 | 524 | Il registro `PCL` contiene gli LSB del *program counter* (da 7 a 0). Gli MSB sono contenuti nel registro `PCLATH` (bit da 12 a 8). 525 | 526 | Per indirizzare in modo indiretto, 2 bit (MSB) provengono dal registro `STATUS` e 7 bit (LSB) dall'istruzione. È possibile indirizzare 512 indirizzi. 527 | 528 | ### TIMER2 529 | 530 | I registri coinvolti per usare il `TIMER2` in modalità PWM sono: 531 | 532 | 1. `CCP1CON`: bit da 5 a 4 gli MSB del PWM, bit da 3 a 0 la modalità di funzionamento (es, capture falling edge, capture rising edge, compare set on match...) 533 | 1. `TRISC2`: per selezionare lo stato del pin (ingresso, uscita...) 534 | 1. `PR2`: per impostare il valore massimo del timer 535 | 1. `T2CON`: bit da 6 a 3 per impostare il prescaler, bit 2 per abilitare la periferica, bit da 1 a 0 per impostare il postscaler 536 | 537 | ### ADC 538 | 539 | I registri coinvolti sono: 540 | 541 | 1. `ADCON0`, `ADCON1`: impostano il comportamento analogico del PIN, abilitando l'ADC 542 | 1. `ADRES`: per impostare la risoluzione dell'ADC 543 | 544 | ## Lezione 1 545 | 546 | ### GPIO 547 | 548 | ![Input-Output](img/input_output.jpg) 549 | 550 | **Nota** Tutti i pin, di default, sono impostati in *input*. Quando si imposta un pin a output, è necessario disattivare il convertitore analogico/digitale (*ADC*) tramite il registro `ANSELx`. 551 | 552 | | Uso | TRIS | ANSEL | 553 | |:------------|:--------------|:---------------| 554 | | Digital Out | TRISx.pin = 0 | don't care | 555 | | Digital In | TRISx.pin = 1 | ANSELx.pin = 0 | 556 | | Analog In | TRISx.pin = 1 | ANSELx.pin = 1 | 557 | 558 | **Nota** se dei pin non vengono definitivamente utilizzati nel progetto, vengono impostati come Output e poi il registro `LATx` viene impostato a 0. Questa operazione previene switch (dovuti a cariche parassite accumulate) della porta che comportano consumo dovuto alla potenza dinamica di `*switching* dell'ingresso. 559 | 560 | Ogni porta ha diversi registri: 561 | 562 | | Registro | 0 = | 1 = | Al reset | 563 | |:---------|:--------------------------|:---------------------------|:-----------------------| 564 | | TRISx | 0 = Output | 1 = Input | Input | 565 | | PORTx | 0 = Reading low | 1 = Reading high | Undefined | 566 | | LATx | 0 = Writing low | 1 = Writing high | Undefined | 567 | | ANSELx | 0 = Digital buffer **ON** | 1 = Digital buffer **OFF** | Digital buffer **OFF** | 568 | 569 | Significato dei registri: 570 | 571 | * `TRISx` indica la direzione (input o output) del pin 572 | * `PORTx` buffer di lettura e scrittura 573 | * `LATx` buffer di scrittura 574 | * `ANSELx` indica il comportamento analogico del pin 575 | 576 | #### BISOGNA USARE PORT O LAT IN INPUT O OUTPUT? 577 | 578 | * **OUTPUT**: `LATx` e `PORTx` effettuano la stessa identica operazione di scrittura. **Tuttavia è preferibile usare il registro** `LATx`. 579 | * **INPUT**: `PORTx` rappresenta lo stato fisico del pin, mentre `LATx` rappresenta il registro che pilota il data latch. 580 | 581 | `Dove possono sorgere dei problemi?` Prendendo, per esempio `RB1` come digital output, è possibile svolgere tutte le operazioni di I/O con `PORTB.RB1` oppure `LATB.RB1` (non fa differenza). 582 | Cambiando `RB1` da digital output a digital input, si supponga che `LATB.RB1=1` sia lo stato finale. Un bottone esterno impone ora il pin fisico con stato a 0. Usando `PORTB.RB1`, si legge che effettivamente il pin è a zero. Consultando invece `LATB.RB1` risulta che il pin è a 1. 583 | 584 | Di conseguenza, quale dei due registri va usato? 585 | 586 | * In *INPUT*, usare sempre e solo `PORT` in *READ*. 587 | * In *OUTPUT*, usare `PORT` e `LAT` in *WRITE*, è indifferente, anche se è preferibile usare il registro `LAT`. 588 | 589 | #### EASYPIC BOARD SETUP 590 | 591 | Uso delle porte (in relazione al corso): 592 | 593 | * `PORTA`: usata per aggiungere pulsanti e per fare polling. `RA7`-`RA7` Connessi all'oscillatore. 594 | * `PORTB`: quasi interamente dedicata al LCD. `IOCB` disponibile da RB7-RB4, LCD connesso da `RB5`-`RB0`. **NOTA**: `RB6` e `RB7` non sono utilizzabili contemporaneamente al debugger. 595 | * `PORTC`: poco spesso usata per altri scopi se non per il modulo sonar. 596 | * `PORTD`: spesso usata per operazioni con i led (tutta la porta). `RD1` ha la funzione PWM. 597 | * `PORTE`: usata per un PWM, PWM software o per dei led di segnalazione. `RE2` ha la funzione PWM, `RE3` MCRL (reset esterno). 598 | 599 | ## Lezione 2 600 | 601 | ### INTERRUPT 602 | 603 | Gli interrupt sono degli strumenti estremamente efficienti per gestire eventi asincroni interni o esterni senza dover alterare la normale esecuzione del programma. 604 | 605 | Esempi di eventi asincroni: 606 | 607 | * **Interni**: fine conversione ADC, overflow del TIMER ... 608 | * **Esterni**: cambi di stato di un pin, ricezione di dati via SPI ... 609 | 610 | Tramite gli interrupt è possibile assegnare priorità differenti a ciascun processo che può avvenire simultaneamente, in modo da gestire temporalmente le varie funzioni. 611 | 612 | Esistono due priorità di interrupt: 613 | 614 | * A **bassa priorità**, *LP* 615 | * Ad **alta priorità**, *HP* 616 | 617 | L'interrupt è gestito salvando lo stato attuale e recuperando l'indirizzo a cui punta la routine di interrupt. L'esecuzione del programma principale può essere interrotta sia da interrupt *HP* ed *LP*, mentre le routine di interrupt possono essere interrotte solo da interrupt di priorità più alta. 618 | 619 | Ci sono 2 registri da usare per gestire l'interrupt: 620 | 621 | 1. `IE` (interrupt enable), serve ad attivare globalmente l'interrupt. Se è riferita all'intero sistema e non ad un singolo componente, si chiama anche `GIE` (global interrupt enable) 622 | 1. `IF` (interrupt flag), serve a capire quale entità ha scatenato l'interrupt 623 | 624 | Dopodiché è necessario definire una `ISR` (interrupt service routine), cioè la funzione che verrà chiamata quando l'interrupt viene causato. Essa deve essere quanto più leggera e veloce possibile, poiché una ISR di durata considerevole può creare problemi alla corretta esecuzione del programma principale. 625 | 626 | L'intero processo di interrupt può essere quindi diviso in due fasi: 627 | 628 | 1. **Inizializzazione** 629 | 630 | 1. Si attiva la periferica che causerà l'interrupt 631 | 1. Si reimposta la `IF` 632 | 1. Si abilita la `GIE` 633 | 634 | 1. **ISR** 635 | 636 | 1. Si controlla la `IF` per capire che periferica ha scatenato l'interrupt 637 | 1. Si resetta la relativa `IF` 638 | 1. Si scrive il codice che deve essere eseguito 639 | 640 | Successivamente, ogni periferica in grado di scatenare interrupt avrà bisogno di essere abilitata agendo sul suo registro specifico. 641 | 642 | Registri relativi all'interrupt: 643 | 644 | * `INTCON`, `INTCON2`, `INTCON3` interrupt control register 645 | * `PIR1`, `PIR2`, `PIR3`, `PIR4`, `PIR5` peripheral interrupt registers 646 | * `PIE1`, `PIE2`, `PIE3`, `PIE4`, `PIE5` peripheral interrupt enable registers 647 | * `IPR1`, `IPR2`, `IPR3`, `IPR4`, `IPR5` interrupt priority registers 648 | * `RCON` reset control register 649 | * `IOCB` interrupt on change port B register 650 | 651 | **NOTA** controllare sempre prima il datasheet per sapere con esattezza quali registri manipolare. Quattro dei pin della porta b (dal 7 al 4) sono configurabili come interrupt-on-change tramite I bit del IOCB. Per poter disattivare il bit **RBIF** è necessario prima leggere il valore sulla porta. 652 | 653 | ### Mismatch nel caso di IOCB 654 | 655 | ![RBIF](img/RBIF.jpg) 656 | 657 | Si osservi, come sia possibile entrare due volte nella stessa `ISR`, nel caso di un *IOCB*, (*interrupt on change on port B*). Si analizzi la seguente situazione: 658 | 659 | 1. Un bottone viene premuto, quindi portando ad `1` il valore letto su `PORTB` 660 | 1. La porta `XOR` darà in output il valore `1`, scatenando l'interrupt e impostando il bit `RBIF` 661 | 1. L'esecuzione si interrompe nella funzione main e inizia l'esecuzione della `ISR` 662 | 663 | Reimpostando immediatamente il valore di `RBIF` a zero senza prima leggere il valore di `PORTB`, la porta `XOR` rimarrà ad `1`, quindi `RBIF` verrà automaticamente riportato ad `1`. 664 | Poiché il valore dell'uscita `Q` del *flip-flop* non è stato alterato (non è ancora avvenuta la lettura della porta) mentre il pulsante è ancora premuto, si verificherà una situazione cosiddetta di *mismatch* e la porta `XOR` continuerà a fornire `1` in uscita, dando origine immediatamente ad una seconda esecuzione della `ISR`. A questo punto, reimpostando `RBIF` a zero, la porta `XOR` non avrà più `1` in uscita poiché il valore è stato resettato dalla prima esecuzione della `ISR` (nel momento della lettura di `PORTB`). 665 | 666 | *Quindi avverranno due esecuzioni consecutive della* `ISR`. 667 | 668 | Per ovviare a questa problematica, bisognerà leggere il valore della porta prima ancora di reimpostare il valore di `RBIF`. Si vedano di seguito due esempi di `ISR`, uno corretto ed uno no: 669 | 670 | ``` C 671 | // ******ISR SCORRETTO****** 672 | void interrupt() { 673 | if (INTCON.RBIF) // flag alzata da PORTB 674 | INTCON.RBIF = 0; // reset della flag 675 | // a questo punto accade il mismatch 676 | // non ho aggiornato PORTB prima di abbassare la flag 677 | if (PORTB.RB6) 678 | i++; // leggo PORTB quindi alla ISR dopo 679 | if (PORTB.RB) 680 | i--; // avrò aggiornato il flip flop 681 | } 682 | 683 | // ******ISR CORRETTO****** 684 | void interrupt() { 685 | if(INTCON.RBIF){ // flag alzata da PORTB 686 | if(PORTB.RB6) 687 | i++; // leggo PORTB -> Flip Flop aggiornato! 688 | if(PORTB.RB) 689 | i--; // altro refresh di PORTB 690 | INTCON.RBIF = 0; //Reset della flag 691 | // a questo punto ho PORTB aggiornato -> NO mismatch 692 | // non entro nella ISR una seconda volta 693 | } 694 | ``` 695 | 696 | ## Lezione 3 697 | 698 | ### TIMER0 699 | 700 | ![timer0](img/timer0.jpg) 701 | 702 | Il TIMER0 è un contatore che subisce un *overflow* (passaggio da valore massimo a zero) dopo un tempo definito dall'utente attraverso l'impostazione dei registri dedicati. 703 | 704 | Inizialmente si seleziona la sorgente dell'oscillazione (interno o esterno). Mediante un multiplexer è possibile selezionare la frequenza di incremento del contatore. La sua frequenza tipica è Fosc/4 in cui, grazie al PLL, Fosc è 32MHz (8MHz di default). 705 | 706 | Il pin `T0CKI` pin *(che durante il corso non verrà mai usato)* è il pin per comunicare con l'esterno che se abilitato come sorgente fornisce un impulso *edge sensitive* in base alla impostazione selezionata con `T0SE` (incremento sul rising/falling edge). 707 | 708 | Successivamente alla selezione della sorgente è necessaria la scelta dell'abilitazione del prescaler, divisore di frequenza. Il modo più facile per costruire un prescaler è utilizzare un contatore. Quest'ultimo quando supera una certa soglia fornisce in uscita un impulso. Può essere impostato solo a potenze di 2, da 4 a 256 (massimo prescaler -> massimo tempo -> minima frequenza). 709 | 710 | L'ultima sezione, tralasciando il *sync clock*, indica semplicemente che devono passare due colpi di clock per aggiornare il timer. Ci ritroviamo infine con il registro `TMR0L` (registro *low* del timer 0). È un registro a 8 bit, i meno significativi del registro di timer. Questo registro è un contatore che imposta la *flag* dell'interrupt `TMR0IF` quando va in overflow. Ad interrupt disabilitato, può anche essere usato come contatore. 711 | 712 | Attraverso gli overflow del TIMER0, quindi attraverso la routine di interrupt legata al flag del timer, è possibile gestire periodicamente delle sottosezioni di codice. 713 | 714 | La frequenza di interrupt è frequenza d'ingresso ( quindi `Fosc/4`) diviso il prescaler che va da 1 (prescaler non attivo) a 256. 715 | 716 | La frequenza di *clock* della scheda è `Fosc=32MHz`. La frequenza di interrupt minima (che corrisponde quindi al massimo periodo tra due interrupt consecutivi), è: 717 | 718 | ```Fmin = Fosc / ( 4 * 256 * ( 256 - TMR0L ))``` 719 | 720 | Bisogna tenere conto del valore di `TMR0L` (il valore iniziale contenuto nel registro). Impostando `TMR0L=0`, si ha la certezza che il timer parta sempre da zero. 721 | 722 | Se si desiderasse una frequenza di interrupt diversa da una potenza di 2, all'interno della routine di interrupt sarà necessario impostare manualmente il valore di `TMR0L`. 723 | 724 | Il periodo dell'interrupt si ottiene semplicemente invertendo la formula: 725 | 726 | ```Tmax = ( 4 * 256 * ( 256 - TMR0L ) ) / Fosc``` 727 | 728 | Valore tipico `4/32MHz = 1/8MHz = 125ns`. Il valore massimo del periodo di interrupt è 8.192 ms *(all'esame, 8ms è una approssimazione più che sufficiente)*. Se si volesse avere più precisione ci sono 2 opzioni: 729 | 730 | * Salvare il valore del tempo di interrupt utilizzando un `long int` e tenendo, per esempio, in conto che ogni interrupt avviene ogni 8192ms: 731 | 1. Scegliere il prescaler 732 | 1. Modulare TMR0L in modo che dia un numero intero, sempre con l'aiuto del prescaler. Per esempio, se `TMR0L=6` allora il periodo è precisamente 8 millisecondi. Questa impostazione deve essere inserita sempre all'interno della routine di interrupt e nelle prime righe di setup del codice così da avere: 733 | 1. il valore iniziale impostato correttamente all'accensione del microcontrollore 734 | 1. il valore iniziale impostato correttamente al termine della routine di interrupt 735 | 736 | ### TIMER1 modalità 16Bit vs 2x8 737 | 738 | **Nota** Solo i timer di indice pari possono funzionare a 16bit (`TIMER2`, `TIMER4`). 739 | 740 | Osservando il datasheet si nota che all'interno del registro `TxCON` è presente un bit (`TxRD16`) che fa riferimento a **modalità 16bit o 2x8**. 741 | 742 | È certamente possibile utilizzare i timer di indice dispari a 8 bit, ma è necessaria una delle seguenti operazioni: 743 | 744 | 1. Usare solo gli 8 bit più significativi del registro in modo tale da poter comunque sfruttare l'overflow una volta pieno il timer. *(Il registro è da 16 bit)* 745 | 1. Sfruttare la *modalità 2x8* 746 | 747 | #### Modalità 2x8 748 | 749 | Questa è la modalità più semplice per usare il timer. `TMRx` (registro da 16 bit) è semplicemente diviso in `TMrxH` e `TMRxL` (registri da 8 bit). La lettura viene fatta un bit alla vota e il flag di interrupt viene alzato all'overflow di `TMRxH`. 750 | 751 | Questa modalità però presenta dei problemi sostanziali, che potrebbero portare a errori nel programma. 752 | 753 | Si consideri questa situazione: 754 | 755 | 1. Con `TMR1` acceso, `TMR1H = 0x00`, `TMR1L = 0xFF` 756 | 1. Si procede con la lettura di `TMR1L` e salva il dato in una variabile. Il risultato sarà pari a `0x00FF` (a 16 bit) 757 | 1. A questo si legge il valore di `TMR1H`, ma il timer non è stato spento ed è andato avanti nel conteggio. Quindi `TMR1L = 0x00`, `TMR1H = 0x01` 758 | 1. Infine, leggendo il byte successivo e il risultato sarà pari a `0x00FF + 0x0100 = 0x01FF`, *valore diverso da quello atteso*, `0x00FF` 759 | 760 | Lo stesso problema si verifica nel caso opposto: 761 | 762 | 1. Si supponga, sempre con `TMR1` acceso, che `TMR1 = 0x00FF` 763 | 1. Il valore in `TMR1H` sarà `TMR1H = 0x0000` 764 | 1. Il timer continuerà a contare, e quindi `TMR1 = 0x0100` 765 | 1. In `TMR1L` ci sarà un valore `0x0000`, diverso da quello aspettato (`0x00FF`). 766 | 767 | Sostanzialmente si verifica sempre una discrepanza tra il valore atteso all'interno del timer e la somma dei valori letti nei due registri a cause del tempo necessario a raccogliere i valori contenuti in questi ultimi. 768 | 769 | ##### Quando allora questa modalità è comoda da usare? 770 | 771 | * Quando non interessa il contenuto del timer ma solo l'overflow 772 | * Quando interessa un solo byte del timer, sia esso H o L (utile, ad esempio, in un **PWM software**. Vedi gli appunti più avanti) 773 | 774 | Per correggere questo problema è stato introdotta l'altra modalità, chiamata **1x16**. 775 | 776 | #### Modalità 1x16 777 | 778 | La modalità *1x16* viene chiamata ufficialmente *16Bit Read/Write* dal datasheet. In sostanza viene introdotto un buffer in lettura su `TMR1H`. 779 | 780 | Il byte alto del timer (da qua in poi chiamato `TMR1H:`) a questo punto non sarà più direttamente controllabile o leggibile, ma si potrà accedere solamente al buffer. 781 | 782 | Il buffer verrà copiato quando il registro `TMR1L` viene manipolato. Quindi, per utilizzare questa modalità bisogna considerare le seguenti operazioni: 783 | 784 | * Lettura di `TMR1L` 785 | 1. Contemporaneamente `TMR1H:` verrà copiato in `TMR1H` 786 | 1. La lettura del byte alto `TMR1H` sarà congruo allo stato del timer nel momento della lettura di `TMR1L` e non soffrirà del ritardo dovuto al tempo passato tra le istruzioni 787 | * Scrittura di `TMR1L` 788 | 1. Contemporaneamente `TMR1H` verrà copiato in `TMR1H:` 789 | 1. Così facendo il valore del byte alto `TMR1H` sarà congruo allo stato del timer nel momento della scrittura di `TMR1L` e non soffrirà del ritardo dovuto al tempo passato tra le istruzioni 790 | 791 | Problemi relativi a questa modalità: 792 | 793 | 1. Se interessasse solamente il valore contenuto in `TMR1H` si deve per forza leggere `TMR1L`, impiegando più istruzioni 794 | 1. Bisogna rispettare sempre l'ordine di lettura o scrittura (prima `TMR1L` e poi `MR1H`). Invertendo le due operazioni si otterrebbero valori non validi e quindi potenzialmente un malfunzionamento del software. 795 | 796 | ### LCD 797 | 798 | Lo schermo LCD è composto da 2 righe e 16 colonne. 799 | 800 | È importante capire che per comunicare con questo display esiste un protocollo apposito, altrimenti sarebbe necessario comunicare direttamente con la memoria interna dell'LCD. Per aggirare questa difficoltà è stata creata una libreria che è ad un “livello di astrazione più alto” rispetto alla manipolazione diretta dell'LCD. 801 | 802 | Per comandare la memoria del dispositivo LCD bisogna rispettare tempi precisi, quindi nella libreria ci dovrà essere un sistema che genera queste temporizzazioni non interessante al programmatore finale. Quando verrà scritta una stringa sull'LCD, questa funzione della libreria impiega parecchio tempo (circa 1000 cicli macchina): scrivere sull'LCD non sarà quindi una operazione immediata come una somma. 803 | 804 | Come fanno a generarsi dei ritardi fissi? Con una funzione come il `delay_ms()`. Si noti che usare `delay_ms()` vuol dire sapere con sicurezza la frequenza di clock del microcontrollore. Per questo sarà necessario impostare in un altro modo l'LCD se volessimo gestirlo ad una frequenza diversa dalla 32MHz (alla quale viene gestito durante lo svolgimento del corso). 805 | 806 | Si ponga particolare attenzione anche allo switch pin 6 (dallo schema e che dovrebbe essere già settato hardware giusto). Il display LCD è collegato alla porta B, sui primi 6 pin della porta B per la precisione, dalla 0 alla 5. 807 | 808 | Rimangono liberi `RB6` e `RB7` Se il display è collegato da `RB0` a `RB5`, questi pin non possono essere usati per altro se non per l'LCD. L'`IOCB` (*Interrupt On Change port B*) funziona da `RB7` al `RB4`. Ma se si volesse usare strumenti come *In Circuit Debugger*, anche `RB6` e `RB7` risulteranno fuori uso. *Per questo motivo l'IOCB non lo verrà più usato nei prossimi laboratori, ma ci si concentrerà sulle altre porte.* 809 | 810 | Codice usato ogni volta per impostare l'LCD *(in esame viene fornita, non è necessario imparala a memoria)*: 811 | 812 | ``` C 813 | // Lcd module connections 814 | sbit LCD_RS at LATB4_bit; 815 | sbit LCD_EN at LATB5_bit; 816 | sbit LCD_D4 at LATB0_bit; 817 | sbit LCD_D5 at LATB1_bit; 818 | sbit LCD_D6 at LATB2_bit; 819 | sbit LCD_D7 at LATB3_bit; 820 | 821 | sbit LCD_RS_Direction at TRISB4_bit; 822 | sbit LCD_EN_Direction at TRISB5_bit; 823 | sbit LCD_D4_Direction at TRISB0_bit; 824 | sbit LCD_D5_Direction at TRISB1_bit; 825 | sbit LCD_D6_Direction at TRISB2_bit; 826 | sbit LCD_D7_Direction at TRISB3_bit; 827 | // End Lcd module connections 828 | 829 | void main(){ 830 | Lcd_init(); 831 | Lcd_Cmd(_LCD_CLEAR); 832 | Lcd_Cmd(_LCD_CURSOR_OFF); 833 | 834 | // main code here.... 835 | } 836 | ``` 837 | 838 | **NOTA** Ricordare di attivare la libreria LCD e la parte di conversione delle char nell sezione delle librerie nell'IDE MikroC. 839 | 840 | Il comando principale è ``Lcd_Out(riga, colonna, stringa)``. Attenzione che le stringhe, in C sono, un semplice *array di char*. Quindi, una volta dichiarata la dimensione, essa non è modificabile in runtime e che *l'indice di riga e di colonna partono da 1 e non da 0*. 841 | 842 | È quindi importante ragionare sulla dimensione di questo array, che risulta essere un puntatore alla prima cella della stringa. Una ulteriore criticità nasce dal fatto che in C non esiste nessun metodo a runtime per conoscere la lunghezza di un vettore. 843 | 844 | Quando viene dichiarata una stringa in C bisogna usare per forza quello che si chiama *carattere di terminazione* `\0` e indica dove la stessa finisce. Quando viene passata una stringa come variabile, il compilatore legge fino a `\0` e poi si ferma. Passando una stringa senza carattere terminatore, infatti, il microprocessore continuerebbe a leggere anche oltre la terminazione di memoria che non appartiene più alla variabile, potenzialmente creando un *segmentation fault*. 845 | 846 | In generale, bisogna inizializzare l'array a `lunghezza stringa + 1`, proprio per la presenza del carattere terminatore. In particolare, un numero a virgola con segno (un *float*, spesso usato in esercitazioni ed esami) necessita di 7 caratteri: 847 | 848 | * 5 per rappresentare il numero (da -32768 a 32767) 849 | * 1 per il segno (+ o -) 850 | * 1 per il carattere terminatore `\0` 851 | 852 | #### Istruzioni per manipolare le stringhe 853 | 854 | * `strcpy(dest, source)` copia una stringa in un altra. 855 | * `source` può essere anche una costante. Ad esempio: `strcpy(txt, "testo")` copierà "testo" nell'array di char `txt` 856 | * `IntToStr(int, dest)` trasforma un intero in una stringa. 857 | * `strcat(str1, str2)` concatena `str1` e `str2` e salva il risultato in `str1` 858 | 859 | ## Lezione 6 - ADC 860 | 861 | ![adc](img/adc.jpg) 862 | 863 | L'ADC ha due modalità di funzionamento: a 8 oppure 10 bit 864 | I suoi registri da impostare sono `ADCON0`, `ADCON1`, `ADCON2`. 865 | 866 | * `ADCON0` serve a determinare attraverso i bit da 6 a 2 il pin scelto del microcontrollore per la conversione. Inoltre il bit `ADCON0.ADC_GO_NOT_DONE` fa partire la conversione se alto. Quando la conversione è completata torna a zero. Il bit `ADCON0.ADON` abilita l'ADC e deve essere impostato a 1. 867 | * `ADCON1` è invece il registro in cui vengono specificate le tensioni di alimentazioni. Durante lo svolgimento del corso, esso sarà alimentato 0/+5V e quindi semplicemente `ADCON1 = 0`. 868 | * `ADCON2` invece contiene il bit 7 (`ADFM`) che è legato alla giustificazione (dove vengono salvati i bit più significativi e dove i meno significativi). L'ADC sfrutta due registri (`ADRESH` e `ADRESL`) in cui verranno salvati i 10 bit risultanti dalla conversione. Esso contiene anche la selezione del numero di `Tad` (vedi sotto) e la frequenza che comanda l'`ADC`. 869 | 870 | Usando l'*ADC* con risoluzione ad 8bit, non è importante dove sia posta la giustificazione, basta infatti ricordare di accedere al registro corretto per leggere il valore convertito. Esso sarà: 871 | 872 | * `ADRESH` se `ADFM = 0` 873 | * `ADRESL` se `ADFM = 1` 874 | 875 | Lavorando invece con risoluzione pari a 10 bit, bisognerà considerare i due registri. Infatti, il valore finale sarà: 876 | 877 | * `ADRESH << 2 + ADRESL >> 6` se `ADFM = 0` 878 | * `ADRESH << 6 + ADRESL` se `ADFM = 1` 879 | 880 | ![adif](img/adif.png) 881 | 882 | I bit da 5 a 3 di `ADCON2` permettono di impostare il tempo di acquisizione (tempo di delay dopo che il condensatore del S&H viene bloccato, scandito in numero di Tad). 883 | Nel caso venga portato a 0, appena viene impostato il bit `GO` dell'*ADC* esso partirà immediatamente con la conversione, la cui lunghezza verrà data da `Tad`. 884 | 885 | Se il tempo di acquisizione è zero o il `Tad` è troppo corto, la conversione non andrà a buon fine. Di conseguenza, sconsigliatissimo tenere il tempo di acquisizione a zero. 886 | 887 | Di solito si imposta ad un valore sia almeno pari a 7us. Quindi, considerando `Tad` pari ad 1us, è possibile impostare il tempo di acquisizione a 16 volte `Tad`. Il datasheet riporta che servono almeno 11 `Tad` per avere una conversione riuscita. 888 | 889 | Selezionare il prescaler per il modulo *ADC* è fondamentale per avere una temporizzazione corretta. È anche possibile usare un lock esterno. 890 | 891 | `Tacqt` è il tempo in cui il *S&H* è ancora agganciato al pin del PIC e quindi il condensatore è ancora libero di caricarsi prima che intervenga `Tad` per iniziare l'acquisizione del valore. 892 | 893 | Impostare sempre bene i registri relativi alla porta, `ANSELx = 1` (per abilitare l'*ADC* sulla porta) e `TRISx = 1` (per impostare la porta come input). 894 | 895 | `AIDF`, il flag relativo all'interrupt scatenato dall'*ADC*, verrà settato alla fine di ogni conversione a prescindere dall'interrupt. 896 | 897 | Esempio di configurazione dell'*ADC*: 898 | 899 | ``` C 900 | // set TRIS here if needed 901 | ANSELC = 0b00001000; // RC3 --> AN15 902 | ADCON2 = 0b10101110; 903 | // +---------Right justified 904 | // +++-----Tacquisition = 12Tad 905 | // +++--Fosc/64 906 | ADCON1 = 0b00000000; // Use all internal voltage supply range 907 | ADCON0 = 0b00111101; // DO NOT SET ADC.GO_NOT_DONE HERE 908 | // +++++----RA15 909 | // +--ADC ON 910 | PIR1 = 0; // reset all flags (adc,ccp,tmr1 ecc) 911 | PIE1.ADIE = 1; // enable ADC interrupt 912 | INTCON = 0b11000000; // enable GIE and PIE 913 | ADCON0.GO_NOT_DONE = 1; // Start ADC Acquisition 914 | ``` 915 | 916 | ## Lezione 7 - PWM 917 | 918 | ![pwm](img/pwm.jpg) 919 | 920 | Il *PWM* è un modulo che permette di generare un'onda quadra con duty cycle variabile. Viene spesso usato per alimentare a diverse potenze un carico. Il PWM ha due comparatori: *HIGH* e *LOW*. 921 | Il primo resetta l'output, mentre il comparatore sopra lo imposta. 922 | 923 | Un problema costruttivo di questa scheda è legato al fatto che il PIC lavora ad 8 bit ma il *PWM* che necessita di 10 bit per funzionare. 924 | È stato possibile ottenere 1024 valori di quantizzazione (quindi 10 bit) in nel seguente modo: 925 | 926 | 1. Dal registro `CCPRxL` provengono gli 8 bit più significativi (MSB) 927 | 1. Dal registro `CCPxCON` provengono gli altri 2 bit meno significativi (LSB) 928 | 929 | La frequenza massima di lavoro è `Fosc / 4`, quindi per coordinare i due bit del timer c'è un contatore dedicato che lavora ad una frequenza pari a quella di clock (`Fosc`). Per motivi legati al coordinamento tra gli MSB e gli LSB del timer, il prescaler è impostabile unicamente a 1, 4 o 16. 930 | 931 | Esempio: impostando `PRx=5`. `CCPRxL=2`, si supponga `TMRx = 0` e l'uscita è *bassa*. Il timer conta e arriva a 5 (valore di PR). Allora, in questo momento: 932 | 933 | 1. Il comparatore *LOW* commuta 934 | 1. L'uscita viene portata ad *alto* 935 | 1. `CCPRxL` viene copiato in in `CCPRxH` 936 | 1. `TMRx` viene resettato 937 | 938 | Quindi, con l'uscita alta, `TMRx` riparte a contare, fino ad arrivare a 2 (valore impostato in `CCPRxH`). Allora: 939 | 940 | 1. Il comparatore *HIGH* commuta 941 | 1. L'uscita viene portata a *basso* 942 | 1. `TMRx` riprende a contare finché non arriva a 5 943 | 1. Il ciclo riparte da capo. 944 | 945 | Agendo sul prescaler, è possibile influenzare la frequenza dell'onda quadra. Infatti aumentando `PRx` (quindi il valore del prescaler), la frequenza di commutazione diminuisce. 946 | 947 | La risoluzione del *CCP* viene impostata dal registro `PRx` (*nota* con `PR = 5` si avranno solo 5 passi modificabili). 948 | 949 | La risoluzione massima con prescaler a 1 è pari a `fosc/4`. Di conseguenza ogni passo è 125ns con una frequenza di oscillazione di 32Mhz. Se il prescaler fosse impostato a 16, allora il periodo del *PWM* sarebbe `125ns * 16 = 2us` e il corrispondente duty cycle sarà pari a `CCPRxL/PRx`. 950 | 951 | Per il *PWM*, il PIC offre solo due moduli moduli *CCP* (4/5) che ne permettono l'utilizzo. Nel caso fosse necessario pilotare più di due pin in questo modo, sarà necessario impostare un PWM software. Di seguito è riportato un esempio di questo codice. 952 | 953 | ``` C 954 | unsigned short int pwm_cnt = 0; 955 | unsigned int time_cnt1 = 0, time_set1 = 100; 956 | void main() 957 | { 958 | unsigned short int pwm_period = 255; // set del periodo del pwm 959 | hi_period = 5; // set della parte HI del pwm ( 1 < hi_period < pwm_period ) 960 | TRISD = 0; // imposto la porta D come output 961 | LATD = 0; // porto tutto a zero 962 | T0CON = 0b11000000; // attiva timer, attivalo a 8 bit e attivalo a 256 prescaler 963 | INTCON = 0b10100000; // attiva timer 0 interrupt 964 | while (1) 965 | { 966 | if (pwm_cnt <= hi_period) 967 | LATD.RD0 = 1; // se sono ancora nella parte alta del pwm 968 | else 969 | LATD.RD0 = 0; // inverto del pin verso la parte bassa 970 | if (pwm_cnt == pwm_period) 971 | pwm_cnt = 0; // reset del periodo pwm 972 | if (time_cnt1 == time_set1) 973 | { // cambio del duty cycle 974 | time_cnt1 = 0; 975 | hi_period++; // incrementa duty cycle 976 | if (hi_period == pwm_period) 977 | hi_period = 0; // se il periodo hi raggiunge il periodo completo, torna a zero 978 | } 979 | } 980 | } 981 | 982 | void interrupt() 983 | { // ISR 984 | if (INTCON.TMR0IF) 985 | { // con questo prescaler, entra circa ogni 8ms 986 | pwm_cnt++; // incrementa contatore del periodo del pwm 987 | time_cnt1++; // incrementa contatore del ciclo di cambio duty cycle 988 | INTCON.TMR0IF = 0; // reimpostare i flag sempre alla fine del codice 989 | } 990 | } 991 | ``` 992 | 993 | Se invece fosse necessario usare entrambi i moduli *PWM*, l'uscita avverrebbe su `RE2` e `RD1`. Di seguito, è riportato un esempio di codice che implementa questa funzione tramite `TIMER2` e `TIMER4`. Da notare che si possono associare ambedue i moduli allo stesso timer. 994 | 995 | ``` C 996 | void main() 997 | { 998 | unsigned short int dir = 1; 999 | // vedi pinout table nel datasheet per vedere a quale pin viene associato quale CCP 1000 | TRISE.RE2 = 1; // DISATTIVO output per impostare ccp5 1001 | TRISD.RD1 = 1; // DISATTIVO output per impostare ccp4 1002 | 1003 | T2CON = 0b00000100; 1004 | // +++++-----postscaler (lascia sempre 0) 1005 | // +----timer on (sempre a 1 per accendere il timer) 1006 | // ++--prescaler (se c'è bisogno di cambio frequenza, cambiare prescaler) 1007 | PR2 = 255; // periodo pwm 1008 | //----TMR4---- 1009 | T4CON = 0b00000110; 1010 | PR4 = 255; 1011 | //----CCP MODULES SETUP---- 1012 | CCP4CON = 0B00001100; 1013 | // ++----impostare QUESTI DUE PER PWM MODE, TUTTI GLI ALTRI BIT A ZERO 1014 | CCP5CON = 0x0F; //settato bit0/1 HIGH perché non vengono usati 1015 | 1016 | //dobbiamo configurare quale timer abbiamo associato al nostro CCP. Setta timer2 in ccptmrs 1017 | CCPTMRS1 = 0b00000001; //associo timer2 a CCP55, associo timer4 a CCP4 1018 | 1019 | CCPR5L = 64; // un quarto di duty cycle 1020 | CCPR4L = 64; // un quarto di duty cycle 1021 | 1022 | TRISE.RE2 = 0; // resetto i pin in uscita per attivare il pwm 1023 | TRISD.RD1 = 0; 1024 | 1025 | while (1) 1026 | { // fade in e out dei led 1027 | if (dir == 0) 1028 | { 1029 | CCPR5L++; 1030 | CCPR4L++; 1031 | } 1032 | else 1033 | { 1034 | CCPR5L--; 1035 | CCPR4L--; 1036 | } 1037 | if (CCPR5L == 255) 1038 | dir = 1; 1039 | else if (CCPR5L == 0) 1040 | dir = 0; 1041 | delay_ms(4); 1042 | } 1043 | } 1044 | ``` 1045 | 1046 | ## Argomenti non visti nell'A.A. 2020/2021 1047 | 1048 | ### Lezione 4 - CCP (Sonar) 1049 | 1050 | ![capture](img/capture.jpg) 1051 | 1052 | Utilizzo del sonar. Quando si intende utilizzare il sonar, le cose importanti da ricordare sono principalmente: 1053 | 1054 | 1. Il sonar è collegato a **PORTC**. Se il sonar viene utilizzato in modalità **Pulse Width Output**, devo impostare **RC2 digital Input** (ANSELC=0 TRISC=1); sennò uso **RC3** come **Analog Input** -> è necessario usare l'**ADC**. 1055 | 2. Il sonar si appoggia al Timer **TXCON** change sovrascrive i dati nel registro del capture **CCPXCON** da 16 bit, suddiviso nei registri **CCPXH** e **CCPXL** (8 bit), high e low rispettivamente. I timer dispari vengono usati **Capture and Compare** mentre per i timer pari vengono usati per il **PWM**. Si imposta il timer con il registro **CCPTMRS0**. 1056 | 3. È una periferica del uC, quindi per attivarne l'interrupt **INTCON.PEIE=1** (non dimenticare il general **INTCON.GIE=1**) 1057 | 4. L'attivazione degli interrupt non è solo legata al registro **INTCON** ma anche ai registri **PIEX** e **PIRX**. Bisogna cercare il numero corretto del registro a cui sostituire la X. **Esistono cinque registri per gli interrupt**. 1058 | 5. Se vuoi lo stream continuo dei dati imposta **LATC.RC6=1**, inoltre deve essere digital Output, quindi TRISC del bit 6 è alto sempre; Gli altri bit si possono mettere benissimo in modalità Input, in particolare **RC2** o **RC3** (digital/analog) 1059 | 6. Non dimenticare la routine di interrupt che è sempre identica: 1060 | 1061 | ``` C 1062 | if (PIR1.CCP1IF) { 1063 | if (CCP1CON.CCP1M0) { // If we are sensing the Rising-Edge 1064 | ta = ( CCPR1H << 8 ) + CCPR1L; // merge 8 and 8 bit in 16 bit 1065 | CCP1CON.CCP1M0 = 0; // Set Sense to Falling 1066 | } else { // If we are sensing the Falling-Edge 1067 | tb = ( CCPR1H << 8 ) + CCPR1L; 1068 | width = tb - ta; 1069 | CCP1CON.CCP1M0 = 1; // Set Sense to Rising 1070 | } 1071 | PIR1.CCP1IF = 0; 1072 | } 1073 | ``` 1074 | 1075 | Il sonar viene utilizzato per misurare le distanze lanciando un segnale ad ultrasuoni e contando il tempo che impiega a tornare. 1076 | 1077 | Il sonar funziona con la modalità **Pulse Width Output** : genera un impulso proporzionale alla distanza misurata. Infatti 1mm equivale a 1us di "larghezza". 1078 | 1079 | Analizziamo il sonar: 1080 | **Pin 2 --> RC2** dove c'è l'uscita dell'impulso collegato in ampiezza 1081 | **Pin 4 --> RC6** serve a dire che il sensore effettua la misura ogni volta che trova il rising edge (se alto allora la misura è continua) 1082 | 1083 | Passano 100mS tra ogni misura. Dal datasheet leggiamo 1mm di risoluzione, minima distanza misurata 300 mm. 1084 | 1085 | Il nostro PIC ci offre a disposizione un modulo che, senza usare cicli macchina ci permette di far partire le misurazioni in automatico mentre noi svolgiamo le altre operazioni: **il modulo CCP** . 1086 | **CCP = Capture Compare PWM**. 1087 | **Esistono 5 moduli CCP in totale collegati a tre pin**, non tutti gli I/O possono essere usati per questa funzione. 1088 | **Il modulo capture si appoggia ad un timer (TMR1/3/5)** e lo utilizza in modalità 16 bit. Quando dal pin esterno riceve un rising/falling edge, lui va a campionare il valore del timer su un registro che noi possiamo andare a leggere. All'arrivo dell'evento campiona e salva il valore del timer in un registro. 1089 | 1090 | Nel nostro caso abbiamo **TMR1** utilizzato in free running a **fosc/4**. Il contatore del timer non fa altro, ovviamente, che sommare al tempo più uno. 1091 | 1092 | Bisogna utilizzare l'interrupt del CAPTURE. Ogni volta che cambio rising e falling (modalità per acquisire la distanza percorsa dal segnale), rischio che scatti un interrupt -> **Nota disabilita l'interrupt del TMR1** 1093 | 1094 | TMR1 è un contatore da 16 bit, **prima o poi andrà in overflow** . 1095 | E se la seconda misura venisse presa dopo un overflow? Come si fa? Analizziamo (esempio dell'orologio): 1096 | 1097 | Immaginiamo un orologio con due lancette A e B. A è prima di mezzo giorno, la seconda è dopo mezzogiorno. La distanza tra le lancette è la stessa ma B raffigura un overflow del timer perché ha superato mezzo giorno. 1098 | **Questo caso viene magicamente risolto dall'ALU**: i designer del micro avevano previsto la possibilità di ovf. Quindi, sapendo che l'ALU fa le somme con il complemento a due (CPL2), l' ALU fa diventare il secondo operando negativo e poi somma i due operandi. Però così facendo ho bisogno di 17 bit. L'ALU conserva quest'ultimo nel bit di carry (vedi lo status). 1099 | 1100 | Unità di misura del timer fosc/4, in tempo ogni colpo avviene ogni 125 ns (sapendo che fosc= 32MHz). 1101 | dt= timer*125ns 1102 | In un microsecondo ci stanno circa 8 volte 125 nanosecondi. Usiamo adeguatamente lo shift per ottenere la misura corretta. 1103 | 1104 | **Vediamo ora un esempio** dell'utilizzo del Sonar con l'LCD: 1105 | 1106 | ``` C 1107 | // Lcd module connections 1108 | sbit LCD_RS at LATB4_bit; 1109 | sbit LCD_EN at LATB5_bit; 1110 | sbit LCD_D4 at LATB0_bit; 1111 | sbit LCD_D5 at LATB1_bit; 1112 | sbit LCD_D6 at LATB2_bit; 1113 | sbit LCD_D7 at LATB3_bit; 1114 | 1115 | sbit LCD_RS_Direction at TRISB4_bit; 1116 | sbit LCD_EN_Direction at TRISB5_bit; 1117 | sbit LCD_D4_Direction at TRISB0_bit; 1118 | sbit LCD_D5_Direction at TRISB1_bit; 1119 | sbit LCD_D6_Direction at TRISB2_bit; 1120 | sbit LCD_D7_Direction at TRISB3_bit; 1121 | // End Lcd module connections 1122 | 1123 | // fosc/4= 125ns quindi nel registro devo fare 1124 | // 1mm è 1us, in 1us ci stanno 8 volte 125ns, quindi uso uno shift 1125 | // delta tempo= differenza contatore * 125ns 1126 | 1127 | // VARIABILI GLOBALI 1128 | unsigned int t_rise=0, t_diff=0; 1129 | 1130 | void main() { 1131 | char txt_num[7], txt_lcd[17]; 1132 | //-----PORTS INIT----- 1133 | ANSELC= 0b11111011; // setto l'input da leggere dove il sonar mi dà l'impulso 1134 | TRISC= 0b10111111; // impostare l'uscita per dire al sonar di continuare a leggere 1135 | LATC.RC6=1; // imposto l'uscita alta 1136 | 1137 | //-----CONFIG TIMER 1----- 1138 | T1CON= 0b00000011; // pagina 166 impostato fosc/4, senza prescaler, senza secondo osc dedicato, timer abilitato e scrittura LOW/HIGH abilitata 1139 | 1140 | //-----CONFIG CCP1----- 1141 | CCP1CON= 0b00000101; // pagina 198 100 capture mode falling edge, 101 rising edge, basta fare ++ e -- nella isr per cambiare modalità 1142 | CCPTMRS0= 0x0; // pagina 201 capture compare mode using TMR1 1143 | 1144 | //-----INTERRUPT CONFIG----- 1145 | INTCON= 0b11000000; // pagina 109 global e peripheral abilitati 1146 | PIR1= 0x0; // pagina 112 CONTIENE flag del CCP1 1147 | PIE1= 0b00000100; // pagina 117 abilitato interrupt CCP1 1148 | //-----LCD INIT ----- 1149 | Lcd_init(); 1150 | Lcd_Cmd(_LCD_CLEAR); 1151 | Lcd_Cmd(_LCD_CURSOR_OFF); 1152 | Lcd_Out(1, 1, "hello world"); 1153 | 1154 | while(1){ 1155 | if(t_diff != 0){ 1156 | t_diff = 0; //resetta conta temporale 1157 | IntToStr(t_diff, txt_num); //converti distanza in stringa 1158 | strcpy(txt_lcd, "dist = "); //inserisci dist = nella stringa txt_lcd 1159 | strcat(txt_lcd, txt_num); //unisci la stringa txt_num nella stringa txt_lcd 1160 | Lcd_Out(1, 1, txt_lcd); //stampa stringa txt_lcd 1161 | } 1162 | } 1163 | } 1164 | void interrupt(){ 1165 | if(PIR1.CCP1IF){ //se c'è stato un interrupt ccp 1166 | if(CCP1CON.CCP1M0){ //if rising 1167 | t_rise = (CCPR1H<<8) + CCPR1L; //inserisci i due registri nel tempo di salita 1168 | CCP1CON=0b00000100; //switch ccp mode to falling 1169 | } 1170 | else{ 1171 | t_diff = (CCPR1H<<8) + CCPR1L; // inserisci i due registri nel tempo di discesa 1172 | t_diff -= t_rise; // calcolo il delta tempo (se fa ovf non c'è problema per la storia del Complemento a due e non mi interesso del bit ovf) 1173 | t_diff= t_diff>>3; // delta tempo= differenza contatore * 125ns che sarà anche la nostra distanza in mm 1174 | CCP1CON=0b00000101; // switch ccp mode to rising 1175 | } 1176 | PIR1.CCP1IF=0; // disattivo flag interrupt 1177 | } 1178 | } 1179 | ``` 1180 | 1181 | #### Cosa succede se TIMER1 va in overflow più volte durante una misura di CCP? 1182 | 1183 | Di certo questo non è il caso del Sonar dato che il **tempo di overflow del TIMER1 è molto maggiore di quello del gradino in uscita dal sonar**. 1184 | Il caso di un overflow singolo è già stato trattato e, da come sappiamo, viene **"risolto" completamente dall'ALU**. 1185 | Quello che non viene gestito dall'ALU è invece un **overflow multiplo durante la misura**. 1186 | Infatti se mantenessimo il sistema come per il SONAR, la misura temporale finale sarebbe **errata**. Vediamo perché succede questo: 1187 | Se non tenessimo conto del numero di volte di overflow del **TIMER1** la misura finale corrisponderebbe a due intervalli: 1188 | 1189 | 1. Tempo passato dal **primo rising edge** al **primo overflow** 1190 | 2. Tempo dall'**ultimo overflow** al **falling edge** 1191 | 1192 | Tutto ciò, da come possiamo immaginare, **non tiene assolutamente conto degli ovf in mezzo**. Come possiamo risolvere questo caso? 1193 | È necessario innanzitutto **contare gli ovf di TIMER1** quindi nella ISR avremo qualcosa simile a: 1194 | 1195 | ``` C 1196 | void interrupt(){ 1197 | if (PIR1.TMR1IF) { 1198 | // inserire condizione che abilita ovf_number++ quando ho intercettato il rising edge 1199 | ovf_number++; 1200 | } 1201 | ... 1202 | // inserire interrupt CCP 1203 | } 1204 | ``` 1205 | 1206 | Bene, ora abbiamo nella variabile ``` ovf_number ``` il nostro numero di overflow. **Dobbiamo aggiungere questo tempo alla conta finale**. Nella routine dell'interrupt CCP dovremo modificare ```ta``` e ```tb``` in modo tale da avere: 1207 | 1208 | ```ta = (((CCPRxH<<8) + CCPRxL ) + ovf_number<<16);``` 1209 | Ovviamente al momento dell'altro fronte dovremo fare la stessa operazione con la variabile ```tb``` : 1210 | ```tb = (((CCPRxH<<8) + CCPRxL ) + ovf_number<<16);``` 1211 | 1212 | Da notare che la variabile ```ovf_number``` al primo aggiornamento (quello di ```ta```) , rispetto al secondo aggiornamento (quello di ```tb```), **sarà stata incrementata in caso di ovf**. 1213 | **Worst case scenario:** anche ```ovf_number``` va in overflow! Basta porla uguale a zero dopo aver fatto l'aggiornamento di tb, così **si escludono tutti gli ovf** avvenuti nel frattempo. 1214 | 1215 | **Nota le parentesi sono importanti**. 1216 | Quando si eseguono operazioni di shift e somma che potrebbero essere fatte in più righe di codice come: 1217 | 1218 | ``` C 1219 | ta = CCPRxH << 8; //ta è a 24 bit; 1220 | ta += CCPRxL; 1221 | ta += ovf_number << 16; 1222 | ``` 1223 | 1224 | è buona regola essere abbondanti di parentesi per **essere** sicuri che il compilatore stia effettivamente facendo le operazioni desiderate e che non stia mescolando operazioni portando a errori che spesso e volentieri sono **ostici da identificare**. 1225 | 1226 | **Nota attenzione alla dimensione delle variabili**. 1227 | Se non trattassimo gli ov allora la variabile ```ta``` sarà (intuitivamente) di 16 bit (8 bit di CCPRxL + 8 bit CCPRxH). L'aggiunta della conta degli ovf però corrisponde ad una variabile di 8 bit con uno shift di 16 -> **sono necessari 24 bit totali** 1228 | A seconda del compilatore, ```ta``` dovrà essere dichiarata come uint oppure ```unsigned long int``` (spesso 32 bit). 1229 | 1230 | ### Appunti sulle conversioni sonar modalità capture e sonar letto con l'ADC 1231 | 1232 | #### SONAR in modalità CAPTURE 1233 | 1234 | ![microcontrolloricapturesonar](img/microcontrolloricapturesonar.jpg) 1235 | 1236 | Premesse legate al datasheet del sonar e al datasheet del PIC 1237 | 1238 | Il sonar in modalità capture viene visto sulla porta RC2 come input digitale. 1239 | 1240 | L'interrupt del capture è PIRX.CCPXIF. 1241 | 1242 | L'enable si trova nel PIEx registro. 1243 | 1244 | Supponiamo di usare il CCP1CON, registro capture relativo al timer 1. 1245 | 1246 | **Come mai il risultato della conversione va diviso per 8?** `width = width >> 3` 1247 | 1248 | La nostra frequenza è 32MHz. Noi lavoriamo alla f del capture che è `fosc / 4 = 8 MHz`. 1249 | 1250 | La pendenza in modalità capture è `1mm = 1us` (vedi datasheet sonar). 1251 | 1252 | Il passo del ccp nel tempo sarà `1/f = 1/8 MHz = 125 ns` 1253 | 1254 | In particolare, se ogni passo del sonar è 1u e un passo del ccp è 125 ns, allora la conversione è `c = t / 125ns =8 passi` 1255 | 1256 | Ovvero, per **ottenere un microsecondo sono necessari otto passi del ccp.** 1257 | 1258 | Quindi se `1us : 125 ns = 8 LSB` 1259 | 1260 | * 300us ovvero `300 mm : 125 ns = 2400` passi del ccp 1261 | 1262 | * 5000 us ovvero `5000 mm : 125 ns = 40000` passi del ccp. 1263 | 1264 | #### SONAR letto con ADC 1265 | 1266 | Premesse legate al datasheet del sonar e al datasheet del PIC 1267 | 1268 | Il sonar letto con l'ADC invece viene visto sulla porta RC3 come input analogico. 1269 | 1270 | L'interrupt dell'ADC è PIR1.ADIF 1271 | 1272 | L'enable si trova nel PIE1.ADIE 1273 | 1274 | ![microcontrollori](img/microcontrollori.jpg) 1275 | 1276 | **Come mai il risultato della conversione va moltiplicato per 5?** `width = width * 5` 1277 | 1278 | La equazione di questa retta sarebbe `V = 5V/5000mm * d` 1279 | 1280 | Dove V è la tensione e d la distanza. 1281 | 1282 | La pendenza della retta sarebbe precisamente 5V/5000mm ovvero, 1mV/1mm 1283 | 1284 | Vediamo ora quanti livelli ha l'ADC: 5V/1024 = 4.88 mV. Passo per livello riconosciuto del sonar. 1285 | 1286 | Ogni passo dell'ADC sarebbe circa 5mV. Più precisamente 4.88 mV. * (per vedere come ottenere massima precisione vedi dopo) 1287 | 1288 | Cerchiamo ora di scoprire a che livello si trova la minima distanza riconosciuta dal sonar, facciamo una proporzione: 1289 | 1290 | `5V : 1024 = 0.3 : X` ovvero `X = 61`. I livelli riconosciuti dall'ADC vanno quindi da 61 a 1024. 1291 | 1292 | Quindi la conversione da V a livelli segue questa equazione: 1293 | 1294 | ` V = a * LSB ` dove LSB è 5mV ( o più precisamente 4.88mV) 1295 | 1296 | Se quindi dobbiamo passare allo spazio tramite i livelli (che sono il risultato nel registro dell'ADC) allora: 1297 | 1298 | ` d = (1mm/1mV) * V ` 1299 | 1300 | Ma V è uguale ad (a * LSB) 1301 | 1302 | `d = (1mm/1mV) * a * LSB = 1mm * a * 4.88 = 4.88 mm * a` 1303 | 1304 | ` d = 4.88mm * a ` 1305 | 1306 | `d = (5000/1024) * a * mm` 1307 | 1308 | (posso scegliere l'unità di misura, da mm a metri ma sarebbe meno preciso) 1309 | 1310 | **se vogliamo tenere 4.88 a precisione massima dobbiamo tenere conto che lavoriamo su registri 16 bit.** 1311 | 1312 | ![microcontrolloriadc2](img/microcontrolloriadc2.jpg) 1313 | 1314 | Sapendo che 5 viene scritto con 3 bit 101 in binario. 1315 | L'operazione da fare sarà fare divisioni e moltiplicazioni in modo da mantenere il risultato nei limiti dei 16 bit. 1316 | 1317 | In particolare sappiamo che i livelli a vanno da 0 a 1024, quindi occupano sempre 10 bit. 1318 | 1319 | Abbiamo quindi massimo 5 bit liberi su cui lavorare (uno ce lo teniamo libero per essere sicuri di non fare errori di calcolo). 1320 | 1321 | Le operazioni da fare saranno, sapendo che 1322 | d= (5000/1024)*a 1323 | 1324 | E sapendo che `5000=2^3 *5^4` e `1024=2^10`, a occupa sempre 10 bit allora: d= ((5^4)/(2^7) )*a 1325 | 1326 | Dovrò moltiplicare per quattro volte 5 e dividere per 2 sette volte. Se lo faccio nella giusta sequenza mantengo precisione: 1327 | 1328 | `a*5 13 bit` 1329 | 1330 | `a*5>>1 12 bit` (quindi c'è spazio per un'altra moltiplicazione per 5 e arrivo a 15 bit che è il limite dello spazio di lavoro) 1331 | 1332 | `(a*5>>1)*5 15 bit` 1333 | 1334 | `((a*5>>1)*5)>>3 12 bit` 1335 | 1336 | `(((a*5>>1)*5)>>3)*5 15 bit` 1337 | 1338 | `(((((a*5>>1)*5)>>3)*5)>>3) 12 bit` 1339 | 1340 | `((((((a*5>>1)*5)>>3)*5)>>3)*5) 15 bit` 1341 | -------------------------------------------------------------------------------- /img/RBIF.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Squareroot7/pic-notes/1d9c31043b984f11290684628c04ad24072c4335/img/RBIF.JPG -------------------------------------------------------------------------------- /img/RBIF.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Squareroot7/pic-notes/1d9c31043b984f11290684628c04ad24072c4335/img/RBIF.jpg -------------------------------------------------------------------------------- /img/adc.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Squareroot7/pic-notes/1d9c31043b984f11290684628c04ad24072c4335/img/adc.JPG -------------------------------------------------------------------------------- /img/adc.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Squareroot7/pic-notes/1d9c31043b984f11290684628c04ad24072c4335/img/adc.jpg -------------------------------------------------------------------------------- /img/adif.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Squareroot7/pic-notes/1d9c31043b984f11290684628c04ad24072c4335/img/adif.png -------------------------------------------------------------------------------- /img/capture.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Squareroot7/pic-notes/1d9c31043b984f11290684628c04ad24072c4335/img/capture.JPG -------------------------------------------------------------------------------- /img/capture.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Squareroot7/pic-notes/1d9c31043b984f11290684628c04ad24072c4335/img/capture.jpg -------------------------------------------------------------------------------- /img/input_output.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Squareroot7/pic-notes/1d9c31043b984f11290684628c04ad24072c4335/img/input_output.jpg -------------------------------------------------------------------------------- /img/instruction_set.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Squareroot7/pic-notes/1d9c31043b984f11290684628c04ad24072c4335/img/instruction_set.png -------------------------------------------------------------------------------- /img/microcontrollori.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Squareroot7/pic-notes/1d9c31043b984f11290684628c04ad24072c4335/img/microcontrollori.jpg -------------------------------------------------------------------------------- /img/microcontrolloriadc2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Squareroot7/pic-notes/1d9c31043b984f11290684628c04ad24072c4335/img/microcontrolloriadc2.jpg -------------------------------------------------------------------------------- /img/microcontrolloricapturesonar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Squareroot7/pic-notes/1d9c31043b984f11290684628c04ad24072c4335/img/microcontrolloricapturesonar.jpg -------------------------------------------------------------------------------- /img/neumann-harvard-architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Squareroot7/pic-notes/1d9c31043b984f11290684628c04ad24072c4335/img/neumann-harvard-architecture.png -------------------------------------------------------------------------------- /img/port_diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Squareroot7/pic-notes/1d9c31043b984f11290684628c04ad24072c4335/img/port_diagram.png -------------------------------------------------------------------------------- /img/pwm.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Squareroot7/pic-notes/1d9c31043b984f11290684628c04ad24072c4335/img/pwm.JPG -------------------------------------------------------------------------------- /img/pwm.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Squareroot7/pic-notes/1d9c31043b984f11290684628c04ad24072c4335/img/pwm.jpg -------------------------------------------------------------------------------- /img/timer0.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Squareroot7/pic-notes/1d9c31043b984f11290684628c04ad24072c4335/img/timer0.JPG -------------------------------------------------------------------------------- /img/timer0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Squareroot7/pic-notes/1d9c31043b984f11290684628c04ad24072c4335/img/timer0.jpg --------------------------------------------------------------------------------