STM32 HAL 库串口 DMA + 空闲中断接收不定长数据:从原理到量产级稳定方案

文章目录

    • 一、为什么串口接收会成为一个问题
    • 二、空闲中断(IDLE)到底是怎么触发的
    • [三、为什么选 DMA + IDLE,而不是别的组合](#三、为什么选 DMA + IDLE,而不是别的组合)
    • [四、CubeMX 配置:三处不能漏](#四、CubeMX 配置:三处不能漏)
    • 五、核心代码:把整条链路跑起来
      • [5.1 变量与初始化](#5.1 变量与初始化)
      • [5.2 中断服务函数](#5.2 中断服务函数)
      • [5.3 主循环消费数据](#5.3 主循环消费数据)
    • 六、三个我踩过的坑(失败路径记录)
      • [坑一:只使能了 IDLE,却没启动 DMA → 永远收不到数据](#坑一:只使能了 IDLE,却没启动 DMA → 永远收不到数据)
      • [坑二:高波特率连续发数据,串口"卡死"再也收不到 → ORE 溢出](#坑二:高波特率连续发数据,串口"卡死"再也收不到 → ORE 溢出)
      • [坑三:上电后偶发"首帧丢失" → 帧错误 FE 挂起中断](#坑三:上电后偶发"首帧丢失" → 帧错误 FE 挂起中断)
    • [七、实测数据:DMA + IDLE 到底快在哪里](#七、实测数据:DMA + IDLE 到底快在哪里)
      • [理论 vs 实测对照](#理论 vs 实测对照)
    • [八、故障排查速查表(4 类高频问题)](#八、故障排查速查表(4 类高频问题))
    • 九、总结
    • 参考资料

摘要:串口是嵌入式最常用的外设,但传统"逐字节中断"和"定长 DMA"在接收可变长度数据时要么频繁打断 CPU、要么受固定包长限制。本文基于 STM32F407ZGT6 + HAL 库,讲解如何用「DMA + 空闲中断(IDLE)」实现不定长数据的零丢失接收,并重点剖析 ORE 溢出、上电帧错误(FE)、数据粘包三类隐蔽故障的根因与修复。实测:115200 波特率下接收 1KB 帧仅 1 次中断,CPU 占用从逐字节中断的 68% 降到 6.2%,连续 50 万帧零丢包。提供完整接线、CubeMX 配置、工程级代码与排查清单。

一、为什么串口接收会成为一个问题

做过项目的朋友应该都有体会:串口本身不难,难的是接收端。发送可以阻塞、可以等,但接收是异步的------数据什么时候来、一次来多少,MCU 完全不知道。

最常见的三种做法,各有各的硬伤:

方式 优点 致命问题
轮询 HAL_UART_Receive 代码简单 数据不定时到达时,CPU 被死死占住,其他任务全被拖垮
逐字节中断 HAL_UART_Receive_IT 响应及时 每收到 1 字节就进一次中断,HAL 库里中断上下文开销大,高波特率下 CPU 被打满
定长 DMA CPU 开销最低 必须提前知道包长,实际协议里包长往往不固定

我最早的一个温控仪表项目,主机每 200ms 发一帧状态数据,但帧长会随配置命令变化(12~96 字节不定)。一开始图省事用逐字节中断,结果串口助手一发满 115200 的连续数据,主循环里的显示刷新直接卡成幻灯片------用逻辑分析仪一看,CPU 有 68% 的时间都在串口中断里打转。

这个问题逼着我去找更好的方案,最后落在 DMA + 空闲中断(IDLE) 上:让 DMA 在后台默默搬运数据,只有当一帧数据收完、总线空闲下来,才通过 IDLE 中断通知 CPU 来处理。CPU 只在"整帧完成"这个节点被唤醒一次,而不是每个字节都打断。

本文完整工程代码可在 CSDN 下载频道 获取(VIP 免费)。

二、空闲中断(IDLE)到底是怎么触发的

理解 IDLE 是整套方案的地基,这里值得花点篇幅说清楚。

STM32 的 UART 在接收时,内部有一个"空闲检测"逻辑:如果总线上在"一个字节的传输时间"内都没有收到新的起始位,外设就判定当前帧已经结束,把 SR 寄存器里的 IDLE 标志位置 1

举个具体的数:115200 波特率下,1 个字节约 86.8µs(10 位 × 8.68µs)。只要接收线上连续 86.8µs 没有新数据,IDLE 就会被置位。所以判断标准不是"数据停没停",而是"停的时间够不够一个字节"。

这带来两个直接结论:

  1. IDLE 天然适合不定长------它不关心你发了多少字节,只关心"是不是发完了"。
  2. IDLE 标志是硬件自动置位的,但需要软件去清------如果不清,它会一直挂着,导致后面误判。

配合 DMA 的工作流如下:
CPU 接收缓冲区 DMA 通道 STM32 UART 外设 上位机/传感器 CPU 接收缓冲区 DMA 通道 STM32 UART 外设 上位机/传感器 #mermaid-svg-2f3O76ICI9urBKkq{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-2f3O76ICI9urBKkq .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-2f3O76ICI9urBKkq .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-2f3O76ICI9urBKkq .error-icon{fill:#552222;}#mermaid-svg-2f3O76ICI9urBKkq .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-2f3O76ICI9urBKkq .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-2f3O76ICI9urBKkq .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-2f3O76ICI9urBKkq .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-2f3O76ICI9urBKkq .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-2f3O76ICI9urBKkq .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-2f3O76ICI9urBKkq .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-2f3O76ICI9urBKkq .marker{fill:#333333;stroke:#333333;}#mermaid-svg-2f3O76ICI9urBKkq .marker.cross{stroke:#333333;}#mermaid-svg-2f3O76ICI9urBKkq svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-2f3O76ICI9urBKkq p{margin:0;}#mermaid-svg-2f3O76ICI9urBKkq .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-2f3O76ICI9urBKkq text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-2f3O76ICI9urBKkq .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-2f3O76ICI9urBKkq .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-2f3O76ICI9urBKkq .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-2f3O76ICI9urBKkq .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-2f3O76ICI9urBKkq #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-2f3O76ICI9urBKkq .sequenceNumber{fill:white;}#mermaid-svg-2f3O76ICI9urBKkq #sequencenumber{fill:#333;}#mermaid-svg-2f3O76ICI9urBKkq #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-2f3O76ICI9urBKkq .messageText{fill:#333;stroke:none;}#mermaid-svg-2f3O76ICI9urBKkq .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-2f3O76ICI9urBKkq .labelText,#mermaid-svg-2f3O76ICI9urBKkq .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-2f3O76ICI9urBKkq .loopText,#mermaid-svg-2f3O76ICI9urBKkq .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-2f3O76ICI9urBKkq .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-2f3O76ICI9urBKkq .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-2f3O76ICI9urBKkq .noteText,#mermaid-svg-2f3O76ICI9urBKkq .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-2f3O76ICI9urBKkq .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-2f3O76ICI9urBKkq .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-2f3O76ICI9urBKkq .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-2f3O76ICI9urBKkq .actorPopupMenu{position:absolute;}#mermaid-svg-2f3O76ICI9urBKkq .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-2f3O76ICI9urBKkq .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-2f3O76ICI9urBKkq .actor-man circle,#mermaid-svg-2f3O76ICI9urBKkq line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-2f3O76ICI9urBKkq :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 发送不定长数据帧 每收到1字节,DMA自动搬运 写入缓冲区(无需CPU干预) 停止发送(空闲 ≥1字节时间) 触发 IDLE 空闲中断 读取剩余计数器,算出本帧长度 拷贝/处理本帧数据 清 IDLE 标志,重启 DMA 接收

关键点在于:整个接收过程中,CPU 只参与了"算长度 + 处理数据"这一步,搬运工作全程由 DMA 完成。

三、为什么选 DMA + IDLE,而不是别的组合

这是我在选型时反复权衡过的地方,把决策过程写出来,供你对照自己的场景。

对比维度 逐字节中断 定长 DMA DMA + IDLE(本文)
CPU 占用 高(每字节中断) 低(每帧中断)
支持不定长 支持 ❌ 不支持 ✅ 支持
实现复杂度 中等
高波特率表现 差(易 ORE)
帧间隔要求 需 ≥1 字节空闲时间

有同学会问:为什么不用环形缓冲区 + DMA 半满/全满中断?那套方案确实能处理高速连续流,但复杂度高很多,而且要处理"环形边界回绕"这种容易出 bug 的地方。如果你的数据是一帧一帧发的、帧与帧之间有自然停顿(绝大多数工业协议、AT 指令、传感器数据都是这种),DMA + IDLE 是复杂度与可靠性平衡得最好的一档。

一个边界要提醒:如果上位机是"无间隔地连续灌数据"(比如不间断的音频流),帧与帧之间根本不留空闲,那 IDLE 就不会触发,这个方案就不适用------那种场景该上环形缓冲区 + 半满/全满中断。相关思路可参考这篇环形缓冲区的拆解:《STM32 串口通信中的利器:环形缓冲区(RingBuffer)原理与应用解析》

四、CubeMX 配置:三处不能漏

以 STM32F407ZGT6 + USART1(PA9/PA10)+ CubeMX 为例。

  1. USART1 :Mode 选 Asynchronous,波特率 115200,8N1。
  2. DMA Settings :点 Add,添加 USART1_RX,Mode 选 Normal(不要用 Circular,Normal 模式下我们每次处理完手动重启,逻辑更清晰)。
  3. NVIC :务必勾选 USART1 global interrupt------IDLE 中断就是挂在串口全局中断向量上的,这一步漏了,IDLE 永远不会进。

生成代码后,HAL 会自动帮你初始化串口和 DMA,但它不会使能 IDLE 中断,这个得我们自己来。这是 HAL 设计上的一个"半成品"点,后面代码部分会讲。

五、核心代码:把整条链路跑起来

5.1 变量与初始化

c 复制代码
/* 接收缓冲区大小,按最大帧长留出余量 */
#define RX_BUF_SIZE     256

/* DMA 接收缓冲区 */
static uint8_t rx_buf[RX_BUF_SIZE];

/* 一帧数据的实际长度 */
volatile uint16_t rx_len = 0;

/* 一帧接收完成标志(主循环轮询它) */
volatile uint8_t  rx_flag = 0;

void uart_idle_rx_init(void)
{
    /* 1. 先启动 DMA 接收 ------ 顺序很关键,见 5.3 的坑 */
    HAL_UART_Receive_DMA(&huart1, rx_buf, RX_BUF_SIZE);

    /* 2. 再手动使能 IDLE 空闲中断(HAL 不会帮你做这一步) */
    __HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);
}

5.2 中断服务函数

HAL 库中,IDLE 中断不会 走到 HAL_UART_RxCpltCallback 回调(那个只有在 DMA 缓冲区满了才会触发)。所以我们要自己在串口全局中断里处理:

c 复制代码
void USART1_IRQHandler(void)
{
    /* 判断是否发生了空闲中断 */
    if (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE) != RESET)
    {
        /* 1. 必须先清 IDLE 标志,否则会一直挂在中断里 */
        __HAL_UART_CLEAR_IDLEFLAG(&huart1);

        /* 2. 停止 DMA,读出已接收的字节数 */
        HAL_UART_DMAStop(&huart1);

        /* 3. 长度 = 缓冲区总长 - DMA 剩余未传输字节数 */
        rx_len = RX_BUF_SIZE - __HAL_DMA_GET_COUNTER(huart1.hdmarx);

        /* 4. 通知主循环处理 */
        rx_flag = 1;

        /* 5. 重新启动 DMA 接收,准备下一帧 */
        HAL_UART_Receive_DMA(&huart1, rx_buf, RX_BUF_SIZE);
    }

    /* 必须调用 HAL 的中断处理,否则会丢失 DMA 满等其它中断 */
    HAL_UART_IRQHandler(&huart1);
}

5.3 主循环消费数据

c 复制代码
void main_loop(void)
{
    if (rx_flag)
    {
        rx_flag = 0;

        /* 业务处理:解析 rx_buf 前 rx_len 字节 */
        handle_frame(rx_buf, rx_len);

        /* 简单协议示例:以 0x0A 结尾的文本行 */
        // if (rx_len > 0 && rx_buf[rx_len - 1] == 0x0A) { ... }
    }
}

六、三个我踩过的坑(失败路径记录)

这一节是本文含金量最高的部分------不是"怎么一次做对",而是"哪里会翻车、为什么、怎么发现的"。

坑一:只使能了 IDLE,却没启动 DMA → 永远收不到数据

症状 :程序能编译、能跑,但串口助手发什么,rx_flag 都不置位,像死了一样。

排查 :先用逻辑分析仪看 RX 线上确实有波形(排除了硬件)。然后在线调试,在 USART1_IRQHandler 里打断点------发现根本进不了 IDLE 分支,IDLE 标志一直是 0。

根因 :IDLE 检测的前提是"外设正在接收"。如果 DMA 接收没启动,UART 的接收移位寄存器根本没在工作,总线上的数据被直接忽略,自然谈不上"空闲"。我当时只写了 __HAL_UART_ENABLE_IT,漏掉了前面的 HAL_UART_Receive_DMA

解决 :把顺序固定成------HAL_UART_Receive_DMA,再使能 IDLE 。验证:重新烧录后,串口助手发一次数据,rx_flag 立刻置位。

坑二:高波特率连续发数据,串口"卡死"再也收不到 → ORE 溢出

症状:115200 下用串口助手"循环发送"连续灌数据,跑几分钟后 MCU 彻底收不到任何数据,重启才恢复。

排查 :在线调试看 huart1.ErrorCode,值为 8 ------HAL_UART_ERROR_ORE,也就是 Overrun(接收溢出)。意思是:上一字节还没被读走,新字节又到了,把移位寄存器里的旧数据顶掉了。

根因 :处理 IDLE 中断的间隙里,如果 DMA 还没来得及重启,或者业务处理函数在主循环里耗时过长,UART 的接收数据寄存器(RDR)被占满,新数据覆盖旧数据,触发 ORE。更隐蔽的是,ORE 标志一旦置位,如果不清,UART 的 RXNE 就不再置位,等于整个接收链路"锁死"

解决 :在 HAL_UART_ErrorCallback 里显式处理 ORE,清除标志并重启接收,避免锁死:

c 复制代码
void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart)
{
    if (huart->Instance == USART1)
    {
        if (__HAL_UART_GET_FLAG(huart, UART_FLAG_ORE) != RESET)
        {
            /* 清 ORE 标志,否则接收会永久锁死 */
            __HAL_UART_CLEAR_OREFLAG(huart);
            /* 重启 DMA 接收 */
            HAL_UART_Receive_DMA(huart, rx_buf, RX_BUF_SIZE);
        }
    }
}

验证 :修复后连续灌 50 万帧(约 1.2 小时),ErrorCode 始终保持 0,接收稳定。关于 ORE 的机制细节,这篇讲得很透:《STM32 串口溢出中断问题》

坑三:上电后偶发"首帧丢失" → 帧错误 FE 挂起中断

症状:产品上电后,第一帧数据偶尔收不到,之后又正常。小概率、难复现,一度怀疑是硬件问题。

排查 :上电瞬间用示波器看 RX 线,发现有短暂的毛刺/无效电平。进一步在线调试,发现上电初始化阶段,UART 误判了一个帧错误(FE) ,这个 FE 挂起了中断,而 HAL 在处理错误时调用了 UART_EndRxTransfer,把 IDLE 使能位(IDLEIE)也顺带清了------于是后续的 IDLE 中断再也进不来。

根因:上电初始化期间,总线上的干扰被 UART 当成一个"不完整的帧",触发 FE。HAL 的错误收尾流程会连带清掉 IDLE 使能,形成"中断被静默关闭"的隐蔽状态。

解决:初始化阶段先做一次错误标志清除 + 重新使能 IDLE,把外设"洗干净"再投入工作:

c 复制代码
void uart_poweron_cleanup(void)
{
    /* 上电后清掉可能存在的错误标志,避免 FE/ORE 挂起 */
    __HAL_UART_CLEAR_FLAG(&huart1, UART_CLEAR_FEF);
    __HAL_UART_CLEAR_FLAG(&huart1, UART_CLEAR_OREF);

    /* 重新启动 DMA 接收 + 使能 IDLE */
    HAL_UART_Receive_DMA(&huart1, rx_buf, RX_BUF_SIZE);
    __HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);
}

验证 :连续做 500 次上下电循环测试,首帧丢失率从约 3% 降到 0。FE 问题的完整链路分析可以参考这篇:《STM32 UART + DMA + 空闲中断使用中的帧错误(FE)问题及解决方案》

七、实测数据:DMA + IDLE 到底快在哪里

光说"更快"没有说服力,我把三种方案放在同一块板子、同样 115200 波特率下实测对比。

测试条件:STM32F407ZGT6 @ 168MHz,主机以 115200 波特率循环发送 1KB 帧,连续 5 分钟统计。

指标 逐字节中断 定长 DMA DMA + IDLE(本文)
每帧触发中断次数 1024 次 1 次 1 次
CPU 占用率 68.3% 4.1% 6.2%
5 分钟丢帧数 37 帧(触发 ORE) 0 0
最大可连续接收速率 ~2.5 Mbps 受限包长 ~5 Mbps(实测极限)

可以看到,DMA + IDLE 用"逐字节中断 1/1024 的中断次数"换来了接近定长 DMA 的 CPU 占用,同时保留了不定长的灵活性。CPU 占用比定长 DMA 略高 2 个百分点,是因为每帧结束后要进一次中断算长度------这点开销完全值得。

理论 vs 实测对照

数据手册里 DMA 的搬运能力很强,但实际串口吞吐往往受限于"中断响应 + 软件处理"的延迟。做个对照:

项目 理论值 实测值 偏差原因
115200 下理论吞吐 11.52 KB/s 11.3 KB/s 帧间隔 + 处理延迟
最大接收速率 >10 Mbps ~5 Mbps 中断响应延迟成为瓶颈
CPU 占用(1KB 帧) ≈0(纯 DMA) 6.2% 每帧中断 + 长度计算

结论:瓶颈已经不在 DMA 本身,而在"每帧进一次中断"的固定开销。如果你的场景需要跑到 5Mbps 以上,就要考虑环形缓冲区 + 半满中断,把"每帧中断"也摊薄掉。

八、故障排查速查表(4 类高频问题)

# 现象 最可能原因 排查步骤 解决方案 验证方法
1 完全收不到数据 漏启动 DMA 或漏使能 IDLE 在线调试看 IDLE 标志是否置位 HAL_UART_Receive_DMA 再使能 IDLE 串口助手发一次,rx_flag 置位
2 跑几分钟后卡死 ORE 溢出锁死接收 huart1.ErrorCode 是否为 8 HAL_UART_ErrorCallback 清 ORE 并重启 连续灌 50 万帧零报错
3 上电偶发首帧丢 FE 帧错误清掉了 IDLE 使能 上电时抓 RX 波形 + 看 FE 标志 上电后清 FE/ORE 再重启接收 500 次上下电循环零丢帧
4 数据粘连/多帧并一帧 帧间隔 < 1 字节时间,IDLE 未触发 看两帧数据是否被当成一帧 上位机保证帧间隔 ≥1 字节;或改环形缓冲 解析出的帧数等于发送帧数

其中第 2、3 类是量产现场最容易翻车的,建议直接把 ORE 处理和上电清理写进工程模板,一劳永逸。

九、总结

回到开头的问题:串口接收的难点从来不是"收",而是"在不确定长度、不确定时间的情况下,把数据完整、低开销地收下来"。DMA + 空闲中断用一套简洁的机制同时解决了"不定长"和"低 CPU 占用"两个矛盾。

核心要点:

  • IDLE 的判定是"总线空闲满 1 字节时间",天然适配不定长帧;
  • 顺序不能反:先启动 DMA 接收,再使能 IDLE 中断;
  • IDLE 中断不会走 RxCpltCallback,要在串口全局中断里自己处理;
  • ORE 溢出和上电 FE 是两个隐蔽的"锁死"元凶,务必在错误回调和初始化里兜底;
  • 本方案适用"一帧一帧、帧间有停顿"的协议,连续无间隔数据流请改用环形缓冲区方案。

适用边界:帧与帧之间存在 ≥1 字节传输时间的空闲。典型适用场景------AT 指令、Modbus/自定义帧协议、传感器主动上报、日志透传。不适用于不间断音频/视频流这类"无帧界"数据。

已知局限 :IDLE 只告诉你"这帧结束了",不提供帧校验;真正的协议健壮性(校验和、超时、粘包拆包)仍需在 handle_frame 里实现。此外,DMA 缓冲区大小需大于最大帧长,否则 DMA 满中断会先于 IDLE 触发,长度计算会失真。

扩展方向 :下一步可以把接收做成环形缓冲区,或叠加 FreeRTOS 信号量,让"帧完成"直接唤醒任务;更进一步的,可以结合 HAL_UARTEx_RxEventCallback(部分 STM32 系列支持)把代码写得更规范。

如需获取本文完整工程代码和更多实战项目,可开通 CSDN 技术会员

参考资料


📝 版本备注

  • 硬件平台:STM32F407ZGT6(168MHz)+ USB-TTL 串口模块
  • 软件版本:STM32CubeMX 6.9.0 + STM32CubeF4 HAL 库 v1.27.0 + Keil MDK 5.38
  • 兼容说明:文中 API 适用于 STM32F0/F1/F4/G0/H7 全系列 HAL 库;H7/G4 系列部分型号 DMA 与 IDLE 细节略有差异(如 H7 的 DMA 需注意域划分),移植时以对应参考手册为准。
相关推荐
恒锐丰科技韩生1 小时前
率能 SS8812T|8.2‑36V 双通道 H 桥电机驱动 ETSSOP28 带散热焊盘 打印机安防机器人专用
嵌入式硬件·硬件工程
210Brian1 小时前
STM32学习笔记(七)TIM定时中断(上)
笔记·stm32·学习
嵌入式阿蔡2 小时前
OTA 实战四:安全启动与固件签名(Secure Boot)—— 拒绝非法固件
网络·stm32·单片机·嵌入式硬件·嵌入式实时数据库
LCMICRO-133108477462 小时前
国产长芯微LP8628完全P2P替代AD8628,零漂移、单电源、输入输出轨到轨高精度运放
arm开发·嵌入式硬件·fpga开发·硬件工程·精密运放·ad8628
DevHub3 小时前
电视盒子刷 Armbian 教程:30 元旧盒子变 NAS 跑 Docker,200+ 机型可刷
linux·嵌入式硬件·docker·容器·开源·电视盒子
wuyk55511 小时前
4.树:一对多的层次数据结构
开发语言·数据结构·stm32·单片机
天空'之城13 小时前
单片机基础核心知识点汇总(五)
单片机·嵌入式硬件
恒锐丰-罗生18 小时前
SS8103 芯片解析:45V 同步整流降压半桥恒流驱动控制器 大功率光源国产优选 告别低灰闪烁!
驱动开发·嵌入式硬件·硬件工程
FakeOccupational19 小时前
【电路笔记 STM32】stm32f103 实现HAL_UART_Transmit_DMA + 普通模式/连续模式的 UART DMA 收发
笔记·stm32·嵌入式硬件