Quadruped and the ServoCape...

Hello,

To anyone using small bots in their life, I would like some feedback. Currently, I am trying a quadruped.

I found some 3D Printed parts to make, got some screws, and threaded inserts. Outside of that idea, I was wanting to upgrade the use of the quadruped to the am335x along w/ the ServoCape.

Currently, I need the use of 12 servos. Luckily, the ServoCape has 16 servo outputs. So, I am covered in this respect.

So…

  1. Is the current 5.10.x kernel ready for a ServoCape .dts file?
  2. If so, will you please guide me on how to initiate it?
  3. Do I need to use uboot or overlays w/ kernel 5.10.x?

Seth

P.S. I think I can program it in time but w/out the current/correct .dtbo for the PCA9685 onboard the ServoCape, I am a bit outdated. I like functional usage in the .dts field for now, esp. when it comes to the ServoCape.

Would I use PWM or GPIO or Serial communication in the .dts file needed for the PCA9685?

According to the datasheet the PCA9685 uses I2C.

@benedict.hewson ,

Hello Sir…this may sound odd. Does the i2c peripheral communicate directly to the PCA9685 from the am335x and the BBB?

And if so, if the two communicate via i2c, does it have access to provide PWM functionality.

I am sort of at a loss here. I have the ServoCape and wanted to learn more about it.

Seth

P.S. At times, I see that it does not matter what peripheral I call to work, they can work in any instance w/ source but I have had some complication w/ this one. I will grab the older .dts file from 4.19.x kernels in Buster and see if I can manage to catch on to what is going on currently w/ the builds of the .dts files.

Looks like you are in luck. There is a PCA9685 driver in the kernel. I can’t see it being used in any of the devicetree overlays. You could try googling. I assume you will get a bunch of devices in /sys/class/pwms when the driver is loaded.

Alternatively you can enable the i2c-dev driver for the bus. This will allow you to read/write messages to the i2c bus from userspace, in C for certain. With this driver you can talk to all devices on the i2c bus. You should see a device in /dev with this driver loaded. It may be already, check with lsmod.

The PCA9685 datasheet will tell you what to write and where. Think of the chip as a I2C memory, just write to the various locations the data. There are 256 bytes, a big chunk are unused, the rest set pwms and control some aspects of the chip. Each pwm channel has 2 bytes associated with the pwm duty cycle.

The address of the chip is configured by 6 pins on the chip. These will either be fixed on the cape, or there maybe some jumpers in case you want to use several capes.

1 Like

Hello Sir,

I appreciate the insight and dedication. I will review your comments here, the driver, and return lsmod output just for anyone else interested in building around this Cape.

Seth

P.S. I have no inputs on my Cape and no jumpers but there is a EEPROM write, two through hole holes.

Anyway, thank you. I will keep w/ it.

Sir…I have /dev/bone/i2c:


debian@BeagleBone:/dev/bone/i2c$ ls
0  1  2
debian@BeagleBone:/dev/bone/i2c$ i2cdetect -l
i2c-1   i2c             OMAP I2C adapter                        I2C adapter
i2c-2   i2c             OMAP I2C adapter                        I2C adapter
i2c-0   i2c             OMAP I2C adapter                        I2C adapter

I tried lsmod but nothing is showing up in the form of a i2c…

Anyway, I found some source from a person from a while ago. I am trying it now. It uses smbus2 and i2c-2.

So, I am trying. Okay, I see after apt install libi2c-dev, some enties/files are in /dev/ for i2c-{}.

Hello Again @benedict.hewson ,

So, it seems that this source I am working from currently w/ smbus2 via a pip3 install, i2c, and pwm has me fooled. This is sort of what I was discussing in my previous post.

  1. Can I use i2c to control pwm?
  2. If that sounds silly, it is b/c I lack the knowledge to make that work for now.
  3. I will list the source and my file for testing (driver and userspace code) that I got a while back during kernel 4.19.x and the release of this specific Cape.

Here is the driver:

from smbus2 import SMBus
from time import sleep

# registers!

