收到MaaXBoard高性能单板机已经有段时间了,体验了该单板机运行了多种系统的性能,感觉比较方便,小巧玲珑。今天介绍的项目是关于使用该单板机运行的一套钞票鉴别真伪系统软件,当然此项目是建立在android的系统之上。其中用到了MaaXBoard板的串口通讯,USB通讯接口。这个项目主要实现的是在不同光源背景下,通过500万的高清数字摄像机查看验钞机报假钞的钞票。现代年轻人都采用网上银行交付,很少会使用纸币,但在银行现金交易业务中,还是会有不少阔太太,大老板选择大额现金交易的,所以说不必怀疑这个产品没有研发价值。
       由于研发的产品还未出售,请允许保留一点项目隐私。该项目与MaaXBoard通讯的下位机选择的是STM32F103开发板,其实没有多大的业务需求,因此芯片选型上使用STM32F103完全足够了。MaaXBoard通过USB鼠标点击切换灯光按钮,从而触发串口数据包的发送,数据包包含协议头,字节数据长度,控制灯光组的数据,以及校验位。下位机创建定时器,串口中断中将标志位置一,再在串口接收函数中做数据包的解析判断。部分代码截图如下:
1.png
2.png
        再来说说上位机MaaXBoard这边,MaaXBoard采用的是Android系统,因此需要使用前面帖子介绍的,烧录好android系统镜像,然后再在此平台上安装开发好的“UVCCamera”,关于这个App的框架,可以在github上找到:https://github.com/saki4510t/UVCCamera,然后再根据项目需求,添加自己的串口通讯及USB调用摄像机的逻辑处理代码。当然在PC端开发的环境使用的是“Android Studio”,部分代码参考如下:
