Un programmatore USB open source per PIC, memorie EEPROM I2C e MicroWire, alcuni micro ATMEL,
dispositivi I2C e SPI, e (in futuro) altri dispositivi
In breve
- Completamente gratuito e Open Source (anche il firmware)
- Programma PIC10-12-16-18, memorie I2C tipo 24XXXX e MicroWire tipo 93SX6, alcuni micro ATMEL;
comunica con dispositivi I2C e SPI (vedi supporto)
- Interfaccia USB 2.0 Full Speed, classe HID (come tastiere, mouse ecc.)
- Auto alimentato
- Non ha bisogno di driver
- Componenti di facile reperibilità (costo stimato ~10€)
- Temporizzazioni gestite in hardware quindi veloce e
affidabile (scrive un 18F2550 in 15s, 8s con Linux)
- Non occupa la CPU e non risente di altri programmi in
esecuzione
- Programmi di controllo open source per Linux e Windows
Una foto del prototipo:
Un altro programmatore?
In questi ultimi anni, con la sparizione delle comodissime
interfacce seriale e parallela, gli appassionati
di elettronica si trovano sempre più in difficoltà nel
programmare i microcontrollori; i classici programmatori non funzionano
più; di solito si ricorre a convertitori
USB-seriale (che però non si possono comandare direttamente ma
solo via API, quindi lentamente) o a integrati di interfaccia da
aggiungere al programmatore, tipo gli FTDIxxxx, che sostanzialmente
appaiono come periferiche seriali o richiedono driver ad hoc. Ma
perché non usare i PIC stessi, che hanno l'USB nativo?
Cercando in lungo e in largo non sono
riuscito a trovare un programmatore USB che fosse funzionale, gratuito,
open source, e così ho deciso di costruirmelo. La filosofia open
source consiste nel fornire tutti gli schemi, sorgenti, informazioni
utili a costruire e modificare questo programmatore.
Firmware USB e HID (v0.6.1)
Per usare l'interfaccia USB sui PIC
è necessario un firmware che implementi una delle classi
definite dal consorzio USB o una nuova; io ho scelto la classe HID
perché è supportata in modo nativo da tutti i sistemi
operativi e quindi non necessita di alcun driver. La velocità
è più che sufficiente: in teoria si raggiungerebbero i
64K/s, ma io ho misurato qualcosa come 20-40 K/s, comunque sicuramente
abbastanza per programmare dispositivi di 1-100K di memoria.
Come ogni periferica USB anche questa ha un identificativo vid e pid,
da ottenere ovviamente a pagamento; siccome io non ho un soldo da sprecare
né ho da vendere il progetto, ho usato il vid della Microchip e
un pid a caso: 0x4D8&0x100; sono entrambi configurabili a piacere
(previa compilazione), quindi lascio all'utente la scelta.
Il programmatore appare al sistema
come una periferica HID che comunica tramite pacchetti di 64 byte
scambiati con cadenza di 1ms.
L'implementazione del firmware USB viene da un progetto open source poco noto,
scritto da Alexander Enzmann, che io ho modificato e adattato al compilatore C18.
Ho anche scritto una
breve guida su come usarlo; per quanto
ne so io questo è l'unico firmware open source con supporto HID.
Il programmatore in se è semplicemente un interprete di comandi
che pilota direttamente le uscite del micro seguendo un apposito insieme di istruzioni.
Il ciclo di controllo principale aspetta che venga ricevuto un
pacchetto via USB, poi comincia a eseguire i comandi specificati
occupandosi tra l'uno e l'altro della comunicazione col pc; allo stesso
tempo gira la funzione di controllo del regolatore di tensione DCDC,
chiamata a intervalli regolari tramite un timer (e un interrupt).
Compilare il tutto richiede solo strumenti gratuti: MPLAB e MCC18 versione
studenti, disponibili sfortunatamente solo per il sistema (in)operativo a finestre.
Compilare con SDCC è sicuramente possibile ma richiede qualche modifica al sorgente.
Rilascio il tutto con licenza GPL2.
Qui si trova il progetto
MPLAB completo in formato compresso;
qui
il firmware compilato in formato hex.
Qui il firmware compilato per 18F2450
(con funzionalità ridotta, vedi
schema elettrico).
Programmi di controllo
La mia idea iniziale era di
modificare un software già esistente, ad esempio winpic o picprog, ma
mi sono accorto che visto il
tipo di comunicazione necessario, basato su pacchetti anziché
seriale, non avrei potuto adattarli facilmente, quindi ho dovuto
scriverne uno (due) da zero.
Purtroppo, o per fortuna, non essendo un
informatico ho tagliato quasi tutto il superfluo: l'interfaccia utente
è davvero minima, inesistente per la versione Linux (con grande
gioia degli appassionati), ci sono sicuramente dei bachi che
mi impegno a eliminare, e mancano delle funzionalità come la
modifica del codice a posteriori.
Però sono programmi molto snelli, piuttosto veloci, e non occupano la CPU per niente.
Quando possibile la scrittura viene verificata durante la programmazione,
altrimenti immediatamente dopo.
Linux (v0.6.2)
OP è un eseguibile da linea di comando; si interfaccia col dispositivo /dev/usb/hiddev0
(o quello che si specifica) e ha bisogno dei diritti di lettura su hiddevX.
es. >sudo chmod a+r /dev/usb/hiddev0
Per abilitare permanentemente un utente alla lettura si può procedere come segue
(su Ubuntu e altre distribuzioni basate su Debian, verificare per le altre):
da root creare un file /etc/udev/rules.d/99-hiddev.rules
nel caso si voglia abilitare un gruppo di utenti scrivervi:
KERNEL=="hiddev[0-9]", SUBSYSTEM=="usb", SYSFS{idProduct}=="0100", SYSFS{idVendor}=="04d8",
GROUP="<gruppo>"
in cui <gruppo> è uno dei gruppi a cui appartiene l'utente (per un elenco digitare "groups");
utilizzare un gruppo adeguato e se necessario aggiungere l'utente al gruppo scelto
("addgroup <utente> <gruppo>").
oppure, nel caso si vogliano abilitare tutti gli utenti, cambiare solo i permessi di lettura:
KERNEL=="hiddev[0-9]", SUBSYSTEM=="usb", SYSFS{idProduct}=="0100", SYSFS{idVendor}=="04d8",
MODE="0664"
riavviare udev per applicare le modifiche:
> /etc/init.d/udev reload
Se collegando il programmatore non appare la periferica /dev/usb/hiddevX (e il LED non
lampeggia a 1 Hz) è sufficiente eseguire alcune volte lsusb per forzare l'enumerazione da parte del sistema,
eventualmente staccando e riattaccando il cavo.
Se non specificato altrimenti il programma si aspetta una periferica USB con vid&pid=0x4d8:0x100.
Scrive e legge file hex8 e hex32.
Nota: per qualche strano motivo per specificare il parametro opzionale dell'opzione -l bisogna usare
-l=<file>. Sarà un baco di getopt?
Con l'opzione "HWtest" e un multimetro è possibile verificare che il circuito stia effettivamente funzionando.
Per comunicare via I2C bisogna sempre specificare byte di controllo e indirizzo
(o indirizzi); il bit RW viene gestito automaticamente.
Il supporto multilingue per ora è fermo a Italiano e Inglese.
Esempi:
> op -h
#guida
> op -d 18F2550 -ee -s prova.hex #legge codice ed EEPROM e salva su file
> op -d 16F628 -ee -w prova.hex #scrive
> op -i2c_r 8 A0 0
#legge 8 byte dal bus I2C, byte di controllo A0, indirizzo 0
Ho incluso anche l'utility Hid_test, che spedisce e riceve un pacchetto (64 byte) alla volta; può
essere utile per fare qualche esperimento; in teoria ci si potrebbe addirittura scrivere uno script di programmazione.
Scarica
Windows (v0.6.2)
OpenProg è scritto in VisualC++ 6.0 usando MFC. L'utilizzo è piuttosto
semplice, basta collegare il programmatore e avviare il programma; poi
selezionare il micro e infine leggere o scrivere; funziona con XP e sVista.
Sulla scheda "Dispositivo" si possono modificare alcune opzioni di programmazione, per esempio
la scrittura delle locazioni ID, calibrazione, l'uso della eeprom ecc.
La scheda I2C/SPI permette di comunicare con dispositivi generici I2C e SPI;
se si utilizza il protocollo I2C bisogna sempre specificare il byte di controllo (e l'indirizzo se diverso da 0);
il bit RW viene gestito automaticamente.
Ad esempio per scrivere manualmente 3 byte su una 24xx16 all'indirizzo 64 si scrive: A0 40 1 2 3
Visto che non sono necessarie librerie extra non ho incluso alcun installatore,
è sufficiente scaricare l'eseguibile e avviarlo.
Il programma scrive e legge file hex8 e hex32.
Le lingue supportate sono Italiano e Inglese; è possibile modificare il
file di risorse (lang.rc) per aggiungerne altre.
Con la funzione "Test Hardware" e un multimetro è possibile verificare
che il circuito stia effettivamente funzionando.
Le opzioni da linea di comando sono:
-d <dispositivo> , seleziona un dispositivo
-r <nome file> , legge dal dispositivo e scrive sul file
-w <nome file> , legge il file e scrive sul dispositivo
-gui , non esce dopo la scrittura o la lettura (solo se specificati -w o -r)
La schermata principale del programma:
Scarica il programma ...
e
i sorgenti (progetto Visual Studio 6)
Potrà risultare interessante il fatto che per la compilazione
non è necessario usare il DDK (driver development kit); nel codice c'è il link esplicito alla
libreria di sistema hid.dll, e le funzioni che servono vengono caricate a mano.
Supporto
Il programmatore è stato provato su un numero ristretto di dispositivi (quelli che posseggo)
indicati in neretto; gli altri sono supportati ma non collaudati.
Chi avesse la possibilità di verificarne la funzionalità è pregato di farmelo sapere.
Ugualmente chi avesse bisogno di ampliare il supporto o scrivesse altri
algoritmi può contattarmi.
supporto in lettura e scrittura:
10F200, 10F202, 10F204,
10F206, 10F220, 10F222,
12F508,
12F509, 12F510,
12F519, 12F609, 12F615, 12F629, 12F635,
12F675,
12F683,
16F505, 16F506, 16F526, 16F54, 16F610, 16F616, 16F627, 16F627A,
16F628,
16F628A,
16F630, 16F631,
16F636, 16F639,
16F648A, 16F676, 16F677, 16F684, 16F685, 16F687,
16F688, 16F689,
16F690, 16F716, 16F73, 16F737, 16F74, 16F747, 16F76, 16F767, 16F77, 16F777, 16F785,
16F818,
16F819, 16F83, 16F83A, 16C83, 16C83A,
16F84, 16C84,
16F84A,
16C84A,
16F87, 16F870, 16F871, 16F872,
16F873, 16F873A, 16F874, 16F874A,
16F876, 16F876A,
16F877, 16F877A,
16F88, 16F882, 16F883, 16F884, 16F886, 16F887, 16F913, 16F914, 16F916, 16F917, 16F946,
18F242, 18F248,
18F252, 18F258, 18F442, 18F448, 18F452,
18F458, 18F1220, 18F1230,
18F1320, 18F1330, 18F2220, 18F2221, 18F2320, 18F2321, 18F2331, 18F2410, 18F2420,
18F2423, 18F2431, 18F2439,
18F2450, 18F2455, 18F2458, 18F2480, 18F2510, 18F2515, 18F2520,
18F2523, 18F2525, 18F2539,
18F2550, 18F2553, 18F2580, 18F2585, 18F2610, 18F2620,
18F2680,
18F2682,
18F2685, 18F4220, 18F4221, 18F4320, 18F4321, 18F4331, 18F4410, 18F4420,
18F4423, 18F4431, 18F4439, 18F4450, 18F4455, 18F4458, 18F4480, 18F4510, 18F4515,
18F4520,
18F4523, 18F4525, 18F4539,
18F4550, 18F4553, 18F4580, 18F4585, 18F4610, 18F4620, 18F4680,
18F4682, 18F4685,
2400, 2401, 2402, 2404, 2408,
2416, 2432, 2464, 24128,
24256, 24512, 241025,
93C46C, 93C56C, 93C66C, 93S46,
93S56, 93S66
AT90S1200,
AT90S8515, AT90S8535, ATmega8, ATmega8A, ATmega8515, ATmega8535,
ATmega16, ATmega16A, ATmega32, ATmega32A
supporto in sola lettura:
12C508, 12C508A, 12C509, 12C509A, 12C671, 12C672,
12CE673, 12CE674
Protocollo di comunicazione
Nella scelta del protocollo di comunicazione bisogna tenere conto di varie esigenze fra loro spesso contrastanti:
velocità ed efficienza di trasferimento, dimensione del codice, universalità ed espandibilità.
A un estremo ci sono i programmatori seriali semplici, che ricevono solo comandi per forzare i livelli di tensione;
il software controlla sia le temporizzazioni che l'algoritmo di programmazione, occupando però completamente
la CPU e risentendo pesantemente degli altri programmi in esecuzione.
All'opposto i programmatori "intelligenti" generano le temporizzazioni e gestiscono gli algoritmi,
ma devono essere aggiornati per supportare nuovi dispositivi.
Qui la scelta è caduta su un sistema ibrido: nel firmware sono stati implementati i comandi elementari ICSP
(In Circuit Serial Programming), ma gli algoritmi sono gestiti dal software.
Per aumentare la velocità e l'efficienza di trasferimento ci sono alcune istruzioni che corrispondono a sequenze di comandi
ripetuti molto spesso, ad esempio le letture sequenziali.
Il vantaggio di questo approccio è che le temporizzazioni sono molto precise perchè gestite dal microcontrollore,
ma allo stesso tempo la grande varietà di algoritmi viene implementata nel software e non aumenta la dimensione del codice firmware.
Per esempio per entrare in program mode col 16F628 e leggere DevID la sequenza sarebbe:
SET_PARAMETER; //imposto i ritardi che verranno usati dalle altre istruzioni
SET_T1T2;
//codice per modificare T1 e T2
1;
//T1=1us
100;
//T2=100us
EN_VPP_VCC;
//Vpp e Vcc a 0
0x0;
SET_CK_D;
//Clock e Dato in uscita a 0
0x0;
EN_VPP_VCC;
//abilito Vpp
0x4;
NOP;
//piccolo ritardo
EN_VPP_VCC;
//abilito Vdd+Vpp
0x5;
NOP;
//piccolo ritardo
LOAD_CONF;
//contatore a 0x2000
0xFF;
//config fasulla
0xFF;
//config fasulla
INC_ADDR_N;
//incrementol'indirizzo di 6
0x06;
READ_DATA_PROG; //leggo DevID
...
Oltre ai comandi ICSP ci sono istruzioni che permettono di gestire il programmatore,
le tensioni di programmazione, imporre ritardi precisi, comunicare via I2C e SPI.
Ogni istruzione dura almeno 40us, dovuti al ciclo di esecuzione dell'interprete;
i comandi ICSP sono temporizzati in base ai parametri T1-T2 o a quanto scritto nei
documenti Microchip; tutte fanno un eco (senza parametri) in uscita, eccetto FLUSH
che svuota la coda di uscita e la spedisce subito in un pacchetto di 64 byte.
Nel caso non ci siano abbastanza parametri viene restituito 0xFE e termina l'esecuzione.
Lo stato della comunicazione USB è segnalato dal LED2: lampeggia a 4 Hz durante l'enumerazione,
a 1 Hz quando la comunicazione è normale.
Il LED1 invece segnala quando ci sono istruzioni in esecuzione.
L'elenco completo delle istruzioni:
| Comando |
Valore |
Parametri |
Risposta |
Note |
| NOP |
0x00 |
no |
eco |
non fa nulla |
| PROG_RST |
0x01 |
no |
eco + 10B |
reset del programmatore; spedisce versione fw. (3B), ID(3B), la stringa " RST" |
| PROG_ID |
0x02 |
no |
eco + 6B |
spedisce versione fw. (3B), ID (3B) |
| CHECK_INS |
0x03 |
1B |
eco + 1B |
se l'istruzione specificata nel parametro esiste ne
rispedisce il codice, altrimenti invia errore (0xFE) |
| FLUSH |
0x04 |
no |
nessuno |
svuota la coda di uscita (invia 64B) e ferma l'interprete comandi per il pacchetto corrente |
| VREG_EN |
0x05 |
no |
eco |
accende il regolatore di tensione |
| VREG_DIS |
0x06 |
no |
eco |
spegne il regolatore di tensione |
| SET_PARAMETER |
0x07 |
1B |
eco |
imposta parametri interni (di ritardo o altro); byte1
parametro da cambiare, byte 2-3 valore:
SET_T1T2 (=0): T1 e T2
SET_T3 (=1): T3(H,L)
SET_timeout (=2): timeout(H,L)
SET_MN (=3): M,N |
| WAIT_T1 |
0x08 |
no |
eco |
ritardo di T1 us (=1us all'avvio) |
| WAIT_T2 |
0x09 |
no |
eco |
ritardo di T2 us (=100us all'avvio) |
| WAIT_T3 |
0x0A |
no |
eco |
ritardo di T3 us (=2ms all'avvio) |
| WAIT_US |
0x0B |
1B |
eco |
ritardo variabile di N us |
| READ_ADC |
0x0C |
no |
eco +2B |
legge la tensione del regolatore (10bit efficaci,MSB-LSB);
considerato il partitore in ingresso la tensione in Volt si ricava come
<valore>/1024*5*34/12 |
| SET_VPP |
0x0D |
1B |
eco +1B |
imposta la tensione del regolatore a <parametro>/10;
se entro 15ms l'errore di tensione è < 200mV rispedisce
<parametro>, altrimenti errore (0xFE) |
| EN_VPP_VCC |
0x0E |
1B |
eco |
controlla Vpp e Vcc del dispositivo da programmare;
bit 0: Vcc, bit 1: Vpp; bit 2 e 3: impedenza delle linee di controllo: lasciare a 0 |
| SET_CK_D |
0x0F |
1B |
eco |
controlla le linee CK, D, PGM del dispositivo da
programmare; 1 bit controlla il livello (0-1), 1bit l'impedenza
(bassa-alta); bit 0-1: D, bit 2-3: CK, bit 4-5: PGM |
| READ_PINS |
0x10 |
no |
eco +1B |
legge lo stato delle linee di controllo e
comunicazione, 1 bit livello (0-1), 1bit impedenza (bassa-alta);
bit 0-1: D, bit 2-3: CK, bit 4-5: PGM |
| LOAD_CONF |
0x11 |
2B |
eco |
Comando ICSP: Load configuration (000000), T1 us tra
comando e dati; 14 bit dati (allineati a destra, MSB-LSB) |
| LOAD_DATA_PROG |
0x12 |
2B |
eco |
Comando ICSP: Load Data in Program Memory (000010), T1
us tra comando e dati; 14 bit dati (allineati a destra, MSB-LSB) |
| LOAD_DATA_DATA |
0x13 |
1B |
eco |
Comando ICSP: Load Data in Data Memory (000011), T1 us tra comando e dati; 8 bit dati |
| READ_DATA_PROG |
0x14 |
no |
eco +2B |
Comando ICSP: Read Data from Program Memory (000100),
T1 us tra comando e dati; 14 bit dati (allineati a destra, MSB-LSB) |
| READ_DATA_DATA |
0x15 |
no |
eco +1B |
Comando ICSP: Read Data from Data Memory (000101), T1 us tra comando e dati; 8 bit dati |
| INC_ADDR |
0x16 |
no |
eco |
Comando ICSP: Increment Address (000110), ritardo di T1 us alla fine |
| INC_ADDR_N |
0x17 |
1B |
eco |
Comando ICSP: Increment Address (000110), ritardo di T1 us alla fine; ripetuto N volte |
| BEGIN_PROG |
0x18 |
no |
eco |
Comando ICSP: Begin Programming (001000) |
| BULK_ERASE_PROG |
0x19 |
no |
eco |
Comando ICSP: Bulk Erase Program Memory (001001) |
| END_PROG |
0x1A |
no |
eco |
Comando ICSP: End Programming (001010) |
| BULK_ERASE_DATA |
0x1B |
no |
eco |
Comando ICSP: Bulk Erase Data Memory (001011) |
| END_PROG2 |
0x1C |
no |
eco |
Comando ICSP: End Programming (001110) |
| ROW_ERASE_PROG |
0x1D |
no |
eco |
Comando ICSP: Row Erase Program Memory (010001) |
| BEGIN_PROG2 |
0x1E |
no |
eco |
Comando ICSP: Begin Programming (0011000) |
| CUST_CMD |
0x1F |
1B |
eco |
Comando ICSP specificato nel parametro |
| PROG_C |
0x20 |
2B |
eco +1B |
Programma word seguendo l'algoritmo per 12Cxxx: 000010,
001000, 001110, M pulses & N overpulses |
| CORE_INS |
0x21 |
2B |
eco |
Comando ICSP PIC18: Core instruction (0000); 16 bit dati (MSB-LSB) |
| SHIFT_TABLAT |
0x22 |
no |
eco +1B |
Comando ICSP PIC18: Shift TABLAT (0010); 8 bit dati |
| TABLE_READ |
0x23 |
no |
eco +1B |
Comando ICSP PIC18: Table read (1000); 8 bit dati |
| TBLR_INC_N |
0x24 |
1B |
eco+N+NB |
Comando ICSP PIC18: Table read post-inc (1001); 8 bit
dati; ripete N volte; rispedice N e NB dati |
| TABLE_WRITE |
0x25 |
2B |
eco |
Comando ICSP PIC18: Table write (1100); 16 bit dati (MSB-LSB) |
| TBLW_INC_N |
0x26 |
(2N+1)B |
eco |
Comando ICSP PIC18: Table write post-inc
(1101); 16 bit dati (MSB-LSB); ripete N volte (N è il primo parametro) |
| TBLW_PROG |
0x27 |
4B |
eco |
Comando ICSP PIC18: Table write and program (1111); 16
bit dati (MSB-LSB); di seguito esegue un NOP con ritardo di scrittura
specificato dal parametro 3-4 (in us) |
| TBLW_PROG_INC
|
0x28 |
4B |
eco |
Comando ICSP PIC18: Table write and program post-inc (1110); 16
bit dati (MSB-LSB); di seguito esegue un NOP con ritardo di scrittura
specificato dal parametro 3-4 (in us) |
| SEND_DATA |
0x29 |
3B |
eco |
Comando ICSP PIC18 specificato nel byte 1; scrive 16 bit dati (MSB-LSB) |
| READ_DATA |
0x2A |
1B |
eco+1B |
Comando ICSP PIC18 specificato nel byte 1; legge 8 bit dati |
| I2C_INIT |
0x2B |
1B |
eco |
Inizializza la periferica I2C: 0xFF disabilita I2C;
bit 6: controllo slew per velocità > 100kbps;
bit 5:3 velocità: 0=100k, 1=200k, 2=400k, 3=800k, 4=1M;
attenzione: usare resistenze di pull-up adatte alla velocità scelta;
bit 2:0: livello logico delle linee A2-A1-A0 del dispositivo |
| I2C_READ |
0x2C |
3B |
eco+1+NB |
Legge <parametro 1> byte dal bus I2C usando il byte di controllo
<parametro 2> e l'indirizzo <parametro 3>; forza automaticamente il bit
RW del byte di controllo. Risponde con <parametro 1> + Dati o
ACK_ERR (0xFD) in caso di errore di acknowledge (ad es. se non ci sono
dispositivi connessi) |
| I2C_WRITE |
0x2D |
3B+NB |
eco |
Scrive <parametro 1> byte sul bus I2C usando il byte di controllo
<parametro 2> e l'indirizzo <parametro 3>;
forza automaticamente il bit RW del byte di controllo.
Risponde (0xFD) in caso di errore di acknowledge (ad es.
se non ci sono dispositivi connessi).
In caso di indirizzi a 2 byte è sufficiente usare il secondo byte di
indirizzo come primo dei dati. |
| I2C_READ2 |
0x2E |
4B |
eco+1+NB |
Legge dal bus I2C; come I2C_READ, ma usa 2 byte per gli indirizzi |
| SPI_INIT |
0x2F |
1B |
eco |
Inizializza la periferica SPI: 0xFF disabilita SPI;
bit 1:0 velocità: 0=100kbps, 1=200kbps, 2=300kbps, 3=500kbps;
bit 2:3 modo SPI |
| SPI_READ |
0x30 |
1B |
eco+1+NB |
Legge <parametro 1> byte dal bus SPI. Risponde con <parametro 1> + Dati
Se <parametro 1>=0 risponde col byte ricevuto durante l'ultimo trasferimento (lettura o scrittura) |
| SPI_WRITE |
0x31 |
1B+NB |
eco+1B |
Scrive <parametro 1> byte sul bus SPI. |
| EXT_PORT |
0x32 |
2B |
eco |
Forza i livelli delle porte di comunicazione:
<parametro 1> = <RB7:RB0> <parametro 2> = <RC7,RC6,RA5:RA3>
Non modifica la direzione dei segnali. |
| AT_READ_DATA |
0x33 |
3B |
eco+1+2NB |
Comando ATMEL: read program memory (0010H000); legge <parametro 1> word
all'indirizzo <parametro 2> : <parametro 3>
via SPI. Risponde con <parametro 1> + Dati |
| AT_LOAD_DATA |
0x34 |
3B+2N |
eco+1B |
Comando ATMEL: load program memory page (0100H000); carica <parametro 1>
word all'indirizzo <parametro 2> : <parametro 3>
via SPI. Risponde con <parametro 1> |
| CLOCK_GEN |
0x35 |
1B |
eco |
Genera un clock su RB3 (tramite CCP1-2 e TIMER1)
bit 2:0 frequenza: 0=100kHz, 1=200kHz, 2=500kHz, 3=1MHz, 4=2MHz, 5=3MHz, 6=6MHz;
Disabilita l'uscita PWM1 (regolatore DCDC).
|
| SIX |
0x36 |
3B |
eco |
Comando ICSP PIC24: Core instruction (0000); 24 bit dati (MSB-LSB) (Sperimentale) |
| REGOUT |
0x37 |
none |
eco +2B |
Comando ICSP PIC24: Shift out VISI register (0001); 16 bit dati (MSB-LSB)) (Sperimentale) |
| ICSP_NOP |
0x38 |
no |
eco |
Comando ICSP PIC24: esegue NOP (0000) (Sperimentale) |
| TX16 |
0x39 |
1+2NB |
eco+1B |
Trasmette N*16 bit via ICSP; N*16 bit dati (MSB-LSB) (Sperimentale) |
| RX16 |
0x3A |
1B |
eco+1+2NB |
Riceve N*16 bit via ICSP; N*16 bit dati (MSB-LSB) (Sperimentale) |
| uW_INIT |
0x3B |
no |
eco |
Inizializza la comunicazione MicroWire |
| uW_TX |
0x3C |
1B+NB |
eco+1B |
Scrive <parametro 1> bit sul bus MicroWire |
| uW_RX |
0x3D |
1B |
eco+1+NB |
Legge <parametro 1> bit dal bus MicroWire |
| READ_RAM |
0xF0 |
2B |
eco+3B |
Legge dalla memoria del micro di controllo; indirizzo a
16 bit, dato 8 bit; fa eco dell'indirizzo |
| WRITE_RAM |
0xF1 |
3B |
eco+3B |
Scrive sulla memoria del micro di controllo; indirizzo
a 16 bit, dato 8 bit; fa eco dell'indirizzo e del dato |
| LOOP |
0xF2 |
no |
no |
Riporta il puntatore istruzioni a 0 ed esegue tutte le istruzioni.
Il micro non accetta più altri comandi. Solo per esigenze di test. |
Schema elettrico (v1.4)
Il progetto è basato sul PIC18F2550, a 28 piedini, ma per ora viene occupato
solo circa un terzo della memoria programma e 0% della eeprom, quindi è possibile usare anche il più piccolo 2455.
I 2458 e 2553 differiscono solo per l'ADC a 12 bit, quindi dovrebbero andare benissimo.
Con alcune modifiche ho adattato il tutto anche al 2450; questo modello non ha il modulo MSSP,
quindi le funzionalità I2C e SPI vengono emulata via software; non ha neanche il secondo canale PWM,
quindi non può generare il clock per i micro Atmel che lo richiedono (che sono configurati per il clock
esterno); in questo caso il piedino RB3 ha la funzione di accendere un eventuale oscillatore
esterno, da inserire opportunamente nella scheda di espansione Atmel.
Per i dispositivi a 40p corrispondenti (4450,4455,4458,4550,4553) bisogna modificare lo stampato.
Per implementare una periferica USB con i PIC
ci vogliono veramente pochi componenti: il micro principale, il quarzo,
qualche condensatore, qualche resistenza e il connettore USB tipo B, tutto
come da Application Note della Microchip.
Oltre a connettere il micro via USB bisogna generare le tensioni per la
programmazione vera e propria; nel caso ICSP quindi servono tre transistor (per
controllare VDD e VPP) e due linee dati (per dati e clock). La tensione
Vpp viene da un
regolatore switching descritto di seguito.
La scheda base può ospitare i PIC a 8,14,16,18,20 piedini (eccetto i 10Fxxx)
che vanno inseriti in U3 allineati al piedino 1:

Per i 10Fxxx a 6 o 8 piedini aggiungerò in futuro un adattatore;
nel frattempo ci si può arrangiare con collegamenti volanti.
In U4 vengono ospitate le memorie EEPROM I2C.
Tramite due connettori di espansione si possono poi collegare gli
adattatori per altri dispositivi:
- PIC a 28-40 piedini + connettore ICSP
- PIC 8-20p. (come sulla scheda base, ma c'è più spazio per uno zoccolo ZIF) + conn. ICSP
- Memorie I2C, SPI, MicroWire e conn. I2C-SPI
- ATMEL 9-14-20-28-40p. e conn. I2C-SPI
- ST72 (in futuro)
Ovviamente se ne possono creare di altri o non costruirne e usarne nessuno.
Piedinatura dei connettori della scheda base e delle espansioni:
Espansione1(vicino al micro): RB7-RB0,RC7,RC6
Espansione2(vicino al bordo): RA3-RA5,RA2,RA1,RE3,VDD,GND,VPPU,VDDU
ICSP: VPPU,VDDU,ICD,ICK,GND
ICSP-IN: VPPU,VDDU,ICD,ICK,GND (da non confondere con ICSP; serve per programmare
il micro principale senza toglierlo)
I2C/SPI: CK,DI,DO,GND (I2C non usa DO)
Mappatura delle risorse del micro principale:
| Pin |
Funzioni varie |
ICSP |
I2C-EEPROM |
SPI-EEPROM |
SPI-ATMEL |
uW-EEPROM |
| RB7 |
|
PGM |
|
|
|
|
| RB6 |
|
ICSP clock |
|
|
|
|
| RB5 |
|
ICSP data |
A2 |
|
|
W (6) |
| RB4 |
|
|
A1 |
HLD |
|
S (1) |
| RB3 |
|
|
A0 |
CS |
Device clock |
PRE (7) |
| RB2 |
espansione |
|
|
|
|
|
| RB1 |
|
|
Clock |
Clock |
SPI Clock |
Clock |
| RB0 |
|
|
Data |
Data out (MOSI) |
Data out (MOSI) |
Data out |
| RC7 |
|
|
|
Data in (MISO) |
Data in (MISO) |
Data in |
| RC6 |
|
|
WP |
WP |
RESET |
|
| RC5 |
USB D+ |
|
|
|
|
|
| RC4 |
USB D- |
|
|
|
|
|
| RC2 |
DCDC PWM |
|
|
|
|
|
| RC1 |
controlla VDD |
|
|
|
|
|
| RC0 |
controlla VPP |
|
|
|
|
|
| RA5 |
espansione |
|
|
|
|
|
| RA4 |
espansione |
|
|
|
|
|
| RA3 |
espansione |
|
|
|
|
|
| RA2 |
LED 2 |
|
|
|
|
|
| RA1 |
LED 1 |
|
|
|
|
|
| RA0 |
ADC per regolatore |
|
|
|
|
|
| RE3 |
Pulsante S1 |
|
|
|
|
|
Schema elettrico del modulo base:
Circuito stampato del modulo base:
Molti componenti sono opzionali: i connettori di espansione CONN2-3, le
resistenze di protezione R11:23 (visto il costo perché non metterle?),
le resistenze di pull-up I2C R26-27,
il pulsante S1 (per ora non è utilizzato dal software),
lo zoccolo per memorie I2C, il connettore ICSP-IN CONN4 (che serve a
programmare il micro principale senza estrarlo, almeno fino
a che non implementerò un auto-aggiornamento via USB).
Il circuito stampato è stato ottimizzato per usare una sola faccia, ma
sono necessari anche alcuni ponticelli sul lato componenti; chi volesse può
farne a meno usando un pcb doppia faccia.
Nel montaggio bisogna fare attenzione all'orientamento dei transistor:
Q1 ha l'emettitore a sinistra, Q2 in alto, Q3 e Q4 a destra.
Sui circuiti adattatori consiglio di inserire i pettini di espansione dal lato componenti
e di lasciare il distanziatore plastico da quel lato; questo aumenta molto la resistenza
meccanica, soprattutto in fase di estrazione.
Per verificare che tutto funzioni bene ho inserito la funzione "test hardware" nei programmi di controllo:
in questa modalità appaiono dei messaggi indicanti le tensioni su VCC, VPP, CK, D, PGM (da misurare su U3); se
il valore corrisponde vuol dire che il circuito è funzionante.
Elenco componenti:
U1 quarzo 12Mhz (anche 4, 8, 16, 20; bisogna riconfigurare il divisore nel micro)
U2 18F2550 (anche 2450,2455,2458,2553,4450,4455,4458,4550,4553)
U3 zoccolo 20p.
U4 zoccolo 8p.
Q1-2 BC557 (o qualsiasi PNP, attenzione al montaggio)
Q3-4 BC547 (o qualsiasi NPN, attenzione al montaggio)
D1-2 LED a piacere
D3 1N4148 (o qualsiasi diodo, meglio se Shottky)
L1 100uH tipo resistenza o altro
R1 22K
R2 12K
R3 100K
R4:6 10K
R7 1M
R8-9 2.2K
R10 10K
R11:23 100
R24-25 300K
R26-27 10K
C1 22-100uF 25V
C2-3 22pF
C4 >= 220nF
C5 100nF
C6 10uF
C7-8 100nF
Lo schema elettrico è stato disegnato con Gschem, un programma
open source che fa parte della suite
GEDA.
Sebbene non venga quasi menzionato nel loro sito (giacchè usano tutti linux) è
disponibile anche per windows, previa installazione di
cygwin
e compilazione degli ultimi sorgenti.
Stesso discorso per gli stampati, disegnati con
PCB,
di cui esiste anche una versione windows (per la verità un po' limitata).
Chi avesse la pazienza può anche fare a meno dello stampato e montare il tutto su millefori.
Schema elettrico del modulo base:
.pdf,
.png;
schede di espansione:
.pdf,
.png;
tutto in formato gschem:
Oprog.sch
Circuito stampato del modulo base:
.pdf,
.png;
base + schede di espansione:
.pdf,
.png;
tutto in formato PCB:
Oprog.pcb
Archivio completo, include sorgenti, gerber, pdf, png.
Come programmare il micro la prima volta?
E' un problema molto interessante, visto che un dispositivo nuovo è vuoto e
non funziona come programmatore: a parte il classico "fattelo programmare da qualcuno che ce l'ha
già" il mio consiglio è di usare una di quelle interfacce
tanto in voga oggi, ad esempio JDM, magari costruita su millefori, assieme a winpic o picprog.
Potrebbe sembrare strano dover costruire un programmatore per farne un altro, ma
non c'è modo di comunicare via USB senza firmware; credo però che ne
valga la pena, visto che i programmatori seriali non sono molto
affidabili, sono lenti, e soprattutto non si possono usare sui nuovi
computer.
Inoltre, almeno fino a che non riuscirò a implementare
un auto-aggiornamento, consiglio di tenere un micro di scorta per
caricare le versioni aggiornate del firmware.
Il circuito base e alcune schede di espansione (8-20p PIC con ZIF, 28-40p PIC, ATMEL, EEPROM):
Regolatore switching
Per generare una tensione Vpp maggiore di 5V bisogna usare un convertitore DCDC a commutazione di tipo boost.
In commercio ne esistono a migliaia, ma seguendo la filosofia del minimo
indispensabile ne ho realizzato uno col micro stesso, al modico costo
di un transistor e qualche componente passivo.
Per tenere fissa la tensione d'uscita al variare delle condizioni operative bisogna poi variare
la larghezza degli impulsi di commutazione in funzione di questa.
In pratica si tratta di realizzare un regolatore con anello di reazione digitale,
come illustrato nello schema seguente.

I blocchi di base ci sono tutti: periferica PWM, convertitore A/D, DSP(quasi).
Attualmente il convertitore A/D usa come riferimento l'alimentazione a 5V, quindi l'uscita varia
in funzione di questa; è però possibile collegare un riferimento esterno su RA3 e migliorare la
precisione.
La frequenza di commutazione è 90 kHz, ben oltre la frequenza di taglio del filtro LC
in uscita (~2,3 kHz).
Le prestazioni in corrente sono limitate dalle perdite del transistor,
del diodo, dell'induttore, ma visto che l'assorbimento del carico è basso si può
usare un comunissimo diodo al silicio, un transistor NPN qualsiasi, e
un induttore a goccia; per migliorare il comportamento si può optare
per un diodo Shottky, tipo BAT41, un transistor più grosso
(o un Mosfet) e un'induttanza avvolta in aria.
Rimane da progettare un regolatore (blocco C) opportuno: è meglio lavorare nel dominio s,
quindi serve modello alle variazioni del convertitore, cosa che
fortunatamente è stata già fatta e si trova un po' dappertutto, ad esempio
qui.
Con i componenti scelti il convertitore opera in regime discontinuo,
infatti la corrente critica risulta:
Icrit=Vu*T/(16*L)=86 mA
molto al di sopra del consumo effettivo, che supponiamo 1mA.
Parametri di calcolo:
Vi=5
Vu=12.5
D=(vu-vi)/vo
L=100e-6
C=47e-6
I=1e-3
R=12/I
Rl=1.6 (resistenza serie dell'induttore)
T=1/90e3
vu 1 vu M-1 Vu 2M -1
--- = Gdo ---------- dove Gdo = 2 --- ------- , M = ---, wp = ----------
D 1 + s/wp D 2M -1 Vi (M-1) RC
La funzione di trasferimento risulta:
vu 127.58
-- = -------------
D 0.22031 s + 1
Il diagramma di Bode è il seguente:

Dunque il sistema sarebbe stabile anche solo chiuso su se stesso, ma
avrebbe un errore a regime inversamente proporzionale al guadagno DC.
Per ovviare a questo bisogna inserire un controllore con un polo
nell'origine e uno zero più avanti per stabilizzare il tutto.
Ad esempio usando un controllore di questo tipo:
D 0.25 (s + 50)
C = --- = -------------
err s
la funzione di trasferimento si modifica come segue:
vu 144.77 s + 7238.4
-- = -----------------
vi s2 + 4.539 s

Si vede che il sistema rimane stabile, con margine di
fase ~75º.
Ora per chiudere l'anello di regolazione in digitale bisogna scegliere
la frequenza di campionamento.
Questa da un lato limita la banda passante del regolatore, dall'altra
viene limitata dalla potenza di calcolo del micro; un periodo di 250us
è risultato un buon compromesso.
Convertendo le varie funzioni di trasferimento nel dominio di z con la
trasformazione bilineare si ha:
vu 0.018199 z2+ 0.00022607 z - 0.017973
-- = ------------------------------------
vi z2 - 1.9989 z + 0.99887
Il regolatore è:
D 0.25156*z - 0.24844 C1 - C2 z-1
C = --- = ------------------- = -----------
err z - 1 1 -z-1
Ricordo che z
-1 rappresenta un ritardo di un
ciclo di clock.
Ora c'è da fare i conti con gli errori di quantizzazione e di calcolo.
Il convertitore A/D è a 10 bit e comadato dal Timer2; al termine
della conversione viene generato un interrupt e viene eseguita la
funzione di regolazione, che ha come uscita il nuovo duty-cycle del
generatore PWM, anch'esso a 10 bit.
Sul ramo di ritorno è necessario un partitore in modo da
limitare la tensione di ingresso all'intervallo [0,5V]; R1 e R2 hanno
questo compito.
Il diagramma del sistema si modifica come segue:
a=12/34
Vu=C'H(Vi-aVu)
Vu C'H
-- = ------
Vi 1+aC'H
Per ritornare al modello precedente moltiplichiamo ambo i membri per a
e poniamo il nuovo ingresso a Vi/a;
questo semplicemente comporterà una divisione del valore di tensione
desiderato.
Vu aC'H CH
---- = ------ = ----
Vi/a 1+aC'H 1+CH
aC'=C
aC1' - aC2' z-1 C1 - C2 z-1
aC'= --------------- = C = ------------
1 - z-1 1 - z-1
aC1'=C1
aC2'=C2
Visto che si lavora con rappresentazioni digitali a 10 bit vogliamo
passare da D/err a pwm/[err]
[err]=err*1024/5
pwm=D*1024
D pwm/1024 pwm C1' - C2'z-1
C'= --- = ------------ = ------- = ------------
err [err]/1024*5 [err]*5 1 -z-1
pwm(1 - z
-1)=[err](5*C1/a - 5*C2/a z
-1)=[err](3.564 - 3.52 z
-1)
E' chiaro che con moltiplicazioni fra interi non si riuscirebbe ad
avere una precisione accettabile sui coefficienti; la soluzione
più semplice è usare come uscita il risultato diviso per un
multiplo di 2
e ricalcolare i coefficienti di conseguenza.
Considerato che il registro pwm è allineato a sinistra risulta
semplicissimo dividere per 64, ossia tralasciare gli ultimi 6 bit.
pwm(1 - z
-1)=[err](k1 - k2 z
-1)/64
k1=5C1/a*64=228.12 ~ 228
k2=5C2/a*64=225.25 ~ 225
Di seguito le risposte al gradino del sistema continuo (blu), di quello
campionato con controllore ideale (rosso) e di quello con i
coefficienti reali (verde); come si vede coincidono tutte.

Il programma di calcolo
è Octave (ovviamente open
source), giunto ormai alla versione 3 e utilizzabile senza troppi
problemi anche col noto sistema (quasi)operativo a finestre.
Se qualcuno fosse interessato
questi
sono i listati dei modelli descritti prima.
Il codice effettivo della funzione di controllo è quasi tutto in
assembly: questo si è reso necessario per ridurre il tempo di
esecuzione; infatti il compilatore C inserisce una chiamata per ogni
moltiplicazione (che in questo caso è a 16 bit) con conseguenti
salvataggi delle variabili sullo stack; alla fine il tempo di
esecuzione arrivava a 50us, ossia una frazione significativa del
periodo.
In assembly invece si risparmiano le chiamate e si può fare
senza sprechi la moltiplicazione 16x8 bit (visti k1 e k2); alla fine il
tempo di esecuzione risulta 12us.
Qualche forma d'onda:
Transitorio di accensione, 50 ms/div

Risposta al variare del carico (carico sopra, uscita in AC sotto),
50ms/div

Risposta al variare del segnale di ingresso (da 11,5 a 12,5 V), 50
ms/div
Come contribuire
Il modo migliore per contribuire a questo progetto è costruirlo, usarlo, e
segnalare eventuali bachi, idee, richieste di supporto.
In particolare rimangono da verificare parecchi dispositivi, vedi
supporto.
Chi avesse la pazienza e le conoscenze adatte può anche aggiungere altri algoritmi di
programmazione o altri comandi interni.
Cerco anche volontari per creare una gui linux, in particolare qualcuno esperto di QT.
Se trovate il progetto utile e lo usate scrivetemi due righe a

,
e se l'avete modificato mostratemi il vostro lavoro.
Scarica
Schema elettrico e
stampato:
archivio
completo
Firmware:
progetto MPLAB completo
o
firmware compilato(.hex)
oppure
una versione per 18F2450
(con funzionalità ridotta, vedi
schema elettrico).
OP (Linux)
OpenProg (windows):
solo il
programma;
i
sorgenti (progetto Visual Studio 6)
Listati per Octave
Cronologia
molto tempo fa: necessità di un programmatore USB affidabile e gratuito
2007:
esperimenti con i PIC USB e vari firmware; regolatore di tensione
2008:
primi prototipi e software
luglio 2008
documentazione e sito, pubblicata versione 0.3
agosto 2008
versione 0.4, aggiunto supporto per EEPROM I2C
novembre 2008
versione 0.5, bus I2C e SPI, aggiunti alcuni micro ATMEL
gennaio 2009
programmi controllo v0.5.1, aggiunti alcuni PIC, corretti vari bachi
marzo 2009
programmi controllo v0.5.2 e v0.5.3, aggiunti alcuni PIC e Atmel, corretti vari bachi
aprile 2009
schema elettrico e pcb v1.4
giugno 2009
versione 0.6, firmware USB GPL2, aggiunte memorie MicroWire 93Sx6
settembre 2009
versione 0.6.1, risolti alcuni bachi SPI, aggiunti alcuni Atmel e 93Cx6C
ottobre 2009
programmi controllo v0.6.2, risolti alcuni bachi
in futuro
ampliare supporto PIC e ATMEL (quando riuscirò a farmi spedire i campioni gratuiti),
aggiungere PIC24, dsPIC, ST72, EEPROM SPI, JTAG; compilare il firmware anche con SDCC;
migrare la GUI verso un sistema comune win-linux
Risorse in rete
Standard USB 2.0
HID page on USB.org
Breve guida al firmware HID
USB Central
USB & PIC
Microchip
Atmel
Documentazione su hiddev
Winpic
ICprog
Octave
GEDA-Gschem
PCB
licenze GNU/GPL
USBPicprog, un altro programmatore open source
Contatti
Per informazioni o commenti:
Alberto Maccioni