ht32f491x3/esk32: add docs and flash helpers

Document the HT32F491x3 ESK32 board, build steps,
flashing flow, and validation commands. Add WSL and
PowerShell flash backends plus a Python wrapper.

Signed-off-by: Felipe Moura <moura.fmo@gmail.com>
This commit is contained in:
Felipe Moura
2026-03-25 08:19:45 -03:00
committed by Xiang Xiao
parent 41c53e7fdc
commit bd6a466317
7 changed files with 554 additions and 5 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 206 KiB

@@ -0,0 +1,177 @@
==================
ESK32 (HT32F49163)
==================
.. tags:: arch:arm, chip:ht32f491x3, chip:ht32f49163, vendor:holtek
The ESK32 is a development board based on the Holtek HT32F49163 MCU.
The current NuttX port targets the HT32F49163 device used on the
HT32F49163 development kit and focuses on a working serial-console NSH
configuration with basic board bring-up.
For additional hardware details, refer to Holtek's
`HT32F491x3 Starter Kit User Guide <https://www.holtek.com/webapi/106680/HT32F491x3_StarterKitUserManualv100.pdf>`_.
.. figure:: ht32f491x3-starter-kit.jpg
:align: center
:alt: HT32F491x3 Starter Kit
HT32F491x3 Starter Kit board photo
Features
========
The current port provides:
* Holtek HT32F49163 MCU from the HT32F491x3 family
* ARM Cortex-M4 core with FPU support
* Boot and clock initialization for the ESK32 8 MHz external crystal
* System clock configured to 150 MHz
* USART1 serial console at 115200 8N1
* ``/bin`` mounted through ``binfs``
* ``/proc`` mounted through ``procfs``
* User LED registration through ``/dev/userleds``
* Basic internal GPIO helpers used by the console and LED support
The default ``esk32:nsh`` configuration also enables these built-in
applications:
* ``hello``
* ``ostest``
* ``dumpstack``
* ``leds``
Buttons and LEDs
================
Board LEDs
----------
Three user LEDs from the development kit are currently mapped by the board
port. They are active-low and are exposed through the standard NuttX
``USERLED`` interface and the ``/dev/userleds`` device.
===== =========== ==========
LED Port/Pin Notes
===== =========== ==========
LED2 PD13 Active-low
LED3 PD14 Active-low
LED4 PD15 Active-low
===== =========== ==========
The generic ``leds`` example from ``nuttx-apps`` can be used to validate the
LED interface.
Board Buttons
-------------
No button is currently exposed by the board port.
Pin Mapping
===========
The current port uses the following MCU pins:
===== ========== ==========
Pin Signal Notes
===== ========== ==========
PA9 USART1_TX Default serial console TX
PA10 USART1_RX Default serial console RX
PD13 LED2 User LED, active-low
PD14 LED3 User LED, active-low
PD15 LED4 User LED, active-low
===== ========== ==========
Flashing
========
The board directory includes a helper script for flashing through Holtek's
Windows OpenOCD package from a WSL-based development environment:
.. code-block:: console
$ ./boards/arm/ht32f491x3/esk32/tools/flash.sh
The script expects:
* ``nuttx.bin`` already generated in the ``nuttx`` directory
* Holtek xPack OpenOCD installed under
``C:\Program Files (x86)\Holtek HT32 Series\HT32-IDE\xPack\xpack-openocd-0.11.0-4``
* an HT32-Link compatible debug connection
* Holtek xPack OpenOCD can be installed together with the HT32 IDE, available
from Holtek's website: `Holtek Downloads <https://www.holtek.com/page/index>`_
Useful options:
.. code-block:: console
$ ./boards/arm/ht32f491x3/esk32/tools/flash.sh --dry-run
$ ./boards/arm/ht32f491x3/esk32/tools/flash.sh --device HT32F49163_100LQFP
$ ./boards/arm/ht32f491x3/esk32/tools/flash.sh --openocd-root /mnt/c/path/to/openocd
Testing Notes
=============
The following commands are useful for validating the current port:
.. code-block:: console
nsh> hello
nsh> ostest
nsh> dumpstack
nsh> leds
When ``leds`` is executed, the example opens ``/dev/userleds`` and cycles
through the LED bitmasks supported by the board.
Current Limitations
===================
The current port is still intentionally small. In particular:
* only the ``nsh`` board configuration is maintained
* only USART1 routing is described by the board port
* LEDs are supported, but board buttons are not yet implemented
* internal GPIO helpers exist, but there is not yet a board-level ``/dev/gpio``
test interface in this port
Configurations
==============
nsh
---
This is the currently maintained configuration for the board. It provides a
serial console with the NuttShell and mounts ``/bin`` and ``/proc`` during
board bring-up.
Configure and build it from the ``nuttx`` directory:
.. code-block:: console
$ ./tools/configure.sh -l esk32:nsh
$ make -j
After boot, a typical prompt looks like:
.. code-block:: console
NuttShell (NSH) NuttX-12.x.x
nsh> ls /
/:
bin/
dev/
proc/
And the built-in applications can be listed with:
.. code-block:: console
nsh> ls /bin
dd
dumpstack
hello
leds
nsh
ostest
sh
@@ -0,0 +1,14 @@
===============
Holtek HT32F491
===============
The HT32F491x3 family is based on the ARM Cortex-M4 core.
Supported Boards
================
.. toctree::
:glob:
:maxdepth: 1
boards/*/*
+3 -3
View File
@@ -56,10 +56,10 @@
#define HT32_NGPIO 6
#define HT32_HICK_FREQUENCY 8000000
#define HT32_HICK48_FREQUENCY 48000000
#define HT32_HICK48_FREQUENCY 48000000
#define HT32_HEXT_MIN_FREQUENCY 4000000
#define HT32_HEXT_MAX_FREQUENCY 25000000
#define HT32_PLL_MAX_FREQUENCY 150000000
#define HT32_HEXT_MAX_FREQUENCY 25000000
#define HT32_PLL_MAX_FREQUENCY 150000000
#define HT32_SYSCLK_FREQUENCY CONFIG_HT32F491X3_SYSCLK_FREQUENCY
#define HT32_HCLK_FREQUENCY HT32_SYSCLK_FREQUENCY
+239
View File
@@ -0,0 +1,239 @@
############################################################################
# boards/arm/ht32f491x3/esk32/tools/flash.ps1
#
# SPDX-License-Identifier: Apache-2.0
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership. The
# ASF licenses this file to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance with the
# License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
############################################################################
param(
[Parameter(ValueFromRemainingArguments = $true)]
[string[]]$RemainingArgs
)
$ErrorActionPreference = "Stop"
Set-StrictMode -Version 2
$ScriptDir = $PSScriptRoot
$TopDir = [System.IO.Path]::GetFullPath((Join-Path $ScriptDir "..\..\..\..\.."))
$DefaultBin = Join-Path $TopDir "nuttx.bin"
$WindowsSetup = "Windows 10 Pro"
$PowerShellSetup = "Windows PowerShell 5.1 or newer"
$HT32IDEVersion = "HT32-IDE 1.0.6 (Build Date: 2025/12/04)"
$HT32IDERoot = "C:\Program Files (x86)\Holtek HT32 Series\HT32-IDE"
$OpenOCDPackage = "xPack OpenOCD 0.11.0-4"
$OpenOCDRoot = Join-Path $HT32IDERoot "xPack\xpack-openocd-0.11.0-4"
$OpenOCDExe = Join-Path $OpenOCDRoot "bin\openocd.exe"
$ScriptsDir = Join-Path $OpenOCDRoot "scripts"
$FlashLoader = Join-Path $OpenOCDRoot "FlashLoader\HT32F491x3_256.HLM"
$DeviceName = "HT32F49163_100LQFP"
$FlashBase = "0x08000000"
$FlashEnd = "0x0803FFFF"
$SRAMBase = "0x20000000"
$WorkAreaSize = "0xC000"
$BinPath = $DefaultBin
$DryRun = $false
$ProgName = $MyInvocation.MyCommand.Name
function Get-AbsolutePath {
param(
[string]$Path
)
if ([System.IO.Path]::IsPathRooted($Path)) {
return [System.IO.Path]::GetFullPath($Path)
}
return [System.IO.Path]::GetFullPath((Join-Path (Get-Location) $Path))
}
function Format-CommandArgument {
param(
[string]$Argument
)
if ($Argument -match '[\s"]') {
return '"' + ($Argument -replace '"', '\"') + '"'
}
return $Argument
}
function Show-Assumptions {
Write-Host "############################################################################"
Write-Host "# Assumptions:"
Write-Host "#"
Write-Host "# - $WindowsSetup"
Write-Host "# - $PowerShellSetup"
Write-Host "# - This is the native Windows backend; use flash.py from the same"
Write-Host "# directory for automatic backend selection, or run this script directly"
Write-Host "# - $HT32IDEVersion installed at:"
Write-Host "# $HT32IDERoot"
Write-Host "# - $OpenOCDPackage available at:"
Write-Host "# $OpenOCDRoot"
Write-Host "# - Holtek HT-Link probe using interface/htlink.cfg"
Write-Host "# - ESK32 board with $DeviceName and FlashLoader\HT32F491x3_256.HLM"
Write-Host "#"
Write-Host "# Update this script if any of the above are not true."
Write-Host "#"
Write-Host "############################################################################"
Write-Host ""
}
function Show-Usage {
Write-Host "Usage: $ProgName [options]"
Write-Host ""
Write-Host "Options:"
Write-Host " --bin PATH Binary to flash. Default: $DefaultBin"
Write-Host " --device NAME Holtek expected device name. Default: $DeviceName"
Write-Host " --openocd-root DIR Holtek xPack OpenOCD root."
Write-Host " --dry-run Print the OpenOCD command without executing it."
Write-Host " --help Show this help."
Write-Host ""
Write-Host "Examples:"
Write-Host " .\$ProgName"
Write-Host " .\$ProgName --dry-run"
Write-Host " .\$ProgName --device HT32F49163_100LQFP"
}
function Fail {
param(
[string]$Message,
[switch]$ShowUsage
)
[Console]::Error.WriteLine($Message)
if ($ShowUsage) {
Show-Usage
}
exit 1
}
Show-Assumptions
for ($i = 0; $i -lt $RemainingArgs.Count; $i++) {
switch ($RemainingArgs[$i]) {
"--bin" {
if ($i + 1 -ge $RemainingArgs.Count) {
Fail "Missing value for --bin" -ShowUsage
}
$i++
$BinPath = Get-AbsolutePath $RemainingArgs[$i]
}
"--device" {
if ($i + 1 -ge $RemainingArgs.Count) {
Fail "Missing value for --device" -ShowUsage
}
$i++
$DeviceName = $RemainingArgs[$i]
}
"--openocd-root" {
if ($i + 1 -ge $RemainingArgs.Count) {
Fail "Missing value for --openocd-root" -ShowUsage
}
$i++
$OpenOCDRoot = Get-AbsolutePath $RemainingArgs[$i]
$OpenOCDExe = Join-Path $OpenOCDRoot "bin\openocd.exe"
$ScriptsDir = Join-Path $OpenOCDRoot "scripts"
$FlashLoader = Join-Path $OpenOCDRoot "FlashLoader\HT32F491x3_256.HLM"
}
"--dry-run" {
$DryRun = $true
}
"--help" {
Show-Usage
exit 0
}
"-h" {
Show-Usage
exit 0
}
default {
Fail "Unknown argument: $($RemainingArgs[$i])" -ShowUsage
}
}
}
if (-not $DryRun) {
if (-not (Test-Path -Path $BinPath -PathType Leaf)) {
Fail "Binary not found: $BinPath"
}
if (-not (Test-Path -Path $OpenOCDExe -PathType Leaf)) {
Fail "OpenOCD executable not found: $OpenOCDExe"
}
if (-not (Test-Path -Path $FlashLoader -PathType Leaf)) {
Fail "Flash loader not found: $FlashLoader"
}
}
$OpenOCDArgs = @(
"-s"
$ScriptsDir
"-c"
"hlm_SRAM $SRAMBase $WorkAreaSize"
"-c"
"hlm_loader {$FlashLoader} $FlashBase $FlashEnd"
"-c"
"ht_flags erase_sector"
"-c"
"set WORKAREASIZE $WorkAreaSize"
"-f"
"interface/htlink.cfg"
"-f"
"target/HLM491x3.cfg"
"-c"
"set_expected_name $DeviceName"
"-c"
"program $BinPath verify reset exit $FlashBase"
)
$CommandParts = @($OpenOCDExe) + $OpenOCDArgs
$CommandText = ($CommandParts | ForEach-Object { Format-CommandArgument $_ }) -join " "
Write-Host ("TOPDIR : {0}" -f $TopDir)
Write-Host ("Binary : {0}" -f $BinPath)
Write-Host ("Device : {0}" -f $DeviceName)
Write-Host ("OpenOCD : {0}" -f $OpenOCDExe)
Write-Host ("Flash loader: {0}" -f $FlashLoader)
if ($DryRun) {
if (-not (Test-Path -Path $BinPath -PathType Leaf)) {
Write-Host "Warning : binary not found yet"
}
if (-not (Test-Path -Path $OpenOCDExe -PathType Leaf)) {
Write-Host "Warning : OpenOCD executable not found"
}
if (-not (Test-Path -Path $FlashLoader -PathType Leaf)) {
Write-Host "Warning : flash loader not found"
}
Write-Host ("Command : {0}" -f $CommandText)
exit 0
}
& $OpenOCDExe @OpenOCDArgs
exit $LASTEXITCODE
+91
View File
@@ -0,0 +1,91 @@
#!/usr/bin/env python3
############################################################################
# boards/arm/ht32f491x3/esk32/tools/flash.py
#
# SPDX-License-Identifier: Apache-2.0
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership. The
# ASF licenses this file to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance with the
# License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
############################################################################
# Wrapper that dispatches to flash.sh in WSL and flash.ps1 on Windows.
import os
import platform
import shutil
import subprocess
import sys
from pathlib import Path
SCRIPT_DIR = Path(__file__).resolve().parent
def is_windows_host():
system = platform.system().lower()
return os.name == "nt" or system.startswith("msys") or system.startswith("cygwin")
def is_wsl():
if "WSL_INTEROP" in os.environ or "WSL_DISTRO_NAME" in os.environ:
return True
release = platform.release().lower()
version = platform.version().lower()
return "microsoft" in release or "microsoft" in version
def build_command(argv):
if is_windows_host():
powershell = shutil.which("powershell.exe") or shutil.which("pwsh.exe")
if powershell is None:
raise RuntimeError("Unable to find powershell.exe or pwsh.exe in PATH.")
backend = SCRIPT_DIR / "flash.ps1"
return [powershell, "-ExecutionPolicy", "Bypass", "-File", str(backend), *argv]
if sys.platform.startswith("linux"):
if not is_wsl():
raise RuntimeError(
"Unsupported host: this wrapper supports Windows native and WSL."
)
bash = shutil.which("bash")
if bash is None:
raise RuntimeError("Unable to find bash in PATH.")
backend = SCRIPT_DIR / "flash.sh"
return [bash, str(backend), *argv]
raise RuntimeError(
"Unsupported host: this wrapper supports Windows native and WSL."
)
def main(argv):
try:
cmd = build_command(argv)
completed = subprocess.run(cmd, check=False)
return completed.returncode
except RuntimeError as err:
print(err, file=sys.stderr)
return 1
except OSError as err:
print(f"Failed to start backend script: {err}", file=sys.stderr)
return 1
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))
+30 -2
View File
@@ -27,7 +27,11 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
TOPDIR="$(cd "${SCRIPT_DIR}/../../../../.." && pwd)"
DEFAULT_BIN="${TOPDIR}/nuttx.bin"
OPENOCD_ROOT="/mnt/c/Program Files (x86)/Holtek HT32 Series/HT32-IDE/xPack/xpack-openocd-0.11.0-4"
WINDOWS_SETUP="Windows 10 Pro with WSL2"
HT32_IDE_VERSION="HT32-IDE 1.0.6 (Build Date: 2025/12/04)"
HT32_IDE_ROOT="/mnt/c/Program Files (x86)/Holtek HT32 Series/HT32-IDE"
OPENOCD_PACKAGE="xPack OpenOCD 0.11.0-4"
OPENOCD_ROOT="${HT32_IDE_ROOT}/xPack/xpack-openocd-0.11.0-4"
OPENOCD_EXE="${OPENOCD_ROOT}/bin/openocd.exe"
SCRIPTS_DIR="${OPENOCD_ROOT}/scripts"
FLASH_LOADER="${OPENOCD_ROOT}/FlashLoader/HT32F491x3_256.HLM"
@@ -39,6 +43,28 @@ WORKAREA_SIZE="0xC000"
BIN_PATH="${DEFAULT_BIN}"
DRY_RUN=0
print_assumptions() {
cat <<EOF
############################################################################
# Assumptions:
#
# - ${WINDOWS_SETUP}; the Windows C: drive is available in WSL at /mnt/c
# - This is the WSL backend; use flash.py from the same directory for
# automatic backend selection, or run this script directly from WSL2
# - ${HT32_IDE_VERSION} installed at:
# ${HT32_IDE_ROOT}
# - ${OPENOCD_PACKAGE} available at:
# ${OPENOCD_ROOT}
# - Holtek HT-Link probe using interface/htlink.cfg
# - ESK32 board with ${DEVICE_NAME} and FlashLoader/HT32F491x3_256.HLM
#
# Update this script if any of the above are not true.
#
############################################################################
EOF
}
usage() {
cat <<EOF
Usage: $0 [options]
@@ -53,10 +79,12 @@ Options:
Examples:
$0
$0 --dry-run
$0 --device HT32F49163_64LQFP
$0 --device HT32F49163_100LQFP
EOF
}
print_assumptions
while (($# > 0)); do
case "$1" in
--bin)