aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEvgeny Zinoviev <me@ch1p.io>2021-10-31 16:30:35 +0300
committerEvgeny Zinoviev <me@ch1p.io>2021-10-31 16:30:35 +0300
commitdd54f14917e1883f2dd8ed64f913d7525e4708cc (patch)
tree40f58e7681cab1ee356ca70ca813bc84a39c97de
initial
-rw-r--r--.gitignore3
-rw-r--r--README.md28
-rwxr-xr-xexample.py44
-rw-r--r--mg996r.py28
-rw-r--r--orangepwm.py83
-rw-r--r--requirements.txt1
6 files changed, 187 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..e07204b
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+/venv
+/.servo-state
+/.idea \ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..780a816
--- /dev/null
+++ b/README.md
@@ -0,0 +1,28 @@
+# opi-mg996r
+
+This is a Python implementation of MG996R servo support for Orange Pi boards (H3 specifically, others untested).
+It does not use hardware PWM but instead emulates PWM with primitive `time.sleep()` calls, so don't expect any real-time
+accuracy. And, well, it's Python, after all.
+
+But it does its job and MG996R servo works just fine!
+
+## Usage
+
+- Clone the repo.
+- Install dependencies (see [here](requirements.txt)).
+- Check out [example.py](example.py) for an example.
+
+You can also just use it from command line like this:
+```
+./example.py --deg 0
+./example.py --deg 180
+./example.py --deg 90
+```
+
+## Credits
+
+The softpwm implementation was taken the [orangepwm](https://github.com/evergreen-it-dev/orangepwm) project.
+
+## License
+
+MIT \ No newline at end of file
diff --git a/example.py b/example.py
new file mode 100755
index 0000000..3ebfc6c
--- /dev/null
+++ b/example.py
@@ -0,0 +1,44 @@
+#!/usr/bin/env python3
+import os, logging
+from argparse import ArgumentParser
+from mg996r import MG996R
+
+default_degree = 360
+state_file = '.servo-state'
+
+if __name__ == '__main__':
+ # set logging level
+ logging.basicConfig(level=logging.DEBUG)
+
+ # parse arguments
+ parser = ArgumentParser()
+ parser.add_argument('--deg', type=int, required=True)
+ parser.add_argument('--pin', type=str, default='PA6',
+ help='GPIO pin to use')
+ parser.add_argument('--reset', action='store_true',
+ help=f'Use clean default state (degree = {default_degree})')
+ args = parser.parse_args()
+
+ start_degree = default_degree
+
+ # restore previous degree from a file
+ if not args.reset:
+ try:
+ if os.path.exists(state_file):
+ with open(state_file, 'r') as f:
+ start_degree = int(f.read())
+ if not 0 <= start_degree <= 360:
+ raise ValueError(f'invalid degree value in {state_file}')
+ except (IOError, ValueError) as e:
+ logging.exception(e)
+ start_degree = default_degree
+
+ servo = MG996R(args.pin, start_degree)
+ servo.move(args.deg)
+
+ # save degree to a file
+ try:
+ with open(state_file, 'w') as f:
+ f.write(str(args.deg))
+ except IOError as e:
+ logging.exception(e)
diff --git a/mg996r.py b/mg996r.py
new file mode 100644
index 0000000..cc70c24
--- /dev/null
+++ b/mg996r.py
@@ -0,0 +1,28 @@
+import logging
+
+from time import sleep
+from orangepwm import OrangePwm
+from pyA20.gpio import gpio
+from pyA20.gpio import port
+
+
+class MG996R:
+ def __init__(self, pin: str, prev: int = 360):
+ self.pwm = OrangePwm(50, getattr(port, pin))
+ self.prev = prev
+ gpio.init()
+
+ logging.debug(f'Initialized MG996R class with starting degree of {prev}')
+
+ def move(self, deg, delay: int = 0):
+ distance = abs(self.prev - deg)
+ sleep_time = distance / 60 * .5 + delay
+
+ duty = deg / 18 + 2
+ logging.debug(f'Moving to {deg} degrees, duty {duty:.0f}%, sleeping for {sleep_time:.1f} sec.')
+
+ self.prev = deg
+
+ self.pwm.start(duty)
+ sleep(sleep_time + delay)
+ self.pwm.stop()
diff --git a/orangepwm.py b/orangepwm.py
new file mode 100644
index 0000000..c874fcb
--- /dev/null
+++ b/orangepwm.py
@@ -0,0 +1,83 @@
+from pyA20.gpio import gpio as GPIO
+import threading
+import time
+
+
+class OrangePwm(threading.Thread):
+ def __init__(self, frequency, gpioPin, gpioScheme=0):
+ """
+ Init the OrangePwm instance. Expected parameters are :
+ - frequency : the frequency in Hz for the PWM pattern. A correct value may be 100.
+ - gpioPin : the gpio.port which will act as PWM ouput
+ - gpioScheme : saved for compatibility with PiZyPWM code
+ """
+ super().__init__()
+
+ self.baseTime = 1.0 / frequency
+ self.maxCycle = 100.0
+ self.sliceTime = self.baseTime / self.maxCycle
+ self.gpioPin = gpioPin
+ self.terminated = False
+ self.toTerminate = False
+ # GPIO.setmode(gpioScheme)
+
+ def start(self, dutyCycle):
+ """
+ Start PWM output. Expected parameter is :
+ - dutyCycle : percentage of a single pattern to set HIGH output on the GPIO pin
+
+ Example : with a frequency of 1 Hz, and a duty cycle set to 25, GPIO pin will
+ stay HIGH for 1*(25/100) seconds on HIGH output, and 1*(75/100) seconds on LOW output.
+ """
+ self.dutyCycle = dutyCycle
+ GPIO.setcfg(self.gpioPin, GPIO.OUTPUT)
+ self.thread = threading.Thread(None, self.run, None, (), {})
+ self.thread.start()
+
+ def run(self):
+ """
+ Run the PWM pattern into a background thread. This function should not be called outside of this class.
+ """
+ while self.toTerminate == False:
+ if self.dutyCycle > 0:
+ GPIO.output(self.gpioPin, GPIO.HIGH)
+ time.sleep(self.dutyCycle * self.sliceTime)
+
+ if self.dutyCycle < self.maxCycle:
+ GPIO.output(self.gpioPin, GPIO.LOW)
+ time.sleep((self.maxCycle - self.dutyCycle) * self.sliceTime)
+
+ self.terminated = True
+
+ def changeDutyCycle(self, dutyCycle):
+ """
+ Change the duration of HIGH output of the pattern. Expected parameter is :
+ - dutyCycle : percentage of a single pattern to set HIGH output on the GPIO pin
+
+ Example : with a frequency of 1 Hz, and a duty cycle set to 25, GPIO pin will
+ stay HIGH for 1*(25/100) seconds on HIGH output, and 1*(75/100) seconds on LOW output.
+ """
+ self.dutyCycle = dutyCycle
+
+ def changeFrequency(self, frequency):
+ """
+ Change the frequency of the PWM pattern. Expected parameter is :
+ - frequency : the frequency in Hz for the PWM pattern. A correct value may be 100.
+
+ Example : with a frequency of 1 Hz, and a duty cycle set to 25, GPIO pin will
+ stay HIGH for 1*(25/100) seconds on HIGH output, and 1*(75/100) seconds on LOW output.
+ """
+ self.baseTime = 1.0 / frequency
+ self.sliceTime = self.baseTime / self.maxCycle
+
+ def stop(self):
+ """
+ Stops PWM output.
+ """
+ self.toTerminate = True
+ while self.terminated == False:
+ # Just wait
+ time.sleep(0.01)
+
+ GPIO.output(self.gpioPin, GPIO.LOW)
+ GPIO.setcfg(self.gpioPin, GPIO.INPUT)
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..61490ac
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1 @@
+git+git://github.com/duxingkei33/orangepi_PC_gpio_pyH3@master