-
Notifications
You must be signed in to change notification settings - Fork 2
/
pca9685.cpp
220 lines (195 loc) · 5.94 KB
/
pca9685.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
#include <memory>
#include <unistd.h>
#include <fcntl.h>
#include <linux/i2c-dev.h>
#include <sys/ioctl.h>
#include "rclcpp/rclcpp.hpp"
#include "bot_hardware/msg/control.hpp"
using std::placeholders::_1;
#define I2C_ADDRESS 0x40
#define PCA9685_MODE1 0x00
#define PCA9685_MODE2 0x01
#define PCA9685_LED0_ON_L 0x06
#define PCA9685_PRESCALE 0xFE
#define THROTTLE_PWM_CHANNEL 0
#define STEERING_PWM_CHANNEL 1
/**
* @brief This class can be used to map a float
* value between -1 and 1 to int value for the
* servos based on the PWM frequency.
*/
class Float2ServoMap
{
public:
using Ptr = std::shared_ptr<Float2ServoMap>;
/**
* @brief builder function
*
* @param[in] freq Desired frequency of the PWM signal
* @param[in] minMiliSec Minimum high time of the PWM singal in milliseconds
* @param[in] maxMiliSec Maximum hight time of the PWM singnal in milliseconds
* @param[in] maxPWM The maximum value for the PWM (defining the resolution for PWM signal generator)
* @return Float2ServoMap::Ptr
*/
static Float2ServoMap::Ptr create(float freq, float minMiliSec, float maxMiliSec, unsigned int maxPWM)
{
return std::make_shared<Float2ServoMap>(freq, minMiliSec, maxMiliSec, maxPWM);
}
float calPwm(float f)
{
return m_ * f + b_;
}
Float2ServoMap(float freq, float minMiliSec, float maxMiliSec, unsigned int maxPWM):
m_(0), b_(0)
{
float ms_2_percent = float(freq) / 1000.0 /* ms */;
float minPWMv = minMiliSec * ms_2_percent * maxPWM;
float maxPWMv = maxMiliSec * ms_2_percent * maxPWM;
float Fmin = -1.0;
float Fmax = 1.0;
m_ = (maxPWMv - minPWMv) / (Fmax - Fmin);
b_ = minPWMv - m_ * Fmin;
}
private:
float m_, b_;
};
class PCA9685 : public rclcpp::Node
{
public:
PCA9685() : Node("pca9685")
{
init_hardware();
control_subscription_ = this->create_subscription<bot_hardware::msg::Control>(
"/control", 10, std::bind(&PCA9685::control_callback, this, _1));
}
~PCA9685()
{
// Close the I2C device
set_pwm(0, 0, 0);
set_pwm(1, 0, 0);
close(i2c_fd_);
}
private:
void init_hardware()
{
// Set the PWM frequency
pwm_freq_ = 60.0;
steeringMap = Float2ServoMap::create(pwm_freq_, 1.0, 2.0, 4095u);
throttleMap = Float2ServoMap::create(pwm_freq_, 1.0, 2.0, 4095u);
// Get the I2C device file descriptor
i2c_fd_ = open("/dev/i2c-1", O_RDWR);
while (i2c_fd_ < 0)
{
RCUTILS_LOG_ERROR_NAMED(get_name(), "Failed to open I2C device: %d, trying again in 1 sec.", i2c_fd_);
usleep(1000000);
i2c_fd_ = open("/dev/i2c-1", O_RDWR);
}
// Set the I2C slave address
dev_address_ = I2C_ADDRESS;
if (ioctl(i2c_fd_, I2C_SLAVE, dev_address_) < 0)
{
RCUTILS_LOG_ERROR_NAMED(get_name(), "Failed to set I2C slave address");
return;
}
set_frequency(pwm_freq_);
// Set the default PWM values
set_pwm(0, 0, 0);
set_pwm(1, 0, 0);
}
// Set the PWM frequency (in Hz)
void set_frequency(uint16_t frequency)
{
uint8_t prescale = (uint8_t)(std::round(25000000.0 / (4096.0 * frequency)) - 1);
write_reg(PCA9685_MODE1, 0b00010000);
usleep(1000);
write_reg(PCA9685_PRESCALE, prescale);
write_reg(PCA9685_MODE1, 0b10100000);
usleep(1000);
// write_reg(PCA9685_MODE2, 0x00);
// usleep(1000);
}
// Set the PWM duty cycle for a specific channel
void set_pwm(uint8_t channel, uint16_t on_time, uint16_t off_time)
{
uint8_t reg_offset = PCA9685_LED0_ON_L + 4 * channel;
uint8_t on_time_low = on_time & 0xFF;
uint8_t on_time_high = (on_time >> 8) & 0x0F;
uint8_t off_time_low = off_time & 0xFF;
uint8_t off_time_high = (off_time >> 8) & 0x0F;
write_reg(reg_offset, on_time_low);
write_reg(reg_offset + 1, on_time_high);
write_reg(reg_offset + 2, off_time_low);
write_reg(reg_offset + 3, off_time_high);
}
void write_reg(uint8_t reg, uint8_t data)
{
uint8_t buf[2] = {reg, data};
if (write(i2c_fd_, buf, 2) != 2)
{
RCUTILS_LOG_ERROR_NAMED(get_name(), "Failed to write I2C register value");
}
}
uint8_t read_reg(uint8_t reg)
{
uint8_t buf[] = {reg};
if (write(i2c_fd_, buf, 1) != 2)
{
RCUTILS_LOG_ERROR_NAMED(get_name(), "Failed to write I2C register value");
}
if (read(i2c_fd_, buf, 1) != 2)
{
RCUTILS_LOG_ERROR_NAMED(get_name(), "Failed to read I2C register value");
}
return buf[0];
}
void control_callback(const bot_hardware::msg::Control::SharedPtr control_msg)
{
if (control_msg->throttle != bot_hardware::msg::Control::INVALID)
{
setThrottle(control_msg->throttle);
} else if (control_msg->steering != bot_hardware::msg::Control::INVALID)
{
setSteering(control_msg->steering);
}
}
/**
* @brief Set the Throttle object
*
* @param[in] throttle between -1 and 1 respectively full backward and full forward
*/
void setThrottle(float throttle)
{
throttle *= -1.0;
if (std::abs(throttle) < 0.001 ) throttle = 0;
uint16_t pwm = (uint16_t)(std::round(throttleMap->calPwm(throttle))) & 0x0FFF ;
RCLCPP_DEBUG(this->get_logger(), "THROTTLE %d, RAW %f", pwm, throttle);
set_pwm(THROTTLE_PWM_CHANNEL, 0, pwm);
}
/**
* @brief Set the Steering object
*
* @param steering between -1 and 1
*/
void setSteering(float steering)
{
steering *= -1.0;
if (std::abs(steering) < 0.0001 ) steering = 0;
steering += 0.29;
uint16_t pwm = (uint16_t)(std::round(steeringMap->calPwm(steering))) & 0x0FFF ;
RCLCPP_DEBUG(this->get_logger(), "STEERING %d, RAW %f", pwm, steering);
set_pwm(STEERING_PWM_CHANNEL, 0, pwm);
}
rclcpp::Subscription<bot_hardware::msg::Control>::SharedPtr control_subscription_;
int i2c_fd_;
uint8_t dev_address_;
float pwm_freq_;
Float2ServoMap::Ptr steeringMap;
Float2ServoMap::Ptr throttleMap;
};
int main(int argc, char *argv[])
{
rclcpp::init(argc, argv);
rclcpp::spin(std::make_shared<PCA9685>());
rclcpp::shutdown();
return 0;
}