1. 程式人生 > >Android 實現串列埠的移植

Android 實現串列埠的移植

安卓串列埠的實現,需要底層C++配合,不過這次我們根據framework中的思想,直接用API修改提供給JAVA層呼叫,這個就比較簡單了。

DEV專案需要,要實現在Android中實現串列埠的收發功能,有幾種方法可以參考使用。

1. 標準的Android HAL層思想,把串列埠的功能加入framework的API中(類似於android中sensor的實現)

    a. 確保驅動層中基於tty的串列埠驅動可以正常read、write、poll資料,當然了,也可以自己寫一個字元驅動來實現串列埠的讀寫功能。

    b. 在BSP的HAL層中新增串列埠讀寫功能的回撥函式(linux 應用層 c/c++)

    c. Android framework中新增jni層,解析HAL中生成的module,然後對回撥函式進行封裝,生成.so庫,提供給java層。

    d. 新增遠端呼叫介面,使用aidl在framework中新增遠端呼叫

    e. 新增serviceManagement

2. 繞過HAL,直接使用JNI來完成讀寫等回撥函式,之後同1 。

3. 繞過android系統,直接編寫jni庫,在應用程式中直接呼叫jni介面,完成串列埠的收發。

------------------------------------------------------------------------------

以上都是可用的方法,這裡我採用最簡單的第三種方法,其中第一種方法最繁瑣,但也是android最標準的方法,之後我會在can bus的移植中使用(先打個啞謎^0^),OK 廢話不多說,開始碼程式碼,工作!

首先是驅動層,我使用的是fsl的開發板,這邊freescale已經幫我們實現了驅動,可以在/dev/下發現ttymxc0,ttymxc1.。。。這些就是CPU上各個串列埠的驅動檔案,可以嘗試echo "123" > /dev/mxctty0 之後可以看到串列埠終端上會打印出“123”。

但是,我們做驅動的不能就這樣拿著別人的東西就用,咱要分析,要學習,要膜拜,要抄襲,要。。。貌似我最喜歡幹這種事情了,好吧,這裡我自己照著Linux裝置驅動詳解這書寫了一個虛擬的字元驅動,當做我們的串列埠吧。

提供了跟串列埠同樣的功能,這個驅動中我使用阻塞的方式來讀寫資料,一邊看書,一邊學習,一邊自己寫程式碼,一邊學習jni,一邊學習android的框架,何樂而不為呢?

首先,我們要註冊一個字元驅動,然後初始化等待佇列,初始化訊號量,初始化變數,給結構體分配記憶體空間,老一套了。。。是個寫驅動的都知道要幹這些事情。 

/*裝置驅動模組載入函式*/  
int globalfifo_init(void)  
{  
    int result;  
    globalfifo_devp = kmalloc(sizeof(struct globalfifo_dev) ,GFP_KERNEL);  
    if(!globalfifo_devp) {  
        result = -ENOMEM;  
    }  
    memset(globalfifo_devp, 0, sizeof(struct globalfifo_dev));  
      
    globalfifo_devp->mdev = mdev_struct;  
  
    result = misc_register(&(globalfifo_devp->mdev));  
    if(result<0)  
        return result;  
  
    init_MUTEX(&globalfifo_devp->sem);   /*初始化訊號量*/  
    init_waitqueue_head(&globalfifo_devp->r_wait); /*初始化讀等待佇列頭*/  
    init_waitqueue_head(&globalfifo_devp->w_wait); /*初始化寫等待佇列頭*/  
  
    return 0;  
  
}  


看到沒,這裡使用了miscdevice驅動,這個簡單容易實現,HOHO~~偷懶了。這裡給我們的全域性結構體分配了記憶體空間,然後把結構體操作函式掛到我們的全域性結構體變數中,最後註冊這個miscdevice驅動。

