Сигнал Android в реальном времени не плавный с использованием библиотеки graphview

Я пытаюсь вывести сигнал в реальном времени с Arduino на свой планшет Android. Я использую потенциометр для целей тестирования, а частота дискретизации АЦП составляет 256 Гц. Моя проблема в том, что график, построенный в приложении для Android, не является гладким. Последовательная связь работает правильно, но построение графика не очень хорошее. Такое ощущение, что приложение отстает, и оно отстает еще больше по мере того, как данные отображаются на графике.

Ниже изображение приложения

скриншот приложения

package com.example.ecgauth;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;

import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.IntentFilter;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbManager;
import android.os.Bundle;
import android.Manifest;
import android.app.Activity;
import android.bluetooth.BluetoothSocket;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
//import android.support.v4.app.ActivityCompat;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.ToggleButton;

import com.felhr.usbserial.UsbSerialDevice;
import com.felhr.usbserial.UsbSerialInterface;
import com.jjoe64.graphview.GraphView;
import com.jjoe64.graphview.GraphView.GraphViewData;
import com.jjoe64.graphview.GraphView.LegendAlign;
import com.jjoe64.graphview.GraphViewSeries;
import com.jjoe64.graphview.GraphViewSeries.GraphViewStyle;
import com.jjoe64.graphview.LineGraphView;

import java.util.HashMap;
import java.util.Map;

public class MainActivity extends Activity  implements View.OnClickListener{

    private static final String ACTION_USB_PERMISSION = "com.example.ecg.USB_PERMISSION";
    TextView hr_value_text;
    UsbDevice device;
    UsbDeviceConnection connection;
    UsbManager usbManager;
    UsbSerialDevice serialPort;
    PendingIntent pendingIntent;
   // private  Handler mHandler;
    public static final int MESSAGE_FROM_SERIAL_PORT = 0;

    final static int gMAX_BLOCKS = 32;   //Maximum blocks
    final static int gMAX_SAMPLES = 8;   //'Maximum samples
    final static int gMAX_CHANNELS = 4; //'Maximum channels
    final static int gSampFreq = 256;    //Sampling frequency
    final static int  gRawBufferSize = gMAX_BLOCKS * gMAX_SAMPLES ;
    final static float swpfactor = 0;
    final static int gSec =0;
    int sample =0;
    private static boolean acq1 = false;
    static int block =0;
    int g_intRawBuffer[][] = new int[gMAX_BLOCKS][gMAX_SAMPLES];
    double g_sngDnOutBuf[] = new double[gMAX_SAMPLES ];

    @Override
    public void onBackPressed() {
//        // TODO Auto-generated method stub
//        if (Bluetooth.connectedThread != null) {
//            Bluetooth.connectedThread.write("Q");}//Stop streaming
        super.onBackPressed();
    }

    //toggle Button
    static boolean Lock;//whether lock the x-axis to 0-5
    static boolean AutoScrollX;//auto scroll to the last x value
    static boolean Stream;//Start or stop streaming
    int old_interval=0;
    int new_interval=0;
    int mean_interval=20;
    //Button init
    Button bXminus;
    Button bXplus;
    ToggleButton tbLock;
    ToggleButton tbScroll;
    ToggleButton tbStream;
    boolean startthread = true;
    //GraphView init
    static LinearLayout GraphView;
    static GraphView graphView;
    static GraphViewSeries Series;
    //graph value
    private static double graph2LastXValue = 0;
    private static int Xview=1023;
    Button bConnect, bDisconnect;
    private TextView connectionstatus;

    public int control_a = 0;
    public int TotalSample=0;
    char chdata = 0;
    int i =0;
    String finaldata = "";
    String rawdata = "";
    byte highbyte=0;
    int data = 0 ;
    int plotdata = 0 ;
    int databuff = 0;
    public int[] buffer_bt = new int[768];
    public int[] copy_buffer_bt = new int[768];
    public int ind_bt = 0;


