Matlab串口數(shù)據(jù)接收發(fā)送流程和串口速度提高

在進(jìn)行PID參數(shù)整定的工作過程中,我需要將電機的轉(zhuǎn)速、扭矩、母線電壓、母線電流、相電壓、相電流等參數(shù)通過串口傳遞到電腦上用Matlab來進(jìn)行BP神經(jīng)網(wǎng)絡(luò)算法的PID整定計算,再將計算得到的PID參數(shù)傳給電機的驅(qū)動板,來控制電機運行。在整個過程中采用了Matlab來進(jìn)行串口數(shù)據(jù)的讀寫。目前整個流程已經(jīng)走通,在此將實現(xiàn)過程中遇到的問題,解決方法,以及尚未解決的疑問進(jìn)行總結(jié)。

首先聲明本人是一個初學(xué)Matlab的新手,因此下面大部分都是自己摸索的東西,可能有膚淺或者不簡潔的地方。這里寫出來主要還是對自己工作的一個總結(jié),以便未來的使用和提高。

Matlab串口數(shù)據(jù)接收一般可以通過兩種方法來實現(xiàn),一種是直接調(diào)用tmtool工具,找到相應(yīng)的串口,配置串口參數(shù),生成代碼;另一種方法是通過一系列與串口有關(guān)的函數(shù)來對串口進(jìn)行操作。

通過tmtool進(jìn)行串口設(shè)計比較簡單,如圖所示,在Communiate項目欄中可以對串口進(jìn)行讀寫操作并輸出,這里可以設(shè)置數(shù)據(jù)種類(Data type)為ASCII碼,二進(jìn)制、二進(jìn)制數(shù)據(jù)塊(這里筆者的理解是類似一個數(shù)據(jù)幀,擁有幀頭、傳輸數(shù)據(jù)個數(shù)位等)。通過數(shù)據(jù)格式(Data format)來設(shè)置接收數(shù)據(jù)的格式,例如ASCII碼的格式有字符、字符串、帶換行符的字符串等;二進(jìn)制有位數(shù)等。還可以設(shè)置寫入和讀取是否按照HEX(16進(jìn)制)進(jìn)行表示。


image.png

在Configure項目欄中,可以對串口的參數(shù)進(jìn)行配置,如圖所示。
BauRate是對串口波特率的設(shè)置,只有上下位機采用相同的波特率時串口才能夠進(jìn)行正常通訊。
DataBits : 是數(shù)據(jù)位,有8位和9位兩種,一般選8位。
DataTerminalReady(DTR) : 數(shù)據(jù)終端就緒,表明機器已經(jīng)準(zhǔn)備好可以接受數(shù)據(jù)的一個標(biāo)志位,一般用在RS232的串口場合。
FlowControl : 流控制位,當(dāng)數(shù)據(jù)讀寫速度存在差異時,可以采用流控制,來開啟和關(guān)閉數(shù)據(jù)流,有硬件和軟件控制兩種方式。
InputBufferSize/OutputBufferSize : 是輸入緩存大小和輸出緩存大小,單位是字節(jié)。
Parity : 奇偶校驗位,這里應(yīng)該與下位機格式相匹配。
RequestToSend(RTS): 請求結(jié)束位,也是在RS232中常用。
StopBits: 結(jié)束位,這里應(yīng)該與下位機格式相匹配。
Terminator: 中斷標(biāo)志,這里是以某個字符作為中斷函數(shù)入口的標(biāo)志,檢測到這個字符存在后即可進(jìn)入回調(diào)函數(shù)。
Timeout: 溢出時間,當(dāng)開始接收后超過這個時間還沒有數(shù)據(jù)還沒有接收完成,即可認(rèn)為數(shù)據(jù)接收失敗,這個時間可按照實際傳輸周期來設(shè)定,一般越小越好。
ByreOrder: 是數(shù)據(jù)的傳輸模式,有大端模式和小端模式兩種。小端模式是指數(shù)據(jù)的高字節(jié)保存在內(nèi)存的高地址中,大端模式是指數(shù)據(jù)的高字節(jié)保存在內(nèi)存的低地址中。以unsigned int value = 0x12345678為例,該數(shù)分為四個字節(jié) 12 34 56 78:
小端模式情況下,字節(jié)存儲順序是
Buffer[3]=0x12 ------高位
Buffer[2]=0x34
Buffer[1]=0x56
Buffer[0]=0x78 ------低位
大端模式下,字節(jié)存儲順序是
Buffer[3]=0x78------高位
Buffer[2]=0x56
Buffer[1]=0x34
Buffer[0]=0x12 ------低位

