Skip to content
This repository was archived by the owner on Mar 2, 2026. It is now read-only.

[feat] Add support for pca9685 pwm module#296

Merged
maruel merged 5 commits into
google:masterfrom
alvarowolfx:pca9685
Oct 30, 2018
Merged

[feat] Add support for pca9685 pwm module#296
maruel merged 5 commits into
google:masterfrom
alvarowolfx:pca9685

Conversation

@alvarowolfx
Copy link
Copy Markdown
Contributor

Add support for the PCA9685 16 channel PWM module. I create this for a demo on Gophercon Brazil talking about embedded development with Golang and one of the demos was a robot arm (MeArm) controlled through Google Assistant, Cloud IoT Core and Golang running on an Orange Pi Zero. So I extracted the code for controlling the pca9685 module and I'm sending this PR.

Code for the project: https://github.com/alvarowolfx/golang-voice-iot
Demo of it working: https://youtu.be/Xep6dZ3xOYA

Based on https://github.com/wintersandroid/Android-Things-PCA9685
Datasheets: https://cdn-shop.adafruit.com/datasheets/PCA9685.pdf
Product page: https://www.adafruit.com/product/815

img_4752

Copy link
Copy Markdown
Contributor

@maruel maruel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot for the change!

Comment thread experimental/devices/pca9685/doc.go Outdated
//
// Based on https://github.com/wintersandroid/Android-Things-PCA9685
//
// Datasheets:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For godoc to process this correctly, you need to keep an empty line between and no semi colon, e.g.:

// Datasheet
//
// https://www.nxp.com/docs/en/data-sheet/PCA9685.pdf

Use the original datasheet, not the copy from Adafruit.

Comment thread experimental/devices/pca9685/doc.go Outdated
// https://cdn-shop.adafruit.com/datasheets/PCA9685.pdf
//
// Product page:
// https://www.adafruit.com/product/815
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The product page is https://www.nxp.com/products/analog/interfaces/ic-bus/ic-led-controllers/16-channel-12-bit-pwm-fm-plus-ic-bus-led-controller:PCA9685

I do link to adafruit on the website in the "where to buy" section but not in the documentation.

Comment thread experimental/devices/pca9685/pca9685.go
Comment thread experimental/devices/pca9685/pca9685.go Outdated
dev *i2c.Dev
}

// NewI2CAddress returns a Dev object that communicates over I2C on a alternate address
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Merge these two functions, have the caller specify pca9685.I2CAddr every time and comment that it's what should be normally passed.

Comment thread experimental/devices/pca9685/pca9685.go Outdated
"periph.io/x/periph/conn/i2c"
)

// PCA9685Address i2c default address
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a bit of inconsistency in the drivers for default I2C addresses, I think I'd prefer I2CAddr here, since PCA9685Address stutters.

https://blog.golang.org/package-names talks a bit about this.

Comment thread experimental/devices/pca9685/pca9685.go Outdated
}

// SetPwmFreq set the pwm frequency
func (d *Dev) SetPwmFreq(freqHz float32) error {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do not use floating point, use physic.Frequency instead.

Comment thread experimental/devices/pca9685/pca9685.go
Comment thread experimental/devices/pca9685/servo.go
Comment thread experimental/devices/pca9685/pca9685.go Outdated
dev: &i2c.Dev{Bus: bus, Addr: address},
}
err := dev.init()
return dev, err
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Return nil for *Dev if err != nil

Comment thread experimental/devices/pca9685/pca9685.go Outdated
}

func (d *Dev) init() error {
d.SetAllPwm(0, 0)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please check errors thoroughly.

@alvarowolfx
Copy link
Copy Markdown
Contributor Author

Thanks for the comments, already pushed some updates and need to work more on some ideas.

Copy link
Copy Markdown
Contributor

@maruel maruel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks pretty good, next cycle should be good.

Comment thread experimental/devices/pca9685/pca9685.go
Comment thread experimental/devices/pca9685/servo.go
Comment thread experimental/devices/pca9685/example_test.go
Comment thread experimental/devices/pca9685/pca9685.go Outdated
"time"

"periph.io/x/periph/conn/physic"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please keep the imports grouped, as goimports would normally do.

log.Fatal(err)
}

pca.SetPwmFreq(50)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you don't want to include error checking to keep the example easier to read, then comment to tell the user that they should. But in practice I'd expect the users to copy-paste, so I recommend to keep the error checking.

Comment thread experimental/devices/pca9685/pca9685.go Outdated

prescaleToSend := int(math.Floor(float64(prescaleVal + 0.5)))

var modeRead []byte
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same reduction as above.

Comment thread experimental/devices/pca9685/pca9685.go Outdated