Mode1        = 0x00
Mode2        = 0x01
LED          = 0x06
All_LED      = 0xFA
PRE_SCALE    = 0xFE

class PCA9685:
  def __init__(self, bus, addr):
    self.addr = 0b111111 | addr
    self.bus = bus
    self.write_reg(Mode1, 1 << 5)
    sleep(500e-6)

  def read_reg(self, reg):
    assert reg in range(0, 256)
    return self.bus.read_regs(reg, 1)[0]

  def write_reg(self, reg, value):
    return self.write_regs(reg, [value])

  def read_regs(self, reg, value):
    assert reg in range(0, 256)
    assert count in range(1, 257 - reg)
    return self.bus.red_i2c_block_data(self.addr, reg, count)

  def write_regs(self, reg, values):
    assert reg in range(0, 256)
    return self.bus.write_i2c_block_data(self.addr, reg, values)

  def get_pwm(self, output):
    assert output in range(0, 16)
    reg = LED + 4 * output

    [on_l, on_h, off_l, off_h] = self.read_regs(reg, 4)
    on  = on_l | on_h << 8
    off = off_l | off_h << 8

    phase = on
    duty = (off - on) & 0xfff
    if off & 0x1000:
      duty = 0
    elif on & 0x1000:
      duty = 4096
    return (duty, phase)

  def set_pwm(self, output, duty, phase = 0):
    assert duty in range(0, 4097)
    assert phase in range(0, 4096)

    if output == "all":
      reg = All_LED
    else:
      assert output in range(0, 16)
      reg = LED +4 * output

    on  = phase
    off = (duty + phase) & 0xfff
    if duty == 0:
      off |= 0x1000
    elif duty == 4096:
      on |= 0x1000

    on_l  = on & 0xff
    on_h  = on >> 8
    off_l = off & 0xff
    off_h = off >> 8
    self.write_regs(reg, [on_l, on_h, off_l, off_h])

and the simple source to test the serial driver…

#!/usr/bin/python3

from PCA96xx import *
from time import sleep

i2c_bus = SMBus("/dev/i2c-2")
pwm_controller = PCA9685(i2c_bus, 0b111111)

def servo():
  angle = int(input("Please enter a value from 0 to 90: "))
  if pwm_controller >= 1 and pwm_controller >= 90:
    angle = pwm_controller
  print ("Your Angle is: %d" % pwm_controller)

servo()

I am receiving an error. The error is related to busy resources, i.e. which I have listed below:

Traceback (most recent call last):
  File "/home/debian/ServoCape/./PCA_USE_ONE.py", line 7, in <module>
    pwm_controller = PCA9685(i2c_bus, 0b111111)
  File "/home/debian/ServoCape/PCA96xx.py", line 16, in __init__
    self.write_reg(Mode1, 1 << 5)
  File "/home/debian/ServoCape/PCA96xx.py", line 24, in write_reg
    return self.write_regs(reg, [value])
  File "/home/debian/ServoCape/PCA96xx.py", line 33, in write_regs
    return self.bus.write_i2c_block_data(self.addr, reg, values)
  File "/home/debian/.local/lib/python3.9/site-packages/smbus2/smbus2.py", line 643, in write_i2c_block_data
    ioctl(self.fd, I2C_SMBUS, msg)
OSError: [Errno 121] Remote I/O error

This is w/ i2c-2 being used to handle pwm on the Cape and handle a servo motor.

  1. config-pin p9.21 i2c && config-pin p9.22 i2c
  2. no output as config-pin worked to handle the pin muxing.

Seth

P.S. I know this may be a little bit too much to ask. So, no issue if you do not want to review it.

Oh! I just caught on to what you said in your post.

Okay. I will further investigate this effort in the datasheet. I am now unaware of if the i2c.dts file needs to be used on kernel 5.10.x or if there are other ways to handle the muxing and control to userspace.

New revelations:

So, I changed out the bytes address to hex and it is slowly getting me closer to working order.

Also

Here:

[ 1846.104226] omap_i2c 4819c000.i2c: timeout waiting for bus ready
[ 1872.109328] omap_i2c 4802a000.i2c: timeout waiting for bus ready
[ 2197.353932] omap_i2c 4819c000.i2c: timeout waiting for bus ready
[ 2251.373343] omap_i2c 4819c000.i2c: timeout waiting for bus ready

I changed the files to represent /dev/bone/i2c/2 instead of /dev/i2c-2 which may or may not be correct but when I use /dev/i2c-2, it is my problem. When I use /dev/bone/i2c/2, the system errors out w/ output relaying back the resource busy error.

Traceback (most recent call last):
  File "/home/debian/ServoCape/./PCA_USE_ONE.py", line 7, in <module>
    pwm_controller = PCA9685(i2c_bus, 0x34)
  File "/home/debian/ServoCape/PCA96xx.py", line 16, in __init__
    self.write_reg(Mode1, 1 << 5)
  File "/home/debian/ServoCape/PCA96xx.py", line 24, in write_reg
    return self.write_regs(reg, [value])
  File "/home/debian/ServoCape/PCA96xx.py", line 33, in write_regs
    return self.bus.write_i2c_block_data(self.addr, reg, values)
  File "/home/debian/.local/lib/python3.9/site-packages/smbus2/smbus2.py", line 643, in write_i2c_block_data
    ioctl(self.fd, I2C_SMBUS, msg)
OSError: [Errno 16] Device or resource busy

I know. I lot to take in. I have been on this for a while…

Hi @silver2row

Ok, looks like you have made progress.

A couple of points.

The I2C address is normally 7 bits. Your servo cape lets you configure the bottom 6 bits. By default these are all 1’s. The top address bit is set internally on the chip to a 1, so by default the address of the chip would be 0x7f. You are only setting 0x3f.

self.addr = 0b111111 | addr

and

pwm_controller = PCA9685(i2c_bus, 0b111111)

With the wrong address the code will fail on the first I2C bus access as the device needs to respond with an ACK.

Be aware that when using auto increment, read/writes wrap when going from register 69, back to register 1.

You may want to change the PWM PRE_SCALE register ( address 254) by default the output frequency is 200HZ. The default will probably need to set the for servos is 50Hz, although they may work.

The is also an output enable pin on the chip, connected to GPIO68. This will disable all the PWMs so make sure you control this.

You have a bug in your code I think.

You are checking ‘count’ but passing ‘value’

Hello Sir,

@benedict.hewson … about the bug.

  1. I think I misread at the time what needed to go where in the source.
  2. I have some new source.

And…about the address.

  1. I will reset it back to 0b111111 from my mistake of changing it.
  2. I will change it in the driver and source.

I will change the Pre_Scale register too. I will update you soon.

Seth

P.S. Thank you.

Hello,

it seems I am messing around w/ recursion depth now. Anyway, here is the source from what I can gather “quickly” while researching what I could. If you get bored of these ideas, I completely understand.

from smbus2 import SMBus
from pathlib import Path
from time import sleep

# registers!

Mode1        = 0x00
Mode2        = 0x01
LED          = 0x06
All_LED      = 0xFA
PRE_SCALE    = 0xFE
OE           = Path("/sys/class/gpio/gpio68/direction")
OE.write_text('low')

