通过串口发送 8 位整数到 arduino

Send 8 bit integers over serial to arduino

我创建了一个音乐可视化工具,我想使用 python 的 [=14] 通过串行方式将 3 8 位整数 (0-255) 发送到 Arduino =] 库 我的计算机上有一个名为 rgb.txt 的文本文件,其中包含数据:8,255,255。我正在使用以下代码通过串行方式发送数据:

import serial, time
arduino = serial.Serial('COM3', 9600, timeout=.1)
time.sleep(2) #give the connection a second to settle

while True:
    with open("rgb.txt") as f:
        rgb = f.read().strip("\n")
    arduino.write(rgb.encode("ASCII"))

    data = arduino.readline()
    if data:
        try:
            print(data.decode('ASCII').strip("\r").strip("\n")) # (better to do .read() in the long run for this reason
        except UnicodeDecodeError:
            pass
    time.sleep(0.1)

我收到了这个代码:

#include <stdio.h>
int r = A0;
int g = A1;
int b = A2;
void setup() {
  Serial.begin(9600);
  analogWrite(r, 255); delay(333); analogWrite(r, 0);
  analogWrite(g, 255); delay(333); analogWrite(g, 0);
  analogWrite(b, 255); delay(334); analogWrite(b, 0);
}
void loop() {
  if(Serial.available() > 0) {
    char data = Serial.read();
    char str[2];
    str[0] = data;
    str[1] = '[=11=]';
      Serial.println(str);
    }
  }

我得到的输出是

8
,
2
5
5
,
2
5
5

我如何解析它以便收到:

8
255
255

并且最好在 3 个不同的变量中 (r g b) 谢谢!

如果你总是有一个 ',' 字符,你可以将它转换为 int。

uint8_t rgb[3] = {0,0,0};
uint8_t rgbIndex = 0;

void loop() {
  if(Serial.available() > 0) {
    char data = Serial.read();
    if(data < '0' || data > '9')
      rgbIndex++;
    else
      rgb[rgbIndex] = rgb[rgbIndex] * 10 + (data - '0');
  }
}

您现在要做的是读取 char,将其转换为 CString str,然后在继续下一个 char 之前 println() 它.

您可能可以按照您想要的方式将字节粘在一起,但将接收到的字节读入缓冲区并拆分结果更容易:

发送来自 Python 的 RGB 值,用逗号分隔,最后用 '\n' 分隔,然后在 Arduino 上做这样的事情(未经测试,但你明白了):

void loop() {
  static char buffer[12];
  static uint8_t rgb[3];

  if (Serial.available() > 0) {
    Serial.readBytesUntil('\n', buffer, 12);
    int i = 0;
    char *p = strtok(buffer, ",");
    while (p) {
      rgb[i++] = (uint8_t)atoi(p);
      p = strtok(NULL, ",");
    }
    // You now have uint8s in rgb[]
    Serial.println(rgb[0]);
    Serial.println(rgb[1]);
    Serial.println(rgb[2]); 
  }
}

注意:此代码中没有检查和错误处理。

毫无疑问,还有更漂亮的方法,但我首先想到了这个,我认为它会奏效。也可以使用 String 对象来完成,但我尽量不这样做。

为了使 中的代码正常工作,需要添加一些东西(但我还没有测试这些添加是否足够):

void loop() {
  if (Serial.available() > 0) {
    char data = Serial.read();
    if (data < '0' || data > '9')
      rgbIndex++;
    else
      rgb[rgbIndex] = rgb[rgbIndex] * 10 + (data - 48);
    if (rgbIndex == 3) {
      // You now have uint_8s in rgb[]
      Serial.println(rgb[0]);
      Serial.println(rgb[1]);
      Serial.println(rgb[2]);
      rgbIndex = 0;
      for (int i=0; i<3; i++)
        rgb[i] = 0;
    }
  }
}

请注意,在 Python 端将您从文件中读取的内容转换为字符或整数并简单地发送三个字节将大大简化 Arduino 端的操作。

Arduino 代码:

#include <stdio.h>
int r = A0;
int g = A1;
int b = A2;
void setup() {
  Serial.begin(115200);
  analogWrite(r, 255); delay(333); analogWrite(r, 0);
  analogWrite(g, 255); delay(333); analogWrite(g, 0);
  analogWrite(b, 255); delay(334); analogWrite(b, 0);
}
void loop() {
  static char buffer[12];
  static uint8_t rgb[3];

  if (Serial.available() > 0) {
    Serial.readBytesUntil('\n', buffer, 12);
    int i = 0;
    char *p = strtok(buffer, ",");
    while (p) {
      rgb[i++] = (uint8_t)atoi(p);
      p = strtok(NULL, ",");
    }
    // You now have uint8s in rgb[]
    analogWrite(A0, rgb[0]);
    analogWrite(A1, rgb[1]);
    analogWrite(A2, rgb[2]); 
    Serial.println(rgb[0]);
    Serial.println(rgb[1]);
    Serial.println(rgb[2]);
  }
}

Python代码:

import serial, time
def run():
    arduino = serial.Serial('COM3', 115200, timeout=.1)
    time.sleep(2) #give the connection a second to settle

    while True:
        with open("rgb.txt") as f:
            rgb = f.read()
            rgb += "\n"
        arduino.write(rgb.encode("ASCII"))

        data = arduino.readline()
        if data:
            try:
                print(data.decode('ASCII').strip("\r").strip("\n")) # (better to do .read() in the long run for this reason
            except UnicodeDecodeError:
                pass
        time.sleep(0.1)
