Interfaccia seriale programmabile sperimentale

  • Scheda sperimentale con PIC16F877 / 877A
  • Bootloader seriale all'indirizzo 1F00H
  • Comunicazione seriale 38400 8 N 2
  • Alimentazione 9..25Vcc, assorbimento 20mA
  • Regolatore 5V
  • Adattatore livelli RS232/TTL.
  • LED indicatori trasmissione/ricezione
  • Porte A B C D prolungate su morsettiere e connettore IDC 34 poli
  • All'accensione/reset viene letto un jumper sul pin RE0, se chiuso si avvia il bootloader, altrimenti si salta all'indirizzo 0005H dove deve esserci il programma applicativo.

La scheda

La scheda non è la solita demoboard dotata di pulsanti / display / relé ecc, ma è piuttosto pensata per un uso sperimentale spartano "al volo", mette infatti a disposizione in modo rapido le porte di un PIC16F877, sia attraverso un connettore IDC da 34 poli (che porta anche le alimentazioni) sia attraverso dei comuni morsetti a vite, comodi ad esempio per il collegamento ad una breadboard con spezzoni di filo rigido.




Può essere alimentata attraverso un jack coassiale, un morsetto a vite, e attraverso i due pin a lato del morsetto (con dei coccodrilli). Un diodo protegge dall'inversione di polarità. Il regolatore è alettato in modo da poter alimentare a 5V anche circuiti esterni che richiedono qualche centinaio di mA. Per evitare il surriscaldamento è bene non superare i 4W di dissipazione, il che vuol dire assorbire sui 5V una corrente massima di 200mA se si alimenta il tutto a 25V, che può salire fino a 1A alimentando il tutto a 9..10V

Sulla scheda trova posto il classico convertitore RS232/TTL realizzato con l'integrato MAX232, con l'aggiunta di due LED verdi che evidenziano il passaggio dei dati (si accendono quando transitano i bit a zero).

Per quanto riguarda il PIC i pochi componenti necessari sono i condensatori e il quarzo per il clock, il pulsante di reset, e il jumper con la resistenza di pull-up.




Il bootloader

Prima di essere inserito sulla scheda, il pic va preprogrammato con l'apposito bootloader nell'ultima pagina da 256 byte della memoria, e con le istruzioni di salto nei primi 4 indirizzi, questo permette di usare la scheda come interfaccia/periferica seriale trasferendovi di volta in volta l'applicativo necessario tramite un apposito programma loader. Durante il caricamento da seriale il bootloader si autoprotegge contro la sovrascrittura.

Una volta caricato il programma applicativo, la scheda lo può eseguire autonomamente ad ogni accensione (o reset), per fare questo basta lasciare aperto il jumper sul pin RE0. Se questo jumper viene invece tenuto chiuso allora all'accensione la scheda si predispone sempre in modalità bootloader attendendo comandi dalla seriale.

IMPORTANTE: Il programma utente deve iniziare all'indirizzo 4, dove deve esserci una istruzione NOP oppure una GOTO all'eventuale subroutine di gestione degli interrupt. Il programma vero e proprio inizia all'indirizzo 5. Alla fine del programma, all'indirizzo 1FFCH, va sempre messa una RETLW  con funzione di versione firmware. Questo valore viene letto dal bootloader su richiesta. I 3 indirizzi finali da 1FFDH a 1FFFH possono eventualmente ospitare altre RETLW se un'applicazione lo richiedesse.

            ORG    4
            NOP                ; NOP oppure GOTO ISR
INIZIO      ....
            ....
            ....
            ....
            ORG    1FFCH
            RETLW  207         ; Versione firmware

