1

Iam trying to plot realtime signal from arduino to my android tablet. I've using a potentiometer for testing purpose and ADC sampling rate is 256Hz. My issue is that the graph plotted on the android app is not smooth. The serial communication is working properly but the plotting is not very good.It feels like the App is lagging and it lags further as the data gets plotted.

Below is the app image

app screenshot

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;
        }
    }
}
  • You seem to have two issues: the first one is the plot does not look smooth and the second one the GUI is not loading data from the stream at the pace it should. The first one has to do with the way you plot. For the second it would be useful to see the code running on the Arduino. Are you sending data one byte at a time? – Marcos G. Oct 10 '19 at 09:37
  • Iam using Serial.Println(adcdata). I have a time interrupt in arduino which ticks 256 times per second and in evry timer interrupt iam reading analog value and sending it further using Serial.println() function. I hope my android handler code is correct. – Sumit Mourya Oct 10 '19 at 10:06
  • That's probably a bottleneck, you might want to change the code on your Arduino to store several samples on a buffer and send it only once or twice per second. If you do that the app should look much more responsive. On your Android app you should not read bytes one at a time but keep them in the RX buffer and read them every once in a while (before the buffer overflows, of course). Take a look at [this question](https://stackoverflow.com/questions/56961121/maximizing-serial-communication-speed-for-live-plotting-data-from-teensy-3-2-usi/56968543#56968543) – Marcos G. Oct 10 '19 at 10:33
  • [This question](https://stackoverflow.com/questions/41421035/android-app-not-synchronized-with-arduino-serial-communication) seems to be related to your performance issue. – Marcos G. Oct 10 '19 at 10:42
  • Ok, Thanks for your reply. I think android devices are not meant to handle real time data very efficiently. I've worked on Microcontroller based TFT displays before and I have always sent the data without using any buffer both at sending & receiving sides, but never had any issues in displaying the data. I initially thought I was not able to handle UI thread properly but it is the buffer problem. Will implement your valuable inputs in my arduino code for sure. Thank you once again. – Sumit Mourya Oct 10 '19 at 11:32
  • You're welcome. I have been in the same place, multitasking devices are tricky for real-time. Life is way easier when you don't have to worry about the uncertainties of an OS. Good luck. – Marcos G. Oct 10 '19 at 11:48

0 Answers0