STM32 BootLoader 刷新项目 (十) Flash擦除-命令0x56

news2024/12/24 0:10:06

STM32 BootLoader 刷新项目 (十) Flash擦除-命令0x56

1. STM32F407 BootLoader 中的 Flash 擦除功能详解

在嵌入式系统中,BootLoader 的设计是非常关键的部分,它负责引导主程序的启动、升级以及安全管理。而在 STM32F407 等 MCU 上实现 BootLoader,Flash 操作则是其中的核心功能之一。本文将重点介绍 STM32F407 上的 Flash 擦除功能,并给出实现过程和注意事项。

image-20241114070905643

一、STM32F407 Flash 结构概述

STM32F407 芯片采用 Cortex-M4 内核,内置 Flash 存储用于程序和数据存储。STM32F407 的 Flash 大小为 512KB,分为若干个扇区(Sector),每个扇区的大小并不相同:

  • 扇区 0 到 3:16KB 每扇区
  • 扇区 4:64KB
  • 扇区 5 到 11:128KB 每扇区

image-20241114070535836

这种不同大小的扇区设计适合不同的应用需求,比如小容量扇区用于存储配置数据,大容量扇区用于存储固件。

二、Flash 擦除的原理

Flash 擦除是将 Flash 中的某个扇区的数据重置为 0xFF。由于 Flash 的物理特性,写操作只能将位设置为 0,而擦除操作将位恢复为 1。因此,在更新 Flash 数据时,通常需要先擦除再写入。

在 STM32F407 中,Flash 擦除只能以扇区为单位进行,这意味着无法擦除扇区中的部分数据。每次擦除扇区时,整个扇区的数据都会被清空。

三、Flash 擦除的操作流程

在 STM32F407 的 BootLoader 中实现 Flash 擦除,一般需要遵循以下步骤:

  1. 解锁 Flash 控制寄存器:在进行任何 Flash 操作之前,需要解锁 Flash 的写保护功能。
  2. 等待 Flash 空闲:检查 Flash 状态寄存器,确保没有其他操作正在进行。
  3. 启动扇区擦除:设置目标扇区并启动擦除命令。
  4. 等待擦除完成:监控状态寄存器中的 BSY 位,等待擦除操作完成。
  5. 锁定 Flash 控制寄存器:完成操作后,将 Flash 锁定,以防止误操作。

四、Flash 擦除实现代码

以下是 STM32F407 上实现 Flash 擦除的代码示例。本文假设使用了 STM32 标准外设库,便于调用硬件寄存器。

#include "stm32f4xx.h"

/**
 * @brief  擦除 Flash 指定扇区
 * @param  sector 要擦除的扇区编号
 * @retval 0 表示成功,-1 表示失败
 */
int Flash_EraseSector(uint8_t sector)
{
    // 解锁 Flash 控制寄存器
    FLASH_Unlock();

    // 等待 Flash 空闲
    while (FLASH_GetStatus() == FLASH_BUSY);

    // 擦除操作
    FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_OPERR | FLASH_FLAG_WRPERR | FLASH_FLAG_PGAERR | FLASH_FLAG_PGPERR | FLASH_FLAG_PGSERR);

    FLASH_Status status = FLASH_EraseSector(sector, VoltageRange_3);

    // 等待擦除完成
    if (status != FLASH_COMPLETE)
    {
        FLASH_Lock();
        return -1; // 擦除失败
    }

    // 锁定 Flash 控制寄存器
    FLASH_Lock();
    return 0; // 擦除成功
}

五、代码详解

  • FLASH_Unlock():此函数用于解锁 Flash 控制寄存器,以允许擦除和写入操作。
  • FLASH_GetStatus():用于检查 Flash 是否处于忙碌状态。擦除操作会占用一定的时间,必须等待 Flash 空闲后才能继续。
  • FLASH_ClearFlag():清除可能出现的错误标志位,确保擦除操作不会受到之前错误的影响。
  • FLASH_EraseSector():这是 STM32 库中的擦除函数,输入参数为扇区编号和电压范围(STM32F4 系列通常使用 VoltageRange_3)。
  • FLASH_Lock():操作完成后锁定 Flash 控制寄存器,以防止意外操作。

六、Flash 擦除的注意事项

  1. 擦除单位:在 STM32F407 中,Flash 擦除只能以扇区为单位进行,无法进行字节或页的擦除。因此,设计 BootLoader 时要合理分配数据的存储位置,避免不必要的擦除操作。
  2. 电压范围:擦除 Flash 时,电源电压必须在一定范围内,低电压下可能导致擦除失败甚至损坏 Flash。STM32F407 提供了多种电压模式,通常选择 VoltageRange_3 即可。
  3. 错误处理:Flash 擦除失败可能会出现各种错误,例如写保护、编程错误等。建议在擦除操作前后清除并检查错误标志。
  4. 数据备份:在擦除前备份必要的数据。在 BootLoader 中进行固件更新时,最好将旧固件暂时保存至其他存储介质中,以应对擦除或写入失败的情况。
  5. 上电时间:Flash 擦除时间较长,尤其是 128KB 扇区,因此在设计 BootLoader 时需要考虑电源的持续供电能力,避免中途掉电。

2. BootLoader Flash划分

由下图可以看出本BootLoader的Flash的划分,其中划给BootLoader为32KB,从0x0800 0000-0x0800 7FFF,占用Sector 0-1两个段。App应用程序占用0x0800 8000-0x080F FFFF,总共10个Sector。

image-20241114070819942

下面是BootLoader的跳转过程,关于具体的跳转过程,可以参考上一篇文章: STM32 BootLoader 刷新项目 (九) 跳转指定地址-命令0x55

image-20241114230528904

image-20241114230647450

3. 0x56命令介绍–Flash擦除

在本篇文章,我们的主要是介绍0x56的命令,这个命令主要是在BootLoader中擦除指定Flash Sector的命令。

通过上位机发送8 Byte的数据,其中第1 Byte为整个数据的长度,第2Byte为指令码,第3 Byte为要擦除Flash的起始Sector序号,第4 Byte是擦除Sector的长度,第5-8 Byte为前6个Byte的CRC校验值。上位机通过串口UART发送给下位机,下位机回复地址是否擦除成功的标志。

image-20241114230143679

下面是发送命令过程中上位机与BootLoader之间的交互。

image-20241114230607647

4. Flash擦除命令程序设计

下面我们来进行程序设计,下面是读取上位机指令,并解析指令的过程,通过switch case判断执行哪种命令。目前通过上位机执行BL_FLASH_ERASE指令,然后执行bootloader_handle_flash_erase_cmd(bl_rx_buffer)函数。

下面是代码的详细注释和分析:

bootloader_uart_read_data 函数

该函数用于从主机接收命令数据并调用相应的处理函数来执行命令。它是 BootLoader 的核心输入接口,通过 UART 接收主机的命令,并根据命令类型分发到对应的处理函数。

void bootloader_uart_read_data(void)
{
    uint8_t rcv_len = 0;

    // 打印调试信息,提示接收到命令
    printmsg_Host("BL_DEBUG_MSG: Receive CMD\n\r");

    // 无限循环,持续接收命令并处理
    while (1)
    {
        // 将 LED2 熄灭,表示进入命令接收状态
        HAL_GPIO_WritePin(LED2_GPIO_Port, LED2_Pin, GPIO_PIN_RESET);

        // 清空接收缓冲区,防止残留数据干扰
        memset(bl_rx_buffer, 0, 200);

        // 读取命令包的第一个字节,即命令的长度字段
        HAL_UART_Receive(C_UART, bl_rx_buffer, 1, HAL_MAX_DELAY);

        // 获取命令包长度,长度字节位于第一个字节
        rcv_len = bl_rx_buffer[0];

        // 根据命令包长度读取剩余的字节
        HAL_UART_Receive(C_UART, &bl_rx_buffer[1], rcv_len, HAL_MAX_DELAY);

        // 根据命令码执行相应的命令处理函数
        switch (bl_rx_buffer[1])
        {
            case BL_GET_VER:
                // 处理获取版本命令
                bootloader_handle_getver_cmd(bl_rx_buffer);
                break;

            case BL_GET_HELP:
                // 处理获取帮助命令
                bootloader_handle_gethelp_cmd(bl_rx_buffer);
                break;

            case BL_GET_CID:
                // 处理获取芯片 ID 命令
                bootloader_handle_getcid_cmd(bl_rx_buffer);
                break;

            case BL_GET_RDP_STATUS:
                // 处理获取读保护状态命令
                bootloader_handle_getrdp_cmd(bl_rx_buffer);
                break;

            case BL_GO_TO_ADDR:
                // 处理跳转到地址命令
                bootloader_handle_go_cmd(bl_rx_buffer);
                break;

            case BL_FLASH_ERASE:
                // 处理 Flash 擦除命令
                bootloader_handle_flash_erase_cmd(bl_rx_buffer);
                break;

            default:
                // 未知命令,打印调试信息
                printmsg("BL_DEBUG_MSG: Invalid command code received from host \n");
                break;
        }
    }
}
  • 初始化和循环:函数会无限循环等待主机发送的命令并进行解析。
  • 数据接收:首先读取一个字节的长度字段,然后根据这个长度读取完整的命令数据。
  • 命令解析:通过 switch 语句解析命令码,并调用相应的处理函数。
  • 错误处理:如果命令码不在预期范围内,打印调试信息。

bootloader_handle_flash_erase_cmd 函数

该函数处理 BL_FLASH_ERASE 命令,通过接收到的参数执行 Flash 擦除操作。

void bootloader_handle_flash_erase_cmd(uint8_t *pBuffer)
{
    uint8_t erase_status = 0x00;
    printmsg("BL_DEBUG_MSG: bootloader_handle_flash_erase_cmd\n");

    // 获取命令包的总长度
    uint32_t command_packet_len = bl_rx_buffer[0] + 1;

    // 从命令包中提取主机发送的 CRC32 值
    uint32_t host_crc = *((uint32_t *)(bl_rx_buffer + command_packet_len - 4));

    // 验证接收到的数据包的 CRC 校验
    if (!bootloader_verify_crc(&bl_rx_buffer[0], command_packet_len - 4, host_crc))
    {
        // 如果校验成功
        printmsg("BL_DEBUG_MSG: checksum success !!\n");

        // 发送 ACK 响应,确认命令长度为 1 字节
        bootloader_send_ack(pBuffer[0], 1);

        // 打印擦除信息:初始扇区和扇区数量
        printmsg("BL_DEBUG_MSG: initial_sector: %d, no_of_sectors: %d\n", pBuffer[2], pBuffer[3]);

        // 熄灭 LED 表示擦除操作开始
        HAL_GPIO_WritePin(LED2_GPIO_Port, LED2_Pin, GPIO_PIN_RESET);

        // 调用 `execute_flash_erase` 执行擦除操作,传入初始扇区和扇区数量
        erase_status = execute_flash_erase(pBuffer[2], pBuffer[3]);

        // 重新点亮 LED 表示擦除操作完成
        HAL_GPIO_WritePin(LED2_GPIO_Port, LED2_Pin, GPIO_PIN_SET);

        // 打印擦除操作的状态
        printmsg("BL_DEBUG_MSG: flash erase status: %#x\n", erase_status);

        // 发送擦除状态回主机
        bootloader_uart_write_data(&erase_status, 1);
    }
    else
    {
        // 如果 CRC 校验失败,发送 NACK 响应
        printmsg("BL_DEBUG_MSG: checksum fail !!\n");
        bootloader_send_nack();
    }
}
  • 参数解析和 CRC 校验:从命令包中提取 CRC 值并进行校验,确保数据完整性。
  • 发送 ACK 或 NACK:根据 CRC 校验结果发送确认或拒绝响应。
  • 擦除操作:如果校验通过,提取擦除操作的初始扇区和扇区数量,然后调用 execute_flash_erase 函数执行擦除操作。
  • 状态回传:擦除完成后,将操作状态通过 UART 返回给主机。

execute_flash_erase 函数

execute_flash_erase 函数负责执行 Flash 的具体擦除操作,支持部分扇区擦除和全片擦除。

