Asyncronous serial port BytesAvail​ableFcn-ca​llback messes up variables

10 views (last 30 days)
I would like to communicate with two (or more) devices through a serial port. So I've built a class holding the serial handle to generate one instance for each device. For completely asynchronous operation i've created an event queue (basically a cell-array with one row for each event), that hold all data to send and the expected answers. The asynchronous operation is done by BytesAvailableFcn listening to the terminator of the device.
However the BytesAvailableFcn messes up my event queue: sometimes it adds empty rows at the start (yes, i can detect and delete them) and, more annoying, produces errors like accessing a non existent field (although it exists if the code is stopped by the debugger) or mismaching dimensions. Also sometimes the next send event is not invoked. I'm pretty sure, that this is due to the fact, that the BytesAvaliableFcn can be invoked at any time - also if my other code did call the MyInterface.write()-function and is modifying the event queue. Why am i pretty sure?
  1. These errors occur randomly: I can send the same commands and get the same answers from my device (the data is correct - i checked with data logging) and many times it works and one time i get an error.
  2. I've programmed several microcontrollers before. There are also exisiting interrrupts (similar thing like callbacks) - and if the interrupt does access(read/modify) the same data the (interrupted) main program is currently working on, the data may get messed up and sometimes the uC crashes.
What i would like to have: Something like an interrupt-enable-flag (like there is in microcontrollers): one can disable the execution of the interrupt, but the uC does recognize the interrupt event and executes the interrupt service routine (in MATLAB: callback function) when the execution is re-enabled later on. In MATLAB i can disable the BytesAvailableFcn-callback, but it is not executed on re-enabling, if any event was missed -> so this is a bad option.
I have already tried:
  • storing the event queue as a property of MyInterface as well as serial.UserData -> same bad results
  • programming someting like an Interrupt-enable-flag using a property of myInterface. To not miss an event, this value will get updated in the BytesAvailableFcn and checked outside. -> same problem with shared access (i thought, that maybe a one-element-integer-array might fix the problem)
  • Use a OutputEmptyFcn-callback to send the new data. This was stupid, because i need to wait for the device process the previous command before i can send a new one.
  • Search for something like Mutex although this would be not the right solution for my problem -> MATLAB does not support Mutexes as it is single-threaded.
  • Implementing very baxic Mutex stuff by myself -> variables get messed due to shared access.
Is there any functionality in MATLAB, that would fix my problem? I'm afraid, that even if i write the received data to any buffer and read it in another function of MyInterface the asynchronous callback would mess up the variable.
classdef MyInterface < handle
properties(Access = public, Constant = true)
%event queue states
EVENT_WRITE = 1; %Send something to the Device.
EVENT_READ = 2; %Read a message from the Device. It is not possible to
%add another command to the eventqueue until this
%read command is processed.
EVENT_CHECK_OK = 3; %The previous send command should be confirmed with
%a 'OK\r' by the Device.
end
properties(Access=private, Constant=true)
TERMINATOR = sprintf('\r');
end
properties(Access=private)
event_queue;
serial_h;
end
methods
function [obj] = MyInterface(comport)
obj.flushQueue();
obj.serial_h = serial(comport, 'BaudRate', 9600, ...
'Timeout', 10, ...
'Terminator', obj.TERMINATOR, 'BytesAvailableFcnMode', 'terminator', ...
'BytesAvailableFcn', @obj.bytesAvailableCallback);
fopen(obj.serial_h);
end
function delete(obj)
fclose(obj.serial_h);
end
function [] = write(obj, msg, next_operation)
%[] = write(obj, msg, next_operation)
%Write something to the serial port.
%@param msg: String: Your message.
%@param next_operation: int(1,1): The operation that should be
% performed on data reception
%This function has two sections:
% 1. Add your message to the eventqueue
% 2. Send the next message waiting in the eventqueue (if any)
if( ischar(msg) )
%Add the write command to the event queue
obj.event_queue(end+1:end+2, :) = {msg, obj.EVENT_WRITE, []; ...
msg, next_operation, obj.COMM_TRIALS};
if(size(obj.event_queue,1) == 2)
%only 2 elements in the queue -> the current command is the
%first -> execute it!
msg = NaN;
next_operation = '--processevent';
end
end
if( (numel(msg) == 1) && isnan(msg) && strcmp(next_operation, '--processevent') )
%process next event: msg = nan, next_operation = '--processevent'
if(obj.event_queue{1,2} ~= obj.EVENT_WRITE)
%this is no write event. Done.
return
end
fwrite(obj.serial_h, [obj.event_queue{1,1}, obj.TERMINATOR], 'async');
obj.event_queue(1,:) = [];
%Done. Delete the event
end
end
function [] = flushQueue(obj)
%[] = flushQueue(obj)
%Clear the event queue, including releasing the queue block
obj.event_queue = cell(0, 3);
end
function [] = bytesAvailableCallback(obj, serial_obj, ~) %device_obj, serial_obj, event
%[] = bytesAvailableCallback(device_obj, serial_obj, ~)
%Reads the bytes available on serial port and processes event
%queue.
%read data
obj.received_data = obj.read(serial_obj.BytesAvailable);
obj.received_data = char( obj.received_data(1:end-1) ); %delete terminator
%process event
switch obj.event_queue{1,2}
case obj.EVENT_READ
%Task: Read data. Done -> delete event
obj.event_queue(1,:) = [];
case obj.EVENT_CHECK_OK
%The last command should be confirmed with an 'OK'
if( strcmp(obj.received_data, 'OK') )
%Confirmation passed
obj.event_queue(1,:) = [];
obj.received_data = '';
else
%Confirmation error -> resend command
error('Expected message ''OK'', but got ''%s''.', obj.received_data);
end
otherwise
wrong_event = obj.event_queue{1,2};
obj.flushQueue();
error('Event type %.0f is unknown!', wrong_event);
end
if(~isempty(obj.event_queue) && (obj.event_queue{1,2} == obj.EVENT_WRITE) )
%start next write event
obj.write(NaN, '--processevent');
end
end
end
I am using MATLAB R2016b.
  1 Comment
Theo Husby
Theo Husby on 13 Jun 2023
It's been six years since this question was asked, but I wanted to add my 2 cents. I had a very similar problem handling two-way communication with MATLAB's new serialport class, but I found a workaround.
I found that my BytesAvailableFcn callback would sometimes execute when there were zero bytes in the buffer, but like Lukas observed, this behaviour was inconsistent. The workaround I found was to to eliminate any useage of the read() method, and to structure my code such that all incoming bytes were handled by my BytesAvailableFcn.
I'm not a serial communications expert (perhaps one can weigh in!), but I think BytesAvailableFcn can get queued up anytime that a byte is received, even if it's handled by read(), and there may be some kind of race condition in this case. Regardless of the reason though, using either BytesAvailableFcn or serialport.read() solved my problem.

Sign in to comment.

Answers (0)

Products

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!