在進(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)行表示。

在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é),因此無所謂大小端。
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)。所以還是有些效果的。