package com.serenegiant.usbcameratest3;
  • import android.Manifest;
  • import android.app.Activity;
  • import android.app.ActivityManager;
  • import android.app.AlertDialog;
  • import android.app.ProgressDialog;
  • import android.content.Context;
  • import android.content.DialogInterface;
  • import android.content.Intent;
  • import android.content.pm.PackageManager;
  • import android.graphics.Bitmap;
  • import android.graphics.SurfaceTexture;
  • import android.hardware.usb.UsbDevice;
  • import android.net.Uri;
  • import android.os.Bundle;
  • import android.os.Debug;
  • import android.os.Environment;
  • import android.os.Handler;
  • import android.os.Message;
  • import android.os.Vibrator;
  • import android.provider.MediaStore;
  • import android.support.annotation.NonNull;
  • import android.support.v4.app.ActivityCompat;
  • import android.support.v4.content.ContextCompat;
  • import android.text.format.Formatter;
  • import android.util.Log;
  • import android.view.Surface;
  • import android.view.View;
  • import android.view.View.OnClickListener;
  • import android.view.View.OnLongClickListener;
  • import android.view.Window;
  • import android.view.WindowManager;
  • import android.widget.Button;
  • import android.widget.CompoundButton;
  • import android.widget.ImageButton;
  • import android.widget.LinearLayout;
  • import android.widget.TextView;
  • import android.widget.Toast;
  • import android.widget.ToggleButton;
  • import com.serenegiant.common.BaseActivity;
  • import com.serenegiant.encoder.MediaMuxerWrapper;
  • import com.serenegiant.usb.CameraDialog;
  • import com.serenegiant.usb.DeviceFilter;
  • import com.serenegiant.usb.USBMonitor;
  • import com.serenegiant.usb.USBMonitor.OnDeviceConnectListener;
  • import com.serenegiant.usb.USBMonitor.UsbControlBlock;
  • import com.serenegiant.usb.UVCCamera;
  • import com.serenegiant.usbcameracommon.UVCCameraHandler;
  • import com.serenegiant.widget.CameraViewInterface;
  • import java.io.BufferedReader;
  • import java.io.File;
  • import java.io.FileNotFoundException;
  • import java.io.FileOutputStream;
  • import java.io.IOException;
  • import java.io.InputStream;
  • import java.io.OutputStream;
  • import java.sql.Array;
  • import java.text.SimpleDateFormat;
  • import java.util.ArrayList;
  • import java.util.Arrays;
  • import java.util.Date;
  • import java.util.List;
  • import android_serialport_api.SerialPort;
  • public final class MainActivity extends BaseActivity implements CameraDialog.CameraDialogParent {
  •     private static final boolean DEBUG = true;    // TODO set false on release
  •     private static final String TAG = "MainActivity";
  •     private ArrayList<UsbDevice> list;
  •     private ArrayList<UsbDevice> list0;
  •     private int i = 20;
  •     private boolean opencamer = false;
  •     private boolean one = true;
  •     /**
  •      * set true if you want to record movie using MediaSurfaceEncoder
  •      * (writing frame data into Surface camera from MediaCodec
  •      * by almost same way as USBCameratest2)
  •      * set false if you want to record movie using MediaVideoEncoder
  •      */
  •     private static final boolean USE_SURFACE_ENCODER = false;
  •     /**
  •      * preview resolution(width)
  •      * if your camera does not support specific resolution and mode,
  •      * {@link UVCCamera#setPreviewSize(int, int, int)} throw exception
  •      */
  •     private static final int PREVIEW_WIDTH = 2592;
  •     /**
  •      * preview resolution(height)
  •      * if your camera does not support specific resolution and mode,
  •      * {@link UVCCamera#setPreviewSize(int, int, int)} throw exception
  •      */
  •     private static final int PREVIEW_HEIGHT = 1944;
  •     /**
  •      * preview mode
  •      * if your camera does not support specific resolution and mode,
  •      * {@link UVCCamera#setPreviewSize(int, int, int)} throw exception
  •      * 0:YUYV, other:MJPEG
  •      */
  •     private static final int PREVIEW_MODE = 15;
  •     /**
  •      * for accessing USB
  •      */
  •     private USBMonitor mUSBMonitor;
  •     /**
  •      * Handler to execute camera related methods sequentially on private thread
  •      */
  •     private UVCCameraHandler mCameraHandler;
  •     /**
  •      * for camera preview display
  •      */
  •     private CameraViewInterface mUVCCameraView;
  •     /**
  •      * for open&start / stop&close camera preview
  •      */
  •     private ToggleButton mCameraButton;
  •     /**
  •      * button for start/stop recording
  •      */
  •     private ImageButton mCaptureButton;
  •     private UVCCamera mUVCCamera;
  •     private Button bt_1, bt_2, bt_3;
  •     private TextView tv_1;
  •     private Button linght1, linght2, linght3, linght4, linght5, linght6, linght7, linght8, linght9, linght0, back, light13;
  •     private LinearLayout bt_layout, linght_layout;
  •     private Bitmap map;
  •     private TextView tv1, tv2;
  •     private Surface mPreviewSurface;
  •     protected SerialPort mSerialPort;
  •     protected InputStream mInputStream;
  •     protected OutputStream mOutputStream;
  •     private ReadThread mReadThread;
  •     public static String strData = "";
  •     public long onetime = 0;
  •     public long twotime = 0;
  •     Toast mToast;
  •     public static Thread receiveThread = null;
  •     public static boolean isFlagSerial = false;
  •     private long mLastTime = 0;
  •     private long mCurTime = 0;
  •     private float value;
  •     Vibrator vibrator;
  •     ProgressDialog pd2 = null;
  •     @Override
  •     protected void onCreate(final Bundle savedInstanceState) {
  •         super.onCreate(savedInstanceState);
  •         if (DEBUG) Log.v(TAG, "onCreate:");
  •         requestWindowFeature(Window.FEATURE_NO_TITLE);
  •         getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,WindowManager.LayoutParams.FLAG_FULLSCREEN);
  •         setContentView(R.layout.activity_main);
  •         mCameraButton = (ToggleButton) findViewById(R.id.camera_button);
  •         mCameraButton.setOnCheckedChangeListener(mOnCheckedChangeListener);
  •         final View view = findViewById(R.id.camera_view);
  •         view.setOnLongClickListener(mOnLongClickListener);
  •         mUVCCameraView = (CameraViewInterface) view;
  •         mUVCCameraView.setAspectRatio(PREVIEW_WIDTH / (float) PREVIEW_HEIGHT);
  •         coonet();
  •         vibrator = (Vibrator) getSystemService(VIBRATOR_SERVICE);
  •         bt_1 = findViewById(R.id.bt_1);
  •         bt_1.setOnClickListener(mOnClickListener);
  •         open();
  •         init();
  •     }
  •     private void init() {
  •         linght1 = findButton(R.id.linght1);
  •         linght2 = findButton(R.id.linght2);
  •         linght3 = findButton(R.id.linght3);
  •         linght4 = findButton(R.id.linght4);
  •         linght5 = findButton(R.id.linght5);
  •         linght6 = findButton(R.id.linght6);
  •         linght7 = findButton(R.id.linght7);
  •         linght8 = findButton(R.id.linght8);
  •         mUSBMonitor = new USBMonitor(MainActivity.this, mOnDeviceConnectListener);
  •         final List<DeviceFilter> filter = DeviceFilter.getDeviceFilters(MainActivity.this, com.serenegiant.uvccamera.R.xml.device_filter);
  •         list0 = (ArrayList<UsbDevice>) mUSBMonitor.getDeviceList(filter.get(0));
  •         list = (ArrayList<UsbDevice>) mUSBMonitor.getDeviceList(filter.get(0));
  •             for (int j = 0; j <list0.size() ; j++) {
  •                 final UsbDevice device=list0.get(j);
  •                 int deName=device.getVendorId();
  •                 switch (deName){
  •                     case 1:
  •                         list.set(0,list0.get(j));
  •                         break;
  •                     case 2:
  •                         if (list.get(0)!=null) {
  •                             list.set(1, list0.get(j));
  •                         }
  •                         break;
  •                     case 3:
  •                         if (list.get(0)!=null&&list.get(1)!=null) {
  •                             list.set(2, list0.get(j));
  •                         }
  •                         break;
  •                 }
  •             }
  •         if (list.size() == 0) {
  •             ToastUtils.showToast(getApplicationContext(), "未连接摄像头");
  •         }
  •         storage(getApplicationContext(),MainActivity.this);
  •     }
  • 复制代码
    /**
  •      * 获取当前应用使用的内存大小
  •      *
  •      * @return 单位 MB
  •      */
  •     private double sampleMemory() {
  •         ActivityManager activityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
  •         double mem = 0.0D;
  •         try {
  •             final Debug.MemoryInfo[] memInfo = activityManager.getProcessMemoryInfo(new int[]{android.os.Process.myPid()});
  •             if (memInfo.length > 0) {
  •                 final int totalPss = memInfo[0].getTotalPss();
  •                 if (totalPss >= 0) {
  •                     mem = totalPss / 1024.0D;
  •                 }
  •             }
  •         } catch (Exception e) {
  •             e.printStackTrace();
  •         }
  •         return mem;
  •     }
  •     /**
  •      * event handler when click camera / capture button
  •      */
  •     private final OnClickListener mOnClickListener = new OnClickListener() {
  •         private byte[] E;
  •         @Override
  •         public void onClick(final View view) {
  •             vibrator.vibrate(30);
  •             switch (view.getId()) {
  •                 case R.id.bt_1: {
  •                     File fileLift = MediaMuxerWrapper.getCaptureFile(getRootPath(getApplicationContext()), ".jpg");
  •                     if (mCameraHandler.isOpened()) {
  •                             mCameraHandler.captureStill(fileLift.getPath());
  •                             Message message = new Message();
  •                             message.what = 12;
  •                             message.obj = "已拷贝图片至" + fileLift.getPath() + "下";
  •                             handler.sendMessage(message);
  •                     } else {
  •                         Message message = new Message();
  •                         message.what = 13;
  •                         message.obj = "摄像机未开启";
  •                         handler.sendMessage(message);
  •                     }
  •                 }
  •                 break;
  •                 case R.id.back:
  •                     showLight(false);
  •                     break;
  •                 case R.id.linght1:
  •                     E = new byte[]{(byte) 0xAA, 0x09, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, (byte) 0xB4};
  •                     sendString(E);
  •                     break;
  •                 case R.id.linght2:
  •                     E = new byte[]{(byte) 0xAA, 0x09, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, (byte) 0xB4};
  •                     sendString(E);
  •                     break;
  •                 case R.id.linght3:
  •                     E = new byte[]{(byte) 0xAA, 0x09, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, (byte) 0xB4};
  •                     sendString(E);
  •                     break;
  •                 case R.id.linght4:
  •                     E = new byte[]{(byte) 0xAA, 0x09, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, (byte) 0xB4};
  •                     sendString(E);
  •                     break;
  •                 case R.id.linght5:
  •                     E = new byte[]{(byte) 0xAA, 0x09, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, (byte) 0xB4};
  •                     sendString(E);
  •                     break;
  •                 case R.id.linght6:
  •                     E = new byte[]{(byte) 0xAA, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, (byte) 0xB4};
  •                     sendString(E);
  •                     break;
  •                 case R.id.linght7:
  •                     E = new byte[]{(byte) 0xAA, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, (byte) 0xB4};
  •                     sendString(E);
  •                     break;
  •                 case R.id.linght8:
  •                     mLastTime = mCurTime;
  •                     mCurTime = System.currentTimeMillis();
  •                     if (mCurTime - mLastTime < 300) {//双击事件
  •                         mCurTime = 0;
  •                         mLastTime = 0;
  •                         handler.removeMessages(1);
  •                         handler.sendEmptyMessage(2);
  •                     } else {//单击事件
  •                         handler.sendEmptyMessageDelayed(1, 310);
  •                     }
  •                     break;
  •             }
  •         }
  •     };
  • /**
  •      * to access from CameraDialog
  •      *
  •      * @return
  •      */
  •     @Override
  •     public USBMonitor getUSBMonitor() {
  •         return mUSBMonitor;
  •     }
  •     @Override
  •     public void onDialogResult(boolean canceled) {
  •         if (DEBUG) Log.v(TAG, "onDialogResult:canceled=" + canceled);
  •         if (canceled) {
  •             setCameraButton(false);
  •         }
  •     }
  •     protected void onDataReceived(final byte[] buffer, final int size) {
  •         runOnUiThread(new Runnable() {
  •             public void run() {
  •                 String msg = ByteUtil.bytes2HexString(buffer, size);
  •                 switch (msg) {
  •                     case "AA0100AB":
  •                         Toast.makeText(getApplicationContext(), "返回操作成功", Toast.LENGTH_SHORT).show();
  •                         break;
  •                     case "AA0101AC":
  •                         Toast.makeText(getApplicationContext(), "返回操作失败", Toast.LENGTH_SHORT).show();
  •                         break;
  •                     case "AB03010000AF":
  •                         i = 0;
  •                         OpenCamer();
  •                         break;
  •                     case "AB03000100AF":
  •                         i = 1;
  •                         OpenCamer();
  •                         break;
  •                     case "AB03000001AF":
  •                         if (list.size()<2){
  •                             ToastUtils.showToast(getApplicationContext(),"当前只有两个摄像头");
  •                         }else {
  •                             i = 2;
  •                             OpenCamer();
  •                         }
  •                         break;
  •                     case "AA09000000000000000001B4":
  •                         File fileLift = MediaMuxerWrapper.getCaptureFile(Environment.DIRECTORY_DCIM, ".jpg");
  •                         if (mCameraHandler.isOpened()) {
  •                             mCameraHandler.captureStill(fileLift.getPath());
  •                             Message message = new Message();
  •                             message.what = 12;
  •                             message.obj = "已拷贝图片至" + fileLift.getPath() + "下";
  •                             handler.sendMessage(message);
  •                         } else {
  •                             Message message = new Message();
  •                             message.what = 13;
  •                             message.obj = "摄像机未开启";
  •                             handler.sendMessage(message);
  •                         }
  •                         break;
  •                     default:
  •                         Toast.makeText(getApplicationContext(), "异常操作", Toast.LENGTH_SHORT).show();
  •                         break;
  •                 }
  •                 Log.v("debug", "接收到串口信息======>" + (ByteUtil.bytes2HexString(buffer, size)));
  •             }
  •         });
  •     }
  •     public void showLight(boolean show) {
  •         if (show) {
  •             linght_layout.setVisibility(View.VISIBLE);
  •             bt_layout.setVisibility(View.GONE);
  •         } else {
  •             linght_layout.setVisibility(View.GONE);
  •             bt_layout.setVisibility(View.VISIBLE);
  •         }
  •     }
  •     public String getAvailMemory(Context context) {
  •         ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
  •         ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo();
  •         am.getMemoryInfo(mi);
  •         // mi.availMem; 当前系统的可用内存
  •         return Formatter.formatFileSize(context, mi.availMem);// 将获取的内存大小规格化
  •     }
  •     /**
  •      * 清除缓存
  •      *
  •      * @param context
  •      */
  •     public void clearAllCache(Context context) {
  •         deleteDir(context.getCacheDir());
  •         if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
  •             deleteDir(context.getExternalCacheDir());
  •         }
  •     }
  •     private boolean deleteDir(File dir) {
  •         if (dir != null && dir.isDirectory()) {
  •             String[] children = dir.list();
  •             for (int i = 0; i < children.length; i++) {
  •                 boolean success = deleteDir(new File(dir, children[i]));
  •                 if (!success) {
  •                     return false;
  •                 }
  •             }
  •         }
  •         return dir.delete();
  •     }
  •     @Override
  •     public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
  •         super.onRequestPermissionsResult(requestCode, permissions, grantResults);
  •         switch (requestCode){
  •             case 1:
  •                 if (grantResults.length == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
  •                 ToastUtils.showToast(getApplicationContext(),"获取到存储权限");
  •                 }else {
  •                     storage(getApplicationContext(),MainActivity.this);
  •                     ToastUtils.showToast(getApplicationContext(),"拒绝");
  •                 }
  •                 break;
  •         }
  •     }
  • }
  • 复制代码
            然后编译完成后,将“UVCCamera”apk软件拷贝到U盘,并安装到MaaXBoard板卡中。接入USB摄像机,当然我们采用三只摄像机,有全视角,局部视角,细微局部视角。此次用到三只摄像机,细微局部视角采用外接式连接,在这个模组上有单独使用的按键控制板,因此无需通过MaaXBoard的显示界面去切换灯光源。MaaXBoard板与下位机的连线如下,由于MaaXBoard板上USB只有一个能用,因此考虑采用USB Hub扩展口。
    3.jpg
            然后拿出珍藏已久的一元纸币,来检测一下应用性能
    4.jpg
          白光下检测正常,然后看看紫外功能是否正常
    5.jpg
            然后通过按动鼠标,唤醒边框应用中按钮,切换摄像机,看看局部视角的场景。
    6.jpg
    7.jpg
           同样的方法查看了一下全视角下钞票的特征,由于全视角查看不是很清晰,图片就不再粘贴出来了。此次分享就与大家介绍到这里,使用MaaXBoard板运行这个应用程序还是能正常运行的,不过该应用运行了五分钟左右,板卡就觉得很烫手了,即便有一块小的散热片覆盖,但感觉温度还是有点高,如果检测设备长时间运作下去,估计板卡得吃不消了,如果条件允许的化,硬件上建议加风扇比较合适。感谢英蓓特提供的单机板,同时感谢各位坛友的来访,咱们江湖再见!