fix bad formattingfrom PR
[clinton/Smoothieware.git] / src / modules / tools / laser / Laser.cpp
CommitLineData
de91760a 1/*
58baeec1
MM
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/>.
4cff3ded
AW
6*/
7
23201534 8#include "Laser.h"
73cc27d2
JM
9#include "Module.h"
10#include "Kernel.h"
11#include "nuts_bolts.h"
61134a65
JM
12#include "Config.h"
13#include "StreamOutputPool.h"
73cc27d2 14#include "SerialMessage.h"
7af0714f 15#include "checksumm.h"
8d54c34c 16#include "ConfigValue.h"
23201534
JM
17#include "StepTicker.h"
18#include "Block.h"
19#include "SlowTicker.h"
e70b6417 20#include "Robot.h"
73cc27d2
JM
21#include "utils.h"
22#include "Pin.h"
1f19c40d
JM
23#include "Gcode.h"
24#include "PwmOut.h" // mbed.h lib
289380f2 25#include "PublicDataRequest.h"
1f19c40d 26
8f89a940
JM
27#include <algorithm>
28
289380f2 29#define laser_checksum CHECKSUM("laser")
23201534
JM
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")
5a2b2375 36#define laser_module_minimum_power_checksum CHECKSUM("laser_module_minimum_power")
5a2b2375
FM
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
1f19c40d 41
23201534
JM
42Laser::Laser()
43{
44 laser_on = false;
b7a1ec7c
JM
45 scale = 1;
46 manual_fire = false;
02f9f175 47 fire_duration = 0;
4cff3ded
AW
48}
49
23201534
JM
50void Laser::on_module_loaded()
51{
52 if( !THEKERNEL->config->value( laser_module_enable_checksum )->by_default(false)->as_bool() ) {
0b1e8fd1
BG
53 // as not needed free up resource
54 delete this;
55 return;
56 }
e605a0d6 57
dad4ecfc
AW
58 // Get smoothie-style pin from config
59 Pin* dummy_pin = new Pin();
314ab8f7 60 dummy_pin->from_string(THEKERNEL->config->value(laser_module_pin_checksum)->by_default("nc")->as_string())->as_output();
61134a65 61
39b0a65d
FM
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
4287ba0c 66 pwm_pin = dummy_pin->hardware_pwm();
820ee962 67
23201534 68 if (pwm_pin == NULL) {
580cad4c 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);
820ee962
MM
70 delete dummy_pin;
71 delete this;
72 return;
73 }
74
4287ba0c 75
39f1d9bd 76 this->pwm_inverting = dummy_pin->is_inverting();
dad4ecfc 77
820ee962
MM
78 delete dummy_pin;
79 dummy_pin = NULL;
80
4287ba0c
FM
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();
39f1d9bd 85 this->ttl_inverting = ttl_pin->is_inverting();
ec692f61 86 if (ttl_used) {
23201534 87 ttl_pin->set(0);
ec692f61 88 } else {
23201534
JM
89 delete ttl_pin;
90 ttl_pin = NULL;
ec692f61
FM
91 }
92
4287ba0c 93
b7a1ec7c 94 uint32_t period = THEKERNEL->config->value(laser_module_pwm_period_checksum)->by_default(20)->as_number();
4f71d19f 95 this->pwm_pin->period_us(period);
4287ba0c 96 this->pwm_pin->write(this->pwm_inverting ? 1 : 0);
5a2b2375 97 this->laser_maximum_power = THEKERNEL->config->value(laser_module_maximum_power_checksum)->by_default(1.0f)->as_number() ;
e605a0d6 98
87cde8b9 99 // These config variables are deprecated, they have been replaced with laser_module_maximum_power and laser_module_minimum_power
6e986d7e 100 this->laser_minimum_power = THEKERNEL->config->value(laser_module_tickle_power_checksum)->by_default(0)->as_number() ;
024545c5 101
6e986d7e
BC
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() ;
0b1e8fd1 104
5a2b2375
FM
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
50d6ed15 108 set_laser_power(0);
23201534 109
e605a0d6 110 //register for events
f95c9fce 111 this->register_for_event(ON_HALT);
04197132 112 this->register_for_event(ON_GCODE_RECEIVED);
73cc27d2 113 this->register_for_event(ON_CONSOLE_LINE_RECEIVED);
289380f2 114 this->register_for_event(ON_GET_PUBLIC_DATA);
23201534 115
320f41fb 116 // no point in updating the power more than the PWM frequency, but not faster than 1KHz
b7a1ec7c
JM
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);
4cff3ded
AW
119}
120
73cc27d2
JM
121void Laser::on_console_line_received( void *argument )
122{
123 if(THEKERNEL->is_halted()) return; // if in halted state ignore any commands
124
50d6ed15
JM
125 SerialMessage *msgp = static_cast<SerialMessage *>(argument);
126 string possible_command = msgp->message;
73cc27d2
JM
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);
b26de553 138 if(power.empty()) {
02f9f175 139 msgp->stream->printf("Usage: fire power%% [durationms]|off|status\n");
50d6ed15 140 return;
b26de553 141 }
50d6ed15 142
b26de553 143 float p;
02f9f175
DF
144 fire_duration = 0; // By default unlimited
145 if(power == "status") {
b7a1ec7c
JM
146 msgp->stream->printf("laser manual state: %s\n", manual_fire ? "on" : "off");
147 return;
02f9f175 148 }
b26de553 149 if(power == "off" || power == "0") {
b7a1ec7c 150 p = 0;
50d6ed15 151 msgp->stream->printf("turning laser off and returning to auto mode\n");
b7a1ec7c
JM
152 } else {
153 p = strtof(power.c_str(), NULL);
154 p = confine(p, 0.0F, 100.0F);
02f9f175
DF
155 string duration = shift_parameter(possible_command);
156 if(!duration.empty()) {
b7a1ec7c 157 fire_duration = atoi(duration.c_str());
02f9f175 158 // Avoid negative values, its just incorrect
320f41fb 159 if (fire_duration < ms_per_tick) {
b7a1ec7c
JM
160 msgp->stream->printf("WARNING: Minimal duration is %ld ms, not firing\n", ms_per_tick);
161 return;
320f41fb
DF
162 }
163 // rounding to minimal value
164 if (fire_duration % ms_per_tick != 0) {
b7a1ec7c 165 fire_duration = (fire_duration / ms_per_tick) * ms_per_tick;
320f41fb
DF
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);
02f9f175
DF
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 }
b26de553 171 }
7ed2d732 172
b7a1ec7c
JM
173 p = p / 100.0F;
174 manual_fire = set_laser_power(p);
73cc27d2
JM
175 }
176}
04197132 177
289380f2
JM
178// returns instance
179void 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
04197132
JM
189void Laser::on_gcode_received(void *argument)
190{
191 Gcode *gcode = static_cast<Gcode *>(argument);
192
73cc27d2 193 // M codes execute immediately
04197132
JM
194 if (gcode->has_m) {
195 if (gcode->m == 221) { // M221 S100 change laser power by percentage S
196 if(gcode->has_letter('S')) {
b7a1ec7c 197 this->scale = gcode->get_value('S') / 100.0F;
04197132
JM
198
199 } else {
200 gcode->stream->printf("Laser power scale at %6.2f %%\n", this->scale * 100.0F);
201 }
202 }
203 }
204}
205
23201534
JM
206// calculates the current speed ratio from the currently executing block
207float Laser::current_speed_ratio(const Block *block) const
208{
e70b6417 209 // find the primary moving actuator (the one with the most steps)
b7a1ec7c
JM
210 size_t pm = 0;
211 uint32_t max_steps = 0;
e70b6417
JM
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) {
b7a1ec7c
JM
215 max_steps = block->steps[i];
216 pm = i;
e70b6417
JM
217 }
218 }
219
4245f826
JM
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)
b7a1ec7c 222 float ratio = block->get_trapezoid_rate(pm) / block->nominal_rate;
23201534 223
e70b6417 224 return ratio;
23201534
JM
225}
226
227// get laser power for the currently executing block, returns false if nothing running or a G0
228bool Laser::get_laser_power(float& power) const
229{
230 const Block *block = StepTicker::getInstance()->get_current_block();
231
e70b6417 232 // Note to avoid a race condition where the block is being cleared we check the is_ready flag which gets cleared first,
5c749b4a 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)
e70b6417 234 if(block != nullptr && block->is_ready && block->is_g123) {
b7a1ec7c 235 float requested_power = ((float)block->s_value / (1 << 11)) / this->laser_maximum_s_value; // s_value is 1.11 Fixed point
23201534 236 float ratio = current_speed_ratio(block);
04197132 237 power = requested_power * ratio * scale;
23201534
JM
238
239 return true;
240 }
241
242 return false;
243}
a2cd92c0 244
23201534
JM
245// called every millisecond from timer ISR
246uint32_t Laser::set_proportional_power(uint32_t dummy)
247{
02f9f175 248 if(manual_fire) {
b7a1ec7c
JM
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 }
02f9f175 258 }
b7a1ec7c 259 return 0;
02f9f175 260 }
b26de553 261
23201534
JM
262 float power;
263 if(get_laser_power(power)) {
58baeec1 264 // adjust power to maximum power and actual velocity
23201534 265 float proportional_power = ( (this->laser_maximum_power - this->laser_minimum_power) * power ) + this->laser_minimum_power;
50d6ed15 266 set_laser_power(proportional_power);
23201534
JM
267
268 } else if(laser_on) {
269 // turn laser off
50d6ed15 270 set_laser_power(0);
4cff3ded 271 }
23201534 272 return 0;
2f37949e 273}
f95c9fce 274
50d6ed15
JM
275bool Laser::set_laser_power(float power)
276{
277 // Ensure power is >=0 and <= 1
b7a1ec7c 278 power = confine(power, 0.0F, 1.0F);
50d6ed15
JM
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
b7a1ec7c 285 } else {
50d6ed15
JM
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
f95c9fce
FM
294void Laser::on_halt(void *argument)
295{
296 if(argument == nullptr) {
50d6ed15 297 set_laser_power(0);
b7a1ec7c 298 manual_fire = false;
f95c9fce
FM
299 }
300}
646d62d0
JM
301
302float Laser::get_current_power() const
303{
b7a1ec7c 304 float p = pwm_pin->read();
30f11e42 305 return (this->pwm_inverting ? 1 - p : p) * 100;
646d62d0 306}