pySerial在Python解释器中运行良好,但在独立环境下无法正常工作。

13

早上好!最近我购买了一块Arduino板,想用它来制作类似于房间“光控”的东西。这是我编写的固件代码:

int control = 0;
int pin = 0;

void setup()
{
  Serial.begin(9600);
  for(pin = 0; pin <= 13; pin++) pinMode(pin, OUTPUT);
}

void loop()
{
  control = Serial.read();
  if (control > 0 && control <= 13) digitalWrite(control, HIGH);
  if (control < 256 && control >= (256-13)) digitalWrite((256-control), LOW);
}

之后,我使用Python解释器中的pySerial控制引脚,一切都运行良好。以下是解释器输出的一部分:

Python 2.6.5 (r265:79063, Apr 16 2010, 13:57:41) 
[GCC 4.4.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import serial
>>> ser = serial.Serial('/dev/ttyUSB0', 9600)
>>> ser.write(chr(12))
>>> # The light turned on here
... 
>>> ser.write(chr(256-12))
>>> # The light turned off here
...

于是我决定编写一个简单的Python脚本来完成同样的任务:

#!/usr/bin/env python

import serial
import time

ser = serial.Serial('/dev/ttyUSB0', 9600)

ser.write(chr(12))
time.sleep(1)
ser.write(chr(256-12))

但它根本不起作用! Arduino显示在我启动脚本期间接收到了一些信息,但什么也没有发生。以下是脚本的一部分strace输出:

open("/dev/ttyUSB0", O_RDWR|O_NOCTTY|O_NONBLOCK) = 3
ioctl(3, SNDCTL_TMR_TIMEBASE or TCGETS, {B9600 -opost -isig -icanon -echo ...}) = 0
ioctl(3, SNDCTL_TMR_TIMEBASE or TCGETS, {B9600 -opost -isig -icanon -echo ...}) = 0
ioctl(3, SNDCTL_TMR_TIMEBASE or TCGETS, {B9600 -opost -isig -icanon -echo ...}) = 0
ioctl(3, SNDCTL_TMR_START or TCSETS, {B9600 -opost -isig -icanon -echo ...}) = 0
ioctl(3, SNDCTL_TMR_TIMEBASE or TCGETS, {B9600 -opost -isig -icanon -echo ...}) = 0
open("/dev/ttyUSB0", O_RDWR|O_NOCTTY|O_NONBLOCK) = 4
ioctl(4, SNDCTL_TMR_TIMEBASE or TCGETS, {B9600 -opost -isig -icanon -echo ...}) = 0
ioctl(4, SNDCTL_TMR_TIMEBASE or TCGETS, {B9600 -opost -isig -icanon -echo ...}) = 0
ioctl(4, SNDCTL_TMR_TIMEBASE or TCGETS, {B9600 -opost -isig -icanon -echo ...}) = 0
ioctl(4, SNDCTL_TMR_START or TCSETS, {B9600 -opost -isig -icanon -echo ...}) = 0
ioctl(4, SNDCTL_TMR_TIMEBASE or TCGETS, {B9600 -opost -isig -icanon -echo ...}) = 0
write(4, "\f", 1)                       = 1
close(4)                                = 0
rt_sigaction(SIGINT, {SIG_DFL, [], SA_RESTORER, 0x7f45cf4c88f0}, {0x4d9820, [], SA_RESTORER, 0x7f45cf4c88f0}, 8) = 0
exit_group(0)                           = ?

看起来一切都应该是好的,所以我不知道问题在哪里。我会感激任何帮助,提前致谢!

顺便说一下,当我在PDB下运行程序时,一切都正常。一个Heisenbug。

更新:我让控制器把它接收到的数据发回给我,结果在我运行脚本时它似乎没有接收到任何数据,但是在我从解释器发送数据时,它可以接收到所有数据。固件代码现在像这样:

int control = 0;
int pin = 0;

void setup()
{
  Serial.begin(9600);
  for(pin = 0; pin <= 13; pin++) pinMode(pin, OUTPUT);
}

void loop()
{
  if (Serial.available() > 0)
  {
    control = Serial.read();
    if (control <= 13) digitalWrite(control, HIGH);
    if (control < 256 && control >= (256-13)) digitalWrite((256-control), LOW);
    Serial.println(control);
  }
}

从控制台中执行 ser.write(chr(12)); time.sleep(1); ser.write(chr(256-12)) 是否正常工作? - seriyPS
是的。灯可以打开、关闭,jpnevulator 显示控制器返回的数据。但当我从脚本中执行相同操作时,jpnevulator 没有显示任何返回数据,也没有发生任何事情。 - Ivan Galinskiy
我升级了pySerial,但没有任何结果。 - Ivan Galinskiy
你可以尝试以下方法来解决问题:0. 从控制台和脚本中输出print ser是否相同?1. 将代码放入def main():..中,并使用if __name__=="__main__": main()进行调用保护。2. 在第一个ser.write()之前添加time.sleep(2)。3. 每次写入后添加flush()。4. 添加ser.close() - jfs
1
哇,实际上在第一次写入之前加上 time.sleep(2) 就解决了问题!现在它可以正常工作了!我没想到问题竟然会这么简单。不过为了确保它能够正常工作,应该将时间设置为 2 秒或更长。 - Ivan Galinskiy
在第一次ser.write()之前,您可以尝试使用while not ser.isOpen(): time.sleep(0.04) - jfs
4个回答

9
我认为这可能是串口打开和数据发送之间的竞态条件。我建议在打开和写入调用之间插入一个延迟。
或者,您可以直接打开并写入设备,而不使用此库“serial”,也许它正在执行一些有趣的操作(请参见其他帖子中提到的双重打开)。

是的,time.sleep(2) 起作用了!事实上,这并不取决于编程语言,我甚至在使用 POSIX 调用的 C 和使用 libSerial 的 C++ 中遇到了相同的问题,直到我添加了 sleep(2)。 - Ivan Galinskiy
1
我认为这也是一种竞态条件。具体来说,打开串口会重置Arduino!请参见https://dev59.com/fUnSa4cB1Zd3GeqPSfW8#4941880了解详情。 - Brian Bloniarz

1

我的猜测是与环境有关。

import os
print os.environ['PS1']

来自一个不会被设置的脚本。(也可能有其他原因。)

tty 根据终端是否为交互式而有不同的缓冲方式。这应该是你两种方法之间唯一的区别。许多应用程序根据 PS1(你的终端提示符)是否设置来决定这一点。如果你在环境中手动设置它,它可能会像交互式一样运行。

此外,我建议在你的脚本中手动调用 pyserial flush 命令。(这是首选的方法,而不是伪装成交互式终端。)


我比较了脚本和解释器中的os.environ内容,它们看起来很相似。 - Ivan Galinskiy
是的,当我在pdb下运行脚本时,一切都正常。 - Ivan Galinskiy
我尝试使用flush,但似乎没有起作用。稍后我会尝试修改PS1。 - Ivan Galinskiy

0

你的strace输出显示它两次打开串口进行读写操作。第二次只写入chr(12),然后关闭文件。我没有足够的信息来为您解决问题,但也许这可以帮助您?或者您已经解决了这个问题吗?


是的,看起来它打开了两次并写入第二个描述符。也许我可以尝试使用另一个版本的Python做同样的事情。 - Ivan Galinskiy
我用的是没有串口的笔记本电脑,否则我很乐意帮助你调试! - jcomeau_ictx
由于看起来脚本已经成功将chr(12)写入端口,当您运行它时,至少灯是否点亮? - martineau
他说这样做不行,这表明打开同一个串口的两个句柄可能是问题的一部分。 - jcomeau_ictx
一些串行设备驱动程序和UART处理控制字符与普通字符的方式不同,因此可能会拦截ASCII FF或换页符字符chr(12)。另一件事是串行通信可以是7位或8位,因此如果设置为7位,则很难通过通道发送8位字符chr(256-12)或0xF4。还可能存在其他通信设置,如每个字符的数据位数、奇偶校验和每个字符的停止位数,也可能会导致问题。但这并不能解释interp与script之间的差异。 - martineau
实际上,板子上的灯在所有情况下都会闪烁,但是当我运行脚本时,我的控制器没有重新发送字节给我(我为此设置了它)。来自解释器的命令有效(板子会将数据重新发送回来),但是来自脚本的命令无效,因此我认为这不是硬件问题。很奇怪,不是吗? - Ivan Galinskiy

0

你能否再次确认当你打开串口连接时,Arduino是否会重置?如果它确实重置了,那么你发送的第一个串口字节将被引导程序接收而不是你的代码。然后引导程序可能会认为你想要编程控制器并等待进一步的命令和/或数据。

引导程序的确切行为取决于你特定的Arduino。

为了测试这个问题,编写一个小程序来闪烁LED 13,并查看初始化Python脚本是否影响闪烁。如果是,那么就存在引导程序。

为了解决这个问题,有几种可能的解决方案:

1)确保初始化串口接口不会导致重置。 1a)在Python端执行此操作 1b)在Arduino端执行此操作   1b硬件解决方案)断开板上的有问题的线路   1b软件解决方案)摆脱引导程序

2)在引导程序正在工作时不要发送数据。

最简单的解决方案是(2),我更喜欢的解决方案是摆脱引导程序。但是,在这种情况下,您需要一个系统编程器(这也是一个好主意)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接