Skip to content

[boards] Add generic BK7231N (non-Tuya) QFN32#379

Open
RAR wants to merge 2 commits into
libretiny-eu:masterfrom
RAR:add-bk7231n-quectel-variant
Open

[boards] Add generic BK7231N (non-Tuya) QFN32#379
RAR wants to merge 2 commits into
libretiny-eu:masterfrom
RAR:add-bk7231n-quectel-variant

Conversation

@RAR
Copy link
Copy Markdown

@RAR RAR commented Apr 25, 2026

Summary

Adds boards/generic-bk7231n-qfn32.json — a non-Tuya variant of the BK7231N in the QFN32 package. Same chip/pinout as the existing generic-bk7231n-qfn32-tuya, but inherits beken-7231n directly (skipping the beken-7231n-tuya overlay) and sets bkcrypt_coeffs to all zeros.

Why this is needed

The existing beken-7231n-tuya.json overlay applies two Tuya-specific overrides:

Field beken-72xx (default) beken-7231n-tuya (overlay)
flash.download 0x132000+0xA6000 0x12A000+0xA6000
bkcrypt_coeffs (unset) 510fb093a3cbeadc5993a17ec7adeb03

Both are correct for Tuya-branded modules. They're wrong for modules that ship with a non-Tuya BK7231N stock bootloader, of which the most common in the wild seems to be the Quectel FC41D (used in EV chargers, smart meters, and various OEM Wi-Fi gateways). Those modules:

  1. Have stock bootloaders that follow Beken's canonical reference layout — BK_PARTITION_OTA = 0x132000, exactly matching what framework-beken-bdk's beken378/func/user_driver/BkDriverFlash.c:59 already declares. LibreTiny's bundled BDK and the platform JSON disagreed: BDK said 0x132000, the JSON resolved to 0x12A000. OTA writes landed where the bootloader never looks → silent rollback.

  2. Ship with empty eFuse encryption keys (all zeros), so LibreTiny's pre-flash XOR step needs zero coeffs to produce a plaintext image the chip can run. Without this override, the BDK boot is XOR'd against the Tuya key but the chip XORs back against zeros — bootloops.

The new variant fixes both of these for the non-Tuya case without changing any existing behaviour for Tuya users.

Validation

End-to-end tested on a Quectel FC41D module in a Rippleon ROC001 Level 2 EV charger:

  • Initial UART flash (CEN-tied + bootstrap) — clean boot, all entities visible in Home Assistant.
  • Subsequent Wi-Fi OTA via esphome run — round-trips successfully. The stock Quectel bootloader picks up the staged image from 0x132000 and copies it to 0x011000 on next boot, no flag-bit poking required.

The full deployment (generic-bk7231n-qfn32-quectel.json — same partition fix, just a more specific name) is published at https://github.com/RAR/esphome-rippleon-ev/blob/main/boards/generic-bk7231n-qfn32-quectel.json with deployment notes.

Notes

  • build.variant reuses generic-bk7231n-qfn32-tuya — the pinout is identical (same chip, same package), and there's no Tuya-specific code in the variant .c/.h files. Happy to split into a dedicated variant file if maintainers prefer.
  • Doesn't update any docs — followed the precedent in [boards] Add Tuya T34 (BK7231N, LGA36) #378 (Tuya T34 add) which only added board files.
  • ltchiptool's boards/ directory is gitignored and synced from this repo at release time, so no separate ltchiptool PR is needed.

Test plan

  • Maintainer review of the JSON contents
  • (optional) Test on a non-Quectel BK7231N module with empty eFuse to confirm the zero-coeffs path

Adds a non-Tuya variant for the BK7231N in QFN32 — needed for modules
shipped with non-Tuya bootloaders. The existing generic-bk7231n-qfn32-tuya
inherits beken-7231n-tuya, which overrides:

  - flash.download to 0x12A000 (Tuya-specific staging slot)
  - bkcrypt_coeffs to the Tuya XOR key 510fb093...

Modules whose stock bootloader expects Beken's canonical layout — e.g.
the Quectel FC41D (BK7231N inside) shipped in EV chargers — never see
images written to 0x12A000 because their bootloader copies from 0x132000
(per Beken's own framework-beken-bdk's BkDriverFlash.c partition table
and the upstream beken-72xx.json default). And modules with empty eFuse
keys (zero coeffs) need bkcrypt_coeffs set to all-zeros so LibreTiny
writes images plaintext rather than pre-encrypted with the Tuya key.

This new board inherits beken-72xx (which has download=0x132000 already)
and beken-7231n directly, skipping beken-7231n-tuya, then sets
bkcrypt_coeffs to zeros explicitly.

Pinout is identical to the Tuya QFN32 variant (same chip, same package),
so build.variant reuses generic-bk7231n-qfn32-tuya — happy to split the
variant files in a follow-up if maintainers prefer.

Validated end-to-end on a Quectel FC41D in a Rippleon ROC001 EV charger:
UART flash + Wi-Fi OTA both work, no bootloader replacement needed.
Comment thread boards/generic-bk7231n-qfn32.json Outdated
"build": {
"mcu": "bk7231n",
"variant": "generic-bk7231n-qfn32-tuya",
"bkcrypt_coeffs": "00000000000000000000000000000000"
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct me if I'm wrong, but I don't think this line is needed - it will just default to no encryption if the key is omitted.

Comment thread boards/generic-bk7231n-qfn32.json Outdated
],
"build": {
"mcu": "bk7231n",
"variant": "generic-bk7231n-qfn32-tuya",
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The variant should be the same as the board name - otherwise the CI will probably complain and fail to publish changes. Even if they're the same, this is simply just the way it has always been.

Follow-up to kuba2k2's review on PR libretiny-eu#379:

- Drop the explicit bkcrypt_coeffs="00…" since ltchiptool
  (binary.py:55) already falls back to 32 zeros when the field is
  missing — coeffs = self.board["build.bkcrypt_coeffs"] or ("0" * 32).
  Behavior is identical; this just keeps the JSON minimal.

- Rename the build.variant to match the board name and split the
  auto-generated variant files (.h/.c) so each board owns its own
  pair. The pinout JSON we use is identical to the tuya QFN32, so
  the variant content is a copy with the source-comment updated.
@RAR
Copy link
Copy Markdown
Author

RAR commented May 4, 2026

Thanks for the review! Both addressed in 7c2846b:

  • bkcrypt_coeffs: confirmed via ltchiptool/soc/bk72xx/binary.py:55 (coeffs = self.board["build.bkcrypt_coeffs"] or ("0" * 32)) that omitting the field is identical to setting it to 32 zeros. Dropped the explicit value.
  • variant name: split the auto-generated .h/.c files so the new board owns its own pair, and updated build.variant to match the board name. Pinout content is identical to the tuya QFN32 (same chip, same package), just with the source-comment header pointing at the new JSON.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants