12#include "driver/gpio.h"
13#include "driver/ledc.h"
18constexpr float kDefaultPositionP = 20.0;
19constexpr float kDefaultPositionI = 0.1;
20constexpr float kDefaultPositionD = 5.0;
21constexpr float kDefaultSpeedP = 3.0;
22constexpr float kDefaultSpeedI = 1.0;
23constexpr float kDefaultSpeedD = 1.0;
24constexpr int32_t kDeadRpmZone = 10;
27#if ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(3, 0, 0)
29 const uint8_t negative_pin,
33 const uint32_t reduction_ration,
37 motor_driver_(positive_pin, negative_pin),
38 total_ppr_(ppr * reduction_ration),
41 rpm_pid_.p = kDefaultSpeedP;
42 rpm_pid_.i = kDefaultSpeedI;
43 rpm_pid_.d = kDefaultSpeedD;
49 const uint8_t positive_pin_ledc_channel,
50 const uint8_t negative_pin,
51 const uint8_t negative_pin_ledc_channel,
55 const uint32_t reduction_ration,
59 motor_driver_(positive_pin, positive_pin_ledc_channel, negative_pin, negative_pin_ledc_channel),
60 total_ppr_(ppr * reduction_ration),
63 rpm_pid_.p = kDefaultSpeedP;
64 rpm_pid_.i = kDefaultSpeedI;
65 rpm_pid_.d = kDefaultSpeedD;
70 std::lock_guard<std::mutex> lock(mutex_);
71 if (update_rpm_thread_ !=
nullptr) {
77 pinMode(pin_a_, INPUT_PULLUP);
78 pinMode(pin_b_, INPUT_PULLUP);
80 attachInterruptArg(pin_a_, EncoderMotor::OnPinAFalling,
this, FALLING);
81 update_rpm_thread_ =
new std::thread(&EncoderMotor::UpdateRpm,
this);
85 std::lock_guard<std::mutex> lock(mutex_);
93 rpm_pid_.max_integral = 0;
98 std::lock_guard<std::mutex> lock(mutex_);
110EncoderMotor::~EncoderMotor() {
111 std::unique_lock<std::mutex> lock(mutex_);
112 DeleteThread(driving_thread_);
113 DeleteThread(update_rpm_thread_);
117 std::unique_lock<std::mutex> lock(mutex_);
118 DeleteThread(driving_thread_);
119 target_speed_rpm_ = 0;
121 if (motor_driver_.PwmDuty() == duty) {
125 motor_driver_.RunPwmDuty(duty);
129 std::lock_guard<std::mutex> lock(mutex_);
130 if (driving_thread_ ==
nullptr) {
131 rpm_pid_.integral = 0;
132 driving_thread_ =
new std::thread(&EncoderMotor::Driving,
this);
135 if (speed_rpm == target_speed_rpm_) {
139 target_speed_rpm_ = speed_rpm;
141 condition_.notify_all();
145 std::unique_lock<std::mutex> lock(mutex_);
146 DeleteThread(driving_thread_);
147 motor_driver_.Stop();
148 target_speed_rpm_ = 0;
149 rpm_pid_.integral = 0;
161 std::unique_lock<std::mutex> lock(mutex_);
166 std::lock_guard<std::mutex> lock(mutex_);
167 return motor_driver_.PwmDuty();
171 std::lock_guard<std::mutex> lock(mutex_);
172 return target_speed_rpm_;
175void EncoderMotor::OnPinAFalling(
void* self) {
179void EncoderMotor::OnPinAFalling() {
180 if (gpio_get_level(
static_cast<gpio_num_t
>(pin_b_)) == b_level_at_a_falling_edge_) {
187void EncoderMotor::UpdateRpm() {
188 std::unique_lock<std::mutex> lock(mutex_);
189 last_update_speed_time_ = std::chrono::system_clock::now();
190 while (!condition_.wait_until(
191 lock, last_update_speed_time_ + std::chrono::milliseconds(50), [
this]() { return update_rpm_thread_ == nullptr; })) {
192 const auto now = std::chrono::system_clock::now();
193 const double duration = std::chrono::duration_cast<std::chrono::milliseconds>(now - last_update_speed_time_).count();
194 const double pulse_count = pulse_count_;
195 speed_rpm_ = (pulse_count - previous_pulse_count_) * 60000.0 / duration / total_ppr_;
196 previous_pulse_count_ = pulse_count;
197 last_update_speed_time_ = now;
198 if (driving_thread_ !=
nullptr) {
200 condition_.notify_all();
205void EncoderMotor::Driving() {
206 std::unique_lock<std::mutex> lock(mutex_);
208 while (driving_thread_ !=
nullptr) {
209 condition_.wait(lock, [
this]() {
return driving_thread_ ==
nullptr || drive_; });
211 if (driving_thread_ ==
nullptr) {
215 if (target_speed_rpm_ < kDeadRpmZone && target_speed_rpm_ > -kDeadRpmZone) {
216 motor_driver_.RunPwmDuty(0);
218 const float speed_error = target_speed_rpm_ - speed_rpm_;
219 rpm_pid_.integral = constrain(rpm_pid_.integral + speed_error, -rpm_pid_.max_integral, rpm_pid_.max_integral);
220 const int16_t duty = round(rpm_pid_.p * speed_error + rpm_pid_.i * rpm_pid_.integral);
221 motor_driver_.RunPwmDuty(duty);
227void EncoderMotor::DeleteThread(std::thread*& thread) {
228 if (thread !=
nullptr) {
229#if __cplusplus >= 201402L
230 const auto temp = std::exchange(thread,
nullptr);
232 const auto temp = thread;
235 condition_.notify_all();
int16_t PwmDuty() const
Get the PWM pwm_duty cycle of the motor driver.
void RunSpeed(const int16_t speed_rpm)
Run motor at speed setpoint.
void ResetPulseCount()
Reset the encoder pulse count to 0.
PhaseRelation
Used to clarify the phase relationship between phase A and phase B of the encoder when the motor is r...
@ kAPhaseLeads
Represents the situation where phase A leads phase B when the motor is rotating forward.
void RunPwmDuty(const int16_t pwm_duty)
Set motor PWM directly.
int64_t EncoderPulseCount() const
Get encoder pulse count. The count value is incremented by one during forward rotation and decremente...
int32_t TargetRpm() const
Get the target speed of the motor in RPM.
void SetSpeedPid(const float p, const float i, const float d)
Set the parameters of the speed PID controller with the given Proportional (P), Integral (I),...
void GetSpeedPid(float *const p, float *const i, float *const d) const
Get the Proportional (P), Integral (I), and Derivative (D) parameter values of the speed PID controll...
EncoderMotor(const uint8_t positive_pin, const uint8_t negative_pin, const uint8_t a_pin, const uint8_t b_pin, const uint32_t ppr, const uint32_t reduction_ration, const PhaseRelation phase_relation)
Constructor for creating an EncoderMotor object.
int32_t SpeedRpm() const
Get the current speed of the motor.
static constexpr int16_t kMaxPwmDuty
The maximum PWM duty cycle value calculated based on the PWM resolution.