Matlab 实时读取串口并绘图
Vofa+
Vofa+ 是一个很好的跨平台上位机软件,但是它无法保存数据,而且作者也并没有要继续更新的意思,保存数据功能应该是遥遥无期了。因此本文使用 Matlab 实时读取串口数据,并使用 plot 函数绘制。
vofa+ 里发送 float 型数据的协议是 justfloat,其具体规则如下:
下位机发送数据
下位机发送程序各有不同,下面是一个例子,使用的是 DSP28027 单片机,定义了全局变量 gTime
做时间向量:
void myPutVariableData_vofa(void)
{
int num = 6; // number of float variables to be sent
float data[6] = {0}; // float variables to be sent
uint16_t txBuf[14] = {0};
float testData = 114514.0;
float testW = 10.0;
txBuf[2 * num] = 0x0000;
txBuf[2 * num + 1] = 0x7f80;
data[0] = testData;
data[1] = testData;
data[2] = testData;
data[3] = testData;
data[4] = testData;
data[5] = sin(2 * 3.1415 * testW * gTime);
memcpy(txBuf, (uint16_t *)data, num * sizeof(float));
mySCI_sendDataBlocking(txBuf, 2 * num + 2);
}
Matlab 代码
使用 matlab 读取串口主要分为接收数据和协议解析,用的方法比较粗暴,直接读取串口然后判断帧尾。
Step 1. 设置串口
Matlab 中使用 serialportlist
命令查找电脑连接的串口,找到对应设备名称后在设置相关参数
devicelist = serialportlist' % This command shows the port connected.
baudrate = 115200;
device = "/dev/cu.usbserial-A700eG4x";
myPort = serialport(device,baudrate);
myPort.DataBits = 8;
myPort.StopBits = 1;
myPort.Parity = "none";
Step 2. 设置数据缓冲区
设置绘图数据的个数和时间窗口大小,本文设置 6 个数据, 2000 个采样点的窗口,每一帧传输时间使用 d t = ( n ∗ 4 + 4 ) ∗ 8 BIT Baudrate dt = \frac{(n*4+4)*8 \text{BIT}}{\text{Baudrate}} dt=Baudrate(n∗4+4)∗8BIT 计算, n n n 为传输数据个数。然后将数据缓冲区都初始化为0:
varNum = 6;
buf = 2000;
dt = (varNum * 4 + 4) * 8 / baudrate;
time = -dt*(buf/2):dt:dt*(buf/2-1);
dataBuf.var0 = zeros(buf,1);
dataBuf.var1 = zeros(buf,1);
dataBuf.var2 = zeros(buf,1);
dataBuf.var3 = zeros(buf,1);
dataBuf.var4 = zeros(buf,1);
dataBuf.var5 = zeros(buf,1);
这里也可以使用动态命名变量来初始化:
varNum = 6;
buf = 2000;
dt = (varNum * 4 + 4) * 8 / baudrate;
time = -dt*(buf/2):dt:dt*(buf/2-1);
for i = 1:varNum
namelist{i} = ['var',num2str(i-1)];
dataBuf.(namelist{i}) = zeros(buf,1);
end
Step 3. 协议解析
协议解析主要是找到帧尾标志,使用 read()
读取4字节串口数据并判断是否是帧尾,找到帧尾就可以按照对应位置解析数据。转换 00 00 80 7F
为十进制数组 0 0 128 127
再进行判断,如果找到帧尾,就依次读取数据位(这里读取的是下一帧的数据,差一帧)。然后使用 circshift()
循环存储数据,让缓冲区的最后一位始终存储最新的数据。
因为数据是小端浮点数组(先发低位再发高位),所以使用 flip()
函数重新排序,然后将读取到的数据转换为 uint32
,再使用 typecast()
函数转换为单精度浮点型数据。
dec2uint32 = @(dec)uint32(dec * [2^24, 2^16, 2^8, 2^0]');
temp = read(myPort,4,'uint8');
if prod(temp == [0 0 128 127])
temp = read(myPort, varNum * 4, 'uint8');
dataBuf.var0 = circshift(dataBuf.var0);
dataBuf.var0(end) = typecast(dec2uint32(flip(temp(1:4))),'single');
dataBuf.var1 = circshift(dataBuf.var1);
dataBuf.var1(end) = typecast(dec2uint32(flip(temp(5:8))),'single');
dataBuf.var2 = circshift(dataBuf.var2);
dataBuf.var2(end) = typecast(dec2uint32(flip(temp(9:12))),'single');
dataBuf.var3 = circshift(dataBuf.var3);
dataBuf.var3(end) = typecast(dec2uint32(flip(temp(13:16))),'single');
dataBuf.var4 = circshift(dataBuf.var4);
dataBuf.var4(end) = typecast(dec2uint32(flip(temp(17:20))),'single');
dataBuf.var5 = circshift(dataBuf.var5);
dataBuf.var5(end) = typecast(dec2uint32(flip(temp(21:24))),'single');
end
同样这里可以使用动态变量命名简化代码:
temp = read(myPort,4,'uint8');
if prod(temp == [0 0 128 127])
temp = read(myPort, varNum * 4, 'uint8');
for i = 1:varNum
dataBuf.(namelist{i}) = circshift(dataBuf.(namelist{i}),-1);
dataBuf.(namelist{i})(end) = typecast(dec2uint32(flip(temp((i-1) * 4 + 1: i * 4))),'single');
end
end
Step 4. 绘制数据
先建立 figure
对象,使用 while
判断窗体存在条件,持续绘制数据
h = figure;
plotenabled = 0;
%%
while(ishandle(h))
temp = read(myPort,4,'uint8');
if prod(temp == [0 0 128 127])
temp = read(myPort, varNum * 4, 'uint8');
for i = 1:varNum
dataBuf.(namelist{i}) = circshift(dataBuf.(namelist{i}),-1);
dataBuf.(namelist{i})(end) = typecast(dec2uint32(flip(temp((i-1) * 4 + 1: i * 4))),'single');
end
plot(time, dataBuf.var5,'b-','LineWidth',1.5);
if plotenabled == 0
plotenabled = 1;
grid on
title('Matlab Serial Port Read','FontSize',14,'FontName','Times New Roman');
xlabel('Time','FontSize',14,'FontName','Times New Roman');
ylabel('Value','FontSize',14,'FontName','Times New Roman');
end
end
plotenabled = 0;
end
- 变量
plotenabled
用于首次绘图设置绘图参数,只有第一次绘图的时候会执行设置title
,xlabel
,ylabel
等参数。
完整的 Matlab 代码如下:
%---------------------------------------
% This is the series port read script
%
% hu 2023-07-07 Created
% hu 2024-04-10 Test vofa+ protocol
%---------------------------------------
clc,clear,close all
devicelist = serialportlist' % This command shows the port connected.
baudrate = 115200;
device = "/dev/cu.usbserial-A700eG4x";
myPort = serialport(device,baudrate);
myPort.DataBits = 8;
myPort.StopBits = 1;
myPort.Parity = "none";
varNum = 6;
buf = 2000;
dt = (varNum * 4 + 4) * 8 / baudrate;
time = -dt*(buf/2):dt:dt*(buf/2 - 1);
for i = 1:varNum
namelist{i} = ['var',num2str(i-1)];
dataBuf.(namelist{i}) = zeros(buf,1);
end
dec2uint32 = @(dec)uint32(dec * [2^24, 2^16, 2^8, 2^0]');
h = figure;
plotenabled = 0;
%%
while(ishandle(h))
temp = read(myPort,4,'uint8');
if prod(temp == [0 0 128 127])
temp = read(myPort, varNum * 4, 'uint8');
for i = 1:varNum
dataBuf.(namelist{i}) = circshift(dataBuf.(namelist{i}),-1);
dataBuf.(namelist{i})(end) = typecast(dec2uint32(flip(temp((i-1) * 4 + 1: i * 4))),'single');
end
plot(time, dataBuf.var5,'b-','LineWidth',1.5);
if plotenabled == 0
plotenabled = 1;
grid on
title('Matlab Serial Port Read','FontSize',14,'FontName','Times New Roman');
xlabel('Time','FontSize',14,'FontName','Times New Roman');
ylabel('Value','FontSize',14,'FontName','Times New Roman');
end
end
plotenabled = 0;
end
clc,clear,close all
绘制数据可以通过 fig 窗口保存到本地,或者在数据转换后保存到工作区,这里不再赘述。