ARM-SWI 和未定义指令异常中断处理程序的返回(七)

news2024/11/17 17:39:17

文章目录

    • 处理流程
    • 示例
    • 代码实现
      • SWI
      • 未定义指令
    • 附录源码

处理流程

SWI 和未定义指令异常中断是由当前执行的指令自身产生的,当 SWI 和未定义指令异常中断产生时,程序计数器的 PC 的值还未更新,它指向当前指令后面第 2 条指令(对于 ARM 指令来说,它指向当前指令地址加 8 个字节的位置;对于 Thumb 指令来说,它指向当前指令地址加 4 个字节的位置)。
当 SWI 和未定义指令异常中断发生时,处理器将 PC-4 保存到异常模式下的 LR_mode 寄存器中。这时 LR_mode 中的值即为PC-4 ,即指向当前指令的第一条指令。因此返回操作直接将 LR_mode(指向当前指令的下一条指令) 的值赋给 PC 即可,可以通过下面的指令实现:

MOVS PC, LR   // 注意 S ,指定了 S 意味着同时将SPSR 拷贝到 CPSR

该指令将寄存器 LR 中的值,复制到程序计数器 PC 中,实现程序返回,同时将 SPSR_mode 寄存器内容复制到当前程序计数器 CPSR 中。
当异常中断处理程序中使用了数据栈时,可以通过下面的指令在异常中断处理程序时保存被中断程序的执行现场,在退出异常中断处理程序时恢复被中断程序的执行现场。异常中断处理程序中使用的数据栈由用户提供。

stmdb sp!, {r0-r12, lr}   // 保存现场 r0-r12 - reg_list
// user code
ldmia sp!, {r0-r12, pc}^  // 恢复现场 r0-r12 - reg_list

在上述指令中,reg_list 是异常中断处理程序中使用的寄存器列表。标识符 ^ 指示将 SPSR_mode 寄存器内容复制到 CPSR 寄存器中,该指令只能在特权模式下使用。

示例

在这里插入图片描述

  • 假设在 instruction+0 指令执行完成后发生 swi/undef 异常
  • 此时 PC 值 未更新还是指向 instruction+2
  • 进入 swi/undef 异常后,LR_mode = PC -4,所以此时 LR 指向 instruction+1
  • swi/undef 异常处理完成之后,程序需要返回到 instruction+1 处执行,所以将此时的 LR_mode 寄存器的值赋给 PC 即可

代码实现

SWI

异常处理程序

    .align 2
    .arm
    .weak SVC_Handler
    .type SVC_Handler, %function
SVC_Handler:
    /* 执行到这里之前:
    * 1. lr_svc 保存有被中断模式中的下一条即将执行的指令的地址
    * 2. SPSR_svc 保存有被中断模式的 CPSR
    * 3. CPSR 中的 M4-M0 被设置为 10011, 进入到 svc 模式
    * 4. 跳到 0x08 的地方执行程序
    */

    /* 保存现场 */
    /* 在 swi 异常处理函数中有可能会修改 r0-r12, 所以先保存 */
    /* lr 是异常处理完后的返回地址, 也要保存 */
    stmdb sp!, {r0-r12, lr}

.if 0
    mov r4, lr /* LR 由硬件自动保存软中断指令下一条指令的地址 */

    /* 处理swi异常 */
    mrs r0, cpsr
    ldr r1, =swi_string
    bl printException

    sub r0, r4, #4 /* LR 为硬件自动保存 SWI xxx 指令的下一条指令地址,LR – 4 就是 SWI 指令地址,由 SWI 指令编码知识可知,SWI 指令低 24 位保存有软中断号 */
    bl printSWIVal
