C语言 如何正确读取进入串口的整数?

92dk7w1h  于 2023-10-16  发布在  其他
关注(0)|答案(2)|浏览(101)

我在客户端有这段代码(从更大的代码块简化)

int key=0;
  while (1) {
    int n_read=read(fd, &key, 4);
    printf("%d\n",key);
    fflush(stdout);
    if(key==1){
        printf("in loop\n");
        }   
  }

并将此代码在服务器端ATmega2560单片机上实现

while(1){
  int key=1;
  printf("%d", key);
}

服务器中的printf使用此代码

void usart_putchar(char data) {
    // Wait for empty transmit buffer
    while ( !(UCSR0A & (_BV(UDRE0))) );
    // Start transmission
    UDR0 = data; 
}

// this function is called by printf as a stream handler
int usart_putchar_printf(char var, FILE *stream) {
    // translate \n to \r for br@y++ terminal
    if (var == '\n') usart_putchar('\r');
    usart_putchar(var);
    return 0;
}

我希望它打印“in loop”,但这从未发生过,当我在客户端打印循环中的键时,它的值为825307441,而不是1。
我想这可能是因为我从串行阅读它后,如何将字节一个接一个地放在整数变量中,或者我以不正确的方式使用读取函数,但我真的不知道
编辑:
在阅读了答案后,我像这样更改了服务器:

for(int j=0; j<4;j++){
        while ( !(UCSR0A & (_BV(UDRE0))) );
        UDR0=(key >> (8*j)) & 0xff;;
        }

这给我带来了一些麻烦,我现在有这样的结果:

1
in loop
1
in loop
1
in loop
1
in loop
1
in loop
1
in loop
1
in loop
1
in loop
16777216
16777216
16777216
16777216
16777216
65536
65536
65536
65536
65536
256
256
256
1
in loop
1
in loop
1
in loop
1
in loop
1
in loop
1

这是endianness问题吗?有办法解决吗?
如果我不能解决这个问题,我将使用“忙碌者的建议”。

pobjuy32

pobjuy321#

你混合了文本和二进制的值表示。
服务器发送以字符表示的密钥,因为您使用printf()。实际上,由于值为1,因此只发送一个字符:'1'
客户端需要4个字节的二进制表示,因为您使用read()
让我们看看你收到了什么。从串行端口读取的4字节值为十进制825307441。快速抛出一个桌面计算器,并转换为十六进制显示,它是0x 31313131。
这4个字节是'1'的4个ASCII字符。如果你可以计算传输的次数,你会看到服务器需要通过它的循环运行4次,客户端才能收到1个密钥。

如何解决?

你有两个选择,只使用二进制表示或只使用文本表示。
如果您想避免endianness问题,请选择文本表示。为此,将客户端代码更改为:

/* fd needs to be transformed into a FILE* f. */
  int key = 0;
  while (1) {
    int n_converted = fscanf(f, "%d", &key);
    if (n_converted == 1) {
      printf("%d\n", key);
      /* not necessary: fflush(stdout); */
      if (key == 1) {
        printf("in loop\n");
      }
    } else {
      /* error handling */
    }
  }

您需要用空格分隔数字,空格至少是空白,制表符或换行符之一。扩展服务器代码:

while (1) {
    int key = 1;
    printf("%d\n", key);
  }

要使用二进制表示,您需要定义一个协议,指定传输的字节顺序、每个值的字节数以及分隔值的方式。这并不简单,通常过于广泛,无法在这里深入展示,并取决于您(我们不知道)的要求。
如果您选择只传输一个字节,这会给您一个0到255或-128到+127的值范围,您可以避免其他两个决定。每个值使用一个字节,就没有字节顺序(在字节级别上),并且由于每个字节都是一个完整的值,因此不需要额外的措施来分隔值。
以下是示例实现:

/* client */
  char key = 0;
  while (1) {
    int n_read=read(fd, &key, 1);
    if (n_read == 1) {
      printf("%d\n", (int)key); /* or printf("%hhd\n", key); */
      if (key == 1) {
        printf("in loop\n");
      }
    } else {
      /* error handling */
    }
  }
/* server */
  while (1) {
    char key = 1;
    usart_putchar(key);
  }

您在客户机中看到的输出来自read()的超时行为。串行传输需要时间,主要取决于波特率。显然,read()返回的速度比传输单个字节所需的速度要快。
由于您没有检查read()的返回值,因此即使没有收到字节,也可以继续使用key中的任何内容。这就给出了你看到的多个输出。

7cjasjjr

7cjasjjr2#

是的,您错误地使用了read。我不应该这么说,因为你可以这么做,但这与正在发送的数据不匹配。你根本没有发送int。您正在发送组成int的asspark字符。你这么想吧...如果我发送int44444,那么我 * 应该 * 从read中得到的是'4'的5个字符串。这实际上会使缓冲区溢出,因为int的大小为4。通过printf打印int s是提供人类可读 * 文本 * 输出的方式。
有两种方法可以做到这一点。你可以像现在这样做。当你读的时候,你需要发送一个字节缓冲区:

char buf[25];
while (1) {
    int n_read=read(fd, buf, sizeof buf);
    int key = 0;

    // TODO: parse the integer out of buf with strtol or whatever 

    printf("%d\n",key);
    fflush(stdout);
    if(key==1) {
        printf("in loop\n");
    }   
}

然后另一种方法是直接发送int的字节,如下所示:

int key = 1;
for (int i = 0; i < sizeof key; ++i) {
    usart_putchar(key[i]);
}

然后,您需要在客户端解压缩这些字节。直接将它们阅读成整数可能会起作用,但前提是它们的大小相同,而我很确定它们的大小是 * 不 *。我建议使用大小为int的类型,如int16_tint32_t,这样你就知道有多少字节会到达。

相关问题