Skip to content

Commit 188a418

Browse files
committed
Servo: remove pigpio.h
1 parent b173fc5 commit 188a418

1 file changed

Lines changed: 229 additions & 1 deletion

File tree

ArduinoCore-Linux/libraries/Servo.h

Lines changed: 229 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,234 @@
11
#pragma once
22
/*
3-
Servo.h - Servo library for Raspberry Pi using pigpio
3+
Servo.h - Servo library for Raspberry Pi using HardwareGPIO_RPI (sysfs PWM)
4+
Copyright (c) 2025 Phil Schatzmann. All right reserved.
5+
6+
This library is free software; you can redistribute it and/or
7+
modify it under the terms of the GNU Lesser General Public
8+
License as published by the Free Software Foundation; either
9+
version 2.1 of the License, or (at your option) any later version.
10+
11+
This library is distributed in the hope that it will be useful,
12+
but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14+
Lesser General Public License for more details.
15+
16+
You should have received a copy of the GNU Lesser General Public
17+
License along with this library; if not, write to the Free Software
18+
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19+
*/
20+
21+
#if defined(USE_RPI)
22+
#include <stdint.h>
23+
#include <algorithm>
24+
#include <cstdio>
25+
#include "HardwareGPIO_RPI.h"
26+
27+
/// Standard servo pulse range in microseconds
28+
#define SERVO_DEFAULT_MIN_US 544
29+
#define SERVO_DEFAULT_MAX_US 2400
30+
31+
/// Servo PWM frequency: 50 Hz => 20,000,000 ns period
32+
#define SERVO_PERIOD_NS 20000000UL
33+
34+
/// Invalid pin sentinel
35+
#define SERVO_NO_PIN 255
36+
37+
namespace arduino {
38+
39+
/**
40+
* @class Servo
41+
* @brief Arduino-compatible Servo library for Raspberry Pi.
42+
*
43+
* Uses the Linux sysfs PWM interface via HardwareGPIO_RPI.
44+
* Supports hardware PWM pins: 12, 13, 18, 19 (BCM numbering).
45+
*
46+
* The servo period is fixed at 50 Hz (20 ms). Pulse widths in the range
47+
* SERVO_DEFAULT_MIN_US–SERVO_DEFAULT_MAX_US map to 0–180 degrees.
48+
*
49+
* @note Call gpio.begin() on your HardwareGPIO_RPI instance before
50+
* attaching any servos.
51+
*
52+
* Usage:
53+
* @code
54+
* HardwareGPIO_RPI gpio;
55+
* gpio.begin();
56+
* Servo myServo(gpio);
57+
* myServo.attach(18);
58+
* myServo.write(90);
59+
* @endcode
60+
*/
61+
class Servo {
62+
public:
63+
/**
64+
* @brief Constructor. Requires a reference to an initialised HardwareGPIO_RPI.
65+
*/
66+
Servo(HardwareGPIO_RPI& gpio)
67+
: _gpio(gpio),
68+
_pin(SERVO_NO_PIN),
69+
_min(SERVO_DEFAULT_MIN_US),
70+
_max(SERVO_DEFAULT_MAX_US),
71+
_pulseUs((SERVO_DEFAULT_MIN_US + SERVO_DEFAULT_MAX_US) / 2) {}
72+
73+
~Servo() {
74+
if (attached()) detach();
75+
}
76+
77+
/**
78+
* @brief Attach the servo to a GPIO pin using default pulse range.
79+
* @param pin GPIO pin number (BCM), must be a hardware PWM pin (12,13,18,19).
80+
* @return 0 on success.
81+
*/
82+
uint8_t attach(int pin) {
83+
return attach(pin, SERVO_DEFAULT_MIN_US, SERVO_DEFAULT_MAX_US);
84+
}
85+
86+
/**
87+
* @brief Attach the servo to a GPIO pin with custom min/max pulse widths.
88+
* @param pin GPIO pin number (BCM), must be a hardware PWM pin (12,13,18,19).
89+
* @param min Minimum pulse width in microseconds (corresponds to 0°).
90+
* @param max Maximum pulse width in microseconds (corresponds to 180°).
91+
* @return 0 on success.
92+
*/
93+
uint8_t attach(int pin, int min, int max) {
94+
_pin = pin;
95+
_min = min;
96+
_max = max;
97+
_gpio.pinMode(_pin, OUTPUT);
98+
// Fix PWM frequency to 50 Hz for servo control
99+
_gpio.analogWriteFrequency(_pin, 50);
100+
// Write current position
101+
_writePulse(_pulseUs);
102+
return 0;
103+
}
104+
105+
/**
106+
* @brief Detach the servo: set duty cycle to 0 and release the pin.
107+
*/
108+
void detach() {
109+
if (attached()) {
110+
_writePulse(0);
111+
_pin = SERVO_NO_PIN;
112+
}
113+
}
114+
115+
/**
116+
* @brief Write a position to the servo.
117+
* @param value Angle in degrees (0–180), or pulse width in µs if > 200.
118+
*/
119+
void write(int value) {
120+
if (value <= 200) {
121+
value = std::max(0, std::min(value, 180));
122+
writeMicroseconds(_angleToPulse(value));
123+
} else {
124+
writeMicroseconds(value);
125+
}
126+
}
127+
128+
/**
129+
* @brief Write a pulse width directly in microseconds.
130+
* @param us Pulse width in microseconds.
131+
*/
132+
void writeMicroseconds(int us) {
133+
_pulseUs = std::max(_min, std::min(us, _max));
134+
if (attached()) _writePulse(_pulseUs);
135+
}
136+
137+
/**
138+
* @brief Read the current servo position in degrees (0–180).
139+
*/
140+
int read() const {
141+
return _pulseToAngle(_pulseUs);
142+
}
143+
144+
/**
145+
* @brief Read the current pulse width in microseconds.
146+
*/
147+
int readMicroseconds() const {
148+
return _pulseUs;
149+
}
150+
151+
/**
152+
* @brief Check if the servo is attached to a pin.
153+
*/
154+
bool attached() const {
155+
return _pin != SERVO_NO_PIN;
156+
}
157+
158+
private:
159+
HardwareGPIO_RPI& _gpio;
160+
int _pin;
161+
int _min;
162+
int _max;
163+
int _pulseUs;
164+
165+
/// Map angle (0–180°) to pulse width in µs
166+
int _angleToPulse(int angle) const {
167+
return _min + (angle * (_max - _min)) / 180;
168+
}
169+
170+
/// Map pulse width in µs to angle (0–180°)
171+
int _pulseToAngle(int us) const {
172+
if (_max == _min) return 0;
173+
int angle = ((us - _min) * 180) / (_max - _min);
174+
return std::max(0, std::min(angle, 180));
175+
}
176+
177+
/**
178+
* @brief Write pulse width directly to sysfs PWM duty_cycle.
179+
*
180+
* HardwareGPIO_RPI::analogWrite() maps a 0–max_value integer to a
181+
* duty_cycle fraction of the period. For servo control we need the
182+
* duty cycle expressed as a fraction of the 20 ms period.
183+
*
184+
* We bypass analogWrite() and write the duty_cycle sysfs file directly
185+
* so we can specify an exact nanosecond value.
186+
*
187+
* @param us Pulse width in microseconds (0 = off).
188+
*/
189+
void _writePulse(int us) {
190+
// Determine sysfs PWM channel for the pin
191+
int pwm_channel = -1;
192+
if (_pin == 18 || _pin == 12) pwm_channel = 0;
193+
else if (_pin == 19 || _pin == 13) pwm_channel = 1;
194+
if (pwm_channel < 0) return;
195+
196+
// Ensure channel is exported
197+
char export_path[64];
198+
snprintf(export_path, sizeof(export_path),
199+
"/sys/class/pwm/pwmchip0/export");
200+
FILE* fexp = fopen(export_path, "w");
201+
if (fexp) { fprintf(fexp, "%d", pwm_channel); fclose(fexp); }
202+
203+
char period_path[64], duty_path[64], enable_path[64];
204+
snprintf(period_path, sizeof(period_path),
205+
"/sys/class/pwm/pwmchip0/pwm%d/period", pwm_channel);
206+
snprintf(duty_path, sizeof(duty_path),
207+
"/sys/class/pwm/pwmchip0/pwm%d/duty_cycle", pwm_channel);
208+
snprintf(enable_path, sizeof(enable_path),
209+
"/sys/class/pwm/pwmchip0/pwm%d/enable", pwm_channel);
210+
211+
// Set period to 20,000,000 ns (50 Hz)
212+
FILE* fp = fopen(period_path, "w");
213+
if (fp) { fprintf(fp, "%lu", SERVO_PERIOD_NS); fclose(fp); }
214+
215+
// Convert µs to ns and write duty cycle
216+
unsigned long duty_ns = (us > 0) ? (unsigned long)us * 1000UL : 0UL;
217+
FILE* fd = fopen(duty_path, "w");
218+
if (fd) { fprintf(fd, "%lu", duty_ns); fclose(fd); }
219+
220+
// Enable (or disable if us==0)
221+
FILE* fe = fopen(enable_path, "w");
222+
if (fe) { fprintf(fe, "%d", us > 0 ? 1 : 0); fclose(fe); }
223+
}
224+
};
225+
226+
} // namespace arduino
227+
228+
// Pull into global namespace for Arduino sketch compatibility
229+
using arduino::Servo;
230+
231+
#endif // USE_RPI
4232
Copyright (c) 2025 Phil Schatzmann. All right reserved.
5233

6234
This library is free software; you can redistribute it and/or

0 commit comments

Comments
 (0)