1

I'm looking for suggestions on how I can improve my code so that I can get it to run more efficiently in real time. I've spent a fair bit of time trying to figure out how to vectorize my good and reduce loops and do better with plotting my data in real time (using Matlab R2019a)

I would appreciate suggestions on

1) Code vectorization

2) Trying to figure out how to use hold with subplot. Currently I initialize my subplot and plot before entering into the serial data routine. However, instead of adding each data point to the plot, I'm repeatedly plotting all my XData and YData from the beginning to the current point every iteration using the set command, which I think is wasteful.

3) Any other efficiency suggestions

4) f.stop is a function that creates a button letting me to start and stop the routine. If there's a better way to do this, for example using the plot handles in such a way that the routine stops when I close the plot, that would be appreciated as well

Code Background

The code is serially reading and plotting multiplexed capacitance data from a custom circuit board that is controlled by an arduino. The circuit board is polling a 3x3 array of capacitor cells, where each cell has 4 differential capacitance signals - a total of 36 signals. This is being plotted into a 3x3 subplot, 4 signals in each plot. The data is just a stream of numbers and I'm using 29069 and 29070 as markers to let me know when the board is going to start and finish polling all the excitation channels. The data stream is being read by looking at all 12 excitation channel signals sequentially and binning it into sets of 4, going to the next capacitance channel, repeating the process 3 times in total (case statements).

