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