大端模式和小端模式只在傳輸?shù)囊粋€數(shù)據(jù)是個多字節(jié)數(shù)據(jù)時,才需要區(qū)分。一般來說下位機ARM是按照小端模式來存儲的,這一現(xiàn)象我會在另一篇下位機串口通訊時將共用體時舉出例子。因為我這里傳輸?shù)臄?shù)據(jù)每次就是一個字節(jié),因此無所謂大小端。

image

Session Log項目欄可以將之前的各種操作用函數(shù)語言來表示。

采用與串口有關(guān)的函數(shù)也能實現(xiàn)對串口的操作。作者主要是通過這種方法來實現(xiàn)整個串口的運行。

首先了解幾個主要函數(shù):

delete(instrfindall) : 刪除所有串口信息,在程序結(jié)束后沒有刪除串口信息,可能會導(dǎo)致串口被占用,下一次程序無法使用串口或者其他軟件無法使用串口。因此在不再使用串口的時候,應(yīng)將串口釋放。為了Matlab運行的正確性,也可以將運行中的所有工作變量刪除,這里可以采用clera all語句。

obj1=serial('com2'): 定義某串口為變量obj1,也就是相當(dāng)于為串口com2起了一個名字叫obj1,后面對obj1操作就相當(dāng)于對com2操作。

fclose(obj1)/fopen(obj1): 關(guān)閉串口和開啟串口,有些參數(shù)設(shè)定需要先關(guān)閉串口,因此在進(jìn)行串口參數(shù)設(shè)定前先將串口關(guān)閉,設(shè)定完成后再打開。

obj1:直接在命令行窗口輸入定義好的串口變量,可以看到一個串口obj1的參數(shù)列表。

set(obj1, 'BaudRate', 115200): 通過set函數(shù)可以對obj1進(jìn)行參數(shù)設(shè)定,格式是set(串口變量名,'參數(shù)名稱',參數(shù)),這里是設(shè)定波特率是115200。

set(obj1, 'InputBufferSize', 100): 輸入緩存大小 100 字節(jié)。

set(obj1, 'OutputBufferSize', 100): 輸出緩存大小 100 字節(jié)。

set(obj1, 'Timeout', 1.0): 溢出時間 1.0 s。

set(obj1,'BytesAvailableFcnMode','terminator'): 回調(diào)函數(shù)中斷的模式,有兩種,一種是terminator模式,關(guān)鍵詞中斷,當(dāng)檢測到某一關(guān)鍵字時進(jìn)行中斷。一種是byte模式,當(dāng)二進(jìn)制達(dá)到某個字節(jié)數(shù)時中斷。

set(obj1,'terminator','D'): 中斷關(guān)鍵字是ASCII的D

set(obj1,'BytesAvailableFcn',@my_callback): 回調(diào)函數(shù)的設(shè)定,當(dāng)發(fā)生上述中斷時,進(jìn)入回調(diào)函數(shù)my_callback,在回調(diào)函數(shù)里可以對數(shù)據(jù)進(jìn)行讀寫等操作。

(上面串口中斷模式還有byte模式,這里給出函數(shù)示例: set(obj1,'BytesAvailableFcnMode','terminator')
set(obj1,'byte',24) )

在回調(diào)函數(shù)my_callback里面,可以進(jìn)行串口數(shù)據(jù)的讀取和發(fā)送等操作,下面先介紹幾個串口接收和發(fā)送相關(guān)的函數(shù):

data=fread(obj1, 24):向串口obj1讀取24個數(shù)據(jù),因為我這里數(shù)據(jù)是一個數(shù)據(jù)一個字節(jié),因此也就相當(dāng)于24個字節(jié),這里默認(rèn)情況下是按照一個數(shù)據(jù)8位,也可以加上數(shù)據(jù)格式,告訴電腦一個每個數(shù)據(jù)是什么格式,如data= fread(obj1, 24,'short')。

fwrite(obj2, sendbuff, 'uint8'):向串口obj2發(fā)送數(shù)據(jù),被發(fā)送的數(shù)據(jù)存儲在筆者自定的一個向量sendbuff中,這里會將sendbuff中的所有數(shù)據(jù)按照unit8的的格式依次發(fā)送。

