pySerial works fine in Python interpreter, but not

2019-01-23 16:42发布

问题:

Good morning! Recently I bought an Arduino board to make sort of "light control" in my room. Here is the code of the firmware I wrote:

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);
}

After that, I used pySerial from Python interpreter to control the pins, and everything was working fine. Here is a piece of interpreter output:

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
...

Then I decided to write a simple Python script to do the same:

#!/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))

But it doesn't work at all! The Arduino shows that something was received during the time I launched the script, but nothing happens. Here is a piece of strace output for the script:

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)                           = ?

It looks like everything should be fine, so I don't know what the problem can be. I would appreciate any help, many thanks in advance!

PS When I run the program under PDB, everything works fine. A Heisenbug.

UPDATE: I made the controller send me back the data it was receiving and it looks like it isn't receiving anything when I am running the script, but receives everything when I send the data from the interpreter. The code of the firmware now looks like this:

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);
  }
}

回答1:

I think it's probably a race condition between when the serial port is opened and when the data are sent. I'd probably stick a sleep in between the open and the write calls.

Alternatively, instead of using this library "serial" you might want to just open and write directly to the device, perhaps it's doing something funny (see the double open mentioned in other posts)



回答2:

My guess is it has something to with the environment.

import os
print os.environ['PS1']

From a script that will not be set. (And maybe something else too.)

tty's will buffer differently depending on whether or not they think the terminal is interactive. That should be the only difference between the way your two methods work. A lot applications decide this on whether or not PS1 (your terminal prompt) is set. If you set this in you environment manually it may start behaving the same way as it does interactively.

Also, I would call the call the pyserial flush command manually in your script. (And this would be the preferred way to do it. Instead of masquerading as an interactive terminal.)



回答3:

your strace output shows it opens the serial port read/write twice. The second time it writes only the chr(12), then closes the file. I don't have enough info to solve the problem for you, but perhaps this helps? or did you already figure that out?



回答4:

Can you double check if the Arduino resets when you open the serial connection? In case it does reset the first serial bytes you send will be received by the bootloader and not by your code. The bootloader might then assume that you want to program the controller and wait for further commands and/or data.

The exact behaviour of the bootloader depends on your specific Arduino.

In order to test for this write a small sketch that blinks LED 13 and see if initializing your Python script affects the blinking. If so there is a bootloader.

In order to fix this there are several possible solutions:

1) ensure that there is no reset caused by initializing the serial interface. 1a) do this on the Python side 1b) do this on the Arduino side 1b hardware solution) disconnect the offending traces on the board 1b software solution) get rid of the bootloader

2) do not send data while the bootloader is doing its work.

The simplest solution is (2) my prefered solution is getting rid of the bootloader. However in this case you need an in system programmer (which is a good idea anyway).