merged Smothieware Master into spindle-refactor branch
[clinton/Smoothieware.git] / src / modules / tools / spindle / PWMSpindleControl.cpp
CommitLineData
65d97468
PA
1/*
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/>.
6*/
7
8#include "libs/Module.h"
9#include "libs/Kernel.h"
8322515e 10#include "PWMSpindleControl.h"
65d97468
PA
11#include "Config.h"
12#include "libs/nuts_bolts.h"
13#include "checksumm.h"
14#include "ConfigValue.h"
15#include "Gcode.h"
16#include "StreamOutputPool.h"
17#include "SlowTicker.h"
18#include "Conveyor.h"
19#include "system_LPC17xx.h"
20
21#include "libs/Pin.h"
22#include "InterruptIn.h"
23#include "PwmOut.h"
24#include "port_api.h"
5a9f7a28 25#include "us_ticker_api.h"
65d97468 26
8322515e 27#define spindle_checksum CHECKSUM("spindle")
28#define spindle_vfd_type_checksum CHECKSUM("vfd_type")
29#define spindle_pwm_pin_checksum CHECKSUM("pwm_pin")
30#define spindle_pwm_period_checksum CHECKSUM("pwm_period")
31#define spindle_feedback_pin_checksum CHECKSUM("feedback_pin")
32#define spindle_pulses_per_rev_checksum CHECKSUM("pulses_per_rev")
33#define spindle_default_rpm_checksum CHECKSUM("default_rpm")
34#define spindle_control_P_checksum CHECKSUM("control_P")
35#define spindle_control_I_checksum CHECKSUM("control_I")
36#define spindle_control_D_checksum CHECKSUM("control_D")
37#define spindle_control_smoothing_checksum CHECKSUM("control_smoothing")
65d97468
PA
38
39#define UPDATE_FREQ 1000
40
8322515e 41PWMSpindleControl::PWMSpindleControl()
65d97468
PA
42{
43}
44
8322515e 45void PWMSpindleControl::on_module_loaded()
65d97468 46{
8322515e 47
48 printf("PWM Spindle Control loaded\n");
49
65d97468
PA
50 last_time = 0;
51 last_edge = 0;
52 current_rpm = 0;
53 current_I_value = 0;
54 current_pwm_value = 0;
55 time_since_update = 0;
8322515e 56
57 spindle_on = false;
58
59 pulses_per_rev = THEKERNEL->config->value(spindle_checksum, spindle_pulses_per_rev_checksum)->by_default(1.0f)->as_number();
60 target_rpm = THEKERNEL->config->value(spindle_checksum, spindle_default_rpm_checksum)->by_default(5000.0f)->as_number();
61 control_P_term = THEKERNEL->config->value(spindle_checksum, spindle_control_P_checksum)->by_default(0.0001f)->as_number();
62 control_I_term = THEKERNEL->config->value(spindle_checksum, spindle_control_I_checksum)->by_default(0.0001f)->as_number();
63 control_D_term = THEKERNEL->config->value(spindle_checksum, spindle_control_D_checksum)->by_default(0.0001f)->as_number();
6e92ab91 64
5a9f7a28 65 // Smoothing value is low pass filter time constant in seconds.
8322515e 66 float smoothing_time = THEKERNEL->config->value(spindle_checksum, spindle_control_smoothing_checksum)->by_default(0.1f)->as_number();
5a9f7a28
PA
67 if (smoothing_time * UPDATE_FREQ < 1.0f)
68 smoothing_decay = 1.0f;
69 else
70 smoothing_decay = 1.0f / (UPDATE_FREQ * smoothing_time);
6e92ab91 71
65d97468
PA
72 // Get the pin for hardware pwm
73 {
74 Pin *smoothie_pin = new Pin();
8322515e 75 smoothie_pin->from_string(THEKERNEL->config->value(spindle_checksum, spindle_pwm_pin_checksum)->by_default("nc")->as_string());
76 pwm_pin = smoothie_pin->as_output()->hardware_pwm();
65d97468
PA
77 output_inverted = smoothie_pin->inverting;
78 delete smoothie_pin;
79 }
8322515e 80
81 if (pwm_pin == NULL)
65d97468
PA
82 {
83 THEKERNEL->streams->printf("Error: Spindle PWM pin must be P2.0-2.5 or other PWM pin\n");
84 delete this;
85 return;
86 }
8322515e 87
88 int period = THEKERNEL->config->value(spindle_checksum, spindle_pwm_period_checksum)->by_default(1000)->as_int();
89 pwm_pin->period_us(period);
90 pwm_pin->write(output_inverted ? 1 : 0);
6e92ab91 91
65d97468
PA
92 // Get the pin for interrupt
93 {
94 Pin *smoothie_pin = new Pin();
8322515e 95 smoothie_pin->from_string(THEKERNEL->config->value(spindle_checksum, spindle_feedback_pin_checksum)->by_default("nc")->as_string());
65d97468
PA
96 smoothie_pin->as_input();
97 if (smoothie_pin->port_number == 0 || smoothie_pin->port_number == 2)
98 {
99 PinName pinname = port_pin((PortName)smoothie_pin->port_number, smoothie_pin->pin);
100 feedback_pin = new mbed::InterruptIn(pinname);
8322515e 101 feedback_pin->rise(this, &PWMSpindleControl::on_pin_rise);
5a9f7a28 102 NVIC_SetPriority(EINT3_IRQn, 16);
65d97468
PA
103 }
104 else
105 {
106 THEKERNEL->streams->printf("Error: Spindle feedback pin has to be on P0 or P2.\n");
107 delete this;
108 return;
109 }
110 delete smoothie_pin;
111 }
8322515e 112
113 THEKERNEL->slow_ticker->attach(UPDATE_FREQ, this, &PWMSpindleControl::on_update_speed);
6e92ab91 114
65d97468
PA
115 register_for_event(ON_GCODE_RECEIVED);
116 register_for_event(ON_GCODE_EXECUTE);
8322515e 117
65d97468
PA
118}
119
8322515e 120void PWMSpindleControl::on_pin_rise()
65d97468 121{
5a9f7a28
PA
122 uint32_t timestamp = us_ticker_read();
123 last_time = timestamp - last_edge;
65d97468
PA
124 last_edge = timestamp;
125 irq_count++;
126}
127
8322515e 128uint32_t PWMSpindleControl::on_update_speed(uint32_t dummy)
65d97468
PA
129{
130 // If we don't get any interrupts for 1 second, set current RPM to 0
131 uint32_t new_irq = irq_count;
132 if (last_irq != new_irq)
133 time_since_update = 0;
134 else
135 time_since_update++;
136 last_irq = new_irq;
6e92ab91 137
65d97468
PA
138 if (time_since_update > UPDATE_FREQ)
139 last_time = 0;
6e92ab91 140
65d97468
PA
141 // Calculate current RPM
142 uint32_t t = last_time;
143 if (t == 0)
5a9f7a28 144 {
65d97468 145 current_rpm = 0;
5a9f7a28 146 }
65d97468 147 else
5a9f7a28
PA
148 {
149 float new_rpm = 1000000 * 60.0f / (t * pulses_per_rev);
150 current_rpm = smoothing_decay * new_rpm + (1.0f - smoothing_decay) * current_rpm;
151 }
6e92ab91 152
65d97468
PA
153 if (spindle_on)
154 {
155 float error = target_rpm - current_rpm;
6e92ab91 156
65d97468
PA
157 current_I_value += control_I_term * error * 1.0f / UPDATE_FREQ;
158 current_I_value = confine(current_I_value, -1.0f, 1.0f);
6e92ab91 159
65d97468
PA
160 float new_pwm = 0.5f;
161 new_pwm += control_P_term * error;
162 new_pwm += current_I_value;
02810766 163 new_pwm += control_D_term * UPDATE_FREQ * (error - prev_error);
65d97468
PA
164 new_pwm = confine(new_pwm, 0.0f, 1.0f);
165 prev_error = error;
6e92ab91 166
65d97468
PA
167 current_pwm_value = new_pwm;
168 }
169 else
170 {
171 current_I_value = 0;
172 current_pwm_value = 0;
173 }
6e92ab91 174
65d97468 175 if (output_inverted)
8322515e 176 pwm_pin->write(1.0f - current_pwm_value);
65d97468 177 else
8322515e 178 pwm_pin->write(current_pwm_value);
6e92ab91 179
65d97468
PA
180 return 0;
181}
182
183
8322515e 184void PWMSpindleControl::turn_on() {
185 spindle_on = true;
186}
6e92ab91 187
8322515e 188
189void PWMSpindleControl::turn_off() {
190 spindle_on = false;
65d97468
PA
191}
192
6e92ab91 193
8322515e 194void PWMSpindleControl::set_speed(int rpm) {
195 target_rpm = rpm;
196}
6e92ab91 197
8322515e 198
199void PWMSpindleControl::report_speed() {
200 THEKERNEL->streams->printf("Current RPM: %5.0f Target RPM: %5.0f PWM value: %5.3f\n",
201 current_rpm, target_rpm, current_pwm_value);
202}
203
204
205void PWMSpindleControl::set_p_term(float p) {
206 control_P_term = p;
207}
208
209
210void PWMSpindleControl::set_i_term(float i) {
211 control_I_term = i;
212}
213
214
215void PWMSpindleControl::set_d_term(float d) {
216 control_D_term = d;
217}
218
219
220void PWMSpindleControl::report_settings() {
221 THEKERNEL->streams->printf("P: %0.6f I: %0.6f D: %0.6f\n",
222 control_P_term, control_I_term, control_D_term);
65d97468
PA
223}
224