You are now following this question
- You will see updates in your followed content feed.
- You may receive emails, depending on your communication preferences.
How to setup serial port in base workspace and access from function workspace?
2 views (last 30 days)
Show older comments
Hello,
I would like to setup a serial port via s = serialport(port,baudrate) in the base workspace (initialization file) since serial port setup wont change during the life of the connection this really only needs to run once. However, I would like to call the serial port object from other function/functions workspace/workspaces. What are best practices to achieve this or is this even an acceptable practice at all?
Thank you,
Wess
Answers (1)
dpb
on 3 Aug 2023
Simply pass the returned serial port object to the function(s) where it's needed.
Best factorization would probably be to write a little function to call to do the initialization as well rather than just a bunch of constants buried inline in code; gives you the flexibility to change parameters if/when needed.
31 Comments
Walter Roberson
on 3 Aug 2023
Please see http://matlab.wikia.com/wiki/FAQ#How_can_I_share_data_between_callback_functions_in_my_GUI.28s.29.3F for information on better approaches than using global
Wess Gates
on 3 Aug 2023
@Walter Roberson Nice MATLAB Fandom Wiki! Thank you for sharing the link. I dont really plan on using the serial for graphics/GUIS etc... are there some points on the wiki that maybe I am missing you intended for me to lock into?
@dpb Thank you. I have been passing the serial object to the functions but possibly unrealted this really seems to slow things down. I have a microcontroller running a 10Hz loop (fairly slow by todays standards). If I send/recieve in the same "base funtion" it seems to run fast but once I start passing that serial object around messages get bogged down but I am not certain what is causing this.
dpb
on 3 Aug 2023
I guess it will all depend upon how your structure looks; I was presuming basically would be
...
s=serialInitialize(params);
useSerial(s)
cleanup(s)
so that all the work would be done in one place.
If your code structure is either distributed into a whole lot of places so that useSerial() has to pass s on or if it were in a tight loop and passing it every time in the loop, the function overhead could get in the way.
Would have to see more of the structure I think to have any better ideas in that regards.
While global isn't ideal from a code structure viewpoint, sometimes it still has its place.
You might try using the profiler and see if you can point the blame at some particular piece...
Walter Roberson
on 3 Aug 2023
I just did some timing tests.
Creating a serial port in the base workspace, and passing it to a function, is over 50 times faster than creating a serial port in the base workspace and using evalin('base') in the function to bring the port into scope.
dpb
on 3 Aug 2023
Edited: dpb
on 3 Aug 2023
How does passing the object to the function compare to referring to the serial port in the base workspace when declared global, Walter?
I'm not surprised the evalin option would be high overhead; would be about the last option I'd ever think of for the purpose.
Wess Gates
on 3 Aug 2023
Most definately avoided even the idea of evalin etc.... I was attempting to be brief in the original question per the forum etiqutte but after running the profiler per @dpb suggestion I am wondering if I have left important info out?
I am reading/writing to serial via "brut force" method. I am not using a call back funtion or waiting for numByets etc... so I am guessing I probably need to do that. The number of calls to the "serial like functions" is out of control? Maybe some kind of underlying OS/MATALB level interrupts on Tx/Rx or something along those lines?
However, I was testing with and without (rateControl) and the number of calls at the serial I/O level is just as high as if rateControl is ignored from what I can tell.
MyRateObject = rateControl(my10HzRate);
reset(MyRateObject);
myRateLoop()
%execute stuff
waitfor(MyRateObject);
end
I appreciate the help and dicussion. Here is screen shot of performance.
I basically initialize serial in runSimulation and from here I pass it to simulate which then passes it to gncStep.
dpb
on 4 Aug 2023
I'm of no real help on the efficient/efffective use of the MATLAB serial port object itself, sorry, never have used it at all.
The only serial communications project I ever had was done using LMI Forth and assembler runing under CP/M on a S-bus man-in-the-loop tethered robotics system some 40+ years ago. It used 68030s; one for the control station and a pair with shared memory on the vehicle. All the serial communications from the controls to the robot was directly at the line control level and interrupt driven. And, I didn't even write it, a colleague did the communications portion while I concentrated on the controls algorithims for the manipulators and onboard instrumentation and mobility control.
Walter Roberson
on 4 Aug 2023
It is a bit tricky to construct equivalent timing tests. For example if you global a reference to a variable but do nothing with the variable otherwise, then is that really equivalent work to "using" the variable somehow? That is why the functions have class() calls -- for the side effect of forcing the function to refer to the variable somehow.
On my system, the timings were about
no serial, just rand, t = 0.000648458
serial created outside loop, passed in, t = 0.847501
serial created outside loop, evalin, t = 0.854252
serial created outside loop, global, t = 0.818474
with some variability. Over several executions, the global seems to be consistently faster, then passing the value in, with evalin slower.
If you uncomment the t2 and t3 tests (and displaying), those take a little over 20 seconds each on my system -- which demonstrates that creating and destroying the serial port inside the loop is pretty costly.
In some of my earlier versions of the code, the timings were measurably faster than what is displayed above. It turned out that the time required to delete() the serial port was significant, so it made a difference to delete() inside the timing section instead of after the timing section. Also, it turned out that not having a reference to the object (e.g., just passing it in without using it) made quite large difference to the timing.
clear all
%"prime the pump" -- in case the first serialport() call needs to
%pull functions into memory or the like
s = serialport('/dev/tty.URT1', 19200); delete(s);
%start the test
N = 25;
start = tic; for K = 1 : N; version0(); end; t1 = toc(start);
%{
clear s; start = tic; for K = 1 : N; s = serialport('/dev/tty.URT1', 19200); version0(); delete(s); end; t2 = toc(start);
start = tic; for K = 1 : N; version1(); end; t3 = toc(start);
%}
clear s; start = tic; s = serialport('/dev/tty.URT1', 19200); for K = 1 : N; version2(s); end; delete(s); t4 = toc(start);
clear s; start = tic; s = serialport('/dev/tty.URT1', 19200); for K = 1 : N; version3(); end; delete(s); t5 = toc(start);
clear s; global s
start = tic; s = serialport('/dev/tty.URT1', 19200); for K = 1 : N; version4(); end; delete(s); t6 = toc(start);
fprintf('no serial, just rand, t = %g\n', t1);
%{
fprintf('serial created inside loop, but unused, t = %g\n', t2);
fprintf('serial created inside function, t = %g\n', t3);
%}
fprintf('serial created outside loop, passed in, t = %g\n', t4);
fprintf('serial created outside loop, evalin, t = %g\n', t5);
fprintf('serial created outside loop, global, t = %g\n', t6);
function version0
rand(1,1000);
end
function version1
s = serialport('/dev/tty.URT1', 19200);
rand(1,1000);
delete(s);
end
function version2(s)
class(s);
rand(1,1000);
end
function version3()
s = evalin('base', 's');
class(s);
rand(1,1000);
end
function version4()
global s
class(s);
rand(1,1000);
end
dpb
on 5 Aug 2023
Edited: dpb
on 5 Aug 2023
That's pretty discouraging, Walter; there's only 5% difference between the best/worst of those. So is the conclusion just referencing a serial object is extremely expensive without even trying to use it to actually communicate? If that were to be so, it would seem to really make serial communication troublesome.
Or, is it more likely we're timing the wrong thing or demonstrated the wrong architecture?
Seems like what would have to do would be to do more like my earlier flow path of creating the port object, then passing it to the communications function that does its thing with the port until done, then returns to let the port be closed.
That, of course, presumes the port object is designed such that once it is open one can read/write to it without the excessive overhead of just getting around the container wrapper.
That's where my knowledge of how to use serialport fails; I understand low level interrupts from the 8520 UART (or its modern equivalents) and handling them directly; that's very fast even on old silicon but I don't know how to code the MATLAB serialport object to mimic it -- there isn't much in the way of useful examples using the object "in anger".
Walter Roberson
on 5 Aug 2023
I created a version that created a serial port, wrote a double into the base workspace, evalin() the double in a loop, the ndelete the serial port. That was the slowest version of them all, slower than where the evalin() was fetching the serial port. I don't understand that.
I also created a version that uses the same framework but does not create any serial port, just testing the speed of the different ways of accessing data. The fastest way for that was parameter passing, then global, then evalin() being slowest. All of those times were effectively noise compared to the times involving creating the serial ports -- so creating a serial port object appears to take about 0.8 seconds on my system.
I would wonder about whether the differences in timing I saw with the serial port version were just down to noise in the amount of time it took to create the serial object, but in my tests it was pretty consistent that if I did create a serial object, that global was the fastest access... even though Mathworks specifically documents that global is notably slower than the other ways of passing data.
I am starting to wonder whether some of the timings I see are accidents of code order in combination with JIT.
That is, historically I have noticed in the past that in a number of my timing tests, the first timing loop often executes more slowly than the other timing loops -- so if I added another copy of the first timing loop measuring exactly the same thing, that the times for the second copy would be significally better than the first version. This was not something that could be explained by simple JIT, since the code under test was being executed in a loop. The time taken to JIT something might affect the first one or two executions, but after that it should be fully JIT'd... but the longer times did not just affect the first one or two iterations of the timing loop.
In other timing-loop tests I found that the first 3 (especially) iterations often had much higher times, so now I might discard the first 5 iterations in figuring out mean / variance on timing.
Thus I am wondering whether in this particular case, changing the order of the code might affect which one was fastest.
clear all
%start the test
N = 25;
start = tic; for K = 1 : N; version0(); end; t1 = toc(start);
clear s; start = tic; s = 50; for K = 1 : N; version3(); end; t5 = toc(start);
clear s; start = tic; s = 50; for K = 1 : N; version2(s); end; t7 = toc(start);
clear s; global s
start = tic; s = 50; for K = 1 : N; version4(); end; t6 = toc(start);
fprintf('overhead, t = %g\n', t1);
fprintf('double evalin, t = %g\n', t5);
fprintf('double global, t = %g\n', t6);
fprintf('double passed in, t = %g\n', t7);
function version0
%rand(1,1000);
end
function version1
s = serialport('/dev/tty.URT1', 19200);
%rand(1,1000);
delete(s);
end
function version2(s)
class(s);
%rand(1,1000);
end
function version3()
s = evalin('base', 's');
class(s);
%rand(1,1000);
end
function version4()
global s
class(s);
%rand(1,1000);
end
Walter Roberson
on 5 Aug 2023
clear all
%"prime the pump" -- in case the first serialport() call needs to
%pull functions into memory or the like
s = serialport('/dev/tty.URT1', 19200); delete(s);
%start the test
N = 25;
start = tic; for K = 1 : N; version0(); end; t1 = toc(start);
%{
clear s; start = tic; for K = 1 : N; s = serialport('/dev/tty.URT1', 19200); version0(); delete(s); end; t2 = toc(start);
start = tic; for K = 1 : N; version1(); end; t3 = toc(start);
%}
clear s; start = tic; s = serialport('/dev/tty.URT1', 19200); t = 50; for K = 1 : N; version2(s); end; delete(s); t4 = toc(start);
clear s; start = tic; s = serialport('/dev/tty.URT1', 19200); t = 50; for K = 1 : N; version3(); end; delete(s); t5 = toc(start);
clear s; start = tic; s = serialport('/dev/tty.URT1', 19200); t = 50; for K = 1 : N; version2(t); end; delete(s); t7 = toc(start);
clear s; start = tic; t = serialport('/dev/tty.URT1', 19200); s = 50; for K = 1 : N; version3(); end; delete(t); t8 = toc(start);
clear s; global s
start = tic; s = serialport('/dev/tty.URT1', 19200); for K = 1 : N; version4(); end; delete(s); t6 = toc(start);
fprintf('no serial, just rand, t = %g\n', t1);
%{
fprintf('serial created inside loop, but unused, t = %g\n', t2);
fprintf('serial created inside function, t = %g\n', t3);
%}
fprintf('serial created outside loop, passed in, t = %g\n', t4);
fprintf('serial created outside loop, evalin, t = %g\n', t5);
fprintf('serial created outside loop, global, t = %g\n', t6);
fprintf('serial created outside loop, something else passed in, t = %g\n', t7);
fprintf('serial created outside loop, something else evalin, t = %g\n', t8);
function version0
%rand(1,1000);
end
function version1
s = serialport('/dev/tty.URT1', 19200);
%rand(1,1000);
delete(s);
end
function version2(s)
class(s);
%rand(1,1000);
end
function version3()
s = evalin('base', 's');
class(s);
%rand(1,1000);
end
function version4()
global s
class(s);
%rand(1,1000);
end
Wess Gates
on 5 Aug 2023
Edited: Wess Gates
on 6 Aug 2023
Thank you @Walter Roberson. This is starting to prove pretty interesting. I ran the test/code you shared and I see similar behavior. I did just discover that the serialport object defaults to a 10 sec timeout which seems to be what was sufficating my serial coms! I explicitly turned it down to something like 0.25.
I will use your example code and perhaps build it out to see if I can get some sense of "the cost" of using "write","read", "writeline", and "readline" in my setup. I am also using costly string2num and num2string to toss double values back and forth over the serial lines. I am just tyring to get a quick proof of concept up and then worry about binary packing and all that jazz.
dpb
on 6 Aug 2023
Good luck. You can use direct sscanf() and fprintf() to take some overhead off the conversion.
I finally came across configureCallback that seems to be the magic interface to set up the interrupt handling (MATLAB callbacks) for the serialport object that is the way to avoid polling or timeout waiting.
I've never used it under MATLAB and the amount of really useful example code is almost none that I've found, although there is one callback example at the above that seems plausible.
I'm too old for all the high-falutin' wrapping around the internals; I like direct access to the device for this kind of thing, but that, of course, is what MATLAB attempts to try to conceal from the user. Sometimes the abstraction really helps, here, so far for me anyway, it just complicates things. Just how much performance penalty there is if use the callback-driven scenario I don't have any idea, but you should be able to get it down to at least a reasonable latency it would appear.
Walter Roberson
on 6 Aug 2023
serialport does not support fcanf() or fprintf() . It supports read() and write(), which are equivalent to serial fread() and fwrite() . It supports readline() and writeline() and a couple more routines -- but no formatted I/O. So for serialport() you would readline() and sscanf() or otherwise post-process the result, and for output you would sprintf() or compose() and writeline() the result.
I have been using serial ports since the days of CP/M, and many of the design choices for serialport() baffle me.
dpb
on 6 Aug 2023
Edited: dpb
on 6 Aug 2023
It baffles me, but my serial port experience is all in that one application years ago...
The above was a typo/misstep; I meant the "s" versions instead as being faster than the higher-level "x2y" translation versions with which to build/convert the numerics.
Does the old serial work better even though deprecated, maybe?
The penchant TMW has introduced of thinking everything has to be oo really isn't always the best (or even better) choice, imo. It surely has been one of the big contributors in the blow up in size and duplication of functionality..
Wess Gates
on 6 Aug 2023
So it turns out the read() function (and possibly the other functions such as readline(), write() etc...I have not checked yet) suspend MATLAB execution if the user does not explicitly specify a timeout in the setup.
Walter Roberson
on 7 Aug 2023
The processing model used by serialport() and a number of other routines (for example the UDP routines) is as-if steam is of data is continuous until end-of-file / end-of-connection, and that each time you ask for input, the routine should wait patiently for data to become available. As far as the processing model is concerned, any timeout processing exists only to allow the designer to put limits on how long to wait in cases where the stream has failed (for example loss of power, or cable got unplugged, disk failed, or so on.)
The processing model is not designed for real(-ish) time work. It is not designed for the program to be able to poll and say "Hey, is there any available data? If not then I have something else to do", and it is not designed for a program to be able to say, "I'm willing to wait 200 ms for data now, and it is a normal circumstance for data to not always be available, but after 200 ms I have to do something else for a while".
The only way the processing model gives to be able to handle data not currently available is to raise an error() (though serialport() itself allows configuring an error function for timeouts.)
What can you do to avoid problems? Well, you have to pay attention to the NumBytesAvailabe -- and to deliberately skip read() or readline() if there is not enough data. Or configure a BytesAvailableFcn callback to handle data as it comes in.
dpb
on 7 Aug 2023
I notice there is a supporting function configureCallback to interface to the BytesAvailable callback function; it can be set on a number of bytes and then suck those up. The issue then becomes one of how to deal with connection issues as well if, as usual, there are reliability problems with the device.
The NumBytesAvailable property is readable so one could use a polling strategy it seems, if the reference to the object itself doesn't have such high overhead when asking for the value. How well that might work with a repetitive time interrupt I dunno, seems as those have some issues on occasion as well.
Wess Gates
on 7 Aug 2023
I originally avoided using the callback approach mainly because I am fuzzy on what that would look like. I have a MCU running a 10Hz control loop so "it will always" be sending/receiving messages to/from MATLAB. So at first glance the callback approach seems like it "will always be triggering" because there will "always be bytes available" .... maybe I am missing something? I have been trying to get this stuff to work inside of a ratecontrol type of setup and that is when I ran into the global or passing serial objects performance which led to my question of "what are best practices" inlight of this newish serialport object. I guess I will go out and tinker with the callback approach and learn something new.
dpb
on 7 Aug 2023
You set the number of bytes available to something other than 1; I think 64 is default but it can be what makes sense with your communications protocol if it isn't record-based as it seems the idea of the implementator was when writing it as Walter pointed out, being terminator based.
If the device supports hardware RTS/CTS, then it shouldn't lose data as long as your callback can service the port "frequently enough".
You should be able to poll, reading the NumBytesAvailable value and then pull that number; if another arrives meanwhile, it should not be lost; the Q? will be whether the implementation is up to handling the internal counter correctly so if there are N bytes available when ask but N+1 when the read() operation is performed, will the new query after the N are read show 1+however_many_more_come_since?
With direct access to the port in the olden days, you could always find out how that all worked; when its wrapped in an only semi-transparent shell, it's not so easy to determine.
Wess Gates
on 7 Aug 2023
I wish it was a classic UART etc... The MCU is using software serial (USB virtual COM port) so I dont really have the luxury of the RTS/CTS. I could move it to a hardware serial on the MCU side but that opens up another can of worms for me. I will test out the NumBytes but my spidy sense tells me something is odd there. The way I understand the serial callback documentation and examples is that its basically going to trigger at any cost once NumBytes is availalble. I am guessing this is going to break the rest of my code/execution i.e. I am not clear on matters of "execution priority" under the abstracted hood.
dpb
on 7 Aug 2023
Edited: dpb
on 7 Aug 2023
No, the NumBytesAvailable should let you set the size of the trigger buffer and once you unload the buffer, it should take another N bytes before that many are available again. That is, of course, assuming the implementation actually works.
What baud rate does the device use and the size of messages or is it continuous stream? The rate can let you estimate roughly the time between interrupts that should be generated and handled; depending upon the message content as to what has to be done to/with it as far as its content then also matters in order to be able to process it.
If the device doesn't send a terminator after each message to use as the trigger, that would seem to be the way to go.
Walter Roberson
on 7 Aug 2023
When you read() a serialport(), which is a binary read function, you can specify the exact number of items to read. If you are dealing with "lines" then it would be common to read() uint8 and handle buffering yourself, extract a portion of the buffer that is meaningful for your purposes, and typecast() it or char() it or native2unicode() it as needed.
The case where the MCU is sending binary fixed-size packets is relatively easy: test if bytes available is at least the packet size, when it is then read() the packet size, process the packet. Or configure the bytes available function count to the packet size, configure a callback, have the callback loop read() one packet-worth at a time until available bytes is less than the packet size, return from reading function.
Wess Gates
on 7 Aug 2023
Edited: Wess Gates
on 8 Aug 2023
I am using 115.2 kbps but I can run it slower I believe since my messages are short basically GPS Lat-Lon-Alt as double precision values. I dont have a custom binary packet/message yet. I am tossing stuff back and forth between the MCU and MATLAB as line terminated strings and then parsing and converting to/from doubles on either end as needed for now since the MCU and MATLAB offer the functions to convert from string represented doubles etc for quick proof of concept. I am using readline() and writline() but I will keep chugging along until something sticks I prefer to use the read() and write() functions instead as @Walter Roberson response is just reinforcing that I should have taken that route but I think this will insist on a binary structure message/packet.
Walter Roberson
on 7 Aug 2023
configureCallback has a "terminator" option that allows a callback function to be called each time a terminator is received -- allowing you to have a callback triggered each time a "line" is available.
dpb
on 7 Aug 2023
Yeah, if it is line-oriented and text strings of numeric values, then that would seem the best route; set the callback on the line termination and the the builtin readline function is hopefully optimized internally to do the conversion to the native double; hopefully they didn't take the lazy way out and call str2double() internally but the optimized c i/o formatting routines inside.
Walter Roberson
on 7 Aug 2023
readline() returns a string that you would have to process. If the string contains multiple numeric items with the same datatype then sscanf(); if the string contains a single numeric value then double() the string. If the string contains multiple items with different datatypes then you might be able to use textscan but for that kind of work it would also be common to use text manipulation to extract the pieces and convert them as needed, as that would tend to have lower overhead than using textscan()
dpb
on 7 Aug 2023
Edited: dpb
on 7 Aug 2023
I thought I had read that readline and friends returned everything as double()??? Supper's on the table so gotta' run instead of looking up...
OK, I see I had seen that with read, but readline does just return the string without conversion. Nothing like being consistent...so, in that case one has to do the conversion directly, yes. There then becomes question of does the device send data as a fixed format so message lengths are always the same or can/does the format change.
Wess Gates
on 8 Aug 2023
Thank you dpb and Walter. Thank you both. I implemented a callback and used the terminator. I also slowed down the baud rate to get a decent improvement. I am still bogging something down either on the MATLAB side or the MCU side or both but I am sure I will get to the bottom of it now that I have something I can work with. I really appreciate this. You both made the forum and discussion very inviting.
dpb
on 8 Aug 2023
"you might be able to use textscan but for that kind of work it would also be common to use text manipulation to extract the pieces and convert them as needed, as that would tend to have lower overhead than using textscan()"
I hadn't thought necessarily of the former; wonder how the new(ish) extract() and patterns stuff might stack up. I've begun using them a fair amount for convenience, but not for anything that has an performance impact that would show; purely for convenience in writing some pattern matching.
Personally, I'd not think of textscan first, either, but would tend to drop straight down to sscanf for such low-level work.
But, there's the other end of starting with the highest level there is and then only worry about performance/optimization if/when that is shown to not be fast enough.
Walter Roberson
on 8 Aug 2023
Early on, I tested the pattern-based routines, and at least at that time they were much slower than regexp() . But that might have changed.
See Also
Tags
Community Treasure Hunt
Find the treasures in MATLAB Central and discover how the community can help you!
Start Hunting!An Error Occurred
Unable to complete the action because of changes made to the page. Reload the page to see its updated state.
Select a Web Site
Choose a web site to get translated content where available and see local events and offers. Based on your location, we recommend that you select: .
You can also select a web site from the following list
How to Get Best Site Performance
Select the China site (in Chinese or English) for best site performance. Other MathWorks country sites are not optimized for visits from your location.
Americas
- América Latina (Español)
- Canada (English)
- United States (English)
Europe
- Belgium (English)
- Denmark (English)
- Deutschland (Deutsch)
- España (Español)
- Finland (English)
- France (Français)
- Ireland (English)
- Italia (Italiano)
- Luxembourg (English)
- Netherlands (English)
- Norway (English)
- Österreich (Deutsch)
- Portugal (English)
- Sweden (English)
- Switzerland
- United Kingdom(English)
Asia Pacific
- Australia (English)
- India (English)
- New Zealand (English)
- 中国
- 日本Japanese (日本語)
- 한국Korean (한국어)