├── .gitattributes ├── .gitignore ├── CMakeLists.txt ├── COPYING ├── README.md ├── linuxcnc.c ├── powermax.c ├── sheetcam.c ├── thc.c └── thc.h /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto eol=lf 3 | 4 | 5 | # Custom for Visual Studio 6 | *.cs diff=csharp 7 | 8 | # Standard to msysgit 9 | *.doc diff=astextplain 10 | *.DOC diff=astextplain 11 | *.docx diff=astextplain 12 | *.DOCX diff=astextplain 13 | *.dot diff=astextplain 14 | *.DOT diff=astextplain 15 | *.pdf diff=astextplain 16 | *.PDF diff=astextplain 17 | *.rtf diff=astextplain 18 | *.RTF diff=astextplain 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows thumbnail cache files 2 | Thumbs.db 3 | ehthumbs.db 4 | ehthumbs_vista.db 5 | 6 | # Folder config file 7 | Desktop.ini 8 | 9 | # Recycle Bin used on file shares 10 | $RECYCLE.BIN/ 11 | 12 | # Windows Installer files 13 | *.cab 14 | *.msi 15 | *.msm 16 | *.msp 17 | 18 | # Windows shortcuts 19 | *.lnk 20 | 21 | # Subversion folders 22 | 23 | .svn 24 | 25 | # Build folder 26 | 27 | build 28 | 29 | # 30 | # ========================= 31 | # Operating System Files 32 | # ========================= 33 | 34 | .vscode/settings.json 35 | .vscode/c_cpp_properties.json 36 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(plasma INTERFACE) 2 | 3 | target_sources(plasma INTERFACE 4 | ${CMAKE_CURRENT_LIST_DIR}/thc.c 5 | ${CMAKE_CURRENT_LIST_DIR}/linuxcnc.c 6 | ${CMAKE_CURRENT_LIST_DIR}/sheetcam.c 7 | ) 8 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | ------------------------------------------------------------------------------ 2 | COPYRIGHT NOTICE FOR grblHAL Plugin_plasma: 3 | ------------------------------------------------------------------------------ 4 | 5 | grblHAL - Embedded CNC g-code interpreter and motion-controller 6 | 7 | Copyright (c) 2020-2021 Terje Io 8 | 9 | Grbl is free software: you can redistribute it and/or modify 10 | it under the terms of the GNU General Public License as published by 11 | the Free Software Foundation, either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | Grbl is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU General Public License for more details. 18 | 19 | You should have received a copy of the GNU General Public License 20 | along with Grbl. If not, see . 21 | 22 | ------------------------------------------------------------------------------ 23 | COPYRIGHT NOTICE(S) FOR WORK CONTAINED IN THIS SOFTWARE: 24 | ------------------------------------------------------------------------------ 25 | 26 | ------------------------------------------------------------------------------ 27 | GNU GPLv3 TERMS AND CONDITIONS REPRODUCED HERE FOR CLARITY: 28 | ------------------------------------------------------------------------------ 29 | 30 | GNU GENERAL PUBLIC LICENSE 31 | Version 3, 29 June 2007 32 | 33 | Copyright (C) 2007 Free Software Foundation, Inc. 34 | Everyone is permitted to copy and distribute verbatim copies 35 | of this license document, but changing it is not allowed. 36 | 37 | Preamble 38 | 39 | The GNU General Public License is a free, copyleft license for 40 | software and other kinds of works. 41 | 42 | The licenses for most software and other practical works are designed 43 | to take away your freedom to share and change the works. By contrast, 44 | the GNU General Public License is intended to guarantee your freedom to 45 | share and change all versions of a program--to make sure it remains free 46 | software for all its users. We, the Free Software Foundation, use the 47 | GNU General Public License for most of our software; it applies also to 48 | any other work released this way by its authors. You can apply it to 49 | your programs, too. 50 | 51 | When we speak of free software, we are referring to freedom, not 52 | price. Our General Public Licenses are designed to make sure that you 53 | have the freedom to distribute copies of free software (and charge for 54 | them if you wish), that you receive source code or can get it if you 55 | want it, that you can change the software or use pieces of it in new 56 | free programs, and that you know you can do these things. 57 | 58 | To protect your rights, we need to prevent others from denying you 59 | these rights or asking you to surrender the rights. Therefore, you have 60 | certain responsibilities if you distribute copies of the software, or if 61 | you modify it: responsibilities to respect the freedom of others. 62 | 63 | For example, if you distribute copies of such a program, whether 64 | gratis or for a fee, you must pass on to the recipients the same 65 | freedoms that you received. You must make sure that they, too, receive 66 | or can get the source code. And you must show them these terms so they 67 | know their rights. 68 | 69 | Developers that use the GNU GPL protect your rights with two steps: 70 | (1) assert copyright on the software, and (2) offer you this License 71 | giving you legal permission to copy, distribute and/or modify it. 72 | 73 | For the developers' and authors' protection, the GPL clearly explains 74 | that there is no warranty for this free software. For both users' and 75 | authors' sake, the GPL requires that modified versions be marked as 76 | changed, so that their problems will not be attributed erroneously to 77 | authors of previous versions. 78 | 79 | Some devices are designed to deny users access to install or run 80 | modified versions of the software inside them, although the manufacturer 81 | can do so. This is fundamentally incompatible with the aim of 82 | protecting users' freedom to change the software. The systematic 83 | pattern of such abuse occurs in the area of products for individuals to 84 | use, which is precisely where it is most unacceptable. Therefore, we 85 | have designed this version of the GPL to prohibit the practice for those 86 | products. If such problems arise substantially in other domains, we 87 | stand ready to extend this provision to those domains in future versions 88 | of the GPL, as needed to protect the freedom of users. 89 | 90 | Finally, every program is threatened constantly by software patents. 91 | States should not allow patents to restrict development and use of 92 | software on general-purpose computers, but in those that do, we wish to 93 | avoid the special danger that patents applied to a free program could 94 | make it effectively proprietary. To prevent this, the GPL assures that 95 | patents cannot be used to render the program non-free. 96 | 97 | The precise terms and conditions for copying, distribution and 98 | modification follow. 99 | 100 | TERMS AND CONDITIONS 101 | 102 | 0. Definitions. 103 | 104 | "This License" refers to version 3 of the GNU General Public License. 105 | 106 | "Copyright" also means copyright-like laws that apply to other kinds of 107 | works, such as semiconductor masks. 108 | 109 | "The Program" refers to any copyrightable work licensed under this 110 | License. Each licensee is addressed as "you". "Licensees" and 111 | "recipients" may be individuals or organizations. 112 | 113 | To "modify" a work means to copy from or adapt all or part of the work 114 | in a fashion requiring copyright permission, other than the making of an 115 | exact copy. The resulting work is called a "modified version" of the 116 | earlier work or a work "based on" the earlier work. 117 | 118 | A "covered work" means either the unmodified Program or a work based 119 | on the Program. 120 | 121 | To "propagate" a work means to do anything with it that, without 122 | permission, would make you directly or secondarily liable for 123 | infringement under applicable copyright law, except executing it on a 124 | computer or modifying a private copy. Propagation includes copying, 125 | distribution (with or without modification), making available to the 126 | public, and in some countries other activities as well. 127 | 128 | To "convey" a work means any kind of propagation that enables other 129 | parties to make or receive copies. Mere interaction with a user through 130 | a computer network, with no transfer of a copy, is not conveying. 131 | 132 | An interactive user interface displays "Appropriate Legal Notices" 133 | to the extent that it includes a convenient and prominently visible 134 | feature that (1) displays an appropriate copyright notice, and (2) 135 | tells the user that there is no warranty for the work (except to the 136 | extent that warranties are provided), that licensees may convey the 137 | work under this License, and how to view a copy of this License. If 138 | the interface presents a list of user commands or options, such as a 139 | menu, a prominent item in the list meets this criterion. 140 | 141 | 1. Source Code. 142 | 143 | The "source code" for a work means the preferred form of the work 144 | for making modifications to it. "Object code" means any non-source 145 | form of a work. 146 | 147 | A "Standard Interface" means an interface that either is an official 148 | standard defined by a recognized standards body, or, in the case of 149 | interfaces specified for a particular programming language, one that 150 | is widely used among developers working in that language. 151 | 152 | The "System Libraries" of an executable work include anything, other 153 | than the work as a whole, that (a) is included in the normal form of 154 | packaging a Major Component, but which is not part of that Major 155 | Component, and (b) serves only to enable use of the work with that 156 | Major Component, or to implement a Standard Interface for which an 157 | implementation is available to the public in source code form. A 158 | "Major Component", in this context, means a major essential component 159 | (kernel, window system, and so on) of the specific operating system 160 | (if any) on which the executable work runs, or a compiler used to 161 | produce the work, or an object code interpreter used to run it. 162 | 163 | The "Corresponding Source" for a work in object code form means all 164 | the source code needed to generate, install, and (for an executable 165 | work) run the object code and to modify the work, including scripts to 166 | control those activities. However, it does not include the work's 167 | System Libraries, or general-purpose tools or generally available free 168 | programs which are used unmodified in performing those activities but 169 | which are not part of the work. For example, Corresponding Source 170 | includes interface definition files associated with source files for 171 | the work, and the source code for shared libraries and dynamically 172 | linked subprograms that the work is specifically designed to require, 173 | such as by intimate data communication or control flow between those 174 | subprograms and other parts of the work. 175 | 176 | The Corresponding Source need not include anything that users 177 | can regenerate automatically from other parts of the Corresponding 178 | Source. 179 | 180 | The Corresponding Source for a work in source code form is that 181 | same work. 182 | 183 | 2. Basic Permissions. 184 | 185 | All rights granted under this License are granted for the term of 186 | copyright on the Program, and are irrevocable provided the stated 187 | conditions are met. This License explicitly affirms your unlimited 188 | permission to run the unmodified Program. The output from running a 189 | covered work is covered by this License only if the output, given its 190 | content, constitutes a covered work. This License acknowledges your 191 | rights of fair use or other equivalent, as provided by copyright law. 192 | 193 | You may make, run and propagate covered works that you do not 194 | convey, without conditions so long as your license otherwise remains 195 | in force. You may convey covered works to others for the sole purpose 196 | of having them make modifications exclusively for you, or provide you 197 | with facilities for running those works, provided that you comply with 198 | the terms of this License in conveying all material for which you do 199 | not control copyright. Those thus making or running the covered works 200 | for you must do so exclusively on your behalf, under your direction 201 | and control, on terms that prohibit them from making any copies of 202 | your copyrighted material outside their relationship with you. 203 | 204 | Conveying under any other circumstances is permitted solely under 205 | the conditions stated below. Sublicensing is not allowed; section 10 206 | makes it unnecessary. 207 | 208 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 209 | 210 | No covered work shall be deemed part of an effective technological 211 | measure under any applicable law fulfilling obligations under article 212 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 213 | similar laws prohibiting or restricting circumvention of such 214 | measures. 215 | 216 | When you convey a covered work, you waive any legal power to forbid 217 | circumvention of technological measures to the extent such circumvention 218 | is effected by exercising rights under this License with respect to 219 | the covered work, and you disclaim any intention to limit operation or 220 | modification of the work as a means of enforcing, against the work's 221 | users, your or third parties' legal rights to forbid circumvention of 222 | technological measures. 223 | 224 | 4. Conveying Verbatim Copies. 225 | 226 | You may convey verbatim copies of the Program's source code as you 227 | receive it, in any medium, provided that you conspicuously and 228 | appropriately publish on each copy an appropriate copyright notice; 229 | keep intact all notices stating that this License and any 230 | non-permissive terms added in accord with section 7 apply to the code; 231 | keep intact all notices of the absence of any warranty; and give all 232 | recipients a copy of this License along with the Program. 233 | 234 | You may charge any price or no price for each copy that you convey, 235 | and you may offer support or warranty protection for a fee. 236 | 237 | 5. Conveying Modified Source Versions. 238 | 239 | You may convey a work based on the Program, or the modifications to 240 | produce it from the Program, in the form of source code under the 241 | terms of section 4, provided that you also meet all of these conditions: 242 | 243 | a) The work must carry prominent notices stating that you modified 244 | it, and giving a relevant date. 245 | 246 | b) The work must carry prominent notices stating that it is 247 | released under this License and any conditions added under section 248 | 7. This requirement modifies the requirement in section 4 to 249 | "keep intact all notices". 250 | 251 | c) You must license the entire work, as a whole, under this 252 | License to anyone who comes into possession of a copy. This 253 | License will therefore apply, along with any applicable section 7 254 | additional terms, to the whole of the work, and all its parts, 255 | regardless of how they are packaged. This License gives no 256 | permission to license the work in any other way, but it does not 257 | invalidate such permission if you have separately received it. 258 | 259 | d) If the work has interactive user interfaces, each must display 260 | Appropriate Legal Notices; however, if the Program has interactive 261 | interfaces that do not display Appropriate Legal Notices, your 262 | work need not make them do so. 263 | 264 | A compilation of a covered work with other separate and independent 265 | works, which are not by their nature extensions of the covered work, 266 | and which are not combined with it such as to form a larger program, 267 | in or on a volume of a storage or distribution medium, is called an 268 | "aggregate" if the compilation and its resulting copyright are not 269 | used to limit the access or legal rights of the compilation's users 270 | beyond what the individual works permit. Inclusion of a covered work 271 | in an aggregate does not cause this License to apply to the other 272 | parts of the aggregate. 273 | 274 | 6. Conveying Non-Source Forms. 275 | 276 | You may convey a covered work in object code form under the terms 277 | of sections 4 and 5, provided that you also convey the 278 | machine-readable Corresponding Source under the terms of this License, 279 | in one of these ways: 280 | 281 | a) Convey the object code in, or embodied in, a physical product 282 | (including a physical distribution medium), accompanied by the 283 | Corresponding Source fixed on a durable physical medium 284 | customarily used for software interchange. 285 | 286 | b) Convey the object code in, or embodied in, a physical product 287 | (including a physical distribution medium), accompanied by a 288 | written offer, valid for at least three years and valid for as 289 | long as you offer spare parts or customer support for that product 290 | model, to give anyone who possesses the object code either (1) a 291 | copy of the Corresponding Source for all the software in the 292 | product that is covered by this License, on a durable physical 293 | medium customarily used for software interchange, for a price no 294 | more than your reasonable cost of physically performing this 295 | conveying of source, or (2) access to copy the 296 | Corresponding Source from a network server at no charge. 297 | 298 | c) Convey individual copies of the object code with a copy of the 299 | written offer to provide the Corresponding Source. This 300 | alternative is allowed only occasionally and noncommercially, and 301 | only if you received the object code with such an offer, in accord 302 | with subsection 6b. 303 | 304 | d) Convey the object code by offering access from a designated 305 | place (gratis or for a charge), and offer equivalent access to the 306 | Corresponding Source in the same way through the same place at no 307 | further charge. You need not require recipients to copy the 308 | Corresponding Source along with the object code. If the place to 309 | copy the object code is a network server, the Corresponding Source 310 | may be on a different server (operated by you or a third party) 311 | that supports equivalent copying facilities, provided you maintain 312 | clear directions next to the object code saying where to find the 313 | Corresponding Source. Regardless of what server hosts the 314 | Corresponding Source, you remain obligated to ensure that it is 315 | available for as long as needed to satisfy these requirements. 316 | 317 | e) Convey the object code using peer-to-peer transmission, provided 318 | you inform other peers where the object code and Corresponding 319 | Source of the work are being offered to the general public at no 320 | charge under subsection 6d. 321 | 322 | A separable portion of the object code, whose source code is excluded 323 | from the Corresponding Source as a System Library, need not be 324 | included in conveying the object code work. 325 | 326 | A "User Product" is either (1) a "consumer product", which means any 327 | tangible personal property which is normally used for personal, family, 328 | or household purposes, or (2) anything designed or sold for incorporation 329 | into a dwelling. In determining whether a product is a consumer product, 330 | doubtful cases shall be resolved in favor of coverage. For a particular 331 | product received by a particular user, "normally used" refers to a 332 | typical or common use of that class of product, regardless of the status 333 | of the particular user or of the way in which the particular user 334 | actually uses, or expects or is expected to use, the product. A product 335 | is a consumer product regardless of whether the product has substantial 336 | commercial, industrial or non-consumer uses, unless such uses represent 337 | the only significant mode of use of the product. 338 | 339 | "Installation Information" for a User Product means any methods, 340 | procedures, authorization keys, or other information required to install 341 | and execute modified versions of a covered work in that User Product from 342 | a modified version of its Corresponding Source. The information must 343 | suffice to ensure that the continued functioning of the modified object 344 | code is in no case prevented or interfered with solely because 345 | modification has been made. 346 | 347 | If you convey an object code work under this section in, or with, or 348 | specifically for use in, a User Product, and the conveying occurs as 349 | part of a transaction in which the right of possession and use of the 350 | User Product is transferred to the recipient in perpetuity or for a 351 | fixed term (regardless of how the transaction is characterized), the 352 | Corresponding Source conveyed under this section must be accompanied 353 | by the Installation Information. But this requirement does not apply 354 | if neither you nor any third party retains the ability to install 355 | modified object code on the User Product (for example, the work has 356 | been installed in ROM). 357 | 358 | The requirement to provide Installation Information does not include a 359 | requirement to continue to provide support service, warranty, or updates 360 | for a work that has been modified or installed by the recipient, or for 361 | the User Product in which it has been modified or installed. Access to a 362 | network may be denied when the modification itself materially and 363 | adversely affects the operation of the network or violates the rules and 364 | protocols for communication across the network. 365 | 366 | Corresponding Source conveyed, and Installation Information provided, 367 | in accord with this section must be in a format that is publicly 368 | documented (and with an implementation available to the public in 369 | source code form), and must require no special password or key for 370 | unpacking, reading or copying. 371 | 372 | 7. Additional Terms. 373 | 374 | "Additional permissions" are terms that supplement the terms of this 375 | License by making exceptions from one or more of its conditions. 376 | Additional permissions that are applicable to the entire Program shall 377 | be treated as though they were included in this License, to the extent 378 | that they are valid under applicable law. If additional permissions 379 | apply only to part of the Program, that part may be used separately 380 | under those permissions, but the entire Program remains governed by 381 | this License without regard to the additional permissions. 382 | 383 | When you convey a copy of a covered work, you may at your option 384 | remove any additional permissions from that copy, or from any part of 385 | it. (Additional permissions may be written to require their own 386 | removal in certain cases when you modify the work.) You may place 387 | additional permissions on material, added by you to a covered work, 388 | for which you have or can give appropriate copyright permission. 389 | 390 | Notwithstanding any other provision of this License, for material you 391 | add to a covered work, you may (if authorized by the copyright holders of 392 | that material) supplement the terms of this License with terms: 393 | 394 | a) Disclaiming warranty or limiting liability differently from the 395 | terms of sections 15 and 16 of this License; or 396 | 397 | b) Requiring preservation of specified reasonable legal notices or 398 | author attributions in that material or in the Appropriate Legal 399 | Notices displayed by works containing it; or 400 | 401 | c) Prohibiting misrepresentation of the origin of that material, or 402 | requiring that modified versions of such material be marked in 403 | reasonable ways as different from the original version; or 404 | 405 | d) Limiting the use for publicity purposes of names of licensors or 406 | authors of the material; or 407 | 408 | e) Declining to grant rights under trademark law for use of some 409 | trade names, trademarks, or service marks; or 410 | 411 | f) Requiring indemnification of licensors and authors of that 412 | material by anyone who conveys the material (or modified versions of 413 | it) with contractual assumptions of liability to the recipient, for 414 | any liability that these contractual assumptions directly impose on 415 | those licensors and authors. 416 | 417 | All other non-permissive additional terms are considered "further 418 | restrictions" within the meaning of section 10. If the Program as you 419 | received it, or any part of it, contains a notice stating that it is 420 | governed by this License along with a term that is a further 421 | restriction, you may remove that term. If a license document contains 422 | a further restriction but permits relicensing or conveying under this 423 | License, you may add to a covered work material governed by the terms 424 | of that license document, provided that the further restriction does 425 | not survive such relicensing or conveying. 426 | 427 | If you add terms to a covered work in accord with this section, you 428 | must place, in the relevant source files, a statement of the 429 | additional terms that apply to those files, or a notice indicating 430 | where to find the applicable terms. 431 | 432 | Additional terms, permissive or non-permissive, may be stated in the 433 | form of a separately written license, or stated as exceptions; 434 | the above requirements apply either way. 435 | 436 | 8. Termination. 437 | 438 | You may not propagate or modify a covered work except as expressly 439 | provided under this License. Any attempt otherwise to propagate or 440 | modify it is void, and will automatically terminate your rights under 441 | this License (including any patent licenses granted under the third 442 | paragraph of section 11). 443 | 444 | However, if you cease all violation of this License, then your 445 | license from a particular copyright holder is reinstated (a) 446 | provisionally, unless and until the copyright holder explicitly and 447 | finally terminates your license, and (b) permanently, if the copyright 448 | holder fails to notify you of the violation by some reasonable means 449 | prior to 60 days after the cessation. 450 | 451 | Moreover, your license from a particular copyright holder is 452 | reinstated permanently if the copyright holder notifies you of the 453 | violation by some reasonable means, this is the first time you have 454 | received notice of violation of this License (for any work) from that 455 | copyright holder, and you cure the violation prior to 30 days after 456 | your receipt of the notice. 457 | 458 | Termination of your rights under this section does not terminate the 459 | licenses of parties who have received copies or rights from you under 460 | this License. If your rights have been terminated and not permanently 461 | reinstated, you do not qualify to receive new licenses for the same 462 | material under section 10. 463 | 464 | 9. Acceptance Not Required for Having Copies. 465 | 466 | You are not required to accept this License in order to receive or 467 | run a copy of the Program. Ancillary propagation of a covered work 468 | occurring solely as a consequence of using peer-to-peer transmission 469 | to receive a copy likewise does not require acceptance. However, 470 | nothing other than this License grants you permission to propagate or 471 | modify any covered work. These actions infringe copyright if you do 472 | not accept this License. Therefore, by modifying or propagating a 473 | covered work, you indicate your acceptance of this License to do so. 474 | 475 | 10. Automatic Licensing of Downstream Recipients. 476 | 477 | Each time you convey a covered work, the recipient automatically 478 | receives a license from the original licensors, to run, modify and 479 | propagate that work, subject to this License. You are not responsible 480 | for enforcing compliance by third parties with this License. 481 | 482 | An "entity transaction" is a transaction transferring control of an 483 | organization, or substantially all assets of one, or subdividing an 484 | organization, or merging organizations. If propagation of a covered 485 | work results from an entity transaction, each party to that 486 | transaction who receives a copy of the work also receives whatever 487 | licenses to the work the party's predecessor in interest had or could 488 | give under the previous paragraph, plus a right to possession of the 489 | Corresponding Source of the work from the predecessor in interest, if 490 | the predecessor has it or can get it with reasonable efforts. 491 | 492 | You may not impose any further restrictions on the exercise of the 493 | rights granted or affirmed under this License. For example, you may 494 | not impose a license fee, royalty, or other charge for exercise of 495 | rights granted under this License, and you may not initiate litigation 496 | (including a cross-claim or counterclaim in a lawsuit) alleging that 497 | any patent claim is infringed by making, using, selling, offering for 498 | sale, or importing the Program or any portion of it. 499 | 500 | 11. Patents. 501 | 502 | A "contributor" is a copyright holder who authorizes use under this 503 | License of the Program or a work on which the Program is based. The 504 | work thus licensed is called the contributor's "contributor version". 505 | 506 | A contributor's "essential patent claims" are all patent claims 507 | owned or controlled by the contributor, whether already acquired or 508 | hereafter acquired, that would be infringed by some manner, permitted 509 | by this License, of making, using, or selling its contributor version, 510 | but do not include claims that would be infringed only as a 511 | consequence of further modification of the contributor version. For 512 | purposes of this definition, "control" includes the right to grant 513 | patent sublicenses in a manner consistent with the requirements of 514 | this License. 515 | 516 | Each contributor grants you a non-exclusive, worldwide, royalty-free 517 | patent license under the contributor's essential patent claims, to 518 | make, use, sell, offer for sale, import and otherwise run, modify and 519 | propagate the contents of its contributor version. 520 | 521 | In the following three paragraphs, a "patent license" is any express 522 | agreement or commitment, however denominated, not to enforce a patent 523 | (such as an express permission to practice a patent or covenant not to 524 | sue for patent infringement). To "grant" such a patent license to a 525 | party means to make such an agreement or commitment not to enforce a 526 | patent against the party. 527 | 528 | If you convey a covered work, knowingly relying on a patent license, 529 | and the Corresponding Source of the work is not available for anyone 530 | to copy, free of charge and under the terms of this License, through a 531 | publicly available network server or other readily accessible means, 532 | then you must either (1) cause the Corresponding Source to be so 533 | available, or (2) arrange to deprive yourself of the benefit of the 534 | patent license for this particular work, or (3) arrange, in a manner 535 | consistent with the requirements of this License, to extend the patent 536 | license to downstream recipients. "Knowingly relying" means you have 537 | actual knowledge that, but for the patent license, your conveying the 538 | covered work in a country, or your recipient's use of the covered work 539 | in a country, would infringe one or more identifiable patents in that 540 | country that you have reason to believe are valid. 541 | 542 | If, pursuant to or in connection with a single transaction or 543 | arrangement, you convey, or propagate by procuring conveyance of, a 544 | covered work, and grant a patent license to some of the parties 545 | receiving the covered work authorizing them to use, propagate, modify 546 | or convey a specific copy of the covered work, then the patent license 547 | you grant is automatically extended to all recipients of the covered 548 | work and works based on it. 549 | 550 | A patent license is "discriminatory" if it does not include within 551 | the scope of its coverage, prohibits the exercise of, or is 552 | conditioned on the non-exercise of one or more of the rights that are 553 | specifically granted under this License. You may not convey a covered 554 | work if you are a party to an arrangement with a third party that is 555 | in the business of distributing software, under which you make payment 556 | to the third party based on the extent of your activity of conveying 557 | the work, and under which the third party grants, to any of the 558 | parties who would receive the covered work from you, a discriminatory 559 | patent license (a) in connection with copies of the covered work 560 | conveyed by you (or copies made from those copies), or (b) primarily 561 | for and in connection with specific products or compilations that 562 | contain the covered work, unless you entered into that arrangement, 563 | or that patent license was granted, prior to 28 March 2007. 564 | 565 | Nothing in this License shall be construed as excluding or limiting 566 | any implied license or other defenses to infringement that may 567 | otherwise be available to you under applicable patent law. 568 | 569 | 12. No Surrender of Others' Freedom. 570 | 571 | If conditions are imposed on you (whether by court order, agreement or 572 | otherwise) that contradict the conditions of this License, they do not 573 | excuse you from the conditions of this License. If you cannot convey a 574 | covered work so as to satisfy simultaneously your obligations under this 575 | License and any other pertinent obligations, then as a consequence you may 576 | not convey it at all. For example, if you agree to terms that obligate you 577 | to collect a royalty for further conveying from those to whom you convey 578 | the Program, the only way you could satisfy both those terms and this 579 | License would be to refrain entirely from conveying the Program. 580 | 581 | 13. Use with the GNU Affero General Public License. 582 | 583 | Notwithstanding any other provision of this License, you have 584 | permission to link or combine any covered work with a work licensed 585 | under version 3 of the GNU Affero General Public License into a single 586 | combined work, and to convey the resulting work. The terms of this 587 | License will continue to apply to the part which is the covered work, 588 | but the special requirements of the GNU Affero General Public License, 589 | section 13, concerning interaction through a network will apply to the 590 | combination as such. 591 | 592 | 14. Revised Versions of this License. 593 | 594 | The Free Software Foundation may publish revised and/or new versions of 595 | the GNU General Public License from time to time. Such new versions will 596 | be similar in spirit to the present version, but may differ in detail to 597 | address new problems or concerns. 598 | 599 | Each version is given a distinguishing version number. If the 600 | Program specifies that a certain numbered version of the GNU General 601 | Public License "or any later version" applies to it, you have the 602 | option of following the terms and conditions either of that numbered 603 | version or of any later version published by the Free Software 604 | Foundation. If the Program does not specify a version number of the 605 | GNU General Public License, you may choose any version ever published 606 | by the Free Software Foundation. 607 | 608 | If the Program specifies that a proxy can decide which future 609 | versions of the GNU General Public License can be used, that proxy's 610 | public statement of acceptance of a version permanently authorizes you 611 | to choose that version for the Program. 612 | 613 | Later license versions may give you additional or different 614 | permissions. However, no additional obligations are imposed on any 615 | author or copyright holder as a result of your choosing to follow a 616 | later version. 617 | 618 | 15. Disclaimer of Warranty. 619 | 620 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 621 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 622 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 623 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 624 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 625 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 626 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 627 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 628 | 629 | 16. Limitation of Liability. 630 | 631 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 632 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 633 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 634 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 635 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 636 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 637 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 638 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 639 | SUCH DAMAGES. 640 | 641 | 17. Interpretation of Sections 15 and 16. 642 | 643 | If the disclaimer of warranty and limitation of liability provided 644 | above cannot be given local legal effect according to their terms, 645 | reviewing courts shall apply local law that most closely approximates 646 | an absolute waiver of all civil liability in connection with the 647 | Program, unless a warranty or assumption of liability accompanies a 648 | copy of the Program in return for a fee. 649 | 650 | END OF TERMS AND CONDITIONS 651 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Plasma/THC 2 | 3 | Under development. Based on [LinuxCNC specification](http://linuxcnc.org/docs/2.8/html/plasma/plasmac-user-guide.html#config-panel), with limitations. 4 | 5 | #### $350 - Mode of operation 6 | 7 | | Mode | Description | 8 | |------|-------------| 9 | | 0 | Uses an external arc voltage input to calculate both Arc Voltage (for Torch Height Control) and Arc OK.| 10 | | 1 | Uses an external arc voltage input to calculate Arc Voltage (for Torch Height Control).
Uses an external Arc OK input for Arc OK.| 11 | | 2 | Uses an external Arc OK input for Arc OK.
Use external up/down signals for Torch Height Control.| 12 | 13 | #### THC 14 | 15 | | Setting | Modes | Description | 16 | |----------------------------|-------|-------------| 17 | | $351 - Delay | 0,1,2 | This sets the delay (in seconds) measured from the time the Arc OK signal is received until Torch Height Controller (THC) activates.| 18 | | $352 - Threshold \(V\) | 0,1,2 | This sets the voltage variation allowed from the target voltage before for THC makes movements to correct the torch height.| 19 | | $353 - P Gain | - | This sets the Proportional gain for the THC PID loop.
This roughly equates to how quickly the THC attempts to correct changes in height. | 20 | | $354 - I Gain | - | This sets the Integral gain for the THC PID loop.
Integral gain is associated with the sum of errors in the system over time and is not always needed.| 21 | | $355 - D Gain | - | This sets the Derivative gain for the THC PID loop.
Derivative gain works to dampen the system and reduce over correction oscillations and is not always needed.| 22 | | $356 - VAD Threshold \(%\) | - | \(Velocity Anti Dive\) This sets the percentage of the current cut feed rate the machine can slow to before locking the THC to prevent torch dive.| 23 | | $357 - Void Override \(%\) | - | This sets the size of the change in cut voltage necessary to lock the THC to prevent torch dive \(higher values need greater voltage change to lock THC\)| 24 | 25 | #### ARC 26 | 27 | | Setting | Modes | Description | 28 | |------------------------|-------|-------------| 29 | | $358 - Fail Timeout | 0,1,2 | This sets the amount of time (in seconds) PlasmaC will wait between commanding a "Torch On"
and receiving an Arc OK signal before timing out and displaying an error message.| 30 | | $359 - Retry Delay | 0,1,2 | This sets the time (in seconds) between an arc failure and another arc start attempt. 31 | | $360 - Max Retries | 0,1,2 | This sets the number of times PlasmaC will attempt to start the arc.| 32 | | $361 - Voltage Scale | - | This sets the arc voltage input scale and is used to display the correct arc voltage.| 33 | | $362 - Voltage Offset | - | This sets the arc voltage offset and is used to display zero volts when there is zero arc voltage input.| 34 | | $363 - Height Per Volt | - | This sets the distance the torch would need to move to change the arc voltage by one volt.
Used for manual height manipulation only.| 35 | | $364 - Ok High Voltage | - | This sets the voltage threshold below which Arc OK signal is valid.| 36 | | $365 - Ok Low Voltage | - | This sets the voltage threshold above which the Arc OK signal is valid.| 37 | 38 | #### Auxiliary I/O 39 | 40 | | Setting | Description | 41 | |-------------------------|-------------| 42 | | $366 - Arc voltage port | This sets which analog input port to use for the arc voltage signal. Set to -1 to not use any.1 | 43 | | $367 - Arc ok port | This sets which digital input port to use for the arc ok signal. Set to -1 to not use any.2 | 44 | | $368 - Cutter down port | This sets which digital input port to use for the cutter down signal. Set to -1 to not use any. | 45 | | $369 - Cutter up port | This sets which digital input port to use for the cutter up signal. Set to -1 to not use any. | 46 | 47 | 1 This pin/port is required to enable voltage controlled THC. 48 | 2 This pin/port is required to enable the plugin. 49 | 50 | Tip: use the `$PINS` command to list available pins. The port number is the number following the "_Aux in_" text, an example: `[PIN:P3.2,Aux in 0,P0]`. 51 | 52 | #### $674 - Plugin options 53 | 54 | | Bit | Value |Description | 55 | |-----|-------|------------| 56 | | 0 | 1 | Enable virtual ports. | 57 | | 1 | 2 | Sync Z position. Update the Z position when THC control ends. | 58 | 59 | Add the _Value_ fields for the functionality to enable to get the one to use for the setting. 60 | 61 | > [!NOTE] 62 | > Virtual ports will shadow any real ports with the same port number. Some dummy ports may also be added. 63 | 64 | #### Virtual ports 65 | 66 | Virtual ports are controlled by regular M-Codes. 67 | 68 | * `M62 P2` will disable THC \(Synchronized with Motion\) 69 | 70 | * `M63 P2` will enable THC \(Synchronized with Motion\) 71 | 72 | * `M64 P2` will disable THC \(Immediately\) 73 | 74 | * `M65 P2` will enable THC \(Immediately\) 75 | 76 | * `M67 E3 Q-` Velocity Reduction \(Immediately\) 77 | 78 | * `M68 E3 Q-` Velocity Reduction \(Synchronized with Motion) 79 | 80 | The Q-word for M67 and M68 is the percentage of the programmed feed rate the actual feed rate will be changed to. 81 | 82 | The minimum percentage allowed is 10%, values below this will be set to 10%. 83 | The maximum percentage allowed is 100%, values above this will be set to 100%. 84 | 85 | #### Materials 86 | 87 | The plugin can load and partially make use of LinuxCNC and/or SheetCam style material files. Loading is from either a SD card or from root mounted littlefs file system. 88 | 89 | The following files are currently loaded from if present: 90 | * LinuxCNC: _/linuxcnc/material.cfg_ 91 | * SheetCam: _/sheetcam/default.tools_ 92 | 93 | Additionally materials can be modified or added by LinuxCNC style _magic_ [gcode comments](https://linuxcnc.org/docs/html/plasma/qtplasmac.html#plasma:magic-comments). 94 | 95 | To select the material to use `M190P` where `` is the material number. 96 | If NGC parameter support is enabled in the controller the feedrate from the selected material can be set by adding `F#<_hal[plasmac.cut-feed-rate]>` to the gcode file. 97 | 98 | Currently loaded materials can be output to the sender console with the `$EM` command, the output is in a machine readable format. 99 | 100 | > [!NOTE] 101 | > Settings updated via _magic_ comments are currently _not_ written back to the material file. 102 | 103 | #### Dependencies: 104 | 105 | Driver must support a number of auxiliary I/O ports, at least one digital input for the arc ok signal. 106 | Some drivers support the MCP3221 I2C ADC, when enabled it can be used for the arc voltage signal. 107 | 108 | #### Credits: 109 | 110 | LinuxCNC documentation linked to above. 111 | 112 | --- 113 | 2025-02-23 114 | -------------------------------------------------------------------------------- /linuxcnc.c: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | linuxcnc.c - plasma cutter tool height control plugin 4 | 5 | Part of grblHAL 6 | 7 | Copyright (c) 2025 Terje Io 8 | 9 | grblHAL is free software: you can redistribute it and/or modify 10 | it under the terms of the GNU General Public License as published by 11 | the Free Software Foundation, either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | grblHAL is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU General Public License for more details. 18 | 19 | You should have received a copy of the GNU General Public License 20 | along with grblHAL. If not, see . 21 | 22 | */ 23 | 24 | #include "thc.h" 25 | 26 | #if PLASMA_ENABLE 27 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | #include "grbl/vfs.h" 35 | #include "grbl/strutils.h" 36 | 37 | static on_vfs_mount_ptr on_vfs_mount; 38 | 39 | static void load_tools (const char *path, const vfs_t *fs, vfs_st_mode_t mode) 40 | { 41 | // NOTE: must match layout of material_t 42 | static const char params[] = "PIERCE_HEIGHT,PIERCE_DELAY,CUT_SPEED,CUT_HEIGHT,CUT_VOLTS,PAUSE_AT_END,KERF_WIDTH,CUT_AMPS,GAS_PRESSURE,CUT_MODE,PUDDLE_JUMP_HEIGHT,PUDDLE_JUMP_DELAY,-,NAME,THC"; 43 | 44 | char c, *eq, buf[100]; 45 | uint_fast8_t idx = 0; 46 | vfs_file_t *file; 47 | status_code_t status = Status_GcodeUnusedWords; 48 | 49 | material_t material; 50 | 51 | if((file = vfs_open("/linuxcnc/material.cfg", "r"))) { 52 | 53 | while(vfs_read(&c, 1, 1, file) == 1) { 54 | 55 | if(c == ASCII_CR || c == ASCII_LF) { 56 | 57 | buf[idx] = '\0'; 58 | 59 | if(*buf == '[') { 60 | 61 | if(status == Status_OK && plasma_material_is_valid(&material)) 62 | plasma_material_add(&material, true); 63 | 64 | status = Status_GcodeUnusedWords; 65 | 66 | if((eq = strchr(buf, ']'))) { 67 | 68 | *eq = '\0'; 69 | 70 | if((eq = strrchr(buf, '_'))) { 71 | uint32_t id; 72 | uint_fast8_t cc = 1; 73 | if((status = read_uint(eq, &cc, &id)) == Status_OK) { 74 | material.id = (int32_t)id; 75 | material.thc_status = true; //? 76 | *material.name = '\0'; 77 | for(idx = 0; idx < sizeof(material.params) / sizeof(float); idx++) 78 | material.params[idx] = NAN; 79 | } 80 | } 81 | } 82 | } 83 | 84 | if(status == Status_OK && (eq = strchr(buf, '='))) { 85 | 86 | int32_t p; 87 | 88 | *eq++ = '\0'; 89 | 90 | while(*eq == ' ') 91 | eq++; 92 | 93 | switch((p = strlookup(strtok(buf, " "), params, ','))) { 94 | 95 | case -1: 96 | status = Status_GcodeUnsupportedCommand; 97 | break; 98 | 99 | case 13: 100 | strncpy(material.name, eq, sizeof(material.name) - 1); 101 | material.name[sizeof(material.name) - 1] = '\0'; 102 | break; 103 | 104 | case 14: 105 | material.thc_status = *eq != '0'; 106 | break; 107 | 108 | default: 109 | { 110 | uint_fast8_t cc = 0; 111 | if(!read_float(eq, &cc, &material.params[p])) 112 | status = Status_BadNumberFormat; 113 | } 114 | break; 115 | } 116 | } 117 | idx = 0; 118 | } else if(idx < sizeof(buf)) 119 | buf[idx++] = c; 120 | } 121 | 122 | if(status == Status_OK && plasma_material_is_valid(&material)) 123 | plasma_material_add(&material, true); 124 | 125 | vfs_close(file); 126 | } 127 | 128 | if(on_vfs_mount) 129 | on_vfs_mount(path, fs, mode); 130 | } 131 | 132 | void linuxcnc_init (void) 133 | { 134 | on_vfs_mount = vfs.on_mount; 135 | vfs.on_mount = load_tools; 136 | } 137 | 138 | #endif // PLASMA_ENABLE 139 | -------------------------------------------------------------------------------- /powermax.c: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | powermax.c - PowerMax plasma cutter RS-485 communication 4 | 5 | Part of grblHAL 6 | 7 | Copyright (c) 2025 Terje Io 8 | 9 | grblHAL is free software: you can redistribute it and/or modify 10 | it under the terms of the GNU General Public License as published by 11 | the Free Software Foundation, either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | grblHAL is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU General Public License for more details. 18 | 19 | You should have received a copy of the GNU General Public License 20 | along with grblHAL. If not, see . 21 | 22 | */ 23 | 24 | #include "thc.h" 25 | 26 | #if PLASMA_ENABLE && 0 27 | 28 | #include 29 | #include 30 | 31 | #include "../spindle/modbus_rtu.h" 32 | 33 | static uint32_t modbus_address = 1; 34 | 35 | static void rx_packet (modbus_message_t *msg); 36 | static void rx_exception (uint8_t code, void *context); 37 | 38 | static const modbus_callbacks_t callbacks = { 39 | .retries = 5, 40 | .retry_delay = 50, 41 | .on_rx_packet = rx_packet, 42 | .on_rx_exception = rx_exception 43 | }; 44 | 45 | static bool set_cut_mode (uint16_t mode) 46 | { 47 | modbus_message_t rpm_cmd = { 48 | .context = (void *)Plasma_SetMode, 49 | .crc_check = false, 50 | .adu[0] = modbus_address, 51 | .adu[1] = ModBus_WriteRegister, 52 | .adu[2] = 0x20, 53 | .adu[3] = 0x93, 54 | .adu[4] = mode >> 8, 55 | .adu[5] = mode & 0xFF, 56 | .tx_length = 8, 57 | .rx_length = 8 58 | }; 59 | 60 | return modbus_send(&rpm_cmd, &callbacks, true); 61 | } 62 | 63 | static bool set_amperage (float a) 64 | { 65 | uint16_t amps = (uint16_t)(a * 64); 66 | 67 | modbus_message_t rpm_cmd = { 68 | .context = (void *)Plasma_SetCurrent, 69 | .crc_check = false, 70 | .adu[0] = modbus_address, 71 | .adu[1] = ModBus_WriteRegister, 72 | .adu[2] = 0x20, 73 | .adu[3] = 0x94, 74 | .adu[4] = amps >> 8, 75 | .adu[5] = amps & 0xFF, 76 | .tx_length = 8, 77 | .rx_length = 8 78 | }; 79 | 80 | return modbus_send(&rpm_cmd, &callbacks, true); 81 | } 82 | 83 | static bool set_gas_pressure (float psi) 84 | { 85 | uint16_t pressure = (uint16_t)(psi * 128); 86 | 87 | modbus_message_t mode_cmd = { 88 | .context = (void *)Plasma_SetPressure, 89 | .crc_check = false, 90 | .adu[0] = modbus_address, 91 | .adu[1] = ModBus_WriteRegister, 92 | .adu[2] = 0x20, 93 | .adu[3] = 0x96, 94 | .adu[4] = pressure >> 8, 95 | .adu[5] = pressure & 0xFF, 96 | .tx_length = 8, 97 | .rx_length = 8 98 | }; 99 | 100 | return modbus_send(&mode_cmd, &callbacks, true); 101 | } 102 | 103 | static void rx_packet (modbus_message_t *msg) 104 | { 105 | if(!(msg->adu[0] & 0x80)) { 106 | /* 107 | switch((vfd_response_t)msg->context) { 108 | 109 | case VFD_GetRPM: 110 | spindle_validate_at_speed(spindle_data, f2rpm((msg->adu[3] << 8) | msg->adu[4])); 111 | break; 112 | 113 | case VFD_GetMinRPM: 114 | freq_min = (msg->adu[3] << 8) | msg->adu[4]; 115 | break; 116 | 117 | case VFD_GetMaxRPM: 118 | freq_max = (msg->adu[3] << 8) | msg->adu[4]; 119 | spindle_hal->cap.rpm_range_locked = On; 120 | spindle_hal->rpm_min = f2rpm(freq_min); 121 | spindle_hal->rpm_max = f2rpm(freq_max); 122 | break; 123 | 124 | default: 125 | break; 126 | } 127 | */ 128 | } 129 | } 130 | 131 | static void rx_exception (uint8_t code, void *context) 132 | { 133 | // raise alarm? 134 | } 135 | 136 | /* 137 | static void onReportOptions (bool newopt) 138 | { 139 | on_report_options(newopt); 140 | 141 | if(!newopt) 142 | report_plugin("PowerMax RS-485", "0.01"); 143 | } 144 | */ 145 | 146 | void powermax_init (void) 147 | { 148 | cutter.set_cut_mode = set_cut_mode; 149 | cutter.set_current = set_amperage; 150 | cutter.set_pressure = set_gas_pressure; 151 | } 152 | 153 | #endif 154 | -------------------------------------------------------------------------------- /sheetcam.c: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | sheetcam.c - plasma cutter tool height control plugin 4 | 5 | Part of grblHAL 6 | 7 | Copyright (c) 2025 Terje Io 8 | 9 | grblHAL is free software: you can redistribute it and/or modify 10 | it under the terms of the GNU General Public License as published by 11 | the Free Software Foundation, either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | grblHAL is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU General Public License for more details. 18 | 19 | You should have received a copy of the GNU General Public License 20 | along with grblHAL. If not, see . 21 | 22 | */ 23 | 24 | #include "thc.h" 25 | 26 | #if PLASMA_ENABLE 27 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | #include "grbl/vfs.h" 35 | #include "grbl/strutils.h" 36 | 37 | static on_vfs_mount_ptr on_vfs_mount; 38 | 39 | static void load_tools (const char *path, const vfs_t *fs, vfs_st_mode_t mode) 40 | { 41 | static const char params[] = "Pierce\\ height,Pierce\\ delay,Feed\\ rate,Cut\\ height,cv,Pause\\ at\\ end\\ of\\ cut,Kerf\\ width,ca,gp,cm,jh,jd,Tool\\ number,Name,th"; // NOTE: must match layout of material_t 42 | 43 | char c, *eq, buf[50]; 44 | uint_fast8_t idx = 0; 45 | vfs_file_t *file; 46 | status_code_t status = Status_GcodeUnusedWords; 47 | 48 | material_t material; 49 | 50 | if((file = vfs_open("/sheetcam/default.tools", "r"))) { 51 | 52 | while(vfs_read(&c, 1, 1, file) == 1) { 53 | 54 | if(c == ASCII_CR || c == ASCII_LF) { 55 | buf[idx] = '\0'; 56 | if(*buf) { 57 | if(*buf == '[') { 58 | 59 | if(status == Status_OK && plasma_material_is_valid(&material)) 60 | plasma_material_add(&material, true); 61 | 62 | material.id = -1; 63 | *material.name = '\0'; 64 | status = Status_GcodeUnusedWords; 65 | 66 | for(idx = 0; idx < sizeof(material.params) / sizeof(float); idx++) 67 | material.params[idx] = NAN; 68 | } 69 | } 70 | 71 | if((eq = strchr(buf, '='))) { 72 | 73 | int32_t p; 74 | 75 | *eq = '\0'; 76 | 77 | switch((p = strlookup(buf, params, ','))) { 78 | 79 | case -1: 80 | break; 81 | 82 | case 12: 83 | { 84 | uint32_t id; 85 | uint_fast8_t cc = 1; 86 | if((status = read_uint(eq, &cc, &id)) == Status_OK) 87 | material.id = (int32_t)id; 88 | } 89 | break; 90 | 91 | case 13: 92 | strncpy(material.name, eq + 1, sizeof(material.name) - 1); 93 | break; 94 | 95 | case 14: 96 | material.thc_status = eq[1] != '0'; 97 | break; 98 | 99 | default: 100 | { 101 | uint_fast8_t cc = 1; 102 | if(!read_float(eq, &cc, &material.params[p])) 103 | status = Status_BadNumberFormat; 104 | } 105 | break; 106 | } 107 | } 108 | idx = 0; 109 | } else if(idx < sizeof(buf)) 110 | buf[idx++] = c; 111 | } 112 | 113 | if(status == Status_OK && plasma_material_is_valid(&material)) 114 | plasma_material_add(&material, true); 115 | 116 | vfs_close(file); 117 | } 118 | 119 | if(on_vfs_mount) 120 | on_vfs_mount(path, fs, mode); 121 | } 122 | 123 | void sheetcam_init (void) 124 | { 125 | on_vfs_mount = vfs.on_mount; 126 | vfs.on_mount = load_tools; 127 | } 128 | 129 | #endif // PLASMA_ENABLE 130 | -------------------------------------------------------------------------------- /thc.c: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | thc.c - plasma cutter tool height control plugin 4 | 5 | Part of grblHAL 6 | 7 | Copyright (c) 2020-2025 Terje Io 8 | 9 | grblHAL is free software: you can redistribute it and/or modify 10 | it under the terms of the GNU General Public License as published by 11 | the Free Software Foundation, either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | grblHAL is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU General Public License for more details. 18 | 19 | You should have received a copy of the GNU General Public License 20 | along with grblHAL. If not, see . 21 | 22 | */ 23 | 24 | #include "thc.h" 25 | 26 | #if PLASMA_ENABLE 27 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | #include "grbl/config.h" 35 | #include "grbl/hal.h" 36 | #include "grbl/protocol.h" 37 | #include "grbl/report.h" 38 | #include "grbl/pid.h" 39 | #include "grbl/nvs_buffer.h" 40 | #include "grbl/stepper2.h" 41 | #include "grbl/state_machine.h" 42 | #include "grbl/strutils.h" 43 | #include "grbl/motion_control.h" 44 | #include "grbl/task.h" 45 | #if NGC_EXPRESSIONS_ENABLE 46 | #include "grbl/ngc_expr.h" 47 | #endif 48 | 49 | extern void linuxcnc_init (void); 50 | extern void sheetcam_init (void); 51 | 52 | #define THC_SAMPLE_AVG 5 53 | #define PLASMA_TMP_MATERIAL_ID_START 1000000 54 | // Digital ports 55 | #define PLASMA_THC_DISABLE_PORT 2 // output 56 | #define PLASMA_TORCH_DISABLE_PORT 3 // output 57 | // Analog ports 58 | #define PLASMA_FEED_OVERRIDE_PORT 3 59 | 60 | typedef enum { 61 | Plasma_ModeOff = 0, 62 | Plasma_ModeVoltage, 63 | Plasma_ModeUpDown, 64 | Plasma_ModeArcOK 65 | } plasma_mode_t; 66 | 67 | // float parameters must be in the same order as in material_t. 68 | typedef struct { 69 | int32_t id; 70 | bool thc_disabled; 71 | union { 72 | float params[6]; 73 | struct { 74 | float pierce_height; 75 | float pierce_delay; 76 | float feed_rate; 77 | float cut_height; 78 | float cut_voltage; 79 | float pause_at_end; 80 | }; 81 | }; 82 | } plasma_job_t; 83 | 84 | typedef union { 85 | uint8_t flags; 86 | struct { 87 | uint8_t virtual_ports :1, 88 | sync_pos :1, 89 | onoffmode :1, 90 | unassigned :5; 91 | }; 92 | } thc_options_t; 93 | 94 | typedef struct { 95 | float thc_delay; 96 | float thc_threshold; 97 | uint32_t vad_threshold; 98 | uint32_t thc_override; 99 | float pierce_height; 100 | float pierce_delay; 101 | float pause_at_end; 102 | float arc_retry_delay; 103 | float arc_fail_timeout; 104 | float arc_voltage_scale; 105 | float arc_voltage_offset; 106 | float arc_height_per_volt; 107 | float arc_ok_low_voltage; 108 | float arc_high_low_voltage; 109 | uint8_t arc_retries; 110 | thc_options_t option; 111 | uint8_t unused1; 112 | uint8_t unused2; 113 | plasma_mode_t mode; 114 | pid_values_t pid; 115 | uint8_t port_arc_voltage; 116 | uint8_t port_arc_ok; 117 | uint8_t port_cutter_down; 118 | uint8_t port_cutter_up; 119 | } plasma_settings_t; 120 | 121 | typedef union { 122 | uint16_t bits; 123 | uint16_t value; 124 | struct { 125 | uint16_t arc_ok :1, 126 | torch_on :1, 127 | enabled :1, 128 | ohmic_probe :1, 129 | float_switch :1, 130 | breakaway :1, 131 | active :1, 132 | up :1, 133 | down :1, 134 | velocity_lock :1, 135 | void_lock :1, 136 | report_up :1, 137 | report_down :1, 138 | unassigned :3; 139 | }; 140 | } thc_signals_t; 141 | 142 | static void state_idle (void); 143 | static void state_thc_delay (void); 144 | static void state_thc_pid (void); 145 | static void state_thc_adjust (void); 146 | static void state_arc_monitor (void); 147 | static void state_vad_lock (void); 148 | 149 | plasma_control_t cutter = {}; 150 | 151 | static bool set_feed_override = false, updown_enabled = false, init_ok = false; 152 | static uint8_t n_ain, n_din; 153 | static uint8_t port_arc_ok, port_arc_voltage; 154 | static uint_fast8_t feed_override, segment_id = 0; 155 | static uint32_t thc_delay = 0, v_count = 0; 156 | static int32_t step_count; 157 | static char max_aport[4], max_dport[4]; 158 | static float arc_vref = 0.0f, arc_voltage = 0.0f, arc_voltage_low, arc_voltage_high; //, vad_threshold; 159 | static float fr_pgm, fr_actual, fr_thr_99, fr_thr_vad; 160 | static thc_signals_t thc = {0}; 161 | static pidf_t pid; 162 | static nvs_address_t nvs_address; 163 | static char thc_modes[] = "Off,Voltage,Up/down,Arc ok"; 164 | static plasma_settings_t plasma; 165 | static st2_motor_t *z_motor; 166 | static void (*volatile stateHandler)(void) = state_idle; 167 | static plasma_mode_t mode = Plasma_ModeOff; 168 | static xbar_t arc_ok, cutter_down, cutter_up, parc_voltage; 169 | static uint32_t material_id = PLASMA_TMP_MATERIAL_ID_START; 170 | static material_t *materials = NULL, tmp_material = { .id = -1 }; 171 | static plasma_job_t job = {}; 172 | 173 | static settings_changed_ptr settings_changed; 174 | static driver_reset_ptr driver_reset = NULL; 175 | static spindle_set_state_ptr spindle_set_state_ = NULL; 176 | //static control_signals_callback_ptr control_interrupt_callback = NULL; 177 | static stepper_pulse_start_ptr stepper_pulse_start = NULL; 178 | static enumerate_pins_ptr enumerate_pins; 179 | static on_report_options_ptr on_report_options; 180 | static on_spindle_selected_ptr on_spindle_selected; 181 | static on_execute_realtime_ptr on_execute_realtime = NULL; 182 | static on_realtime_report_ptr on_realtime_report = NULL; 183 | static on_gcode_message_ptr on_gcode_comment; 184 | static user_mcode_ptrs_t user_mcode; 185 | 186 | static const char params[] = "ph,pd,fr,ch,cv,pe,kw,ca,gp,cm,jh,jd,nu,na,th"; // NOTE: must match layout of material_t 187 | 188 | // --- Virtual ports start 189 | 190 | static io_port_t rport = {0}; 191 | static io_ports_data_t digital, analog; 192 | 193 | typedef struct { 194 | uint8_t pin_id; 195 | pin_info_ptr pin_info; 196 | void *data; 197 | bool aux_dout0; 198 | bool aux_dout1; 199 | bool aux_aout0; 200 | bool aux_aout1; 201 | bool aux_aout2; 202 | } pin_stat_t; 203 | 204 | static void enum_trap (xbar_t *pin, void *data) 205 | { 206 | ((pin_stat_t *)data)->pin_id = max(((pin_stat_t *)data)->pin_id, pin->id); 207 | 208 | if(pin->function == Output_Aux0) 209 | ((pin_stat_t *)data)->aux_dout0 = true; 210 | else if(pin->function == Output_Aux1) 211 | ((pin_stat_t *)data)->aux_dout1 = true; 212 | else if(pin->function == Output_Analog_Aux0) 213 | ((pin_stat_t *)data)->aux_aout0 = true; 214 | else if(pin->function == Output_Analog_Aux1) 215 | ((pin_stat_t *)data)->aux_aout1 = true; 216 | else if(pin->function == Output_Analog_Aux2) 217 | ((pin_stat_t *)data)->aux_aout2 = true; 218 | 219 | if((pin->function == Output_Aux2 || pin->function == Output_Aux3 || pin->function == Output_Analog_Aux3)) 220 | pin->description = "Shadowed by THC"; 221 | 222 | if(((pin_stat_t *)data)->pin_info) 223 | ((pin_stat_t *)data)->pin_info(pin, ((pin_stat_t *)data)->data); 224 | } 225 | 226 | static void enumeratePins (bool low_level, pin_info_ptr pin_info, void *data) 227 | { 228 | pin_stat_t pin_stat = { 229 | .pin_id = 0, 230 | .pin_info = pin_info, 231 | .data = data 232 | }; 233 | 234 | static xbar_t pin = { 235 | .id = 0, 236 | .pin = 0, 237 | .group = PinGroup_Virtual, 238 | .function = Virtual_Pin, 239 | .cap.claimable = Off, 240 | .mode.output = On, 241 | .mode.claimed = On 242 | }; 243 | 244 | enumerate_pins(low_level, enum_trap, &pin_stat); 245 | 246 | if(!pin_stat.aux_dout0) { 247 | pin.id = ++pin_stat.pin_id; 248 | pin.description = "P0,Dummy out"; 249 | pin_info(&pin, data); 250 | } 251 | 252 | if(!pin_stat.aux_dout1) { 253 | pin.id = ++pin_stat.pin_id; 254 | pin.description = "P1,Dummy out"; 255 | pin_info(&pin, data); 256 | } 257 | 258 | pin.id = ++pin_stat.pin_id; 259 | pin.port = "THCD", 260 | pin.description = "P2,THC on/off"; 261 | pin_info(&pin, data); 262 | 263 | pin.id = ++pin_stat.pin_id; 264 | pin.pin++; 265 | pin.description = "P3,THC torch control"; 266 | pin_info(&pin, data); 267 | 268 | pin.pin = 0; 269 | pin.port = "THCA"; 270 | pin.mode.analog = On; 271 | 272 | if(!pin_stat.aux_aout0) { 273 | pin.id = ++pin_stat.pin_id; 274 | pin.description = "E0,Dummy out"; 275 | pin_info(&pin, data); 276 | pin.pin++; 277 | } 278 | 279 | if(!pin_stat.aux_aout1) { 280 | pin.id = ++pin_stat.pin_id; 281 | pin.description = "E1,Dummy out"; 282 | pin_info(&pin, data); 283 | pin.pin++; 284 | } 285 | 286 | if(!pin_stat.aux_aout1) { 287 | pin.id = ++pin_stat.pin_id; 288 | pin.description = "E2,Dummy out"; 289 | pin_info(&pin, data); 290 | pin.pin++; 291 | } 292 | 293 | pin.id = ++pin_stat.pin_id; 294 | pin.port = "THCA"; 295 | pin.description = "E3,THC feed override"; 296 | pin_info(&pin, data); 297 | } 298 | 299 | static void digital_out (uint8_t portnum, bool on) 300 | { 301 | if(portnum == PLASMA_THC_DISABLE_PORT) { 302 | job.thc_disabled = on; 303 | if(thc.arc_ok && mode != Plasma_ModeArcOK) { 304 | if(!(thc.enabled = !job.thc_disabled)) 305 | stateHandler = state_arc_monitor; 306 | else 307 | stateHandler = mode == Plasma_ModeUpDown ? state_thc_adjust : state_thc_pid; 308 | } 309 | } else if(portnum == PLASMA_TORCH_DISABLE_PORT) { 310 | // PLASMA_TORCH_DISABLE_PORT: 311 | // TODO 312 | } else if(rport.digital_out) 313 | rport.digital_out(portnum, on); 314 | } 315 | 316 | static bool analog_out (uint8_t portnum, float value) 317 | { 318 | if(portnum == PLASMA_FEED_OVERRIDE_PORT) { 319 | // Let the foreground process handle this 320 | set_feed_override = true; 321 | feed_override = (uint_fast8_t)value; 322 | if(feed_override < 10 || feed_override > 100) 323 | feed_override = 100; 324 | } else 325 | return rport.analog_out ? rport.analog_out(portnum, value) : false; 326 | 327 | return true; 328 | } 329 | 330 | static xbar_t *get_pin_info (io_port_type_t type, io_port_direction_t dir, uint8_t port) 331 | { 332 | static xbar_t pin; 333 | 334 | xbar_t *info = rport.get_pin_info(type, dir, port); 335 | 336 | if(info == NULL && dir == Port_Output) { 337 | 338 | if(type == Port_Digital && port >= digital.out.n_start && port < digital.out.n_start + digital.out.n_ports) { 339 | 340 | memset(&pin, 0, sizeof(xbar_t)); 341 | 342 | pin.id = pin.pin = port; 343 | pin.cap.output = On; 344 | pin.cap.claimable = Off; 345 | pin.mode.output = On; 346 | pin.description = port == digital.out.n_start ? "THC enable/disable" : "THC torch control"; 347 | 348 | return &pin; 349 | } 350 | 351 | if(type == Port_Analog && port >= analog.out.n_start && port < analog.out.n_start + analog.out.n_ports) { 352 | 353 | memset(&pin, 0, sizeof(xbar_t)); 354 | 355 | pin.id = pin.pin = port; 356 | pin.cap.output = pin.cap.analog = On; 357 | pin.cap.claimable = Off; 358 | pin.mode.output = pin.cap.analog = On; 359 | pin.description = "THC feed override"; 360 | 361 | return &pin; 362 | } 363 | } 364 | 365 | return info; 366 | } 367 | 368 | static void add_virtual_ports (void *data) 369 | { 370 | uint8_t aux_dout = 2, aux_aout = 1; 371 | pin_stat_t pin_stat = {}; 372 | 373 | if(hal.enumerate_pins) 374 | hal.enumerate_pins(false, enum_trap, &pin_stat); 375 | 376 | if(!pin_stat.aux_dout0) 377 | aux_dout++; 378 | if(!pin_stat.aux_dout1) 379 | aux_dout++; 380 | if(!pin_stat.aux_aout0) 381 | aux_aout++; 382 | if(!pin_stat.aux_aout1) 383 | aux_aout++; 384 | if(!pin_stat.aux_aout2) 385 | aux_aout++; 386 | 387 | if(ioports_add(&digital, Port_Digital, 0, aux_dout) && ioports_add(&analog, Port_Analog, 0, aux_aout)) { 388 | 389 | memcpy(&rport, &hal.port, sizeof(io_port_t)); 390 | 391 | hal.port.digital_out = digital_out; 392 | hal.port.analog_out = analog_out; 393 | hal.port.get_pin_info = get_pin_info; 394 | 395 | enumerate_pins = hal.enumerate_pins; 396 | hal.enumerate_pins = enumeratePins; 397 | } 398 | } 399 | 400 | // --- Virtual ports end 401 | 402 | // --- Materials handling start 403 | 404 | static void set_job_params (material_t *material) 405 | { 406 | uint_fast8_t idx; 407 | 408 | job.id = material ? material->id : -1; 409 | job.thc_disabled = (material && !material->thc_status) || plasma.mode == Plasma_ModeOff || plasma.mode == Plasma_ModeArcOK; 410 | 411 | if(material) { 412 | 413 | for(idx = 0; idx < sizeof(job.params) / sizeof(float); idx++) 414 | job.params[idx] = material->params[idx]; 415 | 416 | if(!isnanf(material->cut_mode) && cutter.set_cut_mode) 417 | cutter.set_cut_mode((uint16_t)material->cut_mode); 418 | 419 | if(!isnanf(material->cut_amps) && cutter.set_current) 420 | cutter.set_current(material->cut_amps); 421 | 422 | if(!isnanf(material->gas_pressure) && cutter.set_pressure) 423 | cutter.set_pressure(material->gas_pressure); 424 | 425 | #if NGC_EXPRESSIONS_ENABLE 426 | ngc_named_param_set("_hal[plasmac.cut-feed-rate]", job.feed_rate); 427 | #endif 428 | } else { 429 | 430 | for(idx = 0; idx < sizeof(job.params) / sizeof(float); idx++) 431 | job.params[idx] = NAN; 432 | 433 | job.pierce_height = plasma.pierce_height; 434 | job.pierce_delay = plasma.pierce_delay; 435 | job.pause_at_end = plasma.pause_at_end; 436 | } 437 | 438 | if(material && *material->name) 439 | report_message(material->name, Message_Info); 440 | } 441 | 442 | static material_t *find_material (uint32_t id) 443 | { 444 | material_t *material = materials, *found = NULL; 445 | 446 | if(material) do { 447 | if(material->id == id) 448 | found = material; 449 | } while(found == NULL && (material = material->next)); 450 | 451 | return found; 452 | } 453 | 454 | static user_mcode_type_t mcode_check (user_mcode_t mcode) 455 | { 456 | return mcode == Plasma_SelectMaterial 457 | ? UserMCode_Normal 458 | : (user_mcode.check ? user_mcode.check(mcode) : UserMCode_Unsupported); 459 | } 460 | 461 | static status_code_t mcode_validate (parser_block_t *gc_block) 462 | { 463 | status_code_t state = Status_OK; 464 | 465 | if(gc_block->user_mcode == Plasma_SelectMaterial) { 466 | if(gc_block->words.p) { 467 | if(!isintf(gc_block->values.p)) 468 | state = Status_BadNumberFormat; 469 | else if(gc_block->words.p && !(gc_block->values.p == -1.0f || find_material((uint32_t)gc_block->values.p))) 470 | state = Status_GcodeValueOutOfRange; 471 | else 472 | gc_block->words.p = Off; 473 | } 474 | } else 475 | state = Status_Unhandled; 476 | 477 | return state == Status_Unhandled && user_mcode.validate ? user_mcode.validate(gc_block) : state; 478 | } 479 | 480 | static void mcode_execute (uint_fast16_t state, parser_block_t *gc_block) 481 | { 482 | if(gc_block->user_mcode == Plasma_SelectMaterial) 483 | set_job_params(gc_block->values.p == -1.0f ? NULL : find_material((uint32_t)gc_block->values.p)); 484 | else if(user_mcode.execute) 485 | user_mcode.execute(state, gc_block); 486 | } 487 | 488 | bool plasma_enumerate_materials (plasma_enumerate_materials_callback_ptr callback, void *data) 489 | { 490 | bool ok = false; 491 | material_t *material = materials; 492 | 493 | if(job.id == tmp_material.id) 494 | ok = callback(&tmp_material, data); 495 | 496 | if(!ok && material) do { 497 | ok = callback(material, data); 498 | } while(!ok && (material = material->next)); 499 | 500 | return ok; 501 | } 502 | 503 | bool plasma_material_is_valid (material_t *material) 504 | { 505 | return !(material->id < 0 || 506 | material->id >= PLASMA_TMP_MATERIAL_ID_START || 507 | isnanf(material->pierce_height) || 508 | isnanf(material->pierce_delay) || 509 | isnanf(material->cut_height) || 510 | isnanf(material->feed_rate)); 511 | } 512 | 513 | bool plasma_material_add (material_t *material, bool overwrite) 514 | { 515 | material_t *m = find_material(material->id), *next = m ? m->next : NULL; 516 | bool add = m == NULL; 517 | 518 | if(overwrite || m == NULL) { 519 | if(m == NULL) 520 | m = malloc(sizeof(material_t)); 521 | if(m) { 522 | memcpy(m, material, sizeof(material_t)); 523 | m->next = next; 524 | if(materials == NULL) 525 | materials = m; 526 | else if(add) { 527 | material_t *last = materials; 528 | while(last->next) 529 | last = last->next; 530 | last->next = m; 531 | } 532 | } // else error.... 533 | } 534 | 535 | return true; 536 | } 537 | 538 | static bool ml_enumerate (material_t *material, void *data) 539 | { 540 | uint_fast8_t i; 541 | char lbl[3], el[]= "| :"; 542 | 543 | hal.stream.write("[MATERIAL:o:"); 544 | hal.stream.write(uitoa(material->id >= PLASMA_TMP_MATERIAL_ID_START ? 0 : 2)); 545 | hal.stream.write("|nu:"); 546 | hal.stream.write(uitoa(material->id)); 547 | if(*material->name) { 548 | hal.stream.write("|na:"); 549 | hal.stream.write(material->name); 550 | } 551 | hal.stream.write("|th:"); 552 | hal.stream.write(uitoa(material->thc_status)); 553 | 554 | for(i = 0; i < sizeof(material->params) / sizeof(float); i++) { 555 | if(!isnanf(material->params[i])) { 556 | strgetentry(lbl, params, i, ','); 557 | el[1] = lbl[0]; 558 | el[2] = lbl[1]; 559 | hal.stream.write(el); 560 | hal.stream.write(trim_float(ftoa(material->params[i], ngc_float_decimals()))); 561 | } 562 | } 563 | hal.stream.write("]" ASCII_EOL); 564 | 565 | return false; 566 | } 567 | 568 | static status_code_t onGcodeComment (char *comment) 569 | { 570 | status_code_t status = Status_OK; 571 | 572 | if(strlen(comment) > 5 && comment[0] == 'o' && comment[1] == '=') { 573 | 574 | char option = comment[2]; 575 | material_t material = {}; 576 | 577 | uint_fast8_t i; 578 | 579 | for(i = 0; i < sizeof(material.params) / sizeof(float); i++) 580 | material.params[i] = NAN; 581 | 582 | char *param = strtok(comment + 4, ","), *eq; 583 | 584 | while(param && status == Status_OK) { 585 | 586 | while(*param == ' ') 587 | param++; 588 | 589 | if((eq = strchr(param, '='))) { 590 | 591 | int32_t p; 592 | 593 | *eq = '\0'; 594 | 595 | switch((p = strlookup(param, params, ','))) { 596 | 597 | case -1: 598 | status = Status_GcodeUnsupportedCommand; 599 | break; 600 | 601 | case 12: 602 | if(option != '0') { 603 | uint32_t id; 604 | uint_fast8_t cc = 1; 605 | if((status = read_uint(eq, &cc, &id)) == Status_OK) 606 | material.id = (int32_t)id; 607 | } 608 | break; 609 | 610 | case 13: 611 | strncpy(material.name, eq + 1, sizeof(material.name) - 1); 612 | break; 613 | 614 | case 14: 615 | material.thc_status = eq[1] != '0'; 616 | break; 617 | 618 | default: 619 | { 620 | uint_fast8_t cc = 1; 621 | if(!read_float(eq, &cc, &material.params[p])) 622 | status = Status_BadNumberFormat; 623 | } 624 | break; 625 | } 626 | *eq = '='; 627 | } 628 | param = strtok(NULL, ","); 629 | } 630 | 631 | if(status == Status_OK && !plasma_material_is_valid(&material)) 632 | status = Status_GcodeValueWordMissing; 633 | 634 | if(status == Status_OK) switch(option) { 635 | 636 | case '0': 637 | material.id = material_id++; 638 | memcpy(&tmp_material, &material, sizeof(material_t)); 639 | set_job_params(&tmp_material); 640 | break; 641 | 642 | case '1': 643 | case '2': 644 | plasma_material_add(&material, option == '2'); 645 | break; 646 | 647 | default: 648 | status = Status_GcodeUnsupportedCommand; 649 | break; 650 | } 651 | 652 | } else if(on_gcode_comment) 653 | status = on_gcode_comment(comment); 654 | 655 | return status; 656 | } 657 | 658 | // --- Materials handling end 659 | 660 | static bool moveto (float z_position) 661 | { 662 | bool ok; 663 | coord_data_t target; 664 | plan_line_data_t plan_data; 665 | 666 | plan_data_init(&plan_data); 667 | plan_data.condition.rapid_motion = On; 668 | 669 | system_convert_array_steps_to_mpos(target.values, sys.position); 670 | 671 | target.z = z_position + gc_get_offset(Z_AXIS, false); 672 | if((ok = mc_line(target.values, &plan_data))) { 673 | protocol_buffer_synchronize(); 674 | sync_position(); 675 | } 676 | 677 | return ok; 678 | } 679 | 680 | static void set_target_voltage (float v) 681 | { 682 | arc_vref = arc_voltage = v; 683 | arc_voltage_low = arc_vref - plasma.thc_threshold; 684 | arc_voltage_high = arc_vref + plasma.thc_threshold; 685 | v_count = 0; 686 | } 687 | 688 | static void pause_on_error (void) 689 | { 690 | stateHandler = state_idle; 691 | system_set_exec_state_flag(EXEC_FEED_HOLD); // Set up program pause for manual tool change 692 | // system_set_exec_state_flag(EXEC_TOOL_CHANGE); // Set up program pause for manual tool change 693 | protocol_execute_realtime(); // Execute... 694 | } 695 | 696 | /* THC state machine */ 697 | 698 | static void state_idle (void) 699 | { 700 | if(mode == Plasma_ModeVoltage) 701 | arc_voltage = parc_voltage.get_value(&parc_voltage) * plasma.arc_voltage_scale - plasma.arc_voltage_offset; 702 | 703 | if(plasma.option.sync_pos && state_get() == STATE_IDLE) { 704 | 705 | if(mode != Plasma_ModeUpDown) 706 | step_count =(uint32_t)st2_get_position(z_motor); 707 | 708 | if(step_count && state_get() == STATE_IDLE) { 709 | sys.position[Z_AXIS] += step_count; 710 | step_count = 0; 711 | st2_set_position(z_motor, 0LL); 712 | sync_position(); 713 | } 714 | } 715 | } 716 | 717 | static void state_thc_delay (void) 718 | { 719 | if(hal.get_elapsed_ticks() >= thc_delay) { 720 | 721 | if(!(thc.enabled = !(job.thc_disabled || mode == Plasma_ModeArcOK))) 722 | stateHandler = state_arc_monitor; 723 | else if(mode == Plasma_ModeUpDown) { 724 | step_count = 0; 725 | stateHandler = state_thc_adjust; 726 | } else { 727 | pidf_reset(&pid); 728 | st2_set_position(z_motor, 0LL); 729 | if(!isnanf(job.cut_voltage)) 730 | set_target_voltage(job.cut_voltage); 731 | else 732 | set_target_voltage(parc_voltage.get_value(&parc_voltage) * plasma.arc_voltage_scale - plasma.arc_voltage_offset); 733 | stateHandler = state_vad_lock; 734 | stateHandler(); 735 | } 736 | } 737 | } 738 | 739 | static void state_arc_monitor (void) 740 | { 741 | if(!(thc.arc_ok = arc_ok.get_value(&arc_ok) == 1.0f)) 742 | pause_on_error(); 743 | } 744 | 745 | static void state_thc_adjust (void) 746 | { 747 | if((thc.arc_ok = arc_ok.get_value(&arc_ok) == 1.0f)) { 748 | if(updown_enabled) { 749 | thc.up = thc.report_up = cutter_up.get_value(&cutter_up) == 1.0f; 750 | thc.down = thc.report_down = cutter_down.get_value(&cutter_down) == 1.0f; 751 | if(thc.up != thc.down) { 752 | if(thc.up) { 753 | step_count++; 754 | hal.stepper.output_step((axes_signals_t){Z_AXIS_BIT}, (axes_signals_t){0}); 755 | } else { 756 | step_count--; 757 | hal.stepper.output_step((axes_signals_t){Z_AXIS_BIT}, (axes_signals_t){Z_AXIS_BIT}); 758 | } 759 | } 760 | } 761 | } else 762 | pause_on_error(); 763 | } 764 | 765 | static void state_vad_lock (void) 766 | { 767 | arc_voltage = parc_voltage.get_value(&parc_voltage) * plasma.arc_voltage_scale - plasma.arc_voltage_offset; 768 | 769 | if((thc.active = fr_actual >= fr_thr_99)) 770 | stateHandler = state_thc_pid; 771 | } 772 | 773 | static void state_thc_pid (void) 774 | { 775 | static float v; 776 | 777 | if(!(thc.active = fr_actual >= fr_thr_vad)) { 778 | stateHandler = state_vad_lock; 779 | return; 780 | } 781 | 782 | if((thc.arc_ok = arc_ok.get_value(&arc_ok)) == 1.0f) { 783 | 784 | if(v_count == 0) 785 | v = 0.0f; 786 | 787 | arc_voltage = parc_voltage.get_value(&parc_voltage) * plasma.arc_voltage_scale - plasma.arc_voltage_offset; 788 | v += arc_voltage; 789 | if(++v_count == THC_SAMPLE_AVG) { 790 | 791 | arc_voltage = v / (float)THC_SAMPLE_AVG; 792 | v_count = 0; 793 | 794 | if(arc_voltage < arc_voltage_low || arc_voltage > arc_voltage_high) { 795 | float err = pidf(&pid, arc_vref, arc_voltage, 1.0f); 796 | if(!st2_motor_running(z_motor)) { 797 | /* 798 | char buf[50]; 799 | strcpy(buf, ftoa(arc_vref, 1)); 800 | strcat(buf, ","); 801 | strcat(buf, ftoa(arc_voltage, 1)); 802 | strcat(buf, ","); 803 | strcat(buf, ftoa(err, 1)); 804 | report_message(buf, Message_Info); 805 | */ 806 | st2_motor_move(z_motor, -err * plasma.arc_height_per_volt, settings.axis[Z_AXIS].max_rate, Stepper2_mm); 807 | } 808 | } 809 | } 810 | /* 811 | if(arc_voltage >= arc_voltage_high) 812 | hal.stepper.output_step((axes_signals_t){Z_AXIS_BIT}, (axes_signals_t){Z_AXIS_BIT}); 813 | else if(arc_voltage <= arc_voltage_low) 814 | hal.stepper.output_step((axes_signals_t){Z_AXIS_BIT}, (axes_signals_t){0}); 815 | */ 816 | 817 | } else 818 | pause_on_error(); 819 | } 820 | 821 | /* end THC state machine */ 822 | 823 | static void exec_state_machine (void *data) 824 | { 825 | stateHandler(); 826 | 827 | if(set_feed_override) { 828 | set_feed_override = false; 829 | plan_feed_override(feed_override, sys.override.rapid_rate); 830 | } 831 | 832 | /* 833 | if(or) { 834 | or = false; 835 | hal.stream.write("[MSG:FR "); 836 | hal.stream.write(ftoa(fr_pgm, 1)); 837 | hal.stream.write(" "); 838 | hal.stream.write(ftoa(fr_actual, 1)); 839 | hal.stream.write("]" ASCII_EOL); 840 | } 841 | */ 842 | } 843 | 844 | static void onExecuteRealtime (uint_fast16_t state) 845 | { 846 | if(stateHandler == state_thc_pid) 847 | st2_motor_run(z_motor); 848 | 849 | on_execute_realtime(state); 850 | } 851 | 852 | static void reset (void) 853 | { 854 | thc.value = 0; 855 | stateHandler = state_idle; 856 | 857 | st2_motor_stop(z_motor); 858 | 859 | driver_reset(); 860 | } 861 | 862 | // Start or stop arc 863 | static void arcSetState (spindle_ptrs_t *spindle, spindle_state_t state, float rpm) 864 | { 865 | if(driver_reset == NULL) { 866 | spindle_set_state_(spindle, state, rpm); 867 | if(state.on) 868 | report_message("Plasma mode not available!", Message_Warning); 869 | return; 870 | } 871 | 872 | if(!state.on) { 873 | 874 | if(!isnanf(job.pause_at_end)) 875 | delay_sec(job.pause_at_end, DelayMode_Dwell); 876 | spindle_set_state_(spindle, state, rpm); 877 | thc.torch_on = thc.arc_ok = thc.enabled = Off; 878 | stateHandler = state_idle; 879 | 880 | } else { 881 | 882 | uint_fast8_t retries = plasma.arc_retries; 883 | 884 | if(job.pierce_height != 0.0f) 885 | moveto(job.pierce_height); 886 | 887 | do { 888 | spindle_set_state_(spindle, state, rpm); 889 | thc.torch_on = On; 890 | report_message("arc on", Message_Plain); 891 | if((thc.arc_ok = hal.port.wait_on_input(Port_Digital, port_arc_ok, WaitMode_High, plasma.arc_fail_timeout) != -1)) { 892 | report_message("arc ok", Message_Plain); 893 | delay_sec(job.pierce_delay, DelayMode_Dwell); 894 | if(!isnanf(job.cut_height)) 895 | moveto(job.cut_height); 896 | retries = 0; 897 | thc_delay = hal.get_elapsed_ticks() + (uint32_t)ceilf(1000.0f * plasma.thc_delay); // handle overflow! 898 | stateHandler = state_thc_delay; 899 | } else if(!(--retries)) { 900 | thc.torch_on = Off; 901 | report_message("arc failed", Message_Warning); 902 | spindle_set_state_(spindle, (spindle_state_t){0}, 0.0f); 903 | pause_on_error(); // output message and enter similar state as tool change state (allow jogging before resume) 904 | } else { 905 | thc.torch_on = Off; 906 | report_message("arc delay", Message_Plain); 907 | spindle_set_state_(spindle, (spindle_state_t){0}, 0.0f); 908 | delay_sec(plasma.arc_retry_delay, DelayMode_Dwell); 909 | } 910 | } while(retries); 911 | } 912 | } 913 | 914 | static void stepperPulseStart (stepper_t *stepper) 915 | { 916 | // static volatile bool get_rates = false; 917 | 918 | if(stepper->new_block) { 919 | // get_rates = true; 920 | fr_pgm = stepper->exec_block->programmed_rate * 0.01f * sys.override.feed_rate; 921 | fr_thr_99 = fr_pgm * 0.99f; 922 | fr_thr_vad = fr_pgm * 0.01f * (float)plasma.vad_threshold; 923 | segment_id = 0; 924 | } 925 | 926 | if(stepper->exec_segment->id != segment_id) { 927 | segment_id = stepper->exec_segment->id; 928 | fr_actual = stepper->exec_segment->current_rate; 929 | } 930 | 931 | stepper_pulse_start(stepper); 932 | } 933 | 934 | // Trap cycle start commands and redirect to foreground process 935 | // by temporarily claiming the HAL execute_realtime entry point 936 | // in order to execute probing and spindle/coolant change. 937 | // TODO: move to state machine with own EXEC_ bit? 938 | /* 939 | ISR_CODE static void trap_control_interrupts (control_signals_t signals) 940 | { 941 | if(signals.value) 942 | control_interrupt_callback(signals); 943 | } 944 | */ 945 | 946 | // Convert control signals bits to string representation. 947 | // NOTE: returns pointer to null terminator! 948 | static inline char *signals_tostring (char *buf, thc_signals_t signals) 949 | { 950 | static const char signals_map[] = "ATEOFBR VHUD "; 951 | 952 | char *map = (char *)signals_map; 953 | 954 | if(signals.bits) 955 | *buf++ = ','; 956 | 957 | while(signals.bits) { 958 | 959 | if(signals.bits & 0x01) { 960 | switch(*map) { 961 | 962 | case ' ': 963 | break; 964 | 965 | default: 966 | *buf++ = *map; 967 | break; 968 | } 969 | } 970 | 971 | map++; 972 | signals.bits >>= 1; 973 | } 974 | 975 | *buf = '\0'; 976 | 977 | return buf; 978 | } 979 | 980 | 981 | static void onRealtimeReport (stream_write_ptr stream_write, report_tracking_flags_t report) 982 | { 983 | static char buf[24]; 984 | 985 | strcpy(buf, "|THC:"); 986 | strcat(buf, ftoa(arc_voltage, 1)); 987 | signals_tostring(strchr(buf, '\0'), thc); 988 | stream_write(buf); 989 | 990 | thc.report_up = thc.report_down = Off; 991 | 992 | if(on_realtime_report) 993 | on_realtime_report(stream_write, report); 994 | } 995 | 996 | static void onSpindleSelected (spindle_ptrs_t *spindle) 997 | { 998 | spindle_set_state_ = spindle->set_state; 999 | 1000 | spindle->set_state = arcSetState; 1001 | // TODO: only change caps if PWM spindle active? 1002 | spindle->cap.at_speed = Off; 1003 | //?? spindle->cap.laser = Off; 1004 | spindle->cap.torch = On; 1005 | 1006 | if(on_spindle_selected) 1007 | on_spindle_selected(spindle); 1008 | } 1009 | 1010 | static void plasma_setup (settings_t *settings, settings_changed_flags_t changed) 1011 | { 1012 | settings_changed(settings, changed); 1013 | 1014 | if(!driver_reset) { 1015 | 1016 | driver_reset = hal.driver_reset; 1017 | hal.driver_reset = reset; 1018 | 1019 | on_spindle_selected = grbl.on_spindle_selected; 1020 | grbl.on_spindle_selected = onSpindleSelected; 1021 | 1022 | on_realtime_report = grbl.on_realtime_report; 1023 | grbl.on_realtime_report = onRealtimeReport; 1024 | 1025 | if(st2_motor_poll(z_motor)) { 1026 | on_execute_realtime = grbl.on_execute_realtime; 1027 | grbl.on_execute_realtime = onExecuteRealtime; 1028 | } 1029 | 1030 | task_add_systick(exec_state_machine, NULL); 1031 | } 1032 | 1033 | // Reclaim entry points that may have been changed on settings change. 1034 | 1035 | if(hal.stepper.pulse_start != stepperPulseStart) { 1036 | stepper_pulse_start = hal.stepper.pulse_start; 1037 | hal.stepper.pulse_start = stepperPulseStart; 1038 | } 1039 | } 1040 | 1041 | PROGMEM static const setting_group_detail_t plasma_groups[] = { 1042 | { Group_Root, Group_Plasma, "Plasma" }, 1043 | }; 1044 | 1045 | static bool is_setting_available (const setting_detail_t *setting, uint_fast16_t offset) 1046 | { 1047 | bool ok = false; 1048 | 1049 | switch(setting->id) { 1050 | 1051 | case Setting_THC_CutterDownPort: 1052 | case Setting_THC_CutterUpPort: 1053 | ok = n_din >= 3; 1054 | break; 1055 | 1056 | case Setting_Arc_VoltagePort: 1057 | ok = n_ain >= 1; 1058 | break; 1059 | 1060 | case Setting_THC_VADThreshold: 1061 | ok = init_ok; 1062 | break; 1063 | 1064 | default: 1065 | ok = init_ok && plasma.mode == Plasma_ModeVoltage; 1066 | break; 1067 | } 1068 | 1069 | return ok; 1070 | } 1071 | 1072 | static status_code_t set_port (setting_id_t setting, float value) 1073 | { 1074 | status_code_t status; 1075 | 1076 | if((status = isintf(value) ? Status_OK : Status_BadNumberFormat) == Status_OK) 1077 | switch(setting) { 1078 | 1079 | case Setting_Arc_VoltagePort: 1080 | plasma.port_arc_voltage = value < 0.0f ? 255 : (uint8_t)value; 1081 | break; 1082 | 1083 | case Setting_Arc_OkPort: 1084 | plasma.port_arc_ok = value < 0.0f ? 255 : (uint8_t)value; 1085 | break; 1086 | 1087 | case Setting_THC_CutterDownPort: 1088 | plasma.port_cutter_down = value < 0.0f ? 255 : (uint8_t)value; 1089 | break; 1090 | 1091 | case Setting_THC_CutterUpPort: 1092 | plasma.port_cutter_up = value < 0.0f ? 255 : (uint8_t)value; 1093 | break; 1094 | 1095 | default: 1096 | break; 1097 | } 1098 | 1099 | return status; 1100 | } 1101 | 1102 | static float get_port (setting_id_t setting) 1103 | { 1104 | float value = 0.0f; 1105 | 1106 | switch(setting) { 1107 | 1108 | case Setting_Arc_VoltagePort: 1109 | value = plasma.port_arc_voltage >= n_ain ? -1.0f : (float)plasma.port_arc_voltage; 1110 | break; 1111 | 1112 | case Setting_Arc_OkPort: 1113 | value = plasma.port_arc_ok >= n_din ? -1.0f : (float)plasma.port_arc_ok; 1114 | break; 1115 | 1116 | case Setting_THC_CutterDownPort: 1117 | value = plasma.port_cutter_down >= n_din ? -1.0f : (float)plasma.port_cutter_down; 1118 | break; 1119 | 1120 | case Setting_THC_CutterUpPort: 1121 | value = plasma.port_cutter_up >= n_din ? -1.0f : (float)plasma.port_cutter_up; 1122 | break; 1123 | 1124 | default: 1125 | break; 1126 | } 1127 | 1128 | return value; 1129 | } 1130 | 1131 | PROGMEM static const setting_detail_t plasma_settings[] = { 1132 | { Setting_THC_Mode, Group_Plasma, "Plasma mode", NULL, Format_RadioButtons, thc_modes, NULL, NULL, Setting_NonCore, &plasma.mode, NULL, NULL, { .reboot_required = On } }, 1133 | { Setting_THC_Delay, Group_Plasma, "Plasma THC delay", "s", Format_Decimal, "#0.0", NULL, NULL, Setting_NonCore, &plasma.thc_delay, NULL, NULL }, 1134 | { Setting_THC_Threshold, Group_Plasma, "Plasma THC threshold", "V", Format_Decimal, "#0.00", NULL, NULL, Setting_NonCore, &plasma.thc_threshold, NULL, is_setting_available }, 1135 | { Setting_THC_PGain, Group_Plasma, "Plasma THC P-gain", NULL, Format_Decimal, "###0.000", NULL, NULL, Setting_NonCore, &plasma.pid.p_gain, NULL, is_setting_available }, 1136 | { Setting_THC_IGain, Group_Plasma, "Plasma THC I-gain", NULL, Format_Decimal, "###0.000", NULL, NULL, Setting_NonCore, &plasma.pid.i_gain, NULL, is_setting_available }, 1137 | { Setting_THC_DGain, Group_Plasma, "Plasma THC D-gain", NULL, Format_Decimal, "###0.000", NULL, NULL, Setting_NonCore, &plasma.pid.d_gain, NULL, is_setting_available }, 1138 | { Setting_THC_VADThreshold, Group_Plasma, "Plasma THC VAD threshold", "percent", Format_Integer, "##0", "0", "100", Setting_NonCore, &plasma.vad_threshold, NULL, is_setting_available }, 1139 | { Setting_THC_VoidOverride, Group_Plasma, "Plasma THC Void override", "percent", Format_Integer, "##0", "0", "100", Setting_NonCore, &plasma.thc_override, NULL, is_setting_available }, 1140 | { Setting_Arc_FailTimeout, Group_Plasma, "Plasma Arc fail timeout", "seconds", Format_Decimal, "#0.0", NULL, NULL, Setting_NonCore, &plasma.arc_fail_timeout, NULL, NULL }, 1141 | { Setting_Arc_RetryDelay, Group_Plasma, "Plasma Arc retry delay", "seconds", Format_Decimal, "#0.0", NULL, NULL, Setting_NonCore, &plasma.arc_retry_delay, NULL, NULL }, 1142 | { Setting_Arc_MaxRetries, Group_Plasma, "Plasma Arc max retries", NULL, Format_Int8, "#0", NULL, NULL, Setting_NonCore, &plasma.arc_retries, NULL, NULL }, 1143 | { Setting_Arc_VoltageScale, Group_Plasma, "Plasma Arc voltage scale", NULL, Format_Decimal, "###0.000", NULL, NULL, Setting_NonCore, &plasma.arc_voltage_scale, NULL, is_setting_available }, 1144 | { Setting_Arc_VoltageOffset, Group_Plasma, "Plasma Arc voltage offset", NULL, Format_Decimal, "###0.000", NULL, NULL, Setting_NonCore, &plasma.arc_voltage_offset, NULL, is_setting_available }, 1145 | { Setting_Arc_HeightPerVolt, Group_Plasma, "Plasma Arc height per volt", "mm", Format_Decimal, "###0.000", NULL, NULL, Setting_NonCore, &plasma.arc_height_per_volt, NULL, is_setting_available }, 1146 | { Setting_Arc_OkHighVoltage, Group_Plasma, "Plasma Arc ok high volts", "V", Format_Decimal, "###0.000", NULL, NULL, Setting_NonCore, &plasma.arc_high_low_voltage, NULL, is_setting_available }, 1147 | { Setting_Arc_OkLowVoltage, Group_Plasma, "Plasma Arc ok low volts", "V", Format_Decimal, "###0.000", NULL, NULL, Setting_NonCore, &plasma.arc_ok_low_voltage, NULL, is_setting_available }, 1148 | { Setting_Arc_VoltagePort, Group_AuxPorts, "Arc voltage port", NULL, Format_Decimal, "-#0", "-1", max_aport, Setting_NonCoreFn, set_port, get_port, is_setting_available, { .reboot_required = On } }, 1149 | { Setting_Arc_OkPort, Group_AuxPorts, "Arc ok port", NULL, Format_Decimal, "-#0", "-1", max_dport, Setting_NonCoreFn, set_port, get_port, NULL, { .reboot_required = On } }, 1150 | { Setting_THC_CutterDownPort, Group_AuxPorts, "Cutter down port", NULL, Format_Decimal, "-#0", "-1", max_dport, Setting_NonCoreFn, set_port, get_port, is_setting_available, { .reboot_required = On } }, 1151 | { Setting_THC_CutterUpPort, Group_AuxPorts, "Cutter up port", NULL, Format_Decimal, "-#0", "-1", max_dport, Setting_NonCoreFn, set_port, get_port, is_setting_available, { .reboot_required = On } }, 1152 | { Setting_THC_Options, Group_Plasma, "Plasma options", NULL, Format_Bitfield, "Virtual ports,Sync Z position", NULL, NULL, Setting_NonCore, &plasma.option.flags, NULL, NULL, { .reboot_required = On } }, 1153 | }; 1154 | 1155 | #ifndef NO_SETTINGS_DESCRIPTIONS 1156 | 1157 | PROGMEM static const setting_descr_t plasma_settings_descr[] = { 1158 | { Setting_THC_Mode, "" }, 1159 | { Setting_THC_Delay, "Delay from cut start until THC activates." }, 1160 | { Setting_THC_Threshold, "Variation from target voltage for THC to correct height." }, 1161 | { Setting_THC_PGain, "" }, 1162 | { Setting_THC_IGain, "" }, 1163 | { Setting_THC_DGain, "" }, 1164 | { Setting_THC_VADThreshold, "Percentage of Cut Feed Rate velocity needs to fall below to lock THC." }, 1165 | { Setting_THC_VoidOverride, "Higher values need greater voltage change to lock THC." }, 1166 | { Setting_Arc_FailTimeout, "The amount of time to wait from torch on until a failure if arc is not detected." }, 1167 | { Setting_Arc_RetryDelay, "The time between an arc failure and another arc start attempt." }, 1168 | { Setting_Arc_MaxRetries, "The number of attempts at starting an arc." }, 1169 | { Setting_Arc_VoltageScale, "The value required to scale the arc voltage input to display the correct arc voltage." }, 1170 | { Setting_Arc_VoltageOffset, "The value required to display zero volts when there is zero arc voltage input.\\n" 1171 | "For initial setup multiply the arc voltage out value by -1 and enter that for Voltage Offset." 1172 | }, 1173 | { Setting_Arc_HeightPerVolt, "The distance the torch would need to move to change the arc voltage by one volt.\\n" 1174 | // "Used for manual height change only." 1175 | }, 1176 | { Setting_Arc_OkHighVoltage, "High voltage threshold for Arc OK." }, 1177 | { Setting_Arc_OkLowVoltage, "Low voltage threshold for Arc OK." }, 1178 | { Setting_Arc_VoltagePort, "Aux port number to use for arc voltage. Set to -1 to disable." }, 1179 | { Setting_Arc_OkPort, "Aux port number to use for arc ok signal. Set to -1 to disable." }, 1180 | { Setting_THC_CutterDownPort, "Aux port number to use for cutter down signal. Set to -1 to disable." }, 1181 | { Setting_THC_CutterUpPort, "Aux port number to use for cutter up signal. Set to -1 to disable." }, 1182 | { Setting_THC_Options, "" } 1183 | }; 1184 | 1185 | #endif 1186 | 1187 | static void plasma_settings_save (void) 1188 | { 1189 | hal.nvs.memcpy_to_nvs(nvs_address, (uint8_t *)&plasma, sizeof(plasma_settings_t), true); 1190 | } 1191 | 1192 | static void plasma_settings_restore (void) 1193 | { 1194 | plasma.mode = mode; 1195 | plasma.option.flags = 0; 1196 | plasma.thc_delay = 3.0f; 1197 | plasma.thc_threshold = 1.0f; 1198 | plasma.thc_override = 100; 1199 | plasma.vad_threshold = 90; 1200 | plasma.pause_at_end = 0.0f; 1201 | plasma.pierce_delay = 0.0f; 1202 | plasma.pierce_height = 1.0f; 1203 | plasma.arc_fail_timeout = 3.0f; 1204 | plasma.arc_retries = 3; 1205 | plasma.arc_retry_delay = 3.0f; 1206 | plasma.arc_fail_timeout = 3.0f; 1207 | plasma.arc_voltage_scale = 1.0f; 1208 | plasma.arc_voltage_offset = 0.0f; 1209 | plasma.arc_height_per_volt = 0.1f; 1210 | plasma.arc_high_low_voltage = 150.0; 1211 | plasma.arc_ok_low_voltage = 100.0f; 1212 | plasma.pid.p_gain = 1.0f; 1213 | plasma.pid.i_gain = 0.0f; 1214 | plasma.pid.d_gain = 0.0f; 1215 | plasma.port_arc_voltage = ioport_find_free(Port_Analog, Port_Input, (pin_cap_t){ .claimable = On }, "Arc voltage"); 1216 | plasma.port_arc_ok = ioport_find_free(Port_Digital, Port_Input, (pin_cap_t){ .claimable = On }, "Arc ok"); 1217 | plasma.port_cutter_down = updown_enabled && plasma.port_arc_ok >= 1 ? plasma.port_arc_ok - 1 : 255; 1218 | plasma.port_cutter_up = updown_enabled && plasma.port_arc_ok >= 2 ? plasma.port_arc_ok - 2 : 255; 1219 | 1220 | hal.nvs.memcpy_to_nvs(nvs_address, (uint8_t *)&plasma, sizeof(plasma_settings_t), true); 1221 | } 1222 | 1223 | static bool plasma_claim_digital_in (xbar_t *target, uint8_t port, const char *description) 1224 | { 1225 | xbar_t *p; 1226 | bool ok = false; 1227 | 1228 | if(port != 255 && (p = ioport_get_info(Port_Digital, Port_Input, port)) && p->get_value && !p->mode.claimed) { 1229 | memcpy(target, p, sizeof(xbar_t)); 1230 | if((ok = ioport_claim(Port_Digital, Port_Input, &port, description)) && target == &arc_ok) 1231 | port_arc_ok = port; 1232 | } 1233 | 1234 | return ok; 1235 | } 1236 | 1237 | static void plasma_settings_load (void) 1238 | { 1239 | if(hal.nvs.memcpy_from_nvs((uint8_t *)&plasma, nvs_address, sizeof(plasma_settings_t), true) != NVS_TransferResult_OK) { 1240 | plasma.port_arc_ok = plasma.port_cutter_down = plasma.port_cutter_up = 255; 1241 | plasma_settings_restore(); 1242 | } 1243 | 1244 | if((mode = plasma.mode) == Plasma_ModeOff) 1245 | return; 1246 | 1247 | if(!(init_ok = mode != Plasma_ModeVoltage)) { 1248 | 1249 | if(plasma.port_arc_voltage != 255) { 1250 | 1251 | xbar_t *p; 1252 | 1253 | if((p = ioport_get_info(Port_Analog, Port_Input, port_arc_voltage)) && p->get_value && !p->mode.claimed) { 1254 | memcpy(&parc_voltage, p, sizeof(xbar_t)); 1255 | init_ok = ioport_claim(Port_Analog, Port_Input, &port_arc_voltage, "Arc voltage"); 1256 | } 1257 | } 1258 | 1259 | if(!init_ok) { 1260 | init_ok = true; 1261 | mode = Plasma_ModeUpDown; 1262 | } 1263 | } 1264 | 1265 | init_ok = init_ok && plasma_claim_digital_in(&arc_ok, plasma.port_arc_ok, "Arc ok"); 1266 | 1267 | if(init_ok && mode == Plasma_ModeUpDown) { 1268 | if(!(plasma_claim_digital_in(&cutter_down, plasma.port_cutter_down, "Cutter down") && 1269 | plasma_claim_digital_in(&cutter_up, plasma.port_cutter_up, "Cutter up"))) 1270 | mode = Plasma_ModeArcOK; 1271 | } 1272 | 1273 | if(init_ok) { 1274 | 1275 | set_job_params(NULL); 1276 | 1277 | updown_enabled = mode == Plasma_ModeUpDown; 1278 | 1279 | settings_changed = hal.settings_changed; 1280 | hal.settings_changed = plasma_setup; 1281 | 1282 | if(plasma.mode != mode) 1283 | task_run_on_startup(report_warning, "Plasma mode changed due to lack of inputs!"); 1284 | 1285 | if(plasma.option.virtual_ports) 1286 | task_run_on_startup(add_virtual_ports, NULL); 1287 | } else 1288 | task_run_on_startup(report_warning, "Plasma mode failed to initialize!"); 1289 | } 1290 | 1291 | static void on_settings_changed (settings_t *settings, settings_changed_flags_t changed) 1292 | { 1293 | pidf_init(&pid, &plasma.pid); 1294 | } 1295 | 1296 | static status_code_t matlist (sys_state_t state, char *args) 1297 | { 1298 | plasma_enumerate_materials(ml_enumerate, NULL); 1299 | 1300 | return Status_OK; 1301 | } 1302 | 1303 | static void onReportOptions (bool newopt) 1304 | { 1305 | on_report_options(newopt); 1306 | 1307 | if(!newopt) { 1308 | 1309 | plasma_mode_t i = Plasma_ModeOff; 1310 | char buf[30] = "PLASMA (", *s1 = &buf[8], *s2 = thc_modes, c; 1311 | 1312 | while((c = *s2++)) { 1313 | if(i == mode) { 1314 | if(c == ',') 1315 | break; 1316 | *s1++ = c; 1317 | } else if(c == ',') 1318 | i++; 1319 | } 1320 | *s1++ = ')'; 1321 | *s1 = '\0'; 1322 | 1323 | report_plugin(buf, "0.21"); 1324 | 1325 | } else if(mode != Plasma_ModeOff) 1326 | hal.stream.write(",THC"); 1327 | } 1328 | 1329 | void plasma_init (void) 1330 | { 1331 | static setting_details_t setting_details = { 1332 | .groups = plasma_groups, 1333 | .n_groups = sizeof(plasma_groups) / sizeof(setting_group_detail_t), 1334 | .settings = plasma_settings, 1335 | .n_settings = sizeof(plasma_settings) / sizeof(setting_detail_t), 1336 | #ifndef NO_SETTINGS_DESCRIPTIONS 1337 | .descriptions = plasma_settings_descr, 1338 | .n_descriptions = sizeof(plasma_settings_descr) / sizeof(setting_descr_t), 1339 | #endif 1340 | .save = plasma_settings_save, 1341 | .load = plasma_settings_load, 1342 | .restore = plasma_settings_restore, 1343 | .on_changed = on_settings_changed 1344 | }; 1345 | 1346 | static const sys_command_t thc_command_list[] = { 1347 | {"EM", matlist, { .allow_blocking = On, .noargs = On }, { .str = "outputs plasma materials list" } } 1348 | }; 1349 | 1350 | static sys_commands_t thc_commands = { 1351 | .n_commands = sizeof(thc_command_list) / sizeof(sys_command_t), 1352 | .commands = thc_command_list 1353 | }; 1354 | 1355 | bool ok; 1356 | 1357 | if((ok = !!hal.stepper.output_step && ioport_can_claim_explicit())) { 1358 | n_ain = ioports_available(Port_Analog, Port_Input); 1359 | n_din = ioports_available(Port_Digital, Port_Input); 1360 | ok = (n_ain >= 1 && n_din >= 1) || (n_din >= 3); 1361 | } 1362 | 1363 | if(ok) { 1364 | updown_enabled = n_ain == 0; 1365 | ok = (nvs_address = nvs_alloc(sizeof(plasma_settings_t))); 1366 | } 1367 | 1368 | if(ok && (z_motor = st2_motor_init(Z_AXIS, false)) != NULL) { 1369 | 1370 | if(n_ain) 1371 | strcpy(max_aport, uitoa(n_ain - 1)); 1372 | strcpy(max_dport, uitoa(n_din - 1)); 1373 | 1374 | settings_register(&setting_details); 1375 | system_register_commands(&thc_commands); 1376 | 1377 | memcpy(&user_mcode, &grbl.user_mcode, sizeof(user_mcode_ptrs_t)); 1378 | 1379 | grbl.user_mcode.check = mcode_check; 1380 | grbl.user_mcode.validate = mcode_validate; 1381 | grbl.user_mcode.execute = mcode_execute; 1382 | 1383 | on_report_options = grbl.on_report_options; 1384 | grbl.on_report_options = onReportOptions; 1385 | 1386 | on_gcode_comment = grbl.on_gcode_comment; 1387 | grbl.on_gcode_comment = onGcodeComment; 1388 | 1389 | /* 1390 | control_interrupt_callback = hal.control_interrupt_callback; 1391 | hal.control_interrupt_callback = trap_control_interrupts; 1392 | */ 1393 | 1394 | if(n_ain == 0) 1395 | setting_remove_elements(Setting_THC_Mode, 0b1101); 1396 | 1397 | if(n_din < 3) 1398 | setting_remove_elements(Setting_THC_Mode, 0b1011); 1399 | 1400 | // Load materials 1401 | sheetcam_init(); 1402 | linuxcnc_init(); 1403 | 1404 | } else 1405 | task_run_on_startup(report_warning, "Plasma mode failed to initialize!"); 1406 | } 1407 | 1408 | #endif // PLASMA_ENABLE 1409 | -------------------------------------------------------------------------------- /thc.h: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | thc.h - plasma cutter tool height control plugin 4 | 5 | Part of grblHAL 6 | 7 | Copyright (c) 2020-2025 Terje Io 8 | 9 | grblHAL is free software: you can redistribute it and/or modify 10 | it under the terms of the GNU General Public License as published by 11 | the Free Software Foundation, either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | grblHAL is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU General Public License for more details. 18 | 19 | You should have received a copy of the GNU General Public License 20 | along with grblHAL. If not, see . 21 | 22 | */ 23 | 24 | #pragma once 25 | 26 | #include "driver.h" 27 | 28 | #if PLASMA_ENABLE 29 | 30 | typedef enum { 31 | Plasma_SetMode = 0, 32 | Plasma_SetCurrent, 33 | Plasma_SetPressure, 34 | Plasma_GetCurrentMin, 35 | Plasma_GetCurrentMax, 36 | Plasma_GetPressureMin, 37 | Plasma_GetPressureMax, 38 | Plasma_GetArcOnTimeLow, 39 | Plasma_GetArcOnTimeHigh, 40 | } plasma_rs485_msg_t; 41 | 42 | typedef struct material 43 | { 44 | int32_t id; // nu 45 | char name[50]; // na 46 | bool thc_status; // th 47 | union { 48 | float params[12]; 49 | struct { 50 | float pierce_height; // ph - mandatory 51 | float pierce_delay; // pd - mandatory 52 | float feed_rate; // fr - mandatory 53 | float cut_height; // ch - mandatory 54 | float cut_voltage; // cv 55 | float pause_at_end; // pe 56 | float kerf_width; // kw 57 | float cut_amps; // ca - PowerMax 58 | float gas_pressure; // gp - PowerMax 59 | float cut_mode; // cm - PowerMax 60 | float jump_height; // jh 61 | float jump_delay; // jd 62 | }; 63 | }; 64 | struct material *next; 65 | } material_t; 66 | 67 | typedef bool plasma_enumerate_materials_callback_ptr (material_t *material, void *data); 68 | typedef bool (*plasma_set_current_ptr)(float current); 69 | typedef bool (*plasma_set_pressure_ptr)(float psi); 70 | typedef bool (*plasma_set_cut_mode_ptr)(uint16_t mode); 71 | 72 | typedef struct { 73 | plasma_set_current_ptr set_current; 74 | plasma_set_pressure_ptr set_pressure; 75 | plasma_set_cut_mode_ptr set_cut_mode; 76 | } plasma_control_t; 77 | 78 | extern plasma_control_t cutter; 79 | 80 | bool plasma_material_is_valid (material_t *material); 81 | bool plasma_material_add (material_t *material, bool overwrite); 82 | bool plasma_enumerate_materials (plasma_enumerate_materials_callback_ptr callback, void *data); 83 | 84 | #endif 85 | --------------------------------------------------------------------------------