.else
    ldr r4,[lr, #-4] /* LR 由硬件自动保存软中断指令下一条指令的地址,指令地址存放的是指令的操作码,将操作码取出,低 24 位置,即为软中断号 */
    bic r0, r4, #0xff000000 /* LR 为硬件自动保存 SWI xxx 指令的下一条指令地址,LR – 4 就是 SWI 指令地址,由 SWI 指令编码知识可知,SWI 指令低 24 位保存有软中断号 */
    push {r0}

    /* 处理swi异常 */
    mrs r0, cpsr
    ldr r1, =swi_string
    bl printException

    pop {r0}

    bl printSWIVal
.endif

    /* 恢复现场 */
    ldmia sp!, {r0-r12, pc}^  /* ^ 会把 spsr 的值恢复到 cpsr 里 */

swi_string:
    .string "swi exception"

触发 swi

swi_code:
    swi 0x123  /* 执行此命令, 触发SWI异常, 进入0x8执行 */

C 语言中将软中断号打印出来

void printSWIVal(unsigned int *pSWI)
{
    swi_flag = 1;
    printf("SWI val = 0x%x\r\n", *pSWI & ~0xff000000);
}

未定义指令

异常处理程序

    .align 2
    .arm
    .weak Undefined_Handler
    .type Undefined_Handler, %function
Undefined_Handler:
    /* 执行到这里之前:
    * 1. lr_und 保存有被中断模式中的下一条即将执行的指令的地址
    * 2. SPSR_und 保存有被中断模式的CPSR
    * 3. CPSR 中的 M4-M0 被设置为 11011, 进入到 und 模式
    * 4. 跳到 0x4 的地方执行程序
    */

    /* 在 und 异常处理函数中有可能会修改 r0-r12, 所以先保存 */
    /* lr 是异常处理完后的返回地址, 也要保存 */
    stmdb sp!, {r0-r12, lr}

    /* 保存现场 */
    /* 处理 und 异常 */
    mrs r0, cpsr
    ldr r1, =und_string
    bl printException

    /* 恢复现场 */
    ldmia sp!, {r0-r12, pc}^  /* ^ 会把 spsr 的值恢复到 cpsr 里 */

und_string:
    .string "undefined instruction exception"

触发未定义指令异常

und_code:
    nop              /* for debug */
    .word 0xeeadc0de /* undefine instruction */
    nop              /* for debug */
    .word 0xFFFFFFFF /* undefine instruction */
    nop              /* for debug */

    ldr   r0, =main
    bx    r0

C 语言中将异常字符串打印出来

void printException(unsigned int cpsr, char *str)
{
    excep_flag = 1;
    printf("Exception! cpsr is 0x%x\r\n", cpsr);
}

附录源码

.equ Mode_USR,        0x10
.equ Mode_FIQ,        0x11
.equ Mode_IRQ,        0x12
.equ Mode_SVC,        0x13
.equ Mode_MON,        0x16
.equ Mode_ABT,        0x17
.equ Mode_HYP,        0x1A
.equ Mode_UND,        0x1B
.equ Mode_SYS,        0x1F

.equ Stack_size,      0x400
.equ Stack_Start,     0x80200000

.equ Mode_USR_Stack,  Stack_Start + Stack_size
.equ Mode_FIQ_Stack,  Mode_USR_Stack + Stack_size
.equ Mode_IRQ_Stack,  Mode_FIQ_Stack + Stack_size
.equ Mode_SVC_Stack,  Mode_IRQ_Stack + Stack_size
.equ Mode_MON_Stack,  Mode_SVC_Stack + Stack_size
.equ Mode_ABT_Stack,  Mode_MON_Stack + Stack_size
.equ Mode_HYP_Stack,  Mode_ABT_Stack + Stack_size
.equ Mode_UND_Stack,  Mode_HYP_Stack + Stack_size
.equ Mode_SYS_Stack,  Mode_UND_Stack + Stack_size

    /* 定义一个 .isr_vector 段,链接脚本中定义相应的段来存放该段的数据 */
    .section .isr_vector, "a"
    .arm
    .align 2
    .globl __isr_vector
__isr_vector:
    ldr     pc, =Reset_Handler           /* Reset                  */
    ldr     pc, =Undefined_Handler       /* Undefined instructions */
    ldr     pc, =SVC_Handler             /* Supervisor Call        */
    ldr     pc, =PrefAbort_Handler       /* Prefetch abort         */
    ldr     pc, =DataAbort_Handler       /* Data abort             */
    .word   0                            /* RESERVED               */
    ldr     pc, =IRQ_Handler             /* IRQ interrupt          */
    ldr     pc, =FIQ_Handler             /* FIQ interrupt          */

    /* 余下的指令的放在 .text 中,所以用 .text 指定段 */
/* Reset Handler */
    .text
    .arm
    .align 2
    .globl   Reset_Handler
    .weak    Reset_Handler
    .type    Reset_Handler, %function
Reset_Handler:

    cpsid i /* 关闭全局中断 */

    /* 关闭I,DCache和MMU
     * 采取读-改-写的方式。
     */
    mrc p15, 0, r0, c1, c0, 0     /* 读取 CP15 的 C1 寄存器到 R0 中 */
    bic r0,  r0, #(0x1 << 12)     /* 清除 C1 寄存器的  bit12 位(I位),关闭 I Cache */
    bic r0,  r0, #(0x1 <<  2)     /* 清除 C1 寄存器的 bit2 (C位),关闭 D Cache */
    bic r0,  r0, #0x2             /* 清除 C1 寄存器的 bit1 (A位),关闭对齐 */
    bic r0,  r0, #(0x1 << 11)     /* 清除 C1 寄存器的 bit11 (Z位),关闭分支预测 */
    bic r0,  r0, #0x1             /* 清除 C1 寄存器的 bit0 (M位),关闭 MMU */
    mcr p15, 0, r0, c1, c0, 0     /* 将 r0 寄存器中的值写入到 CP15 的 C1 寄存器中 */

#if 0
    /* 汇编版本设置中断向量表偏移 */
    ldr r0, =0x80000000

    dsb
    isb
    mcr p15, 0, r0, c12, c0, 0
    dsb
    isb
#endif

    /* 模式切换并设置 sp 地址 */
    cps     #Mode_FIQ
    ldr     sp, =Mode_FIQ_Stack

    cps     #Mode_IRQ
    ldr     sp, =Mode_IRQ_Stack

    cps     #Mode_SVC
    ldr     sp, =Mode_SVC_Stack

    cps     #Mode_MON
    ldr     sp, =Mode_MON_Stack

    cps     #Mode_ABT
    ldr     sp, =Mode_ABT_Stack

    cps     #Mode_HYP
    ldr     sp, =Mode_HYP_Stack

    cps     #Mode_UND
    ldr     sp, =Mode_UND_Stack

    /* sys mode and user have common sp register */
    cps     #Mode_SYS
    ldr     sp, =Mode_SYS_Stack

    cpsie i /* 打开全局中断 */

    /* clear bss */
    ldr r1, =__bss_start__
    ldr r2, =__bss_end__
    mov    r0, #0
bss_loop:
    cmp     r1, r2
    itt     lt
    strlt   r0, [r1], #4
    blt    bss_loop


    /* 使能IRQ中断 */
    mrs r0, cpsr        /* 读取cpsr寄存器值到r0中 */
    bic r0, r0, #0x80   /* 将r0寄存器中bit7清零,也就是CPSR中的I位清零,表示允许IRQ中断 */
    msr cpsr, r0        /* 将r0重新写入到cpsr中 */

swi_code:
    swi 0x123  /* 执行此命令, 触发SWI异常, 进入0x8执行 */

und_code:
    nop              /* for debug */
    .word 0xeeadc0de /* undefine instruction */
    nop              /* for debug */
    .word 0xFFFFFFFF /* undefine instruction */
    nop              /* for debug */

    ldr   r0, =main
    bx    r0


    .align 2
    .arm
    .weak Undefined_Handler
    .type Undefined_Handler, %function
Undefined_Handler:
    /* 执行到这里之前:
    * 1. lr_und 保存有被中断模式中的下一条即将执行的指令的地址
    * 2. SPSR_und 保存有被中断模式的CPSR
    * 3. CPSR 中的 M4-M0 被设置为 11011, 进入到 und 模式
    * 4. 跳到 0x4 的地方执行程序
    */

    /* 在 und 异常处理函数中有可能会修改 r0-r12, 所以先保存 */
    /* lr 是异常处理完后的返回地址, 也要保存 */
    stmdb sp!, {r0-r12, lr}

    /* 保存现场 */
    /* 处理 und 异常 */
    mrs r0, cpsr
    ldr r1, =und_string
    bl printException

    /* 恢复现场 */
    ldmia sp!, {r0-r12, pc}^  /* ^ 会把 spsr 的值恢复到 cpsr 里 */

und_string:
    .string "undefined instruction exception"

    .align 2
    .arm
    .weak SVC_Handler
    .type SVC_Handler, %function
SVC_Handler:
    /* 执行到这里之前:
    * 1. lr_svc 保存有被中断模式中的下一条即将执行的指令的地址
    * 2. SPSR_svc 保存有被中断模式的 CPSR
    * 3. CPSR 中的 M4-M0 被设置为 10011, 进入到 svc 模式
    * 4. 跳到 0x08 的地方执行程序
    */

    /* 保存现场 */
    /* 在 swi 异常处理函数中有可能会修改 r0-r12, 所以先保存 */
    /* lr 是异常处理完后的返回地址, 也要保存 */
    stmdb sp!, {r0-r12, lr}

.if 0
    mov r4, lr /* LR 由硬件自动保存软中断指令下一条指令的地址 */

    /* 处理swi异常 */
    mrs r0, cpsr
    ldr r1, =swi_string
    bl printException

    sub r0, r4, #4 /* LR 为硬件自动保存 SWI xxx 指令的下一条指令地址,LR – 4 就是 SWI 指令地址,由 SWI 指令编码知识可知,SWI 指令低 24 位保存有软中断号 */
    bl printSWIVal
.else
    ldr r4,[lr, #-4] /* LR 由硬件自动保存软中断指令下一条指令的地址,指令地址存放的是指令的操作码,将操作码取出,低 24 位置,即为软中断号 */
    bic r0, r4, #0xff000000 /* LR 为硬件自动保存 SWI xxx 指令的下一条指令地址,LR – 4 就是 SWI 指令地址,由 SWI 指令编码知识可知,SWI 指令低 24 位保存有软中断号 */
    push {r0}

    /* 处理swi异常 */
    mrs r0, cpsr
    ldr r1, =swi_string
    bl printException

    pop {r0}

    bl printSWIVal
.endif

    /* 恢复现场 */
    ldmia sp!, {r0-r12, pc}^  /* ^ 会把 spsr 的值恢复到 cpsr 里 */

swi_string:
    .string "swi exception"


    .align 2
    .arm
    .weak PrefAbort_Handler
    .type PrefAbort_Handler, %function
PrefAbort_Handler:
    ldr r0, =PrefAbort_Handler
    bx r0

    .align 2
    .arm
    .weak DataAbort_Handler
    .type DataAbort_Handler, %function
DataAbort_Handler:
    ldr r0, =DataAbort_Handler
    bx r0

    .align 2
    .arm
    .weak IRQ_Handler
    .type IRQ_Handler, %function

IRQ_Handler:
    push {lr}                   /* 保存lr地址 */
    push {r0-r3, r12}           /* 保存r0-r3,r12寄存器 */

    mrs r0, spsr                /* 读取spsr寄存器 */
    push {r0}                   /* 保存spsr寄存器 */

.if 0 /* it always return 0, i don't know why */
    mrc p15, 4, r1, c15, c0, 0 /* 从CP15的C0寄存器内的值到R1寄存器中
                                * 参考文档ARM Cortex-A(armV7)编程手册V4.0.pdf P49
                                * Cortex-A7 Technical ReferenceManua.pdf P68 P138
                                */
.else
    ldr r1, =0x00a00000
.endif
    add r1, r1, #0X2000         /* GIC基地址加0X2000,也就是GIC的CPU接口端基地址 */
    ldr r0, [r1, #0XC]          /* GIC的CPU接口端基地址加0X0C就是GICC_IAR寄存器,
                                    * GICC_IAR寄存器保存这当前发生中断的中断号,我们要根据
                                    * 这个中断号来绝对调用哪个中断服务函数
                                    */
    push {r0, r1}               /* 保存r0,r1 */

    cps #0x13                   /* 进入SVC模式,允许其他中断再次进去 */

    push {lr}                   /* 保存SVC模式的lr寄存器 */
    ldr r2, =system_irqhandler	/* 加载C语言中断处理函数到r2寄存器中*/
    blx r2                      /* 运行C语言中断处理函数,带有一个参数,保存在R0寄存器中 */

    pop {lr}                    /* 执行完C语言中断服务函数,lr出栈 */
    cps #0x12                   /* 进入IRQ模式 */
    pop {r0, r1}
    str r0, [r1, #0X10]         /* 中断执行完成,写EOIR */

    pop {r0}
    msr spsr_cxsf, r0           /* 恢复spsr */

    pop {r0-r3, r12}            /* r0-r3,r12出栈 */
    pop {lr}                    /* lr出栈 */
    subs pc, lr, #4             /* 将lr-4赋给pc */


    .align 2
    .arm
    .weak FIQ_Handler
    .type FIQ_Handler, %function
FIQ_Handler:
    ldr r0, =FIQ_Handler
    bx r0


    .end

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

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

相关文章

Git的常见操作

Git版本控制 开发难题 在实际开发中我们会遇到一些问题&#xff0c;电脑蓝屏&#xff0c;代码丢了&#xff0c;懊悔不&#xff1f; 时间长了&#xff0c;文件找不到了。懊悔不&#xff1f;手欠&#xff0c;之前代码运行好好的&#xff0c;非要去优化下。结果还 不如以前&am…

京东天猫数据查询与分析:2023年厨电细分市场数据分析

随着消费者对生活品质的追求持续提高&#xff0c;我国厨房电器产品的需求也日趋多样化&#xff0c;市场中厨房电器的品类越来越多&#xff0c;我国厨房电器的市场规模也不断扩大。 根据鲸参谋电商数据显示&#xff0c;2023年1月至4月&#xff0c;天猫平台上厨房电器的销量为670…

搭建个人hMailServer 邮件服务实现远程发送邮件

文章目录 1. 安装hMailServer2. 设置hMailServer3. 客户端安装添加账号4. 测试发送邮件5. 安装cpolar6. 创建公网地址7. 测试远程发送邮件8. 固定连接公网地址9. 测试固定远程地址发送邮件 转载自cpolar极点云文章&#xff1a;搭建个人hMailServer 邮件服务实现远程发送邮件 hM…

ChatGPT微调系列一:总述 微调 的基本流程

文章目录 前言一、啥叫微调二、为啥要微调三、不是所有模型都可以微调的四、总述微调的基本流程&#xff0c;以及涉及的主要函数&#xff0c;参数1. 安装2. 准备训练数据3. openai.api_key os.getenv() 进行一个说明4. 通过API 调用模型 常用函数5. 微调模型 常用函数6. OpenA…

Maven 使用详细教程

目录 Maven 介绍 Maven 安装 1、安装JDK 2、下载Maven安装文件 3、配置环境变量 4、检测安装成功 Maven 标准工程结构 Maven 版本要素 Maven仓库 1、本地仓库&#xff1a; 2、中央仓库 3、其他远程仓库 创建Maven工程 使用命令方式创建Maven工程 Eclipse中创建…

智能大棚自动控制系统 实现传统农业精细化管理

新型农业经营主体管理系统是指为了适应农村经济发展需求&#xff0c;提高农业生产组织化、规模化、现代化程度&#xff0c;促进农业产业结构调整和农村产业转型升级&#xff0c;推动农村经济社会持续健康发展而建立的一套管理体系。 该系统主要包括农产品生产、种植、养…

JavaWeb学习路线(8)——登录

一、基本登录功能 &#xff08;一&#xff09;需求&#xff1a; 根据账号与密码判别用户是否可以登录 &#xff08;二&#xff09;实现步骤 Controller接收传递的JSON格式数据&#xff0c;使用RequestBody实体类进行接收&#xff0c;调用Service具体处理。Service创建登录接…

循环购应运而生,让老百姓敢于消费、有钱消费、愿意消费

​小编介绍&#xff1a;10年专注商业模式设计及软件开发&#xff0c;擅长企业生态商业模式&#xff0c;商业零售会员增长裂变模式策划、商业闭环模式设计及方案落地&#xff1b;扶持10余个电商平台做到营收过千万&#xff0c;数百个平台达到百万会员&#xff0c;欢迎咨询。 无论…

7 植物背景分离、RGB、HSV特征提取案例(matlab程序)

学习目标&#xff1a;背景分离和RGB等特征提取 1.简述 叶片RGB图像背景精确分离的方法,包括以下图像背景分离方法:S1:选取叶片,所得到的原始图像;S2:采用MATLAB 2016R软件将RGB图像转化为HSV图像,以饱和度0.190.21为界限,将小于界限的图像明度调整为0,并转化为灰度图;S3:用edg…

Redis高并发分布式锁

文章目录 高并发场景秒杀抢购超卖Bug高并发场景秒杀抢购Demo测试结果 JVM级别锁使用nginx对本地服务进行负载均衡 Redis实现分布式锁Redis分布式锁实现DemoRedis分布式锁有关问题 分布式锁性能的提升减少锁的粒度使用异步处理 高并发场景秒杀抢购超卖Bug 在今天的数字化世界中&…

供应商索赔(金税数据)导入并创建凭证(ALV长篇备忘三)

情境/背景:供应商三包索赔款项源起QMS质量系统&#xff0c;联动金税系统完成发票开具&#xff0c;最终在SAP系统中创建完成财务凭证。该流程为手工操作&#xff0c;费时费力且效率低下容易出错。 目标/任务:把QMS供应商三包索赔业务搬上线,同SAP FI顾问梳理功能说明书&#xf…

2023-06-29:redis中什么是热点Key?该如何解决?

2023-06-29&#xff1a;redis中什么是热点Key&#xff1f;该如何解决&#xff1f; 答案2023-06-29&#xff1a; 在Redis中&#xff0c;经常被访问的key被称为热点key。 产生原因和危害 原因 热点key问题产生的原因可以归纳为以下两种情况&#xff1a; 用户对于某些数据的…

安卓弹出popup之XPopup

弹窗自己写的话。虽然很简单。但不够丝滑。如果要优雅点的。又要添加动画。但是。。。如果用上了XPopup&#xff0c;动画别人帮你写。爽不爽&#xff1f;丝滑不丝滑。。&#xff1f; 丝滑第一步。先引入依赖 implementation com.github.li-xiaojun:XPopup:2.9.19如果没有这些…

git版本回退操作

本文 git 相关命令&#xff1a; git reset&#xff1a;回退版本&#xff0c;可指定某一次提交的版本。git reset [--soft | --mixed | --hard] commitId。git revert&#xff1a;撤销某个提交&#xff0c;做反向操作&#xff0c;生成新的commitId&#xff0c;原有提交记录保留…

基于java+swing+mysql图书管理系统V7.0

基于javaswingmysql图书管理系统V7.0 一、系统介绍二、功能展示1.项目骨架2.数据库表3.项目内容4.主界面5.登陆6、借阅管理7、修改读者信息8、图书验收9、新书订购 四、其它1.其他系统实现五.获取源码 一、系统介绍 项目类型&#xff1a;Java SE项目&#xff08;awtswing&…

(四)python实战——Sqlite3数据库表的增、删、查、改操作案例

前言 Sqlite3是一个轻量级的数据库&#xff0c;本节内容我们介绍一下如何在python环境中使用Sqlite数据库&#xff0c;完成数据库表的简单增、删、查、改操作。开始本节内容之前&#xff0c;我们需要先安装好python环境&#xff0c;我们使用的是python3的环境。 正文 ①创建…

【Docker】利用Dockerfile制作个人的镜像文件详细讲解

前言 Docker 是一个开源的应用容器引擎&#xff0c;让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的Linux或Windows操作系统的机器上,也可以实现虚拟化,容器是完全使用沙箱机制,相互之间不会有任何接口。 &#x1f4d5;作者简介&#xff1a;热…

如何避免死锁--方法三--scoped_lock

scoped_lock是c17新增的一种模板&#xff0c;也是RAII模式。其是可变参数&#xff0c;可以接受各种互斥类型作为参数模板&#xff0c;可以指定多个互斥量。 前文中我们说到&#xff0c;lock可以锁定多个互斥量&#xff0c;scoped_lock也可以做到。 void thread1() {cout <&…

【网络互联设备】网络杂谈(15)之网桥、路由器、网关、集线器、交换机、中继器的作用与概念

涉及知识点 网桥、路由器、网关、集线器、交换机、中继器的作用与概念&#xff0c;常见的网络互联设备&#xff0c;什么是网桥、路由器、网关、集线器、交换机、中继器。 原创于&#xff1a;CSDN博主-《拄杖盲学轻声码》&#xff0c;更多内容可去其主页关注下哈&#xff0c;不…

STM32F103基于HAL工程挂载FatFS驱动SD卡实现IAP功能

STM32F103基于HAL工程挂载FatFS驱动SD卡实现IAP功能 &#x1f3ac;基于SD卡IAP升级演示&#xff1a; &#x1f4cd;相关篇《STM32F103基于HAL工程挂载FatFS驱动SD卡》 &#x1f4cc;《使用STM32F103的串口实现IAP程序升级功能》 &#x1f449;&#x1f3fb;ST相关文档&…