uint8_t execute_flash_erase(uint8_t sector_number, uint8_t number_of_sector)
{
    // STM32F407 芯片共有 12 个扇区(0 到 11)
    // 如果 sector_number = 0xff,表示全片擦除
    FLASH_EraseInitTypeDef flashErase_handle;
    uint32_t sectorError;
    HAL_StatusTypeDef status;

    // 如果指定的扇区数量超过有效范围,返回无效扇区
    if (number_of_sector > 11)
        return INVALID_SECTOR;

    // 检查扇区号是否有效
    if ((sector_number == 0xFF) || (sector_number <= 11))
    {
        // 判断是否全片擦除
        if (sector_number == (uint8_t)0xFF)
        {
            flashErase_handle.TypeErase = FLASH_TYPEERASE_MASSERASE;
        }
        else
        {
            // 部分扇区擦除
            // 计算剩余可擦除的扇区数
            uint8_t remaining_sector = 12 - sector_number;
            if (number_of_sector > remaining_sector)
            {
                number_of_sector = remaining_sector;
            }

            flashErase_handle.TypeErase = FLASH_TYPEERASE_SECTORS;
            flashErase_handle.Sector = sector_number;
            flashErase_handle.NbSectors = number_of_sector;
        }

        // 设置擦除银行(STM32F4 系列有两个 Flash Bank,选取 Bank 1)
        flashErase_handle.Banks = FLASH_BANK_1;

        // 解锁 Flash 寄存器,允许擦除操作
        HAL_FLASH_Unlock();
        
        // 设置电压范围,适应 STM32F4 的工作电压范围
        flashErase_handle.VoltageRange = FLASH_VOLTAGE_RANGE_3;
        
        // 执行擦除操作,传入擦除配置和错误记录变量
        status = (uint8_t)HAL_FLASHEx_Erase(&flashErase_handle, &sectorError);

        // 锁定 Flash 控制寄存器,防止误操作
        HAL_FLASH_Lock();

        return status;  // 返回擦除操作的状态
    }
    return INVALID_SECTOR;  // 如果无效扇区,返回无效状态
}
  • 全片和部分擦除:函数支持全片擦除(sector_number == 0xFF)和从指定扇区开始的部分擦除。
  • 擦除范围检查:确保扇区数量不超过剩余的可擦除范围。
  • 解锁和锁定:在擦除操作前解锁 Flash 控制寄存器,擦除完成后重新锁定。
  • 电压范围设置:设置为 FLASH_VOLTAGE_RANGE_3,适配 STM32F4 的工作电压。
  • 状态返回:返回擦除状态,以便调用方确认擦除是否成功。

总结

  • bootloader_uart_read_data:从 UART 接收命令,解析命令码并调用对应的命令处理函数。
  • bootloader_handle_flash_erase_cmd:处理 Flash 擦除命令,进行 CRC 校验

5. 实战演练

下面是上位机的命令菜单,通过在终端调用Python脚本,然后在终端输入下位机连接的串口号,即可进入命令界面,目前可支持如下命令:

image-20240713104433991

下面我们执行命令–7,执行BL_FLASH_ERASE,即为在BootLoader中执行Flash擦除命令0x56,第一步输入要开始擦除的Sector起始段,第二步输入擦除Sector的长度,最后BootLoader向上位机返回是否擦除成功的Status。

image-20241115071431811

下面我们来看Flash擦除命令是否擦除成功,用STM32CubeProgrammer工具,用ST-LINK连接Debug口,上一步中我们擦除的Sector 3的Flash,这里面我们输入Sector 2的起始地址0x0800 C000,可以看出全部擦除为FF。至此Flash擦除命令已经讲完。

image-20241115072819345

6. 结论

Flash 擦除是 STM32F407 BootLoader 设计中的重要环节,它为固件更新和数据存储提供了基础。本文详细介绍了 Flash 擦除的原理和实现方法,并提供了相关代码和注意事项。在实际应用中,设计人员可以根据需要将擦除功能整合至 BootLoader 的主流程中,确保系统的可靠性和可升级性。

希望本篇文章能为读者在 STM32F407 的 BootLoader 开发中提供一些帮助,若有疑问,欢迎交流讨论。

7. 系列文章

STM32 BootLoader 刷新项目 (一) STM32CubeMX UART串口通信工程搭建

STM32 BootLoader 刷新项目 (二) 方案介绍

STM32 BootLoader 刷新项目 (三) 程序框架搭建及刷新演示

STM32 BootLoader 刷新项目 (四) 通信协议

STM32 BootLoader 刷新项目 (五) 获取软件版本号-命令0x51

STM32 BootLoader 刷新项目 (六) 获取帮助-命令0x52

STM32 BootLoader 刷新项目 (七) 获取芯片ID-0x53