Il bootloader effettua una CALL all'indirizzo 5 quando all'accensione/reset trova aperto il jumper, oppure quando gli viene comandato via seriale. La figura seguente mostra i salti che avvengono.


                               +--------------+
                               | MOVLW 1FH    | 0000H
                               | MOVWF PCLATH |
                       .------ | GOTO  1FF0H  |
                       |       | NOP          |
                       |       +--------------+
                       |       | NOP          | 0004H
                       |       | ....         | <---------.
                       |       |              |           |
                       |       |              |           |
           JUMP 1FF0H  |       |              |           |
                       |       |              |           | CALL 0005H
                       |       |              |           |
                       |       |              |           |
                       |       |              |           |
                       |       |              |           |
                       |       +--------------+           |
                       '-----> |              | 1FF0H     |
                               |  BOOTLOADER  |           |
                               |   256 BYTES  | ----------'
                               |              |
                               +--------------+


Il bootloader configura dapprima tutte le porte come ingressi, poi controlla lo stato del jumper e se lo trova aperto salta all'indirizzo 5 (in realtà non fa un GOTO ma una CALL, in modo che, se servisse, con un semplice RETURN l'applicativo potrebbe ripassare il controllo al bootloader).

Se trova il jumper chiuso si mette in ascolto sulla seriale. La trasmissione/ricezione seriale è realizzata a software in modo da lasciare libera la USART interna. Nonostante l'uso di un quarzo a soli 4MHz, la velocità di comunicazione raggiunta è di 38400 bit/secondo grazie ad un uso ottimizzato dei cicli macchina nelle subroutines di trasmissione e ricezione.



Le funzioni del bootloader

Il bootloader ha tre funzioni che si attivano quando vengono ricevuti i byte 31 32 e 34, la prima è una semplice richiesta di sincronismo, il modulo risponde con un byte 66 (carattere ascii "B") e un secondo byte ricavato da una RETLW all'indirizzo 1FFCH (che si usa come indicatore versione firmware). La seconda riceve i dati da scrivere nella memoria flash, la terza è un "esegui" e forza una CALL all'indirizzo 5 avviando il programma applicativo senza bisogno di resettare il micro o aprire il jumper (la scheda funziona quindi come interfaccia/periferica esterna del PC).

La funzione load è la più complessa, richiede due byte che specificano l'indirizzo di memoria in cui scrivere, seguito da 4 words (8 byte). Il PIC 16F877 potrebbe scrivere in memoria una word alla volta, ma la versione 877A richiede la scrittura di 4 word alla volta inizianti ad indirizzi con i due bit bassi a zero, quindi 0, 4, 8, 12 ecc. Il bootloader di questo progetto utilizza il metodo della versione 877A in modo da poter usare entrambe le versioni senza alcuna modifica.

I valori dell'indirizzo e delle words vengono ricevuti sempre prima il byte basso e poi il byte alto. La scrittura nella flash comprende la verifica automatica, la funzione load risponde con il byte 79 (carattere ascii "O") se tutto è andato a buon fine, con 75 (carattere ascii "K") se si è riscontrato un errore durante la rilettura dei dati, o con 80 (carattere ascii "P") se si è tentato di sovrascrivere il bootloader.

Lo schema seguente riassume le tre funzioni del bootloader:


                      +--------+                  +--------+
               31 --->|        |           34 --->|        |
                      |  SYNC  |                  |  EXEC  |
         'B' + n  <---|        |                  |        |
                      +--------+                  +--------+


                      +--------+
               32 --->|        |
                      |  LOAD  |
           L ADDR --->|        |
           H ADDR --->|        |
                      |        |
           BYTE 1 --->|        |
                   :  |        |
           BYTE 8 --->|        |
                      |        |
'O' or 'K' or 'P' <---|        |
                      +--------+




boot.zip  Sorgente e .HEX del bootloader, compreso il programma di test lamp.asm




Per utilizzare le funzioni del bootloader di ricezione /  trasmissione / attesa silenzio sulla linea, un firmware applicativo può effettuare delle call ai seguenti indirizzi:

       1FF9H      Attesa 197 millisecondi silenzio dati
       1FFAH      Trasmissione seriale del valore di W
       1FFBH      Ricezione seriale, il valore e' posto in 7FH

Queste subroutines utilizzano gli ultimi 4 byte della ram da 7CH a 7FH.





Il loader seriale

Il bootloader sul PIC attende comandi sulla seriale, quindi sul PC occorre una controparte che glieli invii: il loader.

