blob: 6ed7328515944abd67c9bdb3114c5e4b1da3362e [file] [log] [blame]
Jim Ostrowski2a483b32022-02-15 18:19:14 -08001#ifndef Y2022_VISION_GPIO_H_
2#define Y2022_VISION_GPIO_H_
3
4/* Modified from: https://elinux.org/RPi_GPIO_Code_Samples
5 * Raspberry Pi GPIO example using sysfs interface.
6 */
7
8#include <fcntl.h>
9#include <stdio.h>
10#include <stdlib.h>
11#include <sys/stat.h>
12#include <sys/types.h>
13#include <unistd.h>
14
15#include "aos/init.h"
16
17namespace y2022 {
18namespace vision {
19
20using namespace frc971::vision;
21
22// Physical pin 19 maps to sysfs pin 10
23// This pin is MOSI, being used to control the LED Disable
24static constexpr int GPIO_PIN_MOSI_DISABLE = 10;
25
Jim Ostrowskia49d20e2022-02-26 18:34:05 -080026// Physical pin 23 maps to sysfs pin 11
27// This pin is SCLK, and is used to talk to the IMU
28// To drive the lights, we have to set this pin to an input
29static constexpr int GPIO_PIN_SCLK_IMU = 11;
30
Jim Ostrowski2a483b32022-02-15 18:19:14 -080031// Physical pin 33 maps to sysfs pin 13
32// This pin is SCK, being used to control the LED PWM
33static constexpr int GPIO_PIN_SCK_PWM = 13;
34
35static constexpr int BUFFER_MAX = 3;
36static constexpr int VALUE_MAX = 30;
37
38// Is GPIO an input (read) or output (write)
39static constexpr int kGPIOIn = 0;
40static constexpr int kGPIOOut = 1;
41
42// Pin voltage level low or high
43static constexpr int kGPIOLow = 0;
44static constexpr int kGPIOHigh = 1;
45
46class GPIOControl {
47 public:
48 GPIOControl(int pin, int direction, int default_value = kGPIOLow)
49 : pin_(pin), direction_(direction), default_value_(default_value) {
50 // Set up the pin
51 GPIOExport();
52 GPIODirection();
53
54 // Get the file descriptor used to read / write
55 gpio_rw_fd_ = GPIOGetFileDesc();
56
57 // If it's an output, set it in the appropriate default state
58 if (direction_ == kGPIOOut) {
59 GPIOWrite(default_value_);
60 }
61 }
62
63 ~GPIOControl() {
64 if (direction_ == kGPIOOut) {
65 // Set pin to "default" value (e.g., turn off before closing)
66 GPIOWrite(default_value_);
67 }
68
69 close(gpio_rw_fd_);
70 GPIOUnexport();
71 }
72
73 //
74 // Set up pin for active use ("export" it)
75 // Keeping this static method that allows exporting a specific pin #
76 //
77 static int GPIOExport(int pin) {
78 VLOG(2) << "GPIOExport of pin " << pin;
79
80 char buffer[BUFFER_MAX];
81 size_t bytes_to_write, bytes_written;
82
83 int fd = open("/sys/class/gpio/export", O_WRONLY);
84 if (-1 == fd) {
85 LOG(INFO) << "Failed to open export for writing!";
86 return (-1);
87 }
88
89 bytes_to_write = snprintf(buffer, BUFFER_MAX, "%d", pin);
90 bytes_written = write(fd, buffer, bytes_to_write);
91 if (bytes_to_write != bytes_written) {
92 LOG(INFO) << "Issue exporting GPIO pin " << pin
93 << ". May be OK if pin already exported";
94 }
95
96 close(fd);
97
98 // Adding this sleep, since we get some sort of race condition
99 // if we try to act on this too soon after export
100 std::this_thread::sleep_for(std::chrono::milliseconds(50));
101 return 0;
102 }
103
104 // For calling pin defined by this class
105 int GPIOExport() { return GPIOExport(pin_); }
106
107 //
108 // Remove pin from active use ("unexport" it)
109 // Keeping this static method that allows unexporting of a specific pin #
110 //
111 static int GPIOUnexport(int pin) {
112 VLOG(2) << "GPIOUnexport of pin " << pin;
113
114 int fd = open("/sys/class/gpio/unexport", O_WRONLY);
115 if (-1 == fd) {
116 LOG(INFO) << "Failed to open unexport for writing!";
117 return (-1);
118 }
119
120 char buffer[BUFFER_MAX];
121 ssize_t bytes_to_write, bytes_written;
122 bytes_to_write = snprintf(buffer, BUFFER_MAX, "%d", pin);
123 bytes_written = write(fd, buffer, bytes_to_write);
124 if (bytes_to_write != bytes_written) {
125 LOG(INFO) << "Issue unexporting GPIO pin " << pin
126 << ". May be OK if pin already unexported";
127 }
128
129 close(fd);
130 return 0;
131 }
132
133 int GPIOUnexport() { return GPIOUnexport(pin_); }
134
135 //
136 // Set input / output direction of a pin
137 // Keeping this static method that allows setting direction of a specific pin
138 //
139 static int GPIODirection(int pin, int dir) {
140 VLOG(2) << "Setting GPIODirection for pin " << pin << " to " << dir;
141 constexpr int DIRECTION_MAX = 50;
142 char path[DIRECTION_MAX];
143
144 snprintf(path, DIRECTION_MAX, "/sys/class/gpio/gpio%d/direction", pin);
145 int fd = open(path, O_WRONLY);
146 if (-1 == fd) {
147 LOG(ERROR) << "Failed to open gpio direction for writing!\nPath = "
148 << path;
149 return (-1);
150 }
151
152 if (-1 == write(fd, ((kGPIOIn == dir) ? "in" : "out"),
153 ((kGPIOIn == dir) ? 2 : 3))) {
154 LOG(ERROR) << "Failed to set direction!";
155 return (-1);
156 }
157
158 close(fd);
159 // TODO: See if we can remove this sleep
160 std::this_thread::sleep_for(std::chrono::milliseconds(50));
161 return 0;
162 }
163
164 // Call for internal use of pin_ and direction_
165 int GPIODirection() { return GPIODirection(pin_, direction_); }
166
167 //
168 // Read pin
169 // Keeping this static method that allows reading of a specific pin with no
170 // file descriptor open
171 //
172 static int GPIORead(int pin, int fd = -1) {
173 char value_str[3];
174 bool need_to_close_fd = false;
175
176 if (fd == -1) {
177 char path[VALUE_MAX];
178
179 snprintf(path, VALUE_MAX, "/sys/class/gpio/gpio%d/value", pin);
180 fd = open(path, O_RDONLY);
181 if (-1 == fd) {
182 LOG(ERROR) << "Failed to open gpio value for reading on pin " << pin;
183 return (-1);
184 }
185 need_to_close_fd = true;
186 }
187
188 if (-1 == read(fd, value_str, 3)) {
189 LOG(ERROR) << "Failed to read value on pin " << pin;
190 return (-1);
191 }
192
193 if (need_to_close_fd) {
194 close(fd);
195 }
196
197 return (atoi(value_str));
198 }
199
200 int GPIORead() { return GPIORead(pin_, gpio_rw_fd_); }
201
202 //
203 // Write to pin
204 // Keeping this static method that allows writing to a specific pin with no
205 // file descriptor open
206 //
207 static int GPIOWrite(int pin, int value, int fd = -1) {
208 static const char s_values_str[] = "01";
209 bool need_to_close_fd = false;
210
211 if (fd == -1) {
212 char path[VALUE_MAX];
213 snprintf(path, VALUE_MAX, "/sys/class/gpio/gpio%d/value", pin);
214 fd = open(path, O_WRONLY);
215 if (-1 == fd) {
216 LOG(ERROR) << "Failed to open gpio pin " << pin
217 << " for reading / writing";
218 return (-1);
219 }
220 LOG(INFO) << "Opened fd as " << fd;
221 need_to_close_fd = true;
222 }
223
224 if (1 != write(fd, &s_values_str[(kGPIOLow == value) ? 0 : 1], 1)) {
225 LOG(ERROR) << "Failed to write value " << value << " to pin " << pin;
226 return (-1);
227 }
228
229 if (need_to_close_fd) {
230 close(fd);
231 }
232 return 0;
233 }
234
235 int GPIOWrite(int value) { return GPIOWrite(pin_, value, gpio_rw_fd_); }
236
237 int GPIOGetFileDesc() {
238 char path[VALUE_MAX];
239 snprintf(path, VALUE_MAX, "/sys/class/gpio/gpio%d/value", pin_);
240 int fd = open(path, O_WRONLY);
241 if (-1 == fd) {
242 LOG(ERROR) << "Failed to open gpio pin " << pin_
243 << " for reading / writing";
244 return (-1);
245 }
246 return fd;
247 }
248
249 // pin # to read / write to
250 int pin_;
251 // direction for reading / writing
252 int direction_;
253 // A default ("safe") value to set the pin to on start / finish
254 int default_value_;
255 // file descriptor for reading / writing
256 int gpio_rw_fd_;
257};
258
259// Control GPIO pin using HW Control
260// A bit hacky-- uses system call to gpio library
261
262class GPIOPWMControl {
263 public:
264 GPIOPWMControl(int pin, double duty_cycle)
265 : pin_(pin), gpio_control_(GPIOControl(pin_, kGPIOOut, kGPIOLow)) {
266 VLOG(2) << "Using gpio command-based PWM control";
267 // Set pin to PWM mode
268 std::string cmd = "gpio -g mode " + std::to_string(pin_) + " pwm";
269 int ret = std::system(cmd.c_str());
270 CHECK_EQ(ret, 0) << "cmd: " + cmd + " failed with return value " << ret;
271
272 // Set PWM mode in Mark-Space mode (duty cycle is on, then off)
273 cmd = "gpio pwm-ms";
274 ret = std::system(cmd.c_str());
275 CHECK_EQ(ret, 0) << "cmd: " + cmd + " failed with return value " << ret;
276
277 // Our PWM clock is going at 19.2 MHz
278 // With a clock divisor of 192, we get a clock tick of 100kHz
279 // or 10 us / tick
280 cmd = "gpio pwmc 192";
281 ret = std::system(cmd.c_str());
282 CHECK_EQ(ret, 0) << "cmd: " + cmd + " failed with return value " << ret;
283
284 // With a range of 100, and 10 us / tick, this gives
285 // a period of 100*10us = 1ms, or 1kHz frequency
286 cmd = "gpio pwmr " + std::to_string(kRange);
287 ret = std::system(cmd.c_str());
288 CHECK_EQ(ret, 0) << "cmd: " + cmd + " failed with return value " << ret;
289
290 setPWMDutyCycle(duty_cycle);
291 };
292
293 ~GPIOPWMControl() { setPWMDutyCycle(0.0); }
294
295 int setPWMDutyCycle(double duty_cycle) {
296 CHECK_GE(duty_cycle, 0.0) << "Duty Cycle must be between 0 and 1";
297 CHECK_LE(duty_cycle, 1.0) << "Duty Cycle must be between 0 and 1";
298 int val = static_cast<int>(duty_cycle * kRange);
299 std::string cmd =
300 "gpio -g pwm " + std::to_string(pin_) + " " + std::to_string(val);
301 int ret = std::system(cmd.c_str());
302 CHECK_EQ(ret, 0) << "cmd: " + cmd + " failed with return value " << ret;
303
304 // TODO: Maybe worth doing error handling / return
305 return ret;
306 }
307
308 static constexpr int kRange = 100;
309 int pin_;
310 GPIOControl gpio_control_;
311};
312
313// Hack up a SW controlled PWM, in case we need it
314
315class GPIOSWPWMControl {
316 public:
317 GPIOSWPWMControl(aos::ShmEventLoop *event_loop, int pin, double frequency,
318 double duty_cycle)
319 : event_loop_(event_loop),
320 pin_(pin),
321 frequency_(frequency),
322 duty_cycle_(duty_cycle),
323 leds_on_(false),
324 gpio_control_(GPIOControl(pin_, kGPIOOut, kGPIOLow)),
325 pwm_timer_(event_loop_->AddTimer([this]() {
326 gpio_control_.GPIOWrite(leds_on_ ? kGPIOHigh : kGPIOLow);
327 int period_us = static_cast<int>(1000000.0 / frequency_);
328 int on_time_us =
329 static_cast<int>(duty_cycle_ * 1000000.0 / frequency_);
330
331 // Trigger the next change
332 // If the leds are currently off, turn them on for duty_cycle % of
333 // period If they are currently on, turn them off for 1 -
334 // duty_cycle % of period
Philipp Schradera6712522023-07-05 20:25:11 -0700335 pwm_timer_->Schedule(
Jim Ostrowski2a483b32022-02-15 18:19:14 -0800336 event_loop_->monotonic_now() +
337 std::chrono::microseconds(leds_on_ ? (period_us - on_time_us)
338 : on_time_us));
339 leds_on_ = !leds_on_;
340 })) {
Philipp Schradera6712522023-07-05 20:25:11 -0700341 pwm_timer_->Schedule(event_loop_->monotonic_now());
Jim Ostrowski2a483b32022-02-15 18:19:14 -0800342 };
343
344 void set_duty_cycle(double duty_cycle) { duty_cycle_ = duty_cycle; }
345 void set_frequency(double frequency) { frequency_ = frequency; }
346
347 aos::ShmEventLoop *const event_loop_;
348 int pin_;
349 double frequency_;
350 double duty_cycle_;
351 bool leds_on_;
352 GPIOControl gpio_control_;
353 aos::TimerHandler *const pwm_timer_;
354};
355
356} // namespace vision
357} // namespace y2022
358#endif // Y2022_VISION_GPIO_H_