Files
WallE/control/pca9685/pca9685.py

177 lines
5.6 KiB
Python

#!/usr/bin/python
'''
**********************************************************************
* Filename : PCA9685.py
* Description : A driver module for PCA9685
* Author : Cavon
* Brand : SunFounder
* E-mail : service@sunfounder.com
* Website : www.sunfounder.com
* Version : v2.0.0
**********************************************************************
'''
from smbus2 import SMBus
import time
import math
import os
MOCK_ENV = 'PCA9685_MOCK_SMBUS'
class MockSMBus:
def __init__(self, bus_number):
self.bus_number = bus_number
self.data = {}
def write_byte_data(self, addr, res, value):
print(f"SMBus write {addr}[{res}] {value}")
if addr not in self.data:
self.data[addr] = {}
self.data[addr][res] = value if value else 0
def read_byte_data(self, addr, res):
print(f"SMBus read {addr}[{res}]")
if addr not in self.data:
self.data[addr] = {}
return self.data[addr][res] if res in self.data[addr] else 0
def map_range(x, in_min, in_max, out_min, out_max):
"""To map the value from arange to another"""
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min
class PWM:
"""A PWM control class for PCA9685."""
_MODE1 = 0x00
_MODE2 = 0x01
_SUBADR1 = 0x02
_SUBADR2 = 0x03
_SUBADR3 = 0x04
_PRESCALE = 0xFE
_LED0_ON_L = 0x06
_LED0_ON_H = 0x07
_LED0_OFF_L = 0x08
_LED0_OFF_H = 0x09
_ALL_LED_ON_L = 0xFA
_ALL_LED_ON_H = 0xFB
_ALL_LED_OFF_L = 0xFC
_ALL_LED_OFF_H = 0xFD
_RESTART = 0x80
_SLEEP = 0x10
_ALLCALL = 0x01
_INVRT = 0x10
_OUTDRV = 0x04
_DEBUG = False
_DEBUG_INFO = 'DEBUG "PCA9685.py":'
def __init__(self, bus_number=None, address=0x40):
self.address = address
if bus_number is None:
self.bus_number = 1
else:
self.bus_number = bus_number
if MOCK_ENV in os.environ and os.environ[MOCK_ENV] == 'true':
self.bus = MockSMBus(self.bus_number)
else:
self.bus = SMBus(self.bus_number)
self._frequency = 60
def _debug_(self, message):
if self._DEBUG:
print(self._DEBUG_INFO, message)
def setup(self):
'''Init the class with bus_number and address'''
self._debug_('Reseting PCA9685 MODE1 (without SLEEP) and MODE2')
self.write_all_value(0, 0)
self._write_byte_data(self._MODE2, self._OUTDRV)
self._write_byte_data(self._MODE1, self._ALLCALL)
time.sleep(0.005)
mode1 = self._read_byte_data(self._MODE1)
mode1 = mode1 & ~self._SLEEP
self._write_byte_data(self._MODE1, mode1)
time.sleep(0.005)
self._frequency = 60
def _write_byte_data(self, reg, value):
'''Write data to I2C with self.address'''
self._debug_('Writing value %2X to %2X' % (value, reg))
try:
self.bus.write_byte_data(self.address, reg, value)
except Exception as i:
print(i)
def _read_byte_data(self, reg):
'''Read data from I2C with self.address'''
self._debug_('Reading value from %2X' % reg)
try:
results = self.bus.read_byte_data(self.address, reg)
return results
except Exception as i:
print(i)
@property
def frequency(self):
return self._frequency
@frequency.setter
def frequency(self, freq):
"""Set PWM frequency"""
self._debug_('Set frequency to %d' % freq)
self._frequency = freq
prescale_value = 25000000.0
prescale_value /= 4096.0
prescale_value /= float(freq)
prescale_value -= 1.0
self._debug_('Setting PWM frequency to %d Hz' % freq)
self._debug_('Estimated pre-scale: %d' % prescale_value)
prescale = math.floor(prescale_value + 0.5)
self._debug_('Final pre-scale: %d' % prescale)
old_mode = self._read_byte_data(self._MODE1)
new_mode = (old_mode & 0x7F) | 0x10
self._write_byte_data(self._MODE1, new_mode)
self._write_byte_data(self._PRESCALE, int(math.floor(prescale)))
self._write_byte_data(self._MODE1, old_mode)
time.sleep(0.005)
self._write_byte_data(self._MODE1, old_mode | 0x80)
def write(self, channel, on, off):
"""Set on and off value on specific channel"""
self._debug_('Set channel "%d" to value "%d"' % (channel, off))
self._write_byte_data(self._LED0_ON_L + 4 * channel, on & 0xFF)
self._write_byte_data(self._LED0_ON_H + 4 * channel, on >> 8)
self._write_byte_data(self._LED0_OFF_L + 4 * channel, off & 0xFF)
self._write_byte_data(self._LED0_OFF_H + 4 * channel, off >> 8)
def write_all_value(self, on, off):
"""Set on and off value on all channel"""
self._debug_('Set all channel to value "%d"' % (off))
self._write_byte_data(self._ALL_LED_ON_L, on & 0xFF)
self._write_byte_data(self._ALL_LED_ON_H, on >> 8)
self._write_byte_data(self._ALL_LED_OFF_L, off & 0xFF)
self._write_byte_data(self._ALL_LED_OFF_H, off >> 8)
@property
def debug(self):
return self._DEBUG
@debug.setter
def debug(self, debug):
"""Set if debug information shows"""
if debug in (True, False):
self._DEBUG = debug
else:
raise ValueError('debug must be "True" (Set debug on) or "False" (Set debug off), not "{0}"'.format(debug))
if self._DEBUG:
print(self._DEBUG_INFO, "Set debug on")
else:
print(self._DEBUG_INFO, "Set debug off")