12

I'm trying to read lines from an Arduino board with a very simple code (for the sake of showcasing the problem) on Linux.

Python code:

# arduino.py
import serial
arduino = serial.Serial('/dev/ttyACM0')

with arduino:
    while True:
        print(arduino.readline())

Arduino code:

// simpleWrite.ino
long ii = 0;

void setup() {
  // initialize serial communications at 9600 bps:
  Serial.begin(9600);
}

void loop() {
  Serial.println(ii);
  ii++;
}

As the board auto-resets when the serial connection is opened, the first bytes are likely garbage. After a second or two everything works fine.

This is a typical output:

$ python arduino.py 
b'09\r\n'
b'540\r\n'
b'541\r\n'
b'542\r\n'
b'543\r\n'
b'544\r\n'
b'545\r\n'
b'546\r\n'
b'547\r\n'
b'548\r\n'
b'549\r\n'
b'550\r\n'
b'551\r\n'
b'552\r\n'
b'553\r\n'
b'554\r\n'
b'555\r\n'
b'556\r\n'
b'557\r\n'
b'55\xfe0\r\n'  # <---- Here the board restarted
b'1\r\n'
b'2\r\n'
b'3\r\n'
b'4\r\n'
b'5\r\n'
b'6\r\n'
b'7\r\n'
b'8\r\n'
b'9\r\n'
b'10\r\n'

However, I see the Arduino IDE Serial Monitor doesn't have this problem, and properly shows a delay (while restarting) and then prints all the lines starting from the first one.

Is there a way to emulate this behaviour in Python using pySerial? That is, discarding all the output before restarting and nothing more? Perhaps through some low-level functions?

I tried looking at the relevant Arduino source code, but I don't know Java and it didn't help.

Note: Of course I could sleep for, say, three seconds, discard everything and start from there, but I would probably discard some of the first lines too.

Edit: Apparently, this problem doesn't exist on Windows and the accepted solution was not necessary.

astrojuanlu
  • 6,744
  • 8
  • 45
  • 105
  • This is not a problem with pySerial, This is garbage sent from your arduino serial, you can try to sleep for a short time 10[ms] it may help. – Kobi K Jan 12 '14 at 09:32
  • As I said, it's a consequence of the Arduino restarting when the serial connection is open. You can reproduce the result pressing the Reset button: the Arduino blocks, an hex char is printed and then it goes on. – astrojuanlu Jan 12 '14 at 12:40

4 Answers4

18

The Arduino IDE's monitor toggle's the assigned DTR pin of the port when connected. Where this toggling causes a reset on the Arduino. Noting that the DTR is toggled after the Monitor has opened the Serial port and is ready to receive data. In your case the below example should do the same.

Import serial

arduino = serial.Serial('/dev/ttyS0',
                     baudrate=9600,
                     bytesize=serial.EIGHTBITS,
                     parity=serial.PARITY_NONE,
                     stopbits=serial.STOPBITS_ONE,
                     timeout=1,
                     xonxoff=0,
                     rtscts=0
                     )
# Toggle DTR to reset Arduino
arduino.setDTR(False)
sleep(1)
# toss any data already received, see
# http://pyserial.sourceforge.net/pyserial_api.html#serial.Serial.flushInput
arduino.flushInput()
arduino.setDTR(True)

with arduino:
    while True:
        print(arduino.readline())

I would also add the compliment to the DTR for the Arduino's with AVR's using built-in USB, such as the Leonoardo, Esplora and alike. The setup() should have the following while, to wait for the USB to be opened by the Host.

void setup() {
  //Initialize serial and wait for port to open:
  Serial.begin(9600);
  while (!Serial) {
    ; // wait for serial port to connect. Needed for Leonardo only
  }
}

It will have no effect for FTDI's based UNO's and such.

mpflaga
  • 2,807
  • 2
  • 15
  • 19
  • Thanks for your answer, it seems similar to what I wanted. However, I'm using Python 3 and latest pySerial on Linux and just discovered that `.flush()` _actually won't flush_ the data from the device. I'm gonna try with `.flushInput()` and see. – astrojuanlu Jan 13 '14 at 22:06
  • What if you'd never issue setDTR(true), it works without restarting the board? Have anyone tried this code? – Marcel Mar 12 '15 at 21:01
  • arduino.setDTR(True) It is permanent on the board? or only during the script? – JBarbosa Jul 07 '16 at 05:32
2

I realize this is an old question, but hopefully this can be useful to somebody else out there with the same problem.

I had an issue where if I used any baudrates other than 9600, the serial connection in python would just receive gibberish all the time, even if Serial.begin(...) is properly set on the arduino and matches the value used in the python code. I read online that the bootloader or watchdog may take a second to load (when the board is power-cycled) and it may send stuff over serial at some specific baudrate (for chip programming possibly). I'm guessing that this is what messes up the serial communication in python.

Here's the piece of code that gives me reliable results:

import serial
from time import sleep
arduino = serial.Serial('/dev/ttyACM0') # dummy connection to receive all the watchdog gibberish (unplug + replug) and properly reset the arduino
with arduino: # the reset part is actually optional but the sleep is nice to have either way.
  arduino.setDTR(False)
  sleep(1)
  arduino.flushInput()
  arduino.setDTR(True)

# reopen the serial, but this time with proper baudrate. This is the correct and working connection.
arduino = serial.Serial('/dev/ttyACM0',baudrate=57600)

with arduino:
    while True:
        print(arduino.readline())

The code used on the arduino side for testing is as simple as this:

void setup() {
  Serial.begin(57600);
  Serial.println("setup");
}

void loop() {
  Serial.println("hello");
  delay(200);
}
Weboide
  • 1,112
  • 1
  • 11
  • 22