這里我將整個串口設(shè)置和回調(diào)函數(shù)的代碼給出:
代碼1:主函數(shù)
因為項目需要,這里有一個串口接收數(shù)據(jù),一個串口發(fā)送數(shù)據(jù)。這里我接收的數(shù)據(jù)是一個數(shù)據(jù)幀24個字節(jié),發(fā)送的數(shù)據(jù)是8個字節(jié)。在接收的字節(jié)中幀頭是'D',所以采用關(guān)鍵字中斷模式,中斷標(biāo)志是'D'。

%主函數(shù) 進(jìn)行串口設(shè)置和開啟
delete(instrfindall)
clear obj1
clear obj2
clear all
global obj1;
global obj2;
global data;
global sendbuff;
global data1;
global data2;
global data3;
global data4;
global data5;
global data6;

global n;
n=0;
data=zeros(24,1)
sendbuff=zeros(1,8);
sendbuff(7)= hex2dec('0D');
sendbuff(8)= hex2dec('0A');

data1=0;
data2=0;
data3=0;
data4=0;
data5=0;
data6=0;

obj1=serial('com9');
fclose(obj1);
set(obj1, 'InputBufferSize', 100);
set(obj1, 'OutputBufferSize', 100);
set(obj1, 'BaudRate', 112500);
set(obj1, 'Timeout', 1.0);
set(obj1,'BytesAvailableFcnMode','terminator');
set(obj1,'terminator','D');
set(obj1,'BytesAvailableFcn',@my_callback1);
fopen(obj1);

obj2=serial('com8');
fclose(obj2);
set(obj2, 'InputBufferSize', 24);
set(obj2, 'OutputBufferSize', 24);
set(obj2, 'BaudRate', 115200);
set(obj2, 'Timeout', 1.0);
fopen(obj2);

代碼2:回調(diào)函數(shù)
進(jìn)入回調(diào)函數(shù)后對數(shù)據(jù)進(jìn)行讀取和處理,然后發(fā)送給下位機
接收數(shù)據(jù)幀的格式是:
字節(jié)1:'D',10進(jìn)制表示是68
字節(jié)2:0x05,10進(jìn)制表示是5,1-2是幀頭,然后是數(shù)據(jù)位
字節(jié)3-4:表示電機轉(zhuǎn)速,16位數(shù)據(jù)
字節(jié)5-6:表示負(fù)載扭矩,16位數(shù)據(jù)
字節(jié)7-10:表示母線電壓,32位數(shù)據(jù)
字節(jié)11-14:表示母線電流,32位數(shù)據(jù)
字節(jié)15-18:表示相電壓,32位數(shù)據(jù)
字節(jié)19-22:表示相電流,32位數(shù)據(jù)
字節(jié)23:0X0D
字節(jié)24:0X0A

發(fā)送數(shù)據(jù)幀的格式:
字節(jié)1-2:表示Kp,16位數(shù)據(jù)
字節(jié)3-4:表示Ki,16位數(shù)據(jù)
字節(jié)5-6:表示Kd,16位數(shù)據(jù)
字節(jié)7:0X0D
字節(jié)8:0X0A

function my_callback1(obj1,event)
global data;
global obj2;
global sendbuff;
global data1;
global data2;
global data3;
global data4;
global data5;
global data6;
global n;
n=n+1;

data= fread(obj1, 24)
if lenth(data)==24

  if data(1) == 68
    if data(2) == 5
            data1 = (data(3)*256+data(4))/1000;
            data2 = (data(5)*256+data(6))/1000;
            data3 = (data(7)*16777216+data(8)*65536+data(9)*256+data(10))/100;
            data4 = (data(11)*16777216+data(12)*65536+data(13)*256+data(14))/100;
            data5 = (data(15)*16777216+data(16)*65536+data(17)*256+data(18))/100;
            data6 = (data(19)*16777216+data(20)*65536+data(21)*256+data(22))/100;
    end
  end
 
  if data(1) == 5
            data1 = (data(2)*256+data(3))/1000;
            data2 = (data(4)*256+data(5))/1000;
            data3 = (data(6)*16777216+data(7)*65536+data(8)*256+data(9))/100;
            data4 = (data(10)*16777216+data(11)*65536+data(12)*256+data(13))/100;
            data5 = (data(14)*16777216+data(15)*65536+data(16)*256+data(17))/100;
            data6 = (data(18)*16777216+data(19)*65536+data(20)*256+data(21))/100;
  end
 
    sendbuff(1)=floor((data1+data4)/256);
    sendbuff(2)=floor((data1+data4));
    sendbuff(3)=floor((data2+data5)/256);
    sendbuff(4)=floor((data2+data5));
    sendbuff(5)=floor((data3+data6)/256);
    sendbuff(6)=floor((data4+data6));

    fwrite(obj2, sendbuff, 'uint8');
 %my_fwrite(obj2, sendbuff, 'uint8');
end
end

關(guān)于串口速度的討論
在進(jìn)行PID參數(shù)整定時,由于整個PID的調(diào)整周期較短,因此希望整個串口讀取、處理、發(fā)送的時間最好在2ms以內(nèi)完成。這個速度對于計算機來說其實是不容易實現(xiàn)的,因此如何提高整個回調(diào)函數(shù)的運算速度成為編寫代碼的關(guān)鍵。這里我發(fā)現(xiàn)了一些提高運行速度的方法:
對于串口讀寫速度,主要是串口發(fā)送函數(shù) fwrite(obj2, sendbuff, 'uint8')消耗了太多的時間,運行時這里耗費的時間有20ms+,這對于整個項目時不可接受的,筆者在思考和嘗試的時候發(fā)現(xiàn)了一個方法可以讓這個時間縮小到0.7ms左右。那就是用一個新的my_fwrite.m文件代替原來的內(nèi)部函數(shù)fwrite.m。并將上述的代碼改為my_fwrite(obj2, sendbuff, 'uint8')。
新的文件如下:

function my_fwrite(obj, varargin)
%FWRITE Write binary data to instrument.
%
%   FWRITE(OBJ, A) writes the data, A, to the instrument connected to
%   interface object, OBJ.
%
%   The interface object must be connected to the instrument with the 
%   FOPEN function before any data can be written to the instrument
%   otherwise an error will be returned. A connected interface object 
%   has a Status property value of open.
%
%   FWRITE(OBJ,A,'PRECISION') writes binary data translating MATLAB
%   values to the specified precision, PRECISION. The supported
%   PRECISION strings are defined below. By default the 'uchar' 
%   PRECISION is used. 
%   
%      MATLAB           Description
%      'uchar'          unsigned character,  8 bits.
%      'schar'          signed character,    8 bits.
%      'int8'           integer,             8 bits.
%      'int16'          integer,             16 bits.
%      'int32'          integer,             32 bits.
%      'uint8'          unsigned integer,    8 bits.
%      'uint16'         unsigned integer,    16 bits.
%      'uint32'         unsigned integer,    32 bits.
%      'single'         floating point,      32 bits.
%      'float32'        floating point,      32 bits.
%      'double'         floating point,      64 bits.
%      'float64'        floating point,      64 bits.
%      'char'           character,           8 bits (signed or unsigned).
%      'short'          integer,             16 bits.
%      'int'            integer,             32 bits.
%      'long'           integer,             32 or 64 bits.
%      'ushort'         unsigned integer,    16 bits.
%      'uint'           unsigned integer,    32 bits.
%      'ulong'          unsigned integer,    32 bits or 64 bits.
%      'float'          floating point,      32 bits.
%
%   FWRITE(OBJ, A, 'MODE')
%   FWRITE(OBJ, A, 'PRECISION', 'MODE') writes data asynchronously
%   to the instrument when MODE is 'async' and writes data synchronously
%   to the instrument when MODE is 'sync'. By default, the data is 
%   written with the 'sync' MODE, meaning control is returned to  
%   the MATLAB command line after the specified data has been written  
%   to the instrument or a timeout occurs. When the 'async' MODE is 
%   used, control is returned to the MATLAB command line immediately 
%   after executing the FWRITE function. 
%
%   The byte order of the instrument can be specified with OBJ's 
%   ByteOrder property. 
%
%   OBJ's ValuesSent property will be updated by the number of values
%   written to the instrument.
%
%   If OBJ's RecordStatus property is configured to on with the RECORD
%   function, the data written to the instrument will be recorded in
%   the file specified by OBJ's RecordName property value.
%
%   Example:
%       s = visa('ni', 'ASRL2::INSTR');
%       fopen(s);
%       fwrite(s, [0 5 5 0 5 5 0]);
%       fclose(s);
%
%   See also ICINTERFACE/FOPEN, ICINTERFACE/FPRINTF, ICINTERFACE/RECORD,
%   ICINTERFACE/PROPINFO, INSTRHELP.
%

