Skip to content

Commit 5bb46c1

Browse files
ServoPID: Add PCA9685 support + mock (coded, not tested)
1 parent 63a08fc commit 5bb46c1

File tree

10 files changed

+294
-19
lines changed

10 files changed

+294
-19
lines changed

ServoPID/ServoPID.sln

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServoPidControl", "ServoPID
99
EndProject
1010
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{CC0AF209-905C-441A-B514-14BEF063E465}"
1111
ProjectSection(SolutionItems) = preProject
12+
PCA9684-Arduino\PCA9685.cpp = PCA9684-Arduino\PCA9685.cpp
13+
PCA9684-Arduino\PCA9685.h = PCA9684-Arduino\PCA9685.h
1214
servopid.ino = servopid.ino
1315
EndProjectSection
1416
EndProject

ServoPID/ServoPID.sln.DotSettings

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
22
<s:Boolean x:Key="/Default/CodeInspection/CodeAnnotations/NamespacesWithAnnotations/=ServoPIDControl_002EAnnotations/@EntryIndexedValue">True</s:Boolean>
3-
<s:Boolean x:Key="/Default/UserDictionary/Words/=Arduino/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
3+
<s:Boolean x:Key="/Default/UserDictionary/Words/=Arduino/@EntryIndexedValue">True</s:Boolean>
4+
<s:Boolean x:Key="/Default/UserDictionary/Words/=coeff/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

ServoPID/TestServoPID/ArduinoMock.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@
66
unsigned long gMicros = 0;
77
std::vector<int> gAnalogPins(256, 0);
88
MockSerial Serial;
9+
MockWire Wire;

ServoPID/TestServoPID/ArduinoMock.h

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
#pragma once
22

3-
// ReSharper disable once CppInconsistentNaming
3+
// ReSharper disable CppInconsistentNaming
4+
// ReSharper disable CppMemberFunctionMayBeStatic
5+
// ReSharper disable CppParameterNeverUsed
6+
47
constexpr const char* F(const char* x) { return x; }
58

69
// Mock globals
@@ -28,7 +31,7 @@ T constrain(const T v, const T min, const T max)
2831

2932
// Mock Servo
3033

31-
class Servo
34+
class MockServo
3235
{
3336
public:
3437
void attach(const int pin, const int min, const int max)
@@ -50,14 +53,14 @@ class Servo
5053
int _angle = 0;
5154
};
5255

56+
typedef MockServo Servo;
57+
5358

5459
class MockSerial
5560
{
5661
public:
57-
// ReSharper disable CppMemberFunctionMayBeStatic
58-
void begin(int baudrate) const {}
62+
void begin(int baudRate) const {}
5963
void end() const {}
60-
// ReSharper restore CppMemberFunctionMayBeStatic
6164

6265
int available() const { return dataRead.size(); }
6366

@@ -86,5 +89,17 @@ class MockSerial
8689
std::stringstream dataWrite;
8790
};
8891

