由于研发的产品还未出售,请允许保留一点项目隐私。该项目与MaaXBoard通讯的下位机选择的是STM32F103开发板,其实没有多大的业务需求,因此芯片选型上使用STM32F103完全足够了。MaaXBoard通过USB鼠标点击切换灯光按钮,从而触发串口数据包的发送,数据包包含协议头,字节数据长度,控制灯光组的数据,以及校验位。下位机创建定时器,串口中断中将标志位置一,再在串口接收函数中做数据包的解析判断。部分代码截图如下:
再来说说上位机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; } } }
复制代码然后拿出珍藏已久的一元纸币,来检测一下应用性能
白光下检测正常,然后看看紫外功能是否正常
然后通过按动鼠标,唤醒边框应用中按钮,切换摄像机,看看局部视角的场景。
同样的方法查看了一下全视角下钞票的特征,由于全视角查看不是很清晰,图片就不再粘贴出来了。此次分享就与大家介绍到这里,使用MaaXBoard板运行这个应用程序还是能正常运行的,不过该应用运行了五分钟左右,板卡就觉得很烫手了,即便有一块小的散热片覆盖,但感觉温度还是有点高,如果检测设备长时间运作下去,估计板卡得吃不消了,如果条件允许的化,硬件上建议加风扇比较合适。感谢英蓓特提供的单机板,同时感谢各位坛友的来访,咱们江湖再见!