Pi Servo pHAT (v2) Hookup Guide

Pages
Contributors: QCPete, santaimpersonator
Favorited Favorite 6

Python Examples (archived)

Note: With the addition of the new python package for this product, these examples may become deprecated and will no longer be supported. However, the information is still retained for archival purposes.

We'll go over in some detail here how to access and use the Pi Servo pHAT in Python. Full example code is available in the product GitHub repository.

Set Up Access to SMBus Resources

First point: in most OS level interactions, the I2C bus is referred to as SMBus. Thus we get our first lines of code. This imports the smbus module, creates an object of type SMBus, and attaches it to bus "1" of the Pi's various SMBuses.

language:python
import smbus
bus = smbus.SMBus(1)

We have to tell the program the part's address. By default, it is 0x40, so set a variable to that for later use.

language:python
addr = 0x40

Next, we want to enable the PWM chip and tell it to automatically increment addresses after a write (that lets us do single-operation multi-byte writes).

language:python
bus.write_byte_data(addr, 0, 0x20)
bus.write_byte_data(addr, 0xfe, 0x1e)

Write Values to the PWM Registers

That's all the setup that needs to be done. From here on out, we can write data to the PWM chip and expect to have it respond. Here's an example.

language:python
bus.write_word_data(addr, 0x06, 0)
bus.write_word_data(addr, 0x08, 1250)

The first write is to the "start time" register for channel 0. By default, the PWM frequency of the chip is 200Hz, or one pulse every 5ms. The start time register determines when the pulse goes high in the 5ms cycle. All channels are synchronized to that cycle. Generally, this should be written to 0.

The second write is to the "stop time" register, and it controls when the pulse should go low. The range for this value is from 0 to 4095, and each count represents one slice of that 5ms period (5ms/4095), or about 1.2us. Thus, the value of 1250 written above represents about 1.5ms of high time per 5ms period.

Servo motors get their control signal from that pulse width. Generally speaking, a pulse width of 1.5ms yields a "neutral" position, halfway between the extremes of the motor's range. 1.0ms yields approximately 90 degrees off center, and 2.0ms yields -90 degrees off center. In practice, those values may be slightly more or less than 90 degrees, and the motor may be capable of slightly more or less than 90 degrees of motion in either direction.

To address other channels, simply increase the address of the two registers above by 4. Thus, start time for channel 1 is 0x0A, for channel 2 is 0x0E, channel 3 is 0x12, etc. and stop time address for channel 1 is 0x0C, for channel 2 is 0x10, channel 3 is 0x14, etc. See the table below.

Channel # Start Address Stop Address
Ch 0 0x06 0x08
Ch 1 0x0A 0x0C
Ch 2 0x0E 0x10
Ch 3 0x12 0x14
Ch 4 0x16 0x18
Ch 5 0x1A 0x1C
Ch 6 0x1E 0x20
Ch 7 0x22 0x24
Ch 8 0x26 0x28
Ch 9 0x2A 0x2C
Ch 10 0x2E 0x30
Ch 11 0x32 0x34
Ch 12 0x36 0x38
Ch 13 0x3A 0x3C
Ch 14 0x3E 0x40
Ch 15 0x42 0x44

If you write a 0 to the start address, every degree of offset from 90 degrees requires 4.6 counts written to the stop address. In other words, multiply the number of degrees offset from neutral you wish to achieve by 4.6, then either add or subtract that result from 1250, depending on the direction of motion you wish. For example, a 45 degree offset from center would be 207 (45x4.6) counts either more or less than 1250, depending upon the direction you desire the motion to be in.

Examples

Below, are some convenient examples for users to draw from.

Example 1: 50 Hz Frequency

Unlike the higher-end or modern servos that can handle higher frequencies, most servos (often older and cheaper) prefer a 50 Hz PWM frequency and will struggle with a 200 Hz PWM frequency. Usually, they end up buzzing or over heating, while trying to search for hone/track their position.

Servo in Action
Example 1 with servo working.

Below is an example of how to configure the PCA9685 to drive at a 50 Hz PWM frequency. This does reduce the resolution of the servo arm position, but for most servos that was probably causing the servo to overheat anyways.

