Synchronous Data Transfer between Arduino and Matlab Serially

huangapple go评论60阅读模式
英文:

Synchronous Data Transfer between Arduino and Matlab Serially

问题

我正在进行一个项目,在这个项目中,我使用分裂型核心电流互感器(Split Core CT)在Arduino上实时读取电流信号。我能够使用下面的代码在Arduino Serial Plotter中读取准确的交流电流并复制它。

void setup() {
  Serial.begin(115200);
}
void loop() {
    Serial.println( (double)(analogRead(A5) - analogRead(A0))*0.009765625  ); 
}

但我需要对其进行进一步的计算,如FFT和THD,因此我通过串行通信将数据发送到MATLAB。下面是我的MATLAB脚本,它读取1000个数据样本,将其存储在数组中,执行计算,最后绘制图形。

clc; close all;

if ~isempty(instrfind)
    fclose(instrfind);
    delete(instrfind);
end
s1=serial('COM5','Baudrate',115200);
fopen(s1);

Fs=1000;
LoS = 100;
T = 1/Fs;
t = (0:LoS-1)*T;
  
sig = zeros(1,LoS-1);

str='';
sen=0;
for j = 1:LoS
    str=fscanf(s1);
    sen=str2double(str);    
    sig(j)=sen;
end

subplot(2,1,1);
axis([0 LoS -4 4]);
plot(t,sig);
xlabel('Counts');
ylabel('Magnitude');
title('Signal');

Y=fft(sig);

P2 = abs(Y/LoS);
P1 = P2(1:LoS/2+1);
P1(2:end-1) = 2*P1(2:end-1);
f = Fs*(0:(LoS/2))/LoS;

subplot(2,1,2);
axis([0 100 0 10]);
plot(f,P1);
title('FFT(Signal)');
xlabel('f (Hz)');
ylabel('|Power(f)|');
fclose(s1);
delete(s1);
clear s1;

问题是实际信号的频率是60Hz,但我的代码在31Hz处输出峰值。我在MATLAB模拟的正弦波上检查了相同的代码,它给出了准确的结果。但在真实数据上,它计算错误。我还在LABView上实施了相同的逻辑,结果仍然是31Hz。有人能找出我的错误吗?我真的卡在这里了。

感谢您的时间。

英文:

I am working on a project where i read real time current signal on an Arduino using a Split Core CT. I am able to read exact AC Current and replicate it in Arduino Serial Plotter using the code below.

void setup() {
  Serial.begin(115200);
}
void loop() {
    Serial.println( (double)(analogRead(A5) - analogRead(A0))*0.009765625  ); 
}

But I have to do further calculations on it like FFT and THD, so I am sending that data to MATLAB over serial communication. Below is my Matlab Script which reads 1000 data samples, stores it in an array and performs calculations and finally plots it.

clc; close all;

if ~isempty(instrfind)
    fclose(instrfind);
    delete(instrfind);
end
s1=serial('COM5','Baudrate',115200);
fopen(s1);

Fs=1000;
LoS = 100;
T = 1/Fs;
t = (0:LoS-1)*T;
  
sig = zeros(1,LoS-1);

str='';
sen=0;
for j = 1:LoS
    str=fscanf(s1);
    sen=str2double(str);    
    sig(j)=sen;
end

subplot(2,1,1);
axis([0 LoS -4 4]);
plot(t,sig);
xlabel('Counts');
ylabel('Magnitude');
title('Signal');

Y=fft(sig);

P2 = abs(Y/LoS);
P1 = P2(1:LoS/2+1);
P1(2:end-1) = 2*P1(2:end-1);
f = Fs*(0:(LoS/2))/LoS;

subplot(2,1,2);
axis([0 100 0 10]);
plot(f,P1);
title('FFT(Signal)');
xlabel('f (Hz)');
ylabel('|Power(f)|');
fclose(s1);
delete(s1);
clear s1;

The issue is the frequency of actual signal is 60Hz, but my code outputs a peak at 31Hz. I checked the same code on matlab simulated sinusoids, it gives exact results. But on real data its miscalculating. I implemented the same logic on LABView as well the result remains 31Hz. Can anyone pinpoint my mistake? I am really stuck at this.

Thanks for your time.

答案1

得分: 2

你应该将Arduino的采样率固定为每秒1000个样本。

正如Daniel所评论的,Arduino正在尽可能快地工作,而不是以1KHz的速率进行采样(而是每秒采样1000个样本)。

您可以通过确保每次迭代花费1000微秒来修复Arduino的循环,以便以1KHz的速率进行采样。

下面的循环在每次迭代的开始和结束时都会读取时间。
在每次迭代结束时,有一个延迟,使持续时间达到1000微秒。