Il loader ha il compito di leggere un file .HEX prodotto dall'assemblatore MPASMWIN, estrarre le informazioni necessarie, ed inviarle al bootloader secondo quanto richiesto dalla funzione load.

Un programma per fare questo può essere scritto in qualsiasi linguaggio di programmazione che possa accedere ai files su disco e alla porta seriale. Per le prove con questa scheda il loader è stato scritto in python 2.5, e puo' usare indifferentemente sia porte seriali reali che virtuali attraverso convertitore USB/RS232:

#---------------------------------------------------------------------
#  Loader per PIC16F877/877A (versione seriale)
#  By Claudio Fin 26/10/2008
#
#  REQUISITI:
#  - Interprete Python 2.5 installato sul PC
#  - Estensioni per Windows installate
#  - Modulo pySerial installato
#
#  Scrivere nel programma il nome della porta seriale
#  che si vuole usare (esempio com1) e il nome del
#  file .HEX (esempio lamp.hex). Il file HEX si deve
#  trovare nella stessa directory del programma
#---------------------------------------------------------------------

import serial, struct
try:
    ser = serial.Serial("com1", 38400, 8, "N", 2, timeout=1)
    print "PORTA SERIALE OK"
    for riga in open("lamp.hex", "r"):
        riga = riga.rstrip("\n")
        if len(riga) < 9 or riga[7:9] != "00": continue
        print riga,
        ind = int(riga[3:7], 16) / 2
        dati = riga[9:-2].ljust(16, "F")
        if len(dati) > 16: dati = dati.ljust(32, "F")
        while dati:
            trasm = [32, ind%256, ind/256]
            trasm.extend([int(dati[i:i+2], 16) for i in range(0, 16, 2)])
            ser.write(struct.pack("B"*len(trasm), *trasm))
            risp = ser.read(1)
            if risp != "O":
                print "--\n\nVERIFICA SCRITTURA FALLITA!"
                raise IOError
            dati = dati[16:]
            ind += 4
        print "OK"
    print "\nTRASFERIMENTO COMPLETATO\n"
    
except IOError:
    print "\n\nTRASFERIMENTO FALLITO!\n"
    
ser.close()
raw_input("Invio per terminare...")


Il loader seriale ethernet

Se utilizziamo un ethernet serial server tramite protocollo TCP, ci svincoliamo completamente da driver e sistemi operativi, anzi, si potrebbe anche telecaricare la scheda da remoto. Il programma seguente è compatibile con tutte le versioni di Python dalla 2.5 in poi compresa la 3.x, e non è richiesta l'installazione di alcun modulo aggiuntivo.

#------------------------------------------------------------------------------
#  load.py - Loader TCP/IP per PIC16F877/877A
#  By Claudio Fin 10/8/2011
#
#  REQUISITI:
#  - Interprete Python da 2.5 a 3.x installato sul PC
#
#  Si puo' lanciare il programma da riga di comando
#  specificando il nome del file:
#
#      python load.py nomefile.hex
#
#  Oppure, se non specificato, si apre una dialog window
#  per la scelta del file.
#------------------------------------------------------------------------------

ADDR = "192.168.1.3", 4001    # Scrivere qui l'indirizzo del serial server

#----------------------------- Import moduli ----------------------------------
import socket, sys
from struct import pack
PY3 = sys.version_info[0] > 2
if PY3:
    from Tkinter import Tk
    from tkFileDialog import askopenfilename
else:
    from tkinter import Tk
    from tkinter.filedialog import askopenfilename

#----------------------- acquisizione nome del file ---------------------------
if len(sys.argv) > 1:
    nomeFile = sys.argv[1]
else:
    gui = Tk()
    nomeFile = askopenfilename(parent=gui, filetypes=(("Hex File", "*.hex"),))
    gui.destroy()

