<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Andelf</title>
    <description>The latest articles on Forem by Andelf (@andelf).</description>
    <link>https://forem.com/andelf</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F553824%2F51b9c1ac-b63c-4587-8879-866181b09537.jpg</url>
      <title>Forem: Andelf</title>
      <link>https://forem.com/andelf</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/andelf"/>
    <language>en</language>
    <item>
      <title>Bare-Metal Embedded Programming on K230 Using Rust</title>
      <dc:creator>Andelf</dc:creator>
      <pubDate>Tue, 10 Dec 2024 04:26:13 +0000</pubDate>
      <link>https://forem.com/andelf/bare-metal-embedded-programming-on-k230-using-rust-4h4g</link>
      <guid>https://forem.com/andelf/bare-metal-embedded-programming-on-k230-using-rust-4h4g</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This article is translated to English with the help of CharGPT. The original Chinese version is at &lt;a href="https://andelf.github.io/2024/12/09/k230-bare-metal-embedded-programming-using-rust/" rel="noopener noreferrer"&gt;基于 Rust 的 K230 裸机嵌入式编程&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Difficulty&lt;/strong&gt;: Intermediate. Readers should have foundational knowledge of embedded systems and Rust embedded development.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This article documents the process of conducting bare-metal development using Rust on the K230 chip. From analyzing the startup process, parsing the firmware format, writing bare-metal Rust programs, perfecting initialization code, to actual peripheral control and function implementation, and exploring optimization schemes in subsequent development—all have been thoroughly investigated.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Relevant code repository for this article&lt;/strong&gt;: &lt;a href="https://github.com/andelf/k230-bare-metal" rel="noopener noreferrer"&gt;k230-bare-metal&lt;/a&gt;. It is recommended to refer to earlier commits such as &lt;a href="https://github.com/andelf/k230-bare-metal/tree/e1596804045b95b2f639036e10653605f04c72a6" rel="noopener noreferrer"&gt;e15968040&lt;/a&gt; for better understanding.&lt;/p&gt;

&lt;h2&gt;
  
  
  Project Background
&lt;/h2&gt;

&lt;p&gt;Previously, I received a review opportunity for the &lt;a href="https://wiki.lckfb.com/zh-hans/lushan-pi-k230/" rel="noopener noreferrer"&gt;Luchan Pi K230-CanMV&lt;/a&gt; development board from LCSC(The same group as JLC). In addition, I also own a &lt;a href="https://wiki.youyeetoo.com/en/CanMV-K230" rel="noopener noreferrer"&gt;CanMV-K230&lt;/a&gt; development board.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;K230 chip&lt;/strong&gt; is an AIoT SoC launched by Canaan Technology, adopting a heterogeneous unit acceleration computing architecture. It integrates &lt;strong&gt;2 RISC-V computing cores&lt;/strong&gt; and an AI subsystem &lt;strong&gt;KPU (Knowledge Process Unit)&lt;/strong&gt;. In terms of the timeline, it should be one of the earliest chips on the market to support the &lt;strong&gt;RVV 1.0 vector extension&lt;/strong&gt;. Main features include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Dual-core RISC-V processor&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Core 0&lt;/strong&gt;: 64-bit RISC-V (RV64GCB), &lt;strong&gt;800MHz&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Core 1&lt;/strong&gt;: 64-bit RISC-V, &lt;strong&gt;1.6GHz&lt;/strong&gt;, supports &lt;strong&gt;RVV 1.0 vector extension&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Dedicated acceleration units&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;KPU&lt;/strong&gt;: AI inference accelerator, supports &lt;strong&gt;INT8/INT16&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DPU&lt;/strong&gt;: 3D structured light depth calculation unit&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;VPU&lt;/strong&gt;: Video codec, supports &lt;strong&gt;4K resolution&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Rich peripheral interfaces&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Communication interfaces&lt;/strong&gt;: UART×5, I2C×5, SPI×3&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Storage interfaces&lt;/strong&gt;: USB 2.0×2, SD/eMMC&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Others&lt;/strong&gt;: GPIO×72, PWM×6, WDT, RTC, Timer&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Under normal usage, the development board runs the &lt;strong&gt;&lt;a href="https://github.com/kendryte/canmv_k230" rel="noopener noreferrer"&gt;CanMV&lt;/a&gt;&lt;/strong&gt; firmware, which is compatible with &lt;strong&gt;&lt;a href="https://openmv.io/" rel="noopener noreferrer"&gt;OpenMV&lt;/a&gt;&lt;/strong&gt;, providing a very convenient development environment for developers.&lt;/p&gt;

&lt;p&gt;The firmware is based on &lt;strong&gt;&lt;a href="https://github.com/RT-Thread/rt-thread" rel="noopener noreferrer"&gt;RT-Thread&lt;/a&gt; Smart (RT-Smart)&lt;/strong&gt;, which is a version of RT-Thread that supports user-space applications, suitable for SoCs with MMU, such as the K230. &lt;strong&gt;CanMV&lt;/strong&gt; is implemented as an application (a fork of MicroPython) on RT-Thread.&lt;/p&gt;

&lt;p&gt;Additionally, early versions of the CanMV firmware used &lt;strong&gt;Linux + RT-Thread + MicroPython&lt;/strong&gt;. The official sources also provide a pure &lt;strong&gt;Linux&lt;/strong&gt; version of the firmware.&lt;/p&gt;

&lt;p&gt;This project aims to explore:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The differences in startup methods and usage modes between &lt;strong&gt;MPUs and MCUs&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;How to use &lt;strong&gt;Rust&lt;/strong&gt; for bare-metal development on MPU chips&lt;/li&gt;
&lt;li&gt;The underlying startup mechanism and hardware features of the &lt;strong&gt;K230&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For MPUs and most MCUs, there is an on-chip Boot ROM used to start the system. Typically, the Boot ROM initializes some hardware (e.g., SPI Flash, TF Card), loads the firmware into memory, and then executes the first instruction of the system firmware (such as U-Boot). Subsequently, the system firmware provided by the user further initializes more hardware and loads the actual operating system.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bare-metal development&lt;/strong&gt; refers to running programs directly on the hardware without using an operating system, similar to how an MCU runs directly after the system's Boot ROM.&lt;/p&gt;

&lt;h2&gt;
  
  
  Boot Code Analysis
&lt;/h2&gt;

&lt;p&gt;First, we need to read the code from the official &lt;a href="https://github.com/kendryte/canmv_k230" rel="noopener noreferrer"&gt;CanMV&lt;/a&gt; repository to determine if there are any non-open-source parts, especially core components like &lt;strong&gt;U-Boot&lt;/strong&gt; and &lt;strong&gt;RT-Thread/Linux drivers&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For U-Boot, we also need to confirm whether the &lt;strong&gt;Secondary Program Loader (SPL)&lt;/strong&gt; is open-source. SPL is often used to initialize peripherals like DDR and to load U-Boot. Many manufacturers do not open-source it and only provide binary files.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: SPL literally means &lt;strong&gt;Secondary Program Loader&lt;/strong&gt;. Boot ROM is generally considered the first-stage loader.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The good news is that the relevant code is all in the &lt;strong&gt;&lt;a href="https://github.com/kendryte/canmv_k230" rel="noopener noreferrer"&gt;CanMV&lt;/a&gt;&lt;/strong&gt; repository and open-source. However, the code structure is relatively complex, requiring some time to read and analyze the specific startup processes and logic.&lt;/p&gt;

