2 This file is part of Smoothie (http://smoothieware.org/). The motion control part is heavily based on Grbl (https://github.com/simen/grbl).
3 Smoothie is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
4 Smoothie is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
5 You should have received a copy of the GNU General Public License along with Smoothie. If not, see <http://www.gnu.org/licenses/>.
8 #include "libs/Module.h"
9 #include "libs/Kernel.h"
10 #include "PWMSpindleControl.h"
12 #include "checksumm.h"
13 #include "ConfigValue.h"
14 #include "StreamOutputPool.h"
15 #include "SlowTicker.h"
17 #include "system_LPC17xx.h"
21 #include "InterruptIn.h"
24 #include "us_ticker_api.h"
26 #define spindle_checksum CHECKSUM("spindle")
27 #define spindle_pwm_pin_checksum CHECKSUM("pwm_pin")
28 #define spindle_pwm_period_checksum CHECKSUM("pwm_period")
29 #define spindle_max_pwm_checksum CHECKSUM("max_pwm")
30 #define spindle_feedback_pin_checksum CHECKSUM("feedback_pin")
31 #define spindle_pulses_per_rev_checksum CHECKSUM("pulses_per_rev")
32 #define spindle_default_rpm_checksum CHECKSUM("default_rpm")
33 #define spindle_control_P_checksum CHECKSUM("control_P")
34 #define spindle_control_I_checksum CHECKSUM("control_I")
35 #define spindle_control_D_checksum CHECKSUM("control_D")
36 #define spindle_control_smoothing_checksum CHECKSUM("control_smoothing")
38 #define UPDATE_FREQ 1000
40 PWMSpindleControl::PWMSpindleControl()
44 void PWMSpindleControl::on_module_loaded()
50 current_pwm_value
= 0;
51 time_since_update
= 0;
55 pulses_per_rev
= THEKERNEL
->config
->value(spindle_checksum
, spindle_pulses_per_rev_checksum
)->by_default(1.0f
)->as_number();
56 target_rpm
= THEKERNEL
->config
->value(spindle_checksum
, spindle_default_rpm_checksum
)->by_default(5000.0f
)->as_number();
57 control_P_term
= THEKERNEL
->config
->value(spindle_checksum
, spindle_control_P_checksum
)->by_default(0.0001f
)->as_number();
58 control_I_term
= THEKERNEL
->config
->value(spindle_checksum
, spindle_control_I_checksum
)->by_default(0.0001f
)->as_number();
59 control_D_term
= THEKERNEL
->config
->value(spindle_checksum
, spindle_control_D_checksum
)->by_default(0.0001f
)->as_number();
61 // Smoothing value is low pass filter time constant in seconds.
62 float smoothing_time
= THEKERNEL
->config
->value(spindle_checksum
, spindle_control_smoothing_checksum
)->by_default(0.1f
)->as_number();
63 if (smoothing_time
* UPDATE_FREQ
< 1.0f
)
64 smoothing_decay
= 1.0f
;
66 smoothing_decay
= 1.0f
/ (UPDATE_FREQ
* smoothing_time
);
68 // Get the pin for hardware pwm
70 Pin
*smoothie_pin
= new Pin();
71 smoothie_pin
->from_string(THEKERNEL
->config
->value(spindle_checksum
, spindle_pwm_pin_checksum
)->by_default("nc")->as_string());
72 pwm_pin
= smoothie_pin
->as_output()->hardware_pwm();
73 output_inverted
= smoothie_pin
->is_inverting();
79 THEKERNEL
->streams
->printf("Error: Spindle PWM pin must be P2.0-2.5 or other PWM pin\n");
84 max_pwm
= THEKERNEL
->config
->value(spindle_checksum
, spindle_max_pwm_checksum
)->by_default(1.0f
)->as_number();
86 int period
= THEKERNEL
->config
->value(spindle_checksum
, spindle_pwm_period_checksum
)->by_default(1000)->as_int();
87 pwm_pin
->period_us(period
);
88 pwm_pin
->write(output_inverted
? 1 : 0);
90 // Get the pin for interrupt
92 Pin
*smoothie_pin
= new Pin();
93 smoothie_pin
->from_string(THEKERNEL
->config
->value(spindle_checksum
, spindle_feedback_pin_checksum
)->by_default("nc")->as_string());
94 smoothie_pin
->as_input();
95 if (smoothie_pin
->port_number
== 0 || smoothie_pin
->port_number
== 2) {
96 PinName pinname
= port_pin((PortName
)smoothie_pin
->port_number
, smoothie_pin
->pin
);
97 feedback_pin
= new mbed::InterruptIn(pinname
);
98 feedback_pin
->rise(this, &PWMSpindleControl::on_pin_rise
);
99 NVIC_SetPriority(EINT3_IRQn
, 16);
101 THEKERNEL
->streams
->printf("Error: Spindle feedback pin has to be on P0 or P2.\n");
108 THEKERNEL
->slow_ticker
->attach(UPDATE_FREQ
, this, &PWMSpindleControl::on_update_speed
);
111 void PWMSpindleControl::on_pin_rise()
113 uint32_t timestamp
= us_ticker_read();
114 last_time
= timestamp
- last_edge
;
115 last_edge
= timestamp
;
119 uint32_t PWMSpindleControl::on_update_speed(uint32_t dummy
)
121 // If we don't get any interrupts for 1 second, set current RPM to 0
122 uint32_t new_irq
= irq_count
;
123 if (last_irq
!= new_irq
)
124 time_since_update
= 0;
129 if (time_since_update
> UPDATE_FREQ
)
132 // Calculate current RPM
133 uint32_t t
= last_time
;
137 float new_rpm
= 1000000 * 60.0f
/ (t
* pulses_per_rev
);
138 current_rpm
= smoothing_decay
* new_rpm
+ (1.0f
- smoothing_decay
) * current_rpm
;
142 float error
= target_rpm
- current_rpm
;
144 current_I_value
+= control_I_term
* error
* 1.0f
/ UPDATE_FREQ
;
145 current_I_value
= confine(current_I_value
, -1.0f
, 1.0f
);
147 float new_pwm
= 0.5f
;
148 new_pwm
+= control_P_term
* error
;
149 new_pwm
+= current_I_value
;
150 new_pwm
+= control_D_term
* UPDATE_FREQ
* (error
- prev_error
);
151 new_pwm
= confine(new_pwm
, 0.0f
, 1.0f
);
154 current_pwm_value
= new_pwm
;
156 if (current_pwm_value
> max_pwm
) {
157 current_pwm_value
= max_pwm
;
161 current_pwm_value
= 0;
165 pwm_pin
->write(1.0f
- current_pwm_value
);
167 pwm_pin
->write(current_pwm_value
);
172 void PWMSpindleControl::turn_on() {
176 void PWMSpindleControl::turn_off() {
181 void PWMSpindleControl::set_speed(int rpm
) {
186 void PWMSpindleControl::report_speed() {
187 THEKERNEL
->streams
->printf("Current RPM: %5.0f Target RPM: %5.0f PWM value: %5.3f\n",
188 current_rpm
, target_rpm
, current_pwm_value
);
192 void PWMSpindleControl::set_p_term(float p
) {
197 void PWMSpindleControl::set_i_term(float i
) {
202 void PWMSpindleControl::set_d_term(float d
) {
207 void PWMSpindleControl::report_settings() {
208 THEKERNEL
->streams
->printf("P: %0.6f I: %0.6f D: %0.6f\n",
209 control_P_term
, control_I_term
, control_D_term
);