STM32 BootLoader 刷新项目 (八) 读取Flash保护ROP-0x54

STM32 BootLoader 刷新项目 (九) 跳转指定地址-命令0x55

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2241726.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Homework】【5】Learning resources for DQ Robotics in MATLAB

Lesson 5 代码-TwoDofPlanarRobot.m 表示一个 2 自由度平面机器人。该类包含构造函数、计算正向运动学模型的函数、计算平移雅可比矩阵的函数&#xff0c;以及在二维空间中绘制机器人的函数。 classdef TwoDofPlanarRobot%TwoDofPlanarRobot - 表示一个 2 自由度平面机器人类…

Uniapp 引入 Android aar 包 和 Android 离线打包

需求&#xff1a; 原生安卓 apk 要求嵌入到 uniapp 中&#xff0c;并通过 uniapp 前端调起 app 的相关组件。 下面手把手教你&#xff0c;从 apk 到 aar&#xff0c;以及打包冲突到如何运行&#xff0c;期间我所遇到的问题都会 一 一 进行说明&#xff0c;相关版本以我文章内为…

你可以通过以下步骤找到并打开 **Visual Studio 开发者命令提示符**:

你可以通过以下步骤找到并打开 Visual Studio 开发者命令提示符&#xff1a; 1. 通过开始菜单查找 打开 开始菜单&#xff08;点击屏幕左下角的 Windows 图标&#xff09;。在搜索框中输入 Developer Command Prompt。你应该看到以下几种选项&#xff08;具体取决于你的 Visu…

北京大学c++程序设计听课笔记101

基本概念 程序运行期间&#xff0c;每个函数都会占用一段连续的内存空间。而函数名就是该函数所占内存区域的起始地址&#xff08;也称“入口地址”&#xff09;。我们可以将函数的入口地址赋给一个指针变量&#xff0c;使该指针变量指向该函数。然后通过指针变量就可以调用这个…

(时序论文阅读)TimeMixer: Decomposable Multiscale Mixing for Time Series Forecasting

来源论文iclr2024 论文地址&#xff1a;https://arxiv.org/abs/2405.14616 源码地址&#xff1a; https://github.com/kwuking/TimeMixer 背景 数据是有连续性&#xff0c;周期性&#xff0c;趋势性的。我们这篇文章主要围绕的是用MLP结构来预测数据的周期性具体为&#xff…

Springboot 使用EasyExcel导出含图片并设置样式的Excel文件

Springboot 使用EasyExcel导出含图片并设置样式的Excel文件 Excel导出系列目录&#xff1a;★★★★尤其注意&#xff1a;引入依赖创建导出模板类逻辑处理controllerservice 导出效果总结 Excel导出系列目录&#xff1a; 【Springboot 使用EasyExcel导出Excel文件】 【Springb…

【论文分享】基于街景图像识别和深度学习的针对不同移动能力老年人的街道步行可达性研究——以南京成贤街社区为例

全球老龄化趋势加剧, 许多城市中老年人数量不断增加&#xff0c;而现有街道和社区基础设施往往未能满足其步行安全和便利需求。本次我们给大家带来一篇SCI论文的全文翻译&#xff0c;该论文通过探讨不同步行能力的老年人对城市步行环境的需求&#xff0c;提供了关于如何改善城市…

LM2 : A Simple Society of Language Models Solves Complex Reasoning

文章目录 题目摘要简介相关工作方法论实验结果结论局限性 题目 LM2&#xff1a;简单的语言模型社会解决复杂推理问题 论文地址&#xff1a;https://aclanthology.org/2024.emnlp-main.920/ 项目地址&#xff1a; https://github.com/LCS2-IIITD/Language_Model_Multiplex 摘要…

【因果分析方法】MATLAB计算Liang-Kleeman信息流

【因果分析方法】MATLAB计算Liang-Kleeman信息流 1 Liang-Kleeman信息流2 MATLAB代码2.1 函数代码2.2 案例参考Liang-Kleeman 信息流(Liang-Kleeman Information Flow)是由 Liang 和 Kleeman 提出的基于信息论的因果分析方法。该方法用于量化变量之间的因果关系,通过计算信息…

[含文档+PPT+源码等]精品基于springboot实现的原生Andriod手机使用管理软件