89-
// ReSharper disable once CppInconsistentNaming
9092
extern MockSerial Serial;
93+
94+
class MockWire
95+
{
96+
public:
97+
void begin() const {}
98+
void setClock(const int baudRate) const {}
99+
};
100+
101+
extern MockWire Wire;
102+
103+
// ReSharper restore CppInconsistentNaming
104+
// ReSharper restore CppMemberFunctionMayBeStatic
105+
// ReSharper restore CppParameterNeverUsed
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
#include "pch.h"
2+
3+
#include "ArduinoMock.h"
4+
#include "PCA9685Mock.h"
5+
6+
constexpr uint16_t PCA9685_PWM_FULL = 0x01000; // Special value for full on/full off LEDx modes
7+
8+
// ReSharper disable CppMemberFunctionMayBeConst
9+
10+
PCA9685_ServoEvaluator::PCA9685_ServoEvaluator(uint16_t n90PWMAmount, uint16_t p90PWMAmount)
11+
{
12+
n90PWMAmount = constrain(n90PWMAmount, uint16_t(0), PCA9685_PWM_FULL);
13+
p90PWMAmount = constrain(p90PWMAmount, n90PWMAmount, PCA9685_PWM_FULL);
14+
15+
_coeff = new float[2];
16+
_isCSpline = false;
17+
18+
_coeff[0] = n90PWMAmount;
19+
_coeff[1] = (p90PWMAmount - n90PWMAmount) / 180.0f;
20+
}
21+
22+
PCA9685_ServoEvaluator::PCA9685_ServoEvaluator(uint16_t n90PWMAmount, uint16_t zeroPWMAmount, uint16_t p90PWMAmount)
23+
{
24+
n90PWMAmount = constrain(n90PWMAmount, uint16_t(0), PCA9685_PWM_FULL);
25+
zeroPWMAmount = constrain(zeroPWMAmount, n90PWMAmount, PCA9685_PWM_FULL);
26+
p90PWMAmount = constrain(p90PWMAmount, zeroPWMAmount, PCA9685_PWM_FULL);
27+
28+
if (p90PWMAmount - zeroPWMAmount != zeroPWMAmount - n90PWMAmount)
29+
{
30+
_coeff = new float[8];
31+
_isCSpline = true;
32+
33+
// Cubic spline code adapted from: https://shiftedbits.org/2011/01/30/cubic-spline-interpolation/
34+
/* "THE BEER-WARE LICENSE" (Revision 42): Devin Lane wrote this [part]. As long as you retain
35+
* this notice you can do whatever you want with this stuff. If we meet some day, and you
36+
* think this stuff is worth it, you can buy me a beer in return. */
37+
38+
float x[3] = {0, 90, 180};
39+
float y[3] = {float(n90PWMAmount), float(zeroPWMAmount), static_cast<float>(p90PWMAmount)};
40+
float c[3], b[2], d[2], h[2], l[1], u[2], a[1], z[2]; // n = 3
41+
42+
h[0] = x[1] - x[0];
43+
u[0] = z[0] = 0;
44+
c[2] = 0;
45+
46+
for (int i = 1; i < 2; ++i)
47+
{
48+
h[i] = x[i + 1] - x[i];
49+
l[i - 1] = (2 * (x[i + 1] - x[i - 1])) - h[i - 1] * u[i - 1];
50+
u[i] = h[i] / l[i - 1];
51+
a[i - 1] = (3 / h[i]) * (y[i + 1] - y[i]) - (3 / h[i - 1]) * (y[i] - y[i - 1]);
52+
z[i] = (a[i - 1] - h[i - 1] * z[i - 1]) / l[i - 1];
53+
}
54+
55+
for (int i = 1; i >= 0; --i)
56+
{
57+
c[i] = z[i] - u[i] * c[i + 1];
58+
b[i] = (y[i + 1] - y[i]) / h[i] - (h[i] * (c[i + 1] + 2 * c[i])) / 3;
59+
d[i] = (c[i + 1] - c[i]) / (3 * h[i]);
60+
61+
_coeff[4 * i + 0] = y[i]; // a
62+
_coeff[4 * i + 1] = b[i]; // b
63+
_coeff[4 * i + 2] = c[i]; // c
64+
_coeff[4 * i + 3] = d[i]; // d
65+
}
66+
}
67+
else
68+
{
69+
_coeff = new float[2];
70+
_isCSpline = false;
71+
72+
_coeff[0] = n90PWMAmount;
73+
_coeff[1] = float(p90PWMAmount - n90PWMAmount) / 180.0f;
74+
}
75+
}
76+
77+
PCA9685_ServoEvaluator::~PCA9685_ServoEvaluator()
78+
{
79+
delete[] _coeff;
80+
}
81+
82+
uint16_t PCA9685_ServoEvaluator::pwmForAngle(float angle)
83+
{
84+
float retVal;
85+
angle = constrain(angle + 90, 0.0f, 180.0f);
86+
87+
if (!_isCSpline)
88+
{
89+
retVal = _coeff[0] + (_coeff[1] * angle);
90+
}
91+
else
92+
{
93+
if (angle <= 90)
94+
{
95+
retVal = _coeff[0] + (_coeff[1] * angle) + (_coeff[2] * angle * angle) + (_coeff[3] * angle * angle * angle
96+
);
97+
}
98+
else
99+
{
100+
angle -= 90;
101+
retVal = _coeff[4] + (_coeff[5] * angle) + (_coeff[6] * angle * angle) + (_coeff[7] * angle * angle * angle
102+
);
103+
}
104+
}
105+
106+
return constrain(uint16_t(roundf(retVal)), uint16_t(0), PCA9685_PWM_FULL);
107+
};
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
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+
};

ServoPID/TestServoPID/TestServoPID.vcxproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@
153153
<ItemGroup>
154154
<ClCompile Include="ArduinoMock.cpp" />
155155
<ClCompile Include="Main.cpp" />
156+
<ClCompile Include="PCA9685Mock.cpp" />
156157
<ClCompile Include="pch.cpp">
157158
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Create</PrecompiledHeader>
158159
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Create</PrecompiledHeader>
@@ -166,6 +167,7 @@
166167
</ItemGroup>
167168
<ItemGroup>
168169
<ClInclude Include="ArduinoMock.h" />
170+
<ClInclude Include="PCA9685Mock.h" />
169171
<ClInclude Include="pch.h" />
170172
</ItemGroup>
171173
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />

ServoPID/TestServoPID/TestServoPID.vcxproj.filters

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@
2727
<ClCompile Include="pch.cpp">
2828
<Filter>Source Files</Filter>
2929
</ClCompile>
30+
<ClCompile Include="PCA9685Mock.cpp">
31+
<Filter>Source Files</Filter>
32+
</ClCompile>
3033
</ItemGroup>
3134
<ItemGroup>
3235
<None Include="README.md" />
@@ -38,5 +41,8 @@
3841
<ClInclude Include="pch.h">
3942
<Filter>Header Files</Filter>
4043
</ClInclude>
44+
<ClInclude Include="PCA9685Mock.h">
45+
<Filter>Header Files</Filter>
46+
</ClInclude>
4147
</ItemGroup>
4248
</Project>

ServoPID/TestServoPID/pch.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@
55
#include <vector>
66
#include <string>
77
#include <sstream>
8+
#include <array>
89

0 commit comments

Comments
 (0)