class PCA9685:
  def __init__(self, bus, addr):
    self.addr = 0b111111 | addr
    self.bus = bus
    self.write_reg(Mode1, 1 << 5)
    sleep(500e-6)
    OE.write_text('high')
    sleep(1)

  def read_reg(self, reg):
    assert reg in range(0, 256)
    return self.bus.read_regs(reg, 1)[0]

  def write_reg(self, reg, value):
    return self.write_reg(reg, [value])

  def read_regs(self, reg, value):
    assert reg in range(0, 256)
    assert value in range(1, 257 - reg)
    return self.bus.red_i2c_block_data(self.addr, reg, count)

  def write_regs(self, reg, values):
    assert reg in range(0, 256)
    return self.bus.write_i2c_block_data(self.addr, reg, values)

  def get_pwm(self, output):
    assert output in range(0, 16)
    reg = LED + 4 * output

    [on_l, on_h, off_l, off_h] = self.read_regs(reg, 4)
    on  = on_l | on_h << 8
    off = off_l | off_h << 8

    phase = on
    duty = (off - on) & 0xfff
    if off & 0x1000:
      duty = 0
    elif on & 0x1000:
      duty = 4096
    return (duty, phase)

  def set_pwm(self, output, duty, phase = 0):
    assert duty in range(0, 4097)
    assert phase in range(0, 4096)

    if output == "all":
      reg = All_LED
    else:
      assert output in range(0, 16)
      reg = LED +4 * output

    on  = phase
    off = (duty + phase) & 0xfff
    if duty == 0:
      off |= 0x1000
    elif duty == 4096:
      on |= 0x1000

    on_l  = on & 0xff
    on_h  = on >> 8
    off_l = off & 0xff
    off_h = off >> 8
    self.write_regs(reg, [on_l, on_h, off_l, off_h])

So, that is the updated driver from getting help on IRC years ago and I found it just recently.

I noticed you saying something about "OE" and GPIO68. Okay, I will get on this soon. I can probably use pathlib to notify the source of this specific gpio68 needing to be on/off.

Here is some error about the recursion depth that I briefly mentioned.

Traceback (most recent call last):
  File "/home/debian/ServoCape/./PCA_USE_ONE.py", line 7, in <module>
    pwm_controller = PCA9685(i2c_bus, 0b111111)
  File "/home/debian/ServoCape/PCA96xx.py", line 17, in __init__
    self.write_reg(Mode1, 1 << 5)
  File "/home/debian/ServoCape/PCA96xx.py", line 25, in write_reg
    return self.write_reg(reg, [value])
  File "/home/debian/ServoCape/PCA96xx.py", line 25, in write_reg
    return self.write_reg(reg, [value])
  File "/home/debian/ServoCape/PCA96xx.py", line 25, in write_reg
    return self.write_reg(reg, [value])
  [Previous line repeated 994 more times]
RecursionError: maximum recursion depth exceeded

Also, it seems that the max. recursion depth is done to prevent a stack overflow or halting the computer entirely, i.e. BBB.

Seth

P.S. Oh. I changed out the source a bit to handle the driver.

  1. I have added in the OE for gpio68 yet.
  2. Is gpio68 p9.36? I did the math but it seems it could be anything. I will look through one of my older books.
  3. It seems I was calling pwm_controller in the source instead of angle.
  4. gpio68 = 32 * 1 + 32. That or it is on P8.36 or P8.4. I first thought it has to be P9.36. This was taken over from ADC. So, I removed the comment in front of the ADC line in uEnv.txt.

Anyway, thank you for your words. I will keep trying.

a. So, OE.write_text is done for GPIO68 via pahtlib.Path.
b. I changed the address back to 0b111111 for each, separate file.
c. Even after a reboot, the error remains. RecursionError: maximum recursion depth exceeded .

You have a bug in write_reg.

Looks like you have accidentally deleted the ‘s’

Hello Sir,

@benedict.hewson … yep. That cured the issue. Now, I have a remote I/O error.

I will keep researching my errors. Maybe I can post it soon.

Seth

P.S. I mean, sheesh, It could actually be my simple source that I am referring from the driver to make the servo move via angle = int(input("Add you favorite number between 0 and 180: ")) ?

OK this does dot make any sense.

pwm_controller is a class.

To move a servo you need to set the duty cycle to the correct value.
See this link Servo Motors

For a given angle you will need to work out the PWM duty cycle to get the right timing , and then write to the 4 registers for that PWM channel to set the on and off period.

Your servo() function does not do any of that, It would also help if you have access to a scope to check the PWM outputs to make sure you have something. A dvm would help at a pinch.

Have you seen this

Interface the Beaglebone Black with the PCA9685 PWM controller

1 Like

