├── .gitignore ├── dashboard.png ├── Matlab reference ├── read_test.m ├── servoController.m ├── evolution.m └── ard_init.m ├── Arduino ├── servoToMatlab │ └── servoToMatlab.ino ├── ReadEMGDemo.ino └── DC Motor │ └── DC_example.ino ├── README.md ├── static └── dash.html ├── emg_server.py ├── emg_web_dashboard.py └── emg_api.py /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.pyc 3 | *.asv 4 | *.pickle 5 | test.png 6 | .idea/ 7 | -------------------------------------------------------------------------------- /dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fasashen/DIY-Myo-Controller/HEAD/dashboard.png -------------------------------------------------------------------------------- /Matlab reference/read_test.m: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fasashen/DIY-Myo-Controller/HEAD/Matlab reference/read_test.m -------------------------------------------------------------------------------- /Matlab reference/servoController.m: -------------------------------------------------------------------------------- 1 | 2 | if ~isempty(instrfind) 3 | fclose(instrfind); 4 | end 5 | 6 | s = serial('COM3'); 7 | set(s,'BaudRate',115200); 8 | fopen(s); 9 | 10 | j = 200; 11 | i = 0; 12 | 13 | % fprintf(s,0); 14 | 15 | while i < j 16 | % fwrite(s,i); 17 | disp(i) 18 | fwrite(s,i); 19 | % in = fread(s); 20 | pause(0.5); 21 | if i < (j - 10) 22 | i = i + 10; 23 | else 24 | i = 0; 25 | end 26 | end 27 | 28 | i=0; 29 | fwrite(s,i); 30 | fclose(s); -------------------------------------------------------------------------------- /Arduino/servoToMatlab/servoToMatlab.ino: -------------------------------------------------------------------------------- 1 | /* Sweep 2 | by BARRAGAN 3 | This example code is in the public domain. 4 | 5 | modified 8 Nov 2013 6 | by Scott Fitzgerald 7 | http://www.arduino.cc/en/Tutorial/Sweep 8 | */ 9 | 10 | #include 11 | 12 | Servo myservo; // create servo object to control a servo 13 | // twelve servo objects can be created on most boards 14 | 15 | int pos = 0; // variable to store the servo position 16 | 17 | void setup() { 18 | Serial.begin(115200); 19 | myservo.attach(10); // attaches the servo on pin 9 to the servo object 20 | } 21 | 22 | void loop() { 23 | for (pos = 0; pos <= 180; pos += 1) { // goes from 0 degrees to 180 degrees 24 | // in steps of 1 degree 25 | myservo.write(pos); // tell servo to go to position in variable 'pos' 26 | delay(15); // waits 15ms for the servo to reach the position 27 | } 28 | for (pos = 180; pos >= 0; pos -= 1) { // goes from 180 degrees to 0 degrees 29 | myservo.write(pos); // tell servo to go to position in variable 'pos' 30 | delay(15); // waits 15ms for the servo to reach the position 31 | } 32 | Serial.print("Anton"); 33 | } 34 | 35 | -------------------------------------------------------------------------------- /Matlab reference/evolution.m: -------------------------------------------------------------------------------- 1 | k_size = 20; 2 | i_size = 2; 3 | j_size = 2; 4 | 5 | generations = 10000; 6 | 7 | H = zeros(); 8 | H_evol = zeros(i_size,j_size,k_size); 9 | 10 | X{1} = x1; 11 | X{2} = x2; 12 | p1 = zeros(); 13 | p2 = zeros(); 14 | 15 | 16 | % u = H*x 17 | 18 | for k=1:k_size 19 | for i=1:i_size 20 | for j=1:j_size 21 | H(i,j,k) = rand(); 22 | end 23 | end 24 | end 25 | 26 | 27 | for g=1:generations 28 | 29 | for k=1:k_size 30 | HH = H(:,:,k); 31 | uu1{k} = zeros(); 32 | uu2{k} = zeros(); 33 | uu1{k} = (HH*X{1}')'; 34 | uu2{k} = (HH*X{2}')'; 35 | 36 | u_error1{k} = u1 - uu1{k}; 37 | u_error2{k} = u2 - uu2{k}; 38 | 39 | p1(k) = sum(sum( abs(u_error1{k}) + abs(u_error2{k}) )); 40 | % p2(k) = sum(sum(abs(u_error2{k}))); 41 | end 42 | 43 | p = p1; 44 | [s,i] = sort(p); 45 | 46 | H_king = H(:,:,i(1)); 47 | H_evol(:,:,1) = H_king; 48 | 49 | disp(s(1)); 50 | 51 | kking = i(1); 52 | 53 | for k=2:2:(k_size/2) 54 | if k ~= 2 55 | kk = k-1; 56 | else 57 | kk = k; 58 | end 59 | H_evol(1,:,kk) = H(1,:,i(k)); 60 | H_evol(2,:,kk) = H(2,:,i(k+1)); 61 | end 62 | 63 | for k=(k_size/2-1):k_size 64 | for i=1:i_size 65 | for j=1:j_size 66 | H_evol(i,j,k) = rand(); 67 | end 68 | end 69 | end 70 | 71 | H = H_evol; 72 | 73 | end 74 | 75 | H_sk = H(:,:,kking); 76 | 77 | u_result1 = (H_sk*x1')'; 78 | u_result2 = (H_sk*x2')'; 79 | -------------------------------------------------------------------------------- /Matlab reference/ard_init.m: -------------------------------------------------------------------------------- 1 | function [ Ard,goodnessflag ] = ard_init( COMPORTSTR,baud ) 2 | %ScouseTom_ard_initialise Setup connection to arduino using 3 | %establishcontact() function 4 | %which sends 'A' (65) until soemthing is writtten to it 5 | % Inputs: 6 | % COMPORTSTR - string for com port i.e. 'COM11' 7 | % timeout - timeout in s for communication (optional) 8 | % Outputs: 9 | % S - Serial object 10 | % flag - goodnessflag flag. 1 is good 11 | % 12 | % written by the dissolute yet munificent Jimmy 2015 13 | 14 | % set baud rate 15 | if exist('baud','var') == 0 || isempty(baud) 16 | baud=57600; 17 | end 18 | 19 | 20 | if ~isempty(instrfind) 21 | fclose(instrfind); 22 | end 23 | 24 | %arduino resets on opening serial port 25 | 26 | %open serial com 27 | Ard=serial(COMPORTSTR,'BaudRate',baud); 28 | 29 | % disp('Resetting Arduino'); 30 | fopen(Ard); %arduino now reset 31 | 32 | %declare timing variables 33 | start=tic; 34 | elapsed=0; 35 | %this has to be set to more than 1 s as fopen resets arduino and 36 | %some other stuff runs before it establishes contact 37 | 38 | timeout =5; 39 | goodnessflag=0; 40 | 41 | in=0;%inbyte 42 | 43 | while elapsed < timeout 44 | 45 | elapsed=toc(start); 46 | 47 | if (Ard.BytesAvailable > 0) 48 | %read a byte 49 | in=fread(Ard,1); 50 | %disp(in) 51 | end 52 | 53 | %input should be 'A' or 65 54 | if in==65 55 | % disp('i can hear arduino'); 56 | fprintf(Ard,'h'); % this can be anything, if its not a capital letter it is ignored 57 | goodnessflag=1; 58 | elapsed=timeout+1; 59 | end 60 | 61 | end 62 | 63 | if goodnessflag==1 64 | % disp('Communication with Arduino was OK'); 65 | else 66 | disp('Commuinocation with Arduino failed! BOOO'); 67 | fclose(Ard); 68 | end 69 | 70 | 71 | 72 | 73 | 74 | end 75 | 76 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Electromyography (EMG) data controller 2 | 3 | This project is currently in development and it's meant to process EMG muscle data using machine learning algorithm to generate control commands. 4 | 5 | ![Live dashboard using Dash by plotly](https://i.imgur.com/0bKAc9m.png) 6 | 7 | This is a student project, it's goint to have a basic machine learning algorithm to control a Arduino-Uno based robotic hand or any other device or it could be used as a controller. Arduino send voltage from non-invasive on-skin electrodes (EMG). 8 | Repository contains arduino source code for passing raw EMG data to computer and python3 code for analyzing and sending control signals. 9 | 10 | ### What's done: 11 | * Arduino code that sends voltage data 12 | * Python module *emg_api.py* that provides API to connect, synchronise and read data in real-time from Arduino 13 | * Server on Flask *emg_server.py* for real-time reading, processing and analyzing data from Arduino and sending processed data (e.g. Fourie transform) it to localhost:5000/emg/{channel} in *json* format. For now it's processing this data: 14 | * Fourie transform for frequency spectre analysis 15 | * Standart deviation calculation that will be used for ML as input 16 | * Just a raw voltage data 17 | * Live web dashboard *emg_web_dashboard.py* made with [Dash by Plotly](https://plot.ly/products/dash/) that reads data from server and visualize it 18 | ![Changing number of channels showing](https://media.giphy.com/media/TH2ezXdGOONqkhkqFF/giphy.gif) 19 | * Vue.js web dashboard prototype (made just for fun to see what possibilities are available) 20 | ![Vue.js web dashboard scratch](https://media.giphy.com/media/1ylcxnn1thUjvHeb5r/giphy.gif) 21 | 22 | ### What's going to be done: 23 | * Implementation of TensorFlow for analyzing hand gestures 24 | * Build web virtual hand to be controlled by EMG 25 | * Redesign server side to get rid of http data sending to front-end 26 | -------------------------------------------------------------------------------- /static/dash.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Page Title 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 |
30 | 31 | 32 | 33 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /emg_server.py: -------------------------------------------------------------------------------- 1 | import atexit 2 | import json 3 | from time import gmtime, strftime 4 | 5 | import numpy as np 6 | from apscheduler.schedulers.background import BackgroundScheduler 7 | from apscheduler.triggers.interval import IntervalTrigger 8 | from flask import Flask 9 | 10 | import emg_api 11 | 12 | app = Flask(__name__, static_url_path='/static') 13 | 14 | @app.route("/") 15 | def index(): 16 | return app.send_static_file('dash.html') 17 | 18 | @app.route("/emg/", methods=['GET']) 19 | @app.route("/emg/", methods=['GET']) 20 | def get_emg(channels_to_plot=1): 21 | 22 | channels_to_plot = int(channels_to_plot) 23 | 24 | channels = range(0,channels_to_plot) 25 | voltage = [] 26 | nanstd = [] 27 | 28 | for channel in channels: 29 | voltage.append(list(emg.data[channel])) 30 | nanstd.append(list(emg.nstd_data[channel])) 31 | 32 | data = json.dumps( 33 | {'voltage_y': list(voltage), 34 | 'voltage_x': list(emg.x_time), 35 | 'nanstd_y': list(nanstd), 36 | 'nanstd_x': list(emg.nstd_time), 37 | 'freq_y': list(emg.ft_data), 38 | 'freq_x': list(emg.ft_x), 39 | 'server_time': strftime("%H:%M:%S", gmtime())}, 40 | sort_keys=False, 41 | indent=4, 42 | separators=(',', ': ')) 43 | 44 | return data 45 | 46 | # Updates EMG data in realtime 47 | def emg_realtime_update(): 48 | emg.read_packs() 49 | packets_inwaiting = emg.inwaiting() 50 | if packets_inwaiting >= 50: 51 | print('Update rate is slow: {} packets inwaiting, {} second delay.'.format(packets_inwaiting, np.round(packets_inwaiting/256,2))) 52 | 53 | 54 | scheduler = BackgroundScheduler() 55 | scheduler.start() 56 | scheduler.add_job( 57 | func=emg_realtime_update, 58 | trigger=IntervalTrigger(seconds=0.020), 59 | replace_existing=True) 60 | # Shut down the scheduler when exiting the app 61 | atexit.register(lambda: scheduler.shutdown()) 62 | 63 | if __name__ == '__main__': 64 | try: 65 | emg = emg_api.EMG('COM3',numread=20, plotting=False, plotsize = 256, nstd_timespan = 2560) 66 | connection = emg.establish_connection() 67 | except Exception as e: 68 | print('Connection to arduino failed: {}'.format(e)) 69 | print('Flask server started') 70 | app.run(host='0.0.0.0', port=5000, debug=True, use_reloader=False, threaded=True) 71 | -------------------------------------------------------------------------------- /emg_web_dashboard.py: -------------------------------------------------------------------------------- 1 | from time import gmtime, strftime 2 | 3 | import dash 4 | import dash_core_components as dcc 5 | import dash_html_components as html 6 | import plotly.graph_objs as go 7 | import requests 8 | from dash.dependencies import Output, Event, Input 9 | 10 | app = dash.Dash(__name__) 11 | 12 | external_css = ["https://cdnjs.cloudflare.com/ajax/libs/materialize/0.100.2/css/materialize.min.css"] 13 | for css in external_css: 14 | app.css.append_css({"external_url": css}) 15 | 16 | external_js = ['https://cdnjs.cloudflare.com/ajax/libs/materialize/0.100.2/js/materialize.min.js'] 17 | for js in external_css: 18 | app.scripts.append_script({'external_url': js}) 19 | 20 | app.layout = html.Div([ 21 | 22 | html.Div([ 23 | html.H3(children='EMG Dashboard', className='col s12'), 24 | 25 | html.H6( 26 | children='''Realtime EMG data processing''', 27 | className='col s4'), 28 | 29 | dcc.Slider( 30 | id='slider-update', 31 | updatemode='drag', 32 | min=1, 33 | max=6, 34 | marks={i+1: 'Channel {}'.format(i+1) for i in range(6)}, 35 | step=None, 36 | value=2, 37 | className='col s4 offset-s2'), 38 | 39 | ], className='row', style={'margin-left':30,'margin-right':30,'vertical-align': 'middle'}), 40 | 41 | html.Div( 42 | id='time-div', 43 | className="row", 44 | style={'margin-left':30,'margin-right':30,'vertical-align': 'middle'}), 45 | 46 | html.Div([ 47 | 48 | dcc.Graph( 49 | id='nanstd-graph', 50 | animate=True 51 | ) 52 | 53 | ], className="row", style={'width':'100%','float':'center', 'display': 'inline-block'}), 54 | 55 | dcc.Interval( 56 | id='graph-update', 57 | interval=1*1000), 58 | 59 | dcc.Interval( 60 | id='nanstd-update', 61 | interval=1*1000), 62 | 63 | dcc.Interval( 64 | id='data-update', 65 | interval=1*2000), 66 | 67 | html.Div(id='hidden-div', style={'display':'none'}), 68 | 69 | html.Div([ 70 | html.Div([ 71 | html.Div([ 72 | dcc.Graph( 73 | id='voltage-graph', 74 | animate=True 75 | ) 76 | ], className="col s6"), 77 | 78 | html.Div([ 79 | dcc.Graph( 80 | id='freq-graph', 81 | animate=True 82 | ) 83 | ], className="col s6") 84 | 85 | ], className="row", style={'width':'100%','float':'center'}), 86 | ], className='row', style={'margin-left':30,'margin-right':30,'vertical-align': 'middle'}), 87 | 88 | ], 89 | className="container", 90 | style={'float':'center','width':'95%','margin-left':30,'margin-right':30,'max-width':2000} 91 | 92 | ) 93 | 94 | 95 | @app.callback(Output('time-div', 'children'), 96 | events=[Event('data-update', 'interval')], 97 | inputs=[Input('slider-update', 'value')]) 98 | def update_all_data(channels_to_plot): 99 | 100 | global data 101 | 102 | r = requests.get('http://localhost:5000/emg/{}'.format(channels_to_plot)) 103 | data = r.json() 104 | print(r,data['server_time']) 105 | 106 | 107 | 108 | server_time = html.H6( 109 | children='Server time: ' + data['server_time'], 110 | className="col s12", 111 | style={'width':'100%','float':'center','text-align': 'center'}) 112 | 113 | 114 | return [server_time] 115 | 116 | 117 | 118 | @app.callback(Output('voltage-graph', 'figure'), 119 | events=[Event('graph-update', 'interval')]) 120 | def update_voltage_plot(): 121 | 122 | global data 123 | 124 | volt = data['voltage_y'] 125 | X = data['voltage_x'] 126 | channels_to_plot = len(volt) 127 | 128 | channels = range(0,channels_to_plot) 129 | maxval, minval = max_min(volt[:channels_to_plot]) 130 | voltage_plots = [] 131 | 132 | for ch in channels: 133 | voltage_plots.append(go.Scatter( 134 | x = list(X), 135 | y = list(volt[ch]), 136 | name ='Channel {}'.format(ch+1), 137 | mode = 'lines' 138 | )) 139 | 140 | return {'data': voltage_plots,'layout' : go.Layout(xaxis=dict(range=[min(X),max(X)]), 141 | yaxis=dict(range=[minval,maxval]), 142 | title="Raw Voltage Data")} 143 | 144 | 145 | @app.callback(Output('nanstd-graph', 'figure'), 146 | events=[Event('nanstd-update', 'interval')]) 147 | def update_nanstd_plot(): 148 | 149 | global data 150 | 151 | nanstd = data['nanstd_y'] 152 | x_nanstd = data['nanstd_x'] 153 | channels_to_plot = len(nanstd) 154 | 155 | channels = range(0,channels_to_plot) 156 | maxval, minval = max_min(nanstd[:channels_to_plot]) 157 | nanstd_plots = [] 158 | 159 | for ch in channels: 160 | nanstd_plots.append(go.Scatter( 161 | x = list(x_nanstd), 162 | y = list(nanstd[ch]), 163 | name ='Channel {}'.format(ch+1), 164 | mode = 'lines' 165 | )) 166 | 167 | return {'data': nanstd_plots,'layout' : go.Layout(xaxis=dict(range=[min(x_nanstd),max(x_nanstd)]), 168 | yaxis=dict(range=[minval,maxval]), 169 | title="Standard deviation, Plot Time: {}".format(strftime("%H:%M:%S", gmtime())))} 170 | 171 | @app.callback(Output('freq-graph', 'figure'), 172 | events=[Event('graph-update', 'interval')]) 173 | def update_freq_plot(): 174 | 175 | global data 176 | 177 | freq = data['freq_y'] 178 | x_freq = data['freq_x'] 179 | channels_to_plot = len(freq) 180 | 181 | channels = range(0,channels_to_plot) 182 | nanstd_plots = [] 183 | 184 | for ch in channels: 185 | nanstd_plots.append(go.Scatter( 186 | x = list(x_freq), 187 | y = list(freq[ch]), 188 | name ='Channel {}'.format(ch+1), 189 | mode = 'lines' 190 | )) 191 | 192 | return {'data': nanstd_plots,'layout' : go.Layout(xaxis=dict(range=[min(x_freq),max(x_freq)]), 193 | yaxis=dict(range=[min(freq[0]),max(freq[0])]), 194 | title="Fourie transform")} 195 | 196 | 197 | 198 | def max_min(lists): 199 | maxval = max(lists[0]) 200 | minval = min(lists[0]) 201 | for list in lists: 202 | if maxval < max(list): maxval = max(list) 203 | if minval > min(list): minval = min(list) 204 | return maxval, minval 205 | 206 | if __name__ == '__main__': 207 | data = {} 208 | app.run_server(debug=True) 209 | -------------------------------------------------------------------------------- /Arduino/ReadEMGDemo.ino: -------------------------------------------------------------------------------- 1 | /**********************************************************/ 2 | /* Demo program for: */ 3 | /* Board: SHIELD-EKG/EMG + Olimexino328 */ 4 | /* Manufacture: OLIMEX */ 5 | /* COPYRIGHT (C) 2012 */ 6 | /* Designed by: Penko Todorov Bozhkov */ 7 | /* Module Name: Sketch */ 8 | /* File Name: ShieldEkgEmgDemo.ino */ 9 | /* Revision: Rev.A */ 10 | /* -> Added is suppport for all Arduino boards. */ 11 | /* This code could be recompiled for all of them! */ 12 | /* Date: 19.12.2012 */ 13 | /* Built with Arduino C/C++ Compiler, version: 1.0.3 */ 14 | /**********************************************************/ 15 | /********************************************************** 16 | Purpose of this programme is to give you an easy way to 17 | connect Olimexino328 to ElectricGuru(TM), see: 18 | https://www.olimex.com/Products/EEG/OpenEEG/EEG-SMT/resources/ElecGuru40.zip 19 | where you'll be able to observe yours own EKG or EMG signal. 20 | It is based on: 21 | *********************************************************** 22 | * ModularEEG firmware for one-way transmission, v0.5.4-p2 23 | * Copyright (c) 2002-2003, Joerg Hansmann, Jim Peters, Andreas Robinson 24 | * License: GNU General Public License (GPL) v2 25 | *********************************************************** 26 | For proper communication packet format given below have to be supported: 27 | /////////////////////////////////////////////// 28 | ////////// Packet Format Version 2 //////////// 29 | /////////////////////////////////////////////// 30 | // 17-byte packets are transmitted from Olimexino328 at 256Hz, 31 | // using 1 start bit, 8 data bits, 1 stop bit, no parity, 57600 bits per second. 32 | 33 | // Minimial transmission speed is 256Hz * sizeof(Olimexino328_packet) * 10 = 43520 bps. 34 | 35 | struct Olimexino328_packet 36 | { 37 | uint8_t sync0; // = 0xa5 38 | uint8_t sync1; // = 0x5a 39 | uint8_t version; // = 2 (packet version) 40 | uint8_t count; // packet counter. Increases by 1 each packet. 41 | uint16_t data[6]; // 10-bit sample (= 0 - 1023) in big endian (Motorola) format. 42 | uint8_t switches; // State of PD5 to PD2, in bits 3 to 0. 43 | }; 44 | */ 45 | /**********************************************************/ 46 | #include 47 | #include 48 | //#include 49 | 50 | //Servo myservo; 51 | //int pos = 0; 52 | 53 | //http://www.arduino.cc/playground/Main/FlexiTimer2 54 | 55 | // All definitions 56 | #define NUMCHANNELS 6 57 | #define HEADERLEN 4 58 | #define PACKETLEN (NUMCHANNELS * 2 + HEADERLEN + 1) 59 | #define SAMPFREQ 256 // ADC sampling rate 256 60 | #define TIMER2VAL (1024/(SAMPFREQ)) // Set 256Hz sampling frequency 61 | #define LED1 13 62 | #define CAL_SIG 9 63 | 64 | // Global constants and variables 65 | volatile unsigned char TXBuf[PACKETLEN]; //The transmission packet 66 | volatile unsigned char TXIndex; //Next byte to write in the transmission packet. 67 | volatile unsigned char CurrentCh; //Current channel being sampled. 68 | volatile unsigned char counter = 0; //Additional divider used to generate CAL_SIG 69 | volatile unsigned int ADC_Value = 0; //ADC current value 70 | 71 | //~~~~~~~~~~ 72 | // Functions 73 | //~~~~~~~~~~ 74 | 75 | /****************************************************/ 76 | /* Function name: Toggle_LED1 */ 77 | /* Parameters */ 78 | /* Input : No */ 79 | /* Output : No */ 80 | /* Action: Switches-over LED1. */ 81 | /****************************************************/ 82 | void Toggle_LED1(void){ 83 | 84 | if((digitalRead(LED1))==HIGH){ digitalWrite(LED1,LOW); } 85 | else{ digitalWrite(LED1,HIGH); } 86 | 87 | } 88 | 89 | /****************************************************/ 90 | /* Function name: toggle_GAL_SIG */ 91 | /* Parameters */ 92 | /* Input : No */ 93 | /* Output : No */ 94 | /* Action: Switches-over GAL_SIG. */ 95 | /****************************************************/ 96 | void toggle_GAL_SIG(void){ 97 | 98 | if(digitalRead(CAL_SIG) == HIGH){ digitalWrite(CAL_SIG, LOW); } 99 | else{ digitalWrite(CAL_SIG, HIGH); } 100 | 101 | } 102 | 103 | 104 | /****************************************************/ 105 | /* Function name: setup */ 106 | /* Parameters */ 107 | /* Input : No */ 108 | /* Output : No */ 109 | /* Action: Initializes all peripherals */ 110 | /****************************************************/ 111 | void setup() { 112 | 113 | 114 | 115 | // Serial Port 116 | //Serial.begin(57600); 117 | Serial.begin(115200); 118 | 119 | //myservo.attach(10); 120 | 121 | establishContact(); 122 | //Set speed to 57600 bps 123 | 124 | noInterrupts(); // Disable all interrupts before initialization 125 | 126 | // LED1 127 | pinMode(LED1, OUTPUT); //Setup LED1 direction 128 | // digitalWrite(LED1,LOW); //Setup LED1 state 129 | pinMode(CAL_SIG, OUTPUT); 130 | 131 | //Write packet header and footer 132 | TXBuf[0] = 0xa5; //Sync 0 133 | TXBuf[1] = 0x5a; //Sync 1 134 | TXBuf[2] = 2; //Protocol version 135 | TXBuf[3] = 0; //Packet counter 136 | TXBuf[4] = 0x02; //CH1 High Byte 137 | TXBuf[5] = 0x00; //CH1 Low Byte 138 | TXBuf[6] = 0x02; //CH2 High Byte 139 | TXBuf[7] = 0x00; //CH2 Low Byte 140 | TXBuf[8] = 0x02; //CH3 High Byte 141 | TXBuf[9] = 0x00; //CH3 Low Byte 142 | TXBuf[10] = 0x02; //CH4 High Byte 143 | TXBuf[11] = 0x00; //CH4 Low Byte 144 | TXBuf[12] = 0x02; //CH5 High Byte 145 | TXBuf[13] = 0x00; //CH5 Low Byte 146 | TXBuf[14] = 0x02; //CH6 High Byte 147 | TXBuf[15] = 0x00; //CH6 Low Byte 148 | TXBuf[2 * NUMCHANNELS + HEADERLEN] = 0x01; // Switches state 149 | 150 | // Timer2 151 | // Timer2 is used to setup the analag channels sampling frequency and packet update. 152 | // Whenever interrupt occures, the current read packet is sent to the PC 153 | // In addition the CAL_SIG is generated as well, so Timer1 is not required in this case! 154 | FlexiTimer2::set(TIMER2VAL, Timer2_Overflow_ISR); 155 | FlexiTimer2::start(); 156 | 157 | 158 | // MCU sleep mode = idle. 159 | //outb(MCUCR,(inp(MCUCR) | (1<> 8)); // Write High Byte 188 | TXBuf[((2*CurrentCh) + HEADERLEN + 1)] = ((unsigned char)(ADC_Value & 0x00FF)); // Write Low Byte 189 | } 190 | 191 | // Send Packet 192 | for(TXIndex=0;TXIndex<17;TXIndex++){ 193 | Serial.write(TXBuf[TXIndex]); 194 | } 195 | 196 | // Increment the packet counter 197 | TXBuf[3]++; 198 | 199 | // Generate the CAL_SIGnal 200 | counter++; // increment the devider counter 201 | if(counter == 12){ // 250/12/2 = 10.4Hz ->Toggle frequency 202 | counter = 0; 203 | toggle_GAL_SIG(); // Generate CAL signal with frequ ~10Hz 204 | } 205 | } 206 | 207 | 208 | /****************************************************/ 209 | /* Function name: loop */ 210 | /* Parameters */ 211 | /* Input : No */ 212 | /* Output : No */ 213 | /* Action: Puts MCU into sleep mode. */ 214 | /****************************************************/ 215 | void loop() { 216 | __asm__ __volatile__ ("sleep"); 217 | 218 | // if (Serial.available() > 0) { 219 | // pos = Serial.read(); 220 | // if (pos != -1) { 221 | // myservo.write(pos); 222 | // delay(100); 223 | // } 224 | //} 225 | 226 | } 227 | -------------------------------------------------------------------------------- /Arduino/DC Motor/DC_example.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | Servo myservo; // create servo object to control a servo 4 | // twelve servo objects can be created on most boards 5 | 6 | 7 | // Arduino pins for the shift register 8 | #define MOTORLATCH 12 9 | #define MOTORCLK 4 10 | #define MOTORENABLE 7 11 | #define MOTORDATA 8 12 | 13 | // 8-bit bus after the 74HC595 shift register 14 | // (not Arduino pins) 15 | // These are used to set the direction of the bridge driver. 16 | #define MOTOR1_A 2 17 | #define MOTOR1_B 3 18 | #define MOTOR2_A 1 19 | #define MOTOR2_B 4 20 | #define MOTOR3_A 5 21 | #define MOTOR3_B 7 22 | #define MOTOR4_A 0 23 | #define MOTOR4_B 6 24 | 25 | // Arduino pins for the PWM signals. 26 | #define MOTOR1_PWM 11 27 | #define MOTOR2_PWM 3 28 | #define MOTOR3_PWM 6 29 | #define MOTOR4_PWM 5 30 | 31 | // Codes for the motor function. 32 | #define FORWARD 1 33 | #define BACKWARD 2 34 | #define BRAKE 3 35 | #define RELEASE 4 36 | 37 | 38 | // All definitions 39 | #define NUMCHANNELS 6 40 | #define HEADERLEN 4 41 | #define PACKETLEN (NUMCHANNELS * 2 + HEADERLEN + 1) 42 | #define SAMPFREQ 256 // ADC sampling rate 256 43 | #define TIMER2VAL (1024/(SAMPFREQ)) // Set 256Hz sampling frequency 44 | #define LED1 13 45 | #define CAL_SIG 9 46 | const int num_read = 26; 47 | // Global constants and variables 48 | volatile unsigned char TXBuf[PACKETLEN]; //The transmission packet 49 | volatile unsigned char TXIndex; //Next byte to write in the transmission packet. 50 | volatile unsigned char CurrentCh; //Current channel being sampled. 51 | volatile unsigned char counter = 0; //Additional divider used to generate CAL_SIG 52 | volatile unsigned int ADC_Value[6] = {0, 0, 0, 0, 0, 0}; //ADC current value 53 | float Data[num_read][2]; //Data circular buffer 54 | float RMS[3][2]; 55 | float F[2][2]; 56 | int Data_p = 0, one23 = 0; 57 | float B_f[3] = { -0.0214, 0.9572, -0.0214}; 58 | byte motor_speed = 0; 59 | 60 | 61 | 62 | void setup() { 63 | Serial.begin(115200); 64 | 65 | myservo.attach(10); 66 | // make motor 4 move forwards at full speed 67 | motor(1,BACKWARD,255); 68 | // make motor 4 move forwards at full speed 69 | motor(2,BACKWARD,255); 70 | // make motor 4 move forwards at full speed 71 | motor(3,BACKWARD,255); 72 | // make motor 4 move forwards at full speed 73 | motor(4,BACKWARD,255); 74 | 75 | myservo.write(110); 76 | Serial.println("Backwards reset"); 77 | myservo.write(40); 78 | delay(2000); 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | } 88 | 89 | 90 | void loop() { 91 | 92 | // make motor 4 move forwards at full speed 93 | motor(1,FORWARD,255); 94 | // make motor 4 move forwards at full speed 95 | motor(2,FORWARD,255); 96 | // make motor 4 move forwards at full speed 97 | motor(3,FORWARD,255); 98 | // make motor 4 move forwards at full speed 99 | motor(4,FORWARD,255); 100 | myservo.write(40); 101 | Serial.println("Forward "); 102 | 103 | delay(1500); 104 | 105 | // make motor 4 move forwards at full speed 106 | motor(1,BACKWARD,255); 107 | // make motor 4 move forwards at full speed 108 | motor(2,BACKWARD,255); 109 | // make motor 4 move forwards at full speed 110 | motor(3,BACKWARD,255); 111 | // make motor 4 move forwards at full speed 112 | motor(4,BACKWARD,255); 113 | myservo.write(110); 114 | Serial.println("backwards "); 115 | 116 | delay(1500); 117 | 118 | 119 | } 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | // IGNORE BELOW HERE 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | void motor(int nMotor, int command, int speed) 142 | { 143 | int motorA, motorB; 144 | 145 | if (nMotor >= 1 && nMotor <= 4) 146 | { 147 | switch (nMotor) 148 | { 149 | case 1: 150 | motorA = MOTOR1_A; 151 | motorB = MOTOR1_B; 152 | break; 153 | case 2: 154 | motorA = MOTOR2_A; 155 | motorB = MOTOR2_B; 156 | break; 157 | case 3: 158 | motorA = MOTOR3_A; 159 | motorB = MOTOR3_B; 160 | break; 161 | case 4: 162 | motorA = MOTOR4_A; 163 | motorB = MOTOR4_B; 164 | break; 165 | default: 166 | break; 167 | } 168 | 169 | switch (command) 170 | { 171 | case FORWARD: 172 | motor_output (motorA, HIGH, speed); 173 | motor_output (motorB, LOW, -1); // -1: no PWM set 174 | break; 175 | case BACKWARD: 176 | motor_output (motorA, LOW, speed); 177 | motor_output (motorB, HIGH, -1); // -1: no PWM set 178 | break; 179 | case BRAKE: 180 | // The AdaFruit library didn't implement a brake. 181 | // The L293D motor driver ic doesn't have a good 182 | // brake anyway. 183 | // It uses transistors inside, and not mosfets. 184 | // Some use a software break, by using a short 185 | // reverse voltage. 186 | // This brake will try to brake, by enabling 187 | // the output and by pulling both outputs to ground. 188 | // But it isn't a good break. 189 | motor_output (motorA, LOW, 255); // 255: fully on. 190 | motor_output (motorB, LOW, -1); // -1: no PWM set 191 | break; 192 | case RELEASE: 193 | motor_output (motorA, LOW, 0); // 0: output floating. 194 | motor_output (motorB, LOW, -1); // -1: no PWM set 195 | break; 196 | default: 197 | break; 198 | } 199 | } 200 | } 201 | 202 | 203 | // --------------------------------- 204 | // motor_output 205 | // 206 | // The function motor_ouput uses the motor driver to 207 | // drive normal outputs like lights, relays, solenoids, 208 | // DC motors (but not in reverse). 209 | // 210 | // It is also used as an internal helper function 211 | // for the motor() function. 212 | // 213 | // The high_low variable should be set 'HIGH' 214 | // to drive lights, etc. 215 | // It can be set 'LOW', to switch it off, 216 | // but also a 'speed' of 0 will switch it off. 217 | // 218 | // The 'speed' sets the PWM for 0...255, and is for 219 | // both pins of the motor output. 220 | // For example, if motor 3 side 'A' is used to for a 221 | // dimmed light at 50% (speed is 128), also the 222 | // motor 3 side 'B' output will be dimmed for 50%. 223 | // Set to 0 for completelty off (high impedance). 224 | // Set to 255 for fully on. 225 | // Special settings for the PWM speed: 226 | // Set to -1 for not setting the PWM at all. 227 | // 228 | void motor_output (int output, int high_low, int speed) 229 | { 230 | int motorPWM; 231 | 232 | switch (output) 233 | { 234 | case MOTOR1_A: 235 | case MOTOR1_B: 236 | motorPWM = MOTOR1_PWM; 237 | break; 238 | case MOTOR2_A: 239 | case MOTOR2_B: 240 | motorPWM = MOTOR2_PWM; 241 | break; 242 | case MOTOR3_A: 243 | case MOTOR3_B: 244 | motorPWM = MOTOR3_PWM; 245 | break; 246 | case MOTOR4_A: 247 | case MOTOR4_B: 248 | motorPWM = MOTOR4_PWM; 249 | break; 250 | default: 251 | // Use speed as error flag, -3333 = invalid output. 252 | speed = -3333; 253 | break; 254 | } 255 | 256 | if (speed != -3333) 257 | { 258 | // Set the direction with the shift register 259 | // on the MotorShield, even if the speed = -1. 260 | // In that case the direction will be set, but 261 | // not the PWM. 262 | shiftWrite(output, high_low); 263 | 264 | // set PWM only if it is valid 265 | if (speed >= 0 && speed <= 255) 266 | { 267 | analogWrite(motorPWM, speed); 268 | } 269 | } 270 | } 271 | 272 | 273 | // --------------------------------- 274 | // shiftWrite 275 | // 276 | // The parameters are just like digitalWrite(). 277 | // 278 | // The output is the pin 0...7 (the pin behind 279 | // the shift register). 280 | // The second parameter is HIGH or LOW. 281 | // 282 | // There is no initialization function. 283 | // Initialization is automatically done at the first 284 | // time it is used. 285 | // 286 | void shiftWrite(int output, int high_low) 287 | { 288 | static int latch_copy; 289 | static int shift_register_initialized = false; 290 | 291 | // Do the initialization on the fly, 292 | // at the first time it is used. 293 | if (!shift_register_initialized) 294 | { 295 | // Set pins for shift register to output 296 | pinMode(MOTORLATCH, OUTPUT); 297 | pinMode(MOTORENABLE, OUTPUT); 298 | pinMode(MOTORDATA, OUTPUT); 299 | pinMode(MOTORCLK, OUTPUT); 300 | 301 | // Set pins for shift register to default value (low); 302 | digitalWrite(MOTORDATA, LOW); 303 | digitalWrite(MOTORLATCH, LOW); 304 | digitalWrite(MOTORCLK, LOW); 305 | // Enable the shift register, set Enable pin Low. 306 | digitalWrite(MOTORENABLE, LOW); 307 | 308 | // start with all outputs (of the shift register) low 309 | latch_copy = 0; 310 | 311 | shift_register_initialized = true; 312 | } 313 | 314 | // The defines HIGH and LOW are 1 and 0. 315 | // So this is valid. 316 | bitWrite(latch_copy, output, high_low); 317 | 318 | // Use the default Arduino 'shiftOut()' function to 319 | // shift the bits with the MOTORCLK as clock pulse. 320 | // The 74HC595 shiftregister wants the MSB first. 321 | // After that, generate a latch pulse with MOTORLATCH. 322 | shiftOut(MOTORDATA, MOTORCLK, MSBFIRST, latch_copy); 323 | delayMicroseconds(5); // For safety, not really needed. 324 | digitalWrite(MOTORLATCH, HIGH); 325 | delayMicroseconds(5); // For safety, not really needed. 326 | digitalWrite(MOTORLATCH, LOW); 327 | } 328 | -------------------------------------------------------------------------------- /emg_api.py: -------------------------------------------------------------------------------- 1 | import struct 2 | import time 3 | from collections import deque 4 | from multiprocessing import Process 5 | 6 | import matplotlib.pyplot as plt 7 | import numpy as np 8 | import serial 9 | 10 | 11 | class EMG: 12 | def __init__(self, serialport='/dev/cu.wchusbserial1420',baudrate=115200,numread=30,packsize=17,frequency=256,syncbyte1=165,syncbyte2=90,connection_timeout=5,first_byte = b'A',plotting=True, plotsize=256,nstd_timespan=256): 13 | self.serialport = serialport 14 | self.baudrate = baudrate 15 | self.numread = numread 16 | self.packsize = packsize 17 | self.frequency = frequency 18 | self.syncbyte1 = syncbyte1 19 | self.syncbyte2 = syncbyte2 20 | self.timeout = connection_timeout 21 | self.first_byte = first_byte 22 | self.arduino = None 23 | self.plotting = plotting 24 | self.nstd_timespan = nstd_timespan 25 | self.plotsize = plotsize 26 | 27 | self.plotsize_nstd = self.nstd_timespan 28 | 29 | # Voltage data of 6 channels 30 | self.data = [deque(list(np.zeros(self.plotsize)),maxlen=self.plotsize) for x in range(0,6)] 31 | self.x_time = deque([x/self.frequency for x in range(self.plotsize)],maxlen=self.plotsize) 32 | 33 | # Fourie transform of voltage data for Channel 1 34 | self.nfft = self.frequency 35 | self.ft_x = [int(self.frequency/self.nfft*x) for x in range(int(self.nfft/2))] 36 | self.ft0_data = [[0]*self.nfft] 37 | self.ft_data = [[0]*len(self.ft_x)] 38 | 39 | # Standard deviation of 6 channels 40 | self.nstd_data = [deque([0],maxlen=self.plotsize_nstd) for x in range(0,6)] 41 | 42 | # Trying to pick up rigth time scale (doesn't work properly, redesign is needed) 43 | self.nstd_time = deque([0],maxlen=self.plotsize_nstd) 44 | 45 | 46 | def establish_connection(self): 47 | try: 48 | self.arduino = serial.Serial(self.serialport, self.baudrate) 49 | print('Serial port found. Trying to establish connection...') 50 | except Exception as e: 51 | print('Could not find serial port: error {}'.format(e)) 52 | return None 53 | 54 | time_elapsed = 0 55 | connection_established = False 56 | first_byte = None 57 | 58 | t = time.time() 59 | while time_elapsed < self.timeout: 60 | 61 | time_elapsed = time.time() - t 62 | 63 | if self.arduino.inWaiting(): 64 | first_byte = self.arduino.read(1) 65 | 66 | if first_byte == self.first_byte: 67 | self.arduino.write(b'h') 68 | connection_established = True 69 | time_elapsed = self.timeout + 1 70 | 71 | if connection_established: 72 | print('Connection established in {} seconds.'.format(np.round(time.time() - t, 2))) 73 | return self.arduino 74 | else: 75 | print('Connection failed.') 76 | self.arduino.close() 77 | return None 78 | 79 | def clean_port(self): 80 | while self.arduino.inWaiting() >= 2 * self.packsize: 81 | self.read_pack() 82 | print('Serial port is cleaned') 83 | 84 | def typecast_swap_float(self, arr): 85 | ''' 86 | Magicaly converts uint8 array to uint16 value and swap bytes. 87 | Replica of Matlab typecast() and swapbytes() functions 88 | Returns float value 89 | ''' 90 | arr = np.flip(np.uint8(arr),0) 91 | result = float(np.uint16(arr[1]/255*65535-arr[1]+arr[0])) 92 | return result 93 | 94 | def datasync(self, A): 95 | ''' 96 | Resolve serial data flow desync, 97 | which tend to be a common problem on Mac only (don't know why) 98 | ''' 99 | print(A) 100 | sb1_index = A.index(self.syncbyte1) 101 | sb2_index = A.index(self.syncbyte2) 102 | B = struct.unpack('{}B'.format(sb1_index),self.arduino.read(sb1_index)) 103 | A = A[sb1_index:] + B 104 | print(A) 105 | print('Synchronization is done.') 106 | return(A) 107 | 108 | def read_pack(self): 109 | A = struct.unpack('{}B'.format(self.packsize),self.arduino.read(self.packsize)) 110 | 111 | # Checks if what we just read is valid data, if desync resolve it 112 | while True: 113 | if self.syncbyte1 in A and self.syncbyte2 in A: 114 | break 115 | A = struct.unpack('{}B'.format(self.packsize),self.arduino.read(self.packsize)) 116 | if A[0] != self.syncbyte1 or A[1] != self.syncbyte2: 117 | A = self.datasync(A) 118 | 119 | return A 120 | 121 | def read_packs(self, num = None): 122 | if num == None: num = self.numread 123 | if self.arduino.inWaiting() >= num * self.packsize: 124 | for i in range(self.numread): 125 | A = self.read_pack() 126 | self.data[0].append(self.typecast_swap_float(A[4:6])) # Channel 1 data 127 | self.data[1].append(self.typecast_swap_float(A[6:8])) # Channel 2 data 128 | self.data[2].append(self.typecast_swap_float(A[8:10])) # Channel 3 data 129 | self.data[3].append(self.typecast_swap_float(A[10:12])) # Channel 4 data 130 | self.data[4].append(self.typecast_swap_float(A[12:14])) # Channel 5 data 131 | self.data[5].append(self.typecast_swap_float(A[14:16])) # Channel 6 data 132 | 133 | # Fourie transform 134 | self.ft0_data[0] = np.fft.fft(self.data[0],self.nfft) 135 | self.ft_data[0] = [10*np.log10(abs(x)**2/self.frequency/self.plotsize) for x in self.ft0_data[0][0:int(self.nfft/2)]] 136 | 137 | # Standard deviation 138 | self.compute_nanstd() 139 | self.nstd_time.append(self.nstd_time[-1]+self.numread*(1/self.frequency)) 140 | 141 | return self.data 142 | 143 | def compute_nanstd(self): 144 | self.nstd_data[0].append(np.nanstd(self.data[0])) 145 | self.nstd_data[1].append(np.nanstd(self.data[1])) 146 | self.nstd_data[2].append(np.nanstd(self.data[2])) 147 | self.nstd_data[3].append(np.nanstd(self.data[3])) 148 | self.nstd_data[4].append(np.nanstd(self.data[4])) 149 | self.nstd_data[5].append(np.nanstd(self.data[5])) 150 | return self.nstd_data 151 | 152 | def realtime_emg(self,plotting=True): 153 | ''' 154 | Realtime reading and processing of data from Arduino electrodes 155 | ''' 156 | plotsize = self.frequency 157 | # Plot initialization 158 | self.plot_init() 159 | 160 | p = Process(target=self.plot_update()) 161 | 162 | while True: 163 | 164 | self.read_packs() 165 | 166 | if not self.plot_update(): 167 | # Stop proccess when plot is closed by user 168 | break 169 | 170 | self.arduino.close() 171 | print('Connection closed.') 172 | 173 | def plot_init(self): 174 | self.fig = plt.figure() 175 | 176 | # Voltage channels plot 177 | self.ax = self.fig.add_subplot(311) 178 | self.ax.set_xlim(0,1) 179 | self.ax.set_xlabel('Time') 180 | self.ax.set_ylabel("Voltage") 181 | self.ax.grid() 182 | self.ax.ticklabel_format(axis='both', style='plain') 183 | 184 | self.ch1, = self.ax.plot(self.x_time, self.data[0], '-b', label ='Channel 1', linewidth = 0.5) 185 | self.ch2, = self.ax.plot(self.x_time, self.data[1], '--r', label ='Channel 2', linewidth = 0.5) 186 | self.ch3, = self.ax.plot(self.x_time, self.data[2], '--g', label ='Channel 3', linewidth = 0.5) 187 | self.ax.legend(loc='upper left') 188 | 189 | # Fourie transform plot 190 | self.ftplot = self.fig.add_subplot(312) 191 | self.ftplot.set_xlim(0,128) 192 | self.ftplot.set_title('Fourie transform') 193 | self.ftplot.set_xlabel('Freq') 194 | self.ftplot.set_ylabel('Power') 195 | self.ftplot.grid() 196 | self.ftplot.ticklabel_format(axis='both', style='plain') 197 | self.ft1, = self.ftplot.plot(self.ft_x, self.ft_data[0], '-b', label ='Channel 1', linewidth = 0.5) 198 | 199 | # Standard deviation of voltage plot 200 | self.nstdplot = self.fig.add_subplot(313) 201 | self.nstdplot.grid() 202 | self.nstdplot.set_xlabel('Time, sec') 203 | self.nstdplot.set_ylabel('Standard deviation of Voltage') 204 | 205 | self.ch1_nstd, = self.nstdplot.plot(self.nstd_time, self.nstd_data[0], '-b', label ='Channel 1', linewidth = 0.5) 206 | self.ch2_nstd, = self.nstdplot.plot(self.nstd_time, self.nstd_data[1], '-r', label ='Channel 2', linewidth = 0.5) 207 | self.ch3_nstd, = self.nstdplot.plot(self.nstd_time, self.nstd_data[2], '-g', label ='Channel 3', linewidth = 0.5) 208 | 209 | self.nstdplot.legend(loc='upper left') 210 | 211 | if self.plotting: 212 | print('Plot initialized') 213 | self.fig.show() 214 | 215 | return True 216 | 217 | def plot_update(self): 218 | if self.plotting: 219 | # Send all new data to plot 220 | self.ch1.set_ydata(self.data[0]) 221 | self.ch2.set_ydata(self.data[1]) 222 | self.ch3.set_ydata(self.data[2]) 223 | self.ax.relim() 224 | self.ax.autoscale_view() 225 | 226 | self.ft1.set_ydata(self.ft_data[0]) 227 | self.ftplot.relim() 228 | self.ftplot.autoscale_view() 229 | 230 | self.ch1_nstd.set_ydata(self.nstd_data[0]) 231 | self.ch2_nstd.set_ydata(self.nstd_data[1]) 232 | self.ch3_nstd.set_ydata(self.nstd_data[2]) 233 | self.ch1_nstd.set_xdata(self.nstd_time) 234 | self.ch2_nstd.set_xdata(self.nstd_time) 235 | self.ch3_nstd.set_xdata(self.nstd_time) 236 | 237 | self.nstdplot.relim() 238 | self.nstdplot.autoscale_view() 239 | 240 | # Checks if there are too many packets left in serial, e.g. if speed of processing is fast enough 241 | packets_inwaiting = self.inwaiting() 242 | if packets_inwaiting >= 50: 243 | print('Update rate is slow: {} packets inwaiting, {} second delay.'.format(packets_inwaiting, np.round(packets_inwaiting/256,2))) 244 | 245 | try: 246 | # Catches error when plot is closed by user 247 | plt.pause(0.0001) 248 | except: 249 | return False 250 | 251 | return True 252 | else: 253 | return False 254 | 255 | def inwaiting(self): 256 | return int(np.round(self.arduino.inWaiting()/self.packsize)) 257 | 258 | 259 | if __name__ == '__main__': 260 | print(serial.__file__) 261 | emg = EMG('COM3',numread=30, plotting=True) 262 | arduino = emg.establish_connection() 263 | if arduino: emg.realtime_emg() 264 | --------------------------------------------------------------------------------