Commit | Line | Data |
---|---|---|
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 |
42 | Laser::Laser() |
43 | { | |
44 | laser_on = false; | |
04197132 | 45 | scale= 1; |
b26de553 | 46 | manual_fire= false; |
02f9f175 | 47 | fire_duration = 0; |
4cff3ded AW |
48 | } |
49 | ||
23201534 JM |
50 | void 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 | |
4f71d19f JM |
94 | uint32_t period= THEKERNEL->config->value(laser_module_pwm_period_checksum)->by_default(20)->as_number(); |
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 DF |
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); | |
8f89a940 | 118 | THEKERNEL->slow_ticker->attach(std::min(1000UL, 1000000/period), this, &Laser::set_proportional_power); |
4cff3ded AW |
119 | } |
120 | ||
73cc27d2 JM |
121 | void 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") { | |
146 | msgp->stream->printf("laser manual state: %s\n", manual_fire ? "on" : "off"); | |
147 | return; | |
148 | } | |
b26de553 JM |
149 | if(power == "off" || power == "0") { |
150 | p= 0; | |
50d6ed15 | 151 | msgp->stream->printf("turning laser off and returning to auto mode\n"); |
b26de553 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()) { | |
157 | fire_duration=atoi(duration.c_str()); | |
158 | // Avoid negative values, its just incorrect | |
320f41fb DF |
159 | if (fire_duration < ms_per_tick) { |
160 | msgp->stream->printf("WARNING: Minimal duration is %d 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); | |
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 JM |
172 | |
173 | p= p/100.0F; | |
50d6ed15 | 174 | manual_fire= set_laser_power(p); |
73cc27d2 JM |
175 | } |
176 | } | |
04197132 | 177 | |
289380f2 JM |
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 | ||
04197132 JM |
189 | void 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')) { | |
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 | ||
23201534 JM |
206 | // calculates the current speed ratio from the currently executing block |
207 | float Laser::current_speed_ratio(const Block *block) const | |
208 | { | |
e70b6417 JM |
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 | ||
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) | |
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 | |
228 | bool 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) { |
5c749b4a | 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 |
246 | uint32_t Laser::set_proportional_power(uint32_t dummy) | |
247 | { | |
02f9f175 DF |
248 | if(manual_fire) { |
249 | // If we have fire duration set | |
250 | if (fire_duration) { | |
251 | // Decrease it each ms | |
320f41fb | 252 | fire_duration -= ms_per_tick; |
02f9f175 | 253 | // And if it turned 0, disable laser and manual fire mode |
320f41fb | 254 | if (fire_duration <= 0) { |
02f9f175 DF |
255 | set_laser_power(0); |
256 | manual_fire = false; | |
257 | } | |
258 | } | |
259 | return 0; | |
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 |
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 | ||
f95c9fce FM |
294 | void Laser::on_halt(void *argument) |
295 | { | |
296 | if(argument == nullptr) { | |
50d6ed15 | 297 | set_laser_power(0); |
8303f135 | 298 | manual_fire= false; |
f95c9fce FM |
299 | } |
300 | } | |
646d62d0 JM |
301 | |
302 | float Laser::get_current_power() const | |
303 | { | |
30f11e42 JM |
304 | float p= pwm_pin->read(); |
305 | return (this->pwm_inverting ? 1 - p : p) * 100; | |
646d62d0 | 306 | } |