Properly handling when PWM frequency is slower than 1khz
[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;
04197132 45 scale= 1;
b26de553 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
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
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") {
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
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')) {
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
207float 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
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) {
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
246uint32_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
275bool 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
294void 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
302float 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}