最近在做android串口的開發,找到一個開源的串口類android-serialport-api。其主頁在這裡http://code.google.com/p/android-serialport-api/ ,這裡可以下到APK及對源碼。
但是下載源碼之後發現源碼不能直接使用,而且源碼結構較為復雜。關於串口的操作不外乎幾步:
1.打開串口(及配置串口);
2.讀串口;
3.寫串口;
4.關閉串口。
android-serialport-api的代碼使用了繼承等復雜的行為,不容易使初學者很快掌握關於串口的上述4步,所以我特別自己寫了一個demo,只有一個activity,其中包含了打開串口,寫串口,讀串口的操作,對於關閉串口,大家一開就會不明白怎麼寫了。
這篇文章主要參考http://blog.csdn.net/tangcheng_ok/article/details/7021470
還有http://blog.csdn.net/jerome_home/article/details/8452305
下面言歸正傳:
第一:
說道android 串口,就不得不提JNI技術,它使得java中可以調用c語言寫成的庫。為可在android中使用串口,android-serialport-api的作者自己寫了一個c語言的動態鏈接庫serial_port.so(自動命名成libserial_port.so),並把它放在了libs/aemeabi 裡,其c源文件在JNI中,大家在下載了android-serialport-api的源代碼後,將這兩個文件夾copy到自己新建的工程中即可。
第二:
然後將調用c語言寫成的動態鏈接庫的java類放入到src文件夾下的android.serialport包下,這裡一定要將包名命名成這個,因為對JNI有一定了解的人就會知道,在寫c語言鏈接庫時候,函數的命名是和調用它的類所在的包名相關的,一旦包名與鏈接庫中函數的命名不相符,就不能調用鏈接庫的函數。這裡可以打開jni中的.c文件(他就是動態鏈接庫的源文件),可以看到源碼:
- /*
- *Copyright2009CedricPriscal
- *
- *LicensedundertheApacheLicense,Version2.0(the"License");
- *youmaynotusethisfileexceptincompliancewiththeLicense.
- *YoumayobtainacopyoftheLicenseat
- *
- *http://www.apache.org/licenses/LICENSE-2.0
- *
- *Unlessrequiredbyapplicablelaworagreedtoinwriting,software
- *distributedundertheLicenseisdistributedonan"ASIS"BASIS,
- *WITHOUTWARRANTIESORCONDITIONSOFANYKIND,eitherexpressorimplied.
- *SeetheLicenseforthespecificlanguagegoverningpermissionsand
- *limitationsundertheLicense.
- */
-
- #include
- #include
- #include
- #include
- #include
- #include
- #include
-
- #include"android/log.h"
- staticconstchar*TAG="serial_port";
- #defineLOGI(fmt,args...)__android_log_print(ANDROID_LOG_INFO,TAG,fmt,##args)
- #defineLOGD(fmt,args...)__android_log_print(ANDROID_LOG_DEBUG,TAG,fmt,##args)
- #defineLOGE(fmt,args...)__android_log_print(ANDROID_LOG_ERROR,TAG,fmt,##args)
-
- staticspeed_tgetBaudrate(jintbaudrate)
- {
- switch(baudrate){
- case0:returnB0;
- case50:returnB50;
- case75:returnB75;
- case110:returnB110;
- case134:returnB134;
- case150:returnB150;
- case200:returnB200;
- case300:returnB300;
- case600:returnB600;
- case1200:returnB1200;
- case1800:returnB1800;
- case2400:returnB2400;
- case4800:returnB4800;
- case9600:returnB9600;
- case19200:returnB19200;
- case38400:returnB38400;
- case57600:returnB57600;
- case115200:returnB115200;
- case230400:returnB230400;
- case460800:returnB460800;
- case500000:returnB500000;
- case576000:returnB576000;
- case921600:returnB921600;
- case1000000:returnB1000000;
- case1152000:returnB1152000;
- case1500000:returnB1500000;
- case2000000:returnB2000000;
- case2500000:returnB2500000;
- case3000000:returnB3000000;
- case3500000:returnB3500000;
- case4000000:returnB4000000;
- default:return-1;
- }
- }
-
- /*
- *Class:cedric_serial_SerialPort
- *Method:open
- *Signature:(Ljava/lang/String;)V
- */
- JNIEXPORTjobjectJNICALLJava_android_serialport_SerialPort_open
- (JNIEnv*env,jobjectthiz,jstringpath,jintbaudrate)
- {
- intfd;
- speed_tspeed;
- jobjectmFileDescriptor;
-
- /*Checkarguments*/
- {
- speed=getBaudrate(baudrate);
- if(speed==-1){
- /*TODO:throwanexception*/
- LOGE("Invalidbaudrate");
- returnNULL;
- }
- }
-
- /*Openingdevice*/
- {
- jbooleaniscopy;
- constchar*path_utf=(*env)->GetStringUTFChars(env,path,&iscopy);
- LOGD("Openingserialport%s",path_utf);
- fd=open(path_utf,O_RDWR|O_DIRECT|O_SYNC);
- LOGD("open()fd=%d",fd);
- (*env)->ReleaseStringUTFChars(env,path,path_utf);
- if(fd==-1)
- {
- /*Throwanexception*/
- LOGE("Cannotopenport");
- /*TODO:throwanexception*/
- returnNULL;
- }
- }
-
- /*Configuredevice*/
- {
- structtermioscfg;
- LOGD("Configuringserialport");
- if(tcgetattr(fd,&cfg))
- {
- LOGE("tcgetattr()failed");
- close(fd);
- /*TODO:throwanexception*/
- returnNULL;
- }
-
- cfmakeraw(&cfg);
- cfsetispeed(&cfg,speed);
- cfsetospeed(&cfg,speed);
-
- if(tcsetattr(fd,TCSANOW,&cfg))
- {
- LOGE("tcsetattr()failed");
- close(fd);
- /*TODO:throwanexception*/
- returnNULL;
- }
- }
-
- /*Createacorrespondingfiledescriptor*/
- {
- jclasscFileDescriptor=(*env)->FindClass(env,"java/io/FileDescriptor");
- jmethodIDiFileDescriptor=(*env)->GetMethodID(env,cFileDescriptor,"","()V");
- jfieldIDdescriptorID=(*env)->GetFieldID(env,cFileDescriptor,"descriptor","I");
- mFileDescriptor=(*env)->NewObject(env,cFileDescriptor,iFileDescriptor);
- (*env)->SetIntField(env,mFileDescriptor,descriptorID,(jint)fd);
- }
-
- returnmFileDescriptor;
- }
-
- /*
- *Class:cedric_serial_SerialPort
- *Method:close
- *Signature:()V
- */
- JNIEXPORTvoidJNICALLJava_android_serialport_SerialPort_close
- (JNIEnv*env,jobjectthiz)
- {
- jclassSerialPortClass=(*env)->GetObjectClass(env,thiz);
- jclassFileDescriptorClass=(*env)->FindClass(env,"java/io/FileDescriptor");
-
- jfieldIDmFdID=(*env)->GetFieldID(env,SerialPortClass,"mFd","Ljava/io/FileDescriptor;");
- jfieldIDdescriptorID=(*env)->GetFieldID(env,FileDescriptorClass,"descriptor","I");
-
- jobjectmFd=(*env)->GetObjectField(env,thiz,mFdID);
- jintdescriptor=(*env)->GetIntField(env,mFd,descriptorID);
-
- LOGD("close(fd=%d)",descriptor);
- close(descriptor);
- }
可以看到,函數的命名規則直接和包名有關。
第三:
android.serialport包下,有兩個類,分別是SerialPort.java 和SerialPortFinder.java。
其中,SerialPort.java,這個類主要用來加載SO文件,通過JNI的方式打開關閉串口。
- /*
- *Copyright2009CedricPriscal
- *
- *LicensedundertheApacheLicense,Version2.0(the"License");
- *youmaynotusethisfileexceptincompliancewiththeLicense.
- *YoumayobtainacopyoftheLicenseat
- *
- *http://www.apache.org/licenses/LICENSE-2.0
- *
- *Unlessrequiredbyapplicablelaworagreedtoinwriting,software
- *distributedundertheLicenseisdistributedonan"ASIS"BASIS,
- *WITHOUTWARRANTIESORCONDITIONSOFANYKIND,eitherexpressorimplied.
- *SeetheLicenseforthespecificlanguagegoverningpermissionsand
- *limitationsundertheLicense.
- */
-
- packageandroid.serialport;
-
- importjava.io.File;
- importjava.io.FileDescriptor;
- importjava.io.FileInputStream;
- importjava.io.FileOutputStream;
- importjava.io.IOException;
- importjava.io.InputStream;
- importjava.io.OutputStream;
-
- importandroid.util.Log;
-
- publicclassSerialPort{
-
- privatestaticfinalStringTAG="SerialPort";
-
- /*
- *DonotremoveorrenamethefieldmFd:itisusedbynativemethodclose();
- */
- privateFileDescriptormFd;
- privateFileInputStreammFileInputStream;
- privateFileOutputStreammFileOutputStream;
-
- publicSerialPort(Filedevice,intbaudrate)throwsSecurityException,IOException{
-
- /*Checkaccesspermission*/
- if(!device.canRead()||!device.canWrite()){
- try{
- /*Missingread/writepermission,tryingtochmodthefile*/
- Processsu;
- su=Runtime.getRuntime().exec("/system/bin/su");
- Stringcmd="chmod777"+device.getAbsolutePath()+"\n"
- +"exit\n";
- /*Stringcmd="chmod777/dev/s3c_serial0"+"\n"
- +"exit\n";*/
- su.getOutputStream().write(cmd.getBytes());
- if((su.waitFor()!=0)||!device.canRead()
- ||!device.canWrite()){
- thrownewSecurityException();
- }
- }catch(Exceptione){
- e.printStackTrace();
- thrownewSecurityException();
- }
- }
-
- mFd=open(device.getAbsolutePath(),baudrate);
- if(mFd==null){
- Log.e(TAG,"nativeopenreturnsnull");
- thrownewIOException();
- }
- mFileInputStream=newFileInputStream(mFd);
- mFileOutputStream=newFileOutputStream(mFd);
- }
-
- //Gettersandsetters
- publicInputStreamgetInputStream(){
- returnmFileInputStream;
- }
-
- publicOutputStreamgetOutputStream(){
- returnmFileOutputStream;
- }
-
- //JNI
- privatenativestaticFileDescriptoropen(Stringpath,intbaudrate);
- publicnativevoidclose();
- static{
- System.loadLibrary("serial_port");
- }
- }
可以看到System.loadLibrary("serial_port");一句,這一句就是用來加載動態鏈接庫。我們的串口操作就是要給予這個類來實現。
含有一個類SerialPortFinder.java,這個類是用來找到系統中可以用的串口的,如果你知道的android設備有什麼串口,就不必使用這個類來查找串口了,一次簡化我們的demo。
第四:加入我們自己的Activity類
為了方便我記在android.serialport包下加入了我自己的MyserialActivity.java,大家從上面的圖中也可以看見。
代碼如下:
[java]view plaincopy
- packageandroid.serialport;
-
-
- importjava.io.File;
- importjava.io.FileInputStream;
- importjava.io.FileOutputStream;
- importjava.io.IOException;
-
- importandroid.app.Activity;
-
- importandroid.os.Bundle;
-
-
-
- //importandroid.serialport.sample.R;
- importandroid.serialport.R;
-
- importandroid.view.View;
- importandroid.widget.Button;
- importandroid.widget.EditText;
- importandroid.widget.Toast;
-
- publicclassMyserialActivityextendsActivity{
- /**Calledwhentheactivityisfirstcreated.*/
-
-
- EditTextmReception;
- FileOutputStreammOutputStream;
- FileInputStreammInputStream;
- SerialPortsp;
-
- @Override
-
- publicvoidonCreate(BundlesavedInstanceState){
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
-
-
- finalButtonbuttonSetup=(Button)findViewById(R.id.ButtonSetup);
- buttonSetup.setOnClickListener(newView.OnClickListener(){
- publicvoidonClick(Viewv){
- mReception=(EditText)findViewById(R.id.EditTextRec);
-
- try{
- sp=newSerialPort(newFile("/dev/ttyS2"),9600);
- }catch(SecurityExceptione){
- //TODOAuto-generatedcatchblock
- e.printStackTrace();
- }catch(IOExceptione){
- //TODOAuto-generatedcatchblock
- e.printStackTrace();
- }
-
-
- mOutputStream=(FileOutputStream)sp.getOutputStream();
- mInputStream=(FileInputStream)sp.getInputStream();
-
- Toast.makeText(getApplicationContext(),"open",
- Toast.LENGTH_SHORT).show();
-
- }
- });
-
-
-
- finalButtonbuttonsend=(Button)findViewById(R.id.ButtonSent1);
- buttonsend.setOnClickListener(newView.OnClickListener(){
- publicvoidonClick(Viewv){
-
- try{
- mOutputStream.write(newString("send").getBytes());
- mOutputStream.write('\n');
- }catch(IOExceptione){
- e.printStackTrace();
- }
-
-
- Toast.makeText(getApplicationContext(),"send",
- Toast.LENGTH_SHORT).show();
-
- }
- });
-
-
- finalButtonbuttonrec=(Button)findViewById(R.id.ButtonRec);
- buttonrec.setOnClickListener(newView.OnClickListener(){
- publicvoidonClick(Viewv){
- intsize;
-
- try{
- byte[]buffer=newbyte[64];
- if(mInputStream==null)return;
- size=mInputStream.read(buffer);
- if(size>0){
- onDataReceived(buffer,size);
-
- }
- }catch(IOExceptione){
- e.printStackTrace();
- return;
- }
-
- }
- });
- }
- voidonDataReceived(finalbyte[]buffer,finalintsize){
- runOnUiThread(newRunnable(){
- publicvoidrun(){
- if(mReception!=null){
- mReception.append(newString(buffer,0,size));
- }
- }
- });
- }
-
-
- }
可以看見,功能比較簡單,只有三個按鈕,分別用來打開串口(buttonsetup),寫串口(buttonsend),讀串口(buttonrec),一個文本框用來顯示串口接收到的信息。功能已經簡化到了最簡。
下面先說說在模擬器中使用串口的方法:
應先使用-serial選項打開你的模擬器,如圖(修改你模擬器的名字)
然後進入adb shell
cd /dev
chmod 777 ttyS2
運行後結果:
相比大家都懂得,我們的串口就是ttyS2,使用chmod命令來獲取對它的操作,否則之後你的應用可能沒有串口的操作權限。
然後運行程序:
其中Console就是打開串口(原諒我偷懶,忘改名字了)。
你可以把你的電腦的COM1連接到另一台電腦的串口上,並在那台電腦上打開你的串口助手之類的軟件,配置好串口(參數不難從源代碼裡看出來)。按下模擬器中的send鍵,就能在那台電腦的串口助手中看到:
同樣,從那台電腦向這台電腦發送數據也可以顯示