%% Serial Data Acquisition from Arduino to Matlab
clc
clear all
close all
%Preallocate Arrays
c1 = zeros(4,1000);
c2 = zeros(4,1000);
c3 = zeros(4,1000);
c4 = zeros(4,1000);
c5 = zeros(4,1000);
c6 = zeros(4,1000);
c7 = zeros(4,1000);
c8 = zeros(4,1000);
c9 = zeros(4,1000);
c1_timer = zeros(5,1000);
incoming_data_1 = zeros(4,8)
incoming_data_2 = zeros(4,8)
incoming_data_3 = zeros(4,8)
arduino=serial('COM5','BaudRate',38400);
count = 1;
flag = 0;
f = stoploop
plot_count_1 = 1;
plot_count_2 = 1;
plot_count_3 = 1;
data_count = 1;
exc_ch_number = 1;
total_exc_ch_number = 13;
cin_ch_number = 1;
total_cin_ch_number = 3;
% Setup the Initial Plot
figure(1)
subplot(3,3,1)
plotGraph1 = plot(1:plot_count_1,c1(:,1:plot_count_1));
xtickangle(90)
legend('Top Left', 'Bot Left', 'Bot Right', 'Top Right', 'Location','southwest');
subplot(3,3,2)
plotGraph2 = plot(1:plot_count_1,c2(:,1:plot_count_1));
xtickangle(90)
legend('Top Left', 'Bot Left', 'Bot Right', 'Top Right', 'Location','southwest');
subplot(3,3,3)
plotGraph3 = plot(1:plot_count_2,c3(:,1:plot_count_1));
xtickangle(90)
legend('Top Left', 'Bot Left', 'Bot Right', 'Top Right', 'Location','southwest');
subplot(3,3,4)
plotGraph4 = plot(1:plot_count_2,c4(:,1:plot_count_2));
xtickangle(90)
legend('Top Left', 'Bot Left', 'Bot Right', 'Top Right', 'Location','southwest');
subplot(3,3,5)
plotGraph5 = plot(1:plot_count_2,c5(:,1:plot_count_2));
xtickangle(90)
legend('Top Left', 'Bot Left', 'Bot Right', 'Top Right', 'Location','southwest');
subplot(3,3,6)
plotGraph6 = plot(1:plot_count_2,c6(:,1:plot_count_2));
xtickangle(90)
legend('Top Left', 'Bot Left', 'Bot Right', 'Top Right', 'Location','southwest');
subplot(3,3,7)
plotGraph7 = plot(1:plot_count_3,c7(:,1:plot_count_3));
xtickangle(90)
legend('Top Left', 'Bot Left', 'Bot Right', 'Top Right', 'Location','southwest');
subplot(3,3,8)
plotGraph8 = plot(1:plot_count_3,c8(:,1:plot_count_3));
xtickangle(90)
legend('Top Left', 'Bot Left', 'Bot Right', 'Top Right', 'Location','southwest');
subplot(3,3,9)
plotGraph9 = plot(1:plot_count_3,c9(:,1:plot_count_3));
xtickangle(90)
legend('Top Left', 'Bot Left', 'Bot Right', 'Top Right', 'Location','southwest');
fopen(arduino)
timer_value = tic;
% Start the Serial Data read routine
while (~f.Stop())

    data = fscanf(arduino, '%d')


     if(data == 29069)
        flag = 1;
        continue

     elseif(data == 29070)
         flag = 0;
         data_count = 1;
         exc_ch_number =  exc_ch_number+1;


         if(exc_ch_number>total_exc_ch_number)
             exc_ch_number = 1

             switch cin_ch_number
             case 1
             c1(1:4,plot_count_1) = ((incoming_data_1(3,1:4)'./16777215).*8.192)-4.096;
             c1_timer(5,plot_count_1) = round(toc(timer_value));
             c2(1:4,plot_count_1) = ((incoming_data_1(3,[5 6 7 8])'./16777215).*8.192)-4.096;
             c3(1:4,plot_count_1) = ((incoming_data_1(3,9:12)'./16777215).*8.192)-4.096;

             set(plotGraph1,'XData',c1_timer(5,1:plot_count_1),{'YData'},num2cell(c1(:,1:plot_count_1),2));

             set(plotGraph2,'XData',c1_timer(5,1:plot_count_1),{'YData'},num2cell(c2(:,1:plot_count_1),2));

             set(plotGraph3,'XData',c1_timer(5,1:plot_count_1),{'YData'},num2cell(c3(:,1:plot_count_1),2));

             plot_count_1 = plot_count_1+1;

             case 2
             c4(1:4,plot_count_2) = ((incoming_data_2(3,1:4)'./16777215).*8.192)-4.096;
             c5(1:4,plot_count_2) = ((incoming_data_2(3,[5 6 7 8])'./16777215).*8.192)-4.096;
             c6(1:4,plot_count_2) = ((incoming_data_2(3,9:12)'./16777215).*8.192)-4.096;

             set(plotGraph4,'XData',c1_timer(5,1:plot_count_2),{'YData'},num2cell(c4(:,1:plot_count_2),2));

             set(plotGraph5,'XData',c1_timer(5,1:plot_count_2),{'YData'},num2cell(c5(:,1:plot_count_2),2));

             set(plotGraph6,'XData',c1_timer(5,1:plot_count_2),{'YData'},num2cell(c6(:,1:plot_count_2),2));

             plot_count_2 = plot_count_2+1;


             case 3
             c7(1:4,plot_count_3) = ((incoming_data_3(3,1:4)'./16777215).*8.192)-4.096;
             c8(1:4,plot_count_3) = ((incoming_data_3(3,[5 6 7 8])'./16777215).*8.192)-4.096;
             c9(1:4,plot_count_3) = ((incoming_data_3(3,9:12)'./16777215).*8.192)-4.096;

             set(plotGraph7,'XData',c1_timer(5,1:plot_count_3),{'YData'},num2cell(c7(:,1:plot_count_3),2));

             set(plotGraph8,'XData',c1_timer(5,1:plot_count_3),{'YData'},num2cell(c8(:,1:plot_count_3),2));

             set(plotGraph9,'XData',c1_timer(5,1:plot_count_3),{'YData'},num2cell(c9(:,1:plot_count_3),2));

             plot_count_3 = plot_count_3+1;

             end

             cin_ch_number = cin_ch_number+1;


             if(cin_ch_number>total_cin_ch_number)
                 cin_ch_number = 1;
             end
         end
     end


     if(flag==1)
         switch cin_ch_number
             case 1
                 incoming_data_1(data_count,exc_ch_number) = data;

             case 2
                 incoming_data_2(data_count,exc_ch_number) = data;

             case 3
                 incoming_data_3(data_count,exc_ch_number) = data;
         end

         data_count = data_count+1;
     end



end
delete(arduino)
clear arduino

Cris Luengo
  • 55,762
  • 10
  • 62
  • 120

3 Answers3

0

I didn't try running your code as it would be difficult to do without the missing function or arduino hardware. You should take a look at the documentation for providing a minimal, reproducible example: https://stackoverflow.com/help/minimal-reproducible-example However, I can definitely offer some advice:

First, have you tried using the Matlab profiler? From the editor, you can use the "Run and Time" button. You can also run it from the command prompt like this:

profile on; my_func_here; profile viewer

The way you're updating the plot makes sense to me. That's what I would have done: Updating the XData/YData properties. Whether you re-write the whole vector or update a single position isn't really going to make a difference. As another approach, you could try calling plot to plot a single new point each time; but I suspect that will be slower. This is something you could easy try different approaches, profile each, and see which is the most efficient.

Regarding stopping the plot, your approach seems fine. If you want to make it stop when you close the plot, it will probably do that without any additional code because it will throw an error once the figure and plots are gone. If you want a neater solution, you could get a handle to the figure initially:

f = figure(1);

Then in your while loop, check that the figure is valid:

while isvalid(f)

Of course, you could still end up with the code throwing an error because you may close the figure between the isvalid check and when one of the plot functions are called. So in order to avoid that, you could wrap your calls to modify the plot in a try/catch.

Mike Scannell
  • 378
  • 1
  • 12
  • placing a `try/catch` in a loop supposed to go fast is going to be extremely expensive – Hoki Nov 08 '19 at 17:57
  • Is it? I wasn't aware of that. I ran a simple test function with and without a try/catch, and it didn't appear to make a difference. In what scenario would you expect a slowdown from using try/catch? – Mike Scannell Nov 11 '19 at 11:43
  • To `try` something and be able to `catch` any error/exception, MATLAB has to package the instructions in a special way. It's not exactly that but imagine MATLAB has to run this part of code in a _virtual environment_, to make sure that any fault that this code produce would not corrupt the state of the rest of the code outside the `try/catch` block. Setting up this virtual environment is costly. I don't don't know the details of MATLAB JIT compiler, but in C# if I have to use a `try/catch` I place it outside of the loop (the full loop runs inside the same `try/catch` block). – Hoki Nov 11 '19 at 11:49
0

You preallocate data arrays to 1000 elements, but there’s no guarantee you won’t use more. Thus you will likely be extending these arrays, which costs time.

Next, in statements like this:

set(plotGraph1,'XData',c1_timer(5,1:plot_count_1),{'YData'},num2cell(c1(:,1:plot_count_1),2));

you copy the data by indexing, and copy again to convert into a cell array, for every loop iteration. That is, num2cell(c1(:,1:plot_count_1),2) causes two copies of all the data recorded so far. This is way more expensive than appending to an array*. I would just append to the XData and YData properties, avoid duplicating and copying data all the time:

c1 = ((incoming_data_1(3,1:4)'./16777215).*8.192)-4.096;
c1_timer = round(toc(timer_value));
% ...
for ii=1:4
   plotGraph1(ii).XData(end+1) = c1_timer;
   plotGraph1(ii).YData(end+1) = c1(ii);
end

* Note that MATLAB, when appending, doubles the internal storage for the array when necessary, such that repeatedly adding n elements to an array costs O(n log n) rather than O(n²). See this other Q&A for a demonstration.


Minimal reproducible example:

figure(1)
timer_value = tic;
plotGraph1 = plot(0,zeros(4,1),'.-');
while true
   c1 = randn(4,1);
   c1_timer = toc(timer_value);
   % ...
   for ii=1:4
      plotGraph1(ii).XData(end+1) = c1_timer;
      plotGraph1(ii).YData(end+1) = c1(ii);
   end
   drawnow
   pause(0.1)
end
Cris Luengo
  • 55,762
  • 10
  • 62
  • 120
  • Thanks a bunch! I'm going to look into this. Also, thanks for posting the reproducible example. I had no idea how to do it with serial data – user3808947 Nov 04 '19 at 03:17
0

You probably want to consider using the animatedline function. It takes care of a lot of the optimizations you need to worry about in this type of case.

MPG
  • 835
  • 4
  • 10