软件开发环境及开发工具&#xff1a; 数据库管理工具&#xff1a;phpstudy/Navicat或者phpstudy/sqlyog 开发工具&#xff1a;Android Studio 后台管理系统涉及技术&#xff1a; 后台使用框架&#xff1a;Springboot 前端使用技术&#xff1a;Vue,HTML5,CSS3、JavaScript等…

(三十三)队列(queue)

文章目录 1. 队列&#xff08;queue&#xff09;1.1 定义1.2 函数1.3 习题1.3.1 例题&#xff08;周末舞会&#xff09; 2. 双向队列&#xff08;deque&#xff09;2.1 定义2.2 函数2.3 题目2.3.1 例题&#xff08;打BOSS&#xff09; 1. 队列&#xff08;queue&#xff09; 队…

常用数据类型

1.数值类型 分为整型和浮点型 double(3,1)&#xff1b;长度是3&#xff0c;小数点后是1&#xff0c;比如99.5&#xff0c;10.0&#xff0c;20.8 这里的float和double与java中的类似&#xff0c;都是IEEE 754标准的浮点数&#xff0c;精度会丢失&#xff0c;存在一定误差&#…

Vue3 -- 集成sass【项目集成5】

集成sass&#xff1a; 看过博主的 配置styleLint工具应该已经安装过 sass sass-loader 了&#xff0c;所以我们只需要加上我们的 lang"scss"即可。 <style scoped lang"scss"></style>给项目添加全局样式文件&#xff1a; 在src文件夹下创建…

【云原生系列--Longhorn的部署】

Longhorn部署手册 1.部署longhorn longhorn架构图&#xff1a; 1.1部署环境要求 kubernetes版本要大于v1.21 每个节点都必须装open-iscsi &#xff0c;Longhorn依赖于 iscsiadm主机为 Kubernetes 提供持久卷。 apt-get install -y open-iscsiRWX 支持要求每个节点都安装 N…

Springboot集成ElasticSearch实现minio文件内容全文检索

一、docker安装Elasticsearch &#xff08;1&#xff09;springboot和Elasticsearch的版本对应关系如下&#xff0c;请看版本对应&#xff1a; 注意安装对应版本&#xff0c;否则可能会出现一些未知的错误。 &#xff08;2&#xff09;拉取镜像 docker pull elasticsearch:7…

Diff 算法的误判

起源&#xff1a; 设想一下&#xff0c;假如你桌面上的文件都没有文件名&#xff0c;取而代之的是&#xff0c;你使用通过文件的位置顺序即index来区分它们———第一个文件&#xff0c;第二个文件&#xff0c;以此类推。也许这种方式可行&#xff0c;可是一旦你删除了其中的一…

D69【 python 接口自动化学习】- python 基础之数据库

day69 Python 执行 SQL 语句 学习日期&#xff1a;20241115 学习目标&#xff1a; MySQL 数据库&#xfe63;- Python连接redis 学习笔记&#xff1a; redis数据库的用途 使用Python访问redis数据库 使用Python对redis数据库进行读写操作 总结 1. redis是一款高性能的键…

飞书文档只读限制复制

飞书文档只读限制复制 场景描述解决方式插件安装测试 场景描述 当使用飞书时&#xff0c;可能会存在无对方文档编辑/管理权限&#xff0c;对方只给自己开放只读权限的时候&#xff0c;此时如果文档较重要&#xff0c;需要本地保存一份&#xff0c;但是又无法复制文档或直接屏蔽…

[每周一更]-(第123期):模拟面试|消息队列面试思路解析

文章目录 22|消息队列:消息队列可以用来解决什么问题?1. 你用过消息队列吗?主要用来解决什么问题?异步、削峰和解耦你能各举一个例子吗?2. 你用的是哪个消息队列?为什么使用它而不用别的消息队列?3. 为什么你一定要用消息队列?不用行不行?不用有什么缺点?4. 在对接多…

npm list @types/node 命令用于列出当前项目中 @types/node 包及其依赖关系

文章目录 作用示例常用选项示例命令注意事项 1、实战举例**解决方法**1. **锁定唯一的 types/node 版本**2. **清理依赖并重新安装**3. **设置 tsconfig.json 的 types**4. **验证 Promise 类型支持** **总结** npm list types/node 命令用于列出当前项目中 types/node 包及其…