&lt;p&gt;With the advent of ChatGPT, we can complete code analysis more quickly. I once joked that if ChatGPT had appeared a few years earlier, many toolchains would not need to exist.&lt;/p&gt;

&lt;p&gt;Here, we only consider the &lt;strong&gt;TF card&lt;/strong&gt; startup scenario, where the system firmware is on the TF card, and the on-chip Boot ROM loads the firmware into memory. That is, our program needs to perform the same tasks as U-Boot, including the functions of SPL.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: &lt;strong&gt;TF card&lt;/strong&gt;, &lt;strong&gt;SD card&lt;/strong&gt;, and &lt;strong&gt;eMMC&lt;/strong&gt; are essentially the same at the protocol level. This article does not make strict distinctions among them.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  From Power-On Reset to Loading and Executing User Firmware
&lt;/h3&gt;

&lt;p&gt;First, the &lt;strong&gt;Boot ROM&lt;/strong&gt; loads the firmware into memory. This part of the logic is directly solidified in the chip's Boot ROM and is uncontrollable because the Boot ROM's code and logic are integrated inside the chip and cannot be modified or interfered with by the user. The Boot ROM determines the boot method by reading the status of the &lt;strong&gt;BOOT0&lt;/strong&gt; and &lt;strong&gt;BOOT1&lt;/strong&gt; pins. The voltage levels of these two pins decide from which medium the chip loads the boot program during startup.&lt;/p&gt;

&lt;p&gt;According to the chip manual, the Boot ROM's memory-mapped location is &lt;strong&gt;0x9120_0000 ~ 0x9121_0000&lt;/strong&gt;, using the first half of the SRAM &lt;strong&gt;0x8020_0000 ~ 0x8030_0000&lt;/strong&gt;. This information can be confirmed by reading characteristics like &lt;strong&gt;sp/ra&lt;/strong&gt; through bare-metal programs. For example, the Boot ROM sets the stack pointer &lt;strong&gt;sp&lt;/strong&gt; to the highest address of available memory. The Boot ROM typically uses the &lt;strong&gt;call&lt;/strong&gt; instruction to transfer control to the user firmware, and &lt;strong&gt;ra&lt;/strong&gt; will be set to the &lt;strong&gt;pc&lt;/strong&gt; of the current jump function.&lt;/p&gt;

&lt;p&gt;The Boot ROM loads the firmware (usually U-Boot) from the TF card according to a predetermined fixed format. Specifically, the Boot ROM accesses the TF card, reads the firmware area, decodes it, and copies it to the specified memory location &lt;strong&gt;0x8030_0000&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;After the firmware is loaded, the Boot ROM transfers control to the firmware just loaded into memory, i.e., it jumps to execute U-Boot. This marks the transition of the startup process from the Boot ROM stage to the firmware (U-Boot) stage. U-Boot, as a more powerful bootloader, can further initialize system hardware, load the operating system kernel such as &lt;strong&gt;RT-Thread&lt;/strong&gt; or &lt;strong&gt;Linux Kernel&lt;/strong&gt;, and execute other user-defined startup tasks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;U-Boot&lt;/strong&gt; has a two-stage startup process: &lt;strong&gt;SPL&lt;/strong&gt; and &lt;strong&gt;U-Boot&lt;/strong&gt;. SPL is used to initialize peripherals like DDR and to load U-Boot. We will not consider the logic after U-Boot (e.g., OpenSBI, RT-Thread Smart) in this article. From the firmware format, this part exists in the form of firmware partitions, sequentially loaded by U-Boot SPL to load U-Boot, and then U-Boot loads RT-Thread/Linux Kernel.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;K230&lt;/strong&gt; is equipped with two CPUs, referred to as &lt;strong&gt;CPU0 (small core)&lt;/strong&gt; and &lt;strong&gt;CPU1 (big core)&lt;/strong&gt;. The two cores operate at different frequencies, and &lt;strong&gt;CPU1&lt;/strong&gt; supports the &lt;strong&gt;RVV 1.0 vector extension&lt;/strong&gt;, constituting a heterogeneous multi-core architecture.&lt;/p&gt;

&lt;p&gt;During the startup process, when the chip's reset signal is released, the &lt;strong&gt;Boot ROM&lt;/strong&gt; starts execution on the &lt;strong&gt;small core&lt;/strong&gt;. This means that &lt;strong&gt;CPU0&lt;/strong&gt; is the first activated core, responsible for executing the initial boot program and performing basic system initialization. Meanwhile, the &lt;strong&gt;de-reset&lt;/strong&gt; process of the &lt;strong&gt;big core&lt;/strong&gt; is controlled by the small core. In other words, while the small core completes its own initialization, it also needs to send instructions to release the reset state of the big core, allowing it to start running from a specific location. This architectural design ensures that the small core not only shoulders the responsibility of booting the system but also controls the startup process of the big core, laying the foundation for the entire SoC to begin functioning.&lt;/p&gt;

&lt;h3&gt;
  
  
  Firmware Format
&lt;/h3&gt;

&lt;p&gt;To ensure our firmware is recognized by the Boot ROM, it needs to conform to a specific firmware format. Different SoC manufacturers have different solutions; some use fixed filenames on FAT32, some use fixed formats at specific offsets, and some use configuration files. The K230 uses a &lt;strong&gt;fixed offset firmware format&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The Boot ROM of the &lt;strong&gt;K230&lt;/strong&gt; identifies data characteristics at a fixed offset on the TF card, and firmware that meets the format will be loaded into memory. The Boot ROM has initialized &lt;strong&gt;UART0&lt;/strong&gt; and will output simple error messages, such as &lt;strong&gt;"boot failed with exit code 19"&lt;/strong&gt; indicating that the TF card was not found, or &lt;strong&gt;"boot failed with exit code 13"&lt;/strong&gt; indicating a firmware format error.&lt;/p&gt;

&lt;p&gt;After analyzing the relevant compilation process, we deduced the firmware format of the K230 as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;00000000  +-------------+-------------+-------------+-------------+
          | ........... | ........... | ........... | ........... |  &amp;lt;- Partition table / any other data
          | ........... | ........... | ........... | ........... |
          +-------------+-------------+-------------+-------------+
00100000  | 4B 32 33 30 | 8C FC 02 00 | 00 00 00 00 | BF 8D 0F 38 |   &amp;lt;- Firmware header: "K230...........8"
          | MAGIC: K230 | Length      | Encryption  | SHA256 hash |   &amp;lt;- Encryption 0: none, 1: SM4, 2: AES+RSA
          +-------------+-------------+-------------+-------------+