0

Please follow this link for a reliable PC-Arduino USB serial communication using python.

Python code simply sends a short message to the Arduino and prints the reply it receives.

// This is very similar to Example 3 - Receive with start- and end-markers
//    in Serial Input Basics   http://forum.arduino.cc/index.php?topic=396450.0

const byte numChars = 64;
char receivedChars[numChars];

boolean newData = false;

byte ledPin = 13;   // the onboard LED

//===============

void setup() {
    Serial.begin(115200);

    pinMode(ledPin, OUTPUT);
    digitalWrite(ledPin, HIGH);
    delay(200);
    digitalWrite(ledPin, LOW);
    delay(200);
    digitalWrite(ledPin, HIGH);

    Serial.println("<Arduino is ready>");
}

//===============

void loop() {
    recvWithStartEndMarkers();
    replyToPython();
}

//===============

void recvWithStartEndMarkers() {
    static boolean recvInProgress = false;
    static byte ndx = 0;
    char startMarker = '<';
    char endMarker = '>';
    char rc;

    while (Serial.available() > 0 && newData == false) {
        rc = Serial.read();

        if (recvInProgress == true) {
            if (rc != endMarker) {
                receivedChars[ndx] = rc;
                ndx++;
                if (ndx >= numChars) {
                    ndx = numChars - 1;
                }
            }
            else {
                receivedChars[ndx] = '\0'; // terminate the string
                recvInProgress = false;
                ndx = 0;
                newData = true;
            }
        }

        else if (rc == startMarker) {
            recvInProgress = true;
        }
    }
}

//===============

void replyToPython() {
    if (newData == true) {
        Serial.print("<This just in ... ");
        Serial.print(receivedChars);
        Serial.print("   ");
        Serial.print(millis());
        Serial.print('>');
            // change the state of the LED everytime a reply is sent
        digitalWrite(ledPin, ! digitalRead(ledPin));
        newData = false;
    }
}

//===============

Python Code

import serial
import time

startMarker = '<'
endMarker = '>'
dataStarted = False
dataBuf = ""
messageComplete = False

#========================
#========================
    # the functions

def setupSerial(baudRate, serialPortName):
    
    global  serialPort
    
    serialPort = serial.Serial(port= serialPortName, baudrate = baudRate, timeout=0, rtscts=True)

    print("Serial port " + serialPortName + " opened  Baudrate " + str(baudRate))

    waitForArduino()

#========================

def sendToArduino(stringToSend):
    
        # this adds the start- and end-markers before sending
    global startMarker, endMarker, serialPort
    
    stringWithMarkers = (startMarker)
    stringWithMarkers += stringToSend
    stringWithMarkers += (endMarker)

    serialPort.write(stringWithMarkers.encode('utf-8')) # encode needed for Python3


#==================

def recvLikeArduino():

    global startMarker, endMarker, serialPort, dataStarted, dataBuf, messageComplete

    if serialPort.inWaiting() > 0 and messageComplete == False:
        x = serialPort.read().decode("utf-8") # decode needed for Python3
        
        if dataStarted == True:
            if x != endMarker:
                dataBuf = dataBuf + x
            else:
                dataStarted = False
                messageComplete = True
        elif x == startMarker:
            dataBuf = ''
            dataStarted = True
    
    if (messageComplete == True):
        messageComplete = False
        return dataBuf
    else:
        return "XXX" 

#==================

def waitForArduino():

    # wait until the Arduino sends 'Arduino is ready' - allows time for Arduino reset
    # it also ensures that any bytes left over from a previous message are discarded
    
    print("Waiting for Arduino to reset")
     
    msg = ""
    while msg.find("Arduino is ready") == -1:
        msg = recvLikeArduino()
        if not (msg == 'XXX'): 
            print(msg)



#====================
#====================
    # the program


setupSerial(115200, "/dev/ttyACM0")
count = 0
prevTime = time.time()
while True:
            # check for a reply
    arduinoReply = recvLikeArduino()
    if not (arduinoReply == 'XXX'):
        print ("Time %s  Reply %s" %(time.time(), arduinoReply))
        
        # send a message at intervals
    if time.time() - prevTime > 1.0:
        sendToArduino("this is a test " + str(count))
        prevTime = time.time()
        count += 1
nemesis
  • 150
  • 9
  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Feb 21 '22 at 11:34
  • While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes. - [From Review](/review/late-answers/31122412) – MD Mushfirat Mohaimin Feb 24 '22 at 17:53
-1

you need to set your var , try:

unsigned long ii = 0;

but pay attention that this is a 32 bit var and when it is full ,cause overflow and reboot. for me work. As suggested by @Kobi K add a minimal delay time, for load real data at 9600 boud each char has a duration of 2 ms,

    void loop() {

      Serial.println(ii);
      delay(20);
      ii++;
    }

And in python you need to declare a Pyserial like this:

arduino=serial.Serial('/dev/ttyACM0',9600,timeout=0.0001)

hope this help you

archetipo
  • 579
  • 4
  • 10
  • 1
    The problem is that `timeout` here is immaterial because the `readline` function is inherited from `io.RawIOBase` and does not accept a timeout argument, so the call blocks until a newline char is found. – astrojuanlu Jan 12 '14 at 17:53
  • OTOH, I don't think integer overflow is causing the reboot because it happens at the beginning, with any value of ii, just because the serial connection is opened. It happens too when opening the Arduino Serial Monitor or when typing `cat < /dev/ttyACM0`. – astrojuanlu Jan 12 '14 at 17:53
  • but, I use this method for more than 3 years and I have never had any problems .. – archetipo Jan 12 '14 at 18:21