4
\$\begingroup\$

I want to control a motor which rotates a device over a gear.

Input values for the device are from 0 to 360 degrees. The motor should always rotate the device the shorter way around. For example, if the position is 30° and target position will be 340°, it should rotate over 0° counter clockwise.

The motor controller internally has so called qc unit system. One turnaround of the the device for example constitutes to 2938500, so there is a translation of values necessary. The internal position (in qc) could become negative or positive values like an integer.

I ended up with that code and some tests to be sure it should work. Did I catch all cases? Can I still simplify the move function?

import unittest

qc360 = 2938500

def qc2deg(qc):
    return (qc / float(qc360)) * 360.0

def deg2qc(deg):
    return int(round((deg/360.0) * qc360 ))

def move(is_qc, target_deg):
    is_deg = qc2deg(is_qc)
    diff = is_deg - target_deg

    if abs(diff) <= 180.0:
        if diff < 0:
            target_qc = is_qc + deg2qc(abs(diff))
        else:
            target_qc = is_qc + deg2qc(-diff)
    else:
        if diff < 0:
            target_qc = is_qc + deg2qc(abs(diff)-360)
        else:
            target_qc = is_qc + deg2qc(360-diff)

    return target_qc

class TestMovement(unittest.TestCase):

    def test_1qc2deg(self):
        self.assertEqual(qc2deg(qc360/2), 180)
        self.assertEqual(qc2deg(qc360+qc360/2), 360+180)
        self.assertEqual(qc2deg(-qc360/2), -180)
        self.assertEqual(qc2deg(-qc360-qc360/2), -360-180)

    def test_2deg2qc(self):
        self.assertEqual(deg2qc(180), qc360/2)
        self.assertEqual(deg2qc(360+180), qc360+qc360/2)
        self.assertEqual(deg2qc(-180), -qc360/2)
        self.assertEqual(deg2qc(-360-180), -qc360-qc360/2)

    def test_3simpleMovePositive(self):
        # Move within 180 degrees, without zero crossing
        self.assertEqual(move(0, 170), deg2qc(170))
        self.assertEqual(move(20, 90), deg2qc(90))

    def test_4simpleMoveNegative(self):
        self.assertEqual(move(20, 5), deg2qc(5))
        self.assertEqual(move(210, 170), deg2qc(170))

    def test_5wideMovePositive(self):
        # positive direction
        self.assertEqual(move(0, 190), deg2qc(-170))
        self.assertEqual(move(deg2qc(60), 350), deg2qc(-10))

    def test_6wideMoveNegative(self):
        # negative direction
        self.assertEqual(move(deg2qc(190), 0), deg2qc(360))
        self.assertEqual(move(deg2qc(350), 60), deg2qc(360+60))

    def test_7variousMoves(self):
        self.assertEqual(move(deg2qc(260), 50), deg2qc(360+50))
        self.assertEqual(move(deg2qc(260), 60), deg2qc(360+60))
        self.assertEqual(move(deg2qc(260), 70), deg2qc(360+70))
        self.assertEqual(move(deg2qc(260), 80), deg2qc(80))
        self.assertEqual(move(deg2qc(260), 90), deg2qc(90))
        self.assertEqual(move(deg2qc(260), 80), deg2qc(80))
        self.assertEqual(move(deg2qc(260), 120), deg2qc(120))

if __name__ == '__main__':
    unittest.main()
\$\endgroup\$
3
\$\begingroup\$
    if diff < 0:
        target_qc = is_qc + deg2qc(abs(diff))
    else:
        target_qc = is_qc + deg2qc(-diff)

If diff < 0, then abs(diff) == -diff, just the same as the else case. The

    target_qc = is_qc + deg2qc(-diff)

(no if's and else's) should suffice. The else case (that is, abs(diff) > 180.0) also can be rewritten as

    if diff < 0:
        target_qc = is_qc + deg2qc(-diff - 360)
    else:
        target_qc = is_qc + deg2qc(-diff + 360)

where the cases only differ in signs of 360, which suggests that you are looking for the remainder -diff % 360; notice that python supports the remainder of floats.

To get rid of ugly leading -, you may want to calculate diff as

    diff = target_deg - is_deg
\$\endgroup\$

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.