MoCoMakers / SolarTracking

1 stars 0 forks source link

[report] Timing of StepperFast.ino #2

Open yoursunny opened 5 years ago

yoursunny commented 5 years ago

I measured timing of StepperFast.ino.

This test was performed on a Sintro Uno R3 unit, not connected to any motor etc. The command sequence is: "E", "Z", "Tn", and I measure how soon does - appear after sending the T command.

Test results:

degree duration (seconds)
0 0.10
1~255 3.31
256~359 3.27

I was expecting the duration to increase from 0 to 180 and decrease from 180 to 359. It's surprising that there are only two duration values and the gap suspiciously falls between 255 and 256, the overflow point of uint8_t type.

The program for this test is:

#!/usr/bin/python3

import serial
import time

class Turntable:
    """
    Control a MocoMakers.com turntable over serial port.
    """

    def __init__(self, port, ackTimeout=0.2):
        """
        Initialize a turntable controller.
        :param port: serial port name
        :type port: str
        :param ackTimeout: how long (seconds) to wait for an acknowledgement
        :type ackTimeout: float
        """
        self.ackTimeout = ackTimeout
        self.serial = serial.Serial(
            port=port, baudrate=115200, timeout=ackTimeout, writeTimeout=ackTimeout)
        for i in range(10):
            self.serial.read()

    def close(self):
        self._cmd("E", immediate=True)
        self.serial.close()

    def _cmd(self, cmd, immediate=False, timeout=None):
        """
        Send a command and wait for completion.
        :param cmd: command string including parameters
        :type cmd: str
        :param immediate: whether the command would complete immediately (no "-" return)
        :type immediate: boolean
        :param timeout: timeout (seconds) for command completion
        :type timeout: float
        :return: whether success
        :rtype: bool
        """
        cmd = ("%s\n" % cmd).encode("ascii")
        self.serial.write(cmd)
        time.sleep(0.1)
        ack = self.serial.read()
        if ack != cmd[0:1]:
            return False
        if immediate:
            return True

        timeout = timeout if timeout is not None else 30.0
        elapsed = 0.0
        while elapsed < timeout:
            ack = self.serial.read()
            if ack == b'-':
                return True
            elapsed = elapsed + self.ackTimeout
        return False

    def zero(self):
        """
        Mark current position as zero.
        """
        return self._cmd("Z", immediate=True)

    def rotateBy(self, n, timeout=None):
        """
        Rotate n degrees from current position.
        :param n: relative position (degrees).
        :type n: float
        """
        return self._cmd("B%0.2f" % n, timeout=timeout)

    def rotateTo(self, n, timeout=None):
        """
        Rotate n degrees from zero position.
        :param n: absolute position (degrees).
        :type n: float
        """
        return self._cmd("T%0.2f" % n, timeout=timeout)

    def stop(self, timeout=None):
        """
        Stop rotation.
        """
        return self._cmd("S", timeout=timeout)

    def estop(self):
        """
        Emergency stop.
        """
        return self._cmd("E", immediate=True)

def measureTiming(port):
    """
    Measure rotation timing of a turntable and print as TSV.
    """
    turntable = Turntable(port)
    for n in range(0, 360):
        if not turntable.estop():
            raise RuntimeError("estop command failed")
        if not turntable.zero():
            raise RuntimeError("zero command failed")
        t0 = time.time()
        ok = turntable.rotateTo(n)
        t1 = time.time()
        if not ok:
            raise RuntimeError("rotateTo command failed")
        print("%d\t%0.2f" % (n, t1-t0))

if __name__ == '__main__':
    import argparse
    parser = argparse.ArgumentParser(
        description='Turntable timing measurements.')
    parser.add_argument('--port', type=str, required=True, help='serial port')
    args = parser.parse_args()
    measureTiming(args.port)
yoursunny commented 5 years ago

The last test result was incorrect due to bug #4. I retested on the real unit with the following code:

def measureTiming(port):
    """
    Measure rotation timing of a turntable and print as TSV.
    """
    for n in range(0, 1800, 30):
        turntable = Turntable(port)
        if not turntable.zero():
            raise RuntimeError("zero command failed")
        t0 = time.time()
        ok = turntable.rotateBy(n)
        t1 = time.time()
        if not ok:
            raise RuntimeError("rotateTo command failed")
        print("%d\t%0.2f" % (n, t1-t0))
        turntable.close()

The results are:

rotateBy duration(seconds)
0 0.10
30 6.23
60 7.42
90 8.48
120 9.50
150 10.51
180 11.51
210 12.51
240 13.52
270 14.47
300 15.48
330 16.48
360 17.48
390 18.48
420 19.48
450 20.48
480 21.48
510 22.48
540 23.45
570 24.45
600 25.45
630 26.46
660 27.45
690 28.46
720 29.46
750 30.46
780 31.46
810 32.46
840 33.47
870 34.47

My test indicates that the display is consistent with LCD display, but the actual motor/turntable is very inaccurate. In fact, rotateTo(oldPosition) does not actually go to the old position. This is probably a hardware issue.