@@ -382,7 +382,7 @@ def pwm_pin(
382
382
if pin_provider == PinProvider .RPI_GPIO :
383
383
return PwmPinGpio (pin_number , pin_scheme , frequency_hz )
384
384
if pin_provider == PinProvider .PCA9685 :
385
- return PwmPinPCA9685 (pin_number , pca9685 ( i2c_bus , i2c_address , frequency_hz ) )
385
+ return PwmPinPCA9685 (pin_number , i2c_bus , i2c_address , frequency_hz )
386
386
if pin_provider == PinProvider .PIGPIO :
387
387
if pin_scheme != PinScheme .BCM :
388
388
raise ValueError ("Pin scheme must be PinScheme.BCM for PIGPIO" )
@@ -560,61 +560,85 @@ def duty_cycle(self, duty: float) -> None:
560
560
#
561
561
# ----- PCA9685 implementations -----
562
562
#
563
- class PCA9685 :
563
+ class PCA9685board :
564
564
'''
565
- Pin controller using PCA9685 boards.
566
- This is used for most RC Cars. This
567
- driver can output ttl HIGH or LOW or
568
- produce a duty cycle at the given frequency.
565
+ Adapter over PCA9685 driver.
566
+ It initializes the PCA9685 board at the given busnum:address
567
+ to produce pulses at the given frequency in hertz.
568
+ >> NOTE: The busnum argument is now ignored.
569
+ >> The I2C bus device is auto detected using
570
+ >> Adafruit Blinka board interface, which is
571
+ >> implemented for RaspberryPi, but may not be
572
+ >> implemented for other SBCs.
573
+ >> See [boards.py](https://github.com/adafruit/Adafruit_Blinka/blob/main/src/board.py)
574
+ >> and [busio.py](https://github.com/adafruit/Adafruit_Blinka/blob/b3beba7399bc4d5faa203ef705691ef79fc4e87f/src/busio.py#L29)
569
575
'''
570
- def __init__ (self , busnum : int , address : int , frequency : int ):
576
+ def __init__ (self , busnum : int = 1 , address : int = 0x40 , frequency : int = 60 ):
577
+ import board
578
+ import busio
579
+ import adafruit_pca9685
580
+ i2c = busio .I2C (board .SCL , board .SDA )
581
+ self .pca9685driver = adafruit_pca9685 .PCA9685 (i2c , address = address )
582
+ self .pca9685driver .frequency = frequency
583
+ self .busnum = busnum
584
+ self .address = address
585
+ self ._frequency = frequency
571
586
572
- import Adafruit_PCA9685
573
- if busnum is not None :
574
- from Adafruit_GPIO import I2C
587
+ def get_frequency (self ) -> int :
588
+ return self ._frequency
575
589
576
- # monkey-patch I2C driver to use our bus number
577
- def get_bus ():
578
- return busnum
590
+ class PCA9685Pin :
591
+ '''
592
+ Adapter over PCA9685 pin driver.
593
+ This can output ttl HIGH or LOW or
594
+ produce a duty cycle at the given frequency.
595
+ '''
596
+ def __init__ (self , channel : int , busnum : int = 1 , address : int = 0x40 , frequency : int = 60 ):
579
597
580
- I2C . get_default_bus = get_bus
581
- self .pwm = Adafruit_PCA9685 . PCA9685 ( address = address )
582
- self .pwm . set_pwm_freq ( frequency )
583
- self ._frequency = frequency
598
+ import adafruit_pca9685
599
+ self .pca = pca9685 ( busnum , address , frequency )
600
+ self .pca_pin = adafruit_pca9685 . PWMChannel ( self . pca . pca9685driver , channel )
601
+ self .channel = channel
584
602
585
- def get_frequency (self ):
586
- return self ._frequency
603
+ def get_frequency (self ) -> int :
604
+ return self .pca . get_frequency ()
587
605
588
- def set_high (self , channel : int ):
589
- self .pwm .set_pwm (channel , 4096 , 0 )
606
+ def set_high (self ):
607
+ # adafruit blinka uses 16bit values
608
+ # where 0xFFFF is a flag that means fully high
609
+ self .pca_pin .duty_cycle = 0xFFFF
590
610
591
- def set_low (self , channel : int ):
592
- self .pwm .set_pwm (channel , 0 , 4096 )
611
+ def set_low (self ):
612
+ # adafruit blinka uses 16bit values
613
+ # where 0x0000 is a flag that means fully low
614
+ self .pca_pin .duty_cycle = 0x0000
593
615
594
- def set_duty_cycle (self , channel : int , duty_cycle : float ):
616
+ def set_duty_cycle (self , duty_cycle : float ):
595
617
if duty_cycle < 0 or duty_cycle > 1 :
596
618
raise ValueError ("duty_cycle must be in range 0 to 1" )
597
619
if duty_cycle == 1 :
598
- self .set_high (channel )
620
+ self .set_high ()
599
621
elif duty_cycle == 0 :
600
- self .set_low (channel )
622
+ self .set_low ()
601
623
else :
602
- # duty cycle is fraction of the 12 bits
603
- pulse = int (4096 * duty_cycle )
624
+ # adafruit blinka uses 16 bit values
625
+ # for resolution of duty cycle.
626
+ pulse = int (0x10000 * duty_cycle )
604
627
try :
605
- self .pwm . set_pwm ( channel , 0 , pulse )
628
+ self .pca_pin . duty_cycle = pulse
606
629
except Exception as e :
607
- logger .error (f'Error on PCA9685 channel { channel } : { str (e )} ' )
630
+ logger .error (f'Error on PCA9685 channel { self . channel } : { str (e )} ' )
608
631
609
632
610
633
#
611
634
# lookup map for PCA9685 singletons
612
635
# key is "busnum:address"
613
636
#
614
637
_pca9685 = {}
638
+ _pca9685pin = {}
615
639
616
640
617
- def pca9685 (busnum : int , address : int , frequency : int = 60 ):
641
+ def pca9685 (busnum : int , address : int , frequency : int = 60 ) -> PCA9685board :
618
642
"""
619
643
pca9685 factory allocates driver for pca9685
620
644
at given bus number and i2c address.
@@ -632,21 +656,46 @@ def pca9685(busnum: int, address: int, frequency: int = 60):
632
656
key = str (busnum ) + ":" + hex (address )
633
657
pca = _pca9685 .get (key )
634
658
if pca is None :
635
- pca = PCA9685 (busnum , address , frequency )
659
+ pca = PCA9685board (busnum , address , frequency )
660
+ _pca9685 [key ] = pca
636
661
if pca .get_frequency () != frequency :
637
662
raise ValueError (
638
663
f"Frequency { frequency } conflicts with pca9685 at { key } "
639
- f"with frequency { pca .pwm . get_pwm_freq ()} " )
664
+ f"with frequency { pca .get_frequency ()} " )
640
665
return pca
641
666
642
667
643
- class OutputPinPCA9685 (ABC ):
668
+ def pca9685pin (channel : int , busnum : int , address : int , frequency : int = 60 ) -> PCA9685Pin :
669
+ """
670
+ pca9685 pin factory allocates driver for pin on channel on pca9685
671
+ at given bus number and i2c address.
672
+ If we have already created one for that bus/addr/channel
673
+ pair then use that singleton. If frequency is
674
+ not the same, then error.
675
+ :param channel: PCA9685 channel 0..15 to control
676
+ :param busnum: I2C bus number of PCA9685
677
+ :param address: address of PCA9685 on I2C bus
678
+ :param frequency: frequency in hertz of duty cycle
679
+ :except: PCA9685 has a single frequency for all channels,
680
+ so attempts to allocate a controller at a
681
+ given bus number and address with different
682
+ frequencies will raise a ValueError
683
+ """
684
+ key = str (busnum ) + ":" + hex (address ) + ":" + str (channel )
685
+ pca_pin = _pca9685pin .get (key )
686
+ if pca_pin is None :
687
+ pca_pin = PCA9685Pin (channel , busnum , address , frequency )
688
+ _pca9685pin [key ] = pca_pin
689
+ return pca_pin
690
+
691
+
692
+ class OutputPinPCA9685 (OutputPin ):
644
693
"""
645
694
Output pin ttl HIGH/LOW using PCA9685
646
695
"""
647
- def __init__ (self , pin_number : int , pca9685 : PCA9685 ) -> None :
696
+ def __init__ (self , pin_number : int , busnum : int , address : int , frequency : int = 60 ) -> None :
648
697
self .pin_number = pin_number
649
- self .pca9685 = pca9685
698
+ self .pca_pin = pca9685pin ( pin_number , busnum , address , frequency )
650
699
self ._state = PinState .NOT_STARTED
651
700
652
701
def start (self , state : int = PinState .LOW ) -> None :
@@ -688,19 +737,19 @@ def output(self, state: int) -> None:
688
737
if self .state () == PinState .NOT_STARTED :
689
738
raise RuntimeError (f"Attempt to use pin ({ self .pin_number } ) that is not started" )
690
739
if state == PinState .HIGH :
691
- self .pca9685 .set_high (self . pin_number )
740
+ self .pca_pin .set_high ()
692
741
else :
693
- self .pca9685 .set_low (self . pin_number )
742
+ self .pca_pin .set_low ()
694
743
self ._state = state
695
744
696
745
697
746
class PwmPinPCA9685 (PwmPin ):
698
747
"""
699
748
PWM output pin using PCA9685
700
749
"""
701
- def __init__ (self , pin_number : int , pca9685 : PCA9685 ) -> None :
750
+ def __init__ (self , pin_number : int , busnum : int , address : int , frequency : int = 60 ) -> None :
702
751
self .pin_number = pin_number
703
- self .pca9685 = pca9685
752
+ self .pca_pin = pca9685pin ( pin_number , busnum , address , frequency )
704
753
self ._state = PinState .NOT_STARTED
705
754
706
755
def start (self , duty : float = 0 ) -> None :
@@ -739,7 +788,7 @@ def duty_cycle(self, duty: float) -> None:
739
788
raise RuntimeError (f"Attempt to use pin ({ self .pin_number } ) that is not started" )
740
789
if duty < 0 or duty > 1 :
741
790
raise ValueError ("duty_cycle must be in range 0 to 1" )
742
- self .pca9685 .set_duty_cycle (self . pin_number , duty )
791
+ self .pca_pin .set_duty_cycle (duty )
743
792
self ._state = duty
744
793
745
794
0 commit comments