tasmota-webserial-esptool 9.2.19 → 9.2.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/const.d.ts +13 -3
- package/dist/const.js +10 -0
- package/dist/esp_loader.d.ts +42 -30
- package/dist/esp_loader.js +593 -414
- package/dist/util.d.ts +5 -0
- package/dist/util.js +15 -0
- package/dist/web/index.js +1 -1
- package/js/modules/esptool.js +1 -1
- package/package.json +19 -8
- package/src/const.ts +13 -5
- package/src/esp_loader.ts +727 -488
- package/src/util.ts +20 -0
package/src/esp_loader.ts
CHANGED
|
@@ -58,7 +58,17 @@ import {
|
|
|
58
58
|
CHIP_DETECT_MAGIC_REG_ADDR,
|
|
59
59
|
CHIP_DETECT_MAGIC_VALUES,
|
|
60
60
|
CHIP_ID_TO_INFO,
|
|
61
|
+
ESP32_BASEFUSEADDR,
|
|
62
|
+
ESP32_APB_CTL_DATE_ADDR,
|
|
63
|
+
ESP32S2_EFUSE_BLOCK1_ADDR,
|
|
64
|
+
ESP32S3_EFUSE_BLOCK1_ADDR,
|
|
65
|
+
ESP32C2_EFUSE_BLOCK2_ADDR,
|
|
66
|
+
ESP32C5_EFUSE_BLOCK1_ADDR,
|
|
67
|
+
ESP32C6_EFUSE_BLOCK1_ADDR,
|
|
68
|
+
ESP32C61_EFUSE_BLOCK1_ADDR,
|
|
69
|
+
ESP32H2_EFUSE_BLOCK1_ADDR,
|
|
61
70
|
ESP32P4_EFUSE_BLOCK1_ADDR,
|
|
71
|
+
ESP32S31_EFUSE_BLOCK1_ADDR,
|
|
62
72
|
SlipReadError,
|
|
63
73
|
ESP32S2_RTC_CNTL_WDTWPROTECT_REG,
|
|
64
74
|
ESP32S2_RTC_CNTL_WDTCONFIG0_REG,
|
|
@@ -74,14 +84,10 @@ import {
|
|
|
74
84
|
ESP32S3_RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK,
|
|
75
85
|
ESP32C3_EFUSE_RD_MAC_SPI_SYS_3_REG,
|
|
76
86
|
ESP32C3_EFUSE_RD_MAC_SPI_SYS_5_REG,
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
ESP32C5_C6_RTC_CNTL_WDTWPROTECT_REG,
|
|
82
|
-
ESP32C5_C6_RTC_CNTL_WDTCONFIG0_REG,
|
|
83
|
-
ESP32C5_C6_RTC_CNTL_WDTCONFIG1_REG,
|
|
84
|
-
ESP32C5_C6_RTC_CNTL_WDT_WKEY,
|
|
87
|
+
ESP32C5_UART_CLKDIV_REG,
|
|
88
|
+
ESP32C5_PCR_SYSCLK_CONF_REG,
|
|
89
|
+
ESP32C5_PCR_SYSCLK_XTAL_FREQ_V,
|
|
90
|
+
ESP32C5_PCR_SYSCLK_XTAL_FREQ_S,
|
|
85
91
|
ESP32P4_RTC_CNTL_WDTWPROTECT_REG,
|
|
86
92
|
ESP32P4_RTC_CNTL_WDTCONFIG0_REG,
|
|
87
93
|
ESP32P4_RTC_CNTL_WDTCONFIG1_REG,
|
|
@@ -95,17 +101,13 @@ import {
|
|
|
95
101
|
ESP32P4_PMU_0P1A_TARGET0_0,
|
|
96
102
|
ESP32P4_PMU_0P1A_FORCE_TIEH_SEL_0,
|
|
97
103
|
ESP32P4_PMU_DATE_REG,
|
|
98
|
-
ESP32C5_PCR_SYSCLK_CONF_REG,
|
|
99
|
-
ESP32C5_PCR_SYSCLK_XTAL_FREQ_V,
|
|
100
|
-
ESP32C5_PCR_SYSCLK_XTAL_FREQ_S,
|
|
101
|
-
ESP32C5_UART_CLKDIV_REG,
|
|
102
104
|
ESP32S2_UARTDEV_BUF_NO,
|
|
103
105
|
ESP32S2_UARTDEV_BUF_NO_USB_OTG,
|
|
104
106
|
ESP32S3_UARTDEV_BUF_NO,
|
|
105
|
-
ESP32S3_UARTDEV_BUF_NO_USB_JTAG_SERIAL,
|
|
106
107
|
ESP32S3_UARTDEV_BUF_NO_USB_OTG,
|
|
107
|
-
|
|
108
|
+
ESP32S3_UARTDEV_BUF_NO_USB_JTAG_SERIAL,
|
|
108
109
|
ESP32C3_UARTDEV_BUF_NO_USB_JTAG_SERIAL,
|
|
110
|
+
ESP32C3_BUF_UART_NO_OFFSET,
|
|
109
111
|
ESP32C5_UARTDEV_BUF_NO,
|
|
110
112
|
ESP32C5_UARTDEV_BUF_NO_USB_JTAG_SERIAL,
|
|
111
113
|
ESP32C6_UARTDEV_BUF_NO,
|
|
@@ -120,11 +122,11 @@ import {
|
|
|
120
122
|
ESP32H4_UARTDEV_BUF_NO_USB_JTAG_SERIAL,
|
|
121
123
|
ESP32P4_UARTDEV_BUF_NO_REV0,
|
|
122
124
|
ESP32P4_UARTDEV_BUF_NO_REV300,
|
|
123
|
-
ESP32P4_UARTDEV_BUF_NO_USB_JTAG_SERIAL,
|
|
124
125
|
ESP32P4_UARTDEV_BUF_NO_USB_OTG,
|
|
126
|
+
ESP32P4_UARTDEV_BUF_NO_USB_JTAG_SERIAL,
|
|
125
127
|
} from "./const";
|
|
126
128
|
import { getStubCode } from "./stubs";
|
|
127
|
-
import { hexFormatter, sleep, slipEncode, toHex } from "./util";
|
|
129
|
+
import { hexFormatter, padTo, sleep, slipEncode, toHex } from "./util";
|
|
128
130
|
import { deflate } from "pako";
|
|
129
131
|
import { pack, unpack } from "./struct";
|
|
130
132
|
|
|
@@ -548,7 +550,15 @@ export class ESPLoader extends EventTarget {
|
|
|
548
550
|
);
|
|
549
551
|
} catch (err) {
|
|
550
552
|
this.logger.debug(`Could not detect USB connection type: ${err}`);
|
|
551
|
-
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
try {
|
|
556
|
+
const usbMode = await this.getUsbMode();
|
|
557
|
+
this.logger.debug(
|
|
558
|
+
`USB mode (register): ${usbMode.mode} (uartNo=${usbMode.uartNo})`,
|
|
559
|
+
);
|
|
560
|
+
} catch (err) {
|
|
561
|
+
this.logger.debug(`Could not detect USB mode: ${err}`);
|
|
552
562
|
}
|
|
553
563
|
|
|
554
564
|
// Read the OTP data for this chip and store into this.efuses array
|
|
@@ -557,7 +567,11 @@ export class ESPLoader extends EventTarget {
|
|
|
557
567
|
for (let i = 0; i < 4; i++) {
|
|
558
568
|
this._efuses[i] = await this.readRegister(AddrMAC + 4 * i);
|
|
559
569
|
}
|
|
560
|
-
|
|
570
|
+
const revisionInfo =
|
|
571
|
+
this.chipRevision !== null && this.chipRevision !== undefined
|
|
572
|
+
? ` (revision ${this.chipRevision})`
|
|
573
|
+
: "";
|
|
574
|
+
this.logger.log(`Connected to ${this.chipName}${revisionInfo}`);
|
|
561
575
|
this.logger.debug(
|
|
562
576
|
`Bootloader flash offset: 0x${FlAddr.flashOffs.toString(16)}`,
|
|
563
577
|
);
|
|
@@ -580,21 +594,16 @@ export class ESPLoader extends EventTarget {
|
|
|
580
594
|
this.chipName = chipInfo.name;
|
|
581
595
|
this.chipFamily = chipInfo.family;
|
|
582
596
|
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
this.chipRevision = await this.getChipRevision();
|
|
586
|
-
this.logger.debug(`ESP32-P4 revision: ${this.chipRevision}`);
|
|
597
|
+
this.chipRevision = await this.getChipRevision();
|
|
598
|
+
this.logger.debug(`${this.chipName} revision: ${this.chipRevision}`);
|
|
587
599
|
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
this.
|
|
595
|
-
} else if (this.chipFamily === CHIP_FAMILY_ESP32C3) {
|
|
596
|
-
this.chipRevision = await this.getChipRevision();
|
|
597
|
-
this.logger.debug(`ESP32-C3 revision: ${this.chipRevision}`);
|
|
600
|
+
if (
|
|
601
|
+
this.chipFamily === CHIP_FAMILY_ESP32P4 &&
|
|
602
|
+
this.chipRevision >= 300
|
|
603
|
+
) {
|
|
604
|
+
this.chipVariant = "rev300";
|
|
605
|
+
} else if (this.chipFamily === CHIP_FAMILY_ESP32P4) {
|
|
606
|
+
this.chipVariant = "rev0";
|
|
598
607
|
}
|
|
599
608
|
|
|
600
609
|
this.logger.debug(
|
|
@@ -644,17 +653,12 @@ export class ESPLoader extends EventTarget {
|
|
|
644
653
|
this.chipName = chip.name;
|
|
645
654
|
this.chipFamily = chip.family;
|
|
646
655
|
|
|
647
|
-
|
|
648
|
-
|
|
656
|
+
this.chipRevision = await this.getChipRevision();
|
|
657
|
+
this.logger.debug(`${this.chipName} revision: ${this.chipRevision}`);
|
|
649
658
|
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
} else {
|
|
653
|
-
this.chipVariant = "rev0";
|
|
654
|
-
}
|
|
659
|
+
if (this.chipFamily === CHIP_FAMILY_ESP32P4) {
|
|
660
|
+
this.chipVariant = this.chipRevision >= 300 ? "rev300" : "rev0";
|
|
655
661
|
this.logger.debug(`ESP32-P4 variant: ${this.chipVariant}`);
|
|
656
|
-
} else if (this.chipFamily === CHIP_FAMILY_ESP32C3) {
|
|
657
|
-
this.chipRevision = await this.getChipRevision();
|
|
658
662
|
}
|
|
659
663
|
|
|
660
664
|
this.logger.debug(
|
|
@@ -662,28 +666,102 @@ export class ESPLoader extends EventTarget {
|
|
|
662
666
|
);
|
|
663
667
|
}
|
|
664
668
|
|
|
665
|
-
/**
|
|
666
|
-
* Get chip revision for ESP32-P4
|
|
667
|
-
*/
|
|
668
669
|
async getChipRevision(): Promise<number> {
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
670
|
+
let minor = 0;
|
|
671
|
+
let major = 0;
|
|
672
|
+
|
|
673
|
+
switch (this.chipFamily) {
|
|
674
|
+
case CHIP_FAMILY_ESP32: {
|
|
675
|
+
const efuse3 = await this.readRegister(ESP32_BASEFUSEADDR + 4 * 3);
|
|
676
|
+
const efuse5 = await this.readRegister(ESP32_BASEFUSEADDR + 4 * 5);
|
|
677
|
+
minor = (efuse5 >> 24) & 0x3;
|
|
678
|
+
const revBit0 = (efuse3 >> 15) & 0x1;
|
|
679
|
+
const revBit1 = (efuse5 >> 20) & 0x1;
|
|
680
|
+
const apb = await this.readRegister(ESP32_APB_CTL_DATE_ADDR);
|
|
681
|
+
const revBit2 = (apb >> 31) & 0x1;
|
|
682
|
+
const combined = (revBit2 << 2) | (revBit1 << 1) | revBit0;
|
|
683
|
+
major =
|
|
684
|
+
({ 0: 0, 1: 1, 3: 2, 7: 3 } as Record<number, number>)[combined] ?? 0;
|
|
685
|
+
break;
|
|
686
|
+
}
|
|
687
|
+
case CHIP_FAMILY_ESP32S2: {
|
|
688
|
+
const w3 = await this.readRegister(ESP32S2_EFUSE_BLOCK1_ADDR + 4 * 3);
|
|
689
|
+
const w4 = await this.readRegister(ESP32S2_EFUSE_BLOCK1_ADDR + 4 * 4);
|
|
690
|
+
const hi = (w3 >> 20) & 0x01;
|
|
691
|
+
const lo = (w4 >> 4) & 0x07;
|
|
692
|
+
minor = (hi << 3) + lo;
|
|
693
|
+
major = (w3 >> 18) & 0x03;
|
|
694
|
+
break;
|
|
695
|
+
}
|
|
696
|
+
case CHIP_FAMILY_ESP32S3: {
|
|
697
|
+
const w3 = await this.readRegister(ESP32S3_EFUSE_BLOCK1_ADDR + 4 * 3);
|
|
698
|
+
const w5 = await this.readRegister(ESP32S3_EFUSE_BLOCK1_ADDR + 4 * 5);
|
|
699
|
+
const hi = (w5 >> 23) & 0x01;
|
|
700
|
+
const lo = (w3 >> 18) & 0x07;
|
|
701
|
+
minor = (hi << 3) + lo;
|
|
702
|
+
major = (w5 >> 24) & 0x03;
|
|
703
|
+
break;
|
|
704
|
+
}
|
|
705
|
+
case CHIP_FAMILY_ESP32C2: {
|
|
706
|
+
const w1 = await this.readRegister(ESP32C2_EFUSE_BLOCK2_ADDR + 4 * 1);
|
|
707
|
+
minor = (w1 >> 16) & 0x0f;
|
|
708
|
+
major = (w1 >> 20) & 0x03;
|
|
709
|
+
break;
|
|
710
|
+
}
|
|
711
|
+
case CHIP_FAMILY_ESP32C3: {
|
|
712
|
+
const w3 = await this.readRegister(ESP32C3_EFUSE_RD_MAC_SPI_SYS_3_REG);
|
|
713
|
+
const w5 = await this.readRegister(ESP32C3_EFUSE_RD_MAC_SPI_SYS_5_REG);
|
|
714
|
+
const hi = (w5 >> 23) & 0x01;
|
|
715
|
+
const lo = (w3 >> 18) & 0x07;
|
|
716
|
+
minor = (hi << 3) + lo;
|
|
717
|
+
major = (w5 >> 24) & 0x03;
|
|
718
|
+
break;
|
|
719
|
+
}
|
|
720
|
+
case CHIP_FAMILY_ESP32C5: {
|
|
721
|
+
const w2 = await this.readRegister(ESP32C5_EFUSE_BLOCK1_ADDR + 4 * 2);
|
|
722
|
+
minor = w2 & 0x0f;
|
|
723
|
+
major = (w2 >> 4) & 0x03;
|
|
724
|
+
break;
|
|
725
|
+
}
|
|
726
|
+
case CHIP_FAMILY_ESP32C6: {
|
|
727
|
+
const w3 = await this.readRegister(ESP32C6_EFUSE_BLOCK1_ADDR + 4 * 3);
|
|
728
|
+
minor = (w3 >> 18) & 0x0f;
|
|
729
|
+
major = (w3 >> 22) & 0x03;
|
|
730
|
+
break;
|
|
731
|
+
}
|
|
732
|
+
case CHIP_FAMILY_ESP32C61: {
|
|
733
|
+
const w2 = await this.readRegister(ESP32C61_EFUSE_BLOCK1_ADDR + 4 * 2);
|
|
734
|
+
minor = w2 & 0x0f;
|
|
735
|
+
major = (w2 >> 4) & 0x03;
|
|
736
|
+
break;
|
|
737
|
+
}
|
|
738
|
+
case CHIP_FAMILY_ESP32H2: {
|
|
739
|
+
const w3 = await this.readRegister(ESP32H2_EFUSE_BLOCK1_ADDR + 4 * 3);
|
|
740
|
+
minor = (w3 >> 18) & 0x07;
|
|
741
|
+
major = (w3 >> 21) & 0x03;
|
|
742
|
+
break;
|
|
743
|
+
}
|
|
744
|
+
case CHIP_FAMILY_ESP32H4: {
|
|
745
|
+
break;
|
|
746
|
+
}
|
|
747
|
+
case CHIP_FAMILY_ESP32H21: {
|
|
748
|
+
break;
|
|
749
|
+
}
|
|
750
|
+
case CHIP_FAMILY_ESP32P4: {
|
|
751
|
+
const w2 = await this.readRegister(ESP32P4_EFUSE_BLOCK1_ADDR + 4 * 2);
|
|
752
|
+
minor = w2 & 0x0f;
|
|
753
|
+
major = (((w2 >> 23) & 1) << 2) | ((w2 >> 4) & 0x03);
|
|
754
|
+
break;
|
|
755
|
+
}
|
|
756
|
+
case CHIP_FAMILY_ESP32S31: {
|
|
757
|
+
const w2 = await this.readRegister(ESP32S31_EFUSE_BLOCK1_ADDR + 4 * 2);
|
|
758
|
+
minor = w2 & 0x0f;
|
|
759
|
+
major = (w2 >> 4) & 0x03;
|
|
760
|
+
break;
|
|
761
|
+
}
|
|
684
762
|
}
|
|
685
763
|
|
|
686
|
-
return
|
|
764
|
+
return major * 100 + minor;
|
|
687
765
|
}
|
|
688
766
|
|
|
689
767
|
/**
|
|
@@ -896,10 +974,6 @@ export class ESPLoader extends EventTarget {
|
|
|
896
974
|
this.logger.debug("Finished read loop");
|
|
897
975
|
}
|
|
898
976
|
|
|
899
|
-
sleep(ms = 100) {
|
|
900
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
901
|
-
}
|
|
902
|
-
|
|
903
977
|
state_DTR = false;
|
|
904
978
|
state_RTS = false;
|
|
905
979
|
|
|
@@ -930,14 +1004,11 @@ export class ESPLoader extends EventTarget {
|
|
|
930
1004
|
});
|
|
931
1005
|
}
|
|
932
1006
|
|
|
933
|
-
/**
|
|
934
|
-
* Helper function to run a sequence of signal changes
|
|
935
|
-
* Automatically detects WebUSB vs Web Serial and calls appropriate methods
|
|
936
|
-
*/
|
|
937
1007
|
private async runSignalSequence(
|
|
938
1008
|
steps: Array<{ dtr?: boolean; rts?: boolean; delayMs?: number }>,
|
|
939
1009
|
): Promise<void> {
|
|
940
|
-
const webusb =
|
|
1010
|
+
const webusb =
|
|
1011
|
+
(this.port as unknown as { isWebUSB?: boolean }).isWebUSB === true;
|
|
941
1012
|
for (const step of steps) {
|
|
942
1013
|
if (step.dtr !== undefined && step.rts !== undefined) {
|
|
943
1014
|
if (webusb) {
|
|
@@ -947,14 +1018,18 @@ export class ESPLoader extends EventTarget {
|
|
|
947
1018
|
}
|
|
948
1019
|
} else {
|
|
949
1020
|
if (step.dtr !== undefined) {
|
|
950
|
-
webusb
|
|
951
|
-
|
|
952
|
-
|
|
1021
|
+
if (webusb) {
|
|
1022
|
+
await this.setDTRWebUSB(step.dtr);
|
|
1023
|
+
} else {
|
|
1024
|
+
await this.setDTR(step.dtr);
|
|
1025
|
+
}
|
|
953
1026
|
}
|
|
954
1027
|
if (step.rts !== undefined) {
|
|
955
|
-
webusb
|
|
956
|
-
|
|
957
|
-
|
|
1028
|
+
if (webusb) {
|
|
1029
|
+
await this.setRTSWebUSB(step.rts);
|
|
1030
|
+
} else {
|
|
1031
|
+
await this.setRTS(step.rts);
|
|
1032
|
+
}
|
|
958
1033
|
}
|
|
959
1034
|
}
|
|
960
1035
|
if (step.delayMs) await sleep(step.delayMs);
|
|
@@ -1332,8 +1407,14 @@ export class ESPLoader extends EventTarget {
|
|
|
1332
1407
|
}
|
|
1333
1408
|
}
|
|
1334
1409
|
|
|
1335
|
-
// Add general fallback strategies only for
|
|
1336
|
-
|
|
1410
|
+
// Add general fallback strategies only for Native USB chips (not USB-Serial)
|
|
1411
|
+
// and only for chips not already handled by specific blocks above
|
|
1412
|
+
if (
|
|
1413
|
+
!isUSBSerialChip &&
|
|
1414
|
+
!isCP2102 &&
|
|
1415
|
+
!isESP32S2NativeUSB &&
|
|
1416
|
+
!isUSBJTAGSerial
|
|
1417
|
+
) {
|
|
1337
1418
|
// Classic reset (for chips not handled above)
|
|
1338
1419
|
if (portInfo.usbVendorId !== 0x1a86) {
|
|
1339
1420
|
resetStrategies.push({
|
|
@@ -1369,7 +1450,7 @@ export class ESPLoader extends EventTarget {
|
|
|
1369
1450
|
});
|
|
1370
1451
|
|
|
1371
1452
|
// WebUSB Strategy: USB-JTAG/Serial fallback
|
|
1372
|
-
if (!
|
|
1453
|
+
if (!isEspressifUSB) {
|
|
1373
1454
|
resetStrategies.push({
|
|
1374
1455
|
name: "USB-JTAG/Serial fallback (WebUSB)",
|
|
1375
1456
|
fn: async function () {
|
|
@@ -1457,11 +1538,11 @@ export class ESPLoader extends EventTarget {
|
|
|
1457
1538
|
try {
|
|
1458
1539
|
await Promise.race([syncPromise, timeoutPromise]);
|
|
1459
1540
|
// Sync succeeded
|
|
1460
|
-
this.logger.
|
|
1541
|
+
this.logger.debug(
|
|
1461
1542
|
`Connected CDC/JTAG successfully with ${strategy.name} reset.`,
|
|
1462
1543
|
);
|
|
1463
1544
|
return;
|
|
1464
|
-
} catch
|
|
1545
|
+
} catch {
|
|
1465
1546
|
throw new Error("Sync timeout or abandoned");
|
|
1466
1547
|
}
|
|
1467
1548
|
}
|
|
@@ -1500,41 +1581,20 @@ export class ESPLoader extends EventTarget {
|
|
|
1500
1581
|
|
|
1501
1582
|
/**
|
|
1502
1583
|
* @name watchdogReset
|
|
1503
|
-
* Watchdog reset for ESP32-S2/S3/
|
|
1584
|
+
* Watchdog reset for ESP32-S2/S3/P4 with USB-OTG or USB-JTAG/Serial
|
|
1504
1585
|
* Uses RTC watchdog timer to reset the chip - works when DTR/RTS signals are not available
|
|
1505
1586
|
* This is an alias for rtcWdtResetChipSpecific() for backwards compatibility
|
|
1587
|
+
* Note: ESP32-C3, ESP32-C5, ESP32-C6 do NOT boot correctly after WDT reset
|
|
1506
1588
|
*/
|
|
1507
1589
|
async watchdogReset() {
|
|
1508
1590
|
await this.rtcWdtResetChipSpecific();
|
|
1509
1591
|
}
|
|
1510
1592
|
|
|
1511
1593
|
/**
|
|
1512
|
-
*
|
|
1513
|
-
* Reads from EFUSE registers and calculates revision
|
|
1514
|
-
*/
|
|
1515
|
-
async getChipRevisionC3(): Promise<number> {
|
|
1516
|
-
if (this.chipFamily !== CHIP_FAMILY_ESP32C3) {
|
|
1517
|
-
return 0;
|
|
1518
|
-
}
|
|
1519
|
-
|
|
1520
|
-
// Read EFUSE_RD_MAC_SPI_SYS_3_REG (bits [20:18] = lower 3 bits of revision)
|
|
1521
|
-
const word3 = await this.readRegister(ESP32C3_EFUSE_RD_MAC_SPI_SYS_3_REG);
|
|
1522
|
-
const low = (word3 >> 18) & 0x07;
|
|
1523
|
-
|
|
1524
|
-
// Read EFUSE_RD_MAC_SPI_SYS_5_REG (bits [25:23] = upper 3 bits of revision)
|
|
1525
|
-
const word5 = await this.readRegister(ESP32C3_EFUSE_RD_MAC_SPI_SYS_5_REG);
|
|
1526
|
-
const hi = (word5 >> 23) & 0x07;
|
|
1527
|
-
|
|
1528
|
-
// Combine: upper 3 bits from word5, lower 3 bits from word3
|
|
1529
|
-
const revision = (hi << 3) | low;
|
|
1530
|
-
|
|
1531
|
-
return revision;
|
|
1532
|
-
}
|
|
1533
|
-
|
|
1534
|
-
/**
|
|
1535
|
-
* RTC watchdog timer reset for ESP32-S2, ESP32-S3, ESP32-C3, ESP32-C5, ESP32-C6, and ESP32-P4
|
|
1594
|
+
* RTC watchdog timer reset for ESP32-S2, ESP32-S3, and ESP32-P4
|
|
1536
1595
|
* Uses specific registers for each chip family
|
|
1537
|
-
* Note: ESP32-
|
|
1596
|
+
* Note: ESP32-C3 does NOT boot correctly after WDT reset
|
|
1597
|
+
* Note: ESP32-C5, ESP32-C6, ESP32-C61, ESP32-H2 do NOT support WDT reset (no usable RTC WDT path)
|
|
1538
1598
|
*/
|
|
1539
1599
|
public async rtcWdtResetChipSpecific(): Promise<void> {
|
|
1540
1600
|
this.logger.debug("Hard resetting with watchdog timer...");
|
|
@@ -1554,20 +1614,6 @@ export class ESPLoader extends EventTarget {
|
|
|
1554
1614
|
WDTCONFIG0_REG = ESP32S3_RTC_CNTL_WDTCONFIG0_REG;
|
|
1555
1615
|
WDTCONFIG1_REG = ESP32S3_RTC_CNTL_WDTCONFIG1_REG;
|
|
1556
1616
|
WDT_WKEY = ESP32S3_RTC_CNTL_WDT_WKEY;
|
|
1557
|
-
} else if (this.chipFamily === CHIP_FAMILY_ESP32C3) {
|
|
1558
|
-
WDTWPROTECT_REG = ESP32C3_RTC_CNTL_WDTWPROTECT_REG;
|
|
1559
|
-
WDTCONFIG0_REG = ESP32C3_RTC_CNTL_WDTCONFIG0_REG;
|
|
1560
|
-
WDTCONFIG1_REG = ESP32C3_RTC_CNTL_WDTCONFIG1_REG;
|
|
1561
|
-
WDT_WKEY = ESP32C3_RTC_CNTL_WDT_WKEY;
|
|
1562
|
-
} else if (
|
|
1563
|
-
this.chipFamily === CHIP_FAMILY_ESP32C5 ||
|
|
1564
|
-
this.chipFamily === CHIP_FAMILY_ESP32C6
|
|
1565
|
-
) {
|
|
1566
|
-
// C5 and C6 use LP_WDT (Low Power Watchdog Timer)
|
|
1567
|
-
WDTWPROTECT_REG = ESP32C5_C6_RTC_CNTL_WDTWPROTECT_REG;
|
|
1568
|
-
WDTCONFIG0_REG = ESP32C5_C6_RTC_CNTL_WDTCONFIG0_REG;
|
|
1569
|
-
WDTCONFIG1_REG = ESP32C5_C6_RTC_CNTL_WDTCONFIG1_REG;
|
|
1570
|
-
WDT_WKEY = ESP32C5_C6_RTC_CNTL_WDT_WKEY;
|
|
1571
1617
|
} else if (this.chipFamily === CHIP_FAMILY_ESP32P4) {
|
|
1572
1618
|
// P4 uses LP_WDT (Low Power Watchdog Timer)
|
|
1573
1619
|
WDTWPROTECT_REG = ESP32P4_RTC_CNTL_WDTWPROTECT_REG;
|
|
@@ -1594,29 +1640,151 @@ export class ESPLoader extends EventTarget {
|
|
|
1594
1640
|
await this.writeRegister(WDTWPROTECT_REG, 0, undefined, 0);
|
|
1595
1641
|
|
|
1596
1642
|
// Wait for reset to take effect
|
|
1597
|
-
await
|
|
1643
|
+
await sleep(500);
|
|
1598
1644
|
}
|
|
1599
1645
|
|
|
1600
1646
|
/**
|
|
1601
|
-
*
|
|
1602
|
-
*
|
|
1647
|
+
* Reset device from bootloader mode to firmware mode
|
|
1648
|
+
* Automatically selects the correct reset strategy based on USB connection type
|
|
1649
|
+
* @param clearForceDownloadFlag - If true, clears the force download boot flag (USB-OTG only)
|
|
1650
|
+
* @returns true if port will change (USB-OTG), false otherwise
|
|
1603
1651
|
*/
|
|
1604
|
-
|
|
1605
|
-
|
|
1652
|
+
public async resetToFirmwareMode(
|
|
1653
|
+
clearForceDownloadFlag = true,
|
|
1654
|
+
): Promise<boolean> {
|
|
1655
|
+
this.logger.debug("Resetting from bootloader to firmware mode...");
|
|
1606
1656
|
|
|
1607
|
-
|
|
1608
|
-
//
|
|
1609
|
-
await this.
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1657
|
+
try {
|
|
1658
|
+
// Detect USB connection type
|
|
1659
|
+
const isUsbJtagOrOtg = await this.detectUsbConnectionType();
|
|
1660
|
+
|
|
1661
|
+
if (isUsbJtagOrOtg) {
|
|
1662
|
+
// USB-JTAG/OTG devices need special handling
|
|
1663
|
+
this.logger.debug("USB-JTAG/OTG detected - checking WDT reset support");
|
|
1664
|
+
|
|
1665
|
+
// Get detailed USB mode information
|
|
1666
|
+
let usbMode: {
|
|
1667
|
+
mode: "uart" | "usb-jtag-serial" | "usb-otg";
|
|
1668
|
+
uartNo: number;
|
|
1669
|
+
};
|
|
1670
|
+
try {
|
|
1671
|
+
usbMode = await this.getUsbMode();
|
|
1672
|
+
this.logger.debug(
|
|
1673
|
+
`USB mode: ${usbMode.mode} (uartNo=${usbMode.uartNo})`,
|
|
1674
|
+
);
|
|
1675
|
+
} catch (err) {
|
|
1676
|
+
this.logger.debug(`Could not get USB mode: ${err}`);
|
|
1677
|
+
// Fall back to generic USB-JTAG/OTG handling
|
|
1678
|
+
usbMode = { mode: "usb-jtag-serial", uartNo: 0 };
|
|
1679
|
+
}
|
|
1680
|
+
|
|
1681
|
+
// WDT reset is not needed for ESP32-C3
|
|
1682
|
+
// WDT reset is supported by: ESP32-S2, ESP32-S3, ESP32-P4
|
|
1683
|
+
// WDT reset is NOT supported by: ESP32-C5, ESP32-C6, ESP32-C61, ESP32-H2
|
|
1684
|
+
const supportsWdtReset =
|
|
1685
|
+
this.chipFamily === CHIP_FAMILY_ESP32S2 ||
|
|
1686
|
+
this.chipFamily === CHIP_FAMILY_ESP32S3 ||
|
|
1687
|
+
this.chipFamily === CHIP_FAMILY_ESP32P4;
|
|
1688
|
+
|
|
1689
|
+
if (!supportsWdtReset) {
|
|
1690
|
+
this.logger.debug(
|
|
1691
|
+
`${this.chipName} does not support WDT reset - using classic reset instead`,
|
|
1692
|
+
);
|
|
1693
|
+
|
|
1694
|
+
// Use classic reset for chips without WDT support
|
|
1695
|
+
await this.hardResetToFirmware();
|
|
1696
|
+
this.logger.debug("Classic reset to firmware complete");
|
|
1697
|
+
return false; // Port stays open
|
|
1698
|
+
}
|
|
1699
|
+
|
|
1700
|
+
// WDT reset is supported - proceed with WDT reset logic
|
|
1701
|
+
this.logger.debug(
|
|
1702
|
+
`${this.chipName} supports WDT reset - using WDT reset strategy`,
|
|
1703
|
+
);
|
|
1704
|
+
|
|
1705
|
+
// CRITICAL: WDT register writes require ROM (not stub) and baudrate 115200
|
|
1706
|
+
|
|
1707
|
+
// If on stub, need to return to ROM first
|
|
1708
|
+
if (this.IS_STUB) {
|
|
1709
|
+
this.logger.debug("On stub - returning to ROM before WDT reset");
|
|
1710
|
+
|
|
1711
|
+
// Change baudrate back to ROM baudrate if needed
|
|
1712
|
+
if (this.currentBaudRate !== ESP_ROM_BAUD) {
|
|
1713
|
+
this.logger.debug(
|
|
1714
|
+
`Changing baudrate from ${this.currentBaudRate} to ${ESP_ROM_BAUD}`,
|
|
1715
|
+
);
|
|
1716
|
+
await this.reconfigurePort(ESP_ROM_BAUD);
|
|
1717
|
+
this.currentBaudRate = ESP_ROM_BAUD;
|
|
1718
|
+
this.logger.debug("Baudrate changed to 115200");
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1721
|
+
// CRITICAL: Temporarily clear console mode flag so hardReset(true) works
|
|
1722
|
+
const wasInConsoleMode = this._consoleMode;
|
|
1723
|
+
this._consoleMode = false;
|
|
1724
|
+
|
|
1725
|
+
// Reset to bootloader (ROM)
|
|
1726
|
+
await this.hardReset(true);
|
|
1727
|
+
await sleep(200);
|
|
1728
|
+
|
|
1729
|
+
// Restore console mode flag
|
|
1730
|
+
this._consoleMode = wasInConsoleMode;
|
|
1731
|
+
|
|
1732
|
+
// Sync with ROM
|
|
1733
|
+
await this.sync();
|
|
1734
|
+
this.IS_STUB = false;
|
|
1735
|
+
this.logger.debug("Now on ROM");
|
|
1736
|
+
} else {
|
|
1737
|
+
// Even if not on stub, ensure baudrate is 115200 for WDT register writes
|
|
1738
|
+
if (this.currentBaudRate !== ESP_ROM_BAUD) {
|
|
1739
|
+
this.logger.debug(
|
|
1740
|
+
`Not on stub, but baudrate is ${this.currentBaudRate} - changing to ${ESP_ROM_BAUD} for WDT reset`,
|
|
1741
|
+
);
|
|
1742
|
+
await this.reconfigurePort(ESP_ROM_BAUD);
|
|
1743
|
+
this.currentBaudRate = ESP_ROM_BAUD;
|
|
1744
|
+
this.logger.debug("Baudrate changed to 115200");
|
|
1745
|
+
}
|
|
1746
|
+
}
|
|
1747
|
+
|
|
1748
|
+
// Clear force download boot flag if requested (USB-OTG only)
|
|
1749
|
+
if (clearForceDownloadFlag && usbMode.mode === "usb-otg") {
|
|
1750
|
+
const flagCleared = await this._clearForceDownloadBootIfNeeded();
|
|
1751
|
+
if (flagCleared) {
|
|
1752
|
+
this.logger.debug("Force download boot flag cleared");
|
|
1753
|
+
}
|
|
1754
|
+
}
|
|
1755
|
+
|
|
1756
|
+
// Perform WDT reset to boot into firmware
|
|
1757
|
+
await this.rtcWdtResetChipSpecific();
|
|
1758
|
+
this.logger.debug("WDT reset performed - device will boot to firmware");
|
|
1759
|
+
|
|
1760
|
+
// Check if port will change after WDT reset
|
|
1761
|
+
// USB-OTG (ESP32-S2/P4): Port always changes
|
|
1762
|
+
// USB-JTAG/Serial (ESP32-S3/C3/C5/C6/C61/H2/P4): Port may change depending on platform
|
|
1763
|
+
const portWillChange =
|
|
1764
|
+
usbMode.mode === "usb-otg" || usbMode.mode === "usb-jtag-serial";
|
|
1765
|
+
|
|
1766
|
+
if (portWillChange) {
|
|
1767
|
+
this.logger.debug(
|
|
1768
|
+
`Port will change after WDT reset (${usbMode.mode}) - port reselection needed`,
|
|
1769
|
+
);
|
|
1770
|
+
return true;
|
|
1771
|
+
}
|
|
1772
|
+
|
|
1773
|
+
return false;
|
|
1774
|
+
} else {
|
|
1775
|
+
// External serial chip - use classic reset to firmware
|
|
1776
|
+
this.logger.debug(
|
|
1777
|
+
"External serial chip detected - using classic reset",
|
|
1778
|
+
);
|
|
1779
|
+
|
|
1780
|
+
await this.hardResetToFirmware();
|
|
1781
|
+
this.logger.debug("Classic reset to firmware complete");
|
|
1782
|
+
return false;
|
|
1783
|
+
}
|
|
1784
|
+
} catch (err) {
|
|
1785
|
+
this.logger.error(`Failed to reset to firmware mode: ${err}`);
|
|
1786
|
+
throw err;
|
|
1618
1787
|
}
|
|
1619
|
-
return false;
|
|
1620
1788
|
}
|
|
1621
1789
|
|
|
1622
1790
|
async hardReset(bootloader = false) {
|
|
@@ -1630,58 +1798,98 @@ export class ESPLoader extends EventTarget {
|
|
|
1630
1798
|
}
|
|
1631
1799
|
// Simple hardware reset to restart firmware (IO0=HIGH)
|
|
1632
1800
|
this.logger.debug("Performing hardware reset (console mode)...");
|
|
1633
|
-
await this.
|
|
1801
|
+
await this.resetInConsoleMode();
|
|
1634
1802
|
this.logger.debug("Hardware reset complete");
|
|
1635
1803
|
return;
|
|
1636
1804
|
}
|
|
1637
1805
|
|
|
1638
1806
|
if (bootloader) {
|
|
1639
|
-
//
|
|
1807
|
+
// Enter bootloader/flash mode
|
|
1640
1808
|
if (this.port.getInfo().usbProductId === USB_JTAG_SERIAL_PID) {
|
|
1641
1809
|
await this.hardResetUSBJTAGSerial();
|
|
1642
|
-
this.logger.debug("USB-JTAG/Serial reset.");
|
|
1810
|
+
this.logger.debug("USB-JTAG/Serial reset to bootloader.");
|
|
1643
1811
|
} else {
|
|
1644
1812
|
await this.hardResetClassic();
|
|
1645
|
-
this.logger.debug("Classic reset.");
|
|
1813
|
+
this.logger.debug("Classic reset to bootloader.");
|
|
1646
1814
|
}
|
|
1647
1815
|
} else {
|
|
1648
|
-
//
|
|
1649
|
-
//
|
|
1650
|
-
this.logger.debug("
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
//
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1816
|
+
// Reset to firmware mode (exit bootloader)
|
|
1817
|
+
// Use intelligent reset strategy based on USB connection type
|
|
1818
|
+
this.logger.debug("Resetting to firmware mode...");
|
|
1819
|
+
|
|
1820
|
+
// Detect USB connection type to choose correct reset method
|
|
1821
|
+
const isUsbJtagOrOtg = await this.detectUsbConnectionType();
|
|
1822
|
+
|
|
1823
|
+
if (isUsbJtagOrOtg) {
|
|
1824
|
+
// USB-JTAG/OTG devices: Check if chip supports WDT reset
|
|
1825
|
+
// Only S2, S3, P4 support WDT reset correctly
|
|
1826
|
+
// C3, C5, C6, C61, H2 do NOT boot correctly after WDT reset
|
|
1827
|
+
const supportsWdtReset =
|
|
1828
|
+
this.chipFamily === CHIP_FAMILY_ESP32S2 ||
|
|
1829
|
+
this.chipFamily === CHIP_FAMILY_ESP32S3 ||
|
|
1830
|
+
this.chipFamily === CHIP_FAMILY_ESP32P4;
|
|
1831
|
+
|
|
1832
|
+
if (supportsWdtReset) {
|
|
1833
|
+
this.logger.debug("USB-JTAG/OTG detected - using WDT reset");
|
|
1834
|
+
|
|
1835
|
+
// Get USB mode details
|
|
1836
|
+
let usbMode: {
|
|
1837
|
+
mode: "uart" | "usb-jtag-serial" | "usb-otg";
|
|
1838
|
+
uartNo: number;
|
|
1839
|
+
};
|
|
1840
|
+
try {
|
|
1841
|
+
usbMode = await this.getUsbMode();
|
|
1842
|
+
this.logger.debug(
|
|
1843
|
+
`USB mode: ${usbMode.mode} (uartNo=${usbMode.uartNo})`,
|
|
1844
|
+
);
|
|
1845
|
+
} catch (err) {
|
|
1846
|
+
this.logger.debug(`Could not get USB mode: ${err}`);
|
|
1847
|
+
usbMode = { mode: "usb-jtag-serial", uartNo: 0 };
|
|
1848
|
+
}
|
|
1849
|
+
|
|
1850
|
+
// Clear force download flag for USB-OTG devices
|
|
1851
|
+
if (usbMode.mode === "usb-otg") {
|
|
1852
|
+
try {
|
|
1853
|
+
const flagCleared = await this._clearForceDownloadBootIfNeeded();
|
|
1854
|
+
if (flagCleared) {
|
|
1855
|
+
this.logger.debug("Force download boot flag cleared");
|
|
1856
|
+
}
|
|
1857
|
+
} catch (err) {
|
|
1858
|
+
this.logger.debug(`Could not clear force download flag: ${err}`);
|
|
1859
|
+
}
|
|
1860
|
+
}
|
|
1861
|
+
|
|
1862
|
+
// Perform WDT reset
|
|
1863
|
+
await this.rtcWdtResetChipSpecific();
|
|
1864
|
+
this.logger.debug(`${this.chipName}: WDT reset to firmware complete`);
|
|
1865
|
+
return;
|
|
1866
|
+
} else {
|
|
1867
|
+
// C3, C5, C6, etc. - use classic reset (like external serial chips)
|
|
1868
|
+
this.logger.debug(
|
|
1869
|
+
`${this.chipName} does not support WDT reset - using classic reset instead`,
|
|
1870
|
+
);
|
|
1871
|
+
}
|
|
1872
|
+
} else {
|
|
1873
|
+
// External serial chip: Use classic reset
|
|
1874
|
+
this.logger.debug(
|
|
1875
|
+
"External serial chip detected - using classic reset",
|
|
1876
|
+
);
|
|
1669
1877
|
}
|
|
1670
1878
|
|
|
1671
|
-
//
|
|
1879
|
+
// Classic reset: used for external serial chips and USB-JTAG chips that do not support WDT reset
|
|
1672
1880
|
if (this.isWebUSB()) {
|
|
1673
1881
|
// WebUSB: Use longer delays for better compatibility
|
|
1674
1882
|
await this.setRTSWebUSB(true); // EN->LOW
|
|
1675
|
-
await
|
|
1883
|
+
await sleep(200);
|
|
1676
1884
|
await this.setRTSWebUSB(false);
|
|
1677
|
-
await
|
|
1678
|
-
this.logger.debug("Hard reset (WebUSB).");
|
|
1885
|
+
await sleep(200);
|
|
1886
|
+
this.logger.debug("Hard reset to firmware (WebUSB).");
|
|
1679
1887
|
} else {
|
|
1680
1888
|
// Web Serial: Standard reset
|
|
1681
1889
|
await this.setRTS(true); // EN->LOW
|
|
1682
|
-
await
|
|
1890
|
+
await sleep(100);
|
|
1683
1891
|
await this.setRTS(false);
|
|
1684
|
-
this.logger.debug("Hard reset.");
|
|
1892
|
+
this.logger.debug("Hard reset to firmware.");
|
|
1685
1893
|
}
|
|
1686
1894
|
}
|
|
1687
1895
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -2128,33 +2336,6 @@ export class ESPLoader extends EventTarget {
|
|
|
2128
2336
|
return 26;
|
|
2129
2337
|
}
|
|
2130
2338
|
|
|
2131
|
-
private async setBaudrateC5Rom(baud: number) {
|
|
2132
|
-
const crystalFreqRomExpect = await this.getC5CrystalFreqRomExpect();
|
|
2133
|
-
const crystalFreqDetect = await this.getC5CrystalFreqDetected();
|
|
2134
|
-
this.logger.log(
|
|
2135
|
-
`ROM expects crystal freq: ${crystalFreqRomExpect} MHz, detected ${crystalFreqDetect} MHz.`,
|
|
2136
|
-
);
|
|
2137
|
-
|
|
2138
|
-
let baudRate = baud;
|
|
2139
|
-
if (crystalFreqDetect === 48 && crystalFreqRomExpect === 40) {
|
|
2140
|
-
baudRate = Math.trunc((baud * 40) / 48);
|
|
2141
|
-
} else if (crystalFreqDetect === 40 && crystalFreqRomExpect === 48) {
|
|
2142
|
-
baudRate = Math.trunc((baud * 48) / 40);
|
|
2143
|
-
}
|
|
2144
|
-
|
|
2145
|
-
this.logger.log(`Changing baud rate to ${baudRate}...`);
|
|
2146
|
-
try {
|
|
2147
|
-
const buffer = pack("<II", baudRate, 0);
|
|
2148
|
-
await this.checkCommand(ESP_CHANGE_BAUDRATE, buffer);
|
|
2149
|
-
} catch (e) {
|
|
2150
|
-
this.logger.error(`Baudrate change error: ${e}`);
|
|
2151
|
-
throw new Error(
|
|
2152
|
-
`Unable to change the baud rate to ${baudRate}: No response from set baud rate command.`,
|
|
2153
|
-
);
|
|
2154
|
-
}
|
|
2155
|
-
this.logger.log("Changed.");
|
|
2156
|
-
}
|
|
2157
|
-
|
|
2158
2339
|
async setBaudrate(baud: number) {
|
|
2159
2340
|
const chipFamily = this._parent ? this._parent.chipFamily : this.chipFamily;
|
|
2160
2341
|
|
|
@@ -2162,7 +2343,6 @@ export class ESPLoader extends EventTarget {
|
|
|
2162
2343
|
await this.setBaudrateC5Rom(baud);
|
|
2163
2344
|
} else {
|
|
2164
2345
|
try {
|
|
2165
|
-
// Send ESP_ROM_BAUD(115200) as the old one if running STUB otherwise 0
|
|
2166
2346
|
const buffer = pack("<II", baud, this.IS_STUB ? ESP_ROM_BAUD : 0);
|
|
2167
2347
|
await this.checkCommand(ESP_CHANGE_BAUDRATE, buffer);
|
|
2168
2348
|
} catch (e) {
|
|
@@ -2202,7 +2382,34 @@ export class ESPLoader extends EventTarget {
|
|
|
2202
2382
|
);
|
|
2203
2383
|
}
|
|
2204
2384
|
|
|
2205
|
-
this.logger.
|
|
2385
|
+
this.logger.debug(`Changed baud rate to ${baud}`);
|
|
2386
|
+
}
|
|
2387
|
+
|
|
2388
|
+
private async setBaudrateC5Rom(baud: number) {
|
|
2389
|
+
const crystalFreqRomExpect = await this.getC5CrystalFreqRomExpect();
|
|
2390
|
+
const crystalFreqDetect = await this.getC5CrystalFreqDetected();
|
|
2391
|
+
this.logger.log(
|
|
2392
|
+
`ROM expects crystal freq: ${crystalFreqRomExpect} MHz, detected ${crystalFreqDetect} MHz.`,
|
|
2393
|
+
);
|
|
2394
|
+
|
|
2395
|
+
let baudRate = baud;
|
|
2396
|
+
if (crystalFreqDetect === 48 && crystalFreqRomExpect === 40) {
|
|
2397
|
+
baudRate = Math.trunc((baud * 40) / 48);
|
|
2398
|
+
} else if (crystalFreqDetect === 40 && crystalFreqRomExpect === 48) {
|
|
2399
|
+
baudRate = Math.trunc((baud * 48) / 40);
|
|
2400
|
+
}
|
|
2401
|
+
|
|
2402
|
+
this.logger.log(`Changing baud rate to ${baudRate}...`);
|
|
2403
|
+
try {
|
|
2404
|
+
const buffer = pack("<II", baudRate, 0);
|
|
2405
|
+
await this.checkCommand(ESP_CHANGE_BAUDRATE, buffer);
|
|
2406
|
+
} catch (e) {
|
|
2407
|
+
this.logger.error(`Baudrate change error: ${e}`);
|
|
2408
|
+
throw new Error(
|
|
2409
|
+
`Unable to change the baud rate to ${baudRate}: No response from set baud rate command.`,
|
|
2410
|
+
);
|
|
2411
|
+
}
|
|
2412
|
+
this.logger.log("Changed.");
|
|
2206
2413
|
}
|
|
2207
2414
|
|
|
2208
2415
|
async reconfigurePort(baud: number) {
|
|
@@ -2271,9 +2478,9 @@ export class ESPLoader extends EventTarget {
|
|
|
2271
2478
|
|
|
2272
2479
|
// Restart Readloop
|
|
2273
2480
|
this.readLoop();
|
|
2274
|
-
} catch
|
|
2275
|
-
// this.logger.error(`Reconfigure port error
|
|
2276
|
-
// throw new Error(`Unable to change the baud rate to ${baud}
|
|
2481
|
+
} catch {
|
|
2482
|
+
// this.logger.error(`Reconfigure port error`);
|
|
2483
|
+
// throw new Error(`Unable to change the baud rate to ${baud}`);
|
|
2277
2484
|
} finally {
|
|
2278
2485
|
// Always reset flag, even on error or early return
|
|
2279
2486
|
this._isReconfiguring = false;
|
|
@@ -2401,6 +2608,9 @@ export class ESPLoader extends EventTarget {
|
|
|
2401
2608
|
);
|
|
2402
2609
|
}
|
|
2403
2610
|
|
|
2611
|
+
const paddedData = padTo(new Uint8Array(binaryData), 4);
|
|
2612
|
+
binaryData = paddedData.buffer as ArrayBuffer;
|
|
2613
|
+
|
|
2404
2614
|
const uncompressedFilesize = binaryData.byteLength;
|
|
2405
2615
|
let compressedFilesize = 0;
|
|
2406
2616
|
|
|
@@ -2804,20 +3014,11 @@ export class ESPLoader extends EventTarget {
|
|
|
2804
3014
|
return status;
|
|
2805
3015
|
}
|
|
2806
3016
|
async detectFlashSize() {
|
|
2807
|
-
this.logger.
|
|
3017
|
+
this.logger.debug("Detecting Flash Size");
|
|
2808
3018
|
|
|
2809
3019
|
const flashId = await this.flashId();
|
|
2810
|
-
const manufacturer = flashId & 0xff;
|
|
2811
3020
|
const flashIdLowbyte = (flashId >> 16) & 0xff;
|
|
2812
3021
|
|
|
2813
|
-
this.logger.log(`FlashId: ${toHex(flashId)}`);
|
|
2814
|
-
this.logger.log(`Flash Manufacturer: ${manufacturer.toString(16)}`);
|
|
2815
|
-
this.logger.log(
|
|
2816
|
-
`Flash Device: ${((flashId >> 8) & 0xff).toString(
|
|
2817
|
-
16,
|
|
2818
|
-
)}${flashIdLowbyte.toString(16)}`,
|
|
2819
|
-
);
|
|
2820
|
-
|
|
2821
3022
|
this.flashSize = DETECTED_FLASH_SIZES[flashIdLowbyte];
|
|
2822
3023
|
this.logger.log(`Auto-detected Flash size: ${this.flashSize}`);
|
|
2823
3024
|
}
|
|
@@ -2906,7 +3107,7 @@ export class ESPLoader extends EventTarget {
|
|
|
2906
3107
|
const ramBlock = USB_RAM_BLOCK;
|
|
2907
3108
|
|
|
2908
3109
|
// Upload
|
|
2909
|
-
this.logger.
|
|
3110
|
+
this.logger.debug("Uploading stub...");
|
|
2910
3111
|
for (const field of ["text", "data"] as const) {
|
|
2911
3112
|
const fieldData = stub[field];
|
|
2912
3113
|
const offset = stub[`${field}_start` as "text_start" | "data_start"];
|
|
@@ -2930,7 +3131,7 @@ export class ESPLoader extends EventTarget {
|
|
|
2930
3131
|
if (pChar != "OHAI") {
|
|
2931
3132
|
throw new Error("Failed to start stub. Unexpected response: " + pChar);
|
|
2932
3133
|
}
|
|
2933
|
-
this.logger.
|
|
3134
|
+
this.logger.debug("Stub is now running...");
|
|
2934
3135
|
const espStubLoader = new EspStubLoader(this.port, this.logger, this);
|
|
2935
3136
|
|
|
2936
3137
|
// Try to autodetect the flash size.
|
|
@@ -3057,114 +3258,30 @@ export class ESPLoader extends EventTarget {
|
|
|
3057
3258
|
await this._writeChain;
|
|
3058
3259
|
}
|
|
3059
3260
|
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
3261
|
+
async disconnect() {
|
|
3262
|
+
if (this._parent) {
|
|
3263
|
+
await this._parent.disconnect();
|
|
3264
|
+
return;
|
|
3265
|
+
}
|
|
3266
|
+
if (!this.port.writable) {
|
|
3267
|
+
// this.logger.debug("Port already closed, skipping disconnect");
|
|
3268
|
+
return;
|
|
3269
|
+
}
|
|
3068
3270
|
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
bufNoAddr = ESP32S2_UARTDEV_BUF_NO;
|
|
3076
|
-
otgVal = ESP32S2_UARTDEV_BUF_NO_USB_OTG;
|
|
3077
|
-
break;
|
|
3078
|
-
case CHIP_FAMILY_ESP32S3:
|
|
3079
|
-
bufNoAddr = ESP32S3_UARTDEV_BUF_NO;
|
|
3080
|
-
jtagSerialVal = ESP32S3_UARTDEV_BUF_NO_USB_JTAG_SERIAL;
|
|
3081
|
-
otgVal = ESP32S3_UARTDEV_BUF_NO_USB_OTG;
|
|
3082
|
-
break;
|
|
3083
|
-
case CHIP_FAMILY_ESP32C3: {
|
|
3084
|
-
const bssAddr = revision < 101 ? 0x3fcdf064 : 0x3fcdf060;
|
|
3085
|
-
bufNoAddr = bssAddr + ESP32C3_BUF_UART_NO_OFFSET;
|
|
3086
|
-
jtagSerialVal = ESP32C3_UARTDEV_BUF_NO_USB_JTAG_SERIAL;
|
|
3087
|
-
break;
|
|
3088
|
-
}
|
|
3089
|
-
case CHIP_FAMILY_ESP32C5:
|
|
3090
|
-
bufNoAddr = ESP32C5_UARTDEV_BUF_NO;
|
|
3091
|
-
jtagSerialVal = ESP32C5_UARTDEV_BUF_NO_USB_JTAG_SERIAL;
|
|
3092
|
-
break;
|
|
3093
|
-
case CHIP_FAMILY_ESP32C6:
|
|
3094
|
-
bufNoAddr = ESP32C6_UARTDEV_BUF_NO;
|
|
3095
|
-
jtagSerialVal = ESP32C6_UARTDEV_BUF_NO_USB_JTAG_SERIAL;
|
|
3096
|
-
break;
|
|
3097
|
-
case CHIP_FAMILY_ESP32C61:
|
|
3098
|
-
bufNoAddr =
|
|
3099
|
-
revision <= 200
|
|
3100
|
-
? ESP32C61_UARTDEV_BUF_NO_REV_LE2
|
|
3101
|
-
: ESP32C61_UARTDEV_BUF_NO_REV_GT2;
|
|
3102
|
-
jtagSerialVal =
|
|
3103
|
-
revision <= 200
|
|
3104
|
-
? ESP32C61_UARTDEV_BUF_NO_USB_JTAG_SERIAL_REV_LE2
|
|
3105
|
-
: ESP32C61_UARTDEV_BUF_NO_USB_JTAG_SERIAL_REV_GT2;
|
|
3106
|
-
break;
|
|
3107
|
-
case CHIP_FAMILY_ESP32H2:
|
|
3108
|
-
bufNoAddr = ESP32H2_UARTDEV_BUF_NO;
|
|
3109
|
-
jtagSerialVal = ESP32H2_UARTDEV_BUF_NO_USB_JTAG_SERIAL;
|
|
3110
|
-
break;
|
|
3111
|
-
case CHIP_FAMILY_ESP32H4:
|
|
3112
|
-
bufNoAddr = ESP32H4_UARTDEV_BUF_NO;
|
|
3113
|
-
jtagSerialVal = ESP32H4_UARTDEV_BUF_NO_USB_JTAG_SERIAL;
|
|
3114
|
-
break;
|
|
3115
|
-
case CHIP_FAMILY_ESP32P4:
|
|
3116
|
-
bufNoAddr =
|
|
3117
|
-
revision < 300
|
|
3118
|
-
? ESP32P4_UARTDEV_BUF_NO_REV0
|
|
3119
|
-
: ESP32P4_UARTDEV_BUF_NO_REV300;
|
|
3120
|
-
jtagSerialVal = ESP32P4_UARTDEV_BUF_NO_USB_JTAG_SERIAL;
|
|
3121
|
-
otgVal = ESP32P4_UARTDEV_BUF_NO_USB_OTG;
|
|
3122
|
-
break;
|
|
3123
|
-
}
|
|
3124
|
-
|
|
3125
|
-
if (bufNoAddr === null) {
|
|
3126
|
-
return { mode: "uart", uartNo: 0 };
|
|
3127
|
-
}
|
|
3128
|
-
|
|
3129
|
-
const uartNo = (await this.readRegister(bufNoAddr)) & 0xff;
|
|
3130
|
-
|
|
3131
|
-
if (otgVal !== null && uartNo === otgVal) {
|
|
3132
|
-
this.logger.debug(`USB mode: USB-OTG (uartNo=${uartNo})`);
|
|
3133
|
-
return { mode: "usb-otg", uartNo };
|
|
3134
|
-
}
|
|
3135
|
-
if (jtagSerialVal !== null && uartNo === jtagSerialVal) {
|
|
3136
|
-
this.logger.debug(`USB mode: USB-JTAG/Serial (uartNo=${uartNo})`);
|
|
3137
|
-
return { mode: "usb-jtag-serial", uartNo };
|
|
3138
|
-
}
|
|
3139
|
-
|
|
3140
|
-
this.logger.debug(`USB mode: UART (uartNo=${uartNo})`);
|
|
3141
|
-
return { mode: "uart", uartNo };
|
|
3142
|
-
}
|
|
3143
|
-
|
|
3144
|
-
async disconnect() {
|
|
3145
|
-
if (this._parent) {
|
|
3146
|
-
await this._parent.disconnect();
|
|
3147
|
-
return;
|
|
3148
|
-
}
|
|
3149
|
-
if (!this.port.writable) {
|
|
3150
|
-
// this.logger.debug("Port already closed, skipping disconnect");
|
|
3151
|
-
return;
|
|
3152
|
-
}
|
|
3153
|
-
|
|
3154
|
-
// Wait for pending writes to complete
|
|
3155
|
-
try {
|
|
3156
|
-
await this._writeChain;
|
|
3157
|
-
} catch (err) {
|
|
3158
|
-
// this.logger.debug(`Pending write error during disconnect: ${err}`);
|
|
3159
|
-
}
|
|
3271
|
+
// Wait for pending writes to complete
|
|
3272
|
+
try {
|
|
3273
|
+
await this._writeChain;
|
|
3274
|
+
} catch {
|
|
3275
|
+
// this.logger.debug("Pending write error during disconnect");
|
|
3276
|
+
}
|
|
3160
3277
|
|
|
3161
3278
|
// Release persistent writer before closing
|
|
3162
3279
|
if (this._writer) {
|
|
3163
3280
|
try {
|
|
3164
3281
|
await this._writer.close();
|
|
3165
3282
|
this._writer.releaseLock();
|
|
3166
|
-
} catch
|
|
3167
|
-
// this.logger.debug(
|
|
3283
|
+
} catch {
|
|
3284
|
+
// this.logger.debug("Writer close/release error");
|
|
3168
3285
|
}
|
|
3169
3286
|
this._writer = undefined;
|
|
3170
3287
|
} else {
|
|
@@ -3174,8 +3291,8 @@ export class ESPLoader extends EventTarget {
|
|
|
3174
3291
|
const writer = this.port.writable.getWriter();
|
|
3175
3292
|
await writer.close();
|
|
3176
3293
|
writer.releaseLock();
|
|
3177
|
-
} catch
|
|
3178
|
-
// this.logger.debug(
|
|
3294
|
+
} catch {
|
|
3295
|
+
// this.logger.debug("Direct writer close error");
|
|
3179
3296
|
}
|
|
3180
3297
|
}
|
|
3181
3298
|
|
|
@@ -3203,7 +3320,7 @@ export class ESPLoader extends EventTarget {
|
|
|
3203
3320
|
// Only cancel if reader is still active
|
|
3204
3321
|
try {
|
|
3205
3322
|
this._reader.cancel();
|
|
3206
|
-
} catch
|
|
3323
|
+
} catch {
|
|
3207
3324
|
// Reader already released, resolve immediately
|
|
3208
3325
|
clearTimeout(timeout);
|
|
3209
3326
|
resolve(undefined);
|
|
@@ -3234,8 +3351,8 @@ export class ESPLoader extends EventTarget {
|
|
|
3234
3351
|
// Wait for pending writes to complete
|
|
3235
3352
|
try {
|
|
3236
3353
|
await this._writeChain;
|
|
3237
|
-
} catch
|
|
3238
|
-
// this.logger.debug(
|
|
3354
|
+
} catch {
|
|
3355
|
+
// this.logger.debug("Pending write error during release");
|
|
3239
3356
|
}
|
|
3240
3357
|
|
|
3241
3358
|
// Release writer
|
|
@@ -3249,26 +3366,27 @@ export class ESPLoader extends EventTarget {
|
|
|
3249
3366
|
this._writer = undefined;
|
|
3250
3367
|
}
|
|
3251
3368
|
|
|
3252
|
-
// Cancel
|
|
3369
|
+
// Cancel reader - let readLoop's finally block handle releaseLock()
|
|
3253
3370
|
if (this._reader) {
|
|
3254
|
-
const reader = this._reader;
|
|
3255
3371
|
try {
|
|
3256
3372
|
// Suppress disconnect event during console mode switching
|
|
3257
3373
|
this._suppressDisconnect = true;
|
|
3258
|
-
|
|
3259
|
-
|
|
3374
|
+
|
|
3375
|
+
// Cancel will cause readLoop to exit and call releaseLock() in its finally block
|
|
3376
|
+
await this._reader.cancel();
|
|
3377
|
+
this.logger.debug("Reader cancelled - waiting for readLoop to finish");
|
|
3378
|
+
|
|
3379
|
+
// CRITICAL: Wait a bit for readLoop's finally block to complete
|
|
3380
|
+
// The finally block needs time to call releaseLock() and set _reader = undefined
|
|
3381
|
+
// This is much faster than waiting for browser to unlock (just waiting for JS execution)
|
|
3382
|
+
await sleep(50);
|
|
3383
|
+
|
|
3384
|
+
this.logger.debug("ReadLoop cleanup should be complete");
|
|
3260
3385
|
} catch (err) {
|
|
3261
3386
|
this.logger.debug(`Reader cancel error: ${err}`);
|
|
3262
|
-
} finally {
|
|
3263
|
-
try {
|
|
3264
|
-
reader.releaseLock();
|
|
3265
|
-
} catch (err) {
|
|
3266
|
-
this.logger.debug(`Reader release error: ${err}`);
|
|
3267
|
-
}
|
|
3268
|
-
}
|
|
3269
|
-
if (this._reader === reader) {
|
|
3270
|
-
this._reader = undefined;
|
|
3271
3387
|
}
|
|
3388
|
+
// Don't call releaseLock() or set _reader to undefined here
|
|
3389
|
+
// Let readLoop's finally block handle it to avoid race conditions
|
|
3272
3390
|
}
|
|
3273
3391
|
}
|
|
3274
3392
|
|
|
@@ -3317,6 +3435,157 @@ export class ESPLoader extends EventTarget {
|
|
|
3317
3435
|
return isUsbJtag;
|
|
3318
3436
|
}
|
|
3319
3437
|
|
|
3438
|
+
public async getUsbMode(): Promise<{
|
|
3439
|
+
mode: "uart" | "usb-jtag-serial" | "usb-otg";
|
|
3440
|
+
uartNo: number;
|
|
3441
|
+
}> {
|
|
3442
|
+
const family = this._parent ? this._parent.chipFamily : this.chipFamily;
|
|
3443
|
+
const revision = this._parent
|
|
3444
|
+
? (this._parent.chipRevision ?? 0)
|
|
3445
|
+
: (this.chipRevision ?? 0);
|
|
3446
|
+
|
|
3447
|
+
let bufNoAddr: number | null = null;
|
|
3448
|
+
let jtagSerialVal: number | null = null;
|
|
3449
|
+
let otgVal: number | null = null;
|
|
3450
|
+
|
|
3451
|
+
switch (family) {
|
|
3452
|
+
case CHIP_FAMILY_ESP32S2:
|
|
3453
|
+
bufNoAddr = ESP32S2_UARTDEV_BUF_NO;
|
|
3454
|
+
otgVal = ESP32S2_UARTDEV_BUF_NO_USB_OTG;
|
|
3455
|
+
break;
|
|
3456
|
+
case CHIP_FAMILY_ESP32S3:
|
|
3457
|
+
bufNoAddr = ESP32S3_UARTDEV_BUF_NO;
|
|
3458
|
+
jtagSerialVal = ESP32S3_UARTDEV_BUF_NO_USB_JTAG_SERIAL;
|
|
3459
|
+
otgVal = ESP32S3_UARTDEV_BUF_NO_USB_OTG;
|
|
3460
|
+
break;
|
|
3461
|
+
case CHIP_FAMILY_ESP32C3: {
|
|
3462
|
+
const bssAddr = revision < 101 ? 0x3fcdf064 : 0x3fcdf060;
|
|
3463
|
+
bufNoAddr = bssAddr + ESP32C3_BUF_UART_NO_OFFSET;
|
|
3464
|
+
jtagSerialVal = ESP32C3_UARTDEV_BUF_NO_USB_JTAG_SERIAL;
|
|
3465
|
+
break;
|
|
3466
|
+
}
|
|
3467
|
+
case CHIP_FAMILY_ESP32C5:
|
|
3468
|
+
bufNoAddr = ESP32C5_UARTDEV_BUF_NO;
|
|
3469
|
+
jtagSerialVal = ESP32C5_UARTDEV_BUF_NO_USB_JTAG_SERIAL;
|
|
3470
|
+
break;
|
|
3471
|
+
case CHIP_FAMILY_ESP32C6:
|
|
3472
|
+
bufNoAddr = ESP32C6_UARTDEV_BUF_NO;
|
|
3473
|
+
jtagSerialVal = ESP32C6_UARTDEV_BUF_NO_USB_JTAG_SERIAL;
|
|
3474
|
+
break;
|
|
3475
|
+
case CHIP_FAMILY_ESP32C61:
|
|
3476
|
+
bufNoAddr =
|
|
3477
|
+
revision <= 200
|
|
3478
|
+
? ESP32C61_UARTDEV_BUF_NO_REV_LE2
|
|
3479
|
+
: ESP32C61_UARTDEV_BUF_NO_REV_GT2;
|
|
3480
|
+
jtagSerialVal =
|
|
3481
|
+
revision <= 200
|
|
3482
|
+
? ESP32C61_UARTDEV_BUF_NO_USB_JTAG_SERIAL_REV_LE2
|
|
3483
|
+
: ESP32C61_UARTDEV_BUF_NO_USB_JTAG_SERIAL_REV_GT2;
|
|
3484
|
+
break;
|
|
3485
|
+
case CHIP_FAMILY_ESP32H2:
|
|
3486
|
+
bufNoAddr = ESP32H2_UARTDEV_BUF_NO;
|
|
3487
|
+
jtagSerialVal = ESP32H2_UARTDEV_BUF_NO_USB_JTAG_SERIAL;
|
|
3488
|
+
break;
|
|
3489
|
+
case CHIP_FAMILY_ESP32H4:
|
|
3490
|
+
bufNoAddr = ESP32H4_UARTDEV_BUF_NO;
|
|
3491
|
+
jtagSerialVal = ESP32H4_UARTDEV_BUF_NO_USB_JTAG_SERIAL;
|
|
3492
|
+
break;
|
|
3493
|
+
case CHIP_FAMILY_ESP32P4:
|
|
3494
|
+
bufNoAddr =
|
|
3495
|
+
revision < 300
|
|
3496
|
+
? ESP32P4_UARTDEV_BUF_NO_REV0
|
|
3497
|
+
: ESP32P4_UARTDEV_BUF_NO_REV300;
|
|
3498
|
+
jtagSerialVal = ESP32P4_UARTDEV_BUF_NO_USB_JTAG_SERIAL;
|
|
3499
|
+
otgVal = ESP32P4_UARTDEV_BUF_NO_USB_OTG;
|
|
3500
|
+
break;
|
|
3501
|
+
}
|
|
3502
|
+
|
|
3503
|
+
if (bufNoAddr === null) {
|
|
3504
|
+
return { mode: "uart", uartNo: 0 };
|
|
3505
|
+
}
|
|
3506
|
+
|
|
3507
|
+
const uartNo = (await this.readRegister(bufNoAddr)) & 0xff;
|
|
3508
|
+
|
|
3509
|
+
if (otgVal !== null && uartNo === otgVal) {
|
|
3510
|
+
this.logger.debug(`USB mode: USB-OTG (uartNo=${uartNo})`);
|
|
3511
|
+
return { mode: "usb-otg", uartNo };
|
|
3512
|
+
}
|
|
3513
|
+
if (jtagSerialVal !== null && uartNo === jtagSerialVal) {
|
|
3514
|
+
this.logger.debug(`USB mode: USB-JTAG/Serial (uartNo=${uartNo})`);
|
|
3515
|
+
return { mode: "usb-jtag-serial", uartNo };
|
|
3516
|
+
}
|
|
3517
|
+
|
|
3518
|
+
this.logger.debug(`USB mode: UART (uartNo=${uartNo})`);
|
|
3519
|
+
return { mode: "uart", uartNo };
|
|
3520
|
+
}
|
|
3521
|
+
|
|
3522
|
+
/**
|
|
3523
|
+
* Check if the current chip supports USB-JTAG or USB-OTG
|
|
3524
|
+
* @returns true if chip has native USB support (JTAG or OTG)
|
|
3525
|
+
*/
|
|
3526
|
+
public supportsNativeUsb(): boolean {
|
|
3527
|
+
const family = this._parent ? this._parent.chipFamily : this.chipFamily;
|
|
3528
|
+
|
|
3529
|
+
// Chips with USB-JTAG/Serial or USB-OTG support
|
|
3530
|
+
const usbChips = [
|
|
3531
|
+
CHIP_FAMILY_ESP32S2, // USB-OTG
|
|
3532
|
+
CHIP_FAMILY_ESP32S3, // USB-OTG + USB-JTAG/Serial
|
|
3533
|
+
CHIP_FAMILY_ESP32C3, // USB-JTAG/Serial
|
|
3534
|
+
CHIP_FAMILY_ESP32C5, // USB-JTAG/Serial
|
|
3535
|
+
CHIP_FAMILY_ESP32C6, // USB-JTAG/Serial
|
|
3536
|
+
CHIP_FAMILY_ESP32C61, // USB-JTAG/Serial
|
|
3537
|
+
CHIP_FAMILY_ESP32H2, // USB-JTAG/Serial
|
|
3538
|
+
CHIP_FAMILY_ESP32H4, // USB-JTAG/Serial
|
|
3539
|
+
CHIP_FAMILY_ESP32P4, // USB-OTG + USB-JTAG/Serial
|
|
3540
|
+
];
|
|
3541
|
+
|
|
3542
|
+
return usbChips.includes(family);
|
|
3543
|
+
}
|
|
3544
|
+
|
|
3545
|
+
/**
|
|
3546
|
+
* @name _ensureStreamsReady
|
|
3547
|
+
* After a hardware reset, ensure port streams are available.
|
|
3548
|
+
* On WebUSB, recreates streams since they break after reset.
|
|
3549
|
+
* On Web Serial, waits for streams to become available.
|
|
3550
|
+
*/
|
|
3551
|
+
private async _ensureStreamsReady(): Promise<void> {
|
|
3552
|
+
if (this.isWebUSB()) {
|
|
3553
|
+
try {
|
|
3554
|
+
await (
|
|
3555
|
+
this.port as unknown as { recreateStreams(): Promise<void> }
|
|
3556
|
+
).recreateStreams();
|
|
3557
|
+
this.logger.debug("WebUSB streams recreated");
|
|
3558
|
+
|
|
3559
|
+
let retries = 30;
|
|
3560
|
+
while (retries > 0 && !this.port.readable) {
|
|
3561
|
+
await sleep(100);
|
|
3562
|
+
retries--;
|
|
3563
|
+
}
|
|
3564
|
+
if (!this.port.readable) {
|
|
3565
|
+
throw new Error(
|
|
3566
|
+
"Readable stream not available after recreating streams",
|
|
3567
|
+
);
|
|
3568
|
+
}
|
|
3569
|
+
this.logger.debug("WebUSB streams are ready");
|
|
3570
|
+
} catch (err) {
|
|
3571
|
+
this.logger.error(`Failed to recreate WebUSB streams: ${err}`);
|
|
3572
|
+
this._consoleMode = false;
|
|
3573
|
+
throw err;
|
|
3574
|
+
}
|
|
3575
|
+
} else {
|
|
3576
|
+
let retries = 20;
|
|
3577
|
+
while (retries > 0 && !this.port.readable) {
|
|
3578
|
+
await sleep(100);
|
|
3579
|
+
retries--;
|
|
3580
|
+
}
|
|
3581
|
+
if (!this.port.readable) {
|
|
3582
|
+
this._consoleMode = false;
|
|
3583
|
+
throw new Error("Readable stream not available after reset");
|
|
3584
|
+
}
|
|
3585
|
+
this.logger.debug("Port streams are ready");
|
|
3586
|
+
}
|
|
3587
|
+
}
|
|
3588
|
+
|
|
3320
3589
|
/**
|
|
3321
3590
|
* @name enterConsoleMode
|
|
3322
3591
|
* Prepare device for console mode by resetting to firmware
|
|
@@ -3349,8 +3618,6 @@ export class ESPLoader extends EventTarget {
|
|
|
3349
3618
|
`Cannot enter console mode: USB connection type unknown and detection failed: ${err}`,
|
|
3350
3619
|
);
|
|
3351
3620
|
}
|
|
3352
|
-
// Set console mode flag
|
|
3353
|
-
this._consoleMode = false;
|
|
3354
3621
|
|
|
3355
3622
|
this.logger.debug(
|
|
3356
3623
|
`USB detection failed, using cached value: ${this.isUsbJtagOrOtg}`,
|
|
@@ -3358,56 +3625,40 @@ export class ESPLoader extends EventTarget {
|
|
|
3358
3625
|
isUsbJtag = this.isUsbJtagOrOtg;
|
|
3359
3626
|
}
|
|
3360
3627
|
|
|
3361
|
-
//
|
|
3362
|
-
|
|
3628
|
+
// Set console mode flag BEFORE any operations
|
|
3629
|
+
this._consoleMode = true;
|
|
3630
|
+
|
|
3363
3631
|
if (isUsbJtag) {
|
|
3364
|
-
// USB-JTAG/OTG devices: Use
|
|
3632
|
+
// USB-JTAG/OTG devices: Use reset which may close port
|
|
3365
3633
|
const wasReset = await this._resetToFirmwareIfNeeded();
|
|
3366
|
-
|
|
3634
|
+
if (wasReset) {
|
|
3635
|
+
return true; // port closed, caller must reopen
|
|
3636
|
+
}
|
|
3637
|
+
|
|
3638
|
+
// Port stayed open (e.g. C3/C5/C6/H2 classic reset)
|
|
3639
|
+
await this._ensureStreamsReady();
|
|
3640
|
+
return false;
|
|
3367
3641
|
} else {
|
|
3368
3642
|
// External serial chip devices: Release locks and do simple reset
|
|
3369
3643
|
try {
|
|
3370
3644
|
await this.releaseReaderWriter();
|
|
3371
|
-
await
|
|
3645
|
+
await sleep(100);
|
|
3372
3646
|
} catch (err) {
|
|
3373
3647
|
this.logger.debug(`Failed to release locks: ${err}`);
|
|
3374
3648
|
}
|
|
3375
3649
|
|
|
3376
|
-
// Hardware reset to firmware mode (IO0=HIGH)
|
|
3377
3650
|
try {
|
|
3378
|
-
await this.
|
|
3379
|
-
this.logger.
|
|
3651
|
+
await this.hardResetToFirmware();
|
|
3652
|
+
this.logger.debug("Device reset to firmware mode");
|
|
3380
3653
|
} catch (err) {
|
|
3381
3654
|
this.logger.debug(`Could not reset device: ${err}`);
|
|
3382
3655
|
}
|
|
3383
3656
|
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
try {
|
|
3387
|
-
// Use the public recreateStreams() method to safely recreate streams
|
|
3388
|
-
// without closing the port (important after hardware reset)
|
|
3389
|
-
await (this.port as any).recreateStreams();
|
|
3390
|
-
this.logger.debug("WebUSB streams recreated for console mode");
|
|
3391
|
-
} catch (err) {
|
|
3392
|
-
// Set console mode flag
|
|
3393
|
-
this._consoleMode = false;
|
|
3394
|
-
this.logger.debug(`Failed to recreate WebUSB streams: ${err}`);
|
|
3395
|
-
}
|
|
3396
|
-
}
|
|
3397
|
-
|
|
3398
|
-
// Set console mode flag
|
|
3399
|
-
this._consoleMode = true;
|
|
3400
|
-
|
|
3401
|
-
return false; // Port stays open
|
|
3657
|
+
await this._ensureStreamsReady();
|
|
3658
|
+
return false;
|
|
3402
3659
|
}
|
|
3403
3660
|
}
|
|
3404
3661
|
|
|
3405
|
-
/**
|
|
3406
|
-
* @name _resetToFirmwareIfNeeded
|
|
3407
|
-
* Reset device from bootloader to firmware when switching to console mode
|
|
3408
|
-
* Detects USB-JTAG/Serial and USB-OTG devices and performs appropriate reset
|
|
3409
|
-
* @returns true if reconnect was performed, false otherwise
|
|
3410
|
-
*/
|
|
3411
3662
|
/**
|
|
3412
3663
|
* @name _clearForceDownloadBootIfNeeded
|
|
3413
3664
|
* Read and clear the force download boot flag if it is set
|
|
@@ -3468,7 +3719,15 @@ export class ESPLoader extends EventTarget {
|
|
|
3468
3719
|
}
|
|
3469
3720
|
}
|
|
3470
3721
|
|
|
3722
|
+
/**
|
|
3723
|
+
* @name _resetToFirmwareIfNeeded
|
|
3724
|
+
* Reset device from bootloader to firmware when switching to console mode
|
|
3725
|
+
* Detects USB-JTAG/Serial and USB-OTG devices and performs appropriate reset
|
|
3726
|
+
* @returns true if reconnect was performed, false otherwise
|
|
3727
|
+
*/
|
|
3471
3728
|
private async _resetToFirmwareIfNeeded(): Promise<boolean> {
|
|
3729
|
+
// Detect if we need WDT reset (USB-JTAG/OTG) or classic reset
|
|
3730
|
+
const isUsbJtagOrOtg = await this.detectUsbConnectionType();
|
|
3472
3731
|
try {
|
|
3473
3732
|
// Check if port is open - if not, assume device is already in firmware mode
|
|
3474
3733
|
if (!this.port.writable || !this.port.readable) {
|
|
@@ -3478,115 +3737,65 @@ export class ESPLoader extends EventTarget {
|
|
|
3478
3737
|
return false;
|
|
3479
3738
|
}
|
|
3480
3739
|
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
//
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
);
|
|
3496
|
-
try {
|
|
3497
|
-
await this.reconfigurePort(ESP_ROM_BAUD);
|
|
3498
|
-
this.currentBaudRate = ESP_ROM_BAUD;
|
|
3499
|
-
} catch (err) {
|
|
3500
|
-
this.logger.debug(`Baudrate change failed: ${err}`);
|
|
3501
|
-
// Continue anyway
|
|
3502
|
-
}
|
|
3503
|
-
}
|
|
3504
|
-
|
|
3505
|
-
this.logger.debug("Resetting to bootloader (ROM)...");
|
|
3506
|
-
|
|
3507
|
-
// Reset to bootloader - this will clear the stub from RAM
|
|
3508
|
-
try {
|
|
3509
|
-
await this.hardReset(true);
|
|
3510
|
-
|
|
3511
|
-
// Wait for reset to complete
|
|
3512
|
-
await sleep(200);
|
|
3513
|
-
|
|
3514
|
-
// Sync with ROM
|
|
3515
|
-
await this.sync();
|
|
3516
|
-
|
|
3517
|
-
this.logger.debug("Now on ROM after reset");
|
|
3518
|
-
|
|
3519
|
-
// Mark that we're no longer on stub
|
|
3520
|
-
this.IS_STUB = false;
|
|
3521
|
-
} catch (resetErr) {
|
|
3522
|
-
this.logger.debug(`Reset to ROM failed: ${resetErr}`);
|
|
3523
|
-
// If reset fails, we might already be in firmware mode
|
|
3524
|
-
// In this case, we don't need to do anything - just use normal reset
|
|
3525
|
-
this.logger.debug("Assuming device is already in firmware mode");
|
|
3526
|
-
|
|
3527
|
-
// Release reader/writer before returning
|
|
3528
|
-
await this.releaseReaderWriter();
|
|
3529
|
-
return false; // No port change needed
|
|
3530
|
-
}
|
|
3531
|
-
} else {
|
|
3532
|
-
this.logger.debug("Already on ROM - checking force download flag");
|
|
3533
|
-
}
|
|
3534
|
-
|
|
3535
|
-
// Now check if force download flag is set and clear it if needed
|
|
3536
|
-
const flagWasCleared = await this._clearForceDownloadBootIfNeeded();
|
|
3537
|
-
|
|
3538
|
-
if (flagWasCleared) {
|
|
3539
|
-
this.logger.debug(
|
|
3540
|
-
"Force download flag was cleared - device will boot to firmware after reset",
|
|
3541
|
-
);
|
|
3542
|
-
} else {
|
|
3543
|
-
this.logger.debug(
|
|
3544
|
-
"Force download flag already clear - device will boot to firmware after reset",
|
|
3545
|
-
);
|
|
3546
|
-
}
|
|
3547
|
-
|
|
3548
|
-
// Perform WDT reset BEFORE releasing reader/writer (needs communication)
|
|
3549
|
-
// After WDT reset, the device will reboot into firmware mode
|
|
3550
|
-
await this.hardReset(false);
|
|
3740
|
+
if (isUsbJtagOrOtg) {
|
|
3741
|
+
// USB-JTAG/OTG: DON'T release reader/writer before WDT reset
|
|
3742
|
+
// The WDT reset needs active communication to send register write commands
|
|
3743
|
+
// The port will close automatically after the WDT reset anyway
|
|
3744
|
+
this.logger.debug(
|
|
3745
|
+
"USB-JTAG/OTG: Keeping reader/writer active for WDT reset",
|
|
3746
|
+
);
|
|
3747
|
+
} else {
|
|
3748
|
+
// External serial chip: Release reader/writer before classic reset
|
|
3749
|
+
await this.releaseReaderWriter();
|
|
3750
|
+
this.logger.debug(
|
|
3751
|
+
"External serial: Reader/writer released before reset",
|
|
3752
|
+
);
|
|
3753
|
+
}
|
|
3551
3754
|
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
(this.chipFamily === CHIP_FAMILY_ESP32S2 && isUsingUsbOtg) ||
|
|
3555
|
-
(this.chipFamily === CHIP_FAMILY_ESP32P4 && isUsingUsbOtg);
|
|
3755
|
+
// Use the new resetToFirmwareMode method which handles all the logic
|
|
3756
|
+
const portWillChange = await this.resetToFirmwareMode(true);
|
|
3556
3757
|
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
|
|
3758
|
+
if (portWillChange) {
|
|
3759
|
+
this.logger.debug(
|
|
3760
|
+
`${this.chipName}: Port will change after WDT reset - user must reselect port`,
|
|
3761
|
+
);
|
|
3560
3762
|
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
message: `${this.chipName} USB port changed after reset. Please select the new port.`,
|
|
3572
|
-
reason: "wdt-reset-to-firmware",
|
|
3573
|
-
},
|
|
3574
|
-
}),
|
|
3575
|
-
);
|
|
3763
|
+
// Dispatch event to signal port change
|
|
3764
|
+
this.dispatchEvent(
|
|
3765
|
+
new CustomEvent("usb-otg-port-change", {
|
|
3766
|
+
detail: {
|
|
3767
|
+
chipName: this.chipName,
|
|
3768
|
+
message: `${this.chipName} USB port changed after reset. Please select the new port.`,
|
|
3769
|
+
reason: "wdt-reset-to-firmware",
|
|
3770
|
+
},
|
|
3771
|
+
}),
|
|
3772
|
+
);
|
|
3576
3773
|
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
|
|
3774
|
+
return true;
|
|
3775
|
+
} else {
|
|
3776
|
+
// Port stays the same - release reader/writer now if not already done
|
|
3777
|
+
if (isUsbJtagOrOtg) {
|
|
3581
3778
|
await this.releaseReaderWriter();
|
|
3582
|
-
|
|
3779
|
+
this.logger.debug("Reader/writer released after reset");
|
|
3583
3780
|
}
|
|
3781
|
+
return false;
|
|
3584
3782
|
}
|
|
3585
3783
|
} catch (err) {
|
|
3586
|
-
this.logger.
|
|
3587
|
-
|
|
3784
|
+
this.logger.error(`Reset to firmware mode failed: ${err}`);
|
|
3785
|
+
|
|
3786
|
+
// For USB-JTAG/OTG, the port is likely dead after a failed reset
|
|
3787
|
+
// For external serial, the port is usually still fine
|
|
3788
|
+
if (isUsbJtagOrOtg) {
|
|
3789
|
+
this.logger.debug(
|
|
3790
|
+
"Forcing port reselection due to USB-JTAG/OTG reset failure",
|
|
3791
|
+
);
|
|
3792
|
+
return true;
|
|
3793
|
+
}
|
|
3794
|
+
this.logger.debug(
|
|
3795
|
+
"External serial reset failed, but port should still be usable",
|
|
3796
|
+
);
|
|
3797
|
+
return false;
|
|
3588
3798
|
}
|
|
3589
|
-
return false;
|
|
3590
3799
|
}
|
|
3591
3800
|
|
|
3592
3801
|
/**
|
|
@@ -3839,7 +4048,7 @@ export class ESPLoader extends EventTarget {
|
|
|
3839
4048
|
// Detect chip type
|
|
3840
4049
|
await this.detectChip();
|
|
3841
4050
|
|
|
3842
|
-
this.logger.
|
|
4051
|
+
this.logger.debug(`Reconnected to bootloader: ${this.chipName}`);
|
|
3843
4052
|
} catch (err) {
|
|
3844
4053
|
// Ensure flag is reset on error
|
|
3845
4054
|
this._isReconfiguring = false;
|
|
@@ -3882,7 +4091,7 @@ export class ESPLoader extends EventTarget {
|
|
|
3882
4091
|
|
|
3883
4092
|
if (isUsbOtgChip && isUsbJtagOrOtg) {
|
|
3884
4093
|
// USB-OTG devices: Need to reset to bootloader, which will cause port change
|
|
3885
|
-
this.logger.
|
|
4094
|
+
this.logger.debug(`${this.chipName} USB: Resetting to bootloader mode`);
|
|
3886
4095
|
|
|
3887
4096
|
// Perform hardware reset to bootloader (GPIO0=LOW)
|
|
3888
4097
|
// This will cause the port to change from CDC (firmware) to JTAG (bootloader)
|
|
@@ -3896,7 +4105,7 @@ export class ESPLoader extends EventTarget {
|
|
|
3896
4105
|
// Wait for reset to complete and port to change
|
|
3897
4106
|
await sleep(500);
|
|
3898
4107
|
|
|
3899
|
-
this.logger.
|
|
4108
|
+
this.logger.debug(
|
|
3900
4109
|
`${this.chipName}: Port changed. Please select the bootloader port.`,
|
|
3901
4110
|
);
|
|
3902
4111
|
|
|
@@ -3954,17 +4163,19 @@ export class ESPLoader extends EventTarget {
|
|
|
3954
4163
|
|
|
3955
4164
|
if (!this.isConsoleResetSupported()) {
|
|
3956
4165
|
this.logger.debug(
|
|
3957
|
-
"Console reset not supported for ESP32-S2 USB-JTAG/CDC",
|
|
4166
|
+
"Simple Console reset not supported for ESP32-S2 USB-JTAG/CDC - using exitConsoleMode to enter bootloader",
|
|
4167
|
+
);
|
|
4168
|
+
await this.exitConsoleMode();
|
|
4169
|
+
this.logger.debug(
|
|
4170
|
+
"S2 now in bootloader mode - caller must do syncAndWdtReset on new port, then reconnect console",
|
|
3958
4171
|
);
|
|
3959
|
-
return;
|
|
4172
|
+
return;
|
|
3960
4173
|
}
|
|
3961
4174
|
|
|
3962
4175
|
// For other devices: Use standard firmware reset
|
|
3963
4176
|
try {
|
|
3964
4177
|
this.logger.debug("Resetting device in console mode");
|
|
3965
|
-
|
|
3966
4178
|
await this.hardResetToFirmware();
|
|
3967
|
-
|
|
3968
4179
|
this.logger.debug("Device reset complete");
|
|
3969
4180
|
} catch (err) {
|
|
3970
4181
|
this.logger.error(`Reset failed: ${err}`);
|
|
@@ -3972,6 +4183,47 @@ export class ESPLoader extends EventTarget {
|
|
|
3972
4183
|
}
|
|
3973
4184
|
}
|
|
3974
4185
|
|
|
4186
|
+
/**
|
|
4187
|
+
* @name syncAndWdtReset
|
|
4188
|
+
* Open a new bootloader port, sync with ROM (no stub, no reset strategies), and fire WDT reset.
|
|
4189
|
+
* This is used for ESP32-S2 USB-OTG devices which require WDT reset to switch modes.
|
|
4190
|
+
* After WDT reset the port will re-enumerate again.
|
|
4191
|
+
* The user must select the new port after this method is called.
|
|
4192
|
+
* @param newPort - The bootloader port selected by the user
|
|
4193
|
+
*/
|
|
4194
|
+
async syncAndWdtReset(newPort: SerialPort): Promise<void> {
|
|
4195
|
+
if (this._parent) {
|
|
4196
|
+
await this._parent.syncAndWdtReset(newPort);
|
|
4197
|
+
return;
|
|
4198
|
+
}
|
|
4199
|
+
|
|
4200
|
+
this.port = newPort;
|
|
4201
|
+
this.connected = false;
|
|
4202
|
+
this.IS_STUB = false;
|
|
4203
|
+
this.__inputBuffer = [];
|
|
4204
|
+
this.__inputBufferReadIndex = 0;
|
|
4205
|
+
this.__totalBytesRead = 0;
|
|
4206
|
+
|
|
4207
|
+
this.logger.debug("Opening bootloader port at 115200...");
|
|
4208
|
+
await this.port.open({ baudRate: ESP_ROM_BAUD });
|
|
4209
|
+
this.connected = true;
|
|
4210
|
+
this.currentBaudRate = ESP_ROM_BAUD;
|
|
4211
|
+
|
|
4212
|
+
// Start read loop
|
|
4213
|
+
this.readLoop();
|
|
4214
|
+
await sleep(100);
|
|
4215
|
+
|
|
4216
|
+
// Sync with ROM only - no reset strategies, device is already in bootloader
|
|
4217
|
+
this.logger.debug("Syncing with bootloader ROM...");
|
|
4218
|
+
await this.sync();
|
|
4219
|
+
this.logger.debug("Bootloader sync OK, no stub");
|
|
4220
|
+
|
|
4221
|
+
// Fire WDT reset → device boots into firmware
|
|
4222
|
+
this.logger.debug("Firing WDT reset...");
|
|
4223
|
+
await this.rtcWdtResetChipSpecific();
|
|
4224
|
+
this.logger.debug("WDT reset fired - device will boot to firmware");
|
|
4225
|
+
}
|
|
4226
|
+
|
|
3975
4227
|
/**
|
|
3976
4228
|
* @name drainInputBuffer
|
|
3977
4229
|
* Actively drain the input buffer by reading data for a specified time.
|
|
@@ -4200,7 +4452,7 @@ export class ESPLoader extends EventTarget {
|
|
|
4200
4452
|
} catch (err) {
|
|
4201
4453
|
if (err instanceof SlipReadError) {
|
|
4202
4454
|
this.logger.debug(
|
|
4203
|
-
|
|
4455
|
+
`${err.message} at byte 0x${resp.length.toString(16)}`,
|
|
4204
4456
|
);
|
|
4205
4457
|
|
|
4206
4458
|
// Send empty SLIP frame to abort the stub's read operation
|
|
@@ -4354,22 +4606,9 @@ export class ESPLoader extends EventTarget {
|
|
|
4354
4606
|
if (err instanceof SlipReadError) {
|
|
4355
4607
|
if (retryCount <= MAX_RETRIES) {
|
|
4356
4608
|
this.logger.debug(
|
|
4357
|
-
|
|
4609
|
+
`Cleared buffer and retrying (attempt ${retryCount}/${MAX_RETRIES})...`,
|
|
4358
4610
|
);
|
|
4359
|
-
|
|
4360
|
-
try {
|
|
4361
|
-
await this.drainInputBuffer(200);
|
|
4362
|
-
|
|
4363
|
-
// Clear application buffer
|
|
4364
|
-
await this.flushSerialBuffers();
|
|
4365
|
-
|
|
4366
|
-
// Wait before retry to let hardware settle
|
|
4367
|
-
await sleep(SYNC_TIMEOUT);
|
|
4368
|
-
|
|
4369
|
-
// Continue to retry the same chunk (will send NEW read command)
|
|
4370
|
-
} catch (drainErr) {
|
|
4371
|
-
this.logger.debug(`Buffer drain error: ${drainErr}`);
|
|
4372
|
-
}
|
|
4611
|
+
// Continue to retry the same chunk (will send NEW read command)
|
|
4373
4612
|
} else {
|
|
4374
4613
|
// All retries exhausted - attempt recovery by reloading stub
|
|
4375
4614
|
// IMPORTANT: Do NOT close port to keep ESP32 in bootloader mode
|