    UsbSerialInterface.UsbReadCallback mCallback = new UsbSerialInterface.UsbReadCallback() { //Defining a Callback which triggers whenever data is read.


        @Override
        public void onReceivedData(byte[] arg0) {

// lowbyte = arg0;
            // highbyte = arg0;
            byte[] buffer = arg0;
            for (int i = 0; i <= (buffer.length - 1); i++) {
                if (buffer[i] != 13) {
                    if (buffer[i] == 10) {
                        finaldata = rawdata;
                        rawdata = "";
                    } else {
                        chdata = (char) buffer[i];
                        rawdata += chdata;
                    }
                }

            }

            data = Integer.parseInt(finaldata);
            mHandler.obtainMessage(MESSAGE_FROM_SERIAL_PORT, data).sendToTarget();
            control_a = 1;
            buffer_bt[databuff] = data;
            // }
            //lowbyte = buffer;

            //highbyte = buffer;

            databuff = databuff + 1;


            if (databuff == 767){

            databuff=0;
        }


//
//                for (int uu=0; uu<=767; uu++) {
//                    copy_buffer_bt[uu] = buffer_bt[uu];
//                }
//                databuff=0;
//                ready_bt=1;
//                threadon = true;
//            }
        }
    };



    @Override
    protected void onPause() {
        super.onPause();
        unregisterReceiver(broadcastReceiver);
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
        requestWindowFeature(Window.FEATURE_NO_TITLE);//Hide title
        this.getWindow().setFlags(WindowManager.LayoutParams.
                FLAG_FULLSCREEN,WindowManager.LayoutParams.FLAG_FULLSCREEN);//Hide Status bar
        setContentView(R.layout.activity_main);
        //set background color
        LinearLayout background = (LinearLayout)findViewById(R.id.bg);
        background.setBackgroundColor(Color.BLACK);
      //  mHandler = new Handler();
        //GraphView = (LinearLayout) findViewById(R.id.Graph);
            usbManager = (UsbManager) getSystemService(this.USB_SERVICE);
            connectionstatus = (TextView) findViewById(R.id.tvBluetooth);
            bConnect = (Button)findViewById(R.id.bConnect);
            bConnect.setOnClickListener(this);
            bDisconnect = (Button)findViewById(R.id.bDisconnect);
            bDisconnect.setOnClickListener(this);
            //X-axis control button
            bXminus = (Button)findViewById(R.id.bXminus);
            bXminus.setOnClickListener(this);
            bXplus = (Button)findViewById(R.id.bXplus);
            bXplus.setOnClickListener(this);
            //
            tbLock = (ToggleButton)findViewById(R.id.tbLock);
            tbLock.setOnClickListener(this);
            tbScroll = (ToggleButton)findViewById(R.id.tbScroll);
            tbScroll.setOnClickListener(this);
            tbStream = (ToggleButton)findViewById(R.id.tbStream);
            tbStream.setOnClickListener(this);
            //init toggleButton
            Lock=true;
            AutoScrollX=true;
            Stream=true;
            IntentFilter filter = new IntentFilter();
            filter.addAction(ACTION_USB_PERMISSION);
            filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
            filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
            registerReceiver(broadcastReceiver, filter);



         init();

    }



    private final BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {

        @Override
        public void onReceive(Context context, Intent intent) {
            if (intent.getAction().equals(ACTION_USB_PERMISSION)) {
                boolean granted =
                        intent.getExtras().getBoolean(UsbManager.EXTRA_PERMISSION_GRANTED);
                if (granted) {
                    connection = usbManager.openDevice(device);
                    serialPort = UsbSerialDevice.createUsbSerialDevice(device, connection);
                    if (serialPort != null) {
                        if (serialPort.open()) { //Set Serial Connection Parameters.
                            //setUiEnabled(true); //Enable Buttons in UI
                            serialPort.setBaudRate(57600);
                            serialPort.setDataBits(UsbSerialInterface.DATA_BITS_8);
                            serialPort.setStopBits(UsbSerialInterface.STOP_BITS_1);
                            serialPort.setParity(UsbSerialInterface.PARITY_NONE);
                            serialPort.setFlowControl(UsbSerialInterface.FLOW_CONTROL_OFF);
                            serialPort.read(mCallback); //
                            //connectionstatus.setText("Connected");
                            tvAppend(connectionstatus, " Opened!\n");

                        } else {
                            Log.d("SERIAL", "PORT NOT OPEN");
                        }
                    } else {
                        Log.d("SERIAL", "PORT IS NULL");
                    }
                } else {
                    Log.d("SERIAL", "PERM NOT GRANTED");
                }
            } else if (intent.getAction().equals(UsbManager.ACTION_USB_DEVICE_ATTACHED)) {
                onConnect(bConnect);
            } else if (intent.getAction().equals(UsbManager.ACTION_USB_DEVICE_DETACHED)) {
                //   onClickStop(buttonRestart);
            }

        }
    };

    public void onConnect(View view){
        HashMap<String, UsbDevice> usbDevices = usbManager.getDeviceList();
        if (!usbDevices.isEmpty()) {
            boolean keep = true;
            for (Map.Entry<String, UsbDevice> entry : usbDevices.entrySet()) {
                device = entry.getValue();
                int deviceVID = device.getVendorId();
                if (deviceVID == 0x10C4)//Arduino Vendor ID//0x10C4 cp2102 VID
                {
                    PendingIntent pi = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_USB_PERMISSION), 0);
                    usbManager.requestPermission(device, pi);
                    keep = false;
                } else {
                    connection = null;
                    device = null;
                }

                if (!keep)
                    break;
            }
        }

    }

    private void tvAppend (TextView tv, CharSequence text){
        connectionstatus.setText(" ");
        final TextView ftv = tv;
        final CharSequence ftext = text;
        runOnUiThread(new Runnable() {
            @Override
            public void run() {

                ftv.append(ftext);

            }
        });
    }

    void init(){
        //Bluetooth.gethandler(mHandler);

        //init graphview
        GraphView = (LinearLayout) findViewById(R.id.Graph);
        // init example series data-------------------
        Series = new GraphViewSeries("Signal",
                new GraphViewStyle(Color.YELLOW, 2),//color and thickness of the line
                new GraphViewData[] {new GraphViewData(0, 0)});
        graphView = new LineGraphView(
                this // context
                , "Electrocardiogram AUTH" // heading
        );
        graphView.setViewPort(0, Xview);
        graphView.setScrollable(true);
        graphView.setScalable(false);
        graphView.setShowLegend(false);
        //graphView.setLegendAlign(LegendAlign.BOTTOM);
        graphView.setManualYAxis(true);
        graphView.setManualYAxisBounds(2560, 0);
        graphView.addSeries(Series); // data
        GraphView.addView(graphView);
    }