while True:
    try:
        run()
    except serial.serialutil.SerialTimeoutException:
        print("Is your com port correct?")

您的原始代码几乎是正确的。

您只需稍微更改格式即可同步换行符。

Python代码

import serial, time
arduino = serial.Serial('COM3', 9600, timeout=.1)
time.sleep(2) #give the connection a second to settle

while True:
    with open("rgb.txt") as f:
        rgb = f.read()
        rgb = rgb + '\n'  # Add a newline character after the RGB values to aid sychronisation in the Arduino code.
    arduino.write(rgb.encode("ASCII"))

    data = arduino.readline()
    if data:
        try:
             print(data.decode('ASCII').strip("\r").strip("\n"))
        except UnicodeDecodeError:
            pass
    time.sleep(0.1)

Arduino 代码

我使用 sscanf() 从缓冲区中读取 3 个整数,因为它 returns 是它成功扫描到变量中的项目数的计数。我还添加了一些 #defines 以使其更具可读性和可维护性。

#include <stdio.h>

#define PORT_R A0
#define PORT_G A1
#define PORT_B A2

void setup()
{
  Serial.begin(9600);
  analogWrite(PORT_R, 255); delay(333); analogWrite(PORT_R, 0);
  analogWrite(PORT_G, 255); delay(333); analogWrite(PORT_G, 0);
  analogWrite(PORT_B, 255); delay(334); analogWrite(PORT_B, 0);
}

void loop()
{
  uint8_t r, g, b;
  if (ReadRGB(r, g, b))
  {
    analogWrite(PORT_R, r);
    analogWrite(PORT_G, g);
    analogWrite(PORT_B, b);
    Serial.println(r);
    Serial.println(g);
    Serial.println(b);
  }
}

bool ReadRGB(uint8_t &r, uint8_t &g, uint8_t &b)
{
  if (Serial.available() > 0)
  {
    const int LENGTH = 13;  // nnn,nnn,nnn\r[=11=]
    char buffer[LENGTH];
    size_t count = Serial.readBytesUntil('\n', buffer, LENGTH - 1);  // Allow room for NULL terminator.
    buffer[count] = 0;  // Place the NULL terminator after the last character that was read.
    int i = sscanf(buffer, "%d,%d,%d", &r, &g, &b);
    return i == 3;  // Notify whether we successfully read 3 integers.
  }
  return false;
}

关于Serial.parseInt(),超时不通知。相反,它只是 returns 0,这是一个有效值,因此调用者不知道这是否是由于超时。

同样的问题存在于Serial.readBytesUntil(),因为它没有通知调用者返回的字节数是遇到搜索字符还是超时的结果。如果由于通信错误导致的超时而不是预期的 255,255,255 而收到 255,255,25 怎么办?来电者不会知道。

与 C#.NET 中 int.TryParse() 的稳健方法进行比较,其中 returns 一个 bool 来指示 success/failure 并通过引用传递已解析的 int .

更强大的 Arduino 代码

为了克服 Serial.parseInt()Serial.readBytesUntil() 在串行输入缓冲区为空时超时而不返回错误代码的问题,可以使用 non-blocking algorithm algorithm,像这样每个 loop() 读取一个字符,直到到达换行符,然后扫描缓冲区以获取 3 个整数:

#define PORT_R A0
#define PORT_G A1
#define PORT_B A2

const int LENGTH = 12;  // nnn,nnn,nnn[=12=]
char buffer[LENGTH];

void setup()
{
  Serial.begin(9600);
  Serial.println("setup()");
  analogWrite(PORT_R, 255); delay(333); analogWrite(PORT_R, 0);
  analogWrite(PORT_G, 255); delay(333); analogWrite(PORT_G, 0);
  analogWrite(PORT_B, 255); delay(334); analogWrite(PORT_B, 0);
}

void loop()
{
  Serial.println("loop()");
  uint8_t r, g, b;
  if (ReadRGB(r, g, b))
  {
    analogWrite(PORT_R, r);
    analogWrite(PORT_G, g);
    analogWrite(PORT_B, b);
    Serial.println(r);
    Serial.println(g);
    Serial.println(b);
  }
}

bool ReadRGB(uint8_t &r, uint8_t &g, uint8_t &b)
{
  if (ReadLine(buffer, LENGTH))
  {
    Serial.println("ReadRGB() read a line.");
    int i = sscanf(buffer, "%d,%d,%d", &r, &g, &b);
    return i == 3;  // Notify whether 3 integers were successfully read.
  }
  return false;
}

int ReadLine(char *buffer, const int length)
{
  static int index = 0;
  int last_index = 0;
  int ch = Serial.read();
  if (ch > 0)
  {
    switch(ch)
    {
      case '\r':
        break;
      case '\n':
        last_index = index;
        index = 0;
        return last_index;
      default:
        if (index < length - 1)
        {
          buffer[index++] = ch;
          buffer[index] = 0;
        }
    }
  }
  return 0;
}

如果您想通过串口发送 3 个字节,这通常是非常低的带宽,您真的应该只发送 3 个字节而不是 ASCII 表示形式,以及逗号和换行符。如果您发送:

255,255,255

最后有一个换行符,您实际上发送的是 12 个字节,而不是您需要的 3 个字节。

假设您有 3 个变量,abc 您要发送:

a, b, c = 1, 8, 255

您可以像这样将它们打包为 3 个无符号字节 (B):

import struct

packet = struct.pack('3B',a,b,c)

然后如果你检查内容,你会看到你的 3 个字节:

b'\x01\x08\xff'