Skip to content

Conversation

@yameatmeyourdead
Copy link
Contributor

JH backend was really obtuse and didn't have some nicities I wanted.
This fixes all of the gripes I had with JH's backend. Implements support for external clocks (need to uncomment and test obv).

Tested locally using continuous servo (esc w/ motor). Works just fine. All pca channels work

This does not touch the ROS-interfacing API so no other nodes should have to be touched for this to work. Yay compartmentalization.

I rebased on main so this doesn't have the same 30 commits that the last 12 PRs had holy crap this is ridiculous.

@yameatmeyourdead yameatmeyourdead added the enhancement New feature or request label Mar 26, 2025
@yameatmeyourdead yameatmeyourdead self-assigned this Mar 26, 2025
Copy link
Member

@Hermanoid Hermanoid left a comment

Choose a reason for hiding this comment

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

Some cleaning up but I'm not about to forge into the datasheet (willingly, anyway) so I'm accepting the rest :)

}

void PCA9685::setDuty(uint8_t channel, float duty, float _delay) {
// uint16_t time_on = static_cast<uint16_t>(std::round(duty * 4096));
Copy link
Member

Choose a reason for hiding this comment

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

yeet the comments

Copy link
Contributor Author

@yameatmeyourdead yameatmeyourdead Mar 27, 2025

Choose a reason for hiding this comment

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

comments are the "intended" way of doing this. could not get it to work with 0% duty cycle though since the math for the time_off register underflows at this condition...

This is actually a really hacky way of using the 9685. When counts <= time_off, the output is inverted. When only time_off is utilized, its actually the total number of counts that the pwm signal is asserted (leading edge at 0 counts, trailing edge at time_off counts).

If you could figure out how the math is supposed to work at this condition we would have a more functional 9685 driver (capable of offsetting the pulses wrt each other)

Copy link
Member

Choose a reason for hiding this comment

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

After poking through the datasheet more, I'm noticing a couple of things

  • the first example subtracts one from the time_on for some reason, but the later examples don't and it doesn't make sense to me - if time_on is 42 counts and your duty cycle is zero, you should stop (at least in theory, this seems like an edge case, see the next bullet) at 42 counts, not 42-1 = 41. Similarly, if time_on is 42 and your duty cycle corresponds to an on time of 1, you should stop at 43; stopping at 43-1=42 would be wrong.
  • Examples 3 and 4 on page 18 of the datasheet detail what seem to be special cases for all-on and all-off. The datasheet also doesn't specify anything else about these cases - like, if the on and off times are the same, which one wins? It sounds like this is an undefined-behavior edge case, so we should use the special all-on and all-off cases (setting bit 12) instead

Copy link
Member

Choose a reason for hiding this comment

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

That same argument applies to time_on as well, actually - if you want zero delay, why would you want to turn on at count=-1?

Copy link
Member

Choose a reason for hiding this comment

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

Another note: The special case for fully-off doesn't actually involve bit 12 like I assumed. Full-on involves setting bit 12 on LED_ON, but full-off involves setting the two counts equal - which answers the question of who wins when time_on and time_off are the same

Copy link
Member

Choose a reason for hiding this comment

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

This is fun
There's also a special case for fully off using bit 12 on page 20, bit 12 on LEDn_OFF overrides bit 12 for LEDn_ON.

Copy link
Member

Choose a reason for hiding this comment

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

@yameatmeyourdead thoughts on new impl

…ome commented code is left behind and why certain bitmasks are the way they are
}

void PCA9685::setDuty(uint8_t channel, float duty, float _delay) {
// uint16_t time_on = static_cast<uint16_t>(std::round(duty * 4096));
Copy link
Member

Choose a reason for hiding this comment

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

After poking through the datasheet more, I'm noticing a couple of things

  • the first example subtracts one from the time_on for some reason, but the later examples don't and it doesn't make sense to me - if time_on is 42 counts and your duty cycle is zero, you should stop (at least in theory, this seems like an edge case, see the next bullet) at 42 counts, not 42-1 = 41. Similarly, if time_on is 42 and your duty cycle corresponds to an on time of 1, you should stop at 43; stopping at 43-1=42 would be wrong.
  • Examples 3 and 4 on page 18 of the datasheet detail what seem to be special cases for all-on and all-off. The datasheet also doesn't specify anything else about these cases - like, if the on and off times are the same, which one wins? It sounds like this is an undefined-behavior edge case, so we should use the special all-on and all-off cases (setting bit 12) instead

}

void PCA9685::setDuty(uint8_t channel, float duty, float _delay) {
// uint16_t time_on = static_cast<uint16_t>(std::round(duty * 4096));
Copy link
Member

Choose a reason for hiding this comment

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

That same argument applies to time_on as well, actually - if you want zero delay, why would you want to turn on at count=-1?

}

void PCA9685::setDuty(uint8_t channel, float duty, float _delay) {
// uint16_t time_on = static_cast<uint16_t>(std::round(duty * 4096));
Copy link
Member

Choose a reason for hiding this comment

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

Another note: The special case for fully-off doesn't actually involve bit 12 like I assumed. Full-on involves setting bit 12 on LED_ON, but full-off involves setting the two counts equal - which answers the question of who wins when time_on and time_off are the same

}

void PCA9685::setDuty(uint8_t channel, float duty, float _delay) {
// uint16_t time_on = static_cast<uint16_t>(std::round(duty * 4096));
Copy link
Member

Choose a reason for hiding this comment

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

This is fun
There's also a special case for fully off using bit 12 on page 20, bit 12 on LEDn_OFF overrides bit 12 for LEDn_ON.

}

void PCA9685::setDuty(uint8_t channel, float duty, float _delay) {
// uint16_t time_on = static_cast<uint16_t>(std::round(duty * 4096));
Copy link
Member

Choose a reason for hiding this comment

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

@yameatmeyourdead thoughts on new impl

@Hermanoid
Copy link
Member

@yameatmeyourdead please give this new implementation a test on the hardware :)

@yameatmeyourdead
Copy link
Contributor Author

yameatmeyourdead commented Mar 27, 2025

@yameatmeyourdead please give this new implementation a test on the hardware :)

your implementation works for all off and all on, but not any other duty. Performs a quick flash then turns off, attempting to set the same pwm does nothing

@yameatmeyourdead yameatmeyourdead merged commit a04bcb9 into main Apr 3, 2025
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants