Commit graph

157 commits

Author SHA1 Message Date
Damien George
d5ecda05eb ports: Allow MICROPY_PY_MACHINE_I2C_TARGET to be disabled by board cfg.
Signed-off-by: Damien George <damien@micropython.org>
2025-08-07 10:33:26 +10:00
Damien George
3c9546ea09 esp32/mpconfigport: Disable I2CTarget on ESP32-C6 to reduce code size.
I2CTarget costs about 8k of flash size on ESP32-S2, and about 11k on
ESP32-C6.  The ESP32-C6 only has about 8k remaining, so disable I2CTarget
on that SoC until more flash can be made available.

Signed-off-by: Damien George <damien@micropython.org>
2025-08-02 08:53:36 +10:00
Jonathan Hogg
c3f3339c87 esp32/modesp32: Add esp32.PCNT class.
Add a new `esp32.PCNT` class that provides complete, low-level support to
the ESP32 PCNT pulse counting hardware units.

This can be used as a building block to implement the higher-level
`machine.Counter` and `machine.Encoder` classes.

This is enabled by default on all OG, S2, S3, C6 boards, but not on C3 (as
the PCNT peripheral is not supported).

Original implementation by: Jonathan Hogg <me@jonathanhogg.com>

Signed-off-by: Jim Mussared <jim.mussared@gmail.com>
Signed-off-by: Angus Gratton <angus@redyak.com.au>
2025-08-01 23:45:18 +10:00
Damien George
7bc83afee2 esp32/machine_i2c_target: Implement I2CTarget class.
Only soft IRQs are supported.

Signed-off-by: Damien George <damien@micropython.org>
2025-08-01 23:03:17 +10:00
TianShuang Ke
ca9916968c esp32: Add support for ESP32-C2 (aka ESP8684).
Includes:
esp32/esp32c2: Adapt to target chip ESP32C2.
esp32/esp32c2: Fix heap size is too small to enable Bluetooth.

Signed-off-by: TianShuangKe <qinyun575@gmail.com>
Signed-off-by: Angus Gratton <angus@redyak.com.au>
2025-07-30 10:51:29 +10:00
0c8d35b322 ports: Eliminate define of {U,}INT_FMT where redundant.
The default definition in `py/mpconfig.h` for 32-bit architectures is
`%u/%d`, so these can be removed.

Signed-off-by: Jeff Epler <jepler@gmail.com>
2025-07-25 10:54:27 +10:00
purewack
5f058e9863 esp32: Update ADC driver update to the new esp_adc API.
This commit updates the ADC to use the new driver `esp_adc/adc_oneshot.h`.

There are several errata notes about not being able to change the bit-width
of the ADCs certain chips.  The only chip that can switch resolution to a
lower one is the normal ESP32.  ESP32 C2 and S3 are stuck at 12 bits, while
S2 is at 13 bits.

On the S2, you can change the resolution, but it has no effect on the
resolution, rather, it prevents attenuation from working at all!

The resolution is set to the maximum possible for each SoC, with the ESP32
being the only one not throwing errors when trying to set the bit-width to
9, 10, 11 or 12 bits using `ADC.width(bits)`.

Signed-off-by: Damian Nowacki (purewack) bobimaster15@gmail.com
2025-06-05 17:01:51 +10:00
IhorNehrutsa
a724545193 esp32/mpconfigport: Document how to get more debug info.
Signed-off-by: Ihor Nehrutsa <Ihor.Nehrutsa@gmail.com>
2025-05-16 12:37:14 +10:00
Alessandro Gatti
883dc41d46 esp32/main: Make the entry point function name configurable.
This commit introduces a new port configuration entry allowing the entry
point function name to be changed, from "app_main" to a custom name.

This is needed when MicroPython is embedded as an ESP-IDF component,
since the "app_main" symbol is already provided elsewhere, making
compilation not possible.  Marking MicroPython's symbol as weak would
make it compile and make it possible to create and start the MicroPython
task anyway with the right FreeRTOS task creation incantation, but it is
probably easier to just rename the initialisation function into
something else that can be accessed from outside.

When MicroPython is embedded as an ESP-IDF component, the
MICROPY_ESP_IDF_ENTRY definition can be set to indicate the new entry
point function name.  The new function name prototype should still be
defined in external code to let linking succeed.

Also, the NLR failure callback is marked as weak to give the chance of
handling such error in a more controlled fashion rather than trigger an
unconditional board restart.

Signed-off-by: Alessandro Gatti <a.gatti@frob.it>
2025-05-14 06:26:31 +02:00
Sebastian Romero
5f2d05d417 esp32: Enable machine.USBDevice to configure USB at runtime.
This adds support for `machine.USBDevice` to S2 and S3 boards.

Signed-off-by: Sebastian Romero <s.romero@arduino.cc>
2024-12-16 11:41:40 +11:00
Alessandro Gatti
82e382a399 esp32/mpconfigport: Use the appropriate wait-for-interrupt opcode.
When threading is disabled, the pending events handling code would wait
for an incoming interrupt once there's no more work to do.  This bit of
code was Xtensa-specific and wouldn't compile on a RISC-V based MCU.

This commit provides the RISC-V equivalent to that part of the code,
allowing to make threadless MicroPython builds on RISC-V based MCUs.

Signed-off-by: Alessandro Gatti <a.gatti@frob.it>
2024-12-10 21:57:40 +11:00
Andrew Leech
55dc482d3e esp32/sdkconfig: Disable PMP_IDRAM_SPLIT to fix native emit support.
Signed-off-by: Andrew Leech <andrew@alelec.net>
2024-10-09 23:34:27 +11:00
Andrew Leech
6d799378ba esp32: Add support for esp32c6.
This commit adds general support for ESP32-C6 SoCs.

Signed-off-by: Andrew Leech <andrew@alelec.net>
2024-10-09 23:29:38 +11:00
Andrew Leech
5ae622ef7b esp32: Add automatic bootloader handling for S2 and S3.
Enables support for the ESP standard DTR/RTS based reboot to bootloader.

Switches from OTG to Serial/Jtag mode to workaround issue discussed
in: https://github.com/espressif/arduino-esp32/issues/6762

Signed-off-by: Andrew Leech <andrew@alelec.net>
2024-10-07 11:06:57 +11:00
Andrew Leech
4247921c4e esp32: Use shared/tinyusb integration for S2 and S3 USB.
Uses newer TinyUSB synopsys/dwc2 driver for esp32s2 and esp32s3 rather than
the IDF tinyusb component.  This allows re-use of other tinyusb integration
code and features shared between ports.

Signed-off-by: Andrew Leech <andrew@alelec.net>
2024-10-07 11:06:29 +11:00
Alessandro Gatti
17d8234458 py/parse: Remove old esp32 compiler workaround.
The ESP32 port contains a workaround to avoid having a certain function
in `py/parse.c` being generated incorrectly.  The compiler in question
is not part of any currently supported version of ESP-IDF anymore, and the
problem inside the compiler (well, assembler in this case) has been
corrected a few years ago.

This commit removes all traces of that workaround from the source tree.

Signed-off-by: Alessandro Gatti <a.gatti@frob.it>
2024-09-27 00:03:16 +10:00
Angus Gratton
5e692d0460 esp32: Add MICROPY_HW_USB_CDC macro for native USB-CDC serial.
This fixes issue of ESP32-S3 switching its config over to USB serial/JTAG
instead of native USB.

The the existing logic was hard to follow, adding this config macro makes
it easier to see which USB is in use and to have board definitions that
enable/disable different USB levels.

This commit also drops (nominal) support for manually setting
CONFIG_ESP_CONSOLE_USB_CDC in sdkconfig. No included board configs use this
and it didn't seem to work (if secondary console was set to the default USB
Serial/JTAG then there is no serial output on any port, and if secondary
console was set to None then linking fails.) Can be re-added if there's a
use case for it.

This work was funded through GitHub Sponsors.

Signed-off-by: Angus Gratton <angus@redyak.com.au>
2024-09-03 14:28:26 +10:00
Angus Gratton
6ad6297ef7 esp32: Fix ESP32-C3 USB serial/jtag peripheral pre-IDF 5.1.
Regression in 0a11832cd in IDF 5.0.x where macro
CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG_ENABLED is not defined.

With this patch, ESP32-S3 still USB Serial/JTAG incorrectly (now on all
ESP-IDF versions).

Closes #15701

This work was funded through GitHub Sponsors.

Signed-off-by: Angus Gratton <angus@redyak.com.au>
2024-09-03 10:27:28 +10:00
robert-hh
a04a14163b esp32/machine_uart: Implement Python UART IRQ with IRQ_RX and IRQ_BREAK.
Supported trigger events: IRQ_RX and IRQ_BREAK.  Hard IRQ is not supported.

Signed-off-by: robert-hh <robert@hammelrath.com>
2024-08-29 16:27:43 +10:00
Angus Gratton
b6a3aa10f5 esp32: Remove the increased stack limit margin for ESP32-C3.
The extra limit for C3 dates from 6823514 which added C3 support.
Measuring the minimum stack margins that can pass the stress tests I
measured 768 bytes for ESP32-S3 and 512 bytes for ESP32-C3 on ESP-IDF
V5.2.2 and similar on V5.0.4. i.e. The ESP32-C3 actually needs less stack
margin not more!

I think the extra margin for ESP32-C3 probably arose from:

1. Some toolchain inefficiency in the IDF V4.x RISC-V compiler codegen,
that has since been improved.

OR

2. The race condition that was fixed in e3955f42 where sometimes the limit
wasn't set correctly at all. This seems to trigger more on C3, presumably
some timing artifact, and I'd believe that some binaries might be more
susceptible than others due to random factors.

OR

3. Commit 6007f3e206 which enabled custom
NLR handling for ESP32-C3.

This work was funded through GitHub Sponsors.

Signed-off-by: Angus Gratton <angus@redyak.com.au>
2024-08-16 14:22:51 +10:00
Angus Gratton
6565b3cb34 esp32: Fix Python cstack size for bluetooth irq callbacks.
This value should have been adjusted when the new cstack API was adopted in
e3955f421d, as otherwise the stack limit is too small especially on
ESP32-C3 where the stack limit was 6144 - 2048 - 2048.

Some extra margin is needed for bluetooth irq because invoke_irq_handler()
isn't a top-level task function, NimBLE calls through multiple layers
first. Measuring this overhead on IDF V5.2.2 (by putting an abort() in
invoke_irq_handler() and then measuring the stack size) yielded 672 bytes
on ESP32-S3 and 612 bytes on ESP32-C3, similar to the size reported in
cd66aa05cf.

Sticking with 1024 bytes for added safety margin. This means on Xtensa the
total margin for the BLE task stays the same (2048 bytes) as before
switching to cstack.

This work was funded through GitHub Sponsors.

Signed-off-by: Angus Gratton <angus@redyak.com.au>
2024-08-16 13:55:32 +10:00
Alessandro Gatti
0b75e180a3 esp32/mphalport: Print debug strings even before the GIL is ready.
If verbose debugging is enabled there is some stdout output happening
before the GIL is ready (for example, GC initialisation), and the code
assumed that no string printing occurred before the interpreter was fully
initialised.  Printing long strings would operate without holding the GIL,
which would crash if string output would happen too early in the startup
process.

This commit addresses that issue, making sure verbose debugging output will
work even before the interpreter is fully initialised (as if it is not yet
ready there's no GIL to take care of).

Also, the threshold that would indicate whether a string is "long" (and
thus requiring a GIL release/lock operation) or not was hardcoded to 20
bytes.  This commit makes that configurable, maintaining 20 bytes as a
default.

Fixes issue #15408.

Signed-off-by: Alessandro Gatti <a.gatti@frob.it>
2024-08-16 13:45:03 +10:00
Angus Gratton
e3955f421d esp32: Fix thread stack limit margin, change to new cstack API.
This change moves that complexity out into the stack checker and fixes the
bug where stack margin wasn't set correctly by ESP32-C3 threads.

This work was funded through GitHub Sponsors.

Signed-off-by: Angus Gratton <angus@redyak.com.au>
2024-08-14 12:56:46 +10:00
Angus Gratton
6fead31832 esp32: Enable workaround for math.gamma(-inf) result.
Without this commit, math.gamma(-float("inf")) returns inf instead of
raising a math domain ValueError. Needed for float/math_domain_special.py
test to pass on esp32.

Root cause is an upstream libm bug, has been reported to ESP-IDF.

This work was funded through GitHub Sponsors.

Signed-off-by: Angus Gratton <angus@redyak.com.au>
2024-08-07 14:10:46 +10:00
Alessandro Gatti
6007f3e206 esp32/mpconfigport: Enable the RV32 emitter for ESP32C3 targets.
The RV32 code emitter assumed that the arch-specific NLR was used
instead of the setjmp/longjmp based NLR code.  If the latter NLR
provider was chosen, the emitter would allocate space on the stack
for the NLR buffer but would not fill it in.

This change turns off setjmp()-based NLR and GCREGS for the ESP32C3
target, in favour of more platform-tailored alternatives.  As setjmp()
NLR is now disabled by default, the RV32 emitter can be safely enabled
by default as well for the target in question.

Signed-off-by: Alessandro Gatti <a.gatti@frob.it>
2024-07-26 12:13:03 +10:00
robert-hh
ee1036023e extmod/machine_spi: Support firstbit=LSB for machine.SoftSPI.
Being able to send data out in LSB format can be useful, and having support
in the low-level driver is much better than requiring Python code to
reorder the bits before sending them / after receiving them.  In particular
if the hardware does not support the LSB format (eg RP2040) then one needs
to use the SoftSPI in LSB mode.

For this change a default definition of `MICROPY_PY_MACHINE_SPI_MSB/_LSB`
was added to `py/mpconfig.h`, making them available to all ports.  The
identical defines in `esp32/mpconfigport.h` were deleted.

Resolves issues #5340, #11404.

Signed-off-by: robert-hh <robert@hammelrath.com>
2024-07-12 22:59:31 +10:00
Damien George
bfc3dde2c9 extmod/modmachine: Add MICROPY_PY_MACHINE_RESET configuration option.
Disabled by default, but enabled on all boards that previously had
`MICROPY_PY_MACHINE_BARE_METAL_FUNCS` enabled.

Signed-off-by: Damien George <damien@micropython.org>
2024-03-15 12:04:37 +11:00
Trent Piepho
af67be7adc esp32/modmachine: Support bootloader on ESP32-S2/S3/C2/C3.
On these targets it's possible to enter the bootloader by setting a bit in
an RTC register before resetting.

Structure it in a way that a board can still provide a custom bootloader
handler.  The handler here will be the default if none is provided, for any
board based on the supported targets.

Signed-off-by: Trent Piepho <tpiepho@gmail.com>
2024-03-08 15:55:17 +11:00
Trent Piepho
9fc450689b esp32/mpconfigport: Don't hang when machine.bootloader isn't supported.
Currently only the Arduino Nano ESP32 defines a machine.bootloader handler
for ESP32.  All other boards will intentionally hang.

There is no error message, nor is a NotImplementedError raised.  There's no
indication if Micropython has crashed, or if the bootloader was entered but
USB is not working, which is a real problem the ESP32 bootloader has.  It's
not possible escape from this hang with ^C or any other means besides
physical access to the reset pin or the ability to cycle power.

Change this to only define an implementation of machine.bootloader() when
there is a handler for it.

Signed-off-by: Trent Piepho <tpiepho@gmail.com>
2024-03-08 15:54:44 +11:00
MikeTeachman
0b145fdc2d esp32/machine_i2s: Integrate new I2S IDF driver.
The legacy I2S "shim" is removed and replaced by the new I2S driver.  The
new driver fixes a bug where mono audio plays only in one channel.

Application code size is reduced by 2672 bytes with this change.  Tested on
ESP32, ESP32+spiram, ESP32-S3 using example code from
https://github.com/miketeachman/micropython-i2s-examples

Signed-off-by: Mike Teachman <mike.teachman@gmail.com>
2024-03-08 13:31:02 +11:00
Damien George
cd66aa05cf esp32: Increase NimBLE task stack size and overflow detection headroom.
The Python BLE IRQ handler will most likely run on the NimBLE task, so its
C stack must be large enough to accommodate reasonably complicated Python
code (eg a few call depths).  So increase this stack size.

Also increase the headroom from 1024 to 2048 bytes.  This is needed because
(1) the esp32 architecture uses a fair amount of stack in general; and (2)
by the time execution gets to setting the Python stack top via
`mp_stack_set_top()` in this interlock code, about 600 bytes of stack are
already used, which reduces the amount available for Python.

Fixes issue #12349.

Signed-off-by: Damien George <damien@micropython.org>
2024-02-16 10:19:26 +11:00
IhorNehrutsa
5552896ca8 esp32/mpconfigport: Enable MICROPY_PY_HASHLIB_MD5.
Signed-off-by: IhorNehrutsa <Ihor.Nehrutsa@gmail.com>
2023-12-21 11:33:05 +11:00
Damien George
f6d630877c esp32: Add MICROPY_GC_INITIAL_HEAP_SIZE option and tune it.
This gets back the old heap-size behaviour on ESP32, before auto-split-heap
was introduced: after the heap is grown one time the size is 111936 bytes,
with about 40k left for the IDF.  That's enough to start WiFi and do a
HTTPS request.

Signed-off-by: Damien George <damien@micropython.org>
2023-12-19 18:34:23 +11:00
Damien George
d0758d8a33 esp32/boards/ESP32_GENERIC: Disable network.LAN and VM-opt on D2WD.
To reduce firmware size, because IDF v5.0.4 has increased in size.

Signed-off-by: Damien George <damien@micropython.org>
2023-12-11 13:00:06 +11:00
Damien George
b4b77c17b5 esp32/mpconfigport: Keep some funcs out of IRAM for ESP32-SPIRAM builds.
To make sure the build fits.

Signed-off-by: Damien George <damien@micropython.org>
2023-12-11 12:59:51 +11:00
Ihor Nehrutsa
3069fee386 esp32/machine_i2s: Fix deprecated fields and constants.
Signed-off-by: IhorNehrutsa <Ihor.Nehrutsa@gmail.com>
2023-12-11 12:59:29 +11:00
IhorNehrutsa
495be71d56 esp32/machine_dac: Support one-shot mode of driver.
And simplify board configuration of DAC by using SOC_DAC_SUPPORTED.

Signed-off-by: IhorNehrutsa <Ihor.Nehrutsa@gmail.com>
2023-12-11 12:57:43 +11:00
Damien George
ad806df857 ports: Move definitions of ATOMIC_SECTION macros to mphalport.h.
Also move MICROPY_PY_PENDSV_ENTER/REENTER/EXIT to mphalport.h, for ports
where these are not already there.

This helps separate the hardware implementation of these macros from the
MicroPython configuration (eg for renesas-ra and stm32, the IRQ static
inline helper functions can now be moved to irq.h).

Signed-off-by: Damien George <damien@micropython.org>
2023-12-01 14:37:48 +11:00
Damien George
f523b86541 extmod/modmachine: Provide common implementation of disable/enable_irq.
The ports esp32, mimxrt, rp2 and samd all shared exactly the same
implementation of machine.disable_irq() and machine.enable_irq(),
implemented in terms of MICROPY_{BEGIN,END}_ATOMIC_SECTION.  This commit
factors these implementations into extmod/modmachine.c.

The cc3200, esp8266, nrf, renesas-ra and stm32 ports do not yet use this
common implementation.

Signed-off-by: Damien George <damien@micropython.org>
2023-11-30 16:11:11 +11:00
Damien George
e1ec6af654 extmod/modmachine: Provide common bindings for 6 bare-metal functions.
Minor changes for consistency are:
- nrf gains: unique_id(), freq() [they do nothing]
- samd: deepsleep() now resets after calling lightsleep()
- esp32: lightsleep()/deepsleep() no longer take kw arg "sleep", instead
  it's positional to match others.  also, passing 0 here will now do a 0ms
  sleep instead of acting like nothing was passed.
  reset_cause() no longer takes any args (before it would just ignore them)
- mimxrt: freq() with an argument and lightsleep() both raise
  NotImplementedError

Signed-off-by: Damien George <damien@micropython.org>
2023-11-30 16:11:11 +11:00
Damien George
48b5a7b060 extmod/modmachine: Provide common Python bindings for bootloader().
Signed-off-by: Damien George <damien@micropython.org>
2023-11-30 16:11:11 +11:00
Damien George
7d39db2503 extmod/modmachine: Factor ports' machine module dict to common code.
This is a code factoring to have the dict for the machine module in one
location, and all the ports use that same dict.  The machine.soft_reset()
function implementation is also factored because it's the same for all
ports that did already implement it.  Eventually more functions/bindings
can be factored.

All ports remain functionally the same, except:
- cc3200 port: gains soft_reset, mem8, mem16, mem32, Signal; loses POWER_ON
  (which was a legacy constant, replaced long ago by PWRON_RESET)
- nrf port: gains Signal
- qemu-arm port: gains soft_reset
- unix port: gains soft_reset
- zephyr port: gains soft_reset, mem8, mem16, mem32

Signed-off-by: Damien George <damien@micropython.org>
2023-11-30 16:11:11 +11:00
Damien George
03eae48847 extmod/machine_adc_block: Factor esp32 ADCBlock bindings to common code.
This is a code factoring to have the Python bindings in one location, and
all the ports use those same bindings.  At this stage only esp32 implements
this class, so the code for the bindings comes from that port.

The documentation is also updated to reflect the esp32's behaviour of
ADCBlock.connect().

Signed-off-by: Damien George <damien@micropython.org>
2023-11-07 16:44:35 +11:00
Daniël van de Giessen
e676edac79 esp32/mpconfigport: Remove port-specific GAP name.
Instead use the generic default defined in modbluetooth_nimble.c.
This then also allows custom boards to easily override the default
Bluetooth GAP name.

Signed-off-by: Daniël van de Giessen <daniel@dvdgiessen.nl>
2023-10-31 12:01:47 +11:00
Damien George
5b4a2baff6 extmod/machine_uart: Factor ports' UART Python bindings to common code.
This is a code factoring to have the Python bindings in one location, and
all the ports use those same bindings.  For all ports except the two listed
below there is no functional change.

The nrf port has UART.sendbreak() removed, but this method previously did
nothing.

The zephyr port has the following methods added:
- UART.init(): supports setting timeout and timeout_char.
- UART.deinit(): does nothing, just returns None.
- UART.flush(): raises OSError(EINVAL) because it's not implemented.
- UART.any() and UART.txdone(): raise NotImplementedError.

Signed-off-by: Damien George <damien@micropython.org>
2023-10-26 10:46:42 +11:00
Damien George
95d8b5fd55 extmod/machine_adc: Factor ports' ADC Python bindings to common code.
No functional change, just code factoring to have the Python bindings in
one location, and all the ports use those same bindings.

Signed-off-by: Damien George <damien@micropython.org>
2023-10-23 17:16:45 +11:00
Damien George
f2f3ef162d extmod/machine_i2s: Factor ports' I2S Python bindings to common code.
This factors the basic top-level I2S class code from the ports into
extmod/machine_i2s.c:
- I2S class definition and method table.
- The init and deinit method wrappers.
- The make_new code.

Further factoring will follow.

Signed-off-by: Damien George <damien@micropython.org>
2023-10-20 17:39:57 +11:00
Damien George
60929ec7e2 extmod/machine_wdt: Factor ports' WDT Python bindings to common code.
There are currently 7 ports that implement machine.WDT and a lot of code is
duplicated across these implementations.  This commit factors the common
parts of all these implementations to a single location in
extmod/machine_wdt.c.  This common code provides the top-level Python
bindings (class and method wrappers), and then each port implements the
back end specific to that port.

With this refactor the ports remain functionally the same except for:

- The esp8266 WDT constructor now takes keyword arguments, and accepts the
  "timeout" argument but raises an exception if it's not the default value
  (this port doesn't support changing the timeout).

- The mimxrt and samd ports now interpret the argument to WDT.timeout_ms()
  as signed and if it's negative truncate it to the minimum timeout (rather
  than it being unsigned and a negative value truncating to the maximum
  timeout).

Signed-off-by: Damien George <damien@micropython.org>
2023-10-20 15:36:09 +11:00
Glenn Moloney
9f835df35e esp32,esp8266: Rename MICROPY_ESPNOW to MICROPY_PY_ESPNOW.
For consistency with other Python-level modules.

Also add the corresponding missing preprocessor guard to esp32/modespnow.c,
so that this port compiles if MICROPY_PY_ESPNOW and MICROPY_PY_NETWORK_WLAN
are set to 0.

Fixes #12622.

Signed-off-by: Glenn Moloney <glenn.moloney@gmail.com>
2023-10-10 17:56:36 +11:00
dotnfc
d7f63f994f esp32/main: Allow a board to override the MicroPython task stack size.
This allows the MicroPython task stack size to be overridden by the
mpconfigboard.h settings.

Signed-off-by: dotnfc <dotnfc@163.com>
2023-09-29 16:14:15 +10:00