13

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);
  }
}
dsolimano
  • 8,870
  • 3
  • 48
  • 63
Ivan Galinskiy
  • 135
  • 1
  • 8
  • Did `ser.write(chr(12)); time.sleep(1); ser.write(chr(256-12))` works fine from console? – seriyPS Nov 20 '10 at 23:56
  • Yes. Lights turn on, off, jpnevulator shows the data returned by controller. When I do the same from a script, jpnevulator shows no data returned and nothing happens. – Ivan Galinskiy Nov 21 '10 at 00:05
  • I upgraded pySerial, but no results. – Ivan Galinskiy Nov 21 '10 at 00:24
  • You could try: 0. Does `print ser` produces the same from console and from the script? 1. put the code into `def main():..` and guard the call with `if __name__=="__main__": main()` 2. add `time.sleep(2)` *before* the first `ser.write()` 3. add flush() after each write(). 4. add ser.close() – jfs Nov 22 '10 at 08:31
  • 1
    Wow, actually putting time.sleep(2) before the first write did the trick! It's now working! I didn't think it could be something that simple. It should be 2 seconds of sleep or more to make it work, though. – Ivan Galinskiy Nov 22 '10 at 08:53
  • You could try `while not ser.isOpen(): time.sleep(0.04)` before the first ser.write() – jfs Nov 22 '10 at 12:48

4 Answers4

9

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)

MarkR
  • 62,604
  • 14
  • 116
  • 151
  • Yes, time.sleep(2) worked! In fact, it doesn't depend on the programming language, I had the same problems even in C (using POSIX calls) and C++ (using libSerial) until I added the sleep(2) there. – Ivan Galinskiy Nov 22 '10 at 08:57
  • 1
    I think it's a race condition too. Specifially, opening the serial port resets the arduino! See http://stackoverflow.com/questions/1618141/pyserial-problem-with-arduino-works-with-python-shell-but-not-in-program/4941880#4941880 for details. – Brian Bloniarz Feb 09 '11 at 06:20
1

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

nate c
  • 8,802
  • 2
  • 27
  • 28
0

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

Udo Klein
  • 6,784
  • 1
  • 36
  • 61
0

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?

jcomeau_ictx
  • 37,688
  • 6
  • 92
  • 107
  • Yes, it looks like it opens it twice and then writes to the second descriptor. Maybe I can try to do the same with another version of Python. – Ivan Galinskiy Nov 21 '10 at 00:13
  • I'm on a laptop with no serial port, otherwise I'd enjoy helping you debug this! – jcomeau_ictx Nov 21 '10 at 00:18
  • Since it looks like the script successfully wrote the `chr(12)` to the port, did at least the light turn on when you ran it? – martineau Nov 21 '10 at 01:37
  • he said it did not, indicating that having two handles open to the same serial port is probably part of the problem. – jcomeau_ictx Nov 21 '10 at 01:39
  • Some serial device drivers and UARTs handle control characters differently than normal ones, so maybe the `chr(12)`, which is an ASCII *FF* or formfeed character, is being intercepted. Another thing is that serial communications can be 7 or 8-bit, so if it's get set to 7-bit, it would be hard to send the `chr(256-12)`, or 0xF4, 8-bit character through the channel. There could be other communications setting like number of data bits per character, parity, and number of stop bits per character causing problems, too. None of this explains the interp vs script difference, though. – martineau Nov 21 '10 at 01:52
  • Well, actually the lights on the board blinked in all cases, but my controller didn't resend me the bytes when running the script (I set it up for this). The commands from the interpreter work (the board resends the data back), but not from the script, so I don't think it's about hardware. Weird, isn't it? – Ivan Galinskiy Nov 21 '10 at 06:08