1

I am using 2 adafruit PWM servo shield in order to generate 25 PWM signals. In particular I am using this shield: https://www.adafruit.com/products/1411

A python script sends the commands to the Arduino (Mega 2560) through pyserial. The commands are like "A0 P1 S500\n". Each argument separated by a space. The first chooses the shield, the second one the pin, and the third one the speed.

The code running in the Arduino is as follows:

#include <Adafruit_PWMServoDriver.h>

#define BUFFER_SIZE 200

// Init both adafruit boards
Adafruit_PWMServoDriver pwm0 = Adafruit_PWMServoDriver(0x40);
Adafruit_PWMServoDriver pwm1 = Adafruit_PWMServoDriver(0x41);


int read_command(char *buffer)
{

    static int pos = 0;
    int rpos;

    if (Serial.available() > 0) {
        Serial.println(Serial.available());
        char readch = Serial.read();

        switch (readch) {

            case '\n':
                rpos = pos;
                pos = 0; // Reset position index ready for next time
                Serial.println(buffer);
                return rpos;

            default:

                if (pos < BUFFER_SIZE -1) {
                    buffer[pos++] = readch;
                    buffer[pos] = '\0';
                } else {
                    Serial.println("666"); //buffer overflow
                }
        }
    }
    // no end line or char found, return -1
    return -1;

}


void parse_command(char* command) 
{

    char* parameter;
    parameter = strtok(command, " ");
    long shield, pin, speed;

    while (parameter != NULL) {

        switch(parameter[0]) {

            case 'A':
                shield = strtol(parameter+1, NULL, 10);
                break;

            case 'P':
                pin = strtol(parameter+1, NULL, 10);
                break;

            case 'S':
                speed = strtol(parameter+1, NULL, 10);
                break;

        }
        parameter = strtok(NULL, " ");
    }

    for (int x=0; x < BUFFER_SIZE; x++)
        command[x] = '\0';

  if (shield == 0)
    pwm0.setPWM(pin, 0, speed);

  if (shield == 1)
    pwm1.setPWM(pin, 0, speed);

}


void setup() 
{

  Serial.begin(9600);
  Serial.flush();

  pwm0.begin();
  pwm0.setPWMFreq(100);  //Max is 1600, Kevin was using 1000
  pwm1.begin();
  pwm1.setPWMFreq(100);

}


void loop() 
{

  static char buffer[BUFFER_SIZE];

  if (read_command(buffer) > 0)
    parse_command(buffer);

}

As said, I am sending the commands from a Python script, where for example I have a function called "activate all", that will send 25 commands like "A0 P1 S500\n" (obviously with different values after the A and the P) to the serial port. I also have a function called "disable all", which is very similar to the previous command, but setting the S to 0 so like "A0 P1 S0\n".

So, as I start the serial communication, if I execute from Python "activate_all", I can how all the PWM signals are generated at the same time. I know it is actually 1 by 1, but it is fast enough to seem simultaneously to the human eye. If I then send a disable_all, I can see how all the PWM signals are set to 0 at the same time.

This works as expected for a while, but after some point, when I send an activate_all, it will indeed activate all of them, but it activates each PWM signal one by one, with a delay of around 1 second between activations (so you can clearly see them going one by one), and a very similar effect when disabling them all. From this point on, which seems slightly different each time, every time I want to activate more than one pwm signal at the same time, it will add this 1s delay between activations.

The only way I have to fix this is to send from the python a "flush()". which seems to be fix it. Doing flushes from the Arduino code, like when for example serial.available is 0, doesnt seem to fix it.

I don't know what exactly I am doing wrong. I guess the way I am handling the communication buffer is wrong, but I haven't seen where my mistake is.

A very similar code to the one pasted here was used in another project, in that case it was interfaced with Labview, and in that case everything seem fine.

In case you wonder about the code running in the Python script, it is like this:

import serial, time, json