00100010  | 03 F3 87 07 | FA 1B D8 1D | 4F A0 CD A0 | 7B 54 35 BD |   &amp;lt;- SHA256 hash continuation
          +-------------+-------------+-------------+-------------+
00100020  | 35 82 85 89 | 66 4D AC 27 | CA F8 56 49 | 00 00 00 00 |   &amp;lt;- SHA256 hash continuation + Padding
          +-------------+-------------+-------------+-------------+
00100030  | 00 00 00 00 | 00 00 00 00 | 00 00 00 00 | 00 00 00 00 |   &amp;lt;- Padding zeros
          +-------------+-------------+-------------+-------------+
          | ........... | ........... | ........... | ........... |   &amp;lt;- Padding zeros
          +-------------+-------------+-------------+-------------+
00100210  | 00 00 00 00 | 73 25 40 F1 | 2A 82 AE 84 | 93 01 00 00 |   &amp;lt;- Firmware data, length zero position
          | Version     | OpCodes     | Data        | Padding     |   &amp;lt;- Version: 0
          | ........... | ........... | ........... | ........... |   &amp;lt;- Firmware data, raw opcodes
          +-------------+-------------+-------------+-------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Relevant C structure definitions are located in &lt;strong&gt;&lt;a href="https://github.com/kendryte/canmv_k230" rel="noopener noreferrer"&gt;CanMV&lt;/a&gt;&lt;/strong&gt; at &lt;code&gt;src/uboot/uboot/board/kendryte/common/board_common.h&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here, we simplify the processing by not encrypting the firmware and using version number 0. We write a Python script to create the &lt;code&gt;.img&lt;/code&gt; firmware file for the TF card image:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;#!/usr/bin/env python3
# genimage.py
&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;

&lt;span class="n"&gt;MAGIC&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;K230&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;digest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;digest&lt;/span&gt;

&lt;span class="n"&gt;VERSION&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\x00\x00\x00\x00&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;./firmware.bin&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rb&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;input_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;VERSION&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;

&lt;span class="n"&gt;data_len&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input_data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;raw_data_len&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data_len&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;byteorder&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;little&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;encryption_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="n"&gt;encryption_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;encryption_type&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;byteorder&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;little&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;hash_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input_data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;firmware&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;MAGIC&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;raw_data_len&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;encryption_type&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;hash_data&lt;/span&gt;

&lt;span class="n"&gt;firmware&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nf"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;516&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# padding
&lt;/span&gt;&lt;span class="n"&gt;firmware&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;input_data&lt;/span&gt;

&lt;span class="n"&gt;img&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0x100000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;firmware&lt;/span&gt;  &lt;span class="c1"&gt;# image offset 0x100000
&lt;/span&gt;
&lt;span class="c1"&gt;# Ensure the image size is a multiple of 512 bytes
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;512&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;img&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nf"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;512&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;512&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;./firmware.img&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;wb&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;len&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Where &lt;code&gt;firmware.bin&lt;/code&gt; is generated via &lt;code&gt;objcopy -O binary&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cargo objcopy &lt;span class="nt"&gt;--release&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nt"&gt;-O&lt;/span&gt; binary firmware.bin &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; python3 genimage.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that disk images are generally aligned to 512 bytes, so we need to pad to align to 512 bytes.&lt;/p&gt;

&lt;p&gt;Flashing the firmware can be done using any programming tool, including the &lt;code&gt;dd&lt;/code&gt; command.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start Writing Some Bare-Metal Code
&lt;/h2&gt;

&lt;p&gt;With the firmware loading settled, the SoC control flow can be handed over to our program. Here, we use the Rust language to write a bare-metal program.&lt;/p&gt;

&lt;p&gt;Essential elements for Rust bare-metal embedded development include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Toolchain target&lt;/strong&gt;: Install using &lt;code&gt;rustup&lt;/code&gt;: &lt;code&gt;rustup target add riscv64gc-unknown-none-elf&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Linker script&lt;/strong&gt; &lt;code&gt;link.x&lt;/code&gt;: Used to define memory layout (can also directly define firmware layout)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Startup code&lt;/strong&gt;: Used to initialize the stack, jump to Rust code, similar to &lt;code&gt;start.S&lt;/code&gt; in C embedded development&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;From the relevant code reading, we know that the code in the TF card is loaded to &lt;strong&gt;0x8030_0000 ~ 0x8040_0000&lt;/strong&gt;. To avoid additional uncertainties, we can directly use the linker script from U-Boot to ensure the symbols defined in Rust code are properly loaded.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MEMORY { .spl_mem : ORIGIN = 0x80300000, LENGTH = 0x80000 }
MEMORY { .bss_mem : ORIGIN = 0x80380000, LENGTH = 0x20000 }

OUTPUT_ARCH("riscv")

ENTRY(_start)
PROVIDE(__stack_start__ = ORIGIN(.bss_mem) + LENGTH(.bss_mem));

/* Omitted specific section definitions */
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Due to the lack of first-hand chip development materials, we do not know exactly what the initialized state is after the Boot ROM; at this time, we can only rely on speculation and experimentation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Verifying Bare-Metal Execution - UART
&lt;/h3&gt;

&lt;p&gt;For bare-metal programming, we need to initialize the device's initial state, including the stack pointer &lt;strong&gt;sp&lt;/strong&gt;, system execution mode, interrupt table, enabling interrupts, etc. These tasks are usually completed by &lt;code&gt;start.S&lt;/code&gt; or &lt;code&gt;crt0.c&lt;/code&gt;. Minimal initialization code often only needs to set the stack pointer &lt;strong&gt;sp&lt;/strong&gt; to ensure that functions can be called. If &lt;strong&gt;sp&lt;/strong&gt; is invalid, using the stack (e.g., function calls) will lead to memory access violations or illegal instruction exceptions, i.e., "running wild."&lt;/p&gt;

&lt;p&gt;Without a JTAG debugging environment (the chip supports it, but I didn't use CK-LINK), how do we determine whether our code is being executed and whether it is executing correctly? Here, we can use &lt;strong&gt;UART0&lt;/strong&gt; to output debugging information. Since the Boot ROM has already initialized UART0, we can use it directly.&lt;/p&gt;

&lt;p&gt;From the Device Tree &lt;code&gt;.dtsi&lt;/code&gt; files in the U-Boot source code, we can see that the K230 uses a lot of DesignWare IP peripherals, such as &lt;strong&gt;UART0&lt;/strong&gt;, &lt;strong&gt;SPI&lt;/strong&gt;, &lt;strong&gt;I2C&lt;/strong&gt;, etc. The specific register manuals for these peripherals can be obtained online. The UART peripheral is compatible with the &lt;strong&gt;16550&lt;/strong&gt;, which is the serial port chip we're familiar with on PCs. The register address for UART0 is &lt;strong&gt;0x9140_0000&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;We can use &lt;code&gt;global_asm!&lt;/code&gt; to output characters to verify whether the firmware code is being executed. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#![no_std]&lt;/span&gt;
&lt;span class="nd"&gt;#![no_main]&lt;/span&gt;

&lt;span class="nd"&gt;global_asm!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;r#"
.section .text.start
.global _start
     la sp, __stack_start__
     call _start_rust
"#&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nd"&gt;#[no_mangle]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;extern&lt;/span&gt; &lt;span class="s"&gt;"C"&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;_start_rust&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;loop&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// UART0.THR = 'A'&lt;/span&gt;
        &lt;span class="nn"&gt;core&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;ptr&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;write_volatile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0x9140_0000&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="nb"&gt;u32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0x41&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;100000000&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;unsafe&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nd"&gt;asm!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"nop"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After compiling and flashing the above code, you should see a series of &lt;strong&gt;A&lt;/strong&gt; characters in the serial terminal. This indicates that our code has been successfully executed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Accessing Peripheral Registers - PAC
&lt;/h3&gt;

&lt;p&gt;In Rust embedded development, accessing peripheral registers is often done through &lt;strong&gt;PAC (Peripheral Access Crate)&lt;/strong&gt;, such as the &lt;code&gt;stm32xxxx-pac&lt;/code&gt; crate. However, since the K230 is a relatively new chip, there is no relevant PAC crate available. The official sources are also unlikely to provide an &lt;strong&gt;SVD&lt;/strong&gt; file for reference. Therefore, I chose to use the &lt;strong&gt;&lt;a href="https://github.com/embassy-rs/chiptool" rel="noopener noreferrer"&gt;chiptool&lt;/a&gt;&lt;/strong&gt; method and employed the &lt;strong&gt;&lt;a href="https://github.com/embedded-drivers/yaml2pac" rel="noopener noreferrer"&gt;yaml2pac&lt;/a&gt;&lt;/strong&gt; tool to generate the PAC crate by manually maintaining YAML definitions of the peripheral registers. Regarding PAC access, please refer to my article &lt;a href="https://andelf.github.io/2024/08/23/embedded-rust-peripheral-register-access-svdtools-chiptool-and-metapac-approach/" rel="noopener noreferrer"&gt;Peripheral Register Access in Rust Embedded Development: From svd2rust to chiptool and metapac - Using hpm-data as an Example&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The relevant YAML files can be conveniently created with the assistance of LLM (Large Language Models) by extracting OCR from PDF manuals.&lt;/p&gt;

&lt;p&gt;Using the &lt;strong&gt;&lt;a href="https://github.com/embedded-drivers/yaml2pac" rel="noopener noreferrer"&gt;yaml2pac&lt;/a&gt;&lt;/strong&gt; tool, we can easily generate our own PAC library:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yaml2pac &lt;span class="nt"&gt;-i&lt;/span&gt; registers/uart_dw.yaml &lt;span class="nt"&gt;-o&lt;/span&gt; pac/src/uart_dw.rs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, add specific peripheral address definitions in &lt;code&gt;lib.rs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[path&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"uart_dw.rs"&lt;/span&gt;&lt;span class="nd"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;mod&lt;/span&gt; &lt;span class="n"&gt;uart&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;UART0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;uart&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Uart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;unsafe&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nn"&gt;uart&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;Uart&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from_ptr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0x9140_0000&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;UART1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;uart&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Uart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;unsafe&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nn"&gt;uart&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;Uart&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from_ptr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0x9140_1000&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;UART2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;uart&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Uart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;unsafe&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nn"&gt;uart&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;Uart&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from_ptr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0x9140_2000&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;UART3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;uart&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Uart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;unsafe&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nn"&gt;uart&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;Uart&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from_ptr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0x9140_3000&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With simple encapsulation, we can conveniently access peripherals. Creating and maintaining a PAC in the absence of documentation is relatively difficult, but once completed, it can greatly improve development efficiency.&lt;/p&gt;

&lt;h3&gt;
  
  
  Facilitating Debugging - &lt;code&gt;println!&lt;/code&gt; Macro
&lt;/h3&gt;

&lt;p&gt;With the peripheral register definitions, we can now write a complete UART HAL driver or achieve a &lt;code&gt;println!&lt;/code&gt; macro through simple register access.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[derive(Debug)]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;impl&lt;/span&gt; &lt;span class="nn"&gt;core&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Write&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;Console&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;write_str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;core&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;Result&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;pac&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;UART0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="nf"&gt;.as_bytes&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;unsafe&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;UART0&lt;/span&gt;&lt;span class="nf"&gt;.lsr&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.thre&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="nd"&gt;asm!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"nop"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;

                &lt;span class="n"&gt;UART0&lt;/span&gt;&lt;span class="nf"&gt;.thr&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.write&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="nf"&gt;.set_thr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(())&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;#[macro_export]&lt;/span&gt;
&lt;span class="nd"&gt;macro_rules!&lt;/span&gt; &lt;span class="n"&gt;println&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$arg:tt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;core&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nd"&gt;writeln!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="nv"&gt;$crate&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$arg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.unwrap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;core&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nd"&gt;writeln!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="nv"&gt;$crate&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.unwrap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the &lt;code&gt;println!&lt;/code&gt; macro, we can conveniently output debugging information, significantly improving development efficiency.&lt;/p&gt;

&lt;h2&gt;
  
  
  Complete Initialization Code
&lt;/h2&gt;

&lt;p&gt;So far, we've only initialized the stack; other essential elements such as the system interrupts and even the &lt;code&gt;.bss&lt;/code&gt; section have not been initialized. In a complete embedded program, these are necessary.&lt;/p&gt;

&lt;p&gt;Unlike MCU programming, the code execution of an MPU is loaded into a certain area of memory by the Boot ROM, so the &lt;code&gt;.data&lt;/code&gt; section copy commonly seen in the &lt;code&gt;start.S&lt;/code&gt; of an MCU is not needed. Clearing the &lt;code&gt;.bss&lt;/code&gt; section depends on the situation; since it's relatively simple, we will skip the memory initialization part in this section.&lt;/p&gt;

&lt;h3&gt;
  
  
  Interrupt Handler
&lt;/h3&gt;

&lt;p&gt;For RISC-V, the interrupt handler is a special function. Rust provides the &lt;code&gt;"riscv-interrupt-m"&lt;/code&gt; ABI specifically for the special logic of interrupt handlers. Specifically, it adds stack frame preservation and restoration for interrupt handlers and uses the &lt;code&gt;mret&lt;/code&gt; instruction instead of &lt;code&gt;ret&lt;/code&gt; to return.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[link_section&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;".trap"&lt;/span&gt;&lt;span class="nd"&gt;]&lt;/span&gt;
&lt;span class="nd"&gt;#[no_mangle]&lt;/span&gt;
&lt;span class="k"&gt;unsafe&lt;/span&gt; &lt;span class="k"&gt;extern&lt;/span&gt; &lt;span class="s"&gt;"riscv-interrupt-m"&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;_start_trap_rust&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"trap!"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;mcause&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;riscv&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;register&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;mcause&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"mstatus: {:016x}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;riscv&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;register&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;mstatus&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.bits&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"mcause:  {:016x}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;riscv&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;register&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;mcause&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.bits&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"mtval:   {:016x}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;riscv&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;register&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;mtval&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"mepc:    {:016x}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;riscv&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;register&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;mepc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

    &lt;span class="k"&gt;loop&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, we print some important interrupt information to help determine whether the interrupt function is being called correctly.&lt;/p&gt;

&lt;p&gt;Using &lt;code&gt;#[no_mangle]&lt;/code&gt; is to expose the symbol so that we can set the interrupt handler entry address in assembly code.&lt;/p&gt;

&lt;p&gt;Using &lt;code&gt;#[link_section = ".trap"]&lt;/code&gt; is to place this function in the &lt;code&gt;.trap&lt;/code&gt; section for handling in the linker script, especially memory alignment (&lt;code&gt;ALIGN(8)&lt;/code&gt;). This is a common error when writing bare-metal code because the address of the &lt;code&gt;mtvec&lt;/code&gt; register must be aligned (the lower 2 bits are occupied by the vector mode bits); otherwise, it will cause an exception.&lt;/p&gt;

&lt;p&gt;For now, we don't need to handle interrupts; we just need to observe if interrupts are being triggered and whether the interrupt handler is being executed. So we use &lt;code&gt;loop {}&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Interrupt Initialization
&lt;/h3&gt;

&lt;p&gt;For RISC-V, initializing interrupts generally involves the following steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Set &lt;strong&gt;mtvec&lt;/strong&gt;: Interrupt handler entry address&lt;/li&gt;
&lt;li&gt;Set the &lt;strong&gt;MIE&lt;/strong&gt; bit in &lt;strong&gt;mstatus&lt;/strong&gt;: Enable interrupts&lt;/li&gt;
&lt;li&gt;Set the &lt;strong&gt;MEIE&lt;/strong&gt; bit in &lt;strong&gt;mie&lt;/strong&gt;: Enable external interrupts, timer interrupts, etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The K230 uses a Xuantie C908 core, supporting &lt;strong&gt;CLINT&lt;/strong&gt; and &lt;strong&gt;PLIC&lt;/strong&gt; interrupt controllers. Relevant information can be obtained from the C908 manual.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;global_asm!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"
    .option push
    .option norelax
    la gp, __global_pointer$
    .option pop

    la t1, __stack_start__
    addi sp, t1, -16

    // Initialize interrupts
    la t0, _start_trap_rust
    csrw mtvec, t0

    call _early_init

    // Continue to call _start_rust
    call _start_rust
"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nd"&gt;#[no_mangle]&lt;/span&gt;
&lt;span class="k"&gt;unsafe&lt;/span&gt; &lt;span class="k"&gt;extern&lt;/span&gt; &lt;span class="s"&gt;"C"&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;_early_init&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;riscv&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;register&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nn"&gt;mstatus&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;set_mie&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Enable global interrupts&lt;/span&gt;
    &lt;span class="nn"&gt;mstatus&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;set_sie&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Enable supervisor interrupts&lt;/span&gt;
    &lt;span class="nn"&gt;mie&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;set_mext&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;    &lt;span class="c1"&gt;// Enable external interrupts&lt;/span&gt;
    &lt;span class="nn"&gt;mie&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;set_msoft&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;   &lt;span class="c1"&gt;// Enable software interrupts&lt;/span&gt;
    &lt;span class="nn"&gt;mie&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;set_mtimer&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;  &lt;span class="c1"&gt;// Enable timer interrupts&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;strong&gt;MIE&lt;/strong&gt; bit in the &lt;code&gt;mstatus&lt;/code&gt; register is used to control interrupt enabling, and the &lt;strong&gt;MEXT&lt;/strong&gt; bit in the &lt;code&gt;mie&lt;/code&gt; register is used to control external interrupt enabling, i.e., &lt;strong&gt;PLIC&lt;/strong&gt;, for handling peripheral interrupts.&lt;/p&gt;

&lt;p&gt;Here, we also initialize &lt;strong&gt;gp&lt;/strong&gt;, which is a global pointer register used for accessing global variables in Rust (defined at a special location in the linker script). Of course, when using small and concentrated memory regions, you may not see instructions using the &lt;strong&gt;gp&lt;/strong&gt; register.&lt;/p&gt;

&lt;h3&gt;
  
  
  Other CSR Initialization
&lt;/h3&gt;

&lt;p&gt;Depending on the platform, other hardware may need initialization, such as disabling &lt;strong&gt;PMP&lt;/strong&gt;, initializing the &lt;strong&gt;FPU&lt;/strong&gt;, enabling &lt;strong&gt;mcycle&lt;/strong&gt; and &lt;strong&gt;mtime&lt;/strong&gt; counters, etc.&lt;/p&gt;

&lt;p&gt;Initializing the FPU is necessary; otherwise, any floating-point instruction will cause an exception. Rust's &lt;code&gt;"riscv-interrupt-m"&lt;/code&gt; implementation isn't intelligent enough to determine FPU usage, so when the target includes &lt;code&gt;+f&lt;/code&gt;/&lt;code&gt;+d&lt;/code&gt;, the ABI will default to using FPU push/pop instructions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Omitted platform-specific register initialization&lt;/span&gt;
&lt;span class="c1"&gt;// Including disabling PMP&lt;/span&gt;
&lt;span class="nd"&gt;asm!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"
    li    t0, 0x00001800
    csrw  mstatus, t0
    "&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nn"&gt;mcounteren&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;set_cy&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Enable cycle counter&lt;/span&gt;
&lt;span class="nn"&gt;mcounteren&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;set_tm&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Enable time counter&lt;/span&gt;

&lt;span class="c1"&gt;// FPU initialization&lt;/span&gt;
&lt;span class="nn"&gt;mstatus&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;set_fs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;mstatus&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;FS&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Initial&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nd"&gt;asm!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"csrwi fcsr, 0"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In addition to interrupt enabling, &lt;strong&gt;mstatus&lt;/strong&gt; is also responsible for the current CPU operating mode, such as &lt;strong&gt;M/S/U&lt;/strong&gt; mode.&lt;/p&gt;

&lt;p&gt;With the system's &lt;strong&gt;mcycle&lt;/strong&gt; CSR, we can conveniently use the &lt;code&gt;Delay&lt;/code&gt; trait in the &lt;code&gt;embedded-hal&lt;/code&gt; ecosystem to achieve more precise delays, moving away from using &lt;code&gt;nop&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;CPU0_CORE_CLK&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u32&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;800_000_000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;delay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;riscv&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;McycleDelay&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CPU0_CORE_CLK&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;delay&lt;/span&gt;&lt;span class="nf"&gt;.delay_ms&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Verifying Interrupt Handling
&lt;/h3&gt;

&lt;p&gt;We can verify whether the interrupt handler is being executed by directly triggering a software interrupt. The &lt;strong&gt;CLINT&lt;/strong&gt; interrupt controller of the K230 can trigger a software interrupt through the &lt;code&gt;msip&lt;/code&gt; register.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nn"&gt;pac&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;CLINT&lt;/span&gt;&lt;span class="nf"&gt;.msip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.write&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="nf"&gt;.set_msip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// Trigger software interrupt&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Modify the interrupt handler &lt;code&gt;_start_trap_rust&lt;/code&gt; to add a return:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;mcause&lt;/span&gt;&lt;span class="nf"&gt;.is_interrupt&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;mcause&lt;/span&gt;&lt;span class="nf"&gt;.code&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nn"&gt;riscv&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;interrupt&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;Interrupt&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;MachineSoft&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Machine Software Interrupt"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nn"&gt;pac&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;CLINT&lt;/span&gt;&lt;span class="nf"&gt;.msip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.write&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="nf"&gt;.set_msip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// Clear software interrupt&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using the &lt;code&gt;mtime&lt;/code&gt; and &lt;code&gt;mtimecmp&lt;/code&gt; CSRs can also verify timer interrupts. However, I found a pitfall: reading the &lt;code&gt;mtime&lt;/code&gt; of K230's CLINT via a 64-bit load instruction yields random content without any exception. This means that the 64-bit &lt;code&gt;mtime&lt;/code&gt; must be read twice in 32-bit segments and then combined into 64 bits. Only the &lt;code&gt;rdtime&lt;/code&gt; instruction can read the 64-bit &lt;code&gt;mtime&lt;/code&gt; at once.&lt;/p&gt;

&lt;h3&gt;
  
  
  DDR Initialization
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;DDR initialization&lt;/strong&gt; (SDRAM initialization) is a relatively complex process, generally requiring clock initialization, reset controller configuration, PHY training, chip initialization, timing configuration, self-check, etc. These contents are often provided directly by the manufacturer, and the register writing flow in the corresponding DDR initialization code is also like cryptic scripts.&lt;/p&gt;

&lt;p&gt;Therefore, the DDR initialization code is directly translated from C using LLM (Large Language Models) without additional explanations. The DDR initialization code varies among different DDR chips.&lt;/p&gt;

&lt;p&gt;After DDR initialization, we can use the DDR memory region. A pitfall here is that the starting address of DDR memory is &lt;strong&gt;0x0000_0000&lt;/strong&gt;. However, Rust has many restrictions on accessing the zero address, and most functions will directly panic. Programs should avoid using the &lt;strong&gt;0x0000_0000&lt;/strong&gt; address.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start Real Bare-Metal Programming
&lt;/h2&gt;

&lt;p&gt;With the above initialization foundation, we can finally start actual bare-metal programming—for example, initializing other peripherals, reading and writing peripheral registers, and even implementing some simple functions.&lt;/p&gt;

&lt;p&gt;Here, we demonstrate with two peripherals as examples. The relevant peripheral register definitions are already written in the &lt;strong&gt;&lt;a href="https://github.com/andelf/k230-bare-metal" rel="noopener noreferrer"&gt;k230-bare-metal&lt;/a&gt;&lt;/strong&gt; repository.&lt;/p&gt;

&lt;h3&gt;
  
  
  Blinking an LED Using GPIO
&lt;/h3&gt;

&lt;p&gt;For both MCUs and MPUs, the steps for blinking an LED using GPIO are similar:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Enable (or reset) the GPIO peripheral clock and power&lt;/li&gt;
&lt;li&gt;Set the pin function multiplexing and pin mode&lt;/li&gt;
&lt;li&gt;Perform GPIO write operations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the &lt;strong&gt;K230&lt;/strong&gt;, the peripheral clock and power signals are enabled by default (this can be confirmed by checking the relevant registers). Therefore, we only need to set the multiplexing function through &lt;strong&gt;IOMUX&lt;/strong&gt; and set the pin mode through the GPIO peripheral.&lt;/p&gt;

&lt;p&gt;The functionality can be referenced from official documentation, and the pin multiplexing documentation is located in &lt;code&gt;K230_PINOUT_V*.xlsx&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;IOMUX&lt;/strong&gt; peripheral is a PAD-like structure where each pin is set through a 32-bit register to configure multiplexing functions, pull-up/pull-down settings, input/output enable, etc. I obtained these definitions through &lt;code&gt;.dtsi&lt;/code&gt; files and C header files, also using LLM to translate them into YAML definitions. Calling &lt;code&gt;IOMUX.pad(n).set_sel(0)&lt;/code&gt; sets the pin's mode to the corresponding GPIO.&lt;/p&gt;

&lt;p&gt;The GPIO peripheral comes from &lt;a href="https://www.synopsys.com/dw/ipdir.php?c=DW_apb_gpio" rel="noopener noreferrer"&gt;DW_apb_gpio&lt;/a&gt;. For those familiar with Verilog or other HDL languages, this is a configurable GPIO IP core with up to 4 ports. There are several configuration registers that can obtain the initial parameters of the peripheral:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GPIO0 config_reg1: num_ports=1
GPIO0 config_reg2: len(PA)=32
GPIO1 config_reg1: num_ports=2
GPIO1 config_reg2: len(PA)=32 len(PB)=8
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A total of &lt;strong&gt;32 + 32 + 8 = 72&lt;/strong&gt; pins are divided into two GPIO controllers, where the GPIO1 controller has two ports. This can perfectly fit the cluster/array definition method in &lt;strong&gt;chiptool&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;blinky&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// RGB LED of Luchan Pi K230&lt;/span&gt;
    &lt;span class="c1"&gt;// - R: GPIO62&lt;/span&gt;
    &lt;span class="c1"&gt;// - G: GPIO20&lt;/span&gt;
    &lt;span class="c1"&gt;// - B: GPIO63&lt;/span&gt;
    &lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;pac&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="n"&gt;GPIO0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;GPIO1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IOMUX&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="n"&gt;IOMUX&lt;/span&gt;&lt;span class="nf"&gt;.pad&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.modify&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="nf"&gt;.set_sel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// function = GPIOx&lt;/span&gt;
    &lt;span class="n"&gt;IOMUX&lt;/span&gt;&lt;span class="nf"&gt;.pad&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;62&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.modify&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="nf"&gt;.set_sel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="n"&gt;IOMUX&lt;/span&gt;&lt;span class="nf"&gt;.pad&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;63&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.modify&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="nf"&gt;.set_sel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="n"&gt;GPIO0&lt;/span&gt;&lt;span class="nf"&gt;.swport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.ddr&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.modify&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// output mode&lt;/span&gt;
    &lt;span class="n"&gt;GPIO1&lt;/span&gt;&lt;span class="nf"&gt;.swport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.ddr&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.modify&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;GPIO1&lt;/span&gt;&lt;span class="nf"&gt;.swport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.ddr&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.modify&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;31&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;loop&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;GPIO0&lt;/span&gt;&lt;span class="nf"&gt;.swport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.dr&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.modify&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="o"&gt;^=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// toggle data&lt;/span&gt;
        &lt;span class="c1"&gt;// GPIO1.swport(0).dr().modify(|w| *w ^= 1 &amp;lt;&amp;lt; 30);&lt;/span&gt;
        &lt;span class="n"&gt;GPIO1&lt;/span&gt;&lt;span class="nf"&gt;.swport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.dr&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.modify&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="o"&gt;^=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;31&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nn"&gt;riscv&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;McycleDelay&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CPU0_CORE_CLK&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.delay_ms&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  PWM Buzzer
&lt;/h3&gt;

&lt;p&gt;The K230 has &lt;strong&gt;6 PWM outputs&lt;/strong&gt;, divided into two PWM controllers. Each controller internally has 3 PWM output channels. An additional channel 0 is responsible for configuring the reload.&lt;/p&gt;

&lt;p&gt;The buzzer on the Luchan Pi K230 development board is controlled via &lt;strong&gt;PWM1 GPIO43&lt;/strong&gt;. The input clock of the PWM peripheral is &lt;strong&gt;100MHz&lt;/strong&gt;, and the division factor is set via &lt;strong&gt;PWMCFG.SCALE&lt;/strong&gt; as (2^n).&lt;/p&gt;

&lt;p&gt;To make the buzzer reach a frequency audible to the human ear, the PWM frequency is generally set around 1KHz. The PWM frequency and duty cycle are set through &lt;strong&gt;PWMCFG.SCALE&lt;/strong&gt; and &lt;strong&gt;PWMx.CMP&lt;/strong&gt;. The relevant code is as follows; refer to the comments for register value calculations.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;buzzer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// GPIO43 - PWM1&lt;/span&gt;
    &lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;pac&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="n"&gt;IOMUX&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PWM0&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="c1"&gt;// PCLK, PWM uses the APB clock to program registers and generate waveforms. The default frequency is 100MHz.&lt;/span&gt;
    &lt;span class="n"&gt;IOMUX&lt;/span&gt;&lt;span class="nf"&gt;.pad&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;43&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.modify&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="nf"&gt;.set_sel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// PWM function = 2&lt;/span&gt;
        &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="nf"&gt;.set_oe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="nf"&gt;.set_ds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// Calculations:&lt;/span&gt;
    &lt;span class="c1"&gt;// scale = 2&lt;/span&gt;
    &lt;span class="c1"&gt;// period = 0x5000&lt;/span&gt;
    &lt;span class="c1"&gt;// freq = 100,000,000 / (1 &amp;lt;&amp;lt; 2) / 0x5000 = 1,220.7 Hz&lt;/span&gt;
    &lt;span class="c1"&gt;// duty = period / 2 = 0x2800&lt;/span&gt;
    &lt;span class="n"&gt;PWM0&lt;/span&gt;&lt;span class="nf"&gt;.pwmcfg&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.modify&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="nf"&gt;.set_zerocomp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="nf"&gt;.set_scale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="n"&gt;PWM0&lt;/span&gt;&lt;span class="nf"&gt;.pwmcmp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.write&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="na"&gt;.0&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0x5000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// PWMCMP0: RELOAD&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;duty&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0x2800&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="n"&gt;PWM0&lt;/span&gt;&lt;span class="nf"&gt;.pwmcmp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.modify&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="na"&gt;.0&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;duty&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// PWMCMP2: PWM1&lt;/span&gt;

    &lt;span class="c1"&gt;// Enable PWM&lt;/span&gt;
    &lt;span class="n"&gt;PWM0&lt;/span&gt;&lt;span class="nf"&gt;.pwmcfg&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.modify&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="nf"&gt;.set_enalways&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="nn"&gt;riscv&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;McycleDelay&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CPU0_CORE_CLK&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.delay_ms&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Disable PWM&lt;/span&gt;
    &lt;span class="n"&gt;PWM0&lt;/span&gt;&lt;span class="nf"&gt;.pwmcfg&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.modify&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="nf"&gt;.set_enalways&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="nn"&gt;riscv&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;McycleDelay&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CPU0_CORE_CLK&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.delay_ms&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Some Extended Thoughts
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Why Bare-Metal?
&lt;/h3&gt;

&lt;p&gt;Bare-metal programming is the foundation of embedded development and is also the lowest level of development. Through bare-metal programming, we can better understand the working principles of hardware and the underlying aspects of operating systems.&lt;/p&gt;

&lt;p&gt;Using all the libraries and SDKs out there is not as good as writing one ourselves; once you understand one, you understand many.&lt;/p&gt;

&lt;h3&gt;
  
  
  Shell?
&lt;/h3&gt;

&lt;p&gt;In a bare-metal environment, since there is no operating system, no standard input/output, and no file system, a full-fledged &lt;strong&gt;Shell&lt;/strong&gt; is impossible. However, we can implement simple command-line interaction via the serial port. All we need are two serial port functions: &lt;code&gt;putchar&lt;/code&gt; and &lt;code&gt;getchar&lt;/code&gt;, and a simple parser.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://docs.rs/noline/latest/noline/" rel="noopener noreferrer"&gt;noline&lt;/a&gt;&lt;/strong&gt; is a small no-std line-editing crate that can be used to implement simple command-line interactions. Moreover, it's based on the &lt;code&gt;embedded-hal&lt;/code&gt; ecosystem, making it easy to port. It supports line history and common shortcuts. Of course, writing a readline from scratch is also a good exercise.&lt;/p&gt;

&lt;p&gt;By implementing several shell commands, we can achieve simple interactions such as reading and writing peripheral registers, reading and writing memory, printing system information, etc.&lt;/p&gt;

&lt;p&gt;The relevant implementation can be found in the &lt;strong&gt;&lt;a href="https://github.com/andelf/k230-bare-metal" rel="noopener noreferrer"&gt;k230-bare-metal&lt;/a&gt;&lt;/strong&gt; repository. The final effect is as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;K230&amp;gt; &lt;span class="nb"&gt;help
&lt;/span&gt;Available commands:
  &lt;span class="nb"&gt;help&lt;/span&gt; - print this &lt;span class="nb"&gt;help
  echo&lt;/span&gt; &amp;lt;text&amp;gt; - print &amp;lt;text&amp;gt;
  reboot - reboot the system
  mem_read &amp;lt;address&amp;gt; &amp;lt;length&amp;gt; - &lt;span class="nb"&gt;read &lt;/span&gt;memory
  mem_write &amp;lt;address&amp;gt; &amp;lt;u32&amp;gt; - write memory
  tsensor - &lt;span class="nb"&gt;read &lt;/span&gt;temperature sensor
  cpuid - print CPUID
  serialboot - enter serial boot mode
  jump &amp;lt;address&amp;gt; - jump to address
  jumpbig &amp;lt;address&amp;gt; - jump to big core and run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Download?
&lt;/h3&gt;

&lt;p&gt;The &lt;strong&gt;K230&lt;/strong&gt;, in essence, is more like an SBC (Single Board Computer). Flashing firmware often involves using a TF card, which is extremely inconvenient in bare-metal development. Continuous plugging and unplugging of the TF card can cause poor contact or even damage.&lt;/p&gt;

&lt;p&gt;Referring to how &lt;strong&gt;LiteX&lt;/strong&gt; provides a very convenient kernel/firmware loading method for the FPGA soft core environment—downloading firmware via serial port to a specific memory location (DDR), or even downloading firmware via network—I attempted to port the &lt;strong&gt;litex_term&lt;/strong&gt;'s UART download logic. It comes with a serial port download protocol and serial command line. After detecting a special string, it automatically switches to download mode, downloads the firmware to a specified memory location via the serial port, and jumps to execute it.&lt;/p&gt;

&lt;p&gt;The final effect is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;litex_term /dev/tty.usbmodem56C40035621 &lt;span class="nt"&gt;--kernel-adr&lt;/span&gt; 0x01000000 &lt;span class="nt"&gt;--kernel&lt;/span&gt; ../firmware.img
&lt;span class="c"&gt;......
&lt;/span&gt;&lt;span class="go"&gt;Press Q or ESC to abort boot completely.
sL5DdSMmkekro
[LITEX-TERM] Received firmware download request from the device.
[LITEX-TERM] Uploading ../firmware.img to 0x01000000 (17400 bytes)...
[LITEX-TERM] Upload calibration... failed, switching to --safe mode.
[LITEX-TERM] Upload complete (8.7KB/s).
[LITEX-TERM] Booting the device.
[LITEX-TERM] Done.
Jumping to 0x01000000...
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's very convenient; I might introduce it separately in the future. Note that when writing firmware to the memory area, you need to handle the states of the &lt;strong&gt;I-Cache&lt;/strong&gt; and &lt;strong&gt;D-Cache&lt;/strong&gt;. When writing this article, I chose to completely disable the I-Cache and D-Cache.&lt;/p&gt;

&lt;h3&gt;
  
  
  Jumping to the Big Core
&lt;/h3&gt;

&lt;p&gt;As mentioned earlier, the startup of &lt;strong&gt;CPU1 (big core)&lt;/strong&gt; is controlled by &lt;strong&gt;CPU0 (small core)&lt;/strong&gt;. The specific startup logic is straightforward: set the reset vector and reset &lt;strong&gt;CPU1&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;unsafe&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nn"&gt;ptr&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;write_volatile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0x91102104&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="nb"&gt;u32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;jump_addr&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;u32&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nn"&gt;ptr&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;write_volatile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0x9110100c&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="nb"&gt;u32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0x10001000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nn"&gt;ptr&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;write_volatile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0x9110100c&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="nb"&gt;u32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0x10001&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nn"&gt;ptr&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;write_volatile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0x9110100c&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="nb"&gt;u32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0x10000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To facilitate development and testing, I also made jumping to the big core a &lt;strong&gt;Shell&lt;/strong&gt; command. By inputting &lt;code&gt;jumpbig 0x01000000&lt;/code&gt; via &lt;strong&gt;UART0&lt;/strong&gt;, you can make the big core execute code in the memory region. Attempting to dump the big core's register information, we can see the startup information:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Rust 2nd stage on CPU1
mstatus: 0000000a00001900
mie: 0000000000000000
mip: 0000000000000000
misa: 8000000000b4112f
  RV64ABCDFIMSUVX
mvendorid: 5b7
marchid: 8000000009140d00
mhartid: 0
cpuid: 09140b0d 10050000 260c0001
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, the &lt;code&gt;V&lt;/code&gt; in &lt;code&gt;RV64ABCDFIMSUVX&lt;/code&gt; indicates support for the &lt;strong&gt;RVV&lt;/strong&gt; vector instruction set. The K230 is a heterogeneous dual-core; the small core does not support RVV. This proves that our code has successfully jumped to the big core.&lt;/p&gt;

&lt;p&gt;An interesting point is that &lt;code&gt;mhartid&lt;/code&gt; is &lt;strong&gt;0&lt;/strong&gt;, indicating that the K230 does not comply with the RISC-V specification of assigning different IDs to different harts. This needs attention in actual development. You can only distinguish different harts through miscellaneous CSRs—this is a small pitfall of the K230.&lt;/p&gt;

&lt;p&gt;Next, we can perform more complex operations on the big core, such as applying RVV vector instructions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Through this experiment of bare-metal embedded development using Rust on the &lt;strong&gt;K230 chip&lt;/strong&gt;, we deeply explored the differences in startup methods and usage modes between &lt;strong&gt;MPUs&lt;/strong&gt; and &lt;strong&gt;MCUs&lt;/strong&gt;, mastering the key steps of using Rust for bare-metal development on MPU chips, including startup process, firmware format parsing, interrupts, and peripheral initialization.&lt;/p&gt;

&lt;p&gt;In practice, we successfully achieved UART debug output, GPIO LED blinking, PWM buzzer control, and other functions, deepening our understanding of the K230's underlying startup mechanism and hardware features. These achievements lay a solid foundation for future, more complex embedded development on the K230 and other RISC-V chips.&lt;/p&gt;

&lt;p&gt;Looking ahead, we can further improve peripheral drivers, explore multi-core collaboration, apply RVV vector instructions, and leverage the Rust ecosystem to build efficient and secure embedded systems, contributing more to the &lt;strong&gt;RISC-V&lt;/strong&gt; open-source community.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tips
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The Boot ROM provides exception error messages for illegal execution; you can use this behavior to inversely verify whether the code is being executed—for example, insert illegal instructions to check the &lt;strong&gt;pc&lt;/strong&gt; of the error location.&lt;/li&gt;
&lt;li&gt;It's best to avoid using the full target features in bare-metal code to prevent the compiler from generating instruction features that have not been enabled, such as the &lt;strong&gt;V&lt;/strong&gt; extension.&lt;/li&gt;
&lt;li&gt;In Rust bare-metal development, since there is no operating system, you cannot use the standard library or the &lt;code&gt;panic!&lt;/code&gt; macro; therefore, you need to implement a panic handler yourself.&lt;/li&gt;
&lt;li&gt;The states of &lt;strong&gt;D-Cache&lt;/strong&gt; and &lt;strong&gt;I-Cache&lt;/strong&gt; need to be managed; generally, disable them before jumping to new code to avoid cache inconsistencies.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;println!&lt;/code&gt; macro can conveniently output debug information, but note that printing is blocking and may affect time-sensitive operations.&lt;/li&gt;
&lt;li&gt;Learn to use LLMs to assist your exploration process—for example, export YAML definitions from PDF manuals via OCR, translate DDR initialization code, and get explanations for specific registers.&lt;/li&gt;
&lt;li&gt;The Boot ROM initializes some peripherals like &lt;strong&gt;UART0&lt;/strong&gt;, but the specific states still need to be verified again, such as FIFO mode, baud rate, etc.&lt;/li&gt;
&lt;li&gt;For possible hardware implementation bugs or peculiarities, you can try using equivalent alternative methods.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>tutorial</category>
      <category>riscv</category>
      <category>mcu</category>
      <category>rust</category>
    </item>
  </channel>
</rss>