I saw it. Years ago…I was searching for ideas. Well, I will keep at it. I need to try to understand how I can create the duty cycle too.

So, more research will be underway. Thank you again for your time. I will update soon. It is like I start w/ PWM scripts and then start building other things. I need to get a one-way view that is more dedicated to this venture. I will keep you updated.

Seth

To calculate the duty cycle you need to know how long 1 bit time is.

bit_time = 1 / f *4096

where f is your PWM frequency in Hz

then min PWM high time = 1ms = 0.001/bt
max PWM high time = 2ms = 0.002/bt

To calculate time from angle ( a ), assuming max rotation is 180 degrees.

pwm_high = ( (a/180)*0.001 + 0.001 )/ bt
the PWM low time = 4096 - PWM high time

I think that is correct.

1 Like

Hello Sir,

You are right. I changed it already. I was receiving some odd errors.

def servo():
  angle = int(input("Enter 0 through 180: "))
  if angle == 0 and angle <= 180:
    ...
  print ("Your angle is: %d " % angle)

I will work on the source some more. oh! I found this bit of source for the PCA9685 in C++ source on a repo. on github.

https://github.com/TeraHz/PCA9685/blob/master/src/PCA9685.cpp

Seth

P.S. I will try to reread it in time and convert it to Python3.

Hello Sir,

I was highly unaware of having to calculate the a 1 bit time, i.e. esp. as it being equal to 1 / f * 4096.

Seth

P.S. I read the rest of the posts. I will attempt this info. in source and return service. Thank you.

Hello Sir,

@benedict.hewson , I saw where the info. you were discussing was in plain sight in the datasheet. Phew.

Anyway…it seems for a double dual percentages of delay_time and duty_cycle cannot equal over 100%.

For instance:

delay_time = 25% or 0.25
and...
duty_cycle = 50% or 0.50

This should be okay for one servo. At least this is one way the datasheet is making rules for this PCA9685 use.

Seth

P.S. I will try to put together the correct source and return service, i.e. much later. I have a full day of blah work. Yay for me! Anyway, thank you again and again. I will get you posted.

Okay…

More Info:

1. I have found that /dev/bone/i2c/2 is the file I need to have listed, I think.
2. I know that the Cape is using the /dev/bone/i2c/0, I think.
3. I have some docs. that describe the headers and Cape connections for the ServoCape.
4. Also, I will test each, separate i2c address and location of the file(s).

I got close once but I ruined it. Is the GSoC Cape Compatibility Layer already installed in the local, updated images?

Ooh!

I was reviewing the source here in the /src/arm/BBORG_SERVO-00A0.dts file. It seems okay but I have not configured things correctly, i.e. as I keep getting Remore I/O error.

are you using the overlay from the github you listed? if so, then the driver “pca9685-pwm” most likely is being loaded. from command prompt, do “lsmod | grep pca9685” if the driver is loaded you cannot use the i2c address to access the pca9685 because the driver has it. you could do “rmmod pca9685” (exact name is what lsmod returned. however, after each reboot your will need to do rmmod. or just use the pca9685 exported functions.

Hello @amf99 ,

Seth here. I tried it. It did not work. There are some files in /dev/bone/i2c/0-2 and I noticed a oops in the .dts and in the specific kernels from BeagleBoard-DeviceTrees on github.

I tried that link I listed. I could not get it correctly configured.

Oh! Thank you. Now I know. I will try lsmod to get the correct driver if one and then rmmod to remove it for using my files on the current kernel.

Seth

P.S. I was changing source file after source file thinking that the next try was the cure. It could very well be the idea you are discussing. Thank you so much. I will test it. See, in BeagleBoard-DeviceTrees on the beagleboard/ repo, there is a a couple branches to checkout in git. So, I checked out v5.10.x and v5.10.x-ti. The TI versioning works well and there is one small factor actually. There is no BBORG_SERVO-00A2.dtbo on that current branch. So, I checked out the link listed in my previous post, brick, and then checked out the v5.10.x branch and brick. So, I will reflash now and test your knowledge to the ole-BBB!