使用Arduino和Python通过串口持续控制多个舵机

1 投票
1 回答
3252 浏览
提问于 2025-04-18 05:28

我一直在用Python编写代码,控制Arduino上的一大堆舵机。代码是能运行的,但我发现时间控制上有些奇怪的情况。每次更新舵机的时候,我需要加一个暂停命令。如果不加这个暂停,程序第一次运行的时候没问题,但如果Python代码被停止了,再次通过串口连接Python时,Arduino就不响应了。这可能是串口出了什么问题,我该怎么避免这种情况呢?

另外,我用memcpy来处理传入Arduino的数据,这样做效率高吗?还是有更好或者更标准的方法呢?

我听说过使用serialEvent,这个命令在解析串口数据时有什么好处吗?

//ARDUINO CODE
#include <Servo.h> 

int servoPins[] = {9, 10};
//int servoPins[] = {2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38};
//int servoPins[] = {2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34};
const int numServos = sizeof(servoPins)/sizeof(int);
Servo servos[numServos];

char serialData[numServos * 3];
char tempData[3];

void setup() {
  Serial.begin(9600);
  Serial.println("Ready");
  for (int i=0; i < numServos; i++) {
    servos[i].attach(servoPins[i]);
    //servos[i].write(20);
  }
}

void loop() {
  if (Serial.available()) {
    Serial.readBytesUntil('\0', serialData, numServos * 3);

    for (int i=0; i < numServos; i++) {
      memmove(tempData, serialData + i * 3, 3);
      servos[i].write(atoi(tempData));
    }
  }
}


#PYTHON CODE
""" Control n servos on n arduinos over serial

"""

import glob
import platform
import serial
from time import sleep
import sys

'''
Servo
Id number, angle for servo, Arduino port servo is on
For now the port is used as an index, so please number them going from 0-n

'''
class Servo:
    id_num = 0
    angle = 0
    port = 0

    def __init__(self, id_num, angle, port):
        self.id_num = id_num
        self.angle = angle
        self.port = port

'''
ServoDriver
-Stores a list of servos
-Open ports to Arduinos
-Stores a list of those ports
-Creates a map of which servos are on which ports
-Provides a way to update the angle of all servos on all ports
'''
class ServoDriver:
    def __init__(self, servos):
        self.ports = self.open_ports()
        self.servos = servos

    # looks for all devices that have the same name pattern as an Arduino and opens them
    def open_ports(self):
        # find arduinos
        # note: currently this is Mac only
        devices = glob.glob('/dev/tty.usbmodem*')
        print devices

        if len(devices) == 0:
            print "No Arduinos found"
            sys.ext(1)

        ports = []
        for device in devices:
            try:
                # connect to serial port
                ports.append(serial.Serial(device, 9600))
            except:
                print 'Failed to open port'
                sys.ext(1)


        # need a short delay right after serial port is started for the Arduino to initialize
        sleep(2)
        return ports

    # update the angle of all servos on all ports
    def update(self, servos):
        debug = True
        servo_data = []
        for p in self.ports:
            servo_data.append('')
        for servo_update in servos:
            for servo_stored in self.servos:
                if servo_stored.id_num == servo_update.id_num:
                    this_port = servo_stored.port
                    break

            # constrain servo angles to avoid damaging servos
            if servo_update.angle > 135:
                servo_update.angle = 135
            else if servo_update.angle < 45:
                servo_update.angle = 45

            # append angle to the datum for this port
            servo_data[this_port] = servo_data[this_port] + str(servo_update.angle).zfill(3)

        for servo_datum in servo_data:
            # append null byte for arduino to recognize end of data
            servo_datum = servo_datum + "\0"

        # send data to the Arduinos
        for port,servo_datum in zip(self.ports,servo_data):
            port.write(servo_datum)

    def close_ports(self):
        print 'closing ports'
        for port in self.ports:
            port.close()

# generates values for making a servo sweep back and forth
def servo_iter():
    l = []
    #for i in range(0,1):
    l.append(40)
    #for i in range(0,1):
    l.append(80)
    for pos in l:
        yield pos

def servo_iter_2(total):
    for i in range(0,total):
        yield i

if __name__ == "__main__":
    # create a list of servos with mappings to ports
    # if you have the wrong number of servos it acts weird
    #num_servos = 32
    num_servos = 2
    servos = []
    servos.append(Servo(0, 40, 0))
    servos.append(Servo(1, 40, 0))
    '''
    servos.append(Servo(2, 40, 0))
    servos.append(Servo(3, 40, 0))
    servos.append(Servo(4, 40, 0))
    servos.append(Servo(5, 40, 0))
    servos.append(Servo(6, 40, 0))
    servos.append(Servo(7, 40, 0))
    servos.append(Servo(8, 40, 0))
    servos.append(Servo(9, 40, 0))
    servos.append(Servo(10, 40, 0))
    servos.append(Servo(11, 40, 0))
    servos.append(Servo(12, 40, 0))
    servos.append(Servo(13, 40, 0))
    servos.append(Servo(14, 40, 0))
    servos.append(Servo(15, 40, 0))
    servos.append(Servo(16, 40, 0))
    servos.append(Servo(17, 40, 0))
    servos.append(Servo(18, 40, 0))
    servos.append(Servo(19, 40, 0))
    servos.append(Servo(20, 40, 0))
    servos.append(Servo(21, 40, 0))
    servos.append(Servo(22, 40, 0))
    servos.append(Servo(23, 40, 0))
    servos.append(Servo(24, 40, 0))
    servos.append(Servo(25, 40, 0))
    servos.append(Servo(26, 40, 0))
    servos.append(Servo(27, 40, 0))
    servos.append(Servo(28, 40, 0))
    servos.append(Servo(29, 40, 0))
    servos.append(Servo(30, 40, 0))
    servos.append(Servo(31, 40, 0))
    servos.append(Servo(32, 40, 0))
    '''

    #if len(servos) != num_servos:
    #   print 'wrong number of servos'
    #   sys.ext(1)

    # comment out the next two lines if you only have 1 arduino
    #servos.append(Servo(2, 40, 1))
    #servos.append(Servo(3, 40, 1))
    #servos.append(Servo(4, 40, 2))
    #servos.append(Servo(5, 40, 2))

    angles = []
    for i in range(0,len(servos)):
        angles.append(40)

    try:
        # instantiate a driver
        # must happen inside try-finally
        driver = ServoDriver(servos)

        iter1 = False
        if iter1:
            pos = servo_iter()
        else:
            pos = servo_iter_2(len(servos))

        while True:
            try:
                x = pos.next()
            except StopIteration:
                if iter1:
                    pos = servo_iter()
                else:   
                    pos = servo_iter_2(len(servos))
                x = pos.next()
            # create a list of servos with ids and angles to update positions of servos
            if iter1:
                for servo in servos:
                    servo.angle = x
            else:
                for i,servo in zip(angles,servos):
                    servo.angle = i
            # call the driver with the list of servos
            driver.update(servos)
            sleep(0.5)

            for i in range(0, len(servos)):
                if i == x:
                    angles[i] = 80
                else:
                    angles[i] = 40

    # close the serial port on exit, or you will have to unplug the arduinos to connect again
    finally:
        driver.close_ports()

我觉得这里的正确做法是给Arduino加一个延迟。

1 个回答

3

每次你通过USB连接你的Arduino时,它都会重启。如果你想暂时停止这个重启,可以在地线和重置引脚之间插入一个10微法拉的电容器。这样就能防止它自动重启,而且在上传程序时也可以很方便地把它取掉。

撰写回答