class BZBoard:


    def __init__(self, port):

        self.ser = serial.Serial(port, 9600, timeout=120)
        time.sleep(2) # serial docs recommend a wait just after connection
        self.ser.flush(); self.ser.flushInput(); self.ser.flushOutput();

        self.motors = {
            just naming the pins
            }


    def __del__(self):

        self.disable_all()
        self.ser.close()
        del self.ser


    def close(self):

        self.disable_all()
        self.ser.close()
        del self.ser

    def activate_motor(self, motor_code, speed=300):
        ''' code as in A1 or C2 as marked in the actual board. See the dict motors'''

        shield, pin = self.motors[motor_code]
        command = "A%d P%d S%d\n" % ( shield, pin, speed )
        self.ser.write(command.encode())


    def activate_all(self, speed=300):

        for key in self.motors.keys():
            self.activate_motor(key, speed)


    def disable_motor(self, motor_code):

        shield, pin = self.motors[motor_code]
        command = "A%d P%d S0\n" % ( shield, pin )
        self.ser.write(command.encode())


    def disable_all(self):

        for key in self.motors.keys():
            self.disable_motor(key)


if __name__ == "__main__":

    board = BZBoard("/dev/ttyACM0")

So the user will do for example a lopp that repeats itself 20 times, where it will activate_all, wait 1 second, disable_all, wait 1 second. The first 10 iterations or so, everything will be fine and the PWM are generated all of them at the same time (with respect to the human eye), but after that, the PWM signals are generated 1 by 1, with a delay of 1s or so between them.

  • If you comment out all the Serial output (ie Serial.print() or Serial.println()) does the problem still occur? – James Waldby - jwpat7 Mar 3 '17 at 21:05
  • @jwpat7 yes. initially there was no serial output. I added it to debug – Dr Sokoban Mar 4 '17 at 10:16
  • Your sketch generates greater output for each character of input, so unless you have pacing in your sending code, it will fall behind. It's also possible that your sending code is filling up the transmit buffer that drip-feeds data through the slow serial link. And it's not clear if you are claiming the response data or just letting it fill up an input buffer in the PC operating system. – Chris Stratton Mar 4 '17 at 16:53
  • @ChrisStratton by greater output you mean the PWM signals generated through Adafruit? I think also I have a lot of pacing. I have executed the same code with sleeps of 5 seconds, and the same problem appears. I don't understand what you mean about the transmit buffer, in particular when you say if I am claiming the response data or not. Could you help me with that? thanks for reading my question – Dr Sokoban Mar 4 '17 at 18:07
1

There seems to be reason to think about filled buffers causing stalls and timeouts, but comments regarding 5-second sleeps between trials seem to rule that out. To debug what's actually happening, you might blink an LED each time you receive a character, and another LED each time you call your parse_command() routine; and if you have an oscilloscope, look at the serial transmissions back and forth to see whether delay is due to something at the Arduino end vs at the host end.

That said, I think it would make sense to simplify the protocol by getting rid of your input buffer and just processing input as it comes. For example, you could use the following letters as post-fix actions.

A  Select shield 1
B  Select shield 2
P  Set pin number, eg 11P selects pin 11
S  Set speed, eg 472S sets speed to 472
N  Send current speed to current pin, then increment pin number

For example, the command sequence for “disable_all” could look like:

0S0PANNNNNNNNNNNN 0PBNNNNNNNNNNNNN

This is easy to parse on the fly; declare int val=0 and any other needed variables before loop(), then within loop() say:

  char inc;   // For incoming character
  while (Serial.available()) { // Get characters
    inc = Serial.read();
    if (inc >= '0' && inc <= '9') {
      val = 10*val + (inc-'0');
    } else {
      switch (toupper(inc)) {
      case 'A':
        board = 0;
        break;
      case 'B':
        board = 1;
        break;
      case 'P':
        pin = val;
        break;
      case 'S':
        speed = val;
        break;
      case 'N':
        if (PinNumberIsOk(board, pin))
          pwm[board].setPWM(pin, 0, speed);
        ++pin;
        break;
      default :
        ; // handle blanks, returns, whatever
      }
      val = 0;  // Set val to zero on any non-digit
    }

As another example, the sequence 4PA 11SN13SN17SN would send speeds 11, 13, and 17, respectively, to motors 4, 5, 6 on the first shield.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.