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; }
看到没,这里使用了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结构体的定义了吧,这里,就是这里,所以在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, };
咱有读,写,打开。。。。等函数,继续往下分析。 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);

没东西读的时候,使进程睡眠,在调度到别的任务去

/* 等待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 设备上的开发。

Android 设备上实现串口的移植相关推荐

  1. android几个串口设备,Android 设备上实现串口的移植

    de项目需要,要实现在Android中实现串口的收发功能,有几种方法可以参考使用. 1. 标准的Android HAL层思想,把串口的功能加入framework的API中(类似于android中sen ...

  2. Android设备上一张图片的显示过程

    转自:http://blog.csdn.net/jxt1234and2010/article/details/50524213 Android设备上一张图片的显示过程 应用示例 假如我们现在有一张这样 ...

  3. 手机APP开发之MIT Appinventor详细实战教程(一),利用通过蓝牙控制单片机,以及实现单片机与android设备之间的串口通信

    目录 (一)前期软件准备和硬件准备 ( 二 ) 实现的思路和操作原理 ( 三) 具体的操作方法 MIT Appinventor 是编程领域较为受欢迎且适用的编程软件 ,因其操作流程和使用方法简单,一直 ...

  4. 设置android启动器,教程:在任意 Android 设备上安装 HTC 专属桌面启动器

    HTC 手机的标志性 Sense UI 几乎是伴随着 Android 系统成长起来的,BlinkFeed 作为 Sense 5 的新特性进入了人们的视野之中.经历了几代更新,BlinkFeed 受到更 ...

  5. 如何查看Android设备上的分区信息

    Android设备上,一般都会存在一块eMMC存储芯片来存放系统和用户数据,甚至部分的引导程序. 一般设备出厂时,各个厂商都会将这块存储芯片分成很多的分区,每个分区内存放不同的内容.具体分区的布局每个 ...

  6. java图像分类源码_在Android设备上使用NCNN图像分类的demo

    [实例简介] 在Android设备上使用NCNN图像分类的demo,是一个Android项目 [实例截图] [核心代码] NCNN1 └── NCNN1 ├── app │   ├── app.iml ...

  7. OpenCV在Android设备上运行深度网络

    OpenCV在Android设备上运行深度网络 在Android设备上运行深度网络 介绍 要求 创建一个空的Android Studio项目 添加OpenCV依赖项 做一个样品 在Android设备上 ...

  8. android 摄像头参数,获取Android设备上的详细的摄像头信息

    原标题:获取Android设备上的详细的摄像头信息 如何获取Android设备上的详细的摄像头信息呢? 目前Samsung的Galaxy Tab和Nexus S均有前置摄像头,获取Android摄像头 ...

  9. (转)在ios android设备上使用 Protobuf (使用dll方式)

    自:http://game.ceeger.com/forum/read.php?tid=13479 如果你的工程可以以.Net 2.0 subset模式运行,请看这个帖子中的方法. 地址:http:/ ...

  10. android最简单存储数据结构,什么是存储要在多个Android设备上共享的同步数据的最佳数据结构?...

    我的项目是关于连接多个Android设备,并帮助他们相互沟通.我已经使用蓝牙连接了所有这些设备.一台设备用作服务器,其余用作客户端.这些设备能够发送和接收消息/事件,并在接收到消息时执行某些任务.什么 ...

最新文章

  1. 下一代脑电图可以帮助恢复失去的大脑功能
  2. 听妈妈的话-07年感动的延续
  3. shell随机数及按行切割文件
  4. 小程序支付 PHP
  5. CBitmapButton位图按钮简明教程
  6. GloVe: Global Vectors for Word Representation-学习笔记
  7. java对象模型 指令_深入理解多线程(二)—— Java的对象模型
  8. python cx_oracle配置_python连接oracle的模块cx_Oracle安装和配置
  9. 今年美国什么工作最吃香?程序猿薪酬超医生
  10. 大数据之-Hadoop之HDFS_NameNode和Secondary NameNode工作机制_通过文件的增删改查说明---大数据之hadoop工作笔记0070
  11. [转]PowerDesigner使用技巧
  12. Tortoise SVN 汉化(官网下载汉化包)
  13. 从有到优:百度前端接入技术的升级之路
  14. 图形工作站/服务器硬件如何配置?这里有最新最全的硬件配置方案
  15. lt18i android 2.3.4典藏版,索尼LT18i一键ROOT教程工具 2.3.4已亲测成功
  16. 机器视觉系统——光源及打光
  17. python的pytest模块:pytest命令行详解
  18. 苹果计算机怎么开科学,苹果手机怎样设置科学计算器?
  19. uniapp ping插件 离线打包
  20. pdf怎么在线阅读?一键查阅并不难

热门文章

  1. 保活 进程唤醒_Android 8.0以上系统应用如何保活
  2. python中and和的区别_python中逻辑与或(and、or)和按位与或异或(amp;、|、^)区别...
  3. java实现Base64编码与解码
  4. 廖雪峰Java9正则表达式-2正则表达式进阶-3分组匹配
  5. linux命令学习之sar命令--服务器性能监测
  6. CentOS6.3 编译安装LAMP(4):编译安装 PHP5.3.27
  7. Android 开发者不得不面对的六个问题
  8. VS2010安装部署成.NET 2.0过程的几个问题
  9. python复制图片文件_python批量复制图片到另一个文件夹
  10. mysql1423_MySQL++简介 | 学步园