|
| 1 | +#pragma once |
| 2 | + |
| 3 | +// ReSharper disable CppMemberFunctionMayBeStatic |
| 4 | + |
| 5 | +// ReSharper disable IdentifierTypo |
| 6 | +constexpr int PCA9685_MODE_INVRT = 0x10; // Inverts polarity of channel output signal |
| 7 | +constexpr int PCA9685_MODE_OUTPUT_ONACK = 0x08; // Channel update happens upon ACK (post-set) rather than on STOP (endTransmission) |
| 8 | +constexpr int PCA9685_MODE_OUTPUT_TPOLE = 0x04; // Use a totem-pole (push-pull) style output, typical for boards using this chipset |
| 9 | +constexpr int PCA9685_MODE_OUTNE_HIGHZ = 0x02; // For active low output enable, sets channel output to high-impedance state |
| 10 | +constexpr int PCA9685_MODE_OUTNE_LOW = 0x01; // Similarly, sets channel output to high if in totem-pole mode, otherwise high-impedance state |
| 11 | + |
| 12 | +constexpr int PCA9685_MIN_CHANNEL = 0; |
| 13 | +constexpr int PCA9685_MAX_CHANNEL = 15; |
| 14 | +constexpr int PCA9685_CHANNEL_COUNT = 16; |
| 15 | +// ReSharper restore IdentifierTypo |
| 16 | + |
| 17 | +typedef uint8_t byte; |
| 18 | + |
| 19 | +class PCA9685 |
| 20 | +{ |
| 21 | +public: |
| 22 | + // Should be called only once in setup(), before any init()'s, but after Wire.begin(). |
| 23 | + // Only should be called once on any Wire instance to do a software reset, which |
| 24 | + // will affect all devices on that line. This helps when you're constantly rebuilding |
| 25 | + // and re-uploading to ensure all the devices on that line are reset properly. |
| 26 | + void resetDevices() {} |
| 27 | + |
| 28 | + // Called in setup(). The i2c address here is the value of the A0, A1, A2, A3, A4 and |
| 29 | + // A5 pins ONLY, as the class takes care of its internal base address. i2cAddress |
| 30 | + // should be a value between 0 and 61, since only 62 boards can be addressed. |
| 31 | + void init(byte i2cAddress = 0, byte mode = PCA9685_MODE_OUTPUT_ONACK | PCA9685_MODE_OUTPUT_TPOLE) {} |
| 32 | + |
| 33 | + // Min: 24Hz, Max: 1526Hz, Default: 200Hz (resolution widens as Hz goes higher) |
| 34 | + void setPWMFrequency(float pwmFrequency) { _pwmFreq = pwmFrequency; } |
| 35 | + |
| 36 | + // Turns channel either full on or full off |
| 37 | + void setChannelOn(int channel); |
| 38 | + void setChannelOff(int channel); |
| 39 | + |
| 40 | + // PWM amounts 0 - 4096, 0 full off, 4096 full on |
| 41 | + void setChannelPWM(int channel, uint16_t pwmAmount) { _pwmValues.at(channel) = pwmAmount; } |
| 42 | + void setChannelsPWM(int begChannel, int numChannels, const uint16_t* pwmAmounts) |
| 43 | + { |
| 44 | + for (auto i = 0; i < numChannels; ++i) |
| 45 | + _pwmValues.at(i + begChannel) = pwmAmounts[i]; |
| 46 | + } |
| 47 | + |
| 48 | +private: |
| 49 | + float _pwmFreq = 200.0F; |
| 50 | + std::array<uint16_t, 16> _pwmValues = {}; |
| 51 | +}; |
| 52 | + |
| 53 | +// ReSharper restore CppMemberFunctionMayBeStatic |
| 54 | + |
| 55 | +class PCA9685_ServoEvaluator sealed { // NOLINT |
| 56 | +public: |
| 57 | + // Uses a linear interpolation method to quickly compute PWM output value. Uses |
| 58 | + // default values of 2.5% and 12.5% of phase length for -90/+90. |
| 59 | + explicit PCA9685_ServoEvaluator(uint16_t n90PWMAmount = 102, uint16_t p90PWMAmount = 512); |
| 60 | + PCA9685_ServoEvaluator(const PCA9685_ServoEvaluator&) = delete; |
| 61 | + |
| 62 | + // Uses a cubic spline to interpolate due to an offset zero angle that isn't |
| 63 | + // exactly between -90/+90. This takes more time to compute, but gives a more |
| 64 | + // accurate PWM output value along the entire range. |
| 65 | + PCA9685_ServoEvaluator(uint16_t n90PWMAmount, uint16_t zeroPWMAmount, uint16_t p90PWMAmount); |
| 66 | + |
| 67 | + ~PCA9685_ServoEvaluator(); |
| 68 | + |
| 69 | + // Returns the PWM value to use given the angle (-90 to +90) |
| 70 | + uint16_t pwmForAngle(float angle); |
| 71 | + |
| 72 | +private: |
| 73 | + float* _coeff; // a,b,c,d coefficient values |
| 74 | + bool _isCSpline = false; // Cubic spline tracking, for _coeff length |
| 75 | +}; |
0 commit comments