Merge pull request #1278 from wolfmanjm/upstreamedge
[clinton/Smoothieware.git] / src / modules / tools / laser / Laser.cpp
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 "Laser.h"
9 #include "Module.h"
10 #include "Kernel.h"
11 #include "nuts_bolts.h"
12 #include "Config.h"
13 #include "StreamOutputPool.h"
14 #include "SerialMessage.h"
15 #include "checksumm.h"
16 #include "ConfigValue.h"
17 #include "StepTicker.h"
18 #include "Block.h"
19 #include "SlowTicker.h"
20 #include "Robot.h"
21 #include "utils.h"
22 #include "Pin.h"
23 #include "Gcode.h"
24 #include "PwmOut.h" // mbed.h lib
25 #include "PublicDataRequest.h"
26
27 #include <algorithm>
28
29 #define laser_checksum CHECKSUM("laser")
30 #define laser_module_enable_checksum CHECKSUM("laser_module_enable")
31 #define laser_module_pin_checksum CHECKSUM("laser_module_pin")
32 #define laser_module_pwm_pin_checksum CHECKSUM("laser_module_pwm_pin")
33 #define laser_module_ttl_pin_checksum CHECKSUM("laser_module_ttl_pin")
34 #define laser_module_pwm_period_checksum CHECKSUM("laser_module_pwm_period")
35 #define laser_module_maximum_power_checksum CHECKSUM("laser_module_maximum_power")
36 #define laser_module_minimum_power_checksum CHECKSUM("laser_module_minimum_power")
37 #define laser_module_tickle_power_checksum CHECKSUM("laser_module_tickle_power")
38 #define laser_module_max_power_checksum CHECKSUM("laser_module_max_power")
39 #define laser_module_maximum_s_value_checksum CHECKSUM("laser_module_maximum_s_value")
40
41
42 Laser::Laser()
43 {
44 laser_on = false;
45 scale = 1;
46 manual_fire = false;
47 fire_duration = 0;
48 }
49
50 void Laser::on_module_loaded()
51 {
52 if( !THEKERNEL->config->value( laser_module_enable_checksum )->by_default(false)->as_bool() ) {
53 // as not needed free up resource
54 delete this;
55 return;
56 }
57
58 // Get smoothie-style pin from config
59 Pin* dummy_pin = new Pin();
60 dummy_pin->from_string(THEKERNEL->config->value(laser_module_pin_checksum)->by_default("nc")->as_string())->as_output();
61
62 // Alternative less ambiguous name for pwm_pin
63 if (!dummy_pin->connected())
64 dummy_pin->from_string(THEKERNEL->config->value(laser_module_pwm_pin_checksum)->by_default("nc")->as_string())->as_output();
65
66 pwm_pin = dummy_pin->hardware_pwm();
67
68 if (pwm_pin == NULL) {
69 THEKERNEL->streams->printf("Error: Laser cannot use P%d.%d (P2.0 - P2.5, P1.18, P1.20, P1.21, P1.23, P1.24, P1.26, P3.25, P3.26 only). Laser module disabled.\n", dummy_pin->port_number, dummy_pin->pin);
70 delete dummy_pin;
71 delete this;
72 return;
73 }
74
75
76 this->pwm_inverting = dummy_pin->is_inverting();
77
78 delete dummy_pin;
79 dummy_pin = NULL;
80
81 // TTL settings
82 this->ttl_pin = new Pin();
83 ttl_pin->from_string( THEKERNEL->config->value(laser_module_ttl_pin_checksum)->by_default("nc" )->as_string())->as_output();
84 this->ttl_used = ttl_pin->connected();
85 this->ttl_inverting = ttl_pin->is_inverting();
86 if (ttl_used) {
87 ttl_pin->set(0);
88 } else {
89 delete ttl_pin;
90 ttl_pin = NULL;
91 }
92
93
94 uint32_t period = THEKERNEL->config->value(laser_module_pwm_period_checksum)->by_default(20)->as_number();
95 this->pwm_pin->period_us(period);
96 this->pwm_pin->write(this->pwm_inverting ? 1 : 0);
97 this->laser_maximum_power = THEKERNEL->config->value(laser_module_maximum_power_checksum)->by_default(1.0f)->as_number() ;
98
99 // These config variables are deprecated, they have been replaced with laser_module_maximum_power and laser_module_minimum_power
100 this->laser_minimum_power = THEKERNEL->config->value(laser_module_tickle_power_checksum)->by_default(0)->as_number() ;
101
102 // Load in our preferred config variables
103 this->laser_minimum_power = THEKERNEL->config->value(laser_module_minimum_power_checksum)->by_default(this->laser_minimum_power)->as_number() ;
104
105 // S value that represents maximum (default 1)
106 this->laser_maximum_s_value = THEKERNEL->config->value(laser_module_maximum_s_value_checksum)->by_default(1.0f)->as_number() ;
107
108 set_laser_power(0);
109
110 //register for events
111 this->register_for_event(ON_HALT);
112 this->register_for_event(ON_GCODE_RECEIVED);
113 this->register_for_event(ON_CONSOLE_LINE_RECEIVED);
114 this->register_for_event(ON_GET_PUBLIC_DATA);
115
116 // no point in updating the power more than the PWM frequency, but not faster than 1KHz
117 ms_per_tick = 1000 / std::min(1000UL, 1000000 / period);
118 THEKERNEL->slow_ticker->attach(std::min(1000UL, 1000000 / period), this, &Laser::set_proportional_power);
119 }
120
121 void Laser::on_console_line_received( void *argument )
122 {
123 if(THEKERNEL->is_halted()) return; // if in halted state ignore any commands
124
125 SerialMessage *msgp = static_cast<SerialMessage *>(argument);
126 string possible_command = msgp->message;
127
128 // ignore anything that is not lowercase or a letter
129 if(possible_command.empty() || !islower(possible_command[0]) || !isalpha(possible_command[0])) {
130 return;
131 }
132
133 string cmd = shift_parameter(possible_command);
134
135 // Act depending on command
136 if (cmd == "fire") {
137 string power = shift_parameter(possible_command);
138 if(power.empty()) {
139 msgp->stream->printf("Usage: fire power%% [durationms]|off|status\n");
140 return;
141 }
142
143 float p;
144 fire_duration = 0; // By default unlimited
145 if(power == "status") {
146 msgp->stream->printf("laser manual state: %s\n", manual_fire ? "on" : "off");
147 return;
148 }
149 if(power == "off" || power == "0") {
150 p = 0;
151 msgp->stream->printf("turning laser off and returning to auto mode\n");
152 } else {
153 p = strtof(power.c_str(), NULL);
154 p = confine(p, 0.0F, 100.0F);
155 string duration = shift_parameter(possible_command);
156 if(!duration.empty()) {
157 fire_duration = atoi(duration.c_str());
158 // Avoid negative values, its just incorrect
159 if (fire_duration < ms_per_tick) {
160 msgp->stream->printf("WARNING: Minimal duration is %ld ms, not firing\n", ms_per_tick);
161 return;
162 }
163 // rounding to minimal value
164 if (fire_duration % ms_per_tick != 0) {
165 fire_duration = (fire_duration / ms_per_tick) * ms_per_tick;
166 }
167 msgp->stream->printf("WARNING: Firing laser at %1.2f%% power, for %ld ms, use fire off to stop test fire earlier\n", p, fire_duration);
168 } else {
169 msgp->stream->printf("WARNING: Firing laser at %1.2f%% power, entering manual mode use fire off to return to auto mode\n", p);
170 }
171 }
172
173 p = p / 100.0F;
174 manual_fire = set_laser_power(p);
175 }
176 }
177
178 // returns instance
179 void Laser::on_get_public_data(void* argument)
180 {
181 PublicDataRequest* pdr = static_cast<PublicDataRequest*>(argument);
182
183 if(!pdr->starts_with(laser_checksum)) return;
184 pdr->set_data_ptr(this);
185 pdr->set_taken();
186 }
187
188
189 void Laser::on_gcode_received(void *argument)
190 {
191 Gcode *gcode = static_cast<Gcode *>(argument);
192
193 // M codes execute immediately
194 if (gcode->has_m) {
195 if (gcode->m == 221) { // M221 S100 change laser power by percentage S
196 if(gcode->has_letter('S')) {
197 this->scale = gcode->get_value('S') / 100.0F;
198
199 } else {
200 gcode->stream->printf("Laser power scale at %6.2f %%\n", this->scale * 100.0F);
201 }
202 }
203 }
204 }
205
206 // calculates the current speed ratio from the currently executing block
207 float Laser::current_speed_ratio(const Block *block) const
208 {
209 // find the primary moving actuator (the one with the most steps)
210 size_t pm = 0;
211 uint32_t max_steps = 0;
212 for (size_t i = 0; i < THEROBOT->get_number_registered_motors(); i++) {
213 // find the motor with the most steps
214 if(block->steps[i] > max_steps) {
215 max_steps = block->steps[i];
216 pm = i;
217 }
218 }
219
220 // figure out the ratio of its speed, from 0 to 1 based on where it is on the trapezoid,
221 // this is based on the fraction it is of the requested rate (nominal rate)
222 float ratio = block->get_trapezoid_rate(pm) / block->nominal_rate;
223
224 return ratio;
225 }
226
227 // get laser power for the currently executing block, returns false if nothing running or a G0
228 bool Laser::get_laser_power(float& power) const
229 {
230 const Block *block = StepTicker::getInstance()->get_current_block();
231
232 // Note to avoid a race condition where the block is being cleared we check the is_ready flag which gets cleared first,
233 // as this is an interrupt if that flag is not clear then it cannot be cleared while this is running and the block will still be valid (albeit it may have finished)
234 if(block != nullptr && block->is_ready && block->is_g123) {
235 float requested_power = ((float)block->s_value / (1 << 11)) / this->laser_maximum_s_value; // s_value is 1.11 Fixed point
236 float ratio = current_speed_ratio(block);
237 power = requested_power * ratio * scale;
238
239 return true;
240 }
241
242 return false;
243 }
244
245 // called every millisecond from timer ISR
246 uint32_t Laser::set_proportional_power(uint32_t dummy)
247 {
248 if(manual_fire) {
249 // If we have fire duration set
250 if (fire_duration) {
251 // Decrease it each ms
252 fire_duration -= ms_per_tick;
253 // And if it turned 0, disable laser and manual fire mode
254 if (fire_duration <= 0) {
255 set_laser_power(0);
256 manual_fire = false;
257 }
258 }
259 return 0;
260 }
261
262 float power;
263 if(get_laser_power(power)) {
264 // adjust power to maximum power and actual velocity
265 float proportional_power = ( (this->laser_maximum_power - this->laser_minimum_power) * power ) + this->laser_minimum_power;
266 set_laser_power(proportional_power);
267
268 } else if(laser_on) {
269 // turn laser off
270 set_laser_power(0);
271 }
272 return 0;
273 }
274
275 bool Laser::set_laser_power(float power)
276 {
277 // Ensure power is >=0 and <= 1
278 power = confine(power, 0.0F, 1.0F);
279
280 if(power > 0.00001F) {
281 this->pwm_pin->write(this->pwm_inverting ? 1 - power : power);
282 if(!laser_on && this->ttl_used) this->ttl_pin->set(true);
283 laser_on = true;
284
285 } else {
286 this->pwm_pin->write(this->pwm_inverting ? 1 : 0);
287 if (this->ttl_used) this->ttl_pin->set(false);
288 laser_on = false;
289 }
290
291 return laser_on;
292 }
293
294 void Laser::on_halt(void *argument)
295 {
296 if(argument == nullptr) {
297 set_laser_power(0);
298 manual_fire = false;
299 }
300 }
301
302 float Laser::get_current_power() const
303 {
304 float p = pwm_pin->read();
305 return (this->pwm_inverting ? 1 - p : p) * 100;
306 }