#------------------------------ Caricamento -----------------------------------
s = socket.socket()  # Socket TCP di default
s.settimeout(1)
try:
    s.connect(ADDR)
    for riga in open(nomeFile, "r"):
        riga = riga.rstrip("\n")
        if riga[0:1] != ":" or riga[7:9] != "00":  continue
        print(riga)
        ind = int(riga[3:7], 16) // 2
        dati = riga[9:-2]
        dati = dati.ljust(32 if len(dati) > 16 else 16, "F")
        while dati:
            trasm = [32, ind%256, ind//256]
            trasm.extend([int(dati[i:i+2], 16) for i in range(0, 16, 2)])
            s.sendall(pack("B"*len(trasm), *trasm))
            risp = s.recv(1024)[0]
            if PY3: risp = chr(risp)
            if risp != "O":  raise IOError
            dati = dati[16:]
            ind += 4
    print("\nTRASFERIMENTO COMPLETATO\n")

except:
    print("\nERRORE TRASFERIMENTO FALLITO\n")

finally:
    s.close()

#------------------------ Attesa invio per terminare --------------------------
msg = "Invio per terminare..."
input(msg) if PY3 else raw_input(msg)


Nota sui files .HEX

Qui sotto è riportato un file .HEX, e di seguito la sua scomposizione in colonne per identificare i dati che ci interessano. La prima indica la lunghezza in byte del campo dati, la seconda in blu l'indirizzo a cui iniziano, la terza indica il "tipo record", a noi interessano le righe con "00" che sono quelle che contengono i dati da trasmettere. La quarta colonna sono i singoli byte dati espressi in esadecimale. La quinta è un checksum. Tutti i valori sono espressi in esadecimale.



Il loader deve estrarre dalle righe che ci interessano l'indirizzo e il campo dati. L'indirizzo va diviso per due in quanto il file hex conta gli indirizzi in byte, mentre gli indirizzi per la scrittura nella flash si contano in words. Il campo dati può contenere da 2 a 16 byte (da 1 a 8 words), pertanto per la scrittura nella flash (che richiede 4 words al colpo) si devono eventualmente aggiungere dei byte fittizi a 0FFH per completare il blocco di 4 words.




La versione inscatolata




Questa seconda versione, inscatolata in un contenitore di recupero, è identica alla prima dal punto di vista software, ma più pratica per un uso "sul campo". I pin del PIC (esclusi alcuni) sono portati all'esterno tramite un connettore DB25-F. Sul pannello anteriore si trovano anche il pulsante di reset, l'interruttore run/boot, e i led power on e rtx dati. Sul pannello posteriore c'è il connettore DB9-F della RS232 e il coassiale di alimentazione. Il pannello posteriore serve anche come dissipatore per il regolatore 5V.

L'alimentazione Vcc può essere fornita attraverso il connettore coassiale, oppure attraverso un apposito pin del DB25 (dal quale può essere invece prelevata nel primo caso). Vengono portati all'esterno anche i +5V prodotti dal regolatore interno (a cui è meglio non far dissipare più di un paio di watt). Sul DB25 ci sono poi due masse, una collegata sul regolatore di tensione (massa digitale), l'altra direttamente ad un pin di massa del PIC (massa analogica). Quest' ultima è utile volendo usare il convertitore analogico/digitale interno, in modo da avere due distinti percorsi per segnali analogici e digitali.









Vista la bassa luminosità del led RX/TX ho dovuto aggiungere un circuito di disaccoppiamento e "allargamento". Adesso l'impulso luminoso causato dalla trasmissione o ricezione anche di un singolo byte è chiaramente visibile.




Per non perdere la praticità di collegamento con una bredboard è utile preparare un connettore/adattatore aggiuntivo da innestare sull' uscita dell'interfaccia, formato da una serie di "connettori strip" saldati ai teminali del DB25. Poiché il passo del DB25 è diverso da quello degli strip di contatti, è necessario tagliare questi ultimi in pezzi da 4 o al massimo 5 contatti. Questa suddivisione aiuta tra l'altro a non confondersi nel contare le posizioni.





L'interfaccia, collegata ad un ethernet serial server, può essere controllata da remoto attraverso la rete LAN.





ser_par.zip  Sorgente e .HEX firmware per usare l'interfaccia come I/O parallelo