Android: Dai driver all’interfaccia (Prima Parte..)
Android: Dai driver all’interfaccia (Prima Parte..)
feb 16
Ultimamente mi è capitato di dovermi confrontare con uno sviluppo...a basso livello, diciamo, per una piattaforma di sviluppo basata su Android.
Ho dovuto infatti interfacciare un dispositivo esterno (una “bottoniera”, con bottoni fisici a pressione variabile e configurabile)
Partiamo dall’inizio comunque: i passi da seguire sono scritti in ROSSO (non mi andava di numerarli, troppo fiscale)
Anzitutto, prima di interfacciare un dispositivo su Android, è necessario scaricare le NDK.
Una volta scaricate, è necessario configurare CYGWIN in modo tale che possiate compilare il vostro duro e sudato lavoro
N.B. Purtroppo per mancanza di voglia(e di pazienza
) non ho la possibilità di dettagliare tutti i passaggi, nel caso che qualcuno sia interessato a maggiori informazioni, potrà comunque contattarmi e cercherò di risolvere i vostri dubbi
Comunque, una volta presi questi 2 tool, è necessario installare le SDK Android.
Come consiglio personale, vi segnalo l’integrazione tra ECLIPSE ed SDK Android, che vi permette di avere l’ambiente di sviluppo completo, senza troppi problemi.
Per i neofiti(anche se l’articolo è consigliato a qualcuno che ha già un pochino di esperienza) sappiate che le NDK e le SDK non sono la stessa cosa.Infatti, le NDK permettono di compilare codice scritto in C per piattaforme ARM,relativo al sottostante layer Linux, mentre le SDK permettono di compilare codice JAVA, per il layer di Android. Se non avete intenzione di sviluppare codice nativo, probabilmente non vedrete mai le NDK e vi consiglio di saltare la lettura di questo articolo(anche se un “mi piace” non “mi dispiacerebbe” ugualmente
)
Comunque, dicevo, l’integrazione di tutti questi tool vi permetterà di scrivere/compilare un driver nativo, utilizzarlo in java dal layer Android e visualizzarne i risultati.
Una volta installato tutto, è necessario verificare che sul vostro dispositivo sia disponibile il device per il quale andrete a scrivere il driver. Potrete trovare tutta la lista dei device disponibili sotto la directory /dev
Nel mio caso, il device è “/dev/i2c-0″ (di solito Linux enumera i dispositivi sullo stesso bus con i trattini ed il numero)
N.B. Le regole che sto scrivendo qui si applicano più o meno allo stesso modo per interfacciarsi(ovviamente seguendo un protocollo di comunicazione diverso) con dispositivi su altri bus (usb, uart, seriale etc)
Una volta trovato il vostro device, dovrete cominciare a scrivere codice.. Come consiglio per la formattazione e la colorazione del testo, suggerisco Notepad++.
Vediamo quindi di cosa si compone questo file .c (che io ho chiamato per l’occasione “native.c”
)
Parte di dichiarazione
#include <jni.h>
#include <android/log.h>
#include <linux/i2c.h>
#include <termios.h>
#include <unistd.h>
#include <stdint.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <stdio.h>
#include <stdbool.h>
#define DEBUG_TAG “NDK_I2CBUTTONS_LISTNER”
La discussione di tutte queste librerie esula dall’articolo, purtroppo. E’ importante comunque notare la libreria jni.h, richiesta dal sistema SEMPRE e la libreria di android log.h (che servirà per scrivere sul registro di log).Comunque, come primo inizio, andiamo a definire una funzione di sistema che ci permetterà di risalire al valore letto dal dispositivo.
static int get_i2c_button(int file, char addr, __u8 reg, unsigned char *buffer){
//__android_log_print(ANDROID_LOG_DEBUG, DEBUG_TAG, "Reading button at address %i, register %i",addr,reg);
buffer[0]=0;
if (file < 0) {//cannot open
__android_log_print(ANDROID_LOG_DEBUG, DEBUG_TAG, "Errore non sono riuscito ad aprire il device richiesto");
return 2;
}
if (ioctl(file, I2C_SLAVE, addr) < 0) {
__android_log_print(ANDROID_LOG_DEBUG, DEBUG_TAG, "Non sono riuscito ad effettuare la ioctl indirizzo : %i",addr);
return 2;
}
//char registro = reg[0];
char buf[1];
char readBuf[1];
buf[0] = reg;
if (write(file, buf, 1) != 1) {
__android_log_print(ANDROID_LOG_DEBUG, DEBUG_TAG, "Write su registro %d fallita",reg);
return 2;
}else{
//__android_log_print(ANDROID_LOG_DEBUG, DEBUG_TAG, "Write OK");
//ok we can go ahead
}
if (read(file, readBuf, 1) != 1) {
__android_log_print(ANDROID_LOG_DEBUG, DEBUG_TAG, "Transazione i2c fallita:LETTURA ");
return 2;
} else {
//__android_log_print(ANDROID_LOG_DEBUG, DEBUG_TAG, "Lettura corretta su address: %i registro: %i valore: %i",addr,buf[0],readBuf[0]);
buffer[0]=readBuf[0];
return 0;
}
}
Come avete visto, ho definito una funzione static int get_i2c_button(int file, char addr, __u8 reg, unsigned char *buffer)
Tale funzione prende come parametro un descrittore di file (un int), l’indirizzo del dispositivo, l’indirizzo del registro da richiamare, ed il buffer di callback. E’ importante notare che nel caso di errore, la funzione ritorni il valore 2 a segnalare che c’è stato qualche problema. L’ulteriore log con i dettagli viene poi richiamato tramite __android_log_print(), funzione disponibile da android/log.h
Nel caso in cui non ci siano problemi, viene eseguita una IOCTL,che segnala che su quel descrittore di file verrà aperto uno SLAVE all’indirizzo addr. Se anche questo va a buon fine, vengono eseguite prima una write sul bus(in questo caso non è importante cosa ci sia nel buffer, ovviamente. Basta che il bus “senta” la scrittura) e successivamente una read (ed in questo caso, nel buffer ci finisce il valore del registro!)A questo punto è possibile ritornare il codice di completamento (diciamo uno 0 per indicare che ci sono stati 0 errori, anche se di solito è preferibile un 1).
Nei prossimi articoli, se ci sono interessati (
) parlerò di come compilare il tutto e di come integrarlo in Android ad alto livello.
Ciao
Well done!
Grazie Alessio