%   MP 7-13-99
%   Copyright 1999-2012 The MathWorks, Inc. 

% Error checking.
if ~isa(obj, 'icinterface')
    error(message('instrument:fwrite:invalidOBJInterface'));
end

if length(obj)>1
    error(message('instrument:fwrite:invalidOBJDim'));
end


% Parse the input.
switch nargin
case 1
   error(message('instrument:fwrite:invalidSyntaxA'));
case 2
   cmd = varargin{1};
   precision = 'uchar';
   mode = 0;    
case 3
   % Original assumption: fwrite(obj, cmd, precision); 
   [cmd, precision] = deal(varargin{1:2});
   mode = 0;

   if ~(isa(precision, 'char') || isa(precision, 'double'))
       error(message('instrument:fwrite:invalidArg'));
   end
   
   if strcmpi(precision, 'sync') 
       % Actual: fwrite(obj, cmd, mode);
       mode = 0;
       precision = 'uchar';
   elseif strcmpi(precision, 'async') 
       % Actual: fwrite(obj, cmd, mode);
       mode = 1;
       precision = 'uchar';
   end
case 4
   % Ex. fprintf(obj, format, cmd, mode); 
   [cmd, precision, mode] = deal(varargin{1:3}); 
   
   if ~ischar(mode)
       error(message('instrument:fwrite:invalidMODE'));
   end
   
   if strcmpi(mode, 'sync')
       mode = 0;
   elseif strcmpi(mode, 'async')
       mode = 1;
   else
       error(message('instrument:fwrite:invalidMODE'));
   end
otherwise
   error(message('instrument:fwrite:invalidSyntaxArgv'));
end   

% % % % Error checking.
% % % if ~isa(precision, 'char')
% % %   error(message('instrument:fwrite:invalidPRECISIONstring'));
% % % end
% % % if ~(isnumeric(cmd) || ischar(cmd))
% % %   error(message('instrument:fwrite:invalidA'));
% % % end

% Convert the data to the requested precision.
switch (precision)
case {'uchar', 'char'}
    cmd = uint8(cmd);
    type = 5;
    signed = 0;
case {'schar'}
    cmd = int8(cmd);
    type = 5;
    signed = 1;
case {'int8'}
    cmd = int8(cmd);
    type = 0;
    signed = 1;
case {'int16', 'short'}
    cmd = int16(cmd);
    type = 1;
    signed = 1;
case {'int32', 'int', 'long'}
    cmd = int32(cmd);
    type = 2;
    signed = 1;
case {'uint8'}
    cmd = uint8(cmd);
    type = 0;
    signed = 0;
case {'uint16', 'ushort'}
    cmd = uint16(cmd);
    type = 1;
    signed = 0;
case {'uint32', 'uint', 'ulong'}
    cmd = uint32(cmd);
    type = 2;
    signed = 0;
    import java.lang.Long
    for iLoop = 1:length(cmd)
        tmp(iLoop) = Long(cmd(iLoop)); %#ok<AGROW>
    end
    cmd=tmp;
case {'single', 'float32', 'float'}
    cmd = single(cmd);
    type = 3;
    signed = 1;
case {'double' ,'float64'}
    cmd = double(cmd);
    type = 4;
    signed = 1;
otherwise
    error(message('instrument:fwrite:invalidPRECISION'));
end

% i2c does not support async mode
if mode == 1
    error(message('instrument:fwrite:i2cAyncNotSupported'));
end

% Call the write java method.
try
    fwrite(igetfield(obj, 'jobject'), cmd, length(cmd), type, mode, signed);
catch aException
    newExc = MException('instrument:fwrite:opfailed', aException.message);
    throw(newExc);
end

除此之外,在進(jìn)行數(shù)據(jù)的處理的時候,應(yīng)盡量對數(shù)據(jù)進(jìn)行乘除取整取余等運算而不是調(diào)用一些強制轉(zhuǎn)換的函數(shù)或者移位函數(shù)。筆者考慮可能是在調(diào)用matlab系統(tǒng)內(nèi)部函數(shù)花費了較多的時間。

同時,這個運算速度和matlab版本以及電腦的CPU性能有關(guān),同樣的程序i5跑起來是2ms,i7跑起來0.8ms。

通過以上方法將原來數(shù)十微妙的運行速度降到了運行一次在一微妙以內(nèi)。所以還是有些效果的。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容