Line data Source code
1 : // ---------------------------------------------------------------------------
2 : // USB EPROM/Flash Programmer
3 : //
4 : // Copyright (2023) Robson Martins
5 : //
6 : // This work is licensed under a Creative Commons Attribution-NonCommercial-
7 : // ShareAlike 4.0 International License.
8 : // ---------------------------------------------------------------------------
9 : /**
10 : * @ingroup Software
11 : * @file backend/runner.cpp
12 : * @brief Implementation of the Runner Class.
13 : *
14 : * @author Robson Martins (https://www.robsonmartins.com)
15 : */
16 : // ---------------------------------------------------------------------------
17 :
18 : #include <QDateTime>
19 : #include <QApplication>
20 : #include <QLoggingCategory>
21 :
22 : #include <chrono>
23 : #include <thread>
24 : #include <cstring>
25 :
26 : #include "backend/runner.hpp"
27 : #include "devices/device.hpp"
28 : #include "config.hpp"
29 :
30 : #ifdef Q_OS_WINDOWS
31 : #include <windows.h>
32 : #endif
33 :
34 : // ---------------------------------------------------------------------------
35 : // Logging
36 :
37 452 : Q_LOGGING_CATEGORY(backendRunner, "backend.runner")
38 :
39 : #define DEBUG qCDebug(backendRunner)
40 : #define INFO qCInfo(backendRunner)
41 : #define WARNING qCWarning(backendRunner)
42 : #define CRITICAL qCCritical(backendRunner)
43 : #define FATAL qCFatal(backendRunner)
44 :
45 : // ---------------------------------------------------------------------------
46 :
47 : #ifdef TEST_BUILD
48 : bool QSerialPort::connected = false;
49 : #endif
50 :
51 : // ---------------------------------------------------------------------------
52 :
53 : /* @brief Timeout (in milliseconds) to consider disconnect. */
54 : constexpr int kDisconnectTimeOut = 5000;
55 : /* @brief Timeout (in milliseconds) to read byte. */
56 : constexpr int kReadTimeOut = 3000;
57 :
58 : // ---------------------------------------------------------------------------
59 :
60 64 : bool TRunnerCommand::responseIsOk() const {
61 64 : return OpCode::isOk(response.data(), response.size());
62 : }
63 :
64 7 : float TRunnerCommand::responseAsFloat() const {
65 7 : return OpCode::getValueAsFloat(response.data(), response.size());
66 : }
67 :
68 2 : int TRunnerCommand::responseAsByte() const {
69 2 : return OpCode::getValueAsByte(response.data(), response.size());
70 : }
71 :
72 2 : int TRunnerCommand::responseAsWord() const {
73 2 : return OpCode::getValueAsWord(response.data(), response.size());
74 : }
75 :
76 1 : int TRunnerCommand::responseAsDWord() const {
77 1 : return OpCode::getValueAsDWord(response.data(), response.size());
78 : }
79 :
80 1 : bool TRunnerCommand::responseAsBool() const {
81 1 : return OpCode::getValueAsBool(response.data(), response.size());
82 : }
83 :
84 37 : void TRunnerCommand::set(kCmdOpCodeEnum code) {
85 37 : opcode = OpCode::getOpCode(code);
86 37 : params.resize(1);
87 37 : params[0] = opcode.code;
88 37 : }
89 :
90 8 : void TRunnerCommand::setFloat(kCmdOpCodeEnum code, float param) {
91 8 : opcode = OpCode::getOpCode(code);
92 8 : params.resize(opcode.params + 1);
93 8 : params[0] = opcode.code;
94 8 : OpCode::setFloat(params.data(), params.size(), param);
95 8 : }
96 :
97 4 : void TRunnerCommand::setByte(kCmdOpCodeEnum code, uint8_t param) {
98 4 : opcode = OpCode::getOpCode(code);
99 4 : params.resize(opcode.params + 1);
100 4 : params[0] = opcode.code;
101 4 : OpCode::setByte(params.data(), params.size(), param);
102 4 : }
103 :
104 4 : void TRunnerCommand::setWord(kCmdOpCodeEnum code, uint16_t param) {
105 4 : opcode = OpCode::getOpCode(code);
106 4 : params.resize(opcode.params + 1);
107 4 : params[0] = opcode.code;
108 4 : OpCode::setWord(params.data(), params.size(), param);
109 4 : }
110 :
111 4 : void TRunnerCommand::setDWord(kCmdOpCodeEnum code, uint32_t param) {
112 4 : opcode = OpCode::getOpCode(code);
113 4 : params.resize(opcode.params + 1);
114 4 : params[0] = opcode.code;
115 4 : OpCode::setDWord(params.data(), params.size(), param);
116 4 : }
117 :
118 47 : void TRunnerCommand::setBool(kCmdOpCodeEnum code, bool param) {
119 47 : opcode = OpCode::getOpCode(code);
120 47 : params.resize(opcode.params + 1);
121 47 : params[0] = opcode.code;
122 47 : OpCode::setBool(params.data(), params.size(), param);
123 47 : }
124 :
125 0 : TRunnerCommand& TRunnerCommand::operator=(const TRunnerCommand& src) {
126 0 : this->opcode = src.opcode;
127 0 : this->params = src.params;
128 0 : this->response = src.response;
129 0 : return *this;
130 : }
131 :
132 1 : bool operator==(const TRunnerCommand& a, const TRunnerCommand& b) {
133 2 : return (a.opcode == b.opcode && a.params == b.params &&
134 2 : a.response == b.response);
135 : }
136 :
137 : // ---------------------------------------------------------------------------
138 :
139 8 : Runner::Runner(QObject* parent)
140 : : QObject(parent),
141 8 : serial_(this),
142 8 : timeout_(kReadTimeOut),
143 8 : running_(false),
144 8 : error_(false),
145 8 : address_(0),
146 8 : bufferSize_(1) {
147 8 : flags_.is16bit = false;
148 8 : flags_.pgmCePin = false;
149 8 : flags_.pgmPositive = false;
150 8 : flags_.progWithVpp = false;
151 8 : flags_.skipFF = false;
152 8 : flags_.vppOePin = false;
153 8 : }
154 :
155 8 : Runner::~Runner() {
156 8 : close();
157 8 : }
158 :
159 1 : TSerialPortList Runner::list() const {
160 1 : TSerialPortList result;
161 2 : for (const auto item : QSerialPortInfo::availablePorts()) {
162 2 : if (item.vendorIdentifier() == kUsbVendorId &&
163 1 : item.productIdentifier() == kUsbProductId) {
164 : #ifdef Q_OS_MACOS
165 : if (item.portName().startsWith("cu.usbmodem")) {
166 : continue;
167 : }
168 : #endif // Q_OS_MACOS
169 : #ifdef Q_OS_FREEBSD
170 : if (item.portName().startsWith("tty")) {
171 : continue;
172 : }
173 : #endif // Q_OS_FREEBSD
174 1 : result.push_back(item);
175 : }
176 2 : }
177 1 : return result;
178 0 : }
179 :
180 11 : bool Runner::open(const QString& path) {
181 11 : if (running_) close();
182 11 : if (path.isNull() || path.isEmpty()) return false;
183 20 : DEBUG << "Opening serial port:" << path << "...";
184 10 : serial_.setPortName(path);
185 10 : bool result = serial_.open(QIODevice::ReadWrite);
186 10 : if (result) {
187 10 : running_ = true;
188 : // need under Windows
189 : // https://community.platformio.org/t/
190 : // serial-communication-micro-usb-on-pi-pico-c/27512/5
191 10 : serial_.setDataTerminalReady(true);
192 20 : DEBUG << "Open serial port OK";
193 20 : DEBUG << "Setting timeout:" << QString("%1").arg(timeout_);
194 : } else {
195 0 : WARNING << "Error opening serial port";
196 : }
197 10 : error_ = !result;
198 10 : return result;
199 : }
200 :
201 18 : void Runner::close() {
202 18 : if (serial_.isOpen()) {
203 20 : DEBUG << "Closing serial port...";
204 : }
205 18 : serial_.close();
206 18 : running_ = false;
207 18 : error_ = false;
208 18 : }
209 :
210 3 : bool Runner::isOpen() const {
211 3 : return running_;
212 : }
213 :
214 1 : bool Runner::hasError() const {
215 1 : return error_;
216 : }
217 :
218 1 : QString Runner::getPath() const {
219 1 : return serial_.portName();
220 : }
221 :
222 2 : uint32_t Runner::getTimeOut() const {
223 2 : return timeout_;
224 : }
225 :
226 4 : void Runner::setTimeOut(uint32_t value) {
227 4 : if (timeout_ == value) return;
228 3 : timeout_ = value;
229 6 : DEBUG << "Setting timeout:" << QString("%1").arg(value);
230 : }
231 :
232 0 : uint8_t Runner::getBufferSize() const {
233 0 : return bufferSize_;
234 : }
235 :
236 0 : void Runner::setBufferSize(uint8_t value) {
237 0 : if (!value) value = 1;
238 : // rounds to next power of two
239 0 : if (value & (value - 1)) {
240 0 : uint32_t exp = 1;
241 : // clang-format off
242 0 : for (exp = 1; (1 << exp) < value; exp++) {}
243 : // clang-format on
244 0 : if (exp > 7) exp = 7; // max 128
245 0 : value = 1 << exp;
246 : }
247 0 : if (flags_.is16bit && value == 1) value = 2;
248 0 : if (bufferSize_ == value) return;
249 0 : bufferSize_ = value;
250 0 : DEBUG << "Setting buffer size:" << QString("%1").arg(value);
251 : }
252 :
253 2 : bool Runner::nop() {
254 2 : TRunnerCommand cmd;
255 2 : cmd.set(kCmdNop);
256 2 : if (!sendCommand_(cmd)) return false;
257 1 : return true;
258 2 : }
259 :
260 7 : bool Runner::vddCtrl(bool on) {
261 7 : TRunnerCommand cmd;
262 7 : cmd.setBool(kCmdVddCtrl, on);
263 7 : if (!sendCommand_(cmd)) return false;
264 5 : return true;
265 7 : }
266 :
267 2 : bool Runner::vddSet(float value) {
268 2 : TRunnerCommand cmd;
269 2 : cmd.setFloat(kCmdVddSetV, value);
270 2 : if (!sendCommand_(cmd)) return false;
271 1 : return true;
272 2 : }
273 :
274 2 : float Runner::vddGet() {
275 2 : TRunnerCommand cmd;
276 2 : cmd.set(kCmdVddGetV);
277 2 : if (!sendCommand_(cmd)) return -1.0f;
278 1 : return cmd.responseAsFloat();
279 2 : }
280 :
281 2 : float Runner::vddGetDuty() {
282 2 : TRunnerCommand cmd;
283 2 : cmd.set(kCmdVddGetDuty);
284 2 : if (!sendCommand_(cmd)) return -1.0f;
285 1 : return cmd.responseAsFloat();
286 2 : }
287 :
288 3 : bool Runner::vddInitCal() {
289 3 : TRunnerCommand cmd;
290 3 : cmd.set(kCmdVddInitCal);
291 3 : if (!sendCommand_(cmd)) return false;
292 1 : return true;
293 3 : }
294 :
295 2 : bool Runner::vddSaveCal(float value) {
296 2 : TRunnerCommand cmd;
297 2 : cmd.setFloat(kCmdVddSaveCal, value);
298 : // no retry
299 2 : if (!sendCommand_(cmd, 0)) {
300 2 : DEBUG << "Error in vddSaveCal()"
301 1 : << "Trying use vddInitCal() and retrying.";
302 : // error, use vddInitCal
303 1 : if (!vddInitCal()) return false;
304 : // retry
305 0 : if (!sendCommand_(cmd, 0)) return false;
306 : }
307 1 : return true;
308 2 : }
309 :
310 2 : float Runner::vddGetCal() {
311 2 : TRunnerCommand cmd;
312 2 : cmd.set(kCmdVddGetCal);
313 2 : if (!sendCommand_(cmd)) return -1.0f;
314 1 : return cmd.responseAsFloat();
315 2 : }
316 :
317 4 : bool Runner::vppCtrl(bool on) {
318 4 : TRunnerCommand cmd;
319 4 : cmd.setBool(kCmdVppCtrl, on);
320 4 : if (!sendCommand_(cmd)) return false;
321 3 : return true;
322 4 : }
323 :
324 2 : bool Runner::vppSet(float value) {
325 2 : TRunnerCommand cmd;
326 2 : cmd.setFloat(kCmdVppSetV, value);
327 2 : if (!sendCommand_(cmd)) return false;
328 1 : return true;
329 2 : }
330 :
331 2 : float Runner::vppGet() {
332 2 : TRunnerCommand cmd;
333 2 : cmd.set(kCmdVppGetV);
334 2 : if (!sendCommand_(cmd)) return -1.0f;
335 1 : return cmd.responseAsFloat();
336 2 : }
337 :
338 2 : float Runner::vppGetDuty() {
339 2 : TRunnerCommand cmd;
340 2 : cmd.set(kCmdVppGetDuty);
341 2 : if (!sendCommand_(cmd)) return -1.0f;
342 1 : return cmd.responseAsFloat();
343 2 : }
344 :
345 3 : bool Runner::vppInitCal() {
346 3 : TRunnerCommand cmd;
347 3 : cmd.set(kCmdVppInitCal);
348 3 : if (!sendCommand_(cmd)) return false;
349 1 : return true;
350 3 : }
351 :
352 2 : bool Runner::vppSaveCal(float value) {
353 2 : TRunnerCommand cmd;
354 2 : cmd.setFloat(kCmdVppSaveCal, value);
355 : // no retry
356 2 : if (!sendCommand_(cmd, 0)) {
357 2 : DEBUG << "Error in vppSaveCal()"
358 1 : << "Trying use vppInitCal() and retrying.";
359 : // error, use vppInitCal
360 1 : if (!vppInitCal()) return false;
361 : // retry
362 0 : if (!sendCommand_(cmd, 0)) return false;
363 : }
364 1 : return true;
365 2 : }
366 :
367 2 : float Runner::vppGetCal() {
368 2 : TRunnerCommand cmd;
369 2 : cmd.set(kCmdVppGetCal);
370 2 : if (!sendCommand_(cmd)) return -1.0f;
371 1 : return cmd.responseAsFloat();
372 2 : }
373 :
374 4 : bool Runner::vddOnVpp(bool on) {
375 4 : TRunnerCommand cmd;
376 4 : cmd.setBool(kCmdVddOnVpp, on);
377 4 : if (!sendCommand_(cmd)) return false;
378 3 : return true;
379 4 : }
380 :
381 4 : bool Runner::vppOnA9(bool on) {
382 4 : TRunnerCommand cmd;
383 4 : cmd.setBool(kCmdVppOnA9, on);
384 4 : if (!sendCommand_(cmd)) return false;
385 3 : return true;
386 4 : }
387 :
388 4 : bool Runner::vppOnA18(bool on) {
389 4 : TRunnerCommand cmd;
390 4 : cmd.setBool(kCmdVppOnA18, on);
391 4 : if (!sendCommand_(cmd)) return false;
392 3 : return true;
393 4 : }
394 :
395 4 : bool Runner::vppOnCE(bool on) {
396 4 : TRunnerCommand cmd;
397 4 : cmd.setBool(kCmdVppOnCE, on);
398 4 : if (!sendCommand_(cmd)) return false;
399 3 : return true;
400 4 : }
401 :
402 4 : bool Runner::vppOnOE(bool on) {
403 4 : TRunnerCommand cmd;
404 4 : cmd.setBool(kCmdVppOnOE, on);
405 4 : if (!sendCommand_(cmd)) return false;
406 3 : return true;
407 4 : }
408 :
409 4 : bool Runner::vppOnWE(bool on) {
410 4 : TRunnerCommand cmd;
411 4 : cmd.setBool(kCmdVppOnWE, on);
412 4 : if (!sendCommand_(cmd)) return false;
413 3 : return true;
414 4 : }
415 :
416 4 : bool Runner::setCE(bool on) {
417 4 : TRunnerCommand cmd;
418 4 : cmd.setBool(kCmdBusCE, on);
419 4 : if (!sendCommand_(cmd)) return false;
420 3 : return true;
421 4 : }
422 :
423 4 : bool Runner::setOE(bool on) {
424 4 : TRunnerCommand cmd;
425 4 : cmd.setBool(kCmdBusOE, on);
426 4 : if (!sendCommand_(cmd)) return false;
427 3 : return true;
428 4 : }
429 :
430 4 : bool Runner::setWE(bool on) {
431 4 : TRunnerCommand cmd;
432 4 : cmd.setBool(kCmdBusWE, on);
433 4 : if (!sendCommand_(cmd)) return false;
434 3 : return true;
435 4 : }
436 :
437 5 : bool Runner::addrClr() {
438 5 : TRunnerCommand cmd;
439 5 : cmd.set(kCmdBusAddrClr);
440 5 : if (!sendCommand_(cmd)) return false;
441 4 : address_ = 0;
442 4 : return true;
443 5 : }
444 :
445 2 : bool Runner::addrInc() {
446 2 : TRunnerCommand cmd;
447 2 : cmd.set(kCmdBusAddrInc);
448 : // no retry
449 2 : if (!sendCommand_(cmd, 0)) {
450 2 : DEBUG << "Error in addrInc(). Last address:"
451 2 : << QString("0x%1").arg(address_, 6, 16, QChar('0'))
452 1 : << "Trying use addrSet(). New address:"
453 1 : << QString("0x%1").arg(address_ + 1, 6, 16, QChar('0'));
454 : // error, use addrSet
455 1 : return addrSet(address_ + 1);
456 : }
457 1 : address_++;
458 1 : return true;
459 2 : }
460 :
461 5 : bool Runner::addrSet(uint32_t value) {
462 5 : if (!value) return addrClr();
463 4 : TRunnerCommand cmd;
464 4 : cmd.setDWord(kCmdBusAddrSet, value);
465 4 : if (!sendCommand_(cmd)) return false;
466 2 : address_ = value;
467 2 : return true;
468 4 : }
469 :
470 3 : bool Runner::addrSetB(uint8_t value) {
471 3 : if (!value) return addrClr();
472 2 : TRunnerCommand cmd;
473 2 : cmd.setByte(kCmdBusAddrSetB, value);
474 2 : if (!sendCommand_(cmd)) return false;
475 1 : address_ = value;
476 1 : return true;
477 2 : }
478 :
479 3 : bool Runner::addrSetW(uint16_t value) {
480 3 : if (!value) return addrClr();
481 2 : TRunnerCommand cmd;
482 2 : cmd.setWord(kCmdBusAddrSetW, value);
483 2 : if (!sendCommand_(cmd)) return false;
484 1 : address_ = value;
485 1 : return true;
486 2 : }
487 :
488 1 : uint32_t Runner::addrGet() const {
489 1 : return address_;
490 : }
491 :
492 4 : bool Runner::dataClr() {
493 4 : TRunnerCommand cmd;
494 4 : cmd.set(kCmdBusDataClr);
495 4 : if (!sendCommand_(cmd)) return false;
496 3 : return true;
497 4 : }
498 :
499 3 : bool Runner::dataSet(uint8_t value) {
500 3 : if (!value) return dataClr();
501 2 : TRunnerCommand cmd;
502 2 : cmd.setByte(kCmdBusDataSet, value);
503 2 : if (!sendCommand_(cmd)) return false;
504 1 : return true;
505 2 : }
506 :
507 3 : bool Runner::dataSetW(uint16_t value) {
508 3 : if (!value) return dataClr();
509 2 : TRunnerCommand cmd;
510 2 : cmd.setWord(kCmdBusDataSetW, value);
511 2 : if (!sendCommand_(cmd)) return false;
512 1 : return true;
513 2 : }
514 :
515 2 : uint8_t Runner::dataGet() {
516 2 : TRunnerCommand cmd;
517 2 : cmd.set(kCmdBusDataGet);
518 2 : if (!sendCommand_(cmd)) return 0xFF;
519 1 : return cmd.responseAsByte();
520 2 : }
521 :
522 4 : uint16_t Runner::dataGetW() {
523 4 : TRunnerCommand cmd;
524 4 : cmd.set(kCmdBusDataGetW);
525 4 : if (!sendCommand_(cmd)) return 0xFFFF;
526 1 : return cmd.responseAsWord();
527 4 : }
528 :
529 0 : bool Runner::deviceConfigure(kCmdDeviceAlgorithmEnum algo,
530 : const TDeviceFlags& flags) {
531 0 : TRunnerCommand cmd;
532 0 : uint16_t value = algo;
533 0 : value <<= 8;
534 0 : flags_ = flags;
535 0 : if (flags.is16bit && bufferSize_ == 1) setBufferSize(2);
536 : // clang-format off
537 0 : if (flags.skipFF ) value |= 0x01;
538 0 : if (flags.progWithVpp) value |= 0x02;
539 0 : if (flags.vppOePin ) value |= 0x04;
540 0 : if (flags.pgmCePin ) value |= 0x08;
541 0 : if (flags.pgmPositive) value |= 0x10;
542 0 : if (flags.is16bit ) value |= 0x20;
543 : // clang-format on
544 0 : cmd.setWord(kCmdDeviceConfigure, value);
545 0 : if (!sendCommand_(cmd)) return false;
546 0 : return true;
547 0 : }
548 :
549 0 : bool Runner::deviceSetTwp(uint32_t value) {
550 0 : TRunnerCommand cmd;
551 0 : cmd.setDWord(kCmdDeviceSetTwp, value);
552 0 : if (!sendCommand_(cmd)) return false;
553 : // adjust timeout
554 0 : uint32_t calculatedTime = (value * 2048) / 1000; // 2KB
555 0 : if (calculatedTime > timeout_) {
556 0 : timeout_ = calculatedTime * 2;
557 : }
558 0 : return true;
559 0 : }
560 :
561 0 : bool Runner::deviceSetTwc(uint32_t value) {
562 0 : TRunnerCommand cmd;
563 0 : cmd.setDWord(kCmdDeviceSetTwc, value);
564 0 : if (!sendCommand_(cmd)) return false;
565 : // adjust timeout
566 0 : uint32_t calculatedTime = (value * 2048) / 1000; // 2KB
567 0 : if (calculatedTime > timeout_) {
568 0 : timeout_ = calculatedTime * 2;
569 : }
570 0 : return true;
571 0 : }
572 :
573 0 : bool Runner::deviceSetupBus(kCmdDeviceOperationEnum operation) {
574 0 : TRunnerCommand cmd;
575 0 : cmd.setByte(kCmdDeviceSetupBus, operation);
576 0 : if (!sendCommand_(cmd)) return false;
577 0 : address_ = 0;
578 0 : return true;
579 0 : }
580 :
581 0 : bool Runner::deviceResetBus() {
582 0 : return deviceSetupBus(kCmdDeviceOperationReset);
583 : }
584 :
585 0 : QByteArray Runner::deviceRead() {
586 0 : QByteArray result;
587 0 : TRunnerCommand cmd;
588 0 : cmd.setByte(kCmdDeviceRead, bufferSize_);
589 : // setup expected response size
590 0 : cmd.opcode.result = bufferSize_;
591 : // no retry
592 0 : if (!sendCommand_(cmd, 0)) {
593 0 : DEBUG << "Error in deviceRead(). Last address:"
594 0 : << QString("0x%1").arg(address_, 6, 16, QChar('0'))
595 0 : << "Trying use addrSet()";
596 : // error
597 : // use addrSet
598 0 : if (!addrSet(address_)) return result;
599 : // call deviceRead already
600 0 : if (!sendCommand_(cmd, 0)) return result;
601 : }
602 : // set data
603 0 : result.resize(bufferSize_);
604 0 : memset(result.data(), 0xFF, bufferSize_);
605 0 : memcpy(result.data(), cmd.response.data() + 1,
606 0 : qMin(cmd.response.size() - 1, static_cast<int>(bufferSize_)));
607 0 : address_ += (flags_.is16bit ? (bufferSize_ / 2) : bufferSize_);
608 0 : return result;
609 0 : }
610 :
611 0 : bool Runner::deviceWrite(const QByteArray& data) {
612 0 : TRunnerCommand cmd;
613 0 : cmd.setByte(kCmdDeviceWrite, bufferSize_);
614 : // set data
615 0 : cmd.params.resize(bufferSize_ + 2);
616 0 : memset(cmd.params.data() + 2, 0xFF, bufferSize_);
617 0 : memcpy(cmd.params.data() + 2, data.data(),
618 0 : qMin(data.size(), static_cast<int>(bufferSize_)));
619 : // no retry
620 0 : if (!sendCommand_(cmd, 0)) {
621 0 : DEBUG << "Error in deviceWrite(). Last address:"
622 0 : << QString("0x%1").arg(address_, 6, 16, QChar('0'))
623 0 : << "Trying use addrSet()";
624 : // error
625 : // use addrSet
626 0 : if (!addrSet(address_)) return false;
627 : // call deviceWrite already
628 0 : if (!sendCommand_(cmd, 0)) return false;
629 : }
630 0 : address_ += (flags_.is16bit ? (bufferSize_ / 2) : bufferSize_);
631 0 : return true;
632 0 : }
633 :
634 0 : bool Runner::deviceWriteSector(const QByteArray& data, uint16_t sectorSize) {
635 0 : TRunnerCommand cmd;
636 0 : cmd.setWord(kCmdDeviceWriteSector, sectorSize);
637 : // set data
638 0 : cmd.params.resize(sectorSize + 3);
639 0 : memset(cmd.params.data() + 3, 0xFF, sectorSize);
640 0 : memcpy(cmd.params.data() + 3, data.data(),
641 0 : qMin(data.size(), static_cast<int>(sectorSize)));
642 : // no retry
643 0 : if (!sendCommand_(cmd, 0)) {
644 0 : DEBUG << "Error in deviceWriteSector(). Last address:"
645 0 : << QString("0x%1").arg(address_, 6, 16, QChar('0'))
646 0 : << "Trying use addrSet()";
647 : // error
648 : // use addrSet
649 0 : if (!addrSet(address_)) return false;
650 : // call deviceWriteSector already
651 0 : if (!sendCommand_(cmd, 0)) return false;
652 : }
653 0 : address_ += (flags_.is16bit ? (bufferSize_ / 2) : bufferSize_);
654 0 : return true;
655 0 : }
656 :
657 0 : bool Runner::deviceVerify(const QByteArray& data) {
658 0 : TRunnerCommand cmd;
659 0 : cmd.setByte(kCmdDeviceVerify, bufferSize_);
660 : // set data
661 0 : cmd.params.resize(bufferSize_ + 2);
662 0 : memset(cmd.params.data() + 2, 0xFF, bufferSize_);
663 0 : memcpy(cmd.params.data() + 2, data.data(),
664 0 : qMin(data.size(), static_cast<int>(bufferSize_)));
665 : // no retry
666 0 : if (!sendCommand_(cmd, 0)) {
667 0 : DEBUG << "Error in deviceVerify(). Last address:"
668 0 : << QString("0x%1").arg(address_, 6, 16, QChar('0'))
669 0 : << "Trying use addrSet()";
670 : // error
671 : // use addrSet
672 0 : if (!addrSet(address_)) return false;
673 : // call deviceVerify already
674 0 : if (!sendCommand_(cmd, 0)) return false;
675 : }
676 0 : address_ += (flags_.is16bit ? (bufferSize_ / 2) : bufferSize_);
677 0 : return true;
678 0 : }
679 :
680 0 : bool Runner::deviceBlankCheck() {
681 0 : TRunnerCommand cmd;
682 0 : cmd.setByte(kCmdDeviceBlankCheck, bufferSize_);
683 : // no retry
684 0 : if (!sendCommand_(cmd, 0)) {
685 0 : DEBUG << "Error in deviceBlankCheck(). Last address:"
686 0 : << QString("0x%1").arg(address_, 6, 16, QChar('0'))
687 0 : << "Trying use addrSet()";
688 : // error
689 : // use addrSet
690 0 : if (!addrSet(address_)) return false;
691 : // call deviceBlankCheck already
692 0 : if (!sendCommand_(cmd, 0)) return false;
693 : }
694 0 : address_ += (flags_.is16bit ? (bufferSize_ / 2) : bufferSize_);
695 0 : return true;
696 0 : }
697 :
698 0 : TDeviceID Runner::deviceGetId() {
699 0 : TDeviceID result;
700 0 : TRunnerCommand cmd;
701 0 : cmd.set(kCmdDeviceGetId);
702 0 : if (!sendCommand_(cmd)) return result;
703 0 : uint32_t rawCode = cmd.responseAsDWord();
704 0 : result.manufacturer = (rawCode & 0xFFFF0000) >> 16;
705 0 : result.device = (rawCode & 0xFFFF);
706 0 : return result;
707 0 : }
708 :
709 0 : bool Runner::deviceErase() {
710 0 : TRunnerCommand cmd;
711 0 : cmd.set(kCmdDeviceErase);
712 0 : if (!sendCommand_(cmd)) return false;
713 0 : return true;
714 0 : }
715 :
716 0 : bool Runner::deviceUnprotect() {
717 0 : TRunnerCommand cmd;
718 0 : cmd.set(kCmdDeviceUnprotect);
719 0 : if (!sendCommand_(cmd)) return false;
720 0 : return true;
721 0 : }
722 :
723 0 : bool Runner::deviceProtect() {
724 0 : TRunnerCommand cmd;
725 0 : cmd.set(kCmdDeviceProtect);
726 0 : if (!sendCommand_(cmd)) return false;
727 0 : return true;
728 0 : }
729 :
730 5 : void Runner::usDelay(uint64_t value) {
731 6 : if (!value) return;
732 5 : if (value >= 10000) {
733 1 : msDelay(value / 1000);
734 1 : return;
735 : }
736 4 : auto start = std::chrono::steady_clock::now();
737 4 : auto end = start;
738 4 : int64_t elapsed = 0;
739 : do {
740 24 : std::this_thread::sleep_for(std::chrono::microseconds(1));
741 24 : end = std::chrono::steady_clock::now();
742 : elapsed =
743 24 : std::chrono::duration_cast<std::chrono::microseconds>(end - start)
744 24 : .count();
745 24 : } while (elapsed < value);
746 : }
747 :
748 6 : void Runner::msDelay(uint32_t value) {
749 6 : if (!value) return;
750 6 : auto start = std::chrono::steady_clock::now();
751 6 : auto end = start;
752 6 : int64_t elapsed = 0;
753 : do {
754 : #ifdef Q_OS_WINDOWS
755 : Sleep(1);
756 : #else
757 3364 : std::this_thread::sleep_for(std::chrono::milliseconds(1));
758 : #endif
759 3364 : end = std::chrono::steady_clock::now();
760 : elapsed =
761 3364 : std::chrono::duration_cast<std::chrono::milliseconds>(end - start)
762 3364 : .count();
763 3364 : if (value > 50 && (elapsed % 50) == 0) processEvents();
764 3364 : } while (elapsed < value);
765 : }
766 :
767 68 : void Runner::processEvents() {
768 68 : QApplication::processEvents();
769 : #ifdef Q_OS_WINDOWS
770 : Sleep(1);
771 : #endif
772 68 : }
773 :
774 104 : bool Runner::sendCommand_(TRunnerCommand& cmd, int retry) {
775 104 : if (!serial_.isOpen()) {
776 74 : WARNING << "Serial port not open. Error running command"
777 37 : << cmd.opcode.descr.c_str();
778 37 : error_ = true;
779 37 : return false;
780 : }
781 134 : DEBUG << "Running" << cmd.opcode.descr.c_str()
782 134 : << "[ 0x" + QString(cmd.params.toHex()) << "]"
783 67 : << "Current Address:"
784 67 : << QString("0x%1").arg(address_, 6, 16, QChar('0'));
785 67 : bool success = false;
786 76 : for (int i = 0; i < (retry + 1); i++) {
787 137 : if (!write_(cmd.params) ||
788 64 : !read_(&cmd.response, cmd.opcode.result + 1)) {
789 18 : DEBUG << "Retrying."
790 9 : << "Command" << cmd.opcode.descr.c_str();
791 9 : continue;
792 9 : }
793 64 : error_ = false;
794 64 : success = true;
795 64 : break;
796 : }
797 67 : if (!success) {
798 3 : if (cmd.opcode.code != kCmdBusAddrInc) {
799 6 : WARNING << "Error writing to or reading from serial port."
800 3 : << "Command" << cmd.opcode.descr.c_str();
801 : }
802 3 : error_ = true;
803 3 : return false;
804 : }
805 64 : if (!cmd.responseIsOk()) {
806 0 : WARNING << "Response NOK."
807 0 : << "Command" << cmd.opcode.descr.c_str() << ". Response"
808 0 : << "[ 0x" + QString(cmd.response.toHex()) << "]";
809 0 : error_ = true;
810 0 : return false;
811 : } else {
812 128 : DEBUG << "Response"
813 64 : << "[ 0x" + QString(cmd.response.toHex()) << "]";
814 : }
815 64 : return true;
816 : }
817 :
818 73 : bool Runner::write_(const QByteArray& data) {
819 73 : if (data.isEmpty()) return true;
820 73 : serial_.clear();
821 73 : return serial_.write(data) == data.size();
822 : }
823 :
824 64 : bool Runner::read_(QByteArray* data, uint32_t size) {
825 64 : if (data == nullptr || !size) return true;
826 64 : data->clear();
827 128 : while (data->size() < size) {
828 64 : auto start = std::chrono::steady_clock::now();
829 64 : auto end = start;
830 64 : int64_t elapsed = 0;
831 64 : bool hasData = false;
832 : do {
833 64 : if (serial_.waitForReadyRead(1)) {
834 64 : hasData = true;
835 64 : break;
836 : }
837 : #ifdef Q_OS_WINDOWS
838 : Sleep(1);
839 : #else
840 0 : std::this_thread::sleep_for(std::chrono::milliseconds(1));
841 : #endif
842 0 : end = std::chrono::steady_clock::now();
843 0 : elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
844 0 : end - start)
845 0 : .count();
846 0 : if (timeout_ > 50 && (elapsed % 50) == 0) processEvents();
847 0 : } while (elapsed < timeout_);
848 64 : if (!hasData) {
849 0 : DEBUG << "Error reading serial port: timeout";
850 0 : checkAlive_();
851 0 : return false;
852 : }
853 64 : data->append(serial_.readAll());
854 64 : aliveTick_ = QDateTime::currentMSecsSinceEpoch();
855 : }
856 64 : if (data->size() > size) data->resize(size);
857 64 : if (data->size() != size) {
858 0 : DEBUG << "Error reading serial port: sizes are different";
859 0 : return false;
860 : }
861 64 : return true;
862 : }
863 :
864 0 : void Runner::checkAlive_() {
865 0 : if (!running_) return;
866 0 : if (QDateTime::currentMSecsSinceEpoch() - aliveTick_ > kDisconnectTimeOut) {
867 0 : WARNING << "Serial port: disconnected by timeout";
868 0 : running_ = false;
869 0 : if (serial_.isOpen()) serial_.close();
870 : }
871 : }
|