PCA9685 16-Channel Servo Drive Module
When you encounter a situation in a project where the PWM output pins of the microcontroller chip are not enough, this PCA9685 16-channel servo driver can help you solve this problem quickly. As long as your main control chip supports I2C communication, the main control chip can communicate with the PCA9685 to achieve simultaneous control of multiple servos. The PCA9685 16-channel servo driver uses I2C communication and has a built-in PWM driver and a clock, which means it will be very different from the TLC5940 series; you do not need to continuously send signals that occupy your microcontroller. It is 5V compatible, which means you can also use a 3.3V microcontroller to control and safely drive up to 6V output (you can also use 3.4V+ positive voltage when you want to control white or blue indicators). The address selection pins allow you to connect 62 driver boards on a single I2C bus, for a total of 992 PWM outputs, which is a huge resource. It has an adjustable frequency PWM output of approximately 1.6 kHz, 12-bit resolution output for stepper motors, configurable push-pull output or open-drain output, and an output enable pin that can quickly disable all outputs.
Module Source
Purchase link:
16-Channel PWM Servo Driver Board Robot Controller IIC Interface Driver Module PCA9685
Materials download:
https://pan.baidu.com/s/1FjoAuJm387bxaZxS6g9HEg
Extraction code: 8888
Specifications
Input voltage: 3.3V~5V
Rated current: 15mA
Control method: Serial port
Dimensions: 21(Length)*21(Width)[Unit: mm]
View Materials
I2C Device Address
The PCA9685 is an I2C slave device with a device ID, also called a slave address. The slave address is determined as follows: Board 0: Address = 0×40 Offset = binary 00000 (Default) Board 1: Address = 0×41 Offset = binary 00001 (A0 pulled up) Board 2: Address = 0×42 Offset = binary 00010 (A1 pulled up) Board 3: Address = 0×43 Offset = binary 00011 (A0 and A1 pulled up) Board 4: Address = 0×44 Offset = binary 00100 (A2 pulled up) And so on.
The I2C bus slave address of the PCA9685 is shown in the figure below. To save power, the hardware-configurable address pins do not have internal pull-up resistors; they must be pulled high or low. However, we are using a module, and the module has already connected the pull-up resistors for us.
The last bit of the address byte defines the operation to be performed. When set to logic 1, the read operation is selected, and logic 0 selects the write operation.
In the schematic, all address lines are connected to 0, so the slave address is 0x40. Corresponding to the position in Fig 4, it is:
So the I2C address is 0x80, which is 0x80 when writing and 0x81 when reading.
Set PWM Frequency
The PWM period required for servo control is 20 ms. When using the PCA9685 as a multi-servo controller, you need to set its PWM output period to 20 ms, that is, set the PWM wave frequency to 50 Hz. The PCA9685 output frequency is related to the oscillator, and the setting value refresh_rate of the frequency is shown in the formula below;
Where EXTCLK is the PCA9685 internal clock frequency of 25 MHz; prescale is the frequency to be set, we set it to 50 Hz;
refresh_rate = 25,000,000 /( 4096 * ( 50 + 1 )) refresh_rate = 25,000,000 / 4096 / (50 + 1) refresh_rate = 6,103.52 / (50 + 1) refresh_rate = 6,103.52 / 51 refresh_rate = 119.68
So the value we need to set is 119.68, and rounded to an integer it is 120.
Note that the frequency can only be changed when the PCA9685 chip is in sleep mode.
The following bold text is from the data sheet:
To use the EXTCLK pin, the bit must be set in the following order:
Set the SLEEP bit in mode1. This turns off the internal oscillator and puts the chip in sleep mode.
Write logic 1 to the SLEEP and EXTCLK bits in MODE1. This completes the switch. The external clock can be active during the switching period because the SLEEP bit is set.
This bit is a "sticky bit", meaning it cannot be cleared by writing logic 0. The EXTCLK bit can only be cleared by power cycling or a software reset.
Duty Cycle or Pulse Width Setting
The turn-on time of each PWM pin output and the duty cycle of the PWM can be independently controlled through the LEDn_ON and LEDn_OFF registers.
Each PWM pin output will have two 12-bit registers. These registers are programmed by the user. Both registers will hold values from 0 to 4095. One 12-bit register holds the value of the ON time, and the other 12-bit register holds the value of the OFF time. The ON and OFF times are compared with the value of a 12-bit counter that runs continuously from 0000h to 0FFFh (0 to 4095 in decimal).
The ON time is programmable; it is the time the PWM output is ON, and the OFF time is also programmable; it is the time the PWM output is OFF. This makes the phase shift fully programmable. The resolution of the phase shift is 1/4096 of the target frequency. Table 7 lists these registers.
The following example illustrates how to calculate the values to be loaded into these registers.
(Assuming LED0 output is used, (delay time) + (PWM duty cycle) <= 100%)
Delay time = 10%; PWM duty cycle = 20% (LED ON level = 20%; LED OFF time = 80%). Delay time = 10% = 4096 * 0.1 = 409.6 ~ 410, count = 410 (decimal) = 19A (hex)
Because the counter starts from 0 and ends at 4095, we will subtract 1, so the delay time = 199h counts.
LED0_ON_H = 1h; LED0_ON_L = 99h (After the LED starts to turn on, this delay counts to 409)
LED turn-on time = 20% = 819.2 ~ 819 counts
LED turn-off time = 4CCh (decimal 410 + 819-1 = 1228)
LED0_OFF_H = 4h; LED0_OFF_L = CCh (After counting to 1228, the LED starts to turn off)
The entire period is 4095. The two set values of LED_ON and LED_OFF determine the pulse width. In the code below, LED_ON is set to 0, and LED_OFF is the pulse width. Both are represented here in 2-byte format.
Related Address Table
Only the required addresses are captured here, which are:
#define PCA\_Addr 0x80 //IIC address
#define PCA\_Model 0x00
#define LED0\_ON\_L 0x06
#define LED0\_ON\_H 0x07
#define LED0\_OFF\_L 0x08
#define LED0\_OFF\_H 0x09
#define PCA\_Pre 0xFE //Frequency configuration address2
3
4
5
6
7
Porting Process
Pin Selection
Port to Project
Our goal is to port the example to the ESP32-S3 dev board. Complete driver code has been provided for you. Follow the steps below to complete the porting.
For detailed instructions on creating folders and new .c and .h files, refer to the [DHT11 Temperature and Humidity Sensor] chapter; we will not repeat it here.
Just note that here we change the file names bsp_dht11.c and bsp_dht11.h to bsp_pca9685.c and bsp_pca9685.h, and the folder name to PCA9685.
Write Code
In the file bsp_pca9685.c, write the following code.
/*
* LCSC-Openkits (LCKFB) software and hardware materials and related expansion board software and hardware materials are all open source on the official website.
* Dev board official website: www.lckfb.com
* Technical support resides on the forum; any technical questions are welcome for exchange and learning at any time.
* LCKFB Forum: club.szlcsc.com
* Follow our Bilibili account: [LCSC-Openkits (LCKFB)] to keep up with our latest updates!
* We do not make money by selling boards; we take cultivating engineers as our mission.
* Change Logs:
* Date Author Notes
* 2024-01-16 LCKFB-lp first version
*/
#include "bsp_pca9685.h"
#include "stdio.h"
#include <math.h>
void delay_ms(unsigned int ms)
{
vTaskDelay(ms / portTICK_PERIOD_MS);
}
void delay_us(unsigned int us)
{
ets_delay_us(us);
}
void delay_1ms(unsigned int ms)
{
vTaskDelay(ms / portTICK_PERIOD_MS);
}
void delay_1us(unsigned int us)
{
ets_delay_us(us);
}
/******************************************************************
* Function Name: PCA9685_GPIO_Init
* Function Description: Initialize the PCA9685 pins
* Function Parameters: None
* Function Return: None
* Author: LC
* Notes: None
******************************************************************/
void PCA9685_GPIO_Init(void)
{
gpio_config_t OUT_config = {
.pin_bit_mask = (1ULL<<GPIO_SDA)|(1ULL<<GPIO_SCL), // Configure pins
.mode =GPIO_MODE_OUTPUT, // Output mode
.pull_up_en = GPIO_PULLUP_DISABLE, // Disable pull-up
.pull_down_en = GPIO_PULLDOWN_DISABLE, // Disable pull-down
.intr_type = GPIO_INTR_DISABLE // Disable pin interrupt
};
gpio_config(&OUT_config);
}
/******************************************************************
* Function Name: IIC_Start
* Function Description: IIC start sequence
* Function Parameters: None
* Function Return: None
* Author: LC
* Notes: None
******************************************************************/
void IIC_Start(void)
{
SDA_OUT();
SDA(1);
delay_us(5);
SCL(1);
delay_us(5);
SDA(0);
delay_us(5);
SCL(0);
delay_us(5);
}
/******************************************************************
* Function Name: IIC_Stop
* Function Description: IIC stop signal
* Function Parameters: None
* Function Return: None
* Author: LC
* Notes: None
******************************************************************/
void IIC_Stop(void)
{
SDA_OUT();
SCL(0);
SDA(0);
SCL(1);
delay_us(5);
SDA(1);
delay_us(5);
}
/******************************************************************
* Function Name: IIC_Send_Ack
* Function Description: The master sends an acknowledge or non-acknowledge signal
* Function Parameters: 0 sends acknowledge 1 sends non-acknowledge
* Function Return: None
* Author: LC
* Notes: None
******************************************************************/
void IIC_Send_Ack(unsigned char ack)
{
SDA_OUT();
SCL(0);
SDA(0);
delay_us(5);
if(!ack) SDA(0);
else SDA(1);
SCL(1);
delay_us(5);
SCL(0);
SDA(1);
}
/******************************************************************
* Function Name: I2C_WaitAck
* Function Description: Wait for slave acknowledge
* Function Parameters: None
* Function Return: 0 acknowledged 1 timeout no acknowledge
* Author: LC
* Notes: None
******************************************************************/
unsigned char I2C_WaitAck(void)
{
char ack = 0;
unsigned char ack_flag = 10;
SCL(0);
SDA(1);
SDA_IN();
delay_us(5);
SCL(1);
delay_us(5);
while( (SDA_GET()==1) && ( ack_flag ) )
{
ack_flag--;
delay_us(5);
}
if( ack_flag <= 0 )
{
IIC_Stop();
return 1;
}
else
{
SCL(0);
SDA_OUT();
}
return ack;
}
/******************************************************************
* Function Name: Send_Byte
* Function Description: Write one byte
* Function Parameters: dat data to write
* Function Return: None
* Author: LC
* Notes: None
******************************************************************/
void Send_Byte(uint8_t dat)
{
int i = 0;
SDA_OUT();
SCL(0);//Pull clock low to start data transfer
for( i = 0; i < 8; i++ )
{
SDA( (dat & 0x80) >> 7 );
delay_us(1);
SCL(1);
delay_us(5);
SCL(0);
delay_us(5);
dat<<=1;
}
}
/******************************************************************
* Function Name: Read_Byte
* Function Description: IIC read sequence
* Function Parameters: None
* Function Return: Data read
* Author: LC
* Notes: None
******************************************************************/
unsigned char Read_Byte(void)
{
unsigned char i,receive=0;
SDA_IN();//Set SDA as input
for(i=0;i<8;i++ )
{
SCL(0);
delay_us(5);
SCL(1);
delay_us(5);
receive<<=1;
if( SDA_GET() )
{
receive|=1;
}
delay_us(5);
}
SCL(0);
return receive;
}
/******************************************************************
* Function Name: PCA9685_Write
* Function Description: Write command or data to PCA9685
* Function Parameters: addr register address to write data command or data to write
* Function Return: None
* Author: LC
* Notes: None
******************************************************************/
void PCA9685_Write(uint8_t addr,uint8_t data)
{
IIC_Start();
Send_Byte(PCA_Addr);
I2C_WaitAck();
Send_Byte(addr);
I2C_WaitAck();
Send_Byte(data);
I2C_WaitAck();
IIC_Stop();
}
/******************************************************************
* Function Name: PCA9685_Read
* Function Description: Read PCA9685 data
* Function Parameters: addr register address to read
* Function Return: Data read
* Author: LC
* Notes: None
******************************************************************/
uint8_t PCA9685_Read(uint8_t addr)
{
uint8_t data;
IIC_Start();
Send_Byte(PCA_Addr);
I2C_WaitAck();
Send_Byte(addr);
I2C_WaitAck();
IIC_Stop();
delay_us(10);
IIC_Start();
Send_Byte(PCA_Addr|0x01);
I2C_WaitAck();
data = Read_Byte();
IIC_Send_Ack(1);
IIC_Stop();
return data;
}
/******************************************************************
* Function Name: PCA9685_setPWM
* Function Description: Set the num-th PWM pin, on defaults to 0, control servo rotation off angle
* Function Parameters: num: which pin to set for output, range 0~15
* on: defaults to 0
* off: servo rotation angle, range: 0~180
* Function Return: None
* Author: LC
* Notes: None
******************************************************************/
void PCA9685_setPWM(uint8_t num,uint32_t on,uint32_t off)
{
IIC_Start();
Send_Byte(PCA_Addr);
I2C_WaitAck();
Send_Byte(LED0_ON_L+4*num);
I2C_WaitAck();
Send_Byte(on&0xFF);
I2C_WaitAck();
Send_Byte(on>>8);
I2C_WaitAck();
Send_Byte(off&0xFF);
I2C_WaitAck();
Send_Byte(off>>8);
I2C_WaitAck();
IIC_Stop();
}
/******************************************************************
* Function Name: PCA9685_setFreq
* Function Description: Set the output frequency of PCA9685
* Function Parameters: freq
* Function Return: None
* Author: LC
* Notes:
floor syntax:
FLOOR(number, significance)
Number required. The value to be rounded.
Significance required. The multiple to round to.
Description
Rounds the number argument down (toward zero) to the nearest multiple of significance.
If any argument is non-numeric, FLOOR returns the #VALUE! error value.
If the sign of number is positive and the sign of significance is negative, FLOOR returns the #NUM! error value.
Example
Formula Description Result
FLOOR(3.7,2) Rounds 3.7 down to the nearest multiple of 2 2
FLOOR(-2.5, -2) Rounds -2.5 down to the nearest multiple of -2 -2
******************************************************************/
void PCA9685_setFreq(float freq)
{
uint8_t prescale,oldmode,newmode;
double prescaleval;
// freq *= 0.9; // Correct for overshoot in the frequency setting (see issue #11).
// The internal clock frequency of PCA9685 is 25MHz
// Formula: presale_Volue = round( 25000000/(4096 * update_rate) ) - 1
// round = floor(); floor is a math function, you need to include math.h
// update_rate = freq;
prescaleval = 25000000;
prescaleval /= 4096;
prescaleval /= freq;
prescaleval -= 1;
prescale = floor(prescaleval+0.5f);
//Return the content at MODE1 address (protect other content)
oldmode = PCA9685_Read(PCA_Model);
//Set the SLEEP bit in MODE1
newmode = (oldmode&0x7F)|0x10;
//Write the modified MODE1 value to the MODE1 address to put the chip to sleep
PCA9685_Write(PCA_Model,newmode);
//Write the calculated frequency setting value
//PCA_Pre = presale address is 0xFE, which can be found in the data sheet
PCA9685_Write(PCA_Pre,prescale);
//Reset again
PCA9685_Write(PCA_Model,oldmode);
//Wait for reset to complete
delay_1ms(5);
//Set MODE1 register to enable auto-increment
PCA9685_Write(PCA_Model,oldmode|0xa1);
}
/******************************************************************
* Function Name: setAngle
* Function Description: Set angle
* Function Parameters: num PWM pin to set angle angle to set
* Function Return: None
* Author: LC
* Notes: None
******************************************************************/
void setAngle(uint8_t num,uint8_t angle)
{
uint32_t off = 0;
off = (uint32_t)(158+angle*2.2);
PCA9685_setPWM(num,0,off);
}
/******************************************************************
* Function Name: PCA9685_Init
* Function Description: PCA9685 initialization, configure all PWM output frequencies and servo angles of all PWM pin outputs
* Function Parameters: hz initial frequency to set angle initial angle to set
* Function Return: None
* Author: LC
* Notes: None
******************************************************************/
void PCA9685_Init(float hz,uint8_t angle)
{
uint32_t off = 0;
PCA9685_GPIO_Init();
//Write 0x00 to MODE1 address
PCA9685_Write(PCA_Model,0x00); //This step is critical; without it, the PCA9685 will not work properly.
// The pwm.setPWMFreq(SERVO_FREQ) function mainly sets the output frequency of PCA9685.
// The 16-channel PWM output frequency of PCA9685 is the same, so it cannot achieve different frequencies for different pins.
// Below is the content of the setPWMFreq function, which mainly calculates the PRE_SCALE value based on the frequency.
PCA9685_setFreq(hz);
//Calculate angle
off = (uint32_t)(145+angle*2.4);
//Control 16 servos to output off angle
PCA9685_setPWM(0,0,off);
PCA9685_setPWM(1,0,off);
PCA9685_setPWM(2,0,off);
PCA9685_setPWM(3,0,off);
PCA9685_setPWM(4,0,off);
PCA9685_setPWM(5,0,off);
PCA9685_setPWM(6,0,off);
PCA9685_setPWM(7,0,off);
PCA9685_setPWM(8,0,off);
PCA9685_setPWM(9,0,off);
PCA9685_setPWM(10,0,off);
PCA9685_setPWM(11,0,off);
PCA9685_setPWM(12,0,off);
PCA9685_setPWM(13,0,off);
PCA9685_setPWM(14,0,off);
PCA9685_setPWM(15,0,off);
delay_1ms(100);
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
In the file bsp_pca9685.h, write the following code.
/*
* LCSC-Openkits (LCKFB) software and hardware materials and related expansion board software and hardware materials are all open source on the official website.
* Dev board official website: www.lckfb.com
* Technical support resides on the forum; any technical questions are welcome for exchange and learning at any time.
* LCKFB Forum: club.szlcsc.com
* Follow our Bilibili account: [LCSC-Openkits (LCKFB)] to keep up with our latest updates!
* We do not make money by selling boards; we take cultivating engineers as our mission.
* Change Logs:
* Date Author Notes
* 2024-01-16 LCKFB-lp first version
*/
#ifndef _BSP_PCA9685_H_
#define _BSP_PCA9685_H_
#include <stdio.h>
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/uart.h"
#include "driver/gpio.h"
#include "driver/i2c.h"
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "freertos/queue.h"
#include <inttypes.h>
#include "sdkconfig.h"
#include "driver/gpio.h"
#include "esp_log.h"
#include "rom/ets_sys.h"
#include "esp_system.h"
#include "driver/gpio.h"
#include "driver/spi_master.h"
#include "driver/spi_common.h"
#include "hal/gpio_types.h"
#include "driver/ledc.h"
#include "driver/mcpwm.h"
#include "esp_adc/adc_cali_scheme.h"
#include "esp_adc/adc_cali.h"
#include "driver/adc.h"
#include "esp_adc_cal.h"
#include "string.h"
//Port porting
#define GPIO_SDA 2
#define GPIO_SCL 1
//Set SDA output mode
#define SDA_OUT() gpio_set_direction(GPIO_SDA,GPIO_MODE_OUTPUT)
//Set SDA input mode
#define SDA_IN() gpio_set_direction(GPIO_SDA,GPIO_MODE_INPUT)
//Get the level change of SDA pin
#define SDA_GET() gpio_get_level(GPIO_SDA)
//SDA and SCL output
#define SDA(x) gpio_set_level(GPIO_SDA, (x?1:0))
#define SCL(x) gpio_set_level(GPIO_SCL, (x?1:0))
#define PCA_Addr 0x80 //IIC address
#define PCA_Model 0x00
#define LED0_ON_L 0x06
#define LED0_ON_H 0x07
#define LED0_OFF_L 0x08
#define LED0_OFF_H 0x09
#define PCA_Pre 0xFE //Frequency configuration address
void PCA9685_Init(float hz,uint8_t angle);
void setAngle(uint8_t num,uint8_t angle);
void PCA9685_setFreq(float freq);
void PCA9685_setPWM(uint8_t num,uint32_t on,uint32_t off);
#endif2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
Porting Verification
In the main function of your project, write the following code.
/*
* LCSC-Openkits (LCKFB) software and hardware materials and related expansion board software and hardware materials are all open source on the official website.
* Dev board official website: www.lckfb.com
* Technical support resides on the forum; any technical questions are welcome for exchange and learning at any time.
* LCKFB Forum: club.szlcsc.com
* Follow our Bilibili account: [LCSC-Openkits (LCKFB)] to keep up with our latest updates!
* We do not make money by selling boards; we take cultivating engineers as our mission.
* Change Logs:
* Date Author Notes
* 2024-01-16 LCKFB-lp first version
*/
#include <stdio.h>
#include "bsp_pca9685.h"
#include "string.h"
#include "esp_private/esp_task_wdt.h"
#include "esp_private/esp_task_wdt_impl.h"
int app_main(void)
{
uint8_t i = 0;
esp_task_wdt_deinit();
PCA9685_Init(60 , 0);
vTaskDelay(1000 / portTICK_PERIOD_MS);
printf("Demo Start......\r\n");
while(1)
{
i = ( i + 1 ) % 180;
setAngle(0,i);
vTaskDelay(50 / portTICK_PERIOD_MS);
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
Power-on effect:
Driver file:
File Download
📌 Materials Download Center (Click to Jump)
📌 In the Materials Download Center -> Module Porting Materials Download, inside the compressed package of this chapter.