language:python
import smbus, time
bus = smbus.SMBus(1)
addr = 0x40

## Running this program will move the servo to 0, 45, and 90 degrees with 5 second pauses in between with a 50 Hz PWM signal.

bus.write_byte_data(addr, 0, 0x20) # enable the chip
time.sleep(.25)
bus.write_byte_data(addr, 0, 0x10) # enable Prescale change as noted in the datasheet
time.sleep(.25) # delay for reset
bus.write_byte_data(addr, 0xfe, 0x79) #changes the Prescale register value for 50 Hz, using the equation in the datasheet.
bus.write_byte_data(addr, 0, 0x20) # enables the chip

time.sleep(.25)
bus.write_word_data(addr, 0x06, 0) # chl 0 start time = 0us

time.sleep(.25)
bus.write_word_data(addr, 0x08, 209) # chl 0 end time = 1.0ms (0 degrees)
time.sleep(.15)
bus.write_word_data(addr, 0x08, 312) # chl 0 end time = 1.5ms (45 degrees)
time.sleep(.15)
bus.write_word_data(addr, 0x08, 416) # chl 0 end time = 2.0ms (90 degrees)

while True:
     time.sleep(.5)
     bus.write_word_data(addr, 0x08, 209) # chl 0 end time = 1.0ms (0 degrees)
     time.sleep(.5)
     bus.write_word_data(addr, 0x08, 312) # chl 0 end time = 1.5ms (45 degrees)
     time.sleep(.5)
     bus.write_word_data(addr, 0x08, 209) # chl 0 end time = 1.0ms (0 degrees)
     time.sleep(.5)
     bus.write_word_data(addr, 0x08, 416) # chl 0 end time = 2.0ms (90 degrees)

Example 2: Create a Function

This example creates simple functions to set the start and end times of the PWM signal based on the designated servo position for a specific channel. This example should make it easier for users to create their own scripts with more limited knowledge of the python language.

language:python
import smbus, time
bus = smbus.SMBus(1)
addr = 0x40

def servo_Init(channel):
     # Mapping Channel Register
     channel_0_start = 0x06
     channel_reg = 4*channel + channel_0_start

     # Write to Channel Register
     bus.write_word_data(addr, channel_reg, 0) 


def servo_Pos(channel, deg_range, deg_position):
     # Mapping Channel Register
     channel_0_end = 0x08
     channel_reg = 4*channel + channel_0_end

     # Mapping Sevo Arm Position
     #   209 = 0 deg
     #   312 = 45 deg
     #   416 = 90 deg
     deg_0 = 209
     deg_max = 416
     pos_end_byte = lambda x: (deg_max-deg_0)/deg_range*x + deg_0

     # Write to Channel Register
     bus.write_word_data(addr, channel_reg, round(pos_end_byte(deg_position)))

## Running this program will move the servo to 0, 45, and 90 degrees with 5 second pauses in between with a 50 Hz PWM signal.

# Configure 50Hz PWM Output
bus.write_byte_data(addr, 0, 0x20) # enable the chip
time.sleep(.25)
bus.write_byte_data(addr, 0, 0x10) # enable Prescale change as noted in the datasheet
time.sleep(.25) # delay for reset
bus.write_byte_data(addr, 0xfe, 0x79) #changes the Prescale register value for 50 Hz, using the equation in the datasheet.
bus.write_byte_data(addr, 0, 0x20) # enables the chip

# Initialize Channel (sets start time for channel)
servo_Init(3)

# Run Loop
while True:
     time.sleep(.5)
     servo_Pos(3, 90, 0) # chl 3 end time = 1.0ms (0 degrees)
     time.sleep(.5)
     servo_Pos(3, 90, 45) # chl 3 end time = 1.5ms (45 degrees)
     time.sleep(.5)
     servo_Pos(3, 90, 0) # chl 3 end time = 1.0ms (0 degrees)
     time.sleep(.5)
     servo_Pos(3, 90, 90) # chl 3 end time = 2.0ms (90 degrees)