initial
This commit is contained in:
commit
dd54f14917
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
/venv
|
||||
/.servo-state
|
||||
/.idea
|
28
README.md
Normal file
28
README.md
Normal file
@ -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
|
44
example.py
Executable file
44
example.py
Executable file
@ -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)
|
28
mg996r.py
Normal file
28
mg996r.py
Normal file
@ -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()
|
83
orangepwm.py
Normal file
83
orangepwm.py
Normal file
@ -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)
|
1
requirements.txt
Normal file
1
requirements.txt
Normal file
@ -0,0 +1 @@
|
||||
git+git://github.com/duxingkei33/orangepi_PC_gpio_pyH3@master
|
Loading…
x
Reference in New Issue
Block a user