16-Channel 12-bit PWM/Servo Driver - I2C interface - PCA9685 |
The PCA9685 is produced by NXP Semiconductors and comes with a datasheet in pdf format 16-channel, 12-bit PWM Fm+ I2C-bus LED controller and some more documents at their webpage for the PCA9685|NPX. The interesting features as servo controller are:
-Supply voltage: 2.3 to 5.5V
-I2C bus speed: up to 1 MHz (fast mode)
-16 outputs that can be set to ON or OFF or PWM (12 bit)
-Internal clock (25 MHz) and prescaler (duty cycle between 24 Hz and 1526 Hz, default is 200 Hz)
-Optionally external clock (up to 50 MHz)
-Auto-increment of registers, allowing to write the PWM data for all outputs in a single I2C bus transfer
-PWM is programmed over I2C, no load on the main CPU
The chip has a default address of 100 0000b or 40h. There are six solder pads to change the zeros into ones on my PCB, allowing to configure a maximum of 63 different addresses that can be actually used (address 111 0000 can't be used, and within the 63 addresses there are many reserved addresses that can be used only because the chip doesn't comply to the I2C standard by responding to reserved addresses as well). In order to control rc servos there are one or two things to be configured. It is the pre-scaler, and optionally the auto-increment.
The pre-scaler is configured by writing the desired value to the PRE_SCALE register at address FEh while the chip is sleep mode, and it's done like this:
- Set bit 4 of MODE 1 register (stop all PWM output, stop the internal oscillator)
- Write the pre-scale value to the PRE_SCALE register
- Clear bit 4 of MODE 1 register (start the internal oscillator)
- Wait 500 us
- Set bit 7 of MODE 1 register (enable all PWM output)
PRE_SCALE = 25000000 Hz / (4096 * 50 Hz ) - 1
Auto-increment is configured by setting bit 5 in the MODE 1 register at address 00h, it is not implemented due to limitations in the I2C interface code.
That's it.
As the title suggests here's the AutoIT code "servotest.au3":
;PCA9685_TESTSUITEAnd here's the include file "CH341A_USB_to_I2C_helper.au3":
;Schematic:
;PC -USB- CH341A -I2C- PCA9685 -wire- Oscilloscope
AutoItSetOption("MustDeclareVars", 1)
#include "CH341A_USB_to_I2C_helper.au3"
CH341AInit()
;init PCA9685 (no-auto-increment due to limited driver capabilities)
CH341AWrite(0x40, 0, 16)
CH341AWrite(0x40, 0xFE, 121) ;
CH341AWrite(0x40, 0x00, 0)
Sleep(500)
CH341AWrite(0x40, 0x00, 128) ;restart, not needed (but in the datasheet)
CH341AWrite(0x40, 0x00, 0)
;set servo1 to max, servo2 to middle and servo3 to max
;It takes 20ms to pass 4096 ticks
;It takes 205 ticks for 1 ms (min), 307 ticks for 1.5 ms (middle) and 410 ticks for 2ms (max)
Local $loByte, $hiByte, $hex
$hex = Hex(205,4)
$hiByte = Dec(StringLeft($hex,2))
$loByte = Dec(StringRight($hex,2))
CH341AWrite(0x40, 0x06, 0)
CH341AWrite(0x40, 0x07, 0)
CH341AWrite(0x40, 0x08, $loByte)
CH341AWrite(0x40, 0x09, $hiByte)
$hex = Hex(307,4)
$hiByte = Dec(StringLeft($hex,2))
$loByte = Dec(StringRight($hex,2))
CH341AWrite(0x40, 0x0A, 0)
CH341AWrite(0x40, 0x0B, 0)
CH341AWrite(0x40, 0x0C, $loByte)
CH341AWrite(0x40, 0x0D, $hiByte)
$hex = Hex(410,4)
$hiByte = Dec(StringLeft($hex,2))
$loByte = Dec(StringRight($hex,2))
CH341AWrite(0x40, 0x0E, 0)
CH341AWrite(0x40, 0x0F, 0)
CH341AWrite(0x40, 0x10, $loByte)
CH341AWrite(0x40, 0x11, $hiByte)
Exit
;CH341A_USB_to_I2C_helperThis time no pictures.
;functions:
;CH341AInit()
; Initialize the CH341A chip for i2c
; No return value
;CH341ARead(i2cAddr, i2cRegister)
; Read the value of the given i2c chip and register address
;CH341AWrite(i2cAddr, i2cRegister, data)
; Write data to the given i2c chip and register address
#include <WinAPISys.au3>
;device ID
Global $CH341Aid = 0
Global $CH341Adll = DllOpen("C:\Windows\System32\CH341DLL.DLL")
Func CH341AInit()
;open the dll
If $CH341Adll <> -1 Then
ConsoleWrite("CH341AInit: DllOpen OK" & @CRLF)
Else
ConsoleWrite("CH341AInit: DllOpen error, could not open the dll." & @CRLF)
ConsoleWrite("CH341AInit: FATAL ERROR. EXIT." & @CRLF)
Exit
EndIf
;open the device
Local $aResult = DllCall($CH341Adll, "BOOL", "CH341OpenDevice", "ULONG", $CH341Aid)
If $aResult[0] <> -1 Then
ConsoleWrite("CH341AInit: CH341OpenDevice OK" & @CRLF)
Else
ConsoleWrite("CH341AInit: CH341OpenDevice error: Could not open device " & @CRLF)
ConsoleWrite("CH341AInit: FATAL ERROR. EXIT." & @CRLF)
Exit
EndIf
;set the I2C speed
Local $iMode = 0
Local $aResult = DllCall($CH341Adll, "BOOL", "CH341SetStream", "ULONG", $CH341Aid, "ULONG", $iMode)
If $aResult[0] Then
ConsoleWrite("CH341AInit: CH341SetStream: OK, ")
ConsoleWrite("Param: DeviceID="& $CH341Aid & ", Moe=" & $iMode & @CRLF)
Else
ConsoleWrite("CH341AInit: CH341SetStream: Error" & @CRLF)
ConsoleWrite("CH341AInit: FATAL ERROR. EXIT." & @CRLF)
Exit
EndIf
EndFunc
Func CH341ARead($i2cAddr, $i2cRegister)
Local $sBYTE_r4 = DllStructCreate ("BYTE[4]" )
Local $pBYTE_r4 = DllStructGetPtr($sBYTE_r4)
Local $aResult = DllCall($CH341Adll, "BOOL", "CH341ReadI2C", "BYTE", $CH341Aid, _
"BYTE", $i2cAddr, "BYTE", $i2cRegister, "PTR", $pBYTE_r4)
If $aResult[0] Then
ConsoleWrite("CH341ReadI2C.: OK, ")
ConsoleWrite("Param: $CH341Aid="& $CH341Aid & ", $i2cAddr=" & $i2cAddr & _
", $i2cRegister=" & $i2cRegister & _
", $sBYTE_r4=" & DllStructGetData($sBYTE_r4, 1) & " ")
ConsoleWrite("(" & Dec(StringMid(BinaryMid(DllStructGetData($sBYTE_r4, 1), 1, 1), 3)) & ")" & @CRLF)
Return DllStructGetData($sBYTE_r4, 1)
Else
ConsoleWrite("CH341ReadI2C.: Error" & @CRLF)
ConsoleWrite("CH341ReadI2C.: Param: $CH341Aid="& $CH341Aid & ", $i2cAddr=" & $i2cAddr & _
", $i2cRegister=" & $i2cRegister & _
", $sBYTE_r4=" & DllStructGetData($sBYTE_r4, 1) & @CRLF)
Return -1
EndIf
EndFunc
Func CH341AWrite($i2cAddr, $i2cRegister, $data)
Local $aResult = DllCall($CH341Adll, "BOOL", "CH341WriteI2C", "BYTE", $CH341Aid, _
"BYTE", $i2cAddr, "BYTE", $i2cRegister, "BYTE", $data)
If $aResult[0] Then
ConsoleWrite("CH341WriteI2C: OK, ")
ConsoleWrite("Param: $CH341Aid="& $CH341Aid & ", $i2cAddr=" & $i2cAddr & ", $i2cRegister=" & $i2cRegister & ", $data=" & $data & @CRLF)
Return 0
Else
ConsoleWrite("CH341WriteI2C: Error" & @CRLF)
ConsoleWrite("CH341WriteI2C: Param: $CH341Aid="& $CH341Aid & ", $i2cAddr=" & $i2cAddr & ", $i2cRegister=" & $i2cRegister & ", =$data" & $data & @CRLF)
Return -1
EndIf
EndFunc
No comments:
Post a Comment