void loop() {
  unsigned long start_time_us, delta_time_us;

  //返回自Arduino板开始运行当前程序以来的微秒数
  //https://www.arduino.cc/reference/en/language/functions/time/micros/
  start_time_us = micros();

  Serial.println( (double)(analogRead(A5) - analogRead(A0))*0.009765625  ); 

  //经过的微秒数。
  delta_time_us = micros() - start_time_us;

  if (delta_time_us < 1000)
  {
    //延迟剩余时间 - 完成到1000微秒(保持每秒1000个样本的采样率)。
    //https://www.arduino.cc/reference/en/language/functions/time/delaymicroseconds/
    delayMicroseconds((unsigned long)1000 - delta_time_us);
  }
}

请注意:我无法验证解决方案,因为我没有Arduino板(或模拟器)。


备注:将样本作为文本字符串发送效率低下,可能会饱和串行端口。

我建议您执行以下操作:

  • 发送二进制数据(使用Serial.write而不是Serial.println)。
  • 在从Arduino发送样本之前,将样本转换为short格式(两个字节):发送值(short)(analogRead(A5) - analogRead(A0))
  • 在MATLAB端读取1000个二进制样本:sig = fread(s1, 1000, 'int16');
  • 在MATLAB中执行转换并缩放样本:sig = double(sig) * 0.009765625

Arduino代码:

//Serial.println( (double)(analogRead(A5) - analogRead(A0))*0.009765625  );
short analog_read = (short)(analogRead(A5) - analogRead(A0));
Serial.write((uint8_t*)&analog_read, 2); //以int16二进制格式发送两个字节

MATLAB代码:

% for j = 1:LoS
%     str=fscanf(s1);
%     sen=str2double(str);    
%     sig(j)=sen;
% end
sig = fread(s1, 1000, 'int16'); % 读取1000个模拟样本(每个样本都是int16)。
sig = double(sig) * 0.009765625; % 将1000个样本转换为双精度格式的物理值

上述代码对Arduino和MATLAB都更有效率。

备注:请记住,我没有测试过代码 - 这仅用于演示概念。

英文:

You should fix the Arduino sample rate to 1000 samples per second.

As Daniel commented, the Arduino is working as fast as possible, instead of sampling at 1KHz (instead of sampling 1000 samples per second).

You can fix your Arduino loop to sample at 1KHz by making sure each iteration takes 1000 micro-seconds.

The loop below reads the time at the beginning and at the end of each iteration.
At the end of each iteration, there is a delay that completes the duration to 1000us.

<!-- language: lang-c -->

void loop() {
  unsigned long start_time_us, delta_time_us;

  //Returns the number of microseconds since the Arduino board began running the current program
  //https://www.arduino.cc/reference/en/language/functions/time/micros/
  start_time_us = micros();

  Serial.println( (double)(analogRead(A5) - analogRead(A0))*0.009765625  ); 

  //Passed time in microseconds.
  delta_time_us = micros() - start_time_us;

  if (delta_time_us &lt; 1000)
  {
    //Delay the remaining time - complete to 1000us (keep sample rate at 1000 samples per second).
    //https://www.arduino.cc/reference/en/language/functions/time/delaymicroseconds/
    delayMicroseconds((unsigned long)1000 - delta_time_us);
  }
}

Please note: I could not verify the solution because I don't have an Arduino board (or simulator).


Remark: sending samples as text strings is inefficient, and may saturate the serial port.

I suggest you do the following:

  • Send binary data (using Serial.write instead of Serial.println).
  • Instead of converting the sample to double before sending it from Arduino, send the sample in short format (two bytes): Send the value (short)(analogRead(A5) - analogRead(A0)).
  • In the MATLAB side, read 1000 binary samples: sig = fread(s1, 1000, &#39;int16&#39;);
  • Perform the conversion to double and scale samples in MATLAB: sig = double(sig) * 0.009765625.

Arduino code:

<!-- language: lang-c -->

//Serial.println( (double)(analogRead(A5) - analogRead(A0))*0.009765625  ); 
short analog_read = (short)(analogRead(A5) - analogRead(A0));
Serial.write((uint8_t*)&amp;analog_read, 2); //Send two bytes in int16 binary format

MATLAB code:

% for j = 1:LoS
%     str=fscanf(s1);
%     sen=str2double(str);    
%     sig(j)=sen;
% end
sig = fread(s1, 1000, &#39;int16&#39;); % Read 1000 analog samples (each sample is int16).
sig = double(sig) * 0.009765625; % Convert 1000 samples to physical values in double format

The above code is more efficient for both Arduino and MATLAB.

Remark: Keep in mind that I didn't test the code - it's just for demonstrating the concept.

huangapple
  • 本文由 发表于 2020年1月3日 16:07:31
  • 转载请务必保留本文链接:https://go.coder-hub.com/59575107.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定