Android 设备上实现串口的移植
来源:互联网 发布:js原生日期选择器 编辑:程序博客网 时间:2024/05/18 00:43
转自: http://blog.csdn.net/vv0_0vv/article/details/7449594
de项目需要,要实现在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;
- }
/*设备驱动模块加载函数*/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; /*阻塞写用的等待队列头*/
- };
/*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; /*阻塞写用的等待队列头*/ };
看到我们的globalfifo结构体的定义了吧,这里,就是这里,所以在init函数中,我们要初始化信号量,初始化读写等待队列头。要不咱先来讲讲这里的阻塞的概念吧。
顾名思义,就是堵在那边不动了,其实是真的不动了,利用等待队列实现设备的阻塞,当用户进程访问系统资源的时候,当这个资源不能被访问,我们又不想让之后的事情继续发生,这样的话我们就可以阻塞在那边,放心,我们可以让该进程进入休眠,这样的话就不会浪费CPU的资源了,然而等到这个资源可以访问的时候,我们就可以唤醒该阻塞的进程,继续让他执行下去,如果没有地方唤醒他,那他就真的“堵死”在那边了。
简单的介绍了下,接下来看看我们要实现哪些功能函数
- /*文件操作结构体*/
- 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,
- };
/*文件操作结构体*/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;
- }
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;
- }
/*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);
没东西读的时候,使进程睡眠,在调度到别的任务去
- /* 等待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);
- }
/* 等待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); //唤醒读等待队列
/*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!");
- }
#define FIFO_CLEAR 0x01#define BUFFER_LEN 20#define GLOBALFIFO_PATH"/dev/globalfifo"int globalfifo_fd = -1;JNIEXPORT jint JNICALLJava_com_liujun_globalfifo_init(JNIEnv *env, jobject obj){globalfifo_fd = open(GLOBALFIFO_PATH, O_RDONLY); // | O_NOBLOCKif(globalfifo_fd != -1){__android_log_print(ANDROID_LOG_INFO,"JNI","open device done.");//clear the buffif(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);
- }
JNIEXPORT jstring JNICALLJava_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;
- }
JNIEXPORT jint JNICALLJava_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");
- }
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();
- }
- }
- }
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 设备上的开发。
- Android 设备上实现串口的移植
- Android 设备上实现串口的移植
- Android 设备上实现串口的移植
- Android 实现串口的移植
- 串口键盘在Qt上的移植
- 枚举 pc 上的串口设备
- SMARTARM2200 ADS工程在IAR EWARM 5.3上的移植(4)-printf的串口实现
- android设备上实现摇一摇
- linux设备上的Onvif 实现3 :gSOAP嵌入式linux下的移植与程序开发
- 如何开发板上移植udev并实现移动设备的自动挂载
- Zedboard上的NTShell移植 以及 双串口软件测试
- Android串口设备的应用实现方案以及与WEB的交互
- Android串口设备的应用实现方案以及与WEB的交互
- Android 移植定制之Ubuntu上识别USB设备
- 串口调试Android设备
- 笔记本上的设备管理器上的串口图标 随拔插串口不在发生变化,这样办。
- linux设备驱动之串口移植
- linux设备驱动之串口移植
- LNK2005: __thiscall type_info::type_info(class type_info const &) already defined in LIBCMTD.lib
- JBoss下数据库连接的密码加密
- SQLServer函数
- expdp:ORA-31634: job already exists
- CentOS下以RPM方式安装MySQL5.5
- Android 设备上实现串口的移植
- Android 像桌面左右滑动功能实现
- GDB常用命令简介[zz]
- Win7 vs2010+Silverlight4开发安装顺序
- jQuery跟Extjs的区别
- C#中绘图时遇到滚动条问题
- MYSQL IFNULL函数的使用
- 修改窗口的风格ModifyStyle
- ChkBugReport使用指南