/*globalfifo裝置結構體*/  
struct globalfifo_dev                                       
{                                                          
//  struct cdev cdev; /*cdev結構體*/                         
    struct miscdevice mdev;  
    unsigned int current_len;    /*fifo有效資料長度*/  
    unsigned char mem[GLOBALFIFO_SIZE]; /*全域性記憶體*/          
    struct semaphore sem; /*併發控制用的訊號量*/             
    wait_queue_head_t r_wait; /*阻塞讀用的等待佇列頭*/       
    wait_queue_head_t w_wait; /*阻塞寫用的等待佇列頭*/       
};  


view plain 

看到我們的globalfifo結構體的定義了吧,這裡,就是這裡,所以在init函式中,我們要初始化訊號量,初始化讀寫等待佇列頭。要不咱先來講講這裡的阻塞的概念吧。

顧名思義,就是堵在那邊不動了,其實是真的不動了,利用等待佇列實現裝置的阻塞,當用戶程序訪問系統資源的時候,當這個資源不能被訪問,我們又不想讓之後的事情繼續發生,這樣的話我們就可以阻塞在那邊,放心,我們可以讓該程序進入休眠,這樣的話就不會浪費CPU的資源了,然而等到這個資源可以訪問的時候,我們就可以喚醒該阻塞的程序,繼續讓他執行下去,如果沒有地方喚醒他,那他就真的“堵死”在那邊了。

簡單的介紹了下,接下來看看我們要實現哪些功能函式 plaincopy 

/*檔案操作結構體*/  
static const struct file_operations globalfifo_fops =  
{  
    .owner = THIS_MODULE,  
    .read = globalfifo_read,  
    .write = globalfifo_write,  
    .ioctl = globalfifo_ioctl,  
    .poll = globalfifo_poll,  
    .open = globalfifo_open,  
    .release = globalfifo_release,  
}; 


咱有讀,寫,開啟。。。。等函式,繼續往下分析。

struct globalfifo_dev *globalfifo_devp; /*裝置結構體指標*/  
/*檔案開啟函式*/  
int globalfifo_open(struct inode *inode, struct file *filp)  
{  
    /*將裝置結構體指標賦值給檔案私有資料指標*/  
    filp->private_data = globalfifo_devp;  
    return 0;  
}  
/*檔案釋放函式*/  
int globalfifo_release(struct inode *inode, struct file *filp)  
{  
    return 0;  
}  


open和release函式沒什麼好說的了,其實這裡還是蠻有講究的,比如說這個裝置我們只能讓一個使用者進行訪問,那我們可以再open函式裡面做點手腳,一般我們讀核心驅動模型的時候都會看到很多時候在open函式中都會設計引用計數自加1,這樣的話可以更好的管控我們裝置被開啟次數。

但是這裡我們沒做什麼,我們只是把我們的全域性結構體變數賦值給了這裡filp的一個私有成員變數中,這樣的話我們可以再每一個功能函式中取出這個私有成員,有利於程式碼的可讀性,release就不講了。

/*globalfifo讀函式*/  
static ssize_t globalfifo_read(struct file *filp, char __user *buf, size_t count,  
loff_t *ppos)  
{  
    int ret;  
    struct globalfifo_dev *dev = filp->private_data; //獲得裝置結構體指標  
    DECLARE_WAITQUEUE(wait, current); //定義等待佇列  
  
    down(&dev->sem); //獲得訊號量  
    add_wait_queue(&dev->r_wait, &wait); //進入讀等待佇列頭  
  
    /* 等待FIFO非空 */  
    while (dev->current_len == 0)  
    {  
        if (filp->f_flags &O_NONBLOCK)  
        {  
            ret = - EAGAIN;  
            goto out;  
        }   
        __set_current_state(TASK_INTERRUPTIBLE); //改變程序狀態為睡眠  
        up(&dev->sem);  
  
        schedule(); //排程其他程序執行  
        if (signal_pending(current))  
        //如果是因為訊號喚醒  
        {  
            ret = - ERESTARTSYS;  
            goto out2;  
        }  
  
        down(&dev->sem);  
    }  
  
    /* 拷貝到使用者空間 */  
    if (count > dev->current_len)  
        count = dev->current_len;  
  
    if (copy_to_user(buf, dev->mem, count))  
    {  
        ret = - EFAULT;  
        goto out;  
    }  
    else  
    {  
        memcpy(dev->mem, dev->mem + count, dev->current_len - count); //fifo資料前移  
        dev->current_len -= count; //有效資料長度減少  
        printk(KERN_INFO "read %d bytes(s),current_len:%d\n", count, dev->current_len);  
  
        wake_up_interruptible(&dev->w_wait); //喚醒寫等待佇列  
  
        ret = count;  
    }  
out:   
    up(&dev->sem); //釋放訊號量  
out2:  
    remove_wait_queue(&dev->w_wait, &wait); //從附屬的等待佇列頭移除  
    set_current_state(TASK_RUNNING);  
    return ret;  
}  



