├── COPYING ├── README.md └── popcEsp.ino /COPYING: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc. 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Library General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License 307 | along with this program; if not, write to the Free Software 308 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA 309 | 310 | 311 | Also add information on how to contact you by electronic and paper mail. 312 | 313 | If the program is interactive, make it output a short notice like this 314 | when it starts in an interactive mode: 315 | 316 | Gnomovision version 69, Copyright (C) year name of author 317 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 318 | This is free software, and you are welcome to redistribute it 319 | under certain conditions; type `show c' for details. 320 | 321 | The hypothetical commands `show w' and `show c' should show the appropriate 322 | parts of the General Public License. Of course, the commands you use may 323 | be called something other than `show w' and `show c'; they could even be 324 | mouse-clicks or menu items--whatever suits your program. 325 | 326 | You should also get your employer (if you work as a programmer) or your 327 | school, if any, to sign a "copyright disclaimer" for the program, if 328 | necessary. Here is a sample; alter the names: 329 | 330 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 331 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 332 | 333 | , 1 April 1989 334 | Ty Coon, President of Vice 335 | 336 | This General Public License does not permit incorporating your program into 337 | proprietary programs. If your program is a subroutine library, you may 338 | consider it more useful to permit linking proprietary applications with the 339 | library. If this is what you want to do, use the GNU Library General 340 | Public License instead of this License. 341 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | RoboPopc is an application for coffe roast control and logging. The software supports Arduino 2 | and ESP8266 development boards with software PID controller and drivers for LCD display. 3 | 4 | Without additional hardware either platform can connect to 'Artisan' roaster logging 5 | application, providing 'virtual thermocouple' readings of Bean Temperature to Artisan. 6 | 7 | With additional hardware 2x16 I2C display board, MAX31855 thermocouple interface and 8 | PWM drive for solid state relay are supported. On the ESP8266 version wifi connection and 9 | MQTT messaging allows for control via web based applications, a serial interface is necessary 10 | only for code loading. 11 | 12 | Control of the roasting process is by any of: Artisan Logger over serial interface, manual 13 | setting of temperature / time ramps and target setpoints over a serial interface or manually 14 | adjusting temperature ramp or PWM percentage by way of a 16 position rotary encoder switch. 15 | 16 | PWM control may be selected to be either by slow On-Off cycling, 3mSec granularity in 3 Sec 17 | cycles or, by using the processor's PWM at an approx 31Hz rate. 18 | 19 | Installation is done using the Arduino IDE, for ESP8266 the supplementary steps for ESP8266 20 | development must be done. This code is supplied as an Arduino sketchbook, for ESP8266 the 21 | 'Ticker Scheduler' code and headers must be copied into this sketch's folder. 22 | There are compiler switches near the beginning of the code for processor type and for 23 | various hardware options to be included. 24 | 25 | Sections (units) in this code, ordered alphabetically: 26 | bbrd 'billboard' posts info to either / both of 2x16 LCD display / Serial Port 27 | frnt deprecated, sends data over Serial to Frontside / Process apps on PC for PID tuning 28 | lcds support for I2C 2x16 LCD display 29 | mqtt ref ingo MQTT message queuing publish / subscribe protocol for control via PC MQTT client 30 | offn Slow Off/On approx 3sec cycle PWN for SSR controlled heater 31 | pidc PID controller for PWM powered temperature control; a delta-time incremental PID 32 | pwmd 8-bit PWM control via hardware pwm pins 33 | prof Profile control; selects auto/manual temp setpt, manual pwm width, real/fake temp sensor 34 | rots Rotary 16way encoded ( 4pin + common) selector switch manager 35 | tcpl MAX31855 SPI thermocouple temperature sensor or virtual temp readings for debug 36 | user receive user's or Artisan commands via serial port, MQTT for setpoint, ramp, profiles 37 | 38 | Arduino H/W details: 39 | 40 | pins: lcdi: A4 A5; 41 | tcplSPI: D3 D5 D10 42 | pwm: D9 43 | 44 | Legend / Mode Indicators; Upcase: User Set; LowCase: Auto/Sensed value; User entry: either case; 45 | a toggle run time interface to Artisan 46 | c centigrade entry/display 47 | d toggle diagnostic verbose messages on serial 48 | f fahrenhet entry / display, internals always are Centigrade 49 | i info strings and 'billboard' to serial 50 | l logging (artisan csv) to serial 51 | p profile (tbd) 52 | r ramp C/F Degrees per minute, enter before target setpoint 53 | s setPoint C/F temp 54 | w pwm width override, disssabled PID 55 | y tbd pwm freq Hz 56 | z reset total timecount 57 | 58 | 59 | -------------------------------------------------------------------------------- /popcEsp.ino: -------------------------------------------------------------------------------- 1 | /// popcEsp Arduino UNO / ESP8266 Controller / Artisan Logger with MQTT client 'popc' 2 | // 3 | // Sections (units) in this code, ordered alphabetically: 4 | // bbrd 'billboard' posts info to either / both of 2x16 LCD display / Serial Port 5 | // eprm EEPROM setup and handling 6 | // lcds support for I2C 2x16 LCD display 7 | // mill fast ~1mSec sequencer inc A/D, Off-On, modulo variable freq/pwmd 8 | // mqtt ref ingo MQTT message queuing publish / subscribe protocol for control via PC MQTT client 9 | // offn On-Off <= mains cycle/2 rate cycle PSW for SSR control (eg heater ) 10 | // pidc PID controller for PWM powered temperature control; a delta-time summing anti windup PID 11 | // pwmd 8-bit PWM control via freq >= ~30Hz hardware pwm pins 12 | // prof Profile control; selects auto/manual temp setpt, manual pwm width, real/fake temp sensor 13 | // rots Rotary 16way encoded ( 4pin + common) selector switch manager 14 | // tcpl MAX31855 SPI thermocouple temperature sensor or virtual temp readings for debug 15 | // twio PCD8574 I2C 8b IO extender for ESP 4b In, 4b Out: Rot Sw, indl, SSR on/off drives 16 | // user receive user's or Artisan commands via serial port, MQTT for setpoint, ramp, profiles 17 | // 18 | // Command - Response supported for Artisan interface: 19 | // ** 'CHA' cmd causes auto-switch into Artisan speak 20 | // CHA(N) # Acknowlege channel setup command, no action, respond '#' ** 21 | // FILT # Acknowlege filter parms command, no action, respond '#' 22 | // IO3 nn Set duty cycle to nn <= 100 23 | // OT1 nn (Set duty cycle #1 to nn <= 100), no action, respond '#' 24 | // OT2 nn (Set duty cycle #2 to nn <= 100), no action, respond '#' 25 | // PID;OFF Reset PID, set PWM <= 0, set ROC <= 0, set PID Runs <= 0 26 | // PID;RESET Reset PID, zero all internal PID computations, run status unchanged 27 | // PID;SV nnn Set new target setpoint temp to nnn 28 | // PID;SYNC Zero all internal PID computations, set target temp to sensed temp 29 | // PID;T;pp,ii,dd Set new PID prop, Integral, Differential gain values 30 | // POPC Exit Artisan Mode, PopC <=> Serial 31 | // REA(D) Send Artisan formatted response line 32 | // UNIT;C Set units to Centigrade 33 | // UNIT;F Set units to Fahrenheit 34 | // ** 'CHA' cmd causes auto-switch into Artisan speak 35 | // 36 | // Command & LCD Indicators; Upcase: User Set; LowCase: Auto/Sensed/Readback value 37 | // a/A Set serial interface to Artisan talk 38 | // b/B 39 | // CHAN (Auto from Artisan) Set Artisan speak 40 | // c/C Set centigrade units; LCD display actual/target temp 41 | // d/D toggle diagnostic verbose messages on serial 42 | // e/E Readback / Update From/To EEPROM PID parameters 43 | // f/F Set fahrenheit units; LCD display actual/target temp 44 | // g/G Set gate TmpC / Set ROC goal from sensed RoC 45 | // h/H Set hold temperature setpoint for ramp endpoint (soak temp ) 46 | // Iff Set PID I-Term time (Float Ti: lower value == higher gain) 47 | // Jff Set PID D-Term gain (Float Td: lower value == lower gain) 48 | // k/K 49 | // l/L Send Artisan CSV format on serial ( for capture and Artisan Import ) 50 | // m/M Rsvd MQTT msg / Set bean Mass for virtual tcpl 51 | // n/N Rsvd NetSock / PID oN 52 | // o/O Rsvd / PID Off 53 | // p/Pff Readback / Set PID P-Term gain (Float Kp: lower value == lower gain) 54 | // q/Q 55 | // rnn/Rnn Set Temperature Ramp C/F Deg per min (Set this then set hold temp) 56 | // snn/Snn Set immediate target setPoint C/F temperature 57 | // t/T Spare 58 | // u/U Set PWM Frequency Hz (TBD) 59 | // v/V Readback Version, PID, EEPROM to serial / EPROM => PID Values 60 | // wnn/Wnn Set PWM Duty Cycle nn <= 100, disable PID 61 | // x/X 62 | // y/Y Live Sync PID to current power setting / ESP restart 63 | // z/Z Zero reset all timecounts / Reset PID 64 | // 65 | // Artisan Mode: Chan C, D, F, H ( 3, 4, 6, 0/8 ) may overlay devel fields 66 | // Serial and Wifi MQTT topic interfaces 67 | // 68 | // Non Artisan Serial non-Info (L cmd) toggles Artisan CSV logging 69 | // 70 | // LCD Billboard & Serial: non-Artisan info (non CSV) display 71 | // All characters: Upper Case: Demand lower case: computed 72 | // Lin0 Upr: Ev|Odd Secs: ProfNumber|PWM% Measured | Demanded ROC A | B Chn Temp 73 | // Lin1 Lwr: Ev|Odd Secs: PhaseTmpc | TimeToTgt Since Zero | Step Time Hold | Setp Temp 74 | // 75 | // Modes: 'H'Hold 'M' Manual 'R' Ramp 'S' Setpoint 76 | // Phases: 'o'Off 'c' chge 't' turn 'b' boil 'd'dryEnd 'k'FC Strt 'f' finish 77 | // 78 | // WiFi MQTT (need WMAN or Routr SSDI/PSWD 79 | // publish Artisan command to: /popc/userCmdline 80 | // subscribe on replies at: /popc/artiResp 81 | // subscribe on LCD panel at: /popc/bbrdLin0, /popc/bbrdLin0 82 | // WifiManager WMAN sets up an http server asking for stashed 83 | // ssid, password for your router's Access Point 84 | // 85 | // Copyright (c) 2017 2018 Bitwise Technologies popc@bitwisetech.com 86 | // 87 | // This program is free software; you can redistribute it and/or 88 | // modify it under the terms of the GNU General Public License as 89 | // published by the Free Software Foundation; either version 2 of the 90 | // License, or (at your option) any later version. 91 | // 92 | // This program is distributed in the hope that it will be useful, but 93 | // WITHOUT ANY WARRANTY; without even the implied warranty of 94 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 95 | // General Public License for more details. 96 | // 97 | // You should have received a copy of the GNU General Public License 98 | // along with this program; if not, write to the Free Software 99 | // .Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA 100 | // 101 | /// Compiling this sketch 102 | // for ESP8266 103 | // Setup your Arduino IDE for ESP8266, see www.adafruit.com and install board support package 104 | // Get the ESP TickerScheduler package, copy TickerScheduler.* into the same folder as this sketch, not into library folder. 105 | // Set '1' compile-time switches, below: PROC_ESP ( set unused switches to '0') 106 | // for UNO 107 | // Set '1' compile-time switches, below: PROC_UNO, ( set unused switches to '0') 108 | // for either processor 109 | // Set '1' compile-time switches, below: WITH_LCD, WITH_MAX31855, WITH_MAX6675, WITH_PCF8574 if you have that hardware 110 | // Your sketchbook/library must contain libraries: LiquidCrystal, Adafruit_MAX31855_library (for UNO), MAX31855 (for ESP), PCF8574 as needed 111 | // For ESP: Adafruit_ESP8266 plus libraries for wifi as selected: esp-mqtt-arduino, WiFiManager, WebSockets ( not all wifi options tested ! ) 112 | // Copy from /.arduino15/packages/esp8266/hardware/esp8266/2.3.0/libraries/Ticker/Ticker.h 113 | // 114 | // Code compiler switches: 1/0 Enab/Dsel UNO-ESP proc HW, Wifi options - Rebuild, Upload after changing these 115 | #define IFAC_ARTI 1 // Start with Artisan interface on Serial 116 | #define WITH_LCD 0 // Hdwre has I2C 2x16 LCD display of either type 117 | #define WITH_VIRTTCPL 0 // No hdwre, simulate virtual thermocouple output 118 | #define WITH_TCPL_2 0 // Second Thermocouple MUST be same type ( libraies conflict ) 119 | #define WITH_OFFN 1 // Use 250mSec cycle via mill Off-On SSR 120 | #define WITH_PWMD 0 // Use fast h/w PWM 121 | // 122 | // Developer switches 123 | #define SHOW_OPT1 1 // Opt 1 CSV, Arti chns 3, 4, 6, (0),8 ROC related data 124 | #define NDIA_ARTI 0 // 1 to stop 'd' diag messages being sent to Artisan 125 | // 126 | #define AMBI_TMPC 19 // Set 'Room Temperature' degC, returned on uninstalled sensor channels 127 | #define IDLE_TMPC 48 // Set 'Idle Temperature' degC, returned as temp with eg permanent heater 128 | #define INIT_SETP IDLE_TMPC // Set 'Maxi Temperature' degC allowed temp for PID setpoint 129 | #define INIT_PWMD 0 // Set 'Maxi Temperature' degC allowed temp for PID setpoint 130 | #define MAXI_TMPC 269 // Set 'Maxi Temperature' degC allowed temp for PID setpoint 131 | #define BOIL_TMPC 100 132 | #define DRYE_TMPC 150 133 | #define FC1S_TMPC 200 134 | #define FINI_TMPC 230 135 | /// 136 | /// 137 | // UNO pin assignments 138 | // Off/On SSR driver 139 | #define OFFN_OPIN 13 140 | // Onboard Led indicates duty cycle 141 | #define ONBD_OPIN LED_BUILTIN 142 | #define ONBD_LOWON 0 143 | // PWM Drive 144 | // d9 needed by RFI scan d6 would use tmr0 want d3 used by max13855 145 | #define PWMD_OPIN 9 // Pin D9 146 | #define PWMD_MODE OUTPUT 147 | #define PWMD_FREQ 123 // UNO: timer counter range 148 | // Rotary 16way encoder switch; D13 is LED on UNO 149 | #define ROTS_BIT3 6 // Pin Val 8 150 | #define ROTS_BIT2 10 // Pin Val 4 151 | #define ROTS_BIT1 11 // Pin Val 2 152 | #define ROTS_BIT0 12 // Pin Val 1 153 | // Ensure for direct connected pins in Init(), Valu() 154 | #define PINS_ROTS 1 155 | // spi2 on UNO for alternative tcpl interface (excludes twio) 156 | #define SPI2_CLCK 3 // Pin Clock 157 | #define SPI2_MISO 5 // (Pin D4 used for TCPL) 158 | #define SPI2_CSEL 2 // Pin CSel 159 | #if WITH_TCPL_2 160 | #define TCPL_CSL2 13 // tbd is this pin OK ? 161 | #endif 162 | //#define RSVD_MOSI 5 // Pin D5 Rsvd 163 | // spi on uno FOR tcpl 164 | #define TCPL_CLCK 4 // Pin Clock 165 | #define TCPL_MISO 7 // Pin Data 166 | #define TCPL_CSEL 8 // Pin CSel 167 | // i2c on pcf8574 TWIO (excl spi2) 168 | #define TWIO_SDA 5 // I2C SDA 169 | #define TWIO_SCL 3 // I2C SCL 170 | // 171 | #define SCOP_OPIN 13 // debug flag nixes SPI2_CSEL 172 | /// 173 | 174 | // macros to toggle scope output pin specified above for logic analyser 175 | #define scopHi digitalWrite( SCOP_OPIN, 1) 176 | #define scopLo digitalWrite( SCOP_OPIN, 0) 177 | 178 | typedef unsigned int uint; 179 | // macro for symm +/- rndng flt to int 180 | #define IFR(X) ((X < 0) ? int(X - 0.5) : int(X + 0.5)) 181 | 182 | // shorthand 183 | #define SePrn Serial.print 184 | #define SePln Serial.println 185 | 186 | /// system settings 187 | // milliSecond poll values Primes to suppress beating 188 | #define ADC0_POLL_MILL 2UL // A/D mill steps per cycle 189 | #define LCDS_POLL_MSEC 1000UL // mS lcd display poll 190 | #define MILL_POLL_USEC 5000UL // uS 200Hz mill poll 191 | #define MILL_STEP_PSEC 200UL // mill steps per sec 192 | #define PIDC_POLL_MSEC 101UL // mS pid control poll 193 | #define PROF_POLL_MSEC 997UL // mS run control poll 194 | #define PWMD_POLL_MSEC 103UL // mS pwm driver poll 195 | #define ROTS_POLL_MSEC 503UL // mS rotary sw poll 196 | #define TCPL_POLL_MSEC 253UL // mS termocouple poll 197 | #define USER_POLL_MSEC 101UL // mS user cmd poll 198 | #define VTCP_POLL_MSEC 251UL // mS virt tcpl poll 199 | #define POLL_SLOP_MSEC 1UL // Avge loop time is 10mSec 200 | // 201 | 202 | // BOF preprocessor bug prevent - insert me on top of your arduino-code 203 | // From: http://www.a-control.de/arduino-fehler/?lang=en 204 | #if 1 205 | __asm volatile ("nop"); 206 | #endif 207 | // 208 | 209 | /// library files and addx assignments for options 210 | #include 211 | #include 212 | #include 213 | #include // Comes with Arduino IDE 214 | 215 | // LCD: I2C 2x16 216 | // UNO: Analog Pin A4 SDA Pin A5 SCL I2C ESP: GPIO Pin D4 SDA GPIO Pin D5 SCL 217 | // set LCD address to 0x27 for a A0-A1-A2 display 218 | // args: (addr, en,rw,rs,d4,d5,d6,d7,bl,blpol) 219 | #if WITH_LCD 220 | #include 221 | LiquidCrystal_I2C lcd(0x27, 16, 2); 222 | #endif // any LCD type installed 223 | 224 | // MAX31855 Thermocouple: Different libraries for ESP, UNO 225 | #include "Adafruit_MAX31855.h" 226 | Adafruit_MAX31855 tcpl(TCPL_CLCK, TCPL_CSEL, TCPL_MISO); 227 | #if WITH_TCPL_2 228 | Adafruit_MAX31855 tcp2(TCPL_CLCK, TCPL_CSL2, TCPL_MISO); // Second Tcpl shares clock, data 229 | #endif 230 | // 231 | 232 | /// Declarations by unit 233 | 234 | // Artisan 40+ char serial pkt: Ambient, ChnA, ChnB, ChnC, ChnD, HTR, FAN, SV, ITNL ; 235 | // Fields when csv logging: TimeTotal TimeRamp ChnA(ET) ChnB(BT), ChnC, ChnD, PWM%, IO3, SV, AMB Event 236 | char artiResp[] = "012.4,678.0,234.6,890.2,345.7,901.3,567.9,123.5,789.1 "; // 57chars [0]..[56] + null 237 | // Artisan csv header format: these two lines must contain tab chars, not spaces. Can't figure how to 'Event' at EOL 238 | const char csvlHdr1[] = "Date: Unit:C CHARGE: TP: DRYe: FCs: FCe: SCs: SCe: DROP: COOL: Time:"; 239 | const char csvlHdr2[] = "Time1 Time2 ET BT Event S3 S4 Htr Fan SetPt Amb"; 240 | 241 | // billboard string for LCD + Serial Lower cases: computed/measured Upper case: User/Setpoints 242 | char bbrdLin0[] = "w100% r-123 128c"; 243 | char bbrdLin1[] = "P0-0 S12.3m 228C"; 244 | // UpperCase imperatives 245 | #define holdChar 'H' // Prefix to decimal mins alternating with total time 246 | #define manuChar 'M' 247 | #define rampChar 'R' 248 | #define setpChar 'S' 249 | #define fahrChar 'F' 250 | #define celsChar 'C' 251 | // lowerCase computed 252 | #define orffChar 'o' 253 | #define boilChar 'b' 254 | #define chgdChar 'c' 255 | #define dryeChar 'd' 256 | #define finiChar 'f' 257 | #define fc1sChar 'k' 258 | #define muteChar 'u' 259 | #define prehChar 'p' 260 | #define tpntChar 't' 261 | char modeCode, phseCode; 262 | char userScal = 'C'; 263 | // 264 | String userCmdl(" "); // 49Chrs + null 265 | // char array, below, is incompatible with MQQQTT topic handling 266 | //char userCmdl[] = " "; // 49Chrs + null 267 | // 268 | //eprm 269 | int eprmSize, eprmFree; 270 | // Addresses at end of EEPROM for saved PID parameters, TALE = count 271 | #define EADX_KP (eprmSize - 1 * (sizeof(float))) 272 | #define EADX_TI (eprmSize - 2 * (sizeof(float))) 273 | #define EADX_TD (eprmSize - 3 * (sizeof(float))) 274 | #define EADX_BE (eprmSize - 4 * (sizeof(float))) 275 | #define EADX_GA (eprmSize - 5 * (sizeof(float))) 276 | #define EADX_KA (eprmSize - 6 * (sizeof(float))) 277 | #define EADX_CT (eprmSize - 7 * (sizeof(float))) 278 | #define EPRM_TALE 7 279 | 280 | // Duty Cycle Step Size for OT(U/D) commands 281 | #define DUTY_STEP 5 // for OTn UP/DOWN commands 282 | 283 | //pidc 284 | //Ap15 285 | // Fast response PID to match approx 30Hz PWM frequency 286 | // Date Kp Ti Td Beta Gamma Ka 287 | // My10 6.0 3.0 5.00 1.0 1.0 0.00 popc 288 | // My19 8.0 2.0 2.00 1.0 1.0 0.00 popc 289 | // My19 8.0 3.0 2.00 1.0 1.0 0.00 popc 290 | // 291 | // 292 | float pidcKp = 12.000; // P-Term gain 293 | float pidcKc = 12.000; // P-Term gain compensated for setpoint above ambient 294 | float pidcTi = 1.350; // I-Term Gain sec ( Ti++ = Gain--) 295 | float pidcTd = 0.800; // D-Term Gain sec ( Td++ = Gain++) 296 | // 297 | float pidcBeta = 1.000; // P-term Refr vs YInp 298 | float pidcGamma = 1.000; // D-term Refr vs YInp 299 | float pidcKappa = 0.000; // Ambient comp Kp * ( 1 + Ka (sens - idle)/idle ) 300 | // 301 | float pidcRn = AMBI_TMPC; // Refr setpoint 302 | float pidcYn = AMBI_TMPC; // YInp input 303 | float pidcEn = 0.0; // Refr - Inpu 304 | float dUn, Edn, Epn, Epn1 = 0.0; // Calc error values 305 | float Edfn2, Edfn1, Edfn, Un, Un1 = 0.0; // Calc error, output, prev output values 306 | float Tf, Ts, TsDivTf = 0.0; // Filter, Actual sample period, stash 307 | float pidcPn, pidcIn, pidcDn = 0.0; // per sample P-I-D-Err Terms 308 | float pidcUn = 0.0; // PID controller Output 309 | int pidcPc, pidcIc, pidcDc = 0.0; // cumulative P-I-D components for diagnostic readout 310 | // 311 | #define pidcUMax 255.000 // Outp Max 312 | #define pidcUMin 0.000 // Outp Min 313 | #define pidcAlpha 0.100 // D-term Filter time 314 | #define PIDC_NVDT 0 // Sets Inverted D-Term, not classical Incremental PID 315 | /// 316 | // 317 | const char versChrs[] = "19Dc08-publ-UNO"; 318 | /// wip: stored profiles 319 | // profiles 320 | // stored as profiles 1-9 with steps 0-9 in each 321 | struct profTplt { 322 | float profTarg; // Ramp endpoint or Setpoint if RMin == 0 323 | float profRMin; // Ramp time to TargTemp decimal minutes 324 | float profHMin; // Hold time at TargTemp decimal minutes 325 | }; 326 | // profTplt profLine = { AMBI_TMPC, 0, 99 }; 327 | 328 | // 329 | #define EADX_PROF 0 // EEPROM starting address of stored profiles 330 | #define STEP_SIZE 3 * sizeof( float) 331 | #define PROF_SIZE 9 * STEP_SIZE 332 | // 333 | 334 | // pwmd vbls 335 | byte pwmdPcnt = 0; // Percent duty cycle 336 | int pwmdFreq, pwmdDuty, pwmdTarg, pwmdOutp = 0; // Freq, Duty Cycle Target (255max) Output 337 | 338 | // Run Cntl vbls determine On/Off/Auto/Manual/Attention et al Bit 0:Run 1:Atto 2:Manu 3:Attn 4:spare 5:Dbug 6:Arti 7:Info 339 | #define RCTL_RUNS 0x80 340 | #define RCTL_AUTO 0x40 341 | #define RCTL_MANU 0x20 342 | #define RCTL_ATTN 0x10 343 | #define RCTL_SPAR 0x08 344 | #define RCTL_DIAG 0x04 345 | #define RCTL_ARTI 0x02 346 | #define RCTL_INFO 0x01 347 | 348 | #if IFAC_ARTI 349 | byte bbrdRctl = RCTL_ARTI | RCTL_INFO; 350 | #else 351 | // 352 | byte bbrdRctl = RCTL_INFO; // Non-CSV serial out 353 | //byte bbrdRctl = 0x00; // Force CSV logging 354 | #endif 355 | // 356 | #if WITH_LCD 357 | byte lcdstRctl = RCTL_RUNS; 358 | #else 359 | byte lcdstRctl = 0x00; 360 | #endif 361 | // 362 | byte pidcRctl = (RCTL_RUNS | RCTL_AUTO ); 363 | byte pwmdRctl = (RCTL_RUNS | RCTL_AUTO); 364 | // 365 | #if WITH_OFFN 366 | byte offnRctl = (RCTL_RUNS | RCTL_AUTO); 367 | #else 368 | byte offnRctl = RCTL_RUNS; 369 | #endif 370 | // 371 | byte profRctl = RCTL_MANU; 372 | byte stepRctl = 0x00; 373 | byte rampRctl = 0x00; 374 | byte holdRctl = 0x00; 375 | byte tcplRctl = RCTL_RUNS; 376 | byte userRctl = RCTL_RUNS; 377 | // 378 | byte wifiRctl = 0x00; 379 | // 380 | byte profNmbr, stepNmbr, profChar, stepChar; // Numeric Profile No, Step No, Character values 381 | 382 | // Rotary Switch 383 | byte rotsCurr, rotsNewb; // Current value, newb test for change 384 | boolean offnOutp = 0; // off/on cycle counter, output 385 | 386 | // time markers compared with uS mS and mill count 387 | float pidcPoll = 0UL; 388 | unsigned long lcdsMark, millStep, millMark, pidcMark = 0UL; 389 | unsigned long profMark, pwmdMark, rotsMark, tcplMark, vtcpMark = 0UL; 390 | #if WITH_TCPL_2 391 | unsigned long tcp2Mark = 0UL; 392 | #endif 393 | // 394 | 395 | // 396 | float setpTmpC; // PID setpoint temp degC and previous value 397 | int userDuty = 0; // userSet duty cycle; userSet C/F, meas C dg pm 398 | int userAOT1, userAOT2, userAIO3 = 0; // Arti OT1Dty, Arti OT2Dty, Arti IO3Dty 399 | int baseTmpC, rampCdpm, holdTmpC = 0; // Ramp start temp, hold at endpoint 400 | int stepSecs, secsTgat, totlSecs = 0; // Step elapsed, hold countdown, total run time 401 | int gateTmpC, userDegs = 0; // User C/F temp, temporary array indexers, scratch vbls 402 | float sns1TmpC, sns2TmpC; // Sensed temperature deg C for sensors 1, 2 403 | float *chnATmpC, *chnBTmpC; // Pointers to sensor tempC for first two Artisan response slots 404 | float *pidcSens; // Pointers to sensor input to PID 405 | 406 | // Constants and variables for virtual tcpl, temperature / ROC tracking 407 | #define vChgSpht 0.2 // Specific heat of bean mass 408 | #define vHtrWatt 1500.0 // Heater power 409 | #define vTmpMaxC 260.0 // Limit temp at max heater power 410 | int vChgGrms = 250; // Bean mass, may be altered while running 411 | int vPrvMSec; 412 | float vTmpDegC = IDLE_TMPC; 413 | float vTmpDegF = ((IDLE_TMPC + 40 ) * (9.0 / 5.0 )) - 40 ; 414 | 415 | // History Dataset for moving average of sensor values for e.g. rate of change calculation 416 | int dataSet1[] = { AMBI_TMPC, AMBI_TMPC, AMBI_TMPC, AMBI_TMPC, AMBI_TMPC, AMBI_TMPC, \ 417 | AMBI_TMPC, AMBI_TMPC, AMBI_TMPC, AMBI_TMPC, AMBI_TMPC, AMBI_TMPC, \ 418 | AMBI_TMPC, AMBI_TMPC, AMBI_TMPC, AMBI_TMPC, AMBI_TMPC, AMBI_TMPC, \ 419 | AMBI_TMPC, AMBI_TMPC, AMBI_TMPC, AMBI_TMPC, AMBI_TMPC, AMBI_TMPC, \ 420 | AMBI_TMPC, AMBI_TMPC, AMBI_TMPC, AMBI_TMPC, AMBI_TMPC, AMBI_TMPC }; 421 | #define DATASET1_TALE 30 // How many elements in history 422 | 423 | int dataSet2[] = { AMBI_TMPC, AMBI_TMPC, AMBI_TMPC, AMBI_TMPC, AMBI_TMPC, AMBI_TMPC, \ 424 | AMBI_TMPC, AMBI_TMPC, AMBI_TMPC, AMBI_TMPC, AMBI_TMPC, AMBI_TMPC }; 425 | #define DATASET2_TALE 12 // How many elements in history 426 | // 427 | #define FMA0_WDOW 3 // Window numb slots for fast MA trend direction detection 428 | #define FMA1_WDOW 3 429 | #define FMA2_WDOW 3 430 | // 431 | #define MAV0_WDOW 5 // Window numb slots for moving average Latest values 432 | #define MAV1_WDOW 5 // Window numb slots for moving average Medium values 433 | #define MAV2_WDOW 5 // Window numb slots for moving average Oldest values 434 | // 435 | #define FMA4_WDOW 4 // Window numb slots for PWM Mavs 436 | #define FMA5_DIST 10 // Window numb slots for PWM Mavs 437 | #define PWM0_WDOW 3 438 | #define PWM2_WDOW 3 439 | // 440 | #define DIRN_THLD 4 // Threshold for phase direction detection 441 | #define SCAL_FACT 10 // Mutiplier on history samples for improved resolution 442 | // Ref: https://docs.scipy.org/doc/scipy-0.15.1/reference/generated/scipy.signal.savgol_coeffs.html 443 | // Depending on desired window width uncomment one set of cefficients below 444 | // 445 | int set1Indx, set2Indx; 446 | int mav0Indx, mav1Indx, mav2Indx; // Indeces for Curr, Prev, Old long-Window MAVs for ROC 447 | int histMav0, histMav1, histMav2; // Values for Curr, Prev, Old long-Window MAVs for ROC 448 | int fma0Indx, fma1Indx, fma2Indx; // Indeces for Curr, Prev, Old fast response ROC's for direction trends 449 | int histFma0, histFma1, histFma2; // Values for Curr, Prev, Old fast response ROC's for direction trends 450 | float histRoc1, histRoc2, histFRoc; // Fast, Long PID RoC, fast RRoc 451 | boolean chgeArmd, chgeSeen, tpntArmd, tpntSeen; // Armed, Detection flags for events 452 | // 453 | int pwmdFma0, pwmdFma2, pwmdIdx0, pwmdIdx2; // Values & Index for pwmd Mavs 454 | //float pwmdFRoc, beanRlHt; // Fast Mav of power input; inst ratio power chge / temp chge 455 | int beanRlHt; // Fast Mav of power input; inst ratio power chge / temp chge 456 | // 457 | /// Array indexing, moving averages 458 | // Initialise moving average history and calculated MA with supplied initial valu, Zero headIndex 459 | void initMavs(int initSet1, int initSet2) { 460 | byte i; 461 | // dataSet1 (raw sensor history) elements initialised 462 | for (i=0; (i < DATASET1_TALE ); i++) { 463 | dataSet1[i] = initSet1; 464 | } 465 | histMav2 = histMav1 = histMav0 = initSet1; // Slow Moving Avge 466 | histFma0 = histFma1 = histFma2 = initSet1; 467 | histFRoc = histRoc1 = histRoc2 = 0; // Short, Long rates of change 468 | // 469 | mav0Indx = wrapIndx( ( 0 ), DATASET1_TALE); // Slow MA Index, current: at first element of smoothed history 470 | mav1Indx = wrapIndx( (mav0Indx - (DATASET1_TALE / 2) ), DATASET1_TALE); // MidP MA Index, mid-age: at mid history plus window width 471 | mav2Indx = wrapIndx( (mav0Indx + 1 + MAV2_WDOW ), DATASET1_TALE); // Slow MA index, oldest: at end history, curr plus window width 472 | // 473 | fma0Indx = wrapIndx( ( 0 ), DATASET1_TALE); // Fast MA index // Fast MA Index, current 474 | fma1Indx = wrapIndx( (fma0Indx - FMA0_WDOW ), DATASET1_TALE); // Fast MA Index, mid-age 475 | fma2Indx = wrapIndx( (fma1Indx - FMA2_WDOW ), DATASET1_TALE); // Fast MA Index, oldest 476 | // 477 | pwmdIdx0 = wrapIndx( ( 0 ), DATASET2_TALE); // Slow PWM MA Index, current 478 | pwmdIdx2 = wrapIndx( (pwmdIdx0 - (4 * PWM0_WDOW ) ), DATASET2_TALE); // Delayed PWM MA Index 479 | pwmdFma2 = pwmdFma0 = INIT_PWMD; 480 | //pwmdFRoc = 0; // PWM fast Mav, fast Roc 481 | // dataSet2 (S-G smoothed sensor history) elements initialised 482 | for (i=0; (i < DATASET2_TALE ); i++) { 483 | dataSet2[i] = INIT_PWMD; 484 | } 485 | set1Indx = set2Indx = 0; 486 | } 487 | 488 | // 489 | int wrapIndx ( int iIndx, int iTale) { 490 | // Returns Index number above/below tIndx with wrap around based on tTale: array length 491 | int respIndx = iIndx; 492 | while (respIndx < 0 ) { 493 | respIndx += iTale; 494 | } 495 | while (respIndx >= iTale) { 496 | respIndx -= iTale; 497 | } 498 | //SePrn(F("iIndx: ")); SePrn(iIndx); SePrn(F(" respIndx: ")); SePln(respIndx); 499 | return(respIndx); 500 | } 501 | float incrMavs ( int *aHistAddx, int iHistTale, int iIndx, int iWindWide ) { 502 | float tempFltA; 503 | int tempIndx = wrapIndx ( (iIndx - iWindWide), iHistTale); 504 | tempFltA = float(aHistAddx[iIndx] ); // Add-In latest elem 505 | tempFltA -= float(aHistAddx[tempIndx]); // Del-Out prev oldest 506 | tempFltA /= (iWindWide); // Divide by weighting facor 507 | //Serial.print("#Mavs : "); Serial.println(tempFltA); 508 | return( tempFltA ); 509 | } 510 | 511 | /// Conversion functions 512 | // Temperature conversin 513 | float floatCtoF( float celsInp) { 514 | return (float( ((celsInp + 40.0) * 9.0 / 5.0 ) - 40.0 )); 515 | } 516 | 517 | float floatFtoC( float fahrInp) { 518 | return (float( ((fahrInp + 40.0) * 5.0 / 9.0 ) - 40.0 )); 519 | } 520 | 521 | // 522 | int intgFtoC( int fahrInp) { 523 | return (IFR((fahrInp + 40) * 5 / 9 ) - 40); 524 | } 525 | 526 | // 527 | byte nibl2Hex ( byte tNibl){ 528 | if (tNibl > 15) return '?'; 529 | if (tNibl > 9 ) { 530 | return ( 'A' + ( tNibl - 10)); 531 | } else { 532 | return ( '0' + tNibl ); 533 | } 534 | } 535 | 536 | /// Artisan Serial Response Buffer 537 | void respArti() { 538 | //// Artisan Iface : resp 'READ' = Amb,Ch1,2,3,4 : Ambient, Setpoint ('Expected'), Sensed, CDpm, PW% 539 | // Use Artisan "Config-Device-Extra TC4Ch3-4" 540 | // Clear entire buffer with spaces 541 | int iIndx; 542 | for (iIndx=0; (iIndx < (sizeof(artiResp)-1)); iIndx++) { 543 | artiResp[iIndx] = ' '; 544 | } 545 | #if (!SHOW_OPT1) 546 | if ( userScal == fahrChar) { 547 | dtostrf( floatCtoF(AMBI_TMPC), 5, 1, &artiResp[0] ); // Posn0: Art's Ta Ch 548 | dtostrf( floatCtoF(*chnATmpC), 5, 1, &artiResp[6] ); // P1: ( dflt: ET ) 549 | dtostrf( floatCtoF(*chnBTmpC), 5, 1, &artiResp[12] ); // P2: ( dflt: BT ) 550 | dtostrf( floatCtoF( setpTmpC), 5, 1, &artiResp[42] ); // P7: SV 551 | dtostrf( floatCtoF(IDLE_TMPC), 5, 1, &artiResp[48] ); // P8: (Internal Temp) 552 | } else { 553 | dtostrf( AMBI_TMPC, 5, 1, &artiResp[0] ); // P0: Art's Ta Ch 554 | dtostrf( *chnATmpC, 5, 1, &artiResp[6] ); // P1: ( dflt: ET ) 555 | dtostrf( *chnBTmpC, 5, 1, &artiResp[12] ); // P2: ( dflt: BT ) 556 | dtostrf( *chnATmpC, 5, 1, &artiResp[18] ); // P3: 557 | dtostrf( *chnBTmpC, 5, 1, &artiResp[24] ); // P4: 558 | // ESP pseems to pad with nulls 559 | //artResp[33] = artiResp[32] = artiResp[31] = artiResp[30] = ' '; 560 | dtostrf( pwmdPcnt, 5, 1, &artiResp[30] ); // P5: (HTR) PWM % Duty Cycle 561 | dtostrf( userAIO3, 5, 1, &artiResp[36] ); // P6: (FAN) PWM % Duty Cycle 562 | // ESP pseems to pad with nulls 563 | // artiResp[45] = artiResp[44] = artiResp[43] = artiResp[42] = ' '; 564 | dtostrf( setpTmpC, 5, 1, &artiResp[42] ); // P7: SV 565 | dtostrf( IDLE_TMPC, 5, 1, &artiResp[48] ); // P8: (Internal Temp) 566 | } 567 | #endif // !SHOW_OPTI 568 | // 569 | // Fields below over write Slots 0, 3, 4 Defaults to record ROC's and Savitzky-Golay algorithms 570 | #if ( SHOW_OPT1) 571 | if ( userScal == fahrChar) { 572 | //dtostrf( (histSago * 9 / 5 + 0), 5, 1, &artiResp[ 0] ); // P0: Sa-Go MAvg temperature 573 | dtostrf( 1 * (histRoc1 * 9 / 5 + 0), 5, 1, &artiResp[18] ); // P3: Latest RoC 574 | dtostrf( 1 * (histRoc2 * 9 / 5 + 0), 5, 1, &artiResp[24] ); // P4: Oldest RoC 575 | } else { 576 | // Option-1 Display 577 | //Artisan pulls P8 LED from P0 here 578 | //dtostrf( AMBI_TMPC, 5, 1, &artiResp[0] ); // P0: Art's Ta Ch 579 | dtostrf( ( 1 *(histFma0 / SCAL_FACT )), 5, 1, &artiResp[ 0] ); // P0: MAvg tmpC 580 | #if WITH_TCPL_2 581 | dtostrf( *chnATmpC, 5, 1, &artiResp[6] ); // P1: ( dflt: ET ) 582 | #else 583 | dtostrf( setpTmpC, 5, 1, &artiResp[6] ); // P1: ( dflt: ET ) 584 | #endif 585 | dtostrf( *chnBTmpC, 5, 1, &artiResp[12] ); // P2: ( dflt: BT ) 586 | // 587 | // 588 | //dtostrf( ( 1 *(fma45Roc) + 40), 5, 1, &artiResp[18] ); // P3: 589 | //dtostrf( ( 1 *(fma45Roc) + 50), 5, 1, &artiResp[24] ); // P4: 590 | // 591 | dtostrf( ( 1 *(histRoc1 ) + 0), 5, 1, &artiResp[18] ); // P3: 1/2 Min ROC of Simple MAs, scaled 592 | dtostrf( ( 1 *(histRoc2) + 0), 5, 1, &artiResp[24] ); // P4: 1.0 Min ROC of Simple MAs, scaled 593 | dtostrf( pwmdPcnt, 5, 1, &artiResp[30] ); // P5: (HTR) PWM % Duty Cycle 594 | dtostrf( ( 1 *(histFRoc) + 0), 5, 1, &artiResp[36] ); // P6: FastMA ROC 595 | dtostrf( setpTmpC, 5, 1, &artiResp[42] ); // P7: SV 596 | //dtostrf( ( 1 *(beanRlHt) + 100), 5, 1, &artiResp[48] ); // P8: Opt0-3 597 | dtostrf( ( 1 *(histFma0 / SCAL_FACT )), 5, 1, &artiResp[48] ); // P8: Opt 598 | } 599 | #endif (SHOW_OPT1) 600 | // for debug send easy ID's to Artisan Exra Devices / Chan LEDs 601 | #if (SHOW_TEST) 602 | if ( userScal == fahrChar) { 603 | dtostrf( 109 , 5, 1, &artiResp[ 0] ); // P0: 604 | dtostrf( 139 , 5, 1, &artiResp[18] ); // P3: 605 | dtostrf( 149 , 5, 1, &artiResp[24] ); // P4: 606 | } else { 607 | dtostrf( 108 , 5, 1, &artiResp[0] ); // P0: 608 | dtostrf( 118 , 5, 1, &artiResp[6] ); // P1: 609 | dtostrf( 128 , 5, 1, &artiResp[12] ); // P2: 610 | // 611 | dtostrf( 138 , 5, 1, &artiResp[18] ); // P3: 612 | dtostrf( 148 , 5, 1, &artiResp[24] ); // P4: 613 | // 614 | dtostrf( 158 , 5, 1, &artiResp[30] ); // P5: 615 | dtostrf( 168 , 5, 1, &artiResp[36] ); // P6: 616 | // 617 | dtostrf( 178 , 5, 1, &artiResp[42] ); // P7: SV 618 | dtostrf( 188 , 5, 1, &artiResp[48] ); // P8: 619 | } 620 | #endif (SHOW_TEST) 621 | // Add commas, eof to response fields 622 | artiResp[5] = ','; 623 | artiResp[11] = ','; 624 | artiResp[17] = ','; 625 | artiResp[23] = ','; 626 | artiResp[29] = ','; 627 | artiResp[35] = ','; 628 | artiResp[41] = ','; 629 | artiResp[47] = ','; 630 | //artiResp[53] = '/r'; 631 | //artiResp[54] = '/n'; 632 | // 633 | artiResp[53] = artiResp[54] = artiResp[55] = artiResp[56] = ' '; 634 | } 635 | 636 | // 637 | void fillBbrd() { 638 | // Billboard lines are filled for 2x16 LCD display and/or Serial 'Info' 639 | // strf ops append null chars, fill single chars later 640 | // Billboard Line [0] 641 | // LCD Display alternates on Even/Odd seconds 642 | if ( !(totlSecs % 2 )){ 643 | // 644 | // Even Seconds count 645 | // 646 | bbrdLin0[0] = 'P'; // P(rofile) Indicator 647 | bbrdLin0[1] = nibl2Hex( profNmbr); 648 | bbrdLin0[2] = '-'; 649 | bbrdLin0[3] = nibl2Hex(stepNmbr); 650 | bbrdLin0[4] = ' '; 651 | // Even Secs: show measured ROC 652 | bbrdLin0[6] = 'r'; 653 | // Even Secs: show measured ROC 654 | if ( userScal == fahrChar) { 655 | dtostrf( IFR( histRoc1 * 9.00 / 5.00), +3, 0, &bbrdLin0[7] ); 656 | } else { 657 | dtostrf( IFR( histRoc1), +3, 0, &bbrdLin0[7] ); 658 | } 659 | bbrdLin0[10] = ' '; 660 | // Show thermocouple installed on ChanB = BT 661 | bbrdLin0[11] = 'b'; 662 | if ( userScal == fahrChar) { 663 | dtostrf( floatCtoF(*chnBTmpC), 3, 0, &bbrdLin0[12] ); 664 | } else { 665 | dtostrf( *chnBTmpC, 3, 0, &bbrdLin0[12] ); 666 | } 667 | bbrdLin0[15] = userScal + 0x20; // Measured temp: lower case c/f 668 | } // End Even Seconds 669 | if ( (totlSecs % 2 )){ 670 | // 671 | // Odd Seconds count 672 | // Odd seconds, Show PWM output 673 | if ( pwmdRctl & RCTL_MANU) { 674 | bbrdLin0[0] = 'W'; // Indicate manual PWM upper case 'W' 675 | } else { 676 | bbrdLin0[0] = 'w'; // Indicate PID PWM lower case 'w' 677 | } 678 | dtostrf( pwmdPcnt, 3, 0, &bbrdLin0[1]); 679 | bbrdLin0[4] = '%'; 680 | // Odd Secs: show desred ROC 681 | bbrdLin0[6] = 'R'; 682 | // Odd secs: show Target ROC, ChanB (ET) or Setpoint if not Tcpl2 683 | dtostrf( rampCdpm, +3, 0, &bbrdLin0[7] ); 684 | bbrdLin0[10] = ' '; 685 | #if WITH_TCPL_2 686 | // Second thermocouple installed: Show ChanA = ET 687 | bbrdLin0[11] = 'a'; 688 | if ( userScal == fahrChar) { 689 | dtostrf( floatCtoF(*chnATmpC), 3, 0, &bbrdLin0[12] ); 690 | } else { 691 | dtostrf( *chnATmpC, 3, 0, &bbrdLin0[12] ); 692 | } 693 | #else 694 | bbrdLin0[11] = 's'; 695 | if ( userScal == fahrChar) { 696 | dtostrf( floatCtoF(setpTmpC), 3, 0, &bbrdLin0[12] ); 697 | } else { 698 | dtostrf( setpTmpC, 3, 0, &bbrdLin0[12] ); 699 | } 700 | #endif 701 | } 702 | // Either Secs count, overwrite string nulls with spaces 703 | bbrdLin0[5] = ' '; 704 | bbrdLin0[15] = userScal + 0x20; // Measured temp: lower case c/f 705 | // 706 | // Billboard Line [1] 707 | if ( !(totlSecs % 2 )) { 708 | // Even Secs: Preface time display with T(otal) time from startup 709 | // Profile Nmbr-Step 710 | //bbrdLin1[0] = phseCode; 711 | bbrdLin1[0] = ' '; 712 | // ESP views secsTgat: #if (PROC_NMCU) 713 | //Show total time in min.tenths 714 | dtostrf( (secsTgat / 60.0), 4, 1, &bbrdLin1[0]); 715 | bbrdLin1[4] = ' \''; 716 | // Preface time display with H(old), M(anu), R(amp), S(etp) 717 | bbrdLin1[5] = modeCode; 718 | // Odd Secs: show step time in min.tenths 719 | dtostrf( (stepSecs / 60), 2, 0, &bbrdLin1[6]); 720 | dtostrf( ((stepSecs / 6) % 10), 1, 0, &bbrdLin1[9]); 721 | 722 | if ( userScal == fahrChar) { 723 | dtostrf( floatCtoF(setpTmpC), 3, 0, &bbrdLin1[12] ); 724 | } else { 725 | dtostrf( setpTmpC, 3, 0, &bbrdLin1[12] ); 726 | } 727 | bbrdLin1[11] = setpChar; 728 | // 729 | } // End Even Seconds 730 | if ( totlSecs % 2 ) { 731 | // Odd Secs Lin[1] 732 | bbrdLin1[0] = phseCode; 733 | if ( userScal == fahrChar) { 734 | dtostrf( floatCtoF(gateTmpC), 3, 0, &bbrdLin1[1] ); 735 | } else { 736 | dtostrf( gateTmpC, 3, 0, &bbrdLin1[1] ); 737 | } 738 | bbrdLin1[4] = userScal + 0x20; // est gate temp: lower case c/f 739 | // Total elap time 740 | bbrdLin1[5] = ' '; 741 | // Even Secs: show total time in min.tenths 742 | dtostrf( (totlSecs / 60), 2, 0, &bbrdLin1[6]); 743 | dtostrf( ((totlSecs / 6) % 10), 1, 0, &bbrdLin1[9]); 744 | // 745 | if ( userScal == fahrChar) { 746 | dtostrf( floatCtoF(holdTmpC), 3, 0, &bbrdLin1[12] ); 747 | } else { 748 | dtostrf( holdTmpC, 3, 0, &bbrdLin1[12] ); 749 | } 750 | bbrdLin1[11] = holdChar; // Indicate Hold (Soak) Temp 751 | // 752 | } 753 | // Either Secs count, Always show ChanB-Sensor1 BT; overwrite string nulls with spaces 754 | bbrdLin1[8] = '.'; // Mins decimal pt, indecator, space 755 | bbrdLin1[10] = '\''; 756 | bbrdLin1[15] = userScal; 757 | // 758 | // fill info for serial output either Artisan protocol or console Info/csv logging 759 | if (bbrdRctl & RCTL_ARTI ){ 760 | // nothing is done here, for Artisan response usercmdl == 'REA' does respArti etc 761 | // 12Dc fill the bbrd for mqtt but dont send serial 762 | respArti(); 763 | } else { 764 | // ! RCTL_ARTI so use artiresp space to create csv response 765 | if ( bbrdRctl & RCTL_INFO ) { 766 | // ~ARTI & INFO : Non-CSV tbd 767 | } else { 768 | // ~ARTI & ~INFO : csv logging for Artisan import Select 1s / Ns : TotMin:Sec StepMin:sec BT ET Event SV Duty 769 | dtostrf( (totlSecs / 60) ,02, -0, &artiResp[0]); // CSV-1 Min:Tnths Total 770 | dtostrf( (totlSecs % 60) ,02, -0, &artiResp[3]); 771 | dtostrf( (stepSecs / 60) ,02, -0, &artiResp[6]); // CSV-2 Min:Tnths Step 772 | dtostrf( (stepSecs % 60) ,02, -0, &artiResp[9]); 773 | if ( userScal == fahrChar) { 774 | dtostrf( floatCtoF(*chnATmpC) ,5, 1, &artiResp[12] ); // CSV-3 775 | dtostrf( floatCtoF(*chnBTmpC) ,5, 1, &artiResp[18] ); // CSV-4 776 | // ChnC,D replaced by ROC calculations 777 | dtostrf( (histRoc1 * 9.0 / 5.0 ) ,5, 1, &artiResp[25] ); // CSV-5 778 | dtostrf( (histRoc2 * 9.0 / 5.0 ) ,5, 1, &artiResp[31] ); // CSV-6 779 | dtostrf( floatCtoF( setpTmpC) ,3, 0, &artiResp[46] ); // CSV-7 780 | dtostrf(( histFma0 / SCAL_FACT +0) ,5, 1, &artiResp[41] ); // CSV-8 FMav 781 | } else { 782 | dtostrf( *chnATmpC ,5, 1, &artiResp[12] ); // CSV-3 ET 783 | dtostrf( *chnBTmpC ,5, 1, &artiResp[18] ); // CSV-4 BT 784 | // ChnC,D replaced by ROC calculations 785 | dtostrf( (histRoc1 + 0) ,5, 1, &artiResp[25] ); // CSV-5 Roc1 786 | dtostrf( (histRoc2 + 0) ,5, 1, &artiResp[31] ); // CSV-6 Roc2 787 | dtostrf( pwmdPcnt ,3, 0, &artiResp[37] ); // CSV-7 PWM 788 | dtostrf( ( 1 * beanRlHt + 0 ) ,5, 1, &artiResp[41] ); // CSV-8 Pwr/dTmpC 789 | dtostrf( ( histFRoc + 0) ,5, 1, &artiResp[51] ); // CSV-10 FMav 790 | dtostrf( setpTmpC ,3, 0, &artiResp[47] ); // CSV-9 791 | } 792 | // insert leading zero into timestamps 793 | if (artiResp[0] == ' ') artiResp[0] = '0'; 794 | if (artiResp[3] == ' ') artiResp[3] = '0'; 795 | if (artiResp[6] == ' ') artiResp[6] = '0'; 796 | if (artiResp[9] == ' ') artiResp[9] = '0'; 797 | // fill fixed chars in response string, Artisan needs Tabs 798 | artiResp[2] = ':'; 799 | artiResp[5] = 0x09; 800 | artiResp[8] = ':'; 801 | artiResp[11] = 0x09; 802 | artiResp[17] = 0x09; 803 | artiResp[23] = 0x09; 804 | artiResp[24] = 0x09; // extra tab for 'event' field 805 | artiResp[30] = 0x09; 806 | artiResp[36] = 0x09; 807 | artiResp[40] = 0x09; 808 | artiResp[46] = 0x09; 809 | artiResp[50] = 0x09; 810 | // 811 | artiResp[56] = ' '; 812 | // 813 | // Flag csv is ready for posting 814 | bbrdRctl |= RCTL_ATTN; 815 | } 816 | } 817 | } 818 | 819 | /// EEPROM 820 | void eprmInit() { 821 | eprmSize = EEPROM.length(); 822 | eprmFree = eprmSize - ( EPRM_TALE * sizeof(float)); // Reserve floats: Ka Ga Be Td Ti Kp XB YB CT 823 | } 824 | 825 | void eprmInfo() { 826 | float fromEprm; 827 | Serial.print(F("# EEPROM Size: ")); Serial.print(eprmSize); 828 | Serial.print(F(" Free: ")); Serial.println(eprmFree); 829 | EEPROM.get(EADX_KP, fromEprm); 830 | Serial.print(F("# EEPROM Kp:")); 831 | Serial.print(fromEprm); 832 | EEPROM.get(EADX_TI, fromEprm); 833 | Serial.print(F(" Ti:")); 834 | Serial.print(fromEprm); 835 | EEPROM.get(EADX_TD, fromEprm); 836 | Serial.print(F(" Td:")); 837 | Serial.print(fromEprm); 838 | EEPROM.get(EADX_BE, fromEprm); 839 | Serial.print(F(" Be:")); 840 | Serial.print(fromEprm); 841 | EEPROM.get(EADX_GA, fromEprm); 842 | Serial.print(F(" Ga:")); 843 | Serial.print(fromEprm); 844 | EEPROM.get(EADX_KA, fromEprm); 845 | Serial.print(F(" Ka:")); 846 | Serial.print(fromEprm); 847 | EEPROM.get(EADX_CT, fromEprm); 848 | Serial.print(F(" Tc:")); 849 | Serial.println(fromEprm); 850 | } 851 | 852 | #if WITH_LCD 853 | /// LCD DISPLAY 854 | void lcdsInit() { 855 | modeCode = setpChar; 856 | lcd.begin(16, 2); 857 | lcd.init(); 858 | lcd.backlight(); 859 | lcd.home (); 860 | lcd.print(F("<== PopC-PID ==>")); 861 | lcd.setCursor ( 0, 1 ); 862 | lcd.print(F("@bitwisetech.com")); 863 | delay ( 500 ); // 500mS startup delay 864 | lcd.clear(); 865 | lcdsMark = millis() + LCDS_POLL_MSEC; 866 | } 867 | 868 | void lcdsLoop() { 869 | if (millis() < lcdsMark) { 870 | return; 871 | } else { 872 | lcdsMark += LCDS_POLL_MSEC; 873 | // 874 | if (lcdstRctl == 0) { 875 | // Rctl == 0 Shutdown 876 | lcd.home (); 877 | lcd.print(F("lcdsLoop() Halt ")); 878 | delay ( 500 ); // 500mS startup delay 879 | } else { 880 | // 881 | lcd.setCursor(0, 0); // Posn char 0 line 0 882 | lcd.print(bbrdLin0); 883 | lcd.setCursor(0, 1); // Posn char 0 line 1 884 | lcd.print(bbrdLin1); 885 | } 886 | } 887 | } 888 | #endif 889 | 890 | /// Off-On SSR driver mill 891 | // 892 | // Wrap writes to OFFN_PIN with TWIO Out 893 | void offnDrve ( byte tVal) { 894 | #if WITH_OFFN 895 | digitalWrite( OFFN_OPIN, ((tVal > 0) ? 1:0)); 896 | #endif 897 | } 898 | 899 | void millInit() { 900 | millStep = 0; 901 | #if WITH_OFFN 902 | pinMode( OFFN_OPIN, OUTPUT); 903 | #endif 904 | offnDrve( 0); 905 | millMark = micros() + MILL_POLL_USEC; 906 | } 907 | 908 | void millLoop() { 909 | int sensXMul; 910 | if ( micros() < millMark ) { 911 | return; 912 | } else { 913 | millStep += 1; 914 | millMark += MILL_POLL_USEC; 915 | // mill 916 | // Every step of mill: checck PWM % for slow off/on output change 917 | // Mill step * 400 is 2000mSec cycle (100% PWM), 1%PWM is 4 * Steps 918 | // Writes to flicker onboard LED disabled, interfere if using as output 919 | if ( ( pwmdPcnt * 2 ) > ( millStep % 200 ) ) { 920 | //use Outp as trig to avoid repeated i/o traffic, set on: offn mark time 921 | if (offnOutp == 0) { 922 | // once through to set pin 923 | offnOutp = 1; 924 | offnRctl |= RCTL_ATTN; // for virt tcpl 925 | if ( offnRctl & RCTL_AUTO) { 926 | offnDrve ( 1); 927 | // drive onboard Led 928 | //digitalWrite( ONBD_OPIN, (1 & ~ONBD_LOWON)); 929 | } 930 | } 931 | } else { 932 | if (offnOutp == 1) { 933 | // once through to set pin off: offn space time 934 | //use Outp as trig to avoid repeated i/o traffic 935 | offnOutp = 0; 936 | offnRctl &= ~RCTL_ATTN; // for virt tcpl 937 | if ( offnRctl & RCTL_AUTO) { 938 | offnDrve ( 0); 939 | // drive onboard Led 940 | //digitalWrite( ONBD_OPIN, (0 | ONBD_LOWON)); 941 | } 942 | } 943 | } 944 | // No flicker tell tale LED in case of fast PWM, show offn onboard Led 945 | //if ( (offnRctl & RCTL_AUTO) && (millStep & 64)) { 946 | // digitalWrite( ONBD_OPIN, (0 | ONBD_LOWON)); 947 | //} 948 | // ESP's A/D converter is <= mill rate: controlled by ACDC_POLL_MILL 949 | // Each mill step: 950 | // Per second moving average calculation 951 | if ( !( millStep % MILL_STEP_PSEC) ) { 952 | if (!isnan(*pidcSens)) { 953 | // New value: 954 | sensXMul = int(*pidcSens * SCAL_FACT + 0.5) ; 955 | // SePrn("# sensX:");SePln(sensXMul); 956 | } else { 957 | // Replicate 958 | // 959 | SePln("# RepSns1"); 960 | sensXMul = dataSet1[set1Indx]; 961 | } 962 | /// 963 | // dataSet1: wheel of sample * scale 964 | // 965 | // Sensor history new Value: Incr index, insert new val and uodate moving averages 966 | /// DataSet1: Mavg Roc1, Roc2 967 | set1Indx = wrapIndx(++set1Indx, DATASET1_TALE); 968 | dataSet1[set1Indx] = sensXMul; 969 | // 970 | // Current, mid-age, oldest Slow Moving averages on sensor samples 971 | mav0Indx = wrapIndx( (++mav0Indx) , DATASET1_TALE); 972 | histMav0 += IFR(incrMavs ( dataSet1, DATASET1_TALE, mav0Indx, MAV0_WDOW) + 0 ); 973 | // 974 | mav1Indx = wrapIndx( (++mav1Indx), DATASET1_TALE); 975 | histMav1 += IFR(incrMavs ( dataSet1, DATASET1_TALE, mav1Indx, MAV1_WDOW) + 0 ); 976 | // 977 | mav2Indx = wrapIndx((++mav2Indx), DATASET1_TALE); 978 | histMav2 += IFR(incrMavs ( dataSet1, DATASET1_TALE, mav2Indx, MAV2_WDOW) + 0 ); 979 | // 980 | // Current, older Fast Moving Averages on sensor samples 981 | fma0Indx = wrapIndx(++fma0Indx, DATASET1_TALE); 982 | // incrMavs MAvg calc returns change in cumulative moving average for latest values 983 | histFma0 += IFR(incrMavs ( dataSet1, DATASET1_TALE, fma0Indx, FMA0_WDOW) + 0 ); 984 | // No New value: Bump-Wrap index, Update calculated moving average 985 | fma1Indx = wrapIndx(++fma1Indx, DATASET1_TALE); 986 | // incrMavs MAvg calc returns change in cumulative moving average for latest values 987 | histFma1 += IFR(incrMavs ( dataSet1, DATASET1_TALE, fma1Indx, FMA1_WDOW) + 0 ); 988 | // No New value: Bump-Wrap index, Update calculated moving average 989 | fma2Indx = wrapIndx(++fma2Indx, DATASET1_TALE); 990 | // incrMavs MAvg calc returns change in cumulative moving average for latest values 991 | histFma2 += IFR(incrMavs ( dataSet1, DATASET1_TALE, fma2Indx, FMA2_WDOW) + 0 ); 992 | // 993 | // Scale rates of change by distance between samples 994 | //histRoc1 = (histMav0 - histMav1) * ( (60.0 / SCAL_FACT) / ( DATASET1_TALE / 2 - ( MAV0_WDOW + MAV1_WDOW) / 2)); 995 | histRoc1 = (histMav0 - histMav1) * ( (60.0 / SCAL_FACT) / ( DATASET1_TALE / 2 )); 996 | //histRoc2 = (histMav0 - histMav2) * ( (60.0 / SCAL_FACT) / ( DATASET1_TALE - ( MAV0_WDOW + MAV2_WDOW) / 2)); 997 | // 998 | histRoc2 = (histMav0 - histMav2) * ( (60.0 / SCAL_FACT) / ( DATASET1_TALE )); 999 | histFRoc = (histFma0 - histFma2) * ( (60.0 / SCAL_FACT) / ((FMA0_WDOW+FMA2_WDOW )/2 + FMA1_WDOW)); 1000 | // 1001 | // UNO: Dataset2 History of PWM power 1002 | //Bump-Wrap index, insert scaled value into history 1003 | set2Indx = wrapIndx(++set2Indx, DATASET2_TALE); 1004 | dataSet2[set2Indx] = IFR(pwmdPcnt * SCAL_FACT); 1005 | // bump dset2 indeces 1006 | pwmdIdx0 = wrapIndx( ++pwmdIdx0 , DATASET2_TALE); 1007 | pwmdIdx2 = wrapIndx( ++pwmdIdx2 , DATASET2_TALE); 1008 | // calc rates of change of dset2 samples: change in heater power 1009 | pwmdFma0 += IFR(incrMavs( dataSet2, DATASET2_TALE, pwmdIdx0, PWM0_WDOW) + 0 ); 1010 | pwmdFma2 += IFR(incrMavs( dataSet2, DATASET2_TALE, pwmdIdx2, PWM2_WDOW) + 0 ); 1011 | // pwmdFRoc = float(pwmdFma0 - pwmdFma2) * ( (60.0 / SCAL_FACT) / ((FMA0_WDOW+FMA2_WDOW )/2 + FMA1_WDOW)); 1012 | // Below: dTempC / dPWM 1013 | // beanRlHt = float( histFRoc / pwmdFRoc); 1014 | // Below: MAv TmpC / MAv PWM 1015 | // 1016 | beanRlHt = ( pwmdFma0 / histMav0); // Scale Magnified by arb amount 1017 | //SePln(beanRlHt); 1018 | // Key Event detection 1019 | //SePrn(F("# cA, tA, D1, D0, cS, tS: ")); 1020 | //SePrn(chgeArmd); SePrn(F(" ")); SePrn(tpntArmd); SePrn(F(" ")); 1021 | //SePrn(histFma1 - histFma2); SePrn(F(" ")); SePrn(histFma0 - histFma1); SePrn(F(" ")); SePrn(chgeSeen); 1022 | //SePrn(F(" ")); SePln(tpntSeen); 1023 | if ( chgeArmd) { 1024 | if ( ((histFma0 - histFma1 + DIRN_THLD) < 0 ) && ((histFma0 - histFma2 + DIRN_THLD) < 0 )) { 1025 | chgeSeen = 1; 1026 | chgeArmd = 0; 1027 | phseCode = chgdChar; 1028 | //pidcSync(); 1029 | //Serial.println(F("# CHARGE Seen")); 1030 | } 1031 | } 1032 | if ( tpntArmd) { 1033 | if ( ((histFma1 - histFma2 + DIRN_THLD) < 0 ) && ((histFma0 - histFma1 - DIRN_THLD) > 0 )) { 1034 | tpntSeen = 1; 1035 | tpntArmd = 0; 1036 | phseCode = tpntChar; 1037 | //pidcSync(); 1038 | //Serial.println(F("# TURNPT Seen")); 1039 | } 1040 | } 1041 | if ( histFma0 > BOIL_TMPC / 2 * SCAL_FACT) { phseCode = boilChar; gateTmpC = BOIL_TMPC;} 1042 | if ( histFma0 > BOIL_TMPC * SCAL_FACT) { phseCode = dryeChar; gateTmpC = DRYE_TMPC;} 1043 | if ( histFma0 > DRYE_TMPC * SCAL_FACT) { phseCode = fc1sChar; gateTmpC = FC1S_TMPC;} 1044 | if ( histFma0 > FC1S_TMPC * SCAL_FACT) { phseCode = finiChar; gateTmpC = FINI_TMPC;} 1045 | } // Enp per second moving average 1046 | } // End Mill Step 1047 | } 1048 | 1049 | /// PID Controller 1050 | // pidc - implementation of PID controller 1051 | // dUn = Kp * [ (ep_n - ep_n-1) 1052 | // + ((Ts/Ti) * e_n) 1053 | // + ((Td/Ts) * (edf_n - 2*edf_n-1 + edf_n-2) ) ] 1054 | // 1055 | // Un = Un1 + dUn Output = prev output + delta output 1056 | // 1057 | // Inputs Yn: Measured Rn: Reference 1058 | // Beta: Prop refr weighting Gamma: Diff Refr Weighting 1059 | // Kp: Prop (Overall ?? ) Gain 1060 | // Ti, Td, Ts Integrator, Deriv, Sample Times 1061 | // Umin, Umax Limited output clamps 1062 | // 1063 | // Output: Un 1064 | // 1065 | // Ep: Proportional error with reference weighing 1066 | // Ep = pidcBeta * Rn - Yn 1067 | // E: Error = Rn - Yn 1068 | // Ed: Unfiltered Derivative Error 1069 | // Ed = pidcGamma * Rn -Yn 1070 | // Edf: Deriv error with reference weighing and filtering 1071 | // Edfn = Efn1 / ((Ts/Tf) + 1) + Edn * (Ts/Tf) / ((Ts/Tf) + 1) 1072 | // where: 1073 | // Tf : Filter time 1074 | // Tf = alpha * Td , where alpha usually is set to 0.1 1075 | /// 1076 | 1077 | 1078 | int anewFprm( int eadx, float *targ, char *titl) { 1079 | float fromEprm; 1080 | EEPROM.get (eadx, fromEprm ); 1081 | if ( !( fromEprm == *targ)){ 1082 | if ( !( bbrdRctl & RCTL_ARTI ) ) { 1083 | Serial.print(titl); Serial.print(fromEprm); 1084 | } 1085 | *targ = fromEprm; 1086 | return(1); 1087 | } else { 1088 | return(0); 1089 | } 1090 | } 1091 | 1092 | // Read PID Gain parms from Eprom 1093 | void pidcFprm() { 1094 | byte anewTale = 0; 1095 | if ( !( bbrdRctl & RCTL_ARTI )) { 1096 | Serial.print(F("# NewVals ex EEPROM:")); 1097 | } 1098 | anewTale += anewFprm( EADX_KP, &pidcKp, ("Kp: ")); 1099 | anewTale += anewFprm( EADX_TI, &pidcTi, ("Ti: ")); 1100 | anewTale += anewFprm( EADX_TD, &pidcTd, ("Td: ")); 1101 | anewTale += anewFprm( EADX_CT, &pidcPoll, ("Ct: ")); 1102 | anewTale += anewFprm( EADX_BE, &pidcBeta, ("Be: ")); 1103 | anewTale += anewFprm( EADX_KA, &pidcKappa,("Ka: ")); 1104 | // 1105 | if ( !( bbrdRctl & RCTL_ARTI ) ) { 1106 | Serial.print(F(" "));Serial.print( anewTale); Serial.println(F(" new Vals")); 1107 | } 1108 | } 1109 | 1110 | // Serial logout of PID internal values 1111 | void pidcInfo() { 1112 | Serial.print(F("# PIDC Kp:")); 1113 | Serial.print(pidcKp); 1114 | Serial.print(F(" Ti:")); 1115 | Serial.print(pidcTi); 1116 | Serial.print(F(" Td:")); 1117 | Serial.print(pidcTd); 1118 | //Serial.print(F(" Be:")); 1119 | //Serial.print(pidcBeta); 1120 | //Serial.print(F(" Ga:")); 1121 | //Serial.print(pidcGamma); 1122 | //Serial.print(F(" Ka:")); 1123 | //Serial.print(pidcKappa); 1124 | Serial.print(F(" Tc:")); 1125 | Serial.println(pidcPoll); 1126 | } 1127 | 1128 | void pidcRset() { 1129 | // PID live reset: Zero P, I, D terms Setpoint <= Ambient 1130 | // tbd: Should PID run after reset ? 1131 | Tf = pidcAlpha * pidcTd; 1132 | pidcEn = Epn1 = Epn = 0; 1133 | Edfn2 = Edfn1 = Edfn = 0; 1134 | pidcPn = pidcIn = pidcDn = 0; 1135 | pidcPc = pidcIc = pidcDc = 0; 1136 | pidcUn = Un = Un1 = 0; 1137 | pidcRn = pidcYn = setpTmpC = userDegs = AMBI_TMPC; 1138 | initMavs(AMBI_TMPC, INIT_PWMD); // To clear dataSets and arm chge, tpnt detectors 1139 | modeCode = manuChar; 1140 | phseCode = orffChar; 1141 | #if WITH_OFFN 1142 | offnDrve(0); 1143 | #endif 1144 | #if WITH_PWMD 1145 | pwmdRctl &= ~RCTL_AUTO; 1146 | pwmdRctl |= RCTL_MANU; 1147 | #endif 1148 | #if WITH_VIRTTCPL 1149 | //sns1TmpC = AMBI_TMPC; 1150 | #if WITH_TCPL_2 1151 | //sns2TmpC = AMBI_TMPC; 1152 | #endif 1153 | #endif 1154 | } 1155 | 1156 | // Initialise at processor boot setup; pulls gains from Eprom and overwrites compiled values 1157 | void pidcInit() { 1158 | pidcRset(); 1159 | // uncomm to get pidParms frm eprm 1160 | // first time being enabled. 1161 | pidcPoll = PIDC_POLL_MSEC; 1162 | pidcMark = millis() + pidcPoll; 1163 | setpTmpC = int(AMBI_TMPC); 1164 | } 1165 | 1166 | // PID runtime iteration 1167 | void pidcLoop() { 1168 | if ( millis() < pidcMark ) return; else { 1169 | pidcMark += pidcPoll; 1170 | // 1171 | if ( (pidcRctl & RCTL_RUNS) == 0 ) { 1172 | // Poll/Thermocouple == 0 Shutdown 1173 | if ( ( !NDIA_ARTI ) && ( bbrdRctl & RCTL_DIAG) ) { 1174 | Serial.println(F("# pidc stop")); 1175 | } 1176 | Un = pidcUn = pidcRn = pidcYn = 0.00; 1177 | } else { 1178 | // Run PID iteration 1179 | Ts = (pidcPoll / 1000.0); // Sample Interval (Sec) 1180 | if (!isnan(setpTmpC)) pidcRn = (float)setpTmpC; // Update Reference (Setpoint) 1181 | if (!isnan(*pidcSens)) pidcYn = (float)*pidcSens; // Update Measured Value (Sensed temperature) 1182 | pidcEn = pidcRn - pidcYn; // Error term: = (Setpoint - Measured ) 1183 | Epn = pidcBeta * pidcRn - pidcYn; // Proportional Error term: = (Beta * Setpoint - Measured ) 1184 | // D term 1185 | if ( pidcTd <= 0 ) { 1186 | Edfn2 = Edfn1 = Edfn = 0; 1187 | } else { 1188 | Edn = pidcGamma * pidcRn - pidcYn; // For D term Refence value is scaled by Gamma 1189 | // Filter the derivate error: 1190 | Tf = pidcAlpha * pidcTd; // Filter time is usually Sample time / 10 1191 | TsDivTf = Ts / Tf; // Filter smooths abrupt changes in error 1192 | Edfn = (Edfn1 / (TsDivTf + 1.0)) + (Edn * ((TsDivTf) / (TsDivTf + 1.0))); 1193 | } 1194 | // Accum Combine P, I, D terms 1195 | dUn = 0; 1196 | // P term 1197 | // try temp compensated gain pidcPn = pidcKp * (Epn - Epn1); 1198 | pidcPn = pidcKc * (Epn - Epn1); // P term for this sample period ( Using compensated gain) 1199 | pidcPc += int(pidcPn +0.5); // Pc cumulative used only for logging 1200 | // I term 1201 | if ( pidcTi > 0.0 ) { 1202 | //Jn10 KtKp pidcIn = pidcKp * ((Ts / pidcTi) * pidcEn); 1203 | pidcIn = ((Ts / pidcTi) * pidcEn); // I term for sample period 1204 | } else { 1205 | pidcIn = 0; 1206 | } 1207 | pidcIc += int(pidcIn + 0.5); // Ic cumulative I-term for logging 1208 | // D term computes change in rate 1209 | if ( pidcTd > 0.0 ) { // D term compares ( curr + oldest ) vs (2 * middle) values 1210 | //Jn10 KtKp pidcDn = pidcKp * ((pidcTd / Ts) * (Edfn - (2 * Edfn1) + Edfn2)); 1211 | #if PIDC_NVDT 1212 | pidcDn = ((pidcTd / Ts) * ((2 * Edfn1) - Edfn - Edfn2)); 1213 | #else 1214 | pidcDn = ((pidcTd / Ts) * (Edfn - (2 * Edfn1) + Edfn2)); 1215 | #endif 1216 | } else { 1217 | pidcDn = 0; 1218 | } 1219 | pidcDc += int(pidcDn); // Dc cumulative for logging 1220 | //dUn = pidcPn + pidcIn + pidcDn; 1221 | dUn = pidcPn + pidcIn + pidcDn; // dUn: Combined P+I+D deltas for sample period 1222 | // Integrator anti-windup logic: 1223 | if ( dUn > (pidcUMax - Un1) ) { 1224 | dUn = pidcUMax - Un1; 1225 | //if ( !( bbrdRctl & RCTL_ARTI ) && ( bbrdRctl & RCTL_DIAG) ) { 1226 | // Serial.println(F("# maxSatn")); 1227 | // } 1228 | } else if ( dUn < (pidcUMin - Un1) ) { 1229 | dUn = pidcUMin - Un1; 1230 | //if ( !( bbrdRctl & RCTL_ARTI ) && ( bbrdRctl & RCTL_DIAG) ) { 1231 | // Serial.println(F("# minSatn")); 1232 | //} 1233 | } 1234 | // Update output if increment is valid number 1235 | if (!isnan(dUn)) { 1236 | Un = Un1 + dUn; // Add PID delta to Output value 1237 | } 1238 | if ( pidcRctl & RCTL_AUTO) { 1239 | pidcUn = Un; // Only if PID in Auto: Copy into external Output Variable 1240 | } else { 1241 | pidcUn = 0; 1242 | } 1243 | // Updates indexed values; 1244 | Un1 = Un; 1245 | Epn1 = Epn; 1246 | Edfn2 = Edfn1; 1247 | Edfn1 = Edfn; 1248 | } 1249 | } 1250 | } 1251 | 1252 | void pidcStrt(){ 1253 | // PID ON | START 1254 | pidcRctl |= (RCTL_RUNS | RCTL_AUTO); 1255 | #if WITH_PWMD 1256 | pwmdRctl &= ~RCTL_MANU; 1257 | pwmdRctl |= (RCTL_RUNS | RCTL_AUTO); 1258 | #endif 1259 | modeCode = setpChar; 1260 | } 1261 | 1262 | void pidcStop() { 1263 | modeCode = manuChar; 1264 | pidcRctl &= ~RCTL_AUTO; 1265 | pidcRn = pidcYn = setpTmpC = userDegs = AMBI_TMPC; 1266 | offnDrve(0); 1267 | #if WITH_PWMD 1268 | pwmdRctl &= ~RCTL_AUTO; 1269 | pwmdRctl |= RCTL_MANU; 1270 | #endif 1271 | userDuty = 0; 1272 | phseCode = muteChar; 1273 | } 1274 | 1275 | void pidcSync() { 1276 | // PID live reset: Clear P,I,D terms but not accumuated output 1277 | // PID is not stopped, use this function to e.g clear large error after charge event 1278 | pidcPn = pidcIn = pidcDn = 0; 1279 | pidcPc = pidcIc = pidcDc = 0; 1280 | if (!isnan(*pidcSens)) { 1281 | baseTmpC = pidcRn = pidcYn = userDegs = setpTmpC = *pidcSens; 1282 | initMavs(baseTmpC, pwmdPcnt); // To clear dataSets and arm chge, tpnt detectors 1283 | //SePrn(F("#Sync tmpC:")); SePln(baseTmpC); 1284 | } else { 1285 | pidcRn = pidcYn = float ( IDLE_TMPC + ( pwmdTarg / 255.0 ) * ( MAXI_TMPC - IDLE_TMPC)); 1286 | } 1287 | pidcUn = Un = float ( pwmdTarg); 1288 | phseCode = prehChar; 1289 | } 1290 | 1291 | /// Profile Control Sequence 1292 | // None selected : skip to sequencer end 1293 | // step not active : get next step 1294 | // past last step : reset, skip to sequencer end 1295 | // ramp zero: : skip to hold temperature 1296 | // ramp active: : skip to sequencer end 1297 | // ramp (else ~zero && ~active) : start active ramp, skip to sequencer end 1298 | // ramp done : pr rt active ramp, skip to sequencer end 1299 | // hold time zero : skip to sequencer hold 1300 | // hTemp ctive: : skip to sequencer end 1301 | // ramp (else ~zero && ~active) : start active ramp, skip to sequencer end 1302 | // 1303 | void profInit() { 1304 | // simulation 1305 | *pidcSens = holdTmpC = int(AMBI_TMPC); 1306 | stepSecs = totlSecs = secsTgat = 0; 1307 | gateTmpC = BOIL_TMPC; 1308 | profMark = millis() + PROF_POLL_MSEC; 1309 | // setup chan slots cf tc2: AMB-0 ET-A BT-B etc 1310 | chanSens( 1, 2); 1311 | chanSens( 2, 1); 1312 | chanSens( 3, 3); 1313 | chanSens( 4, 4); 1314 | } 1315 | 1316 | void profLoop() { 1317 | if ( millis() < profMark) return; else { 1318 | profMark += PROF_POLL_MSEC; 1319 | // prevent lcd rollover; 6000 secs == 100 min 1320 | (secsTgat > 0) ? (secsTgat -= 1) : (secsTgat = 0); 1321 | (totlSecs > 5998) ? (totlSecs = 0) : (totlSecs += 1); 1322 | (stepSecs > 5998) ? (stepSecs = 0) : (stepSecs += 1); 1323 | // apply PID gain compensation if non-zero 1324 | if ( (!isnan(*pidcSens)) && ( pidcKappa > 0 ) ) { 1325 | pidcKc = pidcKp * ( 1 + pidcKappa * ( *pidcSens - IDLE_TMPC ) / IDLE_TMPC ); 1326 | } else { 1327 | pidcKc = pidcKp; 1328 | } 1329 | // Handle stored profile 1330 | if (profNmbr <= 0) { 1331 | // Stop auto profile and revert to manual control 1332 | profRctl = RCTL_MANU; 1333 | stepRctl, rampRctl, holdRctl = 0x00; 1334 | } 1335 | else { 1336 | // wip Active saved profile: is active 1337 | if ( rampRctl & RCTL_RUNS ){ 1338 | // Ramp to temp is active 1339 | if ( rampRctl & RCTL_ATTN ){ 1340 | // Ramp to temp is active and complete 1341 | } 1342 | } 1343 | // 1344 | } 1345 | // Is either in ramp or setpt or fixed pwm. Use rampCdpm to find out 1346 | if (rampCdpm == 0) { 1347 | // ramp of zero implies either full power to setpoint or fixed pwm 1348 | // Au21 sb done by calls to common: modeCode = setpChar; 1349 | } else { 1350 | // Profile or User ramp to setpoint temp and then hold 1351 | // User Ramp forces max/min setpoint temp: after 'r' cmd use 's' cmd to set Hold temp 1352 | if (stepSecs > 10 ) { 1353 | if (holdTmpC >= baseTmpC ) { 1354 | // Rising ramp finished when sensed is above hold Temp 1355 | if ((!isnan(*pidcSens)) && (*pidcSens >= holdTmpC)) modeCode = holdChar; 1356 | } else { 1357 | // Falling ramp finished when sensed is below hold Temp 1358 | if ((!isnan(*pidcSens)) && (*pidcSens < holdTmpC)) modeCode = holdChar; 1359 | } 1360 | } 1361 | if ( modeCode == holdChar ) { 1362 | // do not really want to have s mode 1363 | rampCdpm = 0; 1364 | setpTmpC = holdTmpC; 1365 | // wiprampFini = 1; 1366 | } else { 1367 | modeCode = rampChar; 1368 | setpTmpC = float(baseTmpC) \ 1369 | + float (stepSecs) * float(rampCdpm) / 60.0; 1370 | } 1371 | } 1372 | if ( histRoc1 > 0) { 1373 | // secsTgat = 60 * ( gateTmpC - *pidcSens ) / rampCdpm; 1374 | // 1375 | secsTgat = 60 * ( gateTmpC - *pidcSens ) / histRoc1; 1376 | } 1377 | // update billboard 1378 | fillBbrd(); 1379 | int tempIntA; 1380 | // 1381 | if ( !( bbrdRctl & RCTL_ARTI ) ) { 1382 | if ( bbrdRctl & RCTL_INFO ) { 1383 | // Send billboard 'Info' on serial 1384 | for ( tempIntA = 0; tempIntA < 16; tempIntA++ ) { 1385 | Serial.write(bbrdLin0[tempIntA]); 1386 | } 1387 | Serial.write(" <=> "); 1388 | for ( tempIntA = 0; tempIntA < 16; tempIntA++ ) { 1389 | Serial.write(bbrdLin1[tempIntA]); 1390 | } 1391 | // For PID debug Un-comment and set #if 1 at pidcDbug() 1392 | //if ((bbrdRctl & RCTL_DIAG) == RCTL_DIAG) { 1393 | //pidcDbug(); 1394 | //} 1395 | Serial.println(F(" ")); 1396 | } else { 1397 | // If bbrd flagged send Artisan csv logging serial 1398 | if ( bbrdRctl & RCTL_ATTN ) { 1399 | for ( tempIntA = 0; tempIntA < (sizeof(artiResp) -1); tempIntA++ ) { 1400 | Serial.write(artiResp[tempIntA]); 1401 | } 1402 | Serial.write('\r'); 1403 | Serial.write('\n'); 1404 | bbrdRctl &= ~RCTL_ATTN; 1405 | } 1406 | } 1407 | } 1408 | } 1409 | } 1410 | 1411 | /// PWM Drive 1412 | // For Arduino Uno, Nano, Micro Magician, Mini Driver, Lilly Pad, any ATmega 8, 168, 328 board** 1413 | //---------------------------------------------- Set PWM frequency for D5 & D6 ----------------- 1414 | // for PWM freq 62500.00 Hz 1415 | //TCCR0B = TCCR0B & B11111000 | B00000010 tmr 0 divisor: 8 for PWM freq 7812.50 Hz 1416 | //TCCR0B = TCCR0B & B11111000 | B00000011 *tmr 0 divisor: 64 for PWM freq 976.56 Hz 1417 | //TCCR0B = TCCR0B & B11111000 | B00000100 tmr 0 divisor: 256 for PWM freq 244.14 Hz 1418 | //TCCR0B = TCCR0B & B11111000 | B00000101 tmr 0 divisor: 1024 for PWM freq 61.04 Hz 1419 | //---------------------------------------------- Set PWM frequency for D9 & D10 ---------------- 1420 | //TCCR1B = TCCR1B & B11111000 | B00000001 tmr 1 divisor: 1 for PWM freq 31372.55 Hz 1421 | //TCCR1B = TCCR1B & B11111000 | B00000010 tmr 1 divisor: 8 for PWM freq 3921.16 Hz 1422 | //TCCR1B = TCCR1B & B11111000 | B00000011 *tmr 1 divisor: 64 for PWM freq 490.20 Hz 1423 | //TCCR1B = TCCR1B & B11111000 | B00000100 tmr 1 divisor: 256 for PWM freq 122.55 Hz 1424 | //TCCR1B = TCCR1B & B11111000 | B00000101 tmr 1 divisor: 1024 for PWM freq 30.64 Hz 1425 | //------------------------------------------- Set PWM frequency for D3 & D11 ------------------- 1426 | //TCCR2B = TCCR2B & B11111000 | B00000001 tmr 2 divisor: 1 for PWM freq 31372.55 Hz 1427 | //TCCR2B = TCCR2B & B11111000 | B00000010 tmr 2 divisor: 8 for PWM freq 3921.16 Hz 1428 | //TCCR2B = TCCR2B & B11111000 | B00000011 tmr 2 divisor: 32 for PWM freq 980.39 Hz 1429 | //TCCR2B = TCCR2B & B11111000 | B00000100 *tmr 2 divisor: 64 for PWM freq 490.20 Hz 1430 | //TCCR2B = TCCR2B & B11111000 | B00000101 tmr 2 divisor: 128 for PWM freq 245.10 Hz 1431 | //TCCR2B = TCCR2B & B11111000 | B00000110 tmr 2 divisor: 256 for PWM freq 122.55 Hz 1432 | //TCCR2B = TCCR2B & B11111000 | B00000111 tmr 2 divisor: 1024 for PWM freq 30.64 Hz 1433 | // 1434 | void pwmdExpo( byte dtwoExpo) { 1435 | // ESP Default Frequ is 1024. Writing new Freq seems to cause exception 1436 | #if PROC_ESP 1437 | #endif // PROC_ESP 1438 | #if PROC_UNO 1439 | switch (dtwoExpo) { 1440 | case 0: 1441 | // Pscl: 1 Freq: 31372.55Hz 1442 | TCCR1B = TCCR1B & B11111000 | B00000001; 1443 | break; 1444 | case 3: 1445 | // Pscl: 8 Freq: 3921.16Hz 1446 | TCCR1B = TCCR1B & B11111000 | B00000010; 1447 | break; 1448 | case 8: 1449 | // Pscl: 256 Freq: 122.55Hz 1450 | TCCR1B = TCCR1B & B11111000 | B00000100; 1451 | break; 1452 | case 10: 1453 | // Pscl: 1024 Freq: 30.64Hz 1454 | TCCR1B = TCCR1B & B11111000 | B00000101; 1455 | break; 1456 | default: 1457 | // Pscl: 64 Freq: 490.20Hz 1458 | TCCR1B = TCCR1B & B11111000 | B00000011; 1459 | break; 1460 | } 1461 | #endif 1462 | } 1463 | 1464 | void pwmdSetF( double newFreq) { 1465 | if ( newFreq < 43.32) { 1466 | pwmdExpo( 10); 1467 | pwmdFreq = 31; 1468 | } else if ( newFreq < 173.20 ) { 1469 | pwmdExpo( 8); 1470 | pwmdFreq = 123; 1471 | } else if ( newFreq < 693.14 ) { 1472 | pwmdExpo( 6); 1473 | pwmdFreq = 490; 1474 | } else if ( newFreq < 5544.30 ) { 1475 | pwmdExpo( 3); 1476 | pwmdFreq = 3921; 1477 | } else { 1478 | pwmdExpo( 0); 1479 | pwmdFreq = 31372; 1480 | } 1481 | } 1482 | 1483 | void pwmdInit() { 1484 | // PROC either define pins, exponent 1485 | pwmdDuty = pwmdOutp = 0; 1486 | #if WITH_PWMD 1487 | pinMode( PWMD_OPIN, PWMD_MODE); 1488 | digitalWrite( PWMD_OPIN, 0); 1489 | pwmdSetF( PWMD_FREQ); 1490 | #endif 1491 | pwmdMark = millis() + PWMD_POLL_MSEC; 1492 | pwmdRctl &= ~RCTL_ATTN; 1493 | } 1494 | 1495 | // common eg Arti OT1 popcW 1496 | 1497 | // 1498 | int callPwmd( int iCall) { 1499 | // do not print here, may interrupt OT 1 logout 1500 | iCall = (iCall > 99 ) ? 100 : iCall; 1501 | iCall = (iCall < 0 ) ? 0 : iCall; 1502 | // 1503 | pidcRctl &= ~RCTL_AUTO; 1504 | pwmdRctl &= ~RCTL_AUTO; 1505 | pwmdRctl |= ( RCTL_MANU | RCTL_ATTN ); 1506 | // manual pwm will apply in pwmd loop; unset manual ramp ctrl 1507 | modeCode = manuChar; 1508 | rampCdpm = 0; 1509 | if ( iCall == 0) { 1510 | // Power off: Sense ambient ( fan htr pwr), temp setpt to meas ambient 1511 | setpTmpC = AMBI_TMPC; 1512 | pidcStop(); 1513 | } else { 1514 | stepSecs = 0; // User command: reset step timer 1515 | } 1516 | return iCall; 1517 | } 1518 | 1519 | int callRamp( int iCall) { 1520 | if ( userScal == fahrChar) { 1521 | iCall = int ( iCall * 5.00 / 9.00); 1522 | } 1523 | if (!isnan(*pidcSens)) { 1524 | baseTmpC = int(*pidcSens); 1525 | } else { 1526 | } 1527 | // R cmd here must force hold temp, use H command later 1528 | if (iCall > 0) holdTmpC = MAXI_TMPC - 20; 1529 | if (iCall < AMBI_TMPC) holdTmpC = AMBI_TMPC; 1530 | if (iCall == 0) { 1531 | // Selected ROC 0: Hold current temp 1532 | modeCode = holdChar; 1533 | if (!isnan(*pidcSens)) { 1534 | setpTmpC = int(*pidcSens); 1535 | } 1536 | } else { 1537 | // On R0 don't reset step timer 1538 | stepSecs = 0; 1539 | } 1540 | // seting ramp unsets manual PWM width 1541 | pidcRctl |= ( RCTL_AUTO | RCTL_RUNS); 1542 | pwmdRctl &= ~RCTL_MANU; 1543 | pwmdRctl |= RCTL_AUTO; 1544 | return (iCall); 1545 | } 1546 | 1547 | void callSetp(float fCall) { 1548 | // set desired temperatre degC 1549 | setpTmpC = fCall; 1550 | userDegs = int(fCall); 1551 | if ( userScal == fahrChar) { 1552 | setpTmpC = floatFtoC( setpTmpC); 1553 | } 1554 | if (setpTmpC > MAXI_TMPC) setpTmpC = MAXI_TMPC; 1555 | holdTmpC = setpTmpC; 1556 | rampCdpm = 0; // Setting target temp implies no ramp 1557 | stepSecs = 0; // User cmd: reset step timer 1558 | pidcRctl |= (RCTL_RUNS | RCTL_AUTO); 1559 | pwmdRctl &= ~RCTL_MANU; 1560 | pwmdRctl |= (RCTL_AUTO | RCTL_ATTN); // Attn to get one off diag logout 1561 | modeCode = setpChar; 1562 | } 1563 | 1564 | void pwmdLoop() { 1565 | if ( millis() < pwmdMark ) { 1566 | return; 1567 | } else { 1568 | pwmdMark += PWMD_POLL_MSEC; 1569 | // 1570 | if ( (pwmdRctl & RCTL_RUNS) == 0) { 1571 | if ( ( !NDIA_ARTI ) && ( bbrdRctl & RCTL_DIAG) ) { 1572 | Serial.println(F("# pwmdRctl==0")); 1573 | } 1574 | pwmdOutp = 0; 1575 | } else { 1576 | // last run control test has precedence 1577 | if ( pwmdRctl & RCTL_MANU) { 1578 | pwmdTarg = int ( 255.0 * userDuty / 100.0 ); 1579 | } 1580 | if ( pwmdRctl & RCTL_AUTO) { 1581 | pwmdTarg = int (pidcUn + 0.5 ); 1582 | } 1583 | pwmdOutp = pwmdTarg; 1584 | pwmdPcnt = byte ((100.0 * pwmdOutp / 255) +0.5); 1585 | #if WITH_PWMD 1586 | analogWrite( PWMD_OPIN, pwmdOutp); 1587 | #endif 1588 | } 1589 | } 1590 | } 1591 | 1592 | /// Rotary Enc Switch outputs binary 0000-1111 over 16 detent positions 1593 | // 1594 | int rotsValu() { 1595 | byte resp, tVal; 1596 | resp = 0; 1597 | #if PINS_ROTS 1598 | if ( digitalRead(ROTS_BIT3) == LOW ) resp = 8; 1599 | if ( digitalRead(ROTS_BIT2) == LOW ) resp += 4; 1600 | if ( digitalRead(ROTS_BIT1) == LOW ) resp += 2; 1601 | if ( digitalRead(ROTS_BIT0) == LOW ) resp += 1; 1602 | #endif 1603 | return(resp); 1604 | } 1605 | 1606 | void rotsInit() { 1607 | // Set pins to weak pullup 1608 | #if PINS_ROTS 1609 | pinMode( ROTS_BIT3, INPUT_PULLUP); 1610 | pinMode( ROTS_BIT2, INPUT_PULLUP); 1611 | pinMode( ROTS_BIT1, INPUT_PULLUP); 1612 | pinMode( ROTS_BIT0, INPUT_PULLUP); 1613 | #endif 1614 | rotsMark = millis() + ROTS_POLL_MSEC; 1615 | } 1616 | 1617 | void rotsLoop() { 1618 | if (millis() < rotsMark) return; else { 1619 | rotsMark += ROTS_POLL_MSEC; 1620 | // Only process consecutive new steady value 1621 | byte rotsTemp = rotsValu(); 1622 | if ( rotsTemp != rotsNewb) { 1623 | // unsteady value: save as Newb but don't change Curr 1624 | rotsNewb = rotsTemp; 1625 | } else { 1626 | // steady value for two polls 1627 | if ( rotsCurr != rotsNewb) { 1628 | // steady value changed from previous 1629 | rotsCurr = rotsNewb; 1630 | if ( ( !NDIA_ARTI ) && ( bbrdRctl & RCTL_DIAG) ) { 1631 | Serial.print(F("# Rots:")); Serial.println(rotsCurr); 1632 | } 1633 | // For each detent position insert into command command line: ClkWise:Rate CCW:Power settings 1634 | switch( rotsCurr) { 1635 | case 0: 1636 | stepNmbr = 0; 1637 | userCmdl = "W0"; 1638 | break; 1639 | case 1: 1640 | stepNmbr = 1; 1641 | userCmdl = "R3"; 1642 | break; 1643 | case 2: 1644 | stepNmbr = 2; 1645 | userCmdl = "R6"; 1646 | break; 1647 | case 3: 1648 | stepNmbr = 3; 1649 | userCmdl = "R10"; 1650 | break; 1651 | case 4: 1652 | stepNmbr = 4; 1653 | userCmdl = "R12"; 1654 | break; 1655 | case 5: 1656 | stepNmbr = 5; 1657 | userCmdl = "R15"; 1658 | break; 1659 | case 6: 1660 | stepNmbr = 6; 1661 | userCmdl = "R20"; 1662 | break; 1663 | case 7: 1664 | stepNmbr = 7; 1665 | userCmdl = "R30"; 1666 | break; 1667 | case 8: 1668 | stepNmbr = 8; 1669 | userCmdl = "R0"; 1670 | break; 1671 | case 9: 1672 | stepNmbr = 9; 1673 | userCmdl = "W100"; 1674 | break; 1675 | case 10: 1676 | stepNmbr = 10; 1677 | userCmdl = "W95"; 1678 | break; 1679 | case 11: 1680 | stepNmbr = 11; 1681 | userCmdl = "W90"; 1682 | break; 1683 | case 12: 1684 | stepNmbr = 12; 1685 | userCmdl = "W85"; 1686 | break; 1687 | case 13: 1688 | stepNmbr = 13; 1689 | userCmdl = "W80"; 1690 | break; 1691 | case 14: 1692 | stepNmbr = 14; 1693 | userCmdl = "W70"; 1694 | break; 1695 | case 15: 1696 | stepNmbr = 15; 1697 | userCmdl = "W60"; 1698 | break; 1699 | } 1700 | userRctl |= RCTL_ATTN; 1701 | } 1702 | } 1703 | } 1704 | } 1705 | 1706 | /// Channel - sensor allocation 1707 | 1708 | // return addx of sensor's output vble according to index number 1709 | float *sensAddx ( int tSensNumb) { 1710 | switch (tSensNumb) { 1711 | case 2: 1712 | return ( &sns2TmpC); 1713 | break; 1714 | default : 1715 | return ( &sns1TmpC); 1716 | break; 1717 | } 1718 | } 1719 | 1720 | // 1721 | void chanSens( int tChanNumb, char tSensChar ) { 1722 | int sensNumb; 1723 | if ((tSensChar > '0') && (tSensChar < '5')) { 1724 | sensNumb = int(tSensChar - '0'); 1725 | switch (tChanNumb) { 1726 | case 2: 1727 | chnBTmpC = sensAddx( sensNumb); 1728 | break; 1729 | case 1: 1730 | chnATmpC = sensAddx( sensNumb); 1731 | break; 1732 | } 1733 | } 1734 | } 1735 | 1736 | /// Thermocouple 1737 | // 1738 | void tcplInit() { 1739 | // stabilize wait .. lcds banner is 1000mA anyway 1740 | // delay(500); 1741 | // 1742 | tcplMark = millis() + TCPL_POLL_MSEC; 1743 | #if WITH_TCPL_2 1744 | tcp2Mark = millis() + TCPL_POLL_MSEC; 1745 | #endif // WITH_TCPL_2 1746 | vtcpMark = millis() + VTCP_POLL_MSEC; 1747 | sns1TmpC = sns2TmpC = AMBI_TMPC; 1748 | } 1749 | 1750 | // Thermocouple 1751 | // MAX6675 lib has low funtion, same as UNO-MAX31855 library; ESP-MAX31855 has more function 1752 | void tcplLoop() { 1753 | double tcplTmpC; 1754 | byte tResp = 0; // preload response with non-error code 1755 | if ( millis() < tcplMark) return; else { 1756 | tcplMark += TCPL_POLL_MSEC; 1757 | // 1758 | if (tcplRctl== 0) { 1759 | // Rctl == 0 Shutdown 1760 | sns1TmpC = AMBI_TMPC; 1761 | } else { 1762 | // Read thermocouple 1763 | tcplTmpC = tcpl.readCelsius(); 1764 | // Serial.print("# tcpl-1-6675||UNO readCelsius:"); Serial.print(tcplTmpC); 1765 | // Error condition ESP:tResp <> 0 (UNO | MAX6675): isNan Temperature 1766 | if (isnan(tcplTmpC)) { 1767 | // Bad reading: use avges 1768 | sns1TmpC = float( histMav0 / SCAL_FACT ); 1769 | tResp = tcpl.readError(); 1770 | } 1771 | if ( tResp != 0 ) { 1772 | // tResp, extended status available only for ESP-MAX31855 1773 | #if WITH_LCD 1774 | lcd.clear(); 1775 | lcd.home (); 1776 | lcd.print(F("thermoCouple : ")); 1777 | lcd.setCursor ( 0, 1 ); 1778 | #endif 1779 | if ( !( bbrdRctl & RCTL_ARTI ) && ( bbrdRctl & RCTL_DIAG) ) { 1780 | Serial.print(F("# tcpl sns: ")); 1781 | } 1782 | switch ( tResp ) { 1783 | case 0: 1784 | #if WITH_LCD 1785 | lcd.println("STATUS_OK "); 1786 | #endif 1787 | if ( 0 & !( bbrdRctl & RCTL_ARTI ) && ( bbrdRctl & RCTL_DIAG) ) { 1788 | Serial.println(F("OK")); 1789 | } 1790 | break; 1791 | case 1: 1792 | #if WITH_LCD 1793 | lcd.println("Error - Open-Cct"); 1794 | #endif 1795 | if ( 0 & !( bbrdRctl & RCTL_ARTI ) && ( bbrdRctl & RCTL_DIAG) ) { 1796 | Serial.println(F("OpnCct")); 1797 | } 1798 | break; 1799 | case 2: 1800 | #if WITH_LCD 1801 | lcd.println("Error - Shrt-Gnd"); 1802 | #endif 1803 | if ( 0 & !( bbrdRctl & RCTL_ARTI ) && ( bbrdRctl & RCTL_DIAG) ) { 1804 | Serial.println(F("Sh-Gnd")); 1805 | } 1806 | break; 1807 | case 4: 1808 | #if WITH_LCD 1809 | lcd.println("Error - Shrt-Vcc"); 1810 | #endif 1811 | if (0 & !( bbrdRctl & RCTL_ARTI ) && ( bbrdRctl & RCTL_DIAG) ) { 1812 | Serial.println(F("Sh-Vcc")); 1813 | } 1814 | break; 1815 | default: 1816 | #if WITH_LCD 1817 | lcd.println("Error - NReadEtc"); 1818 | #endif 1819 | if ( 0 & !( bbrdRctl & RCTL_ARTI ) && ( bbrdRctl & RCTL_DIAG) ) { 1820 | Serial.println(F("FailNOREAD")); 1821 | } 1822 | break; 1823 | } 1824 | #if WITH_LCD 1825 | delay (500); 1826 | lcd.clear(); 1827 | #endif 1828 | // Bad reading: use avges 1829 | sns1TmpC = float( setpTmpC / 2.00 + dataSet1[0] / 4.00 + dataSet1[1] / 4.00 ); 1830 | } // End tResp != 0 extended status 1831 | else { 1832 | // Valid reading: update sensed temp 1833 | sns1TmpC = float( tcplTmpC); 1834 | if (sns1TmpC > MAXI_TMPC) { 1835 | //pidcStop(); 1836 | Serial.println("# Max TmpC Exceeded Sns1: PID Stop") ; 1837 | } 1838 | } 1839 | } // End tcplRctl != 0 1840 | } //End tcpl poll time 1841 | } // End tcplLoop 1842 | 1843 | #if WITH_TCPL_2 1844 | // Thermocouple 2 1845 | // MAX6675 lib has low funtion, same as UNO-MAX31855 library; ESP-MAX31855 has more function 1846 | void tcp2Loop() { 1847 | double tcp2TmpC; 1848 | byte tResp = 0; // preload response with non-error code 1849 | if ( millis() < tcp2Mark) return; else { 1850 | tcp2Mark += TCPL_POLL_MSEC; 1851 | // 1852 | if (tcplRctl== 0) { 1853 | // Rctl == 0 Shutdown 1854 | sns2TmpC = AMBI_TMPC; 1855 | } else { 1856 | // Read thermocouple 1857 | tcp2TmpC = tcp2.readCelsius(); 1858 | // Serial.print("# tcpl-2-6675||UNO readCelsius:"); Serial.print(tcp2TmpC); 1859 | // Error condition ESP:tResp <> 0 (UNO | MAX6675): isNan Temperature 1860 | if (isnan(tcp2TmpC)) { 1861 | // Bad reading: don't update sensor temperature 1862 | //sns2TmpC = float( setpTmpC / 2.00 + dataSet1[0] / 4.00 + dataSet1[1] / 4.00 ); 1863 | tResp = tcp2.readError(); 1864 | } 1865 | if ( tResp != 0 ) { 1866 | // tResp, extended status available only for ESP-MAX31855 1867 | #if WITH_LCD 1868 | lcd.clear(); 1869 | lcd.home (); 1870 | lcd.print(F("thermoCouple : ")); 1871 | lcd.setCursor ( 0, 1 ); 1872 | #endif 1873 | if ( !( bbrdRctl & RCTL_ARTI ) && ( bbrdRctl & RCTL_DIAG) ) { 1874 | Serial.print(F("# tcp2 sns: ")); 1875 | } 1876 | switch ( tResp ) { 1877 | case 0: 1878 | #if WITH_LCD 1879 | lcd.println("STATUS_OK "); 1880 | #endif 1881 | if ( 0 & !( bbrdRctl & RCTL_ARTI ) && ( bbrdRctl & RCTL_DIAG) ) { 1882 | Serial.println(F("OK")); 1883 | } 1884 | break; 1885 | case 1: 1886 | #if WITH_LCD 1887 | lcd.println("Error - Open-Cct"); 1888 | #endif 1889 | if ( 0 & !( bbrdRctl & RCTL_ARTI ) && ( bbrdRctl & RCTL_DIAG) ) { 1890 | Serial.println(F("Open-Cct")); 1891 | } 1892 | break; 1893 | case 2: 1894 | #if WITH_LCD 1895 | lcd.println("Error - Shrt-Gnd"); 1896 | #endif 1897 | if ( 0 & !( bbrdRctl & RCTL_ARTI ) && ( bbrdRctl & RCTL_DIAG) ) { 1898 | Serial.println(F("Shrt-Gnd")); 1899 | } 1900 | break; 1901 | case 4: 1902 | #if WITH_LCD 1903 | lcd.println("Error - Shrt-Vcc"); 1904 | #endif 1905 | if (0 & !( bbrdRctl & RCTL_ARTI ) && ( bbrdRctl & RCTL_DIAG) ) { 1906 | Serial.println(F("Shrt-Vcc")); 1907 | } 1908 | break; 1909 | default: 1910 | #if WITH_LCD 1911 | lcd.println("Error - NReadEtc"); 1912 | #endif 1913 | if ( 0 & !( bbrdRctl & RCTL_ARTI ) && ( bbrdRctl & RCTL_DIAG) ) { 1914 | Serial.println(F("FailCase-NOREAD")); 1915 | } 1916 | break; 1917 | } 1918 | #if WITH_LCD 1919 | delay (500); 1920 | lcd.clear(); 1921 | #endif 1922 | // Bad reading: don't update 1923 | // sns2TmpC = float( setpTmpC / 2.00 + dataSet1[0] / 4.00 + dataSet1[1] / 4.00 ); 1924 | } // End tResp != 0 extended status 1925 | else { 1926 | // Valid reading: update sensed temp 1927 | sns2TmpC = float( tcp2TmpC); 1928 | if (sns2TmpC >= MAXI_TMPC) { 1929 | pidcStop(); 1930 | Serial.println("# Max Temperature on Sensor 2: PID Stopped") ; 1931 | } 1932 | } 1933 | } // End tcplRctl != 0 1934 | } //End tcp2 poll time 1935 | } // End tcpl2oop 1936 | #endif 1937 | 1938 | /// 1939 | // Virt grams added for PID Charge 1940 | int userChge( int iGms) { 1941 | iGms = (iGms < 10 ) ? 10 : iGms; 1942 | // Bring virt temp down 1943 | if (vTmpDegC > AMBI_TMPC) { 1944 | vTmpDegC -= (iGms / ( vChgGrms + iGms )) * ( vTmpDegC - float(AMBI_TMPC)); 1945 | } 1946 | return( iGms); 1947 | } 1948 | 1949 | void virtTcplInit() { 1950 | vPrvMSec = millis(); 1951 | } 1952 | 1953 | // virtual tcpl for debug 1954 | void virtTcplLoop() { 1955 | float vHtrLoss, vHtrCals; 1956 | int vMSecSmpl; 1957 | vMSecSmpl = millis() - vPrvMSec; 1958 | if (vMSecSmpl < VTCP_POLL_MSEC) { 1959 | return; 1960 | } else { 1961 | vPrvMSec = millis(); 1962 | vHtrLoss = (float(vTmpDegC) - float(IDLE_TMPC)) / (vTmpMaxC - float(IDLE_TMPC)); 1963 | vHtrCals = ( vMSecSmpl / 1000.0) * (vHtrWatt * (pwmdPcnt / 100.0 - vHtrLoss ) / 4.2); 1964 | vTmpDegC += vHtrCals / ( vChgGrms * vChgSpht ); 1965 | sns1TmpC = IFR( vTmpDegC + (random(1) ) ); // random variation 1966 | #if WITH_TCPL_2 1967 | sns2TmpC = sns1TmpC + 22.22 + (random(2) ); // fake second sensor for debug 1968 | #endif 1969 | } 1970 | } 1971 | 1972 | // 1973 | /// User Interface 1974 | // 1975 | void userInit() { 1976 | profNmbr = stepNmbr = 0; 1977 | rampCdpm = 0; 1978 | userScal = celsChar; 1979 | baseTmpC = holdTmpC = userDegs = int(AMBI_TMPC); 1980 | userDuty = 0; 1981 | // Normal Artisan CHAN assignments CHNA,B,C,D:Sensor2, (Sensor1), (None), (None) 1982 | #if WITH_TCPL_2 1983 | chnATmpC = &sns2TmpC; 1984 | #else 1985 | chnATmpC = &setpTmpC; 1986 | #endif 1987 | chnBTmpC = &sns1TmpC; 1988 | pidcSens = &sns1TmpC; 1989 | // 1990 | //userRctl |= RCTL_ATTN; 1991 | chgeSeen = tpntSeen = 0; 1992 | chgeArmd = tpntArmd = 1; 1993 | } 1994 | 1995 | void userLoop() { 1996 | float fromEprm, tempFltA; 1997 | int tempIntA, tempIntB; 1998 | // test for when chars arriving on serial port, set ATTN 1999 | //if ( Serial.available() && Serial.find('\n') ) { 2000 | if ( Serial.available() ) { 2001 | // no wait for entire message .. 115200bps 14 char ~ 1mSec there's a newline 2002 | // delay(100); 2003 | // read from buffer until first linefeed, remaining chars staay in hdwre serial buffer 2004 | userCmdl = Serial.readStringUntil('\n'); 2005 | //userCmdl[userCmdl.length()-1] = '\0'; 2006 | userRctl |= RCTL_ATTN; 2007 | } 2008 | if (userRctl & RCTL_ATTN) { 2009 | if ( (( !NDIA_ARTI )) && ( bbrdRctl & RCTL_DIAG) ) { 2010 | Serial.print (F("# userCmdl: ")); Serial.println(userCmdl); 2011 | } 2012 | #if WIFI_MQTT 2013 | // echo back network originated cmds 2014 | wrapPubl( echoTops, userCmdl.c_str(), userCmdl.length()); 2015 | #endif 2016 | if ( bbrdRctl & RCTL_ARTI ) { 2017 | // Artisan Mode only cmds 2018 | // CHA(N);S1S2S3S4 cmd: Assign inputs ID into message sequence Sx; Resp '#' ack 2019 | if ((userCmdl[0] == 'C') && (userCmdl[1] == 'H') && (userCmdl[2] == 'A')) { 2020 | chanSens( 1, userCmdl.charAt(5)); 2021 | chanSens( 2, userCmdl.charAt(6)); 2022 | chanSens( 3, userCmdl.charAt(7)); 2023 | chanSens( 4, userCmdl.charAt(8)); 2024 | // Send ack response 2025 | if ( !( bbrdRctl & RCTL_ARTI ) ) { 2026 | Serial.println(F("# CHAN")); 2027 | } 2028 | } 2029 | // FILT;L1;L2;L3;L4 cmd: Assign digital filter lever Lx to Input n; Resp '#' ack 2030 | if ((userCmdl[0] == 'F') && (userCmdl[1] == 'I') && (userCmdl[2] == 'L') && (userCmdl[3] == 'T')) { 2031 | // Send ack response 2032 | if ( !( bbrdRctl & RCTL_ARTI ) ) { 2033 | Serial.println(F("# FILT")); 2034 | } 2035 | } 2036 | // IO3 cmd 2037 | if ((userCmdl[0] == 'I') && (userCmdl[1] == 'O') && (userCmdl[2] == '3')) { 2038 | userAIO3 = (userCmdl.substring(4)).toInt(); 2039 | if (userAIO3 > 99) userAIO3 = 100; 2040 | // Send ack response 2041 | if ( !( bbrdRctl & RCTL_ARTI ) ) { 2042 | Serial.println(F("# IO3")); 2043 | } 2044 | } 2045 | // OT(U/D)1/2 Commands 2046 | if ((userCmdl[0] == 'O') && (userCmdl[1] == 'T')) { 2047 | if ( ( !NDIA_ARTI ) && (bbrdRctl & RCTL_DIAG) ) { 2048 | Serial.print(F("# OT")); 2049 | } 2050 | if (userCmdl[2] == '1') { 2051 | if ( ( !NDIA_ARTI ) && (bbrdRctl & RCTL_DIAG) ) { 2052 | Serial.print(F("1: ")); 2053 | } 2054 | if ((userCmdl[4] == 'U')) { 2055 | userDuty = userAOT1 + DUTY_STEP; 2056 | } else if ((userCmdl[4] == 'D')) { 2057 | userDuty = userAOT1 - DUTY_STEP; 2058 | } else { 2059 | userDuty = callPwmd( userCmdl.substring(4).toInt()); 2060 | } 2061 | userAOT1 = userDuty; 2062 | // Send ack response 2063 | if ( ( !NDIA_ARTI ) && (bbrdRctl & RCTL_DIAG) ) { 2064 | Serial.print(userDuty); 2065 | Serial.println(F("%")); 2066 | } 2067 | } 2068 | if (userCmdl[2] == '2') { 2069 | if ((userCmdl[4] == 'U')) { 2070 | userAOT2 = userAOT2 + DUTY_STEP; 2071 | } else if ((userCmdl[4] == 'D')) { 2072 | userAOT2 = userAOT2 - DUTY_STEP; 2073 | } else { 2074 | userAOT2 = (userCmdl.substring(4)).toInt(); 2075 | } 2076 | userAOT2 = (userAOT2 > 99) ? 100 : userAOT2; 2077 | userAOT2 = (userAOT2 < 1) ? 0 : userAOT2; 2078 | // Send ack response 2079 | if ( !( bbrdRctl & RCTL_ARTI ) ) { 2080 | Serial.print(F("2: ")); 2081 | Serial.println(userAOT2); 2082 | } 2083 | } 2084 | } 2085 | // PIDxxxx cmds 2086 | if ((userCmdl[0] == 'P') && (userCmdl[1] == 'I') && (userCmdl[2] == 'D')) { 2087 | // Start ack response 2088 | if ( !( bbrdRctl & RCTL_ARTI ) ) { 2089 | Serial.print(F("# PID ")); 2090 | } 2091 | // PID;CHG;GRMS cmd: PID <= New Cycle Time mSec 2092 | if ((userCmdl[4] == 'C') && (userCmdl[5] == 'H') && (userCmdl[6] == 'G')) { 2093 | // cont ack response 2094 | if ( !( bbrdRctl & RCTL_ARTI ) ) { 2095 | Serial.print(F("CHG: ")); 2096 | } 2097 | vChgGrms = userChge((userCmdl.substring(8)).toInt()); 2098 | } 2099 | // PID;CT;mSec cmd: PID <= New Cycle Time mSec 2100 | if ((userCmdl[4] == 'C') && (userCmdl[5] == 'T')) { 2101 | // cont ack response 2102 | if ( !( bbrdRctl & RCTL_ARTI ) ) { 2103 | Serial.print(F("CT: ")); 2104 | } 2105 | tempFltA = (userCmdl.substring(7)).toFloat(); 2106 | if (!isnan(tempFltA)) { 2107 | pidcPoll = tempFltA; 2108 | if ( !( bbrdRctl & RCTL_ARTI ) ) { 2109 | Serial.print(F("mSec poll: ")); Serial.print(pidcPoll); 2110 | } 2111 | } 2112 | if ( !( bbrdRctl & RCTL_ARTI ) ) { 2113 | Serial.println(F("")); 2114 | } 2115 | } 2116 | // PID;OFF cmd: PID <= Stop PID running, zero outputs 2117 | if ((userCmdl[4] == 'O') && (userCmdl[5] == 'F') && (userCmdl[6] == 'F')) { 2118 | // Artisan <=> TC4 PID;OFF cmd: PWM, PID Run Ctrl Off, PID reset internals 2119 | pidcRset(); // Switch off PID Rctl after this in case pidcRset() doesn't 2120 | pidcStop(); 2121 | if ( !( bbrdRctl & RCTL_ARTI ) ) { 2122 | Serial.println(F("OFF")); 2123 | } 2124 | } 2125 | // PID;ON cmd: PID <= Set PID run control & auto 2126 | if ((userCmdl[4] == 'O') && (userCmdl[5] == 'N')) { 2127 | pidcStrt(); 2128 | if ( !( bbrdRctl & RCTL_ARTI ) ) { 2129 | Serial.println(F("ON")); 2130 | } 2131 | } 2132 | // PID;PV;s cmd: PID <= Select Process Variable ( Input) to Sensor Nbr 's' 2133 | if ((userCmdl[4] == 'P') && (userCmdl[5] == 'V')) { 2134 | // cont ack response 2135 | if ( !( bbrdRctl & RCTL_ARTI ) ) { 2136 | Serial.print(F("PV Inp Snsr: ")); 2137 | } 2138 | tempIntA = (userCmdl.substring(7,8)).toInt(); 2139 | if (( tempIntA > 0 ) && (tempIntA <5 )) { 2140 | pidcSens = sensAddx( tempIntA); 2141 | if ( !( bbrdRctl & RCTL_ARTI ) ) { 2142 | Serial.println( tempIntA); 2143 | } 2144 | } 2145 | } 2146 | // PID;RESET 2147 | if ((userCmdl[4] == 'R') && (userCmdl[5] == 'E') && (userCmdl[6] == 'S')) { 2148 | // Artisan <=> TC4 PID;RESET cmd: PID reset internals Setpoint <= Ambient 2149 | pidcRset(); 2150 | if ( !( bbrdRctl & RCTL_ARTI ) ) { 2151 | Serial.println(F("RESET")); 2152 | } 2153 | } 2154 | // PID;START 2155 | if ( ((userCmdl[4] == 'S') && (userCmdl[5] == 'T') && (userCmdl[6] == 'A')) \ 2156 | ||((userCmdl[4] == 'G') && (userCmdl[5] == 'O') ) ){ 2157 | pidcStrt(); 2158 | if ( !( bbrdRctl & RCTL_ARTI ) ) { 2159 | Serial.println(F("START")); 2160 | } 2161 | } 2162 | // PID;STOP 2163 | if ((userCmdl[4] == 'S') && (userCmdl[5] == 'T') && (userCmdl[6] == 'O')) { 2164 | pidcStop(); 2165 | if ( !( bbrdRctl & RCTL_ARTI ) ) { 2166 | Serial.println(F("STOP")); 2167 | } 2168 | } 2169 | // PID;SV 2170 | if ((userCmdl[4] == 'S') && (userCmdl[5] == 'V')) { 2171 | // call common SV - S 2172 | callSetp(userCmdl.substring(7).toInt()); 2173 | // Send ack response 2174 | if ( !( bbrdRctl & RCTL_ARTI ) ) { 2175 | Serial.print (F("SV: ")); Serial.println(userDegs); 2176 | } 2177 | } 2178 | // PID;SYNC cmd: PID reset internals Setpoint <= pidcSens temperature 2179 | if ((userCmdl[4] == 'S') && (userCmdl[5] == 'Y') && (userCmdl[6] == 'N')) { 2180 | pidcSync(); 2181 | if ( !( bbrdRctl & RCTL_ARTI ) ) { 2182 | Serial.println(F("SYNC")); 2183 | } 2184 | } 2185 | // PID;T;Kp;Ki;Kd tuning values 2186 | if ((userCmdl[3] == ';') && (userCmdl[4] == 'T')) { 2187 | tempIntA = userCmdl.indexOf( ';' , 6); // find third ';' 2188 | if (int(tempIntA) < userCmdl.length()) { 2189 | pidcKp = (userCmdl.substring(6, tempIntA)).toFloat(); 2190 | } 2191 | tempIntB = userCmdl.indexOf( ';' , (tempIntA + 1)); // find fourth ';' 2192 | if (tempIntB < userCmdl.length()) { 2193 | // Artisan's Ki converted to Ti by taking reciprocal 2194 | tempFltA = (userCmdl.substring((tempIntA + 1), tempIntB)).toFloat(); 2195 | if ( tempFltA <= 0.00 ) { 2196 | pidcTi = 99999.99; 2197 | } else { 2198 | pidcTi = ( 1.00 / tempFltA); 2199 | } 2200 | } 2201 | tempFltA = (userCmdl.substring(tempIntB +1)).toFloat(); 2202 | if (!isnan(tempFltA)) pidcTd = tempFltA; 2203 | // Send ack response 2204 | if ( !( bbrdRctl & RCTL_ARTI ) ) { 2205 | Serial.print(F("sets new Kp, Ti, Kd: ")); 2206 | Serial.print( pidcKp); Serial.print(F(", ")); 2207 | Serial.print( pidcTi); Serial.print(F(", ")); 2208 | Serial.println( pidcTd); 2209 | } 2210 | } 2211 | } 2212 | // POPC Cmd : Escape autoArti respond as popC 2213 | if ( ((userCmdl[0] == 'P') && (userCmdl[1] == 'O') && (userCmdl[2] == 'P') && (userCmdl[3] == 'C')) \ 2214 | || ((userCmdl[0] == 'p') && (userCmdl[1] == 'o') && (userCmdl[2] == 'p') && (userCmdl[3] == 'c')) ) { 2215 | bbrdRctl &= ~RCTL_ARTI; 2216 | Serial.println(F("# PopC Speak")); 2217 | } 2218 | // READ cmd: 2219 | if ((userCmdl[0] == 'R') && (userCmdl[1] == 'E') && (userCmdl[2] == 'A')) { 2220 | respArti(); // Fill response with latest 2221 | for ( tempIntA = 0; tempIntA < (sizeof(artiResp)-1); ++tempIntA ) { 2222 | Serial.print(artiResp[tempIntA]); 2223 | } 2224 | //Au08 Serial.println('\r'); 2225 | // don't Send ack response 2226 | // Serial.println(F("#rea")); 2227 | } 2228 | // UNIT(S);F/C cmd: 2229 | if ((userCmdl[0] == 'U') && (userCmdl[1] == 'N') && (userCmdl[2] == 'I') && (userCmdl[3] == 'T')) { 2230 | if ( !( bbrdRctl & RCTL_ARTI ) ) { 2231 | Serial.print(F("# UNITS: degs")); 2232 | } 2233 | // 'units' cmd, set user temperature scale 2234 | if (userCmdl[5] == 'C') { 2235 | userScal = celsChar; 2236 | if ( !( bbrdRctl & RCTL_ARTI ) ) { 2237 | Serial.println(F("C")); 2238 | } 2239 | } 2240 | if (userCmdl[5] == 'F') { 2241 | userScal = fahrChar; 2242 | if ( !( bbrdRctl & RCTL_ARTI ) ) { 2243 | Serial.println(F("F")); 2244 | } 2245 | } 2246 | } 2247 | } 2248 | // non-Artisan 'norm' speak cmds 2249 | // if (!( bbrdRctl & RCTL_ARTI )) { 2250 | // inspect in either mode 2251 | if (1){ 2252 | // a/A Toggle Artisan format serial interface 2253 | if ((userCmdl[0] == 'A') || (userCmdl[0] == 'a')) { 2254 | // Toggle Artisan Interface 2255 | if ( bbrdRctl & RCTL_ARTI ) { 2256 | bbrdRctl &= ~RCTL_ARTI; 2257 | Serial.println(F("# Serial <=> Console")); 2258 | } else { 2259 | bbrdRctl |= RCTL_ARTI; 2260 | Serial.println(F("# Serial <=> Artisan")); 2261 | } 2262 | } 2263 | // Artisan CHAN cmd For reset flip to Artisan mode 2264 | if ((userCmdl[0] == 'C') && (userCmdl[1] == 'H') && (userCmdl[2] == 'A') && (userCmdl[3] == 'N')) { 2265 | bbrdRctl |= RCTL_ARTI; 2266 | // 'chan' cmd, respond '#' 2267 | Serial.println(F("#")); 2268 | } 2269 | // c/C set Centigrade units 2270 | if (((userCmdl[0] == 'C') || (userCmdl[0] == 'c')) && (userCmdl[1] != 'H')) { 2271 | userScal = celsChar; 2272 | } 2273 | // d/D Toggle Diagnostic Flag 2274 | if ((userCmdl[0] == 'D') || (userCmdl[0] == 'd')) { 2275 | if ( bbrdRctl & RCTL_DIAG ) { 2276 | Serial.println(F("# Diagnostics Mode is InActive")); 2277 | bbrdRctl &= ~RCTL_DIAG; 2278 | } else { 2279 | bbrdRctl |= RCTL_DIAG; 2280 | Serial.println(F("# Diagnostics Mode is Active")); 2281 | } 2282 | } 2283 | // e Readback EEPROM values 2284 | if (userCmdl[0] == 'e') { 2285 | eprmInfo(); 2286 | } 2287 | // E Write EEPROM values from PID current parameters 2288 | if (userCmdl[0] == 'E') { 2289 | EEPROM.get(EADX_KP, fromEprm); 2290 | if ( pidcKp != fromEprm ) { 2291 | EEPROM.put( EADX_KP, pidcKp); 2292 | } 2293 | EEPROM.get(EADX_TI, fromEprm); 2294 | if ( pidcTi != fromEprm ) { 2295 | EEPROM.put( EADX_TI, pidcTi); 2296 | } 2297 | EEPROM.get(EADX_TD, fromEprm); 2298 | if ( pidcTd != fromEprm ) { 2299 | EEPROM.put( EADX_TD, pidcTd); 2300 | } 2301 | EEPROM.get(EADX_CT, fromEprm); 2302 | if ( pidcPoll != int(fromEprm)) { 2303 | EEPROM.put( EADX_CT, float(pidcPoll)); 2304 | } 2305 | EEPROM.get(EADX_BE, fromEprm); 2306 | if ( pidcBeta != fromEprm ) { 2307 | EEPROM.put( EADX_BE, pidcBeta); 2308 | } 2309 | EEPROM.get(EADX_GA, fromEprm); 2310 | if ( pidcGamma != fromEprm ) { 2311 | EEPROM.put( EADX_GA, pidcGamma); 2312 | } 2313 | EEPROM.get(EADX_KA, fromEprm); 2314 | if ( pidcKappa != fromEprm ) { 2315 | EEPROM.put( EADX_KA, pidcKappa); 2316 | } 2317 | eprmInfo(); 2318 | } 2319 | // f/F Set fahrenheit units 2320 | if (((userCmdl[0] == 'F') || (userCmdl[0] == 'f')) && (userCmdl[1] != 'I')) { 2321 | userScal = fahrChar; 2322 | } 2323 | // g set gateTmpC 2324 | if (userCmdl[0] == 'g') { 2325 | userDegs = (userCmdl.substring(1)).toInt(); 2326 | if ( userScal == fahrChar) { 2327 | userDegs = intgFtoC( userDegs); 2328 | } 2329 | gateTmpC = userDegs; 2330 | if (gateTmpC > MAXI_TMPC) gateTmpC = MAXI_TMPC; 2331 | } 2332 | 2333 | // G set gatgoal ROC to sensor ROC 2334 | if (userCmdl[0] == 'G') { 2335 | rampCdpm = callRamp(int(histRoc1)); 2336 | } 2337 | 2338 | 2339 | // h/H set desired hold temperatre deg 2340 | if ((userCmdl[0] == 'H') || (userCmdl[0] == 'h')) { 2341 | userDegs = (userCmdl.substring(1)).toInt(); 2342 | if ( userScal == fahrChar) { 2343 | holdTmpC = intgFtoC( userDegs); 2344 | } else { 2345 | holdTmpC = userDegs; 2346 | } 2347 | if (holdTmpC > MAXI_TMPC) holdTmpC = MAXI_TMPC; 2348 | pwmdRctl &= ~RCTL_MANU; 2349 | pwmdRctl |= RCTL_AUTO; 2350 | } 2351 | // I put pid Ti term 2352 | if (userCmdl[0] == 'I') { 2353 | pidcTi = (userCmdl.substring(1)).toFloat(); 2354 | pidcInfo(); 2355 | } 2356 | // J put pid Td term 2357 | if (userCmdl[0] == 'J') { 2358 | pidcTd = (userCmdl.substring(1)).toFloat(); 2359 | pidcInfo(); 2360 | } 2361 | if ((userCmdl[0] == 'L') || (userCmdl[0] == 'l')) { 2362 | // Toggle logging, preface with banner lines when logging started 2363 | if ( bbrdRctl & RCTL_INFO ) { 2364 | // Artisan csv Logging: send two header lines with tab chars 2365 | // prefix with version 2366 | Serial.println(versChrs); 2367 | //eprmInfo(); 2368 | //pidcInfo(); 2369 | Serial.println(csvlHdr1); 2370 | Serial.println(csvlHdr2); 2371 | // Switch On Artisan csv Logging. TotalTime, StepTime must start at 0. 2372 | bbrdRctl &= ~RCTL_INFO; 2373 | stepSecs = totlSecs = 0; 2374 | } else { 2375 | bbrdRctl |= RCTL_INFO; 2376 | } 2377 | } 2378 | if (userCmdl[0] == 'M') { 2379 | vChgGrms = userChge((userCmdl.substring(1)).toInt()); 2380 | } 2381 | // m cmd TBD stored profile from memory 2382 | if (userCmdl[0] == 'm'){ 2383 | profNmbr = (userCmdl.substring(1)).toInt(); 2384 | if ( ( !NDIA_ARTI ) && ( bbrdRctl & RCTL_DIAG) ) { 2385 | } 2386 | stepSecs = 0; 2387 | } 2388 | // N PID oN 2389 | if ((userCmdl[0] == 'N')) { 2390 | pidcRctl ^= RCTL_RUNS; 2391 | pidcStrt(); 2392 | } 2393 | // O PID Off 2394 | if ((userCmdl[0] == 'O') && !(userCmdl[1] == 'T') ) { 2395 | pidcRctl &= ~RCTL_RUNS; 2396 | pidcStop(); 2397 | } 2398 | // P put pid Kp term 2399 | if (((userCmdl[0] == 'p') || (userCmdl[0] == 'P')) && \ 2400 | (userCmdl[1] != 'I') && (userCmdl[1] != 'i') && (userCmdl[1] != 'O') && (userCmdl[1] != 'o')) { 2401 | pidcKp = (userCmdl.substring(1)).toFloat(); 2402 | pidcInfo(); 2403 | } 2404 | // R/r Set RoC 2405 | if (((userCmdl[0] == 'R') || (userCmdl[0] == 'r')) && (userCmdl[1] != 'E')) { 2406 | // Keep user entry for billboard; convert, set profile temp ramp rate degC/min 2407 | rampCdpm = callRamp(userCmdl.substring(1).toInt()); 2408 | // Send ack response 2409 | if ( ( !NDIA_ARTI ) && ( bbrdRctl & RCTL_DIAG) ) { 2410 | Serial.print(F("#R:")); Serial.println(rampCdpm); 2411 | } 2412 | } 2413 | if ((userCmdl[0] == 'S') || (userCmdl[0] == 's')) { 2414 | // call common SV - S 2415 | callSetp(userCmdl.substring(1).toInt()); 2416 | } 2417 | if (userCmdl[0] == 'V') { 2418 | // New PID Values from Eprom => PID 2419 | pidcFprm(); 2420 | } 2421 | if (userCmdl[0] == 'v') { 2422 | // Version string e if Artisan is setting Unit C/F 2423 | if (!( bbrdRctl & RCTL_ARTI )) { 2424 | Serial.println(versChrs); 2425 | eprmInfo(); 2426 | pidcInfo(); 2427 | } 2428 | } 2429 | if ((userCmdl[0] == 'W') || (userCmdl[0] == 'w')) { 2430 | // set new pwmD Width, run control flag to indicate manual override 2431 | userDuty = callPwmd((userCmdl.substring(1)).toInt()); 2432 | if ( ( !NDIA_ARTI ) && (bbrdRctl & RCTL_DIAG) ) { 2433 | Serial.print(F("# Man PWM%: ")); 2434 | Serial.println(userDuty); 2435 | } 2436 | } 2437 | // y re-sync PID to current conditions 2438 | if ((userCmdl[0] == 'y')) { 2439 | // Live sync PID to present condition 2440 | pidcSync(); 2441 | histFma0 = histFma1 = histFma2 = 0; 2442 | chgeSeen = tpntSeen = 0; 2443 | chgeArmd = tpntArmd = 1; 2444 | } 2445 | // Y re-start ESP 2446 | #if PROC_ESP 2447 | // Y Restart ESP 2448 | if ((userCmdl[0] == 'Y') ) { 2449 | Serial.print("# ESP reset in 3: "); 2450 | delay(3000); 2451 | ESP.restart(); 2452 | } 2453 | #endif 2454 | //z reset Time counts 2455 | if ((userCmdl[0] == 'z')) { 2456 | // Zero 'Total Time' 2457 | totlSecs = 0; 2458 | stepSecs = 0; 2459 | phseCode = chgdChar; 2460 | } 2461 | // Z Reset PID 2462 | if ((userCmdl[0] == 'Z')) { 2463 | pidcRset(); 2464 | } 2465 | if ((userCmdl[0] == '?')) { 2466 | // For debug to see if Artisan is setting Unit C/F 2467 | if ( !( bbrdRctl & RCTL_ARTI ) && ( bbrdRctl & RCTL_DIAG) ) { 2468 | Serial.println(userScal); 2469 | } 2470 | } 2471 | // end norm nonArti speak 2472 | } 2473 | // clean out cmdLine 2474 | //for ( tempIntA = 0; tempIntA < (userCmdl.length()); tempIntA++ ) { 2475 | // userCmdl[tempIntA] = ' '; 2476 | //} 2477 | // Drop Attn flag immediately so not masked during process 2478 | userRctl &= ~RCTL_ATTN; 2479 | //Serial.println(F("#z")); 2480 | } 2481 | // End single command parsing 2482 | } 2483 | 2484 | /// Arduino Setup 2485 | // 2486 | void setup() { 2487 | // put your setup code here, to run once: 2488 | Serial.begin(115200); 2489 | //Serial.setTimeout(100); 2490 | delay(100); 2491 | eprmInit(); 2492 | pidcInit(); 2493 | pidcRset(); 2494 | pidcStop(); 2495 | pwmdInit(); 2496 | rotsInit(); 2497 | userInit(); 2498 | tcplInit(); 2499 | virtTcplInit(); 2500 | millInit(); 2501 | initMavs( AMBI_TMPC, INIT_PWMD); 2502 | profInit(); 2503 | // 2504 | #if WITH_LCD 2505 | lcdsInit(); 2506 | #endif 2507 | if ( !( bbrdRctl & RCTL_ARTI ) && ( bbrdRctl & RCTL_DIAG) ) { 2508 | Serial.println(F("# popcEsp init() ends")); 2509 | } 2510 | } 2511 | 2512 | /// Arduino Loop 2513 | // 2514 | void loop() { 2515 | // put your main code here, to run repeatedly: 2516 | pidcLoop(); 2517 | userLoop(); 2518 | pwmdLoop(); 2519 | rotsLoop(); 2520 | #if WITH_VIRTTCPL 2521 | virtTcplLoop(); 2522 | #endif 2523 | tcplLoop(); 2524 | #if WITH_TCPL_2 2525 | tcp2Loop(); 2526 | #endif // WITH_TCPL_2 2527 | millLoop(); 2528 | profLoop(); 2529 | #if WITH_LCD 2530 | lcdsLoop(); 2531 | #endif 2532 | userLoop(); 2533 | // 2534 | } 2535 | --------------------------------------------------------------------------------