<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>STM32 on Zesean 的博客</title><link>https://zeseanblog.top/tags/stm32/</link><description>Recent content in STM32 on Zesean 的博客</description><generator>Hugo -- gohugo.io</generator><language>zh-CN</language><lastBuildDate>Sat, 05 Sep 2026 14:53:14 +0800</lastBuildDate><atom:link href="https://zeseanblog.top/tags/stm32/index.xml" rel="self" type="application/rss+xml"/><item><title>冷阴极 X 射线源球壳钛合金焊缝检测装置（下位机）</title><link>https://zeseanblog.top/projects/xray-motion-control/</link><pubDate>Tue, 01 Sep 2026 22:30:00 +0800</pubDate><guid>https://zeseanblog.top/projects/xray-motion-control/</guid><description>&lt;h2 id="项目概述"&gt;&lt;a href="#%e9%a1%b9%e7%9b%ae%e6%a6%82%e8%bf%b0" class="header-anchor"&gt;&lt;/a&gt;项目概述&#13;&#10;&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;国家重点研发项目&lt;/strong&gt;子课题：冷阴极 X 射线源球壳钛合金焊缝检测装置的运动控制系统（下位机部分）。&lt;/p&gt;&#10;&lt;p&gt;装置通过 X 射线源与平板探测器沿弧形轨道协同扫查，对球壳钛合金焊缝成像检测。下位机负责对 X 射线源、探测器的&lt;strong&gt;协同控制与远程控制&lt;/strong&gt;：配合 QT 上位机调整射线源与探测器的速度与动作，完成单段焊缝、截面焊缝两种检测模式，并实时上报系统状态。&lt;/p&gt;&#10;&lt;h2 id="系统组成"&gt;&lt;a href="#%e7%b3%bb%e7%bb%9f%e7%bb%84%e6%88%90" class="header-anchor"&gt;&lt;/a&gt;系统组成&#13;&#10;&lt;/h2&gt;&lt;pre class="mermaid" style="visibility:hidden"&gt;flowchart TB&#10; PC["QT 上位机"] &lt;--&gt;|"RS485 自定义协议帧&lt;br/&gt;UART 中断+DMA · 字节状态机 · 环形缓冲"| MCU["STM32F429 下位机&lt;br/&gt;FreeRTOS 多任务"]&#10; MCU --&gt;|"Modbus RTU + CRC16"| SERVO["禾川伺服电机驱动器"]&#10; SERVO --&gt; AXIS["弧形轨道多电机协同"]&#10; AXIS --&gt; DEV["X 射线源 + 平板探测器"]&lt;/pre&gt;&lt;p&gt;&lt;img alt="检测装置整机：弧形轨道与伺服驱动系统" class="gallery-image" data-flex-basis="179px" data-flex-grow="74" height="1706" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://zeseanblog.top/projects/xray-motion-control/images/device-overview.jpg" srcset="https://zeseanblog.top/projects/xray-motion-control/images/device-overview_hu_53f8fa1bc98513c9.jpg 800w, https://zeseanblog.top/projects/xray-motion-control/images/device-overview.jpg 1279w" width="1279"&gt;&lt;/p&gt;&#10;&lt;p&gt;整机实拍：左侧为提升立柱与弧形齿轮轨道，右侧为探测器载滑车，地面为多路禾川伺服驱动器与供电线缆。&lt;/p&gt;&#10;&lt;h2 id="软件分层与模块"&gt;&lt;a href="#%e8%bd%af%e4%bb%b6%e5%88%86%e5%b1%82%e4%b8%8e%e6%a8%a1%e5%9d%97" class="header-anchor"&gt;&lt;/a&gt;软件分层与模块&#13;&#10;&lt;/h2&gt;&lt;p&gt;软件分为两层：&lt;strong&gt;APP 应用层&lt;/strong&gt;承载对外协议、运动语义与任务调度；&lt;strong&gt;驱动层&lt;/strong&gt;提供寄存器级能力（Modbus 协议模板与 CRC、电机寄存器配置序列、OLED/菜单/按键、FLASH 参数存储）。&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;模块&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;职责&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;app_conf.h&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;全部可调参数集中管理：协议常量、超时、限幅、软限位、任务栈与优先级、RS485 开关&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;host_proto.h/.c&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;RS485/USART6 二进制协议栈&lt;/strong&gt;：TX DMA + 环形缓冲 + 组包 CRC；RX IDLE+DMA + 环形缓冲 + 字节状态机解析；命令分发回调；心跳时间戳；收发统计&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;motion_api.h/.c&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;统一运动控制接口&lt;/strong&gt;：轴状态机（IDLE/READY/MOVING/FAULT）、方向（速度符号）/速度（±3000 限幅）/动作时序（松闸稳定→运动→清速减速→抱闸）、编码器事务三重校验、软限位、安全关断收敛入口、故障复位&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;dev_task.h/.c&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;四大逻辑任务&lt;/strong&gt;：motionTask（动作执行+ACK）、statusTask（50ms 状态轮询+100ms 状态帧）、monitorTask（心跳超时/故障沿上报/IWDG 喂狗点）、装配函数 &lt;code&gt;APP_TasksInit()&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;h2 id="软件架构"&gt;&lt;a href="#%e8%bd%af%e4%bb%b6%e6%9e%b6%e6%9e%84" class="header-anchor"&gt;&lt;/a&gt;软件架构&#13;&#10;&lt;/h2&gt;&lt;h3 id="数据流下行指令--上行状态"&gt;&lt;a href="#%e6%95%b0%e6%8d%ae%e6%b5%81%e4%b8%8b%e8%a1%8c%e6%8c%87%e4%bb%a4--%e4%b8%8a%e8%a1%8c%e7%8a%b6%e6%80%81" class="header-anchor"&gt;&lt;/a&gt;数据流（下行指令 → 上行状态）&#13;&#10;&lt;/h3&gt;&lt;pre class="mermaid" style="visibility:hidden"&gt;flowchart TB&#10; PC["上位机"] --&gt;|"A5 5A 协议帧"| RX["USART6 RX&lt;br/&gt;IDLE 中断 + DMA 批量入环形缓冲"]&#10; RX --&gt; PARSE["parseTask（AboveNormal）&lt;br/&gt;7 态字节状态机 + CRC16 查表校验"]&#10; PARSE --&gt;|"CRC 通过"| Q["motionQueue&lt;br/&gt;8 深消息队列削峰"]&#10; Q --&gt; MOTION["motionTask（High）&lt;br/&gt;动作时序执行 + ACK 上行"]&#10; MOTION --&gt;|"轴级互斥锁串行化"| MB["UART4/5 RS485&lt;br/&gt;Modbus RTU"]&#10; MB --&gt; DRIVE["禾川伺服驱动器&lt;br/&gt;速度环 + 电流环硬件闭环"]&#10; DRIVE --&gt; AXIS["弧形轨道双轴电机"]&#10;&#10; STATUS["statusTask（Normal · 50ms）&lt;br/&gt;编码器轮询 + 软限位检查"] --&gt; MB&#10; STATUS --&gt;|"100ms 状态帧 0x82&lt;br/&gt;TX 环形缓冲 + DMA"| PC&#10; MON["monitorTask（Normal · 100ms）&lt;br/&gt;心跳超时 / 故障沿 / IWDG"] --&gt;|"SafeShutdown + 0x8F 上报"| MOTION&lt;/pre&gt;&lt;h3 id="任务划分"&gt;&lt;a href="#%e4%bb%bb%e5%8a%a1%e5%88%92%e5%88%86" class="header-anchor"&gt;&lt;/a&gt;任务划分&#13;&#10;&lt;/h3&gt;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;任务&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;CMSIS 优先级&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;栈&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;阻塞点&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;职责&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;parseTask&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;AboveNormal&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;2KB&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;线程标志（50ms 超时）&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;协议解析（状态机+CRC）&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;motionTask&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;High&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;2KB&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;消息队列（100ms 超时）&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;动作执行 + 定时动作截止判定&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;statusTask&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Normal&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;2KB&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;osDelay(50)&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;编码器轮询 + 状态反馈&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;monitorTask&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Normal&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1.5KB&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;osDelay(100)&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;心跳超时/故障沿/安全关断触发&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;oledShowMenuTask&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Low&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;512B&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;事件标志&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;菜单显示（按键扫描定时器 10ms）&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;defaultTask&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Normal&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;512B&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;osDelay(1000)&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;LED 心跳&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;两个关键的执行体设计：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;协议解析与动作执行分离&lt;/strong&gt;：解析出指令后经 8 深消息队列投递给 motionTask 按序执行，队列削峰保证解析节奏不受动作耗时拖累；&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;编码器轮询放在任务而非软件定时器中&lt;/strong&gt;：定时器回调与任务可能并发访问同一 Modbus 通道，统一到任务上下文并配合轴级互斥锁后，「请求-应答」事务被彻底串行化。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="任务优先级设计依据"&gt;&lt;a href="#%e4%bb%bb%e5%8a%a1%e4%bc%98%e5%85%88%e7%ba%a7%e8%ae%be%e8%ae%a1%e4%be%9d%e6%8d%ae" class="header-anchor"&gt;&lt;/a&gt;任务优先级设计依据&#13;&#10;&lt;/h3&gt;&lt;p&gt;优先级不是按“功能重要性”排的，而是按三条工程准则：&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;&lt;strong&gt;截止期与后果&lt;/strong&gt;：动作执行（清速/抱闸/安全关断）有硬时序且后果不可逆 → 最高应用优先级；&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;阻塞特性&lt;/strong&gt;：事件驱动型任务平时全部阻塞在同步原语上，高优先级只影响唤醒延迟，不产生 CPU 争抢；&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;依赖方向&lt;/strong&gt;：数据流上游不能被下游长时间阻塞（parse 喂 motion 的队列；status 的编码器数据被 motion 的轴锁依赖）。&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;完整优先级谱系（NVIC 中断优先级与任务优先级是两套量纲，勿混）：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 2&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 3&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 4&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 5&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 6&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 7&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 8&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 9&#10;&lt;/span&gt;&lt;span class="lnt"&gt;10&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;NVIC 中断 (任何 ISR 可抢占任何任务)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ├─ UART4/5/6/8 抢占优先级=5（恰好等于 FreeRTOS 可调用 API 的阈值，&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ ISR 内允许 FromISR API；只做批量入环形缓冲 + flagsSet，µs 级）&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;任务 (FreeRTOS 原生优先级，抢占式 + 同级时间片轮转)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ├─ 38 High motionTask ← 安全动作的执行权最高&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ├─ 31 AboveNormal parseTask ← 必须高于周期型任务，否则状态轮询可推迟命令解析&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ├─ 23 Normal statusTask / monitorTask / defaultTask&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ├─ 8 Low oledShowMenuTask（人机界面，空闲余量处理）&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ├─ 2 Timer Service（按键扫描定时器，CubeMX 默认值）&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; └─ 0 Idle&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;几个关键取舍：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;为什么 motion(38) &amp;gt; parse(31)&lt;/strong&gt;：命令队列已削峰（8 深），解析晚 1–2ms 不影响正确性；反之若 parse 更高，解析长帧期间到达的停止/安全命令会被推迟执行——&lt;strong&gt;安全动作的执行权高于协议的解析权&lt;/strong&gt;。&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;status/monitor 同级 Normal&lt;/strong&gt;：两者都是周期型软实时（50ms/100ms），判定窗口（软限位/心跳 500ms）远大于调度延迟；它们持锁时间短（单次 Modbus 事务约 6ms），motion 高优先级被轴锁阻塞时由&lt;strong&gt;互斥量优先级继承&lt;/strong&gt;临时提升持锁任务，消除优先级反转。&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;关中断快照 vs 互斥量&lt;/strong&gt;：跨任务读轴状态用 &lt;code&gt;__disable_irq()&lt;/code&gt; 短临界区（µs 级、无反转风险）；锁只用于&lt;strong&gt;必须串行化的 Modbus 事务&lt;/strong&gt;（同一 UART 的请求-应答配对）。&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;一个已知限制&lt;/strong&gt;：定时器守护任务优先级为 CubeMX 默认值 2，低于所有应用任务——当前成立的条件是所有任务都有周期阻塞点，定时器总能在间隙运行；若未来把安全逻辑放进软件定时器（如限位去抖），必须提高该优先级。&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Realtime(47) 刻意留空&lt;/strong&gt;：为硬实时扩展预留（5–20ms 节拍的位置环、限位 EXTI 的紧急处理链）。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="轴状态机"&gt;&lt;a href="#%e8%bd%b4%e7%8a%b6%e6%80%81%e6%9c%ba" class="header-anchor"&gt;&lt;/a&gt;轴状态机&#13;&#10;&lt;/h3&gt;&lt;pre class="mermaid" style="visibility:hidden"&gt;stateDiagram-v2&#10; [*] --&gt; IDLE&#10; IDLE --&gt; READY: InitAll（伺服参数+原点捕获+松闸）&#10; READY --&gt; MOVING: SetSpeed / MoveTimed&#10; MOVING --&gt; READY: Stop（清速→等减速→抱闸）&#10; READY --&gt; FAULT: Safe_Shutdown（异常保护）&#10; MOVING --&gt; FAULT: Safe_Shutdown&#10; FAULT --&gt; READY: FaultReset（重初始化+重捕获原点+松闸）&lt;/pre&gt;&lt;p&gt;任何异常都收敛到&lt;strong&gt;单一安全关断入口&lt;/strong&gt; &lt;code&gt;MOTION_SafeShutdown()&lt;/code&gt;：清速 → 等 150ms（驱动器 100ms 减速斜坡完成，避免高速抱闸冲击）→ 断抱闸继电器 → 锁定 FAULT + 故障码 → monitorTask 沿检测上报。恢复必须显式故障复位指令。&lt;/p&gt;&#10;&lt;p&gt;&lt;img alt="下位机主控板：STM32F429 + 多路 RS485（RJ45 座）与继电器输出" class="gallery-image" data-flex-basis="320px" data-flex-grow="133" height="1279" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://zeseanblog.top/projects/xray-motion-control/images/control-board.jpg" srcset="https://zeseanblog.top/projects/xray-motion-control/images/control-board_hu_6a4d8b99c70223cc.jpg 800w, https://zeseanblog.top/projects/xray-motion-control/images/control-board_hu_957865d0400ddf81.jpg 1600w, https://zeseanblog.top/projects/xray-motion-control/images/control-board.jpg 1706w" width="1706"&gt;&lt;/p&gt;&#10;&lt;h2 id="上位机帧协议"&gt;&lt;a href="#%e4%b8%8a%e4%bd%8d%e6%9c%ba%e5%b8%a7%e5%8d%8f%e8%ae%ae" class="header-anchor"&gt;&lt;/a&gt;上位机帧协议&#13;&#10;&lt;/h2&gt;&lt;h3 id="帧格式"&gt;&lt;a href="#%e5%b8%a7%e6%a0%bc%e5%bc%8f" class="header-anchor"&gt;&lt;/a&gt;帧格式&#13;&#10;&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1&#10;&lt;/span&gt;&lt;span class="lnt"&gt;2&#10;&lt;/span&gt;&lt;span class="lnt"&gt;3&#10;&lt;/span&gt;&lt;span class="lnt"&gt;4&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┌──────┬──────┬─────┬──────┬─────────────┬─────────────┬──────────────┐&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ 0xA5 │ 0x5A │ SEQ │ LEN │ CMD │ DATA[LEN] │ CRC16_LO/HI │&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;└──────┴──────┴─────┴──────┴─────────────┴─────────────┴──────────────┘&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 帧头1 帧头2 序号 载荷长 命令字 0~64B 小端 查表 CRC16，低字节先&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;双帧头 A5 5A&lt;/strong&gt;：误同步率 1/65536；A5/5A 位翻转丰富，示波器上可辨；&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;SEQ 序号&lt;/strong&gt;：下行主机自增（ACK 回显 → 去重/丢帧检测），上行设备独立自增；&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;LEN 显式变长&lt;/strong&gt;：状态机据此收数，天然支持命令扩展；&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;CRC16 覆盖 SEQ..DATA&lt;/strong&gt;（不含帧头与自身），算法与 Modbus 完全一致（同一张查表），全协议栈一套 CRC；&lt;/li&gt;&#10;&lt;li&gt;帧开销 7 字节；载荷中可能出现假帧头，由 CRC 兜底丢弃（更高可靠场景可选 COBS 变长编码）。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="命令集"&gt;&lt;a href="#%e5%91%bd%e4%bb%a4%e9%9b%86" class="header-anchor"&gt;&lt;/a&gt;命令集&#13;&#10;&lt;/h3&gt;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;CMD&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;方向&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;载荷&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;语义&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0x01&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;下行&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;轴号 + 开关&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;抱闸控制&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0x02&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;下行&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;轴号 + rpm(int16)&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;速度指令（符号=方向，±3000 限幅）&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0x03&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;下行&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;轴号 + rpm + 时长(100ms 单位)&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;定时动作（动作时序演示）&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0x04&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;下行&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;轴号（0xFF=全轴）&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;停止&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0x05&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;下行&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;seq 回显&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;心跳（200ms 周期，不回 ACK）&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0x06&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;下行&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;轴号（0xFF=全部）&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;故障复位&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0x07&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;下行&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;无&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;立即请求状态帧（回 0x82）&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0x81&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;上行&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;回显命令/序号 + status&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;命令应答（OK/BUSY/REJECT/BAD_ARG）&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0x82&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;上行&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;状态字×2 + 双轴速度 + 故障码&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;周期状态帧（100ms）&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0x8F&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;上行&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;故障码 + 轴号 + 保留&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;故障上报（故障沿触发一次）&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;h3 id="实例帧crc-实算可直接用于联调"&gt;&lt;a href="#%e5%ae%9e%e4%be%8b%e5%b8%a7crc-%e5%ae%9e%e7%ae%97%e5%8f%af%e7%9b%b4%e6%8e%a5%e7%94%a8%e4%ba%8e%e8%81%94%e8%b0%83" class="header-anchor"&gt;&lt;/a&gt;实例帧（CRC 实算，可直接用于联调）&#13;&#10;&lt;/h3&gt;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;帧&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;字节&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;轴1 速度 -500rpm&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;A5 5A 01 03 02 01 FE 0C 55 D7&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;轴2 800rpm 转 3.0s&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;A5 5A 02 04 03 02 20 03 1E FD CE&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;心跳&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;A5 5A 03 01 05 5A D2 CB&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;状态帧（READY/MOVING，sp1=1500，fault=0x0001）&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;A5 5A 10 08 82 01 02 DC 05 00 00 01 00 97 A8&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;故障帧（驱动器无应答，轴1）&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;A5 5A 11 05 8F 02 00 01 00 00 E2 F4&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;h3 id="解析鲁棒性7-态状态机--三重防护"&gt;&lt;a href="#%e8%a7%a3%e6%9e%90%e9%b2%81%e6%a3%92%e6%80%a77-%e6%80%81%e7%8a%b6%e6%80%81%e6%9c%ba--%e4%b8%89%e9%87%8d%e9%98%b2%e6%8a%a4" class="header-anchor"&gt;&lt;/a&gt;解析鲁棒性（7 态状态机 + 三重防护）&#13;&#10;&lt;/h3&gt;&lt;ol&gt;&#10;&lt;li&gt;&lt;strong&gt;LEN &amp;gt; 64&lt;/strong&gt; → 丢弃回找头（防超长帧打爆缓冲）；&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;帧内字节间隔 &amp;gt; 50ms&lt;/strong&gt; → 强制回找头（防半帧卡死）；&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;CRC 失败&lt;/strong&gt; → 丢当前帧回找头（假同步自愈）；RX 环满 → 丢最新并计数上报统计。&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;接收链路硬件侧使用 UART 中断 + DMA 不定长接收，以 IDLE 总线空闲事件判帧：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1&#10;&lt;/span&gt;&lt;span class="lnt"&gt;2&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;__HAL_UART_ENABLE_IT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;huart6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;UART_IT_IDLE&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cm"&gt;/* 帧长 = 接收缓冲区大小 - __HAL_DMA_GET_COUNTER(&amp;amp;hdma_usart6_rx) */&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;ISR 只做批量搬移入环形缓冲（SPSC 免锁）+ 置线程标志，解析全部在 parseTask 完成——ISR 保持极短是实时性的关键：若在中断里做逐字节处理甚至打印，9600 波特率下一帧即可阻塞几十 ms。&lt;/p&gt;&#10;&lt;h2 id="统一运动控制-api"&gt;&lt;a href="#%e7%bb%9f%e4%b8%80%e8%bf%90%e5%8a%a8%e6%8e%a7%e5%88%b6-api" class="header-anchor"&gt;&lt;/a&gt;统一运动控制 API&#13;&#10;&lt;/h2&gt;&lt;p&gt;对上层屏蔽多电机与 Modbus 细节，全部运动能力收敛为一组 &lt;code&gt;MOTION_*&lt;/code&gt; 接口：&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;API&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;语义&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;MOTION_InitAll()&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;伺服参数写入 + 原点捕获 + 松闸（任务上下文）&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;MOTION_SetSpeed(ax, rpm)&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;方向=符号；自动先松闸再设速&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;MOTION_MoveTimed(ax, rpm, dur)&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;速度 + 截止期（定时动作）&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;MOTION_Stop(ax)&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;清速 → 150ms 减速等待 → 抱闸&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;MOTION_Brake(ax, open)&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;手动抱闸（运动中拒绝）&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;MOTION_Tick()&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;定时动作到期判定（100ms 粒度）&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;MOTION_PollFeedback()&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;双轴编码器轮询 + 无应答计数 + 软限位&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;MOTION_GetSnapshot(ax, &amp;amp;s)&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;状态/速度/位置/故障原子快照&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;MOTION_SafeShutdown(ax, fault)&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;安全关断唯一入口&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;MOTION_FaultReset(ax)&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;FAULT → READY：重初始化 + 重捕获原点 + 松闸&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;底层由一组寄存器级驱动接口支撑：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1&#10;&lt;/span&gt;&lt;span class="lnt"&gt;2&#10;&lt;/span&gt;&lt;span class="lnt"&gt;3&#10;&lt;/span&gt;&lt;span class="lnt"&gt;4&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;Motor_Init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MOTOR1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 初始化：Modbus 链路建立 + 驱动器寄存器配置&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;openMotor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MOTOR1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 电机使能&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;speed_set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MOTOR1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;speed&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 速度模式设定：负速度转补码写入，±3000rpm 软件限幅&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;closeMotor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MOTOR1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 去使能（配合抱闸继电器安全停机）&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;h2 id="modbus-rtu-驱动器接口"&gt;&lt;a href="#modbus-rtu-%e9%a9%b1%e5%8a%a8%e5%99%a8%e6%8e%a5%e5%8f%a3" class="header-anchor"&gt;&lt;/a&gt;Modbus RTU 驱动器接口&#13;&#10;&lt;/h2&gt;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;功能码&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;用途&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0x03&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;读保持寄存器（状态/速度回收）&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0x06&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;写单寄存器（速度设定等）&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0x10&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;写多寄存器（批量参数下发）&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;帧结构：&lt;code&gt;地址(1B) + 功能码(1B) + 数据(NB) + CRC16(2B，低字节在前)&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;CRC16 参数：多项式 &lt;strong&gt;0x8005&lt;/strong&gt;（反射 0xA001）、初值 &lt;strong&gt;0xFFFF&lt;/strong&gt;，实现查表法与逐位法两种等价算法——上位机协议栈的 CRC 直接复用同一张表&lt;/li&gt;&#10;&lt;li&gt;实例帧：&lt;code&gt;01 06 03 03 05 DC&lt;/code&gt; → CRC = &lt;code&gt;7B 47&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;错误应答识别：功能码 + 0x80&lt;/li&gt;&#10;&lt;li&gt;编码器读事务做&lt;strong&gt;功能码 + 长度 + CRC 三重校验&lt;/strong&gt;，连续无应答即触发单轴安全关断&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="异常保护体系"&gt;&lt;a href="#%e5%bc%82%e5%b8%b8%e4%bf%9d%e6%8a%a4%e4%bd%93%e7%b3%bb" class="header-anchor"&gt;&lt;/a&gt;异常保护体系&#13;&#10;&lt;/h2&gt;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;保护&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;触发条件&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;动作&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;心跳丢失&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;500ms 无任何合法帧（首帧后生效）&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;全轴 SafeShutdown + 0x8F 上报&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;驱动器失联&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;连续 3 次编码器读无应答/校验失败&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;单轴 SafeShutdown&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;软限位&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;位置超出 ±170°（可配）且运动中&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;单轴 SafeShutdown&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;硬限位&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;EXTI 接口预留（硬件常闭开关已具备）&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;后续接入 SafeShutdown 链&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;CRC 错帧&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;校验失败&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;丢帧 + 收发统计&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;IWDG&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;喂狗点已置于 monitorTask（启用为编译开关）&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;复位兜底&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;抱闸 failsafe&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;硬件特性：断电即抱死&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;物理兜底&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;h2 id="关于伺服三环控制的工程结论"&gt;&lt;a href="#%e5%85%b3%e4%ba%8e%e4%bc%ba%e6%9c%8d%e4%b8%89%e7%8e%af%e6%8e%a7%e5%88%b6%e7%9a%84%e5%b7%a5%e7%a8%8b%e7%bb%93%e8%ae%ba" class="header-anchor"&gt;&lt;/a&gt;关于伺服三环控制的工程结论&#13;&#10;&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;电流环/速度环/位置环要不要在 MCU 里做？——不需要，这是伺服系统的标准级联结构&lt;/strong&gt;：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;位置环（软件，按曲线节拍）→ 速度环（驱动器硬件闭环）→ 电流环（驱动器硬件闭环，µs 级带宽）；&lt;/li&gt;&#10;&lt;li&gt;若强行在 MCU 做三环：RS485 半双工往返 + RTOS 任务抖动，内环带宽根本撑不起来，反而劣化性能；&lt;/li&gt;&#10;&lt;li&gt;本项目 MCU 只做&lt;strong&gt;位置外环&lt;/strong&gt;：协议与运动接口预留位置指令扩展，速度/电流环交给禾川驱动器。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;轨迹跟踪业务层的实现方案：双电机按 FLASH 中的 1000 点位置/速度曲线做 S 曲线协同跟踪——位置 PID + 速度前馈 + 双电机交叉耦合补偿，位置环输出直接写驱动器速度寄存器；上位机经 LoRa 链路完成曲线分块下传（块号 ACK 隐含重传）、误差曲线回读、前馈/耦合系数在线整定与点动控制；限位开关走 EXTI 硬件链路。&lt;/p&gt;&#10;&lt;h2 id="已知限制与后续项"&gt;&lt;a href="#%e5%b7%b2%e7%9f%a5%e9%99%90%e5%88%b6%e4%b8%8e%e5%90%8e%e7%bb%ad%e9%a1%b9" class="header-anchor"&gt;&lt;/a&gt;已知限制与后续项&#13;&#10;&lt;/h2&gt;&lt;ol&gt;&#10;&lt;li&gt;驱动器应答等待仍为固定 5ms 盲等，改进方向是信号量 + 超时；&lt;/li&gt;&#10;&lt;li&gt;IWDG 未启用（喂狗点与启用步骤已就位，编译开关打开即用）；&lt;/li&gt;&#10;&lt;li&gt;硬限位 EXTI 尚未接入软件保护链（常闭开关硬件与 EXTI 驱动已具备，接入方案：ISR 直接抱闸 + 通知任务走 SafeShutdown）；&lt;/li&gt;&#10;&lt;li&gt;USART6 波特率 9600，协议帧建议提升到 115200；&lt;/li&gt;&#10;&lt;li&gt;printf 复用上位机串口，正式版应以编译开关关闭或改用独立调试口；&lt;/li&gt;&#10;&lt;li&gt;位置闭环当前为速度模式的接口预留，轨迹跟踪按上文 S 曲线方案扩展。&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h2 id="项目状态"&gt;&lt;a href="#%e9%a1%b9%e7%9b%ae%e7%8a%b6%e6%80%81" class="header-anchor"&gt;&lt;/a&gt;项目状态&#13;&#10;&lt;/h2&gt;&lt;p&gt;下位机软件已完成「RS485 协议栈 + 统一运动接口 + 四大逻辑任务 + 多级异常保护」的完整架构实现；驱动层经整机联调实测，Modbus 通信、双轴运动控制、上位机链路稳定工作，支撑完成单段焊缝与截面焊缝检测流程。&lt;/p&gt;&#10;</description></item><item><title>STM32 BootLoader 学习笔记</title><link>https://zeseanblog.top/p/stm32-bootloader/</link><pubDate>Sat, 30 Aug 2025 14:00:00 +0800</pubDate><guid>https://zeseanblog.top/p/stm32-bootloader/</guid><description>&lt;h1 id="bootloader-学习"&gt;&lt;a href="#bootloader-%e5%ad%a6%e4%b9%a0" class="header-anchor"&gt;&lt;/a&gt;BootLoader 学习&#13;&#10;&lt;/h1&gt;&lt;p&gt;硬件：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;stm32f407(嘉立创天空星)&lt;/li&gt;&#10;&lt;li&gt;HC-05蓝牙模块&#10;软件：&lt;/li&gt;&#10;&lt;li&gt;暂时使用python简陋演示功能&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="为什么要有bootloader"&gt;&lt;a href="#%e4%b8%ba%e4%bb%80%e4%b9%88%e8%a6%81%e6%9c%89bootloader" class="header-anchor"&gt;&lt;/a&gt;为什么要有bootloader&#13;&#10;&lt;/h2&gt;&lt;ul&gt;&#10;&lt;li&gt;类似于电脑的boot，可以让我们很方便的去重装系统，可以用u盘重装系统，可以在线重装系统等&lt;/li&gt;&#10;&lt;li&gt;可以把一些固定参数放到bootloader里，比如屏幕亮度，初始参数等等，可以专注于APP的开发&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="前置知识"&gt;&lt;a href="#%e5%89%8d%e7%bd%ae%e7%9f%a5%e8%af%86" class="header-anchor"&gt;&lt;/a&gt;前置知识&#13;&#10;&lt;/h2&gt;&lt;h3 id="单片机组成"&gt;&lt;a href="#%e5%8d%95%e7%89%87%e6%9c%ba%e7%bb%84%e6%88%90" class="header-anchor"&gt;&lt;/a&gt;单片机组成&#13;&#10;&lt;/h3&gt;&lt;p&gt;电脑组成&#10;&lt;img class="gallery-image" data-flex-basis="415px" data-flex-grow="173" height="997" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://zeseanblog.top/p/stm32-bootloader/images/file-20250918142600489.png" srcset="https://zeseanblog.top/p/stm32-bootloader/images/file-20250918142600489_hu_482bceecae9c565.png 800w, https://zeseanblog.top/p/stm32-bootloader/images/file-20250918142600489_hu_8f8c4d3c0cccca90.png 1600w, https://zeseanblog.top/p/stm32-bootloader/images/file-20250918142600489.png 1728w" width="1728"&gt;&#10;stm32内部，与电脑组成类似，主要强调：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;片内Flash：相当于电脑硬盘&lt;/li&gt;&#10;&lt;li&gt;嵌入式 SRAM：相当与电脑内存条&#10;&lt;img class="gallery-image" data-flex-basis="467px" data-flex-grow="194" height="984" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://zeseanblog.top/p/stm32-bootloader/images/file-20250918142716567.png" srcset="https://zeseanblog.top/p/stm32-bootloader/images/file-20250918142716567_hu_a16056c0c5f19165.png 800w, https://zeseanblog.top/p/stm32-bootloader/images/file-20250918142716567_hu_7735bb6a2fdca4bc.png 1600w, https://zeseanblog.top/p/stm32-bootloader/images/file-20250918142716567.png 1915w" width="1915"&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h4 id="你需要知道"&gt;&lt;a href="#%e4%bd%a0%e9%9c%80%e8%a6%81%e7%9f%a5%e9%81%93" class="header-anchor"&gt;&lt;/a&gt;你需要知道：&#13;&#10;&lt;/h4&gt;&lt;ul&gt;&#10;&lt;li&gt;嵌入式SRAM是被映射在$0x2000 0000$上的&#10;&lt;img class="gallery-image" data-flex-basis="589px" data-flex-grow="245" height="673" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://zeseanblog.top/p/stm32-bootloader/images/file-20251007165048150.png" srcset="https://zeseanblog.top/p/stm32-bootloader/images/file-20251007165048150_hu_1aa8dcc6e31e9028.png 800w, https://zeseanblog.top/p/stm32-bootloader/images/file-20251007165048150_hu_87c3aa6ab84ed6e9.png 1600w, https://zeseanblog.top/p/stm32-bootloader/images/file-20251007165048150.png 1652w" width="1652"&gt;&lt;/li&gt;&#10;&lt;li&gt;嵌入式flash是被映射在$0x0800 0000$&#10;&lt;img class="gallery-image" data-flex-basis="364px" data-flex-grow="151" height="1195" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://zeseanblog.top/p/stm32-bootloader/images/file-20251007165329427.png" srcset="https://zeseanblog.top/p/stm32-bootloader/images/file-20251007165329427_hu_cd167254a77522ca.png 800w, https://zeseanblog.top/p/stm32-bootloader/images/file-20251007165329427_hu_15dd6764340d02d1.png 1600w, https://zeseanblog.top/p/stm32-bootloader/images/file-20251007165329427.png 1814w" width="1814"&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="stm32启动流程"&gt;&lt;a href="#stm32%e5%90%af%e5%8a%a8%e6%b5%81%e7%a8%8b" class="header-anchor"&gt;&lt;/a&gt;stm32启动流程&#13;&#10;&lt;/h3&gt;&lt;p&gt;STM32微控制器从&lt;strong&gt;上电或复位&lt;/strong&gt;到执行你编写的 &lt;strong&gt;&lt;code&gt;main()&lt;/code&gt; 函数&lt;/strong&gt; 之前，会经历一系列&lt;strong&gt;由硬件和启动代码&lt;/strong&gt;（通常是汇编语言编写的）完成的初始化步骤&lt;/p&gt;&#10;&lt;p&gt;下面是 STM32 从上电到进入 &lt;code&gt;main()&lt;/code&gt; 函数的详细过程：&lt;/p&gt;&#10;&lt;h4 id="一硬件复位与启动模式选择"&gt;&lt;a href="#%e4%b8%80%e7%a1%ac%e4%bb%b6%e5%a4%8d%e4%bd%8d%e4%b8%8e%e5%90%af%e5%8a%a8%e6%a8%a1%e5%bc%8f%e9%80%89%e6%8b%a9" class="header-anchor"&gt;&lt;/a&gt;一、硬件复位与启动模式选择&#13;&#10;&lt;/h4&gt;&lt;h5 id="1-上电复位-por-或硬件复位"&gt;&lt;a href="#1-%e4%b8%8a%e7%94%b5%e5%a4%8d%e4%bd%8d-por-%e6%88%96%e7%a1%ac%e4%bb%b6%e5%a4%8d%e4%bd%8d" class="header-anchor"&gt;&lt;/a&gt;1. &lt;strong&gt;上电复位 (POR) 或硬件复位&lt;/strong&gt;&#13;&#10;&lt;/h5&gt;&lt;p&gt;当STM32芯片&lt;strong&gt;上电&lt;/strong&gt;或者&lt;strong&gt;复位引脚&lt;/strong&gt;（NRST）被置为低电平并释放时，微控制器会进入&lt;strong&gt;复位状态&lt;/strong&gt;。在复位期间，所有内部寄存器会被设置为预设的初始值，确保系统处于一个确定的、已知的状态。&lt;/p&gt;&#10;&lt;h5 id="2-选择启动模式-boot-pins"&gt;&lt;a href="#2-%e9%80%89%e6%8b%a9%e5%90%af%e5%8a%a8%e6%a8%a1%e5%bc%8f-boot-pins" class="header-anchor"&gt;&lt;/a&gt;2. &lt;strong&gt;选择启动模式 (BOOT Pins)&lt;/strong&gt;&#13;&#10;&lt;/h5&gt;&lt;p&gt;CPU内核在复位结束后，首先会采样特定的&lt;strong&gt;启动引脚&lt;/strong&gt;（如 &lt;code&gt;BOOT0&lt;/code&gt; 和 &lt;code&gt;BOOT1&lt;/code&gt;，具体名称和数量可能因型号而异）。这些引脚的状态决定了微控制器从哪个存储器空间开始执行代码：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;主闪存启动 (Main Flash Memory)&lt;/strong&gt;：最常用的模式，运行用户编写的程序。&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;系统存储器启动 (System Memory)&lt;/strong&gt;：运行内置的 Bootloader，用于通过串口、USB等接口烧录程序。&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;SRAM 启动 (Embedded SRAM)&lt;/strong&gt;：运行驻留在SRAM中的代码，不常用。&#10;通常，在运行用户程序时，会配置为从&lt;strong&gt;主闪存&lt;/strong&gt;启动。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;hr&gt;&#10;&lt;h4 id="二cortex-m-内核的初始寻址"&gt;&lt;a href="#%e4%ba%8ccortex-m-%e5%86%85%e6%a0%b8%e7%9a%84%e5%88%9d%e5%a7%8b%e5%af%bb%e5%9d%80" class="header-anchor"&gt;&lt;/a&gt;二、Cortex-M 内核的初始寻址&#13;&#10;&lt;/h4&gt;&lt;p&gt;STM32 的核心是 &lt;strong&gt;Cortex-M&lt;/strong&gt; 系列内核（如 M0、M3、M4、M7），它们遵循一个标准化的启动流程，这部分是由硬件自动完成的。&lt;/p&gt;&#10;&lt;h5 id="3-读取中断向量表"&gt;&lt;a href="#3-%e8%af%bb%e5%8f%96%e4%b8%ad%e6%96%ad%e5%90%91%e9%87%8f%e8%a1%a8" class="header-anchor"&gt;&lt;/a&gt;3. &lt;strong&gt;读取中断向量表&lt;/strong&gt;&#13;&#10;&lt;/h5&gt;&lt;p&gt;微控制器根据启动模式，确定&lt;strong&gt;中断向量表&lt;/strong&gt;的起始地址（在主闪存启动模式下，通常是 &lt;strong&gt;&lt;code&gt;0x0800 0000&lt;/code&gt;&lt;/strong&gt;）。中断向量表是存储了各种异常和中断服务程序入口地址的表格。&lt;/p&gt;&#10;&lt;h5 id="4-初始化栈指针-sp"&gt;&lt;a href="#4-%e5%88%9d%e5%a7%8b%e5%8c%96%e6%a0%88%e6%8c%87%e9%92%88-sp" class="header-anchor"&gt;&lt;/a&gt;4. &lt;strong&gt;初始化栈指针 (SP)&lt;/strong&gt;&#13;&#10;&lt;/h5&gt;&lt;p&gt;CPU 从向量表的&lt;strong&gt;第一个 32 位字&lt;/strong&gt;（地址 &lt;code&gt;Boot_Address + 0x00&lt;/code&gt;）中读取初始的 &lt;strong&gt;主栈指针 (MSP)&lt;/strong&gt; 的值。这是程序中栈区的最高地址，用于存储局部变量、函数调用返回地址等。&lt;/p&gt;&#10;&lt;h5 id="5-设置程序计数器-pc"&gt;&lt;a href="#5-%e8%ae%be%e7%bd%ae%e7%a8%8b%e5%ba%8f%e8%ae%a1%e6%95%b0%e5%99%a8-pc" class="header-anchor"&gt;&lt;/a&gt;5. &lt;strong&gt;设置程序计数器 (PC)&lt;/strong&gt;&#13;&#10;&lt;/h5&gt;&lt;p&gt;CPU 从向量表的&lt;strong&gt;第二个 32 位字&lt;/strong&gt;（地址 &lt;code&gt;Boot_Address + 0x04&lt;/code&gt;）中读取 &lt;strong&gt;复位处理程序&lt;/strong&gt;（&lt;strong&gt;&lt;code&gt;Reset_Handler&lt;/code&gt;&lt;/strong&gt;）的地址，并将这个地址加载到 &lt;strong&gt;程序计数器 (PC)&lt;/strong&gt; 中。&#10;&lt;strong&gt;至此，硬件初始化完成，程序控制权正式移交给 &lt;code&gt;Reset_Handler&lt;/code&gt;。&lt;/strong&gt;&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h4 id="三执行启动文件代码-reset_handler"&gt;&lt;a href="#%e4%b8%89%e6%89%a7%e8%a1%8c%e5%90%af%e5%8a%a8%e6%96%87%e4%bb%b6%e4%bb%a3%e7%a0%81-reset_handler" class="header-anchor"&gt;&lt;/a&gt;三、执行启动文件代码 (&lt;code&gt;Reset_Handler&lt;/code&gt;)&#13;&#10;&lt;/h4&gt;&lt;p&gt;&lt;code&gt;Reset_Handler&lt;/code&gt; 是由编译器和芯片厂商提供的一个&lt;strong&gt;汇编语言函数&lt;/strong&gt;，通常位于项目中的 &lt;strong&gt;&lt;code&gt;startup_stm32xxx.s&lt;/code&gt;&lt;/strong&gt; 文件（即&lt;strong&gt;启动文件&lt;/strong&gt;）中。它的任务是为C/C++程序的运行准备好环境，包括内存、时钟和库函数等。&lt;/p&gt;&#10;&lt;h5 id="6-调用"&gt;&lt;a href="#6-%e8%b0%83%e7%94%a8" class="header-anchor"&gt;&lt;/a&gt;6. &lt;strong&gt;调用 &lt;code&gt;SystemInit()&lt;/code&gt;&lt;/strong&gt;&#13;&#10;&lt;/h5&gt;&lt;p&gt;&lt;code&gt;Reset_Handler&lt;/code&gt; 做的第一件大事是调用 &lt;strong&gt;&lt;code&gt;SystemInit()&lt;/code&gt;&lt;/strong&gt; 函数（通常在 &lt;code&gt;system_stm32xxx.c&lt;/code&gt; 文件中）。该函数主要完成：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;配置系统时钟&lt;/strong&gt;：设置主时钟源、PLL 倍频等，将系统时钟配置到所需的工作频率（但此时还不涉及用户外设时钟）。&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;配置存储器&lt;/strong&gt;：如设置 Flash 访问等待周期等。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h5 id="7-初始化-ram-中的数据段"&gt;&lt;a href="#7-%e5%88%9d%e5%a7%8b%e5%8c%96-ram-%e4%b8%ad%e7%9a%84%e6%95%b0%e6%8d%ae%e6%ae%b5" class="header-anchor"&gt;&lt;/a&gt;7. &lt;strong&gt;初始化 RAM 中的数据段&lt;/strong&gt;&#13;&#10;&lt;/h5&gt;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;初始化 &lt;code&gt;.data&lt;/code&gt; 段&lt;/strong&gt;：&lt;code&gt;.data&lt;/code&gt; 段存储了&lt;strong&gt;已初始化&lt;/strong&gt;的全局变量和静态变量。这些变量的初始值存储在 Flash 中，&lt;code&gt;Reset_Handler&lt;/code&gt; 会将这些值从 &lt;strong&gt;Flash 复制到 RAM&lt;/strong&gt; 中。&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;初始化 &lt;code&gt;.bss&lt;/code&gt; 段&lt;/strong&gt;：&lt;code&gt;.bss&lt;/code&gt; 段存储了&lt;strong&gt;未初始化&lt;/strong&gt;的全局变量和静态变量。C 语言标准要求它们在程序开始时必须被清零。&lt;code&gt;Reset_Handler&lt;/code&gt; 会将 &lt;code&gt;.bss&lt;/code&gt; 段在 &lt;strong&gt;RAM 中的区域清零&lt;/strong&gt;。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h5 id="8-调用-c-库初始化函数"&gt;&lt;a href="#8-%e8%b0%83%e7%94%a8-c-%e5%ba%93%e5%88%9d%e5%a7%8b%e5%8c%96%e5%87%bd%e6%95%b0" class="header-anchor"&gt;&lt;/a&gt;8. &lt;strong&gt;调用 C 库初始化函数&lt;/strong&gt;&#13;&#10;&lt;/h5&gt;&lt;p&gt;对于标准 C/C++ 环境，&lt;code&gt;Reset_Handler&lt;/code&gt; 还会调用 C 库的初始化函数（例如 &lt;code&gt;__libc_init_array&lt;/code&gt;），用于：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;初始化 C 运行时环境。&lt;/li&gt;&#10;&lt;li&gt;调用 C++ 程序的&lt;strong&gt;静态构造函数&lt;/strong&gt;（如果你的代码中有使用）。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h5 id="9-跳转到"&gt;&lt;a href="#9-%e8%b7%b3%e8%bd%ac%e5%88%b0" class="header-anchor"&gt;&lt;/a&gt;9. &lt;strong&gt;跳转到 &lt;code&gt;main()&lt;/code&gt;&lt;/strong&gt;&#13;&#10;&lt;/h5&gt;&lt;p&gt;在完成所有必要的硬件和软件环境准备后，&lt;code&gt;Reset_Handler&lt;/code&gt; 的最后一步就是执行一条跳转指令，&lt;strong&gt;调用用户编写的 &lt;code&gt;main()&lt;/code&gt; 函数&lt;/strong&gt;。&#10;代码段&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;; 假设这是 Reset_Handler 的最后几行伪汇编代码&#10;; ...&#10;BL SystemInit ; 调用系统时钟初始化函数&#10;BL __libc_init_array ; 调用 C 库初始化函数&#10;LDR R0, =main ; 将 main 函数地址加载到 R0&#10;BX R0 ; 跳转到 main 函数执行&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;至此，STM32 终于进入了你的应用程序，开始执行用户代码。&lt;/strong&gt;&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h4 id="总结流程图"&gt;&lt;a href="#%e6%80%bb%e7%bb%93%e6%b5%81%e7%a8%8b%e5%9b%be" class="header-anchor"&gt;&lt;/a&gt;总结流程图&#13;&#10;&lt;/h4&gt;&lt;ul&gt;&#10;&lt;li&gt;上电后检查boot引脚的高低决定从哪启动（在哪寻找代码）&lt;/li&gt;&#10;&lt;li&gt;读取代码(编译后的2进制文件)的第一个字(主堆栈的地址)，（工厂的工作地点）&lt;/li&gt;&#10;&lt;li&gt;寻找第二个字以运行到main();&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;步骤&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;执行者&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;动作描述&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;目标&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;硬件/CPU&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;上电复位 (POR)&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;强制所有硬件到初始状态。&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;2&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;硬件/CPU&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;检查 BOOT 引脚&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;确定代码的启动位置（如主 Flash）。&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;3&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;硬件/CPU&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;读取向量表&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;确定 SP 和 PC 的初始值。&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;4&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;硬件/CPU&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;设置初始 SP/PC&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;初始化主栈指针，跳转到 &lt;code&gt;Reset_Handler&lt;/code&gt;。&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;5&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;启动文件&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;调用 &lt;code&gt;SystemInit()&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;配置系统时钟和 Flash 等基础硬件。&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;6&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;启动文件&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;初始化数据段&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;从 Flash 复制 &lt;code&gt;.data&lt;/code&gt; 到 RAM，将 &lt;code&gt;.bss&lt;/code&gt; 段清零。&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;7&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;启动文件&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;调用库初始化&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;初始化 C 运行时环境和静态构造函数。&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;8&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;启动文件&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;调用 &lt;code&gt;main()&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;进入用户应用程序的起点&lt;/strong&gt;。&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;h4 id="问题"&gt;&lt;a href="#%e9%97%ae%e9%a2%98" class="header-anchor"&gt;&lt;/a&gt;问题：&#13;&#10;&lt;/h4&gt;&lt;h5 id="question1为什么启动后需要先找到中断向量表呢很重要吗"&gt;&lt;a href="#question1%e4%b8%ba%e4%bb%80%e4%b9%88%e5%90%af%e5%8a%a8%e5%90%8e%e9%9c%80%e8%a6%81%e5%85%88%e6%89%be%e5%88%b0%e4%b8%ad%e6%96%ad%e5%90%91%e9%87%8f%e8%a1%a8%e5%91%a2%e5%be%88%e9%87%8d%e8%a6%81%e5%90%97" class="header-anchor"&gt;&lt;/a&gt;Question1为什么启动后需要先找到中断向量表呢，很重要吗?&#13;&#10;&lt;/h5&gt;&lt;p&gt;&lt;img class="gallery-image" data-flex-basis="141px" data-flex-grow="58" height="2083" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://zeseanblog.top/p/stm32-bootloader/images/file-20251005202856356.png" srcset="https://zeseanblog.top/p/stm32-bootloader/images/file-20251005202856356_hu_fcd9f48efa3e88a4.png 800w, https://zeseanblog.top/p/stm32-bootloader/images/file-20251005202856356.png 1224w" width="1224"&gt;&lt;/p&gt;&#10;&lt;h5 id="question2堆栈是用来干嘛的为什么要先初始化它不初始化会怎么样"&gt;&lt;a href="#question2%e5%a0%86%e6%a0%88%e6%98%af%e7%94%a8%e6%9d%a5%e5%b9%b2%e5%98%9b%e7%9a%84%e4%b8%ba%e4%bb%80%e4%b9%88%e8%a6%81%e5%85%88%e5%88%9d%e5%a7%8b%e5%8c%96%e5%ae%83%e4%b8%8d%e5%88%9d%e5%a7%8b%e5%8c%96%e4%bc%9a%e6%80%8e%e4%b9%88%e6%a0%b7" class="header-anchor"&gt;&lt;/a&gt;Question2:堆栈是用来干嘛的，为什么要先初始化它，不初始化会怎么样&#13;&#10;&lt;/h5&gt;&lt;p&gt;&lt;img class="gallery-image" data-flex-basis="94px" data-flex-grow="39" height="3005" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://zeseanblog.top/p/stm32-bootloader/images/file-20251005202932179.png" srcset="https://zeseanblog.top/p/stm32-bootloader/images/file-20251005202932179_hu_22adc0851ef297df.png 800w, https://zeseanblog.top/p/stm32-bootloader/images/file-20251005202932179.png 1182w" width="1182"&gt;&lt;/p&gt;&#10;&lt;h5 id="question3为什么会映射成0x08000000呢"&gt;&lt;a href="#question3%e4%b8%ba%e4%bb%80%e4%b9%88%e4%bc%9a%e6%98%a0%e5%b0%84%e6%88%900x08000000%e5%91%a2" class="header-anchor"&gt;&lt;/a&gt;Question3：为什么会映射成0x08000000呢&#13;&#10;&lt;/h5&gt;&lt;p&gt;&lt;img class="gallery-image" data-flex-basis="86px" data-flex-grow="36" height="3289" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://zeseanblog.top/p/stm32-bootloader/images/file-20251005212337607.png" srcset="https://zeseanblog.top/p/stm32-bootloader/images/file-20251005212337607_hu_e222a6b9e0a291d9.png 800w, https://zeseanblog.top/p/stm32-bootloader/images/file-20251005212337607.png 1192w" width="1192"&gt;&lt;/p&gt;&#10;&lt;h2 id="简单跳转代码"&gt;&lt;a href="#%e7%ae%80%e5%8d%95%e8%b7%b3%e8%bd%ac%e4%bb%a3%e7%a0%81" class="header-anchor"&gt;&lt;/a&gt;简单跳转代码&#13;&#10;&lt;/h2&gt;&lt;p&gt;使用O3优化可以减小boot大小&#10;&lt;img class="gallery-image" data-flex-basis="488px" data-flex-grow="203" height="455" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://zeseanblog.top/p/stm32-bootloader/images/file-20251007185245764.png" srcset="https://zeseanblog.top/p/stm32-bootloader/images/file-20251007185245764_hu_c0851c9ad5184360.png 800w, https://zeseanblog.top/p/stm32-bootloader/images/file-20251007185245764.png 926w" width="926"&gt;&#10;fromelf &amp;ndash;bin -o &amp;ldquo;$L@L.bin&amp;rdquo; &amp;ldquo;#L&#10;&lt;img class="gallery-image" data-flex-basis="322px" data-flex-grow="134" height="691" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://zeseanblog.top/p/stm32-bootloader/images/file-20251007185024809.png" srcset="https://zeseanblog.top/p/stm32-bootloader/images/file-20251007185024809_hu_4513aa91a47f8252.png 800w, https://zeseanblog.top/p/stm32-bootloader/images/file-20251007185024809.png 928w" width="928"&gt;&lt;/p&gt;&#10;&lt;h3 id="boot"&gt;&lt;a href="#boot" class="header-anchor"&gt;&lt;/a&gt;Boot&#13;&#10;&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 2&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 3&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 4&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 5&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 6&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 7&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 8&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 9&#10;&lt;/span&gt;&lt;span class="lnt"&gt;10&#10;&lt;/span&gt;&lt;span class="lnt"&gt;11&#10;&lt;/span&gt;&lt;span class="lnt"&gt;12&#10;&lt;/span&gt;&lt;span class="lnt"&gt;13&#10;&lt;/span&gt;&lt;span class="lnt"&gt;14&#10;&lt;/span&gt;&lt;span class="lnt"&gt;15&#10;&lt;/span&gt;&lt;span class="lnt"&gt;16&#10;&lt;/span&gt;&lt;span class="lnt"&gt;17&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;typedef&lt;/span&gt; &lt;span class="nf"&gt;void&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;pFunction&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;JumpToApplication&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// 1. 检查应用程序地址是否有效&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// 应用程序的栈顶地址位于其起始地址的第一个字&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&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="n"&gt;__IO&lt;/span&gt; &lt;span class="kt"&gt;uint32_t&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;APP_ADDRESS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="mh"&gt;0x2FFE0000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mh"&gt;0x20000000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// 2. 将中断向量表重定向到应用程序地址&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;SCB&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;VTOR&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;APP_ADDRESS&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// 3. 获取应用程序的栈顶地址和复位处理函数地址&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;pFunction&lt;/span&gt; &lt;span class="n"&gt;app_reset_handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pFunction&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="n"&gt;__IO&lt;/span&gt; &lt;span class="kt"&gt;uint32_t&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;APP_ADDRESS&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;__set_MSP&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="n"&gt;__IO&lt;/span&gt; &lt;span class="kt"&gt;uint32_t&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;APP_ADDRESS&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// 4. 跳转到应用程序&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;app_reset_handler&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;h3 id="app"&gt;&lt;a href="#app" class="header-anchor"&gt;&lt;/a&gt;APP&#13;&#10;&lt;/h3&gt;&lt;p&gt;随便，但是：&#10;&lt;img class="gallery-image" data-flex-basis="335px" data-flex-grow="139" height="670" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://zeseanblog.top/p/stm32-bootloader/images/file-20251007184927290.png" srcset="https://zeseanblog.top/p/stm32-bootloader/images/file-20251007184927290_hu_fd8f4bc16e36c4c5.png 800w, https://zeseanblog.top/p/stm32-bootloader/images/file-20251007184927290.png 937w" width="937"&gt;&lt;/p&gt;&#10;&lt;h3 id="问题-1"&gt;&lt;a href="#%e9%97%ae%e9%a2%98-1" class="header-anchor"&gt;&lt;/a&gt;问题&#13;&#10;&lt;/h3&gt;&lt;h4 id="question1--scb是什么"&gt;&lt;a href="#question1--scb%e6%98%af%e4%bb%80%e4%b9%88" class="header-anchor"&gt;&lt;/a&gt;question1 SCB是什么：&#13;&#10;&lt;/h4&gt;&lt;p&gt;&lt;img class="gallery-image" data-flex-basis="114px" data-flex-grow="47" height="2534" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://zeseanblog.top/p/stm32-bootloader/images/file-20251007184755441.png" srcset="https://zeseanblog.top/p/stm32-bootloader/images/file-20251007184755441_hu_cfd30c48269d8ff.png 800w, https://zeseanblog.top/p/stm32-bootloader/images/file-20251007184755441.png 1205w" width="1205"&gt;&lt;/p&gt;&#10;&lt;h2 id="远程下载讲解"&gt;&lt;a href="#%e8%bf%9c%e7%a8%8b%e4%b8%8b%e8%bd%bd%e8%ae%b2%e8%a7%a3" class="header-anchor"&gt;&lt;/a&gt;远程下载讲解&#13;&#10;&lt;/h2&gt;&lt;p&gt;注意事项：HC-05的VCC接3V3，实测5V无法使用&#10;蓝牙模块只有在通信状态才会显示已连接蓝牙，打不开蓝牙串口参考：&lt;a class="link" href="https://blog.csdn.net/Argon_Ghost/article/details/121734788" target="_blank" rel="noopener"&#13;&#10; &gt;HC05和电脑蓝牙通讯&lt;/a&gt;&#10;HC05-python-stm32的波特率必须都设置为同样的速率，我这里为9600&lt;/p&gt;&#10;&lt;h3 id="蓝牙模块讲解"&gt;&lt;a href="#%e8%93%9d%e7%89%99%e6%a8%a1%e5%9d%97%e8%ae%b2%e8%a7%a3" class="header-anchor"&gt;&lt;/a&gt;蓝牙模块讲解&#13;&#10;&lt;/h3&gt;&lt;p&gt;总结：当成远程串口用就行&lt;/p&gt;&#10;&lt;h4 id="常用at指令"&gt;&lt;a href="#%e5%b8%b8%e7%94%a8at%e6%8c%87%e4%bb%a4" class="header-anchor"&gt;&lt;/a&gt;常用AT指令&#13;&#10;&lt;/h4&gt;&lt;p&gt;波特率为38400&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;指令&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;返回&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;意义&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;AT&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;ok&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;检测固件是否可用&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;AT+RESET&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;ok&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;模块软件复位&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;AT+VERSION?&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;+VERSION:&amp;lt;Param&amp;gt; OK&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;获取固件版本&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;AT+NAME=name&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;ok&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;设置模块名字&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;AT+NAME?&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;+NAME:&amp;lt;Param&amp;gt; OK&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;返回模块名字&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;AT+PSWD=&amp;ldquo;passwd&amp;rdquo;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;OK&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;设置模块密码&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;AT+PSWD?&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;+PSWD:&amp;lt;Param&amp;gt; OK&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;获取模块密码&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;AT+UART=115200,0,0&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;ok&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;设置通信时的波特率&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;AT+UART?&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;+UART:&amp;lt;Param1&amp;gt;,&amp;lt;Param2&amp;gt;,&amp;lt;Param3&amp;gt;&lt;br&gt;OK&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;获取串口参数&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;h4 id="模块指示灯标志"&gt;&lt;a href="#%e6%a8%a1%e5%9d%97%e6%8c%87%e7%a4%ba%e7%81%af%e6%a0%87%e5%bf%97" class="header-anchor"&gt;&lt;/a&gt;模块指示灯标志&#13;&#10;&lt;/h4&gt;&lt;ul&gt;&#10;&lt;li&gt;定时闪两下：已经连接蓝牙，处于通信状态&lt;/li&gt;&#10;&lt;li&gt;快闪：未连接&lt;/li&gt;&#10;&lt;li&gt;慢闪：工作模式，可使用AT指令调整参数（进入：保持按键按下，通电后松开按键即可）&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h4 id="测试流程"&gt;&lt;a href="#%e6%b5%8b%e8%af%95%e6%b5%81%e7%a8%8b" class="header-anchor"&gt;&lt;/a&gt;测试流程&#13;&#10;&lt;/h4&gt;&lt;p&gt;&lt;img class="gallery-image" data-flex-basis="405px" data-flex-grow="169" height="581" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://zeseanblog.top/p/stm32-bootloader/images/file-20251011201852607.png" srcset="https://zeseanblog.top/p/stm32-bootloader/images/file-20251011201852607_hu_cd8f7eb3d5f1e9f9.png 800w, https://zeseanblog.top/p/stm32-bootloader/images/file-20251011201852607.png 982w" width="982"&gt;&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;先进入工作模式，使用&lt;code&gt;AT&lt;/code&gt;检查是否可用，&lt;/li&gt;&#10;&lt;li&gt;设置模块名字：&lt;code&gt;AT+NAME=MyBluetooth&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;获取密码：&lt;code&gt;AT+PSWD?&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;查看波特率：&lt;code&gt;AT+UART?&lt;/code&gt;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h3 id="嵌入式flash操作"&gt;&lt;a href="#%e5%b5%8c%e5%85%a5%e5%bc%8fflash%e6%93%8d%e4%bd%9c" class="header-anchor"&gt;&lt;/a&gt;嵌入式Flash操作&#13;&#10;&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 2&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 3&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 4&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 5&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 6&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 7&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 8&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 9&#10;&lt;/span&gt;&lt;span class="lnt"&gt;10&#10;&lt;/span&gt;&lt;span class="lnt"&gt;11&#10;&lt;/span&gt;&lt;span class="lnt"&gt;12&#10;&lt;/span&gt;&lt;span class="lnt"&gt;13&#10;&lt;/span&gt;&lt;span class="lnt"&gt;14&#10;&lt;/span&gt;&lt;span class="lnt"&gt;15&#10;&lt;/span&gt;&lt;span class="lnt"&gt;16&#10;&lt;/span&gt;&lt;span class="lnt"&gt;17&#10;&lt;/span&gt;&lt;span class="lnt"&gt;18&#10;&lt;/span&gt;&lt;span class="lnt"&gt;19&#10;&lt;/span&gt;&lt;span class="lnt"&gt;20&#10;&lt;/span&gt;&lt;span class="lnt"&gt;21&#10;&lt;/span&gt;&lt;span class="lnt"&gt;22&#10;&lt;/span&gt;&lt;span class="lnt"&gt;23&#10;&lt;/span&gt;&lt;span class="lnt"&gt;24&#10;&lt;/span&gt;&lt;span class="lnt"&gt;25&#10;&lt;/span&gt;&lt;span class="lnt"&gt;26&#10;&lt;/span&gt;&lt;span class="lnt"&gt;27&#10;&lt;/span&gt;&lt;span class="lnt"&gt;28&#10;&lt;/span&gt;&lt;span class="lnt"&gt;29&#10;&lt;/span&gt;&lt;span class="lnt"&gt;30&#10;&lt;/span&gt;&lt;span class="lnt"&gt;31&#10;&lt;/span&gt;&lt;span class="lnt"&gt;32&#10;&lt;/span&gt;&lt;span class="lnt"&gt;33&#10;&lt;/span&gt;&lt;span class="lnt"&gt;34&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-C" data-lang="C"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Flash_Erase_App&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;FLASH_EraseInitTypeDef&lt;/span&gt; &lt;span class="n"&gt;EraseInit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;uint32_t&lt;/span&gt; &lt;span class="n"&gt;PageError&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// 解除Flash保护&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;HAL_FLASH_Unlock&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// F407: sector2(扇区2) = 0x08008000 开始&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;EraseInit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TypeErase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FLASH_TYPEERASE_SECTORS&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;EraseInit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sector&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FLASH_SECTOR_2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;EraseInit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NbSectors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 到 sector7(扇区7)&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;EraseInit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;VoltageRange&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FLASH_VOLTAGE_RANGE_3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;HAL_FLASHEx_Erase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;EraseInit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;PageError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;HAL_OK&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// 错误处理&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// 开启保护&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;HAL_FLASH_Lock&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;HAL_StatusTypeDef&lt;/span&gt; &lt;span class="nf"&gt;Flash_Write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uint32_t&lt;/span&gt; &lt;span class="n"&gt;addr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;uint32_t&lt;/span&gt; &lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;HAL_StatusTypeDef&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;HAL_FLASH_Unlock&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uint32_t&lt;/span&gt; &lt;span class="n"&gt;i&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;i&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;uint32_t&lt;/span&gt; &lt;span class="n"&gt;word&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uint32_t&lt;/span&gt;&lt;span class="o"&gt;*&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;i&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;HAL_FLASH_Program&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FLASH_TYPEPROGRAM_WORD&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;addr&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;HAL_OK&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;HAL_FLASH_Lock&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;h3 id="主要逻辑"&gt;&lt;a href="#%e4%b8%bb%e8%a6%81%e9%80%bb%e8%be%91" class="header-anchor"&gt;&lt;/a&gt;主要逻辑&#13;&#10;&lt;/h3&gt;&lt;p&gt;&lt;img class="gallery-image" data-flex-basis="765px" data-flex-grow="318" height="529" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://zeseanblog.top/p/stm32-bootloader/images/file-20251018141930738.png" srcset="https://zeseanblog.top/p/stm32-bootloader/images/file-20251018141930738_hu_54c76ab6edaf17da.png 800w, https://zeseanblog.top/p/stm32-bootloader/images/file-20251018141930738_hu_178f00e71dce5d6.png 1600w, https://zeseanblog.top/p/stm32-bootloader/images/file-20251018141930738.png 1687w" width="1687"&gt;&lt;/p&gt;&#10;&lt;h3 id="固件发送与接收"&gt;&lt;a href="#%e5%9b%ba%e4%bb%b6%e5%8f%91%e9%80%81%e4%b8%8e%e6%8e%a5%e6%94%b6" class="header-anchor"&gt;&lt;/a&gt;固件发送与接收&#13;&#10;&lt;/h3&gt;&lt;p&gt;&lt;img class="gallery-image" data-flex-basis="438px" data-flex-grow="182" height="828" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://zeseanblog.top/p/stm32-bootloader/images/file-20251011211739002.png" srcset="https://zeseanblog.top/p/stm32-bootloader/images/file-20251011211739002_hu_a388e10cc98e4c16.png 800w, https://zeseanblog.top/p/stm32-bootloader/images/file-20251011211739002.png 1514w" width="1514"&gt;&lt;/p&gt;&#10;&lt;h4 id="bootloader代码逻辑"&gt;&lt;a href="#bootloader%e4%bb%a3%e7%a0%81%e9%80%bb%e8%be%91" class="header-anchor"&gt;&lt;/a&gt;Bootloader代码逻辑&#13;&#10;&lt;/h4&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 2&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 3&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 4&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 5&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 6&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 7&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 8&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 9&#10;&lt;/span&gt;&lt;span class="lnt"&gt;10&#10;&lt;/span&gt;&lt;span class="lnt"&gt;11&#10;&lt;/span&gt;&lt;span class="lnt"&gt;12&#10;&lt;/span&gt;&lt;span class="lnt"&gt;13&#10;&lt;/span&gt;&lt;span class="lnt"&gt;14&#10;&lt;/span&gt;&lt;span class="lnt"&gt;15&#10;&lt;/span&gt;&lt;span class="lnt"&gt;16&#10;&lt;/span&gt;&lt;span class="lnt"&gt;17&#10;&lt;/span&gt;&lt;span class="lnt"&gt;18&#10;&lt;/span&gt;&lt;span class="lnt"&gt;19&#10;&lt;/span&gt;&lt;span class="lnt"&gt;20&#10;&lt;/span&gt;&lt;span class="lnt"&gt;21&#10;&lt;/span&gt;&lt;span class="lnt"&gt;22&#10;&lt;/span&gt;&lt;span class="lnt"&gt;23&#10;&lt;/span&gt;&lt;span class="lnt"&gt;24&#10;&lt;/span&gt;&lt;span class="lnt"&gt;25&#10;&lt;/span&gt;&lt;span class="lnt"&gt;26&#10;&lt;/span&gt;&lt;span class="lnt"&gt;27&#10;&lt;/span&gt;&lt;span class="lnt"&gt;28&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Bootloader_Update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;256&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;uint32_t&lt;/span&gt; &lt;span class="n"&gt;addr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;APP_ADDR&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;Flash_Erase_App&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;HAL_UART_Receive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;huart1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HAL_MAX_DELAY&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;CMD_DATA&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;uint16_t&lt;/span&gt; &lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;HAL_UART_Receive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;huart1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uint8_t&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;len&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="n"&gt;HAL_MAX_DELAY&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;HAL_UART_Receive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;huart1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HAL_MAX_DELAY&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;Flash_Write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;addr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;addr&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;HAL_UART_Transmit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;huart1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uint8_t&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;OK&amp;#34;&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="n"&gt;HAL_MAX_DELAY&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;CMD_END&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;HAL_UART_Transmit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;huart1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uint8_t&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;DONE&amp;#34;&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;HAL_MAX_DELAY&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;h4 id="电脑_python发送"&gt;&lt;a href="#%e7%94%b5%e8%84%91_python%e5%8f%91%e9%80%81" class="header-anchor"&gt;&lt;/a&gt;电脑_python&amp;ndash;发送&#13;&#10;&lt;/h4&gt;&lt;p&gt;python要先安装pyserial跑：&lt;code&gt;pip install pyserial&lt;/code&gt;&#10;固件采用分批的形式发送，使用python脚本进行发送&#10;&lt;code&gt;ser = serial.Serial(&amp;quot;COM4&amp;quot;, 9600)&lt;/code&gt;这里的COM4需要改成你的com端口&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 2&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 3&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 4&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 5&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 6&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 7&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 8&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 9&#10;&lt;/span&gt;&lt;span class="lnt"&gt;10&#10;&lt;/span&gt;&lt;span class="lnt"&gt;11&#10;&lt;/span&gt;&lt;span class="lnt"&gt;12&#10;&lt;/span&gt;&lt;span class="lnt"&gt;13&#10;&lt;/span&gt;&lt;span class="lnt"&gt;14&#10;&lt;/span&gt;&lt;span class="lnt"&gt;15&#10;&lt;/span&gt;&lt;span class="lnt"&gt;16&#10;&lt;/span&gt;&lt;span class="lnt"&gt;17&#10;&lt;/span&gt;&lt;span class="lnt"&gt;18&#10;&lt;/span&gt;&lt;span class="lnt"&gt;19&#10;&lt;/span&gt;&lt;span class="lnt"&gt;20&#10;&lt;/span&gt;&lt;span class="lnt"&gt;21&#10;&lt;/span&gt;&lt;span class="lnt"&gt;22&#10;&lt;/span&gt;&lt;span class="lnt"&gt;23&#10;&lt;/span&gt;&lt;span class="lnt"&gt;24&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;serial&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;struct&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 初始化一个串口对象&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;ser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;serial&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;COM4&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;9600&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;send_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;ser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="se"&gt;\x02&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# CMD_DATA&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# 把当前数据块的长度打包成两个字节的无符号整数（小端序）。&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# 比如 len(data) = 256，那么结果是 b&amp;#39;\x00\x01&amp;#39;，开发板端可以据此知道这包数据有多长。&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;ser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;struct&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&amp;lt;H&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;ser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# 阻塞读取开发板发送的信息，确保是否传递成功&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&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;# OK&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;app.bin&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;rb&amp;#34;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# 每次循环读取256个数据&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;chunk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;256&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;break&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;send_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;ser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="se"&gt;\x03&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# CMD_END&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&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="c1"&gt;# DONE&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;h4 id="stm32_蓝牙接收"&gt;&lt;a href="#stm32_%e8%93%9d%e7%89%99%e6%8e%a5%e6%94%b6" class="header-anchor"&gt;&lt;/a&gt;stm32_蓝牙&amp;ndash;接收&#13;&#10;&lt;/h4&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 2&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 3&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 4&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 5&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 6&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 7&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 8&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 9&#10;&lt;/span&gt;&lt;span class="lnt"&gt;10&#10;&lt;/span&gt;&lt;span class="lnt"&gt;11&#10;&lt;/span&gt;&lt;span class="lnt"&gt;12&#10;&lt;/span&gt;&lt;span class="lnt"&gt;13&#10;&lt;/span&gt;&lt;span class="lnt"&gt;14&#10;&lt;/span&gt;&lt;span class="lnt"&gt;15&#10;&lt;/span&gt;&lt;span class="lnt"&gt;16&#10;&lt;/span&gt;&lt;span class="lnt"&gt;17&#10;&lt;/span&gt;&lt;span class="lnt"&gt;18&#10;&lt;/span&gt;&lt;span class="lnt"&gt;19&#10;&lt;/span&gt;&lt;span class="lnt"&gt;20&#10;&lt;/span&gt;&lt;span class="lnt"&gt;21&#10;&lt;/span&gt;&lt;span class="lnt"&gt;22&#10;&lt;/span&gt;&lt;span class="lnt"&gt;23&#10;&lt;/span&gt;&lt;span class="lnt"&gt;24&#10;&lt;/span&gt;&lt;span class="lnt"&gt;25&#10;&lt;/span&gt;&lt;span class="lnt"&gt;26&#10;&lt;/span&gt;&lt;span class="lnt"&gt;27&#10;&lt;/span&gt;&lt;span class="lnt"&gt;28&#10;&lt;/span&gt;&lt;span class="lnt"&gt;29&#10;&lt;/span&gt;&lt;span class="lnt"&gt;30&#10;&lt;/span&gt;&lt;span class="lnt"&gt;31&#10;&lt;/span&gt;&lt;span class="lnt"&gt;32&#10;&lt;/span&gt;&lt;span class="lnt"&gt;33&#10;&lt;/span&gt;&lt;span class="lnt"&gt;34&#10;&lt;/span&gt;&lt;span class="lnt"&gt;35&#10;&lt;/span&gt;&lt;span class="lnt"&gt;36&#10;&lt;/span&gt;&lt;span class="lnt"&gt;37&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-C" data-lang="C"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#define APP_ADDR 0x08008000&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#define CMD_START 0x01&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#define CMD_DATA 0x02&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#define CMD_END 0x03&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Bootloader_Update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;256&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;uint32_t&lt;/span&gt; &lt;span class="n"&gt;addr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;APP_ADDR&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// 擦除老APP,准备迎接新王&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;Flash_Erase_App&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// 堵塞接收，根据先接收到的命令决定怎么做&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;HAL_UART_Receive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;huart1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HAL_MAX_DELAY&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// 数据命令，表示接收到的是数据&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;CMD_DATA&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;uint16_t&lt;/span&gt; &lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// 获取长度&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;HAL_UART_Receive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;huart1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uint8_t&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;len&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="n"&gt;HAL_MAX_DELAY&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// 获取内容&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;HAL_UART_Receive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;huart1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HAL_MAX_DELAY&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// 写入内容&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;Flash_Write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;addr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// 位移地址，准备写入后续内容&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;addr&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// 回应OK&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;HAL_UART_Transmit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;huart1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uint8_t&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;OK&amp;#34;&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="n"&gt;HAL_MAX_DELAY&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// 结束命令，表示接收完成，退出该函数&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;CMD_END&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;HAL_UART_Transmit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;huart1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uint8_t&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;DONE&amp;#34;&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;HAL_MAX_DELAY&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;h3 id="增加校验-crc32"&gt;&lt;a href="#%e5%a2%9e%e5%8a%a0%e6%a0%a1%e9%aa%8c-crc32" class="header-anchor"&gt;&lt;/a&gt;增加校验-CRC32&#13;&#10;&lt;/h3&gt;&lt;h4 id="一crc32-是什么"&gt;&lt;a href="#%e4%b8%80crc32-%e6%98%af%e4%bb%80%e4%b9%88" class="header-anchor"&gt;&lt;/a&gt;一、CRC32 是什么&#13;&#10;&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;CRC&lt;/strong&gt; 全称 &lt;strong&gt;Cyclic Redundancy Check（循环冗余校验）&lt;/strong&gt;。&lt;br&gt;&#10;&lt;code&gt;CRC32&lt;/code&gt; 表示它使用的是 &lt;strong&gt;32位的多项式运算结果&lt;/strong&gt;。&#10;它不是“加法求和”那种简单校验，而是用一种&lt;strong&gt;二进制多项式除法&lt;/strong&gt;的思想：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;把整个数据看作一个很长的二进制数；&lt;/li&gt;&#10;&lt;li&gt;用一个固定的“生成多项式”去做模2除法；&lt;/li&gt;&#10;&lt;li&gt;最后得到一个 32 位的余数；&lt;/li&gt;&#10;&lt;li&gt;这个余数就是 &lt;strong&gt;CRC 校验码&lt;/strong&gt;。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&#13;&#10; &lt;blockquote&gt;&#13;&#10; &lt;p&gt;🧠 模2除法：相当于普通除法，但所有加减都用“异或（XOR）”。&lt;/p&gt;&#10;&#13;&#10; &lt;/blockquote&gt;&#13;&#10;&lt;h4 id="二crc32-的作用"&gt;&lt;a href="#%e4%ba%8ccrc32-%e7%9a%84%e4%bd%9c%e7%94%a8" class="header-anchor"&gt;&lt;/a&gt;二、CRC32 的作用&#13;&#10;&lt;/h4&gt;&lt;p&gt;CRC32 不是用来“防黑客”或“加密”的，而是为了 &lt;strong&gt;检测传输或存储过程中的错误&lt;/strong&gt;。&#10;例如：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;你通过串口发送 100KB 的固件；&lt;/li&gt;&#10;&lt;li&gt;中途某一字节丢了、翻转了；&lt;/li&gt;&#10;&lt;li&gt;STM32 收到的文件计算出的 CRC 不等于原始文件的 CRC；&lt;/li&gt;&#10;&lt;li&gt;→ 说明传输错误，不执行更新。&#10;可以检测出几乎所有单比特错误、突发错误，非常可靠。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h4 id="电脑端-python增加校验"&gt;&lt;a href="#%e7%94%b5%e8%84%91%e7%ab%af-python%e5%a2%9e%e5%8a%a0%e6%a0%a1%e9%aa%8c" class="header-anchor"&gt;&lt;/a&gt;电脑端-python增加校验&#13;&#10;&lt;/h4&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 2&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 3&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 4&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 5&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 6&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 7&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 8&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 9&#10;&lt;/span&gt;&lt;span class="lnt"&gt;10&#10;&lt;/span&gt;&lt;span class="lnt"&gt;11&#10;&lt;/span&gt;&lt;span class="lnt"&gt;12&#10;&lt;/span&gt;&lt;span class="lnt"&gt;13&#10;&lt;/span&gt;&lt;span class="lnt"&gt;14&#10;&lt;/span&gt;&lt;span class="lnt"&gt;15&#10;&lt;/span&gt;&lt;span class="lnt"&gt;16&#10;&lt;/span&gt;&lt;span class="lnt"&gt;17&#10;&lt;/span&gt;&lt;span class="lnt"&gt;18&#10;&lt;/span&gt;&lt;span class="lnt"&gt;19&#10;&lt;/span&gt;&lt;span class="lnt"&gt;20&#10;&lt;/span&gt;&lt;span class="lnt"&gt;21&#10;&lt;/span&gt;&lt;span class="lnt"&gt;22&#10;&lt;/span&gt;&lt;span class="lnt"&gt;23&#10;&lt;/span&gt;&lt;span class="lnt"&gt;24&#10;&lt;/span&gt;&lt;span class="lnt"&gt;25&#10;&lt;/span&gt;&lt;span class="lnt"&gt;26&#10;&lt;/span&gt;&lt;span class="lnt"&gt;27&#10;&lt;/span&gt;&lt;span class="lnt"&gt;28&#10;&lt;/span&gt;&lt;span class="lnt"&gt;29&#10;&lt;/span&gt;&lt;span class="lnt"&gt;30&#10;&lt;/span&gt;&lt;span class="lnt"&gt;31&#10;&lt;/span&gt;&lt;span class="lnt"&gt;32&#10;&lt;/span&gt;&lt;span class="lnt"&gt;33&#10;&lt;/span&gt;&lt;span class="lnt"&gt;34&#10;&lt;/span&gt;&lt;span class="lnt"&gt;35&#10;&lt;/span&gt;&lt;span class="lnt"&gt;36&#10;&lt;/span&gt;&lt;span class="lnt"&gt;37&#10;&lt;/span&gt;&lt;span class="lnt"&gt;38&#10;&lt;/span&gt;&lt;span class="lnt"&gt;39&#10;&lt;/span&gt;&lt;span class="lnt"&gt;40&#10;&lt;/span&gt;&lt;span class="lnt"&gt;41&#10;&lt;/span&gt;&lt;span class="lnt"&gt;42&#10;&lt;/span&gt;&lt;span class="lnt"&gt;43&#10;&lt;/span&gt;&lt;span class="lnt"&gt;44&#10;&lt;/span&gt;&lt;span class="lnt"&gt;45&#10;&lt;/span&gt;&lt;span class="lnt"&gt;46&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-C" data-lang="C"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;import&lt;/span&gt; &lt;span class="n"&gt;serial&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;import&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;import&lt;/span&gt; &lt;span class="n"&gt;binascii&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="n"&gt;tqdm&lt;/span&gt; &lt;span class="n"&gt;import&lt;/span&gt; &lt;span class="n"&gt;tqdm&lt;/span&gt; &lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="n"&gt;pip&lt;/span&gt; &lt;span class="n"&gt;install&lt;/span&gt; &lt;span class="n"&gt;tqdm&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;# 打开串口&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;ser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;COM4&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;9600&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;send_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="cp"&gt;# 发送数据包&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;ser&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;b&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;&lt;/span&gt;&lt;span class="se"&gt;\x02&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="n"&gt;CMD_DATA&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;ser&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="k"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;&amp;lt;H&amp;#34;&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;data&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt; &lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="err"&gt;长度&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;ser&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;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="err"&gt;数据本体&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ser&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="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="n"&gt;Bootloader&lt;/span&gt; &lt;span class="err"&gt;回复&lt;/span&gt; &lt;span class="n"&gt;OK&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;OK&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;raise&lt;/span&gt; &lt;span class="nf"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Bootloader写入失败，返回: %s&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;send_end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;crc32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;total_size&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="cp"&gt;# 通知 Bootloader 固件结束 + CRC&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;ser&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;b&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;&lt;/span&gt;&lt;span class="se"&gt;\x03&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="n"&gt;CMD_END&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;ser&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="k"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;&amp;lt;I&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;crc32&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="err"&gt;字节&lt;/span&gt; &lt;span class="n"&gt;CRC32&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;ser&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="k"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;&amp;lt;I&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;total_size&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="err"&gt;固件总长度（可选）&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ser&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="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;DONE&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;raise&lt;/span&gt; &lt;span class="nf"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Bootloader校验失败，返回: %s&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;# ---------------- 主流程 -----------------&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&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="s"&gt;&amp;#34;app.bin&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;rb&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;as&lt;/span&gt; &lt;span class="nl"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;# 计算 CRC32（和 Bootloader 保持一致）&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;crc32&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;binascii&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;crc32&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="mh"&gt;0xFFFFFFFF&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;total_size&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;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;固件大小: {total_size} 字节, CRC32 = 0x{crc32:08X}&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;# 分块发送（带进度条）&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;chunk_size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;256&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;tqdm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;total_size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;unit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;B&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;unit_scale&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;desc&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;升级中&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;as&lt;/span&gt; &lt;span class="nl"&gt;pbar&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&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;total_size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chunk_size&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;chunk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nl"&gt;i&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;chunk_size&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;send_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;pbar&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&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;chunk&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;# 通知结束&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;send_end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;crc32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;total_size&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;固件升级完成 ✅&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;h4 id="stm32端-增加校验"&gt;&lt;a href="#stm32%e7%ab%af-%e5%a2%9e%e5%8a%a0%e6%a0%a1%e9%aa%8c" class="header-anchor"&gt;&lt;/a&gt;stm32端-增加校验&#13;&#10;&lt;/h4&gt;&lt;p&gt;在CRC校验算法中，数据被视为一个巨大的二进制数，代表一个多项式的系数。生成多项式（&lt;code&gt;CRC32_POLY&lt;/code&gt;）就是用来与这个数据多项式进行“模2除法”运算的除数，最终得到的余数就是CRC校验值。&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 2&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 3&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 4&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 5&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 6&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 7&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 8&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 9&#10;&lt;/span&gt;&lt;span class="lnt"&gt;10&#10;&lt;/span&gt;&lt;span class="lnt"&gt;11&#10;&lt;/span&gt;&lt;span class="lnt"&gt;12&#10;&lt;/span&gt;&lt;span class="lnt"&gt;13&#10;&lt;/span&gt;&lt;span class="lnt"&gt;14&#10;&lt;/span&gt;&lt;span class="lnt"&gt;15&#10;&lt;/span&gt;&lt;span class="lnt"&gt;16&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-C" data-lang="C"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// 多项式 (Ethernet, ZIP, etc. 通用): 0x04C11DB7&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#define CRC32_POLY 0xEDB88320UL&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;uint32_t&lt;/span&gt; &lt;span class="nf"&gt;crc32_calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;uint32_t&lt;/span&gt; &lt;span class="n"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;uint32_t&lt;/span&gt; &lt;span class="n"&gt;crc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mh"&gt;0xFFFFFFFFUL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uint32_t&lt;/span&gt; &lt;span class="n"&gt;i&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;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;crc&lt;/span&gt; &lt;span class="o"&gt;^=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="n"&gt;j&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;j&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;crc&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;crc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;crc&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt; &lt;span class="n"&gt;CRC32_POLY&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;crc&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="n"&gt;crc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;bootloader逻辑改变&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 2&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 3&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 4&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 5&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 6&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 7&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 8&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 9&#10;&lt;/span&gt;&lt;span class="lnt"&gt;10&#10;&lt;/span&gt;&lt;span class="lnt"&gt;11&#10;&lt;/span&gt;&lt;span class="lnt"&gt;12&#10;&lt;/span&gt;&lt;span class="lnt"&gt;13&#10;&lt;/span&gt;&lt;span class="lnt"&gt;14&#10;&lt;/span&gt;&lt;span class="lnt"&gt;15&#10;&lt;/span&gt;&lt;span class="lnt"&gt;16&#10;&lt;/span&gt;&lt;span class="lnt"&gt;17&#10;&lt;/span&gt;&lt;span class="lnt"&gt;18&#10;&lt;/span&gt;&lt;span class="lnt"&gt;19&#10;&lt;/span&gt;&lt;span class="lnt"&gt;20&#10;&lt;/span&gt;&lt;span class="lnt"&gt;21&#10;&lt;/span&gt;&lt;span class="lnt"&gt;22&#10;&lt;/span&gt;&lt;span class="lnt"&gt;23&#10;&lt;/span&gt;&lt;span class="lnt"&gt;24&#10;&lt;/span&gt;&lt;span class="lnt"&gt;25&#10;&lt;/span&gt;&lt;span class="lnt"&gt;26&#10;&lt;/span&gt;&lt;span class="lnt"&gt;27&#10;&lt;/span&gt;&lt;span class="lnt"&gt;28&#10;&lt;/span&gt;&lt;span class="lnt"&gt;29&#10;&lt;/span&gt;&lt;span class="lnt"&gt;30&#10;&lt;/span&gt;&lt;span class="lnt"&gt;31&#10;&lt;/span&gt;&lt;span class="lnt"&gt;32&#10;&lt;/span&gt;&lt;span class="lnt"&gt;33&#10;&lt;/span&gt;&lt;span class="lnt"&gt;34&#10;&lt;/span&gt;&lt;span class="lnt"&gt;35&#10;&lt;/span&gt;&lt;span class="lnt"&gt;36&#10;&lt;/span&gt;&lt;span class="lnt"&gt;37&#10;&lt;/span&gt;&lt;span class="lnt"&gt;38&#10;&lt;/span&gt;&lt;span class="lnt"&gt;39&#10;&lt;/span&gt;&lt;span class="lnt"&gt;40&#10;&lt;/span&gt;&lt;span class="lnt"&gt;41&#10;&lt;/span&gt;&lt;span class="lnt"&gt;42&#10;&lt;/span&gt;&lt;span class="lnt"&gt;43&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-C" data-lang="C"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#define APP_ADDR 0x08008000&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#define CMD_START 0x01&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#define CMD_DATA 0x02&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#define CMD_END 0x03&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Bootloader_Update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;256&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;uint32_t&lt;/span&gt; &lt;span class="n"&gt;addr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;APP_ADDR&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;Flash_Erase_App&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;HAL_UART_Receive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;huart1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HAL_MAX_DELAY&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;CMD_DATA&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;uint16_t&lt;/span&gt; &lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;HAL_UART_Receive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;huart1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uint8_t&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;len&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="n"&gt;HAL_MAX_DELAY&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;HAL_UART_Receive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;huart1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HAL_MAX_DELAY&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;Flash_Write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;addr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;addr&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;HAL_UART_Transmit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;huart1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uint8_t&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;OK&amp;#34;&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="n"&gt;HAL_MAX_DELAY&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;CMD_END&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;uint32_t&lt;/span&gt; &lt;span class="n"&gt;host_crc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;host_size&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&#9;&lt;span class="nf"&gt;HAL_UART_Receive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;huart1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uint8_t&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;host_crc&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;HAL_MAX_DELAY&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&#9;&lt;span class="nf"&gt;HAL_UART_Receive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;huart1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uint8_t&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;host_size&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;HAL_MAX_DELAY&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&#9;&lt;span class="c1"&gt;// 自己计算 CRC32&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&#9;&lt;span class="kt"&gt;uint32_t&lt;/span&gt; &lt;span class="n"&gt;calc_crc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;crc32_calc&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="kt"&gt;uint8_t&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;APP_ADDR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;host_size&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&#9;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;calc_crc&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;host_crc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&#9;&#9;&lt;span class="nf"&gt;HAL_UART_Transmit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;huart1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uint8_t&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;DONE&amp;#34;&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;HAL_MAX_DELAY&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&#9;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&#9;&#9;&lt;span class="nf"&gt;HAL_UART_Transmit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;huart1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uint8_t&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;FAIL&amp;#34;&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;HAL_MAX_DELAY&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&#9;&lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;</description></item></channel></rss>