oldmode := modeRead[0]
newmode := (byte)((oldmode & 0x7F) | 0x10) // sleep
if _, err := d.dev.Write([]byte{mode1, newmode}); err != nil { // go to sleep;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These 3 can probably be merged (?) I haven't looked at the datasheet to confirm.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried sending commands altogether instead of doing 3 call to dev.Write and unfortunately didn't work on the device.

Comment thread experimental/devices/pca9685/pca9685.go Outdated

time.Sleep(100 * time.Millisecond)

if _, err := d.dev.Write([]byte{mode1, (byte)(oldmode | 0x80)}); err != nil {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is fine to do this:

_, err := d.dev.Write([]byte{mode1, (byte)(oldmode | 0x80)})
return err

Comment thread experimental/devices/pca9685/pca9685.go Outdated

// SetPwmFreq set the pwm frequency
func (d *Dev) SetPwmFreq(freqHz physic.Frequency) error {
prescaleVal := float32(25 * physic.MegaHertz)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for floating point. The rounding can done by adding half of the value you are dividing, all in integers:

p := (25 * physic.MegaHertz / 4096 + freqHz/2) / freqHz

Comment thread experimental/devices/pca9685/pca9685.go Outdated
}

// SetPwm set a pwm value for given pca9685 channel
func (d *Dev) SetPwm(channel int, on uint16, off uint16) error {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ultimately, I'll ask you to use gpio.Duty for PWM duty cycle values. This can be done in a follow up.
https://periph.io/x/periph/conn/gpio#Duty

@maruel
Copy link
Copy Markdown
Contributor

maruel commented Oct 29, 2018

gohci

@maruel
Copy link
Copy Markdown
Contributor

maruel commented Oct 29, 2018

Please fix the test, thanks.

@maruel
Copy link
Copy Markdown
Contributor

maruel commented Oct 30, 2018

gohci

Copy link
Copy Markdown
Contributor

@maruel maruel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok so I have a few comments but nothing that is worth doing more back and forth on this PR. Please address the comments in a follow up at your leisure.

One important point is to use the datasheet as the reference. Do not blindly copy paste constants from sources other than the datasheet.

allLedOffL byte = 0xFC
allLedOffH byte = 0xFD

// Bits
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In a follow up, I think it's worth describing what they are for. This is mixing bitmasks for mode2 (ourDrv, invrt), mode1 (allCall, sleep) and restart is unused and I couldn't find it in the datasheet.

So remove restart for now, we'll handle the rest later.

return err
}

if _, err := d.dev.Write([]byte{mode2, outDrv}); err != nil {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I looked at the datasheet and the way it works is via a memory mapped system where the pointer increments at each byte. That's fairly standard with I²C and https://periph.io/x/periph/conn/mmr exists exactly for this.

That said we should do that in a follow up, let's get something working first.

return d.SetPwmFreq(50 * physic.Hertz)
}

// SetPwmFreq set the pwm frequency
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In a follow up, please terminate all documentation with a period.

sleep byte = 0x10
allCall byte = 0x01
invrt byte = 0x10
outDrv byte = 0x04
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment is only FYI.

This device can sink 25mA and in totem-pole can source 10mA. You always put it in totem-pole at the moment.
I started adding Drive() to some GPIOs (like https://periph.io/x/periph/host/bcm283x#Pin.Drive) but I haven't defined an interface to gpio yet. I realize I should probably have both Sink and Source instead, so maybe I'll change that.

)

// Dev is a handler to pca9685 controller
type Dev struct {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In a follow up, please add Halt() that sets sleep bit, so that all PWMs stop. That's the essence of Halt. :)

const (
mode1 byte = 0x00
mode2 byte = 0x01
subAdr1 byte = 0x02
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are bitmasks on mode1. I'd vote to remove them since they are not used and you are mixing register addresses and bitmasks here, which is super confusing when looking up the datasheet.

Well, there are subadr1~3 registers, but they are 0x19 to 01A.

@maruel maruel merged commit edb8c30 into google:master Oct 30, 2018
@alvarowolfx
Copy link
Copy Markdown
Contributor Author

Thanks for the awesome help @maruel. I'm still fairly new to Golang and I'm learning a lot with your reviews. I'll be fixing the other drivers that I sent and will be probably sending another one later today (bh1750 light sensor driver). Them I can revisit those drivers. You are completely right, I should stop following blindly and porting from other languages and start looking more into the datasheet and understanding more how it works.

@maruel
Copy link
Copy Markdown
Contributor

maruel commented Oct 30, 2018

No worries, you did well. Reading datasheets is an art in itself so there's a ramp up curve there too.

@kevinboulain kevinboulain mentioned this pull request Jul 6, 2019
maruel pushed a commit that referenced this pull request Jul 10, 2019
Some nits from #296.

The auto-increment capability of the device can simplify contiguous
writes.
maruel pushed a commit to periph/devices that referenced this pull request Dec 24, 2020
Some nits from google/periph#296.

The auto-increment capability of the device can simplify contiguous
writes.
maruel pushed a commit to periph/host that referenced this pull request Dec 24, 2020
Some nits from google/periph#296.

The auto-increment capability of the device can simplify contiguous
writes.
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants