lunedì 17 agosto 2009

Beagleboard: piano di battaglia per il connettore di espansione

Finora avevo parlato della Beagleboard quasi come se fosse un PC (installazione di Linux, Firefox, Gnome, compilazione del grosso interprete Ruby, playing di interi film e MP3...)

Ma sulla Beagleboard è disponibile anche una ricca quantità di interfacce per pilotare hardware esterno (il che apre una vera marea di possibilità e di progetti, anni luce al di sopra di quelli che smanettavamo con la Arduino, perché sulla Beagleboard abbiamo molta più potenza di calcolo, molta più memoria, un intero sistema Linux, accelerazione grafica, DSP, etc). Praticamente la Beagleboard unisce i vantaggi di un computer coi vantaggi di un microcontroller (ed in più consuma pochissimo, non ha ventole o parti in movimento, non scalda, ed è di dimensioni minuscole).

In particolare, oltre alle varie USB, DVI-D, etc, abbiamo:

- porte UART (universal asynchronous receiver/transmitter, cioè le porte seriali classiche), di cui una standard RS232 (sul connettore P9) e due in versione TTL 1.8V; normalmente bastano tre fili (TX, RX, GND, cioè trasmissione, ricezione e "ground" di riferimento); per applicazioni più complesse (dove è importante "fermare" l'interlocutore) ci sono anche i segnali CTS/RTS (clear-to-send e request-to-send); ad una seriale RS232 si può attaccare un'infinità di periferiche "vecchia scuola" (fino a non molto tempo fa ogni PC aveva almeno una porta RS232), inclusi, ad esempio, i modem analogici ed i ricevitori GPS che escono su RS232;

- porte TWI (two-wire-interface), dette anche I2C (inter-integrated-circuit, famoso per i nomi dei segnali clock e dato, SCL e SDA: solo due fili, più ovviamente GND); concettualmente sono seriali su cui ci si possono usare parecchie periferiche "moderne", fra cui notevolmente il Wii NunChuck Nintendo (che però è a 3.3V, per cui occorre un adapter), microcamere, sensori di vario genere (accelerometri, compass, etc);

- porte SPI (serial-peripheral-interface, composto da CLK, SIMO, SOMI, CS, cioè clock, slave-in/master-out, slave-out/master-in, chip-select; concettualmente è una porta seriale sincrona full duplex); alla SPI ci si agganciano periferiche meno comuni, per esempio il chip AD5206 (contenente sei potenziometri digitali a 8 bit), piccoli schermi LCD (come quello del Nokia 6610), USB-controller come il MAX3421E, etc;

- porte GPT/PWM (general-purpose timer e pulse-width-modulation), da usare come timer o come uscite PWM; non so immaginare al momento cosa ci si possa connettere, ma so che vengono usate per il dimming dei LED, o per generare segnali modulati, o controllare motori, etc (ma generalmente c'è da smanettarci di più, aggiungendo hardware extra);

- porte GPIO (general-purpose input/output), cioè singoli pin su cui è possibile "scrivere" zero o uno (ottenendo in output 0V oppure 1.8V rispetto a GND), oppure "leggervi" zero o uno (a seconda che vi siano stati posti 0V oppure 1.8V); praticamente sono "interruttori" maneggiabili da software (basta un pin GPIO per accendere un LED, oppure per leggere lo stato di un tasto o switch, etc); i LED presenti sulla Beagleboard sono infatti connessi ai GPIO149 e GPIO150; il suo write-protect dello slot SDIO/MMC è leggibile dal GPIO29; il suo tasto di reset è sul GPIO7; l'interfaccia DVI (più esattamente, il DVI-framer TFP410) si può disabilitare mandando uno zero sul GPIO170 (cfr. tab.5 del BBSRM).

Le porte sono disseminate tra l'Expansion Connector (J3, cioè un header da 28 pin a passo standard 0.1", facilmente "popolabile") ed il LCD Connector (J4 e J5, cioè due header da 20 pin a passo ridotto 0.05", alquanto arduo da "popolare"); l'LCD connector riporta infatti i segnali per pilotare un LCD, ma sui pin sono multiplexati ("MUX") anche GPIO e porte seriali: dato che non intendo usare LCD esterni (giacché ho già sulla Beagleboard un'uscita DVI-D ed una S-Video), allora posso utilizzarne i pin per le porte di I/O.

Qui sotto, alcuni appunti su come intendo utilizzare le porte, per avere a disposizione "un po' di tutto"; col multiplexing (MUX) scelgo le porte sacrificando le varie MMC2, McBSP, etc, che ritengo di non aver bisogno. Il risultato finale sarà di avere questa serie di interfacce (tutte a 1.8V):
- due porte I2C
- due porte UART TTL oltre alla RS232
- una porta SPI
- due pin GPT/PWM
- 12+26 = trentotto pin GPIO (mica male).

Nota: non ho parlato delle altre porte (il secondo slot MMC, le varie multichannel-BSP, etc) perché non intendo farne uso (per cui nel pin-out mi permetto di sacrificarle). Questa pagina intende essere solo un promemoria personale, per ispirare eventualmente altri progetti.

***

L'expansion connector (tab.20 del BBSRM_latest.PDF) ha 28 pin (due file da quattordici pin, disposte a passo di un decimo di pollice); sei di questi pin hanno una funzione fissa:

Pin 1: VIO +1.8V (la logica I/O della Beagleboard è a 1.8V)
Pin 2: DC +5V (dal jack esterno 5V o dalla USB)
Pin 25: REGEN
Pin 26: nRESET
Pin 27: GND
Pin 28: GND (sì, è un comodo duplicato)

Restano perciò i pin da 3 a 24, che intendo allocare così:
- una porta SPI
- una porta I2C (sullo schema del BBSRM citato è indicata come "I2C2")
- due pin GPTimer/PWM
- una seriale UART, senza CTS/RTS perché destinata a leggere dati da un GPS; attenzione: la UART RS232 di serie ha già il suo header (P9, sul lato della USB OTG) che intendo usare solo per l'accesso da console, mentre questa UART (sul connettore di espansione, indicata negli schemi come "UART2") è TTL 1.8V
- dodici pin di general-purpose I/O (alcuni dei quali da associare a tasti).

Ad eccezione dei GPIO associati ai tasti, occorreranno dei level converters (come ad esempio questo o quest'altro, anche se consiglio di studiare bene quanto detto qui) perché non si trova niente in giro che funzioni a 1.8V (c'è invece una marea di periferiche a 5V o a 3.3V; per esempio il Wii NunChuck usa una I2C a 3.3V). Tutta la logica della Beagleboard è a 1.8V, salvo altri voltaggi solo per usi specifici (in questo modo si risparmia sui consumi e sui costi di produzione; uno aggiunge i level converter solo dove servono davvero).

Importante: sui connettori LCD (J4 e J5, descritti nelle tab.22/23 del BBSRM sopra indicato), a seconda dei MUX, sono disponibili (solo sulle Beagleboard versione C2 e successive):
- un'altra UART (sullo schema indicata come "UART1")
- un'altra SPI (sullo schema indicata come "multichannel SPI3")
- un'altra I2C (sullo schema indicata come "I2C3")
- fino ad altre ventotto linee GPIO (!!)

Questi due connettori (il cui pin 1 è quello dove c'è il bordo bianco in grassetto, perciò fare attenzione! non sono simmetrici con l'Expansion Connector) sono a passo di un ventesimo di pollice (ouch!). Le loro ball sono descritte nella tab.14 del BBSRM.

Nota bene: la RS232 dotata di header è "UART3", mentre la "UART2" (TTL 1.8V) è quella disponibile sull'Expansion Connector e la "UART1" (TTL 1.8V) è quella disponibile sull'LCD connnector. La UART3 è disponibile anche in TTL 1.8V, ma è inutile (a meno di non avere urgentissimo bisogno di tre porte seriali TTL).


Pin-out che scelgo per l'Expansion Connector:
- SPI4: 12,18,16,20
- I2C2: 23,24
- GPT10_PWMEVT: 10
- GPT9_PWMEVT: 4
- UART2: 6,8
- GPIO: 3,5,7,9, 11,13,14,15, 17,19,21,22

Nota: se per la UART2 si volessero anche le funzioni RTS e CTS, occorrerà sacrificare i due pin GPT/PWM; infatti l'RTS della UART2 è sul pin 10(MUX0) mentre il suo CTS è sul pin 4(MUX0).


Configurazione dei MUX

Occorre anzitutto la corretta configurazione del MUX (poiché i singoli pin possono avere diversi significati, il valore MUX assegna una delle possibili funzionalità ad ogni pin).

Si può configurare il MUX sia da kernel Linux, sia da UBoot; generalmente la soluzione preferibile mi pare quella del kernel, perché con l'UBoot è più facile pasticciare, e poi anche perché in questi primi tempi può capitare spesso di compilarsi un kernel aggiornato.

Ecco i MUX necessari per la mappa sopra indicata:

UART2: 6(tx):MUX0; 8(rx):MUX1
I2C2: 23(sda), 24(scl): entrambi MUX0
SPI: 12(simo),16(cs0),18(somi),20(clk): tutti MUX1
GPT_PWM: 10, 4: entrambi MUX2
GPIO: 3,5,7,9, 11,13,14,15, 17,19,21,22: tutti MUX4

Ecco perciò la mappa risultante delle funzionalità associate ai pin e dei MUX necessari per inizializzarle (le porte che richiedono il GND, come la UART, possono tranquillamente usare i pin 27 e 28):

3: GPIO(139), MUX4
4: GPT9_PWMEVT, MUX2
5: GPIO(138), MUX4
6: UART2_TX, MUX0
7: GPIO(137), MUX4
8: UART2_RX, MUX1
9: GPIO(136), MUX4
10: GPT10_PWMEVT, MUX2
11: GPIO(135), MUX4
12: SPI_SIMO, MUX1
13: GPIO(134), MUX4
14: GPIO(162), MUX4
15: GPIO(133), MUX4
16: SPI_CS0, MUX1
17: GPIO(132), MUX4
18: SPI_SOMI, MUX1
19: GPIO(131), MUX4
20: SPI_CLK, MUX1
21: GPIO(130), MUX4
22: GPIO(157), MUX4
23: I2C2_SDA, MUX0
24: I2C2_SCL, MUX0


Pin-out che scelgo per l'LCD Connector:

Nota: i connettori J4 e J5 sono di 10+10 pin ciascuno; sul primo abbiamo i pin 1 e 2 collegati al +5V (sì, +5V, anche se il resto dei segnali sono a 1.8V) e sul secondo abbiamo i pin 1 e 2 collegati rispettivamente a 1.8V e 3.3V; su entrambi abbiiamo i pin 19 e 20 collegati a massa (GND).

Header J4 (quello più vicino al bordo esterno):
- dal pin 3 al pin 14: tutti GPIO (MUX4)
- pin 15: I2C3 SDA (MUX0)
- pin 16 e 17: GPIO (MUX4)

Header J5 (quello più interno):
- dal pin 3 al pin 8: tutti GPIO (MUX4)
- pin 9: UART1 RX (MUX2)
- pin 10, 11 e 13: GPIO (MUX4)
- pin 14: I2C3 SCL (MUX0)
- pin 15: UART1 TX (MUX2)
- pin 16, 17 e 18: GPIO (MUX2)

Risultato:
- J4, pin 1: +5V
- J4, pin 2: +5V
- J4, pin 3: GPIO(71), MUX4
- J4, pin 4: GPIO(70), MUX4
- J4, pin 5: GPIO(73), MUX4
- J4, pin 6: GPIO(72), MUX4
- J4, pin 7: GPIO(75), MUX4
- J4, pin 8: GPIO(74), MUX4
- J4, pin 9: GPIO(82), MUX4
- J4, pin 10: GPIO(79), MUX4
- J4, pin 11: GPIO(93), MUX4
- J4, pin 12: GPIO(84), MUX4
- J4, pin 13: GPIO(89), MUX4
- J4, pin 14: GPIO(92), MUX4
- J4, pin 15: I2C3_SDA, MUX0
- J4, pin 16: GPIO(81), MUX4
- J4, pin 17: GPIO(68), MUX4
- J4, pin 18: non utilizzato
- J4, pin 19: GND
- J4, pin 20: GND

- J5, pin 1: +3.3V
- J5, pin 2: +1.8V "reference rail"
- J5, pin 3: GPIO(90), MUX4
- J5, pin 4: GPIO(91), MUX4
- J5, pin 5: GPIO(87), MUX4
- J5, pin 6: GPIO(88), MUX4
- J5, pin 7: GPIO(85), MUX4
- J5, pin 8: GPIO(86), MUX4
- J5, pin 9: UART1_RX, MUX2
- J5, pin 10: GPIO(83), MUX4
- J5, pin 11: GPIO(78), MUX4
- J5, pin 12: non connesso
- J5, pin 13: GPIO(79), MUX4
- J5, pin 14: I2C3_SCL, MUX0
- J5, pin 15: UART1_TX, MUX2
- J5, pin 16: GPIO(66), MUX4
- J5, pin 17: GPIO(69), MUX4
- J5, pin 18: GPIO(67), MUX4

Nota: a quanto pare il pin 12 del J5 ed il pin 18 del J4 non sono stati utilizzati nella Beagleboard.

Importante: il +1.8V è offerto solo come "reference", non deve essere usato per alimentare periferiche. Sul +3.3V si può alimentare qualcosa, a patto di richiedere pochissima corrente; in caso di necessità, il BBSRM suggerisce di disabilitare il TFP410 così da poter assorbire i suoi 80mA (nientemeno!) sulla 3.3V; altrimenti, recuperare i 3.3V "regolandoli" dal +5V sopra citato (quando sento qualcuno che mi suggerisce due diodi in cascata, mi vien da dire yeeeek!!!).


Utilizzo delle porte seriali da software

Le porte UART vengono viste dal kernel Linux come normalissime porte seriali: /dev/ttyS0 (UART1), /dev/ttyS1 (UART2) e /dev/ttyS2 (UART3, quella che è già adattata ai livelli RS232 sull'header P9), indipendentemente dalla presenza di level-converters. Attenzione: ad eccezione della porta standard RS232, sulle UART1 ed UART2 la logica è TTL 1.8V (non sarete mica così tonti da mandarci sopra i 12V di una seriale RS232 normale o i 5V o 3V di una seriale TTL 5V o 3V!!!)

Per motivi storici di compatibilità coi terminali seriali Unix degli anni settanta e ottanta, c'è tutto un piccolo guazzabuglio di regole da seguire per inizializzarle da software nel modo più comodo (cioè per leggere un byte alla volta, o una riga alla volta, oppure leggere senza dover aspettare l'arrivo di caratteri, etc). In compenso quel guazzabuglio è talmente standard che funzionerà su qualsiasi porta seriale associata a un /dev/tty... (dunque si può provare su una seriale del PC e poi ricompilare lo stesso codice comodamente per la Beagleboard).


Utilizzo dei GPIO da software

Per utilizzare i pin di GPIO da software occorre abilitarli (col tanto deprecato sysfs): per esempio, seguendo l'esempio di cui sopra, il pin 22 dell'Expansion Connector corrisponde al GPIO 157. Per attivarlo e configurarlo come output:
echo 157 >/sys/class/gpio/export
echo out >/sys/class/gpio/gpio157/direction

A questo punto vi si potrà scrivere un valore 0 o 1; per esempio:
echo 1 >/sys/class/gpio/gpio157/value

Invece, per attivare ed utilizzare come input la linea GPIO 157, è sufficiente utilizzare la direzione "in" anziché "out":
echo 157 >/sys/class/gpio/export
echo in >/sys/class/gpio/gpio157/direction

E quindi, da software, si può leggere dal file "value" il valore che in quel momento si vede sulla porta. Per esempio:
more /sys/class/gpio/gpio157/value

Nota bene: i pin di GPIO sono configurabili o come "output", o come "input e output". Infine, per disabilitare una porta GPIO basta usare "unexport"; per esempio:
echo 157 >/sys/class/gpio/unexport

Nota: se la porta di GPIO è da utilizzare come tasto, c'è tra i sorgenti del kernel il board-omap3beagle.c che contiene già una funzione gpio_key_button per associare un pin GPIO ad un /dev/input/event* che le applicazioni grafiche Linux vedranno come tasto. Utilissimo per implementare una mini-tastiera.


Esempio: configurazione dei MUX della seconda I2C nell'UBoot

Cominciamo dall'indispensabile, cioè la I2C2, il cui pin-mux da sistemare nei sorgenti di UBoot è:
  MUX_VAL(CP(I2C2_SCL),  (IEN  | PTU | EN  | M0)) /*I2C2_SCL*/\
  MUX_VAL(CP(I2C2_SDA), (IEN  | PTU | EN  | M0)) /*I2C2_SDA*/\

Spiegazione:
- IEN: "input enable" (cioè la porta è bidirezionale)
- M0: "utilizzare MUX0"
- EN: "abilitare pull-up/pull-down" (DIS per disabilitarlo)
- PTU: "scegliere pull-up"

Nota: si presuppone che nel kernel Linux, nel file board-omap3beagle.c, nella funzione omap3_beagle_init(), la porta I2C numero 2 sia correttamente registrata e inizializzata a 100kHz:
        omap_register_i2c_bus(2, 100, NULL, 0);

Dopodiché la porta può essere utilizzata secondo le indicazioni documentate nei sorgenti Linux, nel file Documentation/i2c.txt


Configurazione e multiplexing dei pad

Nel Technical Reference Manual OMAP35xx (un PDF di 38Mb circa, indicato generalmente con "OMAP3 TRM" o "SPRUF98B") viene descritto, a partire dal capitolo 7.4.4, il meccanismo con cui le varie porte GPIO sono associate alle (limitate) connessioni esterne (qui indicate con pin ma nella documentazione sono indicate anche con pad o ball a seconda del contesto).

In teoria l'OMAP35xx supporterebbe circa 270 GPIO; in pratica il numero è decisamente più ridotto a causa del limitato numero di pin disponibili (ulteriormente limitato dalle necessità hardware della Beagleboard: per esempio una trentina di pad se ne vanno per la sola GPMC, general purpose memory controller; la tab.4 del BBSRM indica una parte del resto del setup essenziale). Un articolo completo sul pin multiplexing è sul solito ELinux. Con la mappa sopra citata, avere 38 pin di GPIO più un bel po' di porte extra (UART, I2C, etc) sarà sufficiente per una grande quantità di scopi.

In sintesi:
- ogni pin (cioè ogni ball, o pad; per quel che ci riguarda li consideriamo sinonimi) può avere fino a sette funzioni diverse (dal Mode 0 al Mode 6, cioè da MUX0 a MUX6 - anche se come abbiamo visto sopra sulla Beagleboard ho avuto bisogno solo di MUX0, MUX1, MUX2 e MUX4), più un "safe mode" (MUX7) utilizzato come default su tutte le porte non usate;
- ogni pin può essere configurato come "solo output" oppure "input/output (bidirezionale)"; in questo secondo caso, l'OMAP3 dà anche la possibilità di avere un pull-up o pull-down;
- per ogni due pin, l'OMAP3 ha un registro a 32 bit, cioè 16 bit di controllo per ciascuno (tutto questo lusso serve perché ad ogni pin si può associare un pull-up o pull-down, oppure l'essere di solo input o di input/output, o essere di "wake" per risvegliare la board dallo stand-by, e altre funzioni ancora);
- tali registri sono memory-mapped (nel sopracitato SPRUF98B vengono indicati gli indirizzi esatti) e vanno letti e scritti utilizzando una read/write a 16 bit all'indirizzo indicato (o due byte più avanti qualora si tratti dei sedici bit più significativi del registro da 32);
- la tabella dei registri è nel banco di memoria che comincia all'indirizzo 0x48002000; qui sotto riporto gli offset dell'expansion connector J3 - ho ottenuto la tabella confrontando le indicazioni della Tab.20 del BBSRM per i MUX0 con i valori "Mode 0" della tabella 7.4 dello SPRUF98B, aggiungendo l'offset +2 quando si trattava dei bit [16:31] (così come indicato qui):

J3, pin 3: 0x16a
J3, pin 4: 0x174
J3, pin 5: 0x168
J3, pin 6: 0x178
J3, pin 7: 0x166
J3, pin 8: 0x172
J3, pin 9: 0x164
J3, pin 10: 0x176
J3, pin 11: 0x162
J3, pin 12: 0x190
J3, pin 13: 0x160
J3, pin 14: 0x198
J3, pin 15: 0x15e
J3, pin 16: 0x196
J3, pin 17: 0x15c
J3, pin 18: 0x192
J3, pin 19: 0x15a
J3, pin 20: 0x18c
J3, pin 21: 0x158
J3, pin 22: 0x18e
J3, pin 23: 0x1c0
J3, pin 24: 0x1be

Per configurare un pin abbiamo bisogno di fare un OR di questi valori:
#define INPUTENABLE 0x0080 // abilita anche l'input
#define PULLUP      0x0008 // abilita pull-up sull'input
#define PULLDOWN    0x0018 // abilita pull-down sull'input
#define MUX(a)      (a)    // sceglie il MUX mode
#define SAFEMODE    MUX(7) // disabilita il pin

Per esempio, per configurare il pin 22 per GPIO157 (che come abbiamo visto sopra richiede MUX4), abilitato all'input oltre che all'output e col suo bravo pull-down:

short cfg = MUX(4) | INPUTENABLE | PULLDOWN;
short *addr = 0x48002000 + 0x18e;
*addr = cfg;


Disclaimer: always double-check everything! While I try to be as accurate as possible, don't take too seriously my page!

To be continued...

Nessun commento:

Posta un commento