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