blob: d7b44d4d61001bb1c56fa26cbe4bd7a0a17dbdc5 [file] [log] [blame]
#ifndef Y2022_VISION_GPIO_H_
#define Y2022_VISION_GPIO_H_
/* Modified from: https://elinux.org/RPi_GPIO_Code_Samples
* Raspberry Pi GPIO example using sysfs interface.
*/
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include "aos/init.h"
namespace y2022::vision {
using namespace frc971::vision;
// Physical pin 19 maps to sysfs pin 10
// This pin is MOSI, being used to control the LED Disable
static constexpr int GPIO_PIN_MOSI_DISABLE = 10;
// Physical pin 23 maps to sysfs pin 11
// This pin is SCLK, and is used to talk to the IMU
// To drive the lights, we have to set this pin to an input
static constexpr int GPIO_PIN_SCLK_IMU = 11;
// Physical pin 33 maps to sysfs pin 13
// This pin is SCK, being used to control the LED PWM
static constexpr int GPIO_PIN_SCK_PWM = 13;
static constexpr int BUFFER_MAX = 3;
static constexpr int VALUE_MAX = 30;
// Is GPIO an input (read) or output (write)
static constexpr int kGPIOIn = 0;
static constexpr int kGPIOOut = 1;
// Pin voltage level low or high
static constexpr int kGPIOLow = 0;
static constexpr int kGPIOHigh = 1;
class GPIOControl {
public:
GPIOControl(int pin, int direction, int default_value = kGPIOLow)
: pin_(pin), direction_(direction), default_value_(default_value) {
// Set up the pin
GPIOExport();
GPIODirection();
// Get the file descriptor used to read / write
gpio_rw_fd_ = GPIOGetFileDesc();
// If it's an output, set it in the appropriate default state
if (direction_ == kGPIOOut) {
GPIOWrite(default_value_);
}
}
~GPIOControl() {
if (direction_ == kGPIOOut) {
// Set pin to "default" value (e.g., turn off before closing)
GPIOWrite(default_value_);
}
close(gpio_rw_fd_);
GPIOUnexport();
}
//
// Set up pin for active use ("export" it)
// Keeping this static method that allows exporting a specific pin #
//
static int GPIOExport(int pin) {
VLOG(2) << "GPIOExport of pin " << pin;
char buffer[BUFFER_MAX];
size_t bytes_to_write, bytes_written;
int fd = open("/sys/class/gpio/export", O_WRONLY);
if (-1 == fd) {
LOG(INFO) << "Failed to open export for writing!";
return (-1);
}
bytes_to_write = snprintf(buffer, BUFFER_MAX, "%d", pin);
bytes_written = write(fd, buffer, bytes_to_write);
if (bytes_to_write != bytes_written) {
LOG(INFO) << "Issue exporting GPIO pin " << pin
<< ". May be OK if pin already exported";
}
close(fd);
// Adding this sleep, since we get some sort of race condition
// if we try to act on this too soon after export
std::this_thread::sleep_for(std::chrono::milliseconds(50));
return 0;
}
// For calling pin defined by this class
int GPIOExport() { return GPIOExport(pin_); }
//
// Remove pin from active use ("unexport" it)
// Keeping this static method that allows unexporting of a specific pin #
//
static int GPIOUnexport(int pin) {
VLOG(2) << "GPIOUnexport of pin " << pin;
int fd = open("/sys/class/gpio/unexport", O_WRONLY);
if (-1 == fd) {
LOG(INFO) << "Failed to open unexport for writing!";
return (-1);
}
char buffer[BUFFER_MAX];
ssize_t bytes_to_write, bytes_written;
bytes_to_write = snprintf(buffer, BUFFER_MAX, "%d", pin);
bytes_written = write(fd, buffer, bytes_to_write);
if (bytes_to_write != bytes_written) {
LOG(INFO) << "Issue unexporting GPIO pin " << pin
<< ". May be OK if pin already unexported";
}
close(fd);
return 0;
}
int GPIOUnexport() { return GPIOUnexport(pin_); }
//
// Set input / output direction of a pin
// Keeping this static method that allows setting direction of a specific pin
//
static int GPIODirection(int pin, int dir) {
VLOG(2) << "Setting GPIODirection for pin " << pin << " to " << dir;
constexpr int DIRECTION_MAX = 50;
char path[DIRECTION_MAX];
snprintf(path, DIRECTION_MAX, "/sys/class/gpio/gpio%d/direction", pin);
int fd = open(path, O_WRONLY);
if (-1 == fd) {
LOG(ERROR) << "Failed to open gpio direction for writing!\nPath = "
<< path;
return (-1);
}
if (-1 == write(fd, ((kGPIOIn == dir) ? "in" : "out"),
((kGPIOIn == dir) ? 2 : 3))) {
LOG(ERROR) << "Failed to set direction!";
return (-1);
}
close(fd);
// TODO: See if we can remove this sleep
std::this_thread::sleep_for(std::chrono::milliseconds(50));
return 0;
}
// Call for internal use of pin_ and direction_
int GPIODirection() { return GPIODirection(pin_, direction_); }
//
// Read pin
// Keeping this static method that allows reading of a specific pin with no
// file descriptor open
//
static int GPIORead(int pin, int fd = -1) {
char value_str[3];
bool need_to_close_fd = false;
if (fd == -1) {
char path[VALUE_MAX];
snprintf(path, VALUE_MAX, "/sys/class/gpio/gpio%d/value", pin);
fd = open(path, O_RDONLY);
if (-1 == fd) {
LOG(ERROR) << "Failed to open gpio value for reading on pin " << pin;
return (-1);
}
need_to_close_fd = true;
}
if (-1 == read(fd, value_str, 3)) {
LOG(ERROR) << "Failed to read value on pin " << pin;
return (-1);
}
if (need_to_close_fd) {
close(fd);
}
return (atoi(value_str));
}
int GPIORead() { return GPIORead(pin_, gpio_rw_fd_); }
//
// Write to pin
// Keeping this static method that allows writing to a specific pin with no
// file descriptor open
//
static int GPIOWrite(int pin, int value, int fd = -1) {
static const char s_values_str[] = "01";
bool need_to_close_fd = false;
if (fd == -1) {
char path[VALUE_MAX];
snprintf(path, VALUE_MAX, "/sys/class/gpio/gpio%d/value", pin);
fd = open(path, O_WRONLY);
if (-1 == fd) {
LOG(ERROR) << "Failed to open gpio pin " << pin
<< " for reading / writing";
return (-1);
}
LOG(INFO) << "Opened fd as " << fd;
need_to_close_fd = true;
}
if (1 != write(fd, &s_values_str[(kGPIOLow == value) ? 0 : 1], 1)) {
LOG(ERROR) << "Failed to write value " << value << " to pin " << pin;
return (-1);
}
if (need_to_close_fd) {
close(fd);
}
return 0;
}
int GPIOWrite(int value) { return GPIOWrite(pin_, value, gpio_rw_fd_); }
int GPIOGetFileDesc() {
char path[VALUE_MAX];
snprintf(path, VALUE_MAX, "/sys/class/gpio/gpio%d/value", pin_);
int fd = open(path, O_WRONLY);
if (-1 == fd) {
LOG(ERROR) << "Failed to open gpio pin " << pin_
<< " for reading / writing";
return (-1);
}
return fd;
}
// pin # to read / write to
int pin_;
// direction for reading / writing
int direction_;
// A default ("safe") value to set the pin to on start / finish
int default_value_;
// file descriptor for reading / writing
int gpio_rw_fd_;
};
// Control GPIO pin using HW Control
// A bit hacky-- uses system call to gpio library
class GPIOPWMControl {
public:
GPIOPWMControl(int pin, double duty_cycle)
: pin_(pin), gpio_control_(GPIOControl(pin_, kGPIOOut, kGPIOLow)) {
VLOG(2) << "Using gpio command-based PWM control";
// Set pin to PWM mode
std::string cmd = "gpio -g mode " + std::to_string(pin_) + " pwm";
int ret = std::system(cmd.c_str());
CHECK_EQ(ret, 0) << "cmd: " + cmd + " failed with return value " << ret;
// Set PWM mode in Mark-Space mode (duty cycle is on, then off)
cmd = "gpio pwm-ms";
ret = std::system(cmd.c_str());
CHECK_EQ(ret, 0) << "cmd: " + cmd + " failed with return value " << ret;
// Our PWM clock is going at 19.2 MHz
// With a clock divisor of 192, we get a clock tick of 100kHz
// or 10 us / tick
cmd = "gpio pwmc 192";
ret = std::system(cmd.c_str());
CHECK_EQ(ret, 0) << "cmd: " + cmd + " failed with return value " << ret;
// With a range of 100, and 10 us / tick, this gives
// a period of 100*10us = 1ms, or 1kHz frequency
cmd = "gpio pwmr " + std::to_string(kRange);
ret = std::system(cmd.c_str());
CHECK_EQ(ret, 0) << "cmd: " + cmd + " failed with return value " << ret;
setPWMDutyCycle(duty_cycle);
};
~GPIOPWMControl() { setPWMDutyCycle(0.0); }
int setPWMDutyCycle(double duty_cycle) {
CHECK_GE(duty_cycle, 0.0) << "Duty Cycle must be between 0 and 1";
CHECK_LE(duty_cycle, 1.0) << "Duty Cycle must be between 0 and 1";
int val = static_cast<int>(duty_cycle * kRange);
std::string cmd =
"gpio -g pwm " + std::to_string(pin_) + " " + std::to_string(val);
int ret = std::system(cmd.c_str());
CHECK_EQ(ret, 0) << "cmd: " + cmd + " failed with return value " << ret;
// TODO: Maybe worth doing error handling / return
return ret;
}
static constexpr int kRange = 100;
int pin_;
GPIOControl gpio_control_;
};
// Hack up a SW controlled PWM, in case we need it
class GPIOSWPWMControl {
public:
GPIOSWPWMControl(aos::ShmEventLoop *event_loop, int pin, double frequency,
double duty_cycle)
: event_loop_(event_loop),
pin_(pin),
frequency_(frequency),
duty_cycle_(duty_cycle),
leds_on_(false),
gpio_control_(GPIOControl(pin_, kGPIOOut, kGPIOLow)),
pwm_timer_(event_loop_->AddTimer([this]() {
gpio_control_.GPIOWrite(leds_on_ ? kGPIOHigh : kGPIOLow);
int period_us = static_cast<int>(1000000.0 / frequency_);
int on_time_us =
static_cast<int>(duty_cycle_ * 1000000.0 / frequency_);
// Trigger the next change
// If the leds are currently off, turn them on for duty_cycle % of
// period If they are currently on, turn them off for 1 -
// duty_cycle % of period
pwm_timer_->Schedule(
event_loop_->monotonic_now() +
std::chrono::microseconds(leds_on_ ? (period_us - on_time_us)
: on_time_us));
leds_on_ = !leds_on_;
})) {
pwm_timer_->Schedule(event_loop_->monotonic_now());
};
void set_duty_cycle(double duty_cycle) { duty_cycle_ = duty_cycle; }
void set_frequency(double frequency) { frequency_ = frequency; }
aos::ShmEventLoop *const event_loop_;
int pin_;
double frequency_;
double duty_cycle_;
bool leds_on_;
GPIOControl gpio_control_;
aos::TimerHandler *const pwm_timer_;
};
} // namespace y2022::vision
#endif // Y2022_VISION_GPIO_H_