Handler mHandler = new Handler(){
    @Override
    public void handleMessage(@NonNull Message msg) {
        super.handleMessage(msg);
        switch (msg.what){
            case MESSAGE_FROM_SERIAL_PORT:
                plotdata = data;


                g_intRawBuffer[block][TotalSample] = plotdata;

                TotalSample = TotalSample+1;

                if (TotalSample == 8) {
                    TotalSample = 0;

                    for (int sample = 0; sample <= (gMAX_SAMPLES - 1); sample++) {
                        g_sngDnOutBuf[sample] = g_intRawBuffer[block][sample];
                    }
                    for (int sample = 0; sample <= (gMAX_SAMPLES - 1); sample++) {

                        Series.appendData(new GraphViewData(graph2LastXValue, g_sngDnOutBuf[sample]), AutoScrollX);
                        if (graph2LastXValue >= Xview && Lock == true) {
                            Series.resetData(new GraphViewData[]{});
                            graph2LastXValue = 0;
                        }else
                            graph2LastXValue += 0.5;

                        if (Lock == true)
                            graphView.setViewPort(0, Xview);
                        else
                            graphView.setViewPort(graph2LastXValue - Xview, Xview);
                        //refresh
                        GraphView.removeView(graphView);
                        GraphView.addView(graphView);


                    }




                }

        }
    }
};


    @Override
    public void onClick(View v) {
        // TODO Auto-generated method stub
        switch(v.getId()){
            case R.id.bConnect:
                HashMap<String, UsbDevice> usbDevices = usbManager.getDeviceList();
                if (!usbDevices.isEmpty()) {
                    boolean keep = true;
                    for (Map.Entry<String, UsbDevice> entry : usbDevices.entrySet()) {
                        device = entry.getValue();
                        int deviceVID = device.getVendorId();
                        if (deviceVID == 0x2341)//Arduino Vendor 2341 ID//0x10C4 cp2102 VID
                        {
                            PendingIntent pi = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_USB_PERMISSION), 0);
                            usbManager.requestPermission(device, pi);
                            keep = false;
                        } else {
                            connection = null;
                            device = null;
                        }

                        if (!keep)
                            break;
                    }
                }

                break;
            case R.id.bDisconnect:
                serialPort.close();
                tvAppend(connectionstatus, "Serial Connection Closed!\n");
                break;
            case R.id.bXminus:
                if (Xview<30) Xview++;
                break;
            case R.id.bXplus:
                if (Xview>1) Xview--;
                break;
            case R.id.tbLock:
                if (tbLock.isChecked()){
                    Lock = true;
                }else{
                    Lock = false;
                }
                break;
            case R.id.tbScroll:
                if (tbScroll.isChecked()){
                    AutoScrollX = true;

                }else{
                    AutoScrollX = false;
                }
                break;
            case R.id.tbStream:
                if (tbStream.isChecked()){
                    serialPort.write("a".getBytes());

                }else{
                    serialPort.write("z".getBytes());
                    control_a = 0;
                }
            break;
        }
    }
}

person Sumit Mourya    schedule 10.10.2019    source источник
comment
У вас, кажется, две проблемы: первая - сюжет не выглядит гладким, а вторая - графический интерфейс не загружает данные из потока в нужном темпе. Первый связан с тем, как вы рисуете. Для второго было бы полезно увидеть код, работающий на Arduino. Вы отправляете данные по одному байту за раз?   -  person Marcos G.    schedule 10.10.2019
comment
Я использую Serial.Println(adcdata). У меня есть прерывание по времени в arduino, которое тикает 256 раз в секунду, и в каждом прерывании таймера iam считывает аналоговое значение и отправляет его дальше с помощью функции Serial.println(). Я надеюсь, что мой код обработчика Android правильный.   -  person Sumit Mourya    schedule 10.10.2019
comment
Это, вероятно, узкое место, вы можете изменить код на своем Arduino, чтобы хранить несколько образцов в буфере и отправлять их только один или два раза в секунду. Если вы сделаете это, приложение должно выглядеть намного более отзывчивым. В вашем Android-приложении вы не должны читать байты по одному, а хранить их в буфере RX и читать их время от времени (конечно, до переполнения буфера). Взгляните на этот вопрос   -  person Marcos G.    schedule 10.10.2019
comment
Этот вопрос, похоже, связан с вашей проблемой производительности.   -  person Marcos G.    schedule 10.10.2019
comment
Хорошо, спасибо за ваш ответ. Я думаю, что устройства Android не предназначены для очень эффективной обработки данных в реальном времени. Раньше я работал с TFT-дисплеями на основе микроконтроллеров, и я всегда отправлял данные без использования какого-либо буфера как на стороне отправки, так и на стороне приема, но никогда не было проблем с отображением данных. Сначала я думал, что не могу правильно обрабатывать поток пользовательского интерфейса, но это проблема буфера. Ваш ценный вклад обязательно будет реализован в моем коде Arduino. Еще раз, спасибо.   -  person Sumit Mourya    schedule 10.10.2019
comment
Пожалуйста. Я был в том же месте, многозадачные устройства сложны для реального времени. Жизнь намного проще, когда вам не нужно беспокоиться о неопределенности ОС. Удачи.   -  person Marcos G.    schedule 10.10.2019