然後這就是我們的讀函式,在進行讀之前,我們把等待佇列加進我們的佇列連結串列中,然後檢查我們的buff是否為空,如果為空的話,那就沒什麼好讀的了,所以我們讓進城休眠,當有貨給我們讀了,再喚醒我們的佇列。

首先是把當前進城加入等待佇列中add_wait_queue(&dev->r_wait, &wait); 

沒東西讀的時候,使程序睡眠,在排程到別的任務去ew plaincopy

/* 等待FIFO非空 */  
while (dev->current_len == 0)  
{  
    if (filp->f_flags &O_NONBLOCK)  
    {  
        ret = - EAGAIN;  
        goto out;  
    }   
    __set_current_state(TASK_INTERRUPTIBLE); //改變程序狀態為睡眠  
    up(&dev->sem);  
  
    schedule(); //排程其他程序執行  
    if (signal_pending(current))  
    //如果是因為訊號喚醒  
    {  
        ret = - ERESTARTSYS;  
        goto out2;  
    }  
  
    down(&dev->sem);  
}  


這段程式碼比較關鍵,與寫函式中一樣,當我們的buff被寫滿時,我們也會發生阻塞。

/*globalfifo寫操作*/  
static ssize_t globalfifo_write(struct file *filp, const char __user *buf,  
size_t count, loff_t *ppos)  
{  
    struct globalfifo_dev *dev = filp->private_data; //獲得裝置結構體指標  
    int ret;  
    DECLARE_WAITQUEUE(wait, current); //定義等待佇列  
  
    down(&dev->sem); //獲取訊號量  
    add_wait_queue(&dev->w_wait, &wait); //進入寫等待佇列頭  
  
    /* 等待FIFO非滿 */  
    while (dev->current_len == GLOBALFIFO_SIZE)  
    {  
        if (filp->f_flags &O_NONBLOCK)  
        //如果是非阻塞訪問  
        {  
            ret = - EAGAIN;  
            goto out;  
        }   
        __set_current_state(TASK_INTERRUPTIBLE); //改變程序狀態為睡眠  
        up(&dev->sem);  
  
        schedule(); //排程其他程序執行  
        if (signal_pending(current))  
        //如果是因為訊號喚醒  
        {  
            ret = - ERESTARTSYS;  
            goto out2;  
        }  
  
        down(&dev->sem); //獲得訊號量  
    }  
  
    /*從使用者空間拷貝到核心空間*/  
    if (count > GLOBALFIFO_SIZE - dev->current_len)  
        count = GLOBALFIFO_SIZE - dev->current_len;  
  
    if (copy_from_user(dev->mem + dev->current_len, buf, count))  
    {  
        ret = - EFAULT;  
        goto out;  
    }  
    else  
    {  
        dev->current_len += count;  
        printk(KERN_INFO "written %d bytes(s),current_len:%d\n", count, dev->current_len);  
  
        wake_up_interruptible(&dev->r_wait); //喚醒讀等待佇列


寫函式最後會喚醒我們的等待佇列,因為寫進去東西了,就可以去讀了,就是這樣,這部分跟我們的串列埠收發相同。

別的功能我就不說了,OK,驅動完成之後,我們載入進去,然後進行測試下。

首先我們去cat /dev/globalfifo

發生阻塞,一直停在那,這時候我們再開啟一個終端,去寫資料

echo "123" > /dev/globalfifo

寫完之後,我們立馬會發現之前的cat有東西出來了,每次都會把資料全部讀出來。

==================================================

下面是我們的jni,首先咱要明確我們做的事情,開啟裝置,讀裝置,最後不用的話就關閉裝置,所以我們至少要實現這3個API,

#define FIFO_CLEAR 0x01  
#define BUFFER_LEN 20  
#define GLOBALFIFO_PATH "/dev/globalfifo"  
  
int globalfifo_fd = -1;  
  
JNIEXPORT jint JNICALL  
Java_com_liujun_globalfifo_init(JNIEnv *env, jobject obj)  
{  
    globalfifo_fd = open(GLOBALFIFO_PATH, O_RDONLY); // | O_NOBLOCK  
    if(globalfifo_fd != -1)  
    {  
        __android_log_print(ANDROID_LOG_INFO,"JNI","open device done.");  
        //clear the buff  
        if(ioctl(globalfifo_fd, FIFO_CLEAR, 0) < 0)  
            __android_log_print(ANDROID_LOG_INFO,"JNI","clear buff error!");  
    } else  
        __android_log_print(ANDROID_LOG_INFO,"JNI","open device error!");  
}  


這是我們的初始化函式,定義了一個全域性的檔案描述符,init函式只做了open的動作。

JNIEXPORT jstring JNICALL  
Java_com_liujun_globalfifo_read(JNIEnv *env, jobject obj)  
{  
    int nread = 0;  
    char buff[512] = "";  
    __android_log_print(ANDROID_LOG_INFO,"JNI","read !");  
    nread = read(globalfifo_fd, buff, sizeof(buff));  
    if(nread != -1)  
        __android_log_print(ANDROID_LOG_INFO,"JNI","===> %s", buff);  
      
    return (*env)->NewStringUTF(env, buff);  
}  



這個API封裝了read函式,然後把讀到的buff轉換成為java中能識別的string,最後返回到java層的是string型別的字串。

JNIEXPORT jint JNICALL  
Java_com_liujun_globalfifo_exit(JNIEnv *env, jobject obj)  
{     
    close(globalfifo_fd);  
    __android_log_print(ANDROID_LOG_INFO,"JNI","close done!");  
    return 0;  
}  



最後這是我們的exit函式,呼叫了close來關閉我們的裝置。然後編寫Android.mk檔案,最後編譯,生成globalfifo庫

===========================================

接下來我們建立一個Android 工程,匯入jni庫並且定義native API

public native int init();  
public native String read();  
public native int exit();  
static {    
    System.loadLibrary("globalfifo");    
}    



然後在適當的地方呼叫。設定3個按鍵,先試開啟,然後read,按下read按鍵的時候開啟一個thread去讀資料。

public class MyThread implements Runnable{  
    public void run() {  
        // TODO Auto-generated method stub  
        while (true) {  
            try {  
                Thread.sleep(100);//  
                string = read();  
                Message message=new Message();  
                message.what=1;  
                handler.sendMessage(message);//傳送訊息  
                   
            } catch (InterruptedException e) {  
                // TODO Auto-generated catch block  
                e.printStackTrace();  
            }  
        }  
    }  
}  
  
class MyButtonListener implements OnClickListener{    
         
    public void onClick(View v) {  
        if(v.getId() == R.id.start ){    
            init();  
        }    
        if(v.getId() == R.id.read) {  
//string = read();  
//Toast.makeText(mContext, string, Toast.LENGTH_SHORT).show();    
            new Thread(new MyThread()).start();  
        }   
        if(v.getId() == R.id.close) {  
            exit();  
        }  
    }    
}   



安裝apk,然後執行程式,點選open,然後點選read,使用adb shell進入系統,然後往裡面寫東西

echo "Jay Zhang" > dev/globalfifo

可以看到有Jay Zhang 吐出來。

=================================================

這樣就模擬了串列埠,之後我們會用標準的android流程來完成can bus在android 裝置上的開發。