STM32串口接收不定长数据(空闲中断+DMA)
玩转 STM32 单片机,肯定离不开串口。串口使用一个称为串行通信协议的协议来管理数据传输,该协议在数据传输期间控制数据流,包括数据位数、波特率、校验位和停止位等。由于串口简单易用,在各种产品交互中都有广泛应用。
但在使用串口通讯的时候,我们并不知道对方会发送多少个数据,也不知道数据什么时候发送完,简单来讲就是:如何确保收到一帧完整的数据?
串口发送的数据有长有短,如果没有接收完整,肯定会影响后续业务的处理。为了接收不定长数据,常见的处理方法有:
1. 固定格式
比如双方约定,一帧的数据以 AA BB 开头,以 BB AA 结尾,这样在从机接收数据的时候,一旦收到 AA BB 字符,就知道对方要发来一个数据包了,然后就把后面发来的数据保存起来,直到接收到 BB AA 为止。
这种方法简单高效,但缺点就是需要每个字符都进行判断,浪费 CPU 资源,增加功耗。
2. 接收中断+超时判断
串口接收到一个数据时,就会触发接收中断。但如何判断数据已经发送完了呢?
通常来讲,两帧数据之间,会有个时间间隔。因此,我们可以使用一个计时器,如果在一个固定的时间点里没接收到新的字符,则认为一帧数据接收完成了。
3. 空闲中断
串口在空闲时,也就是说串口在一段时间里没有接收到新数据,则会触发空闲中断。细心的同学应该发现了,空闲中断实际上跟上面的超时判断是一样样的,只不过空闲中断是硬件自带,但超时判断需要我们自己实现。
所以,一旦接收到空闲中断,可以认为接收到一帧完整的数据。
但是,空闲中断并不是所有的 MCU 都具备,一般高端一点的 MCU 才有,低端一些的 MCU 并没有空闲中断。
1. 源码下载及前置阅读
本文首发 良许嵌入式网 :https://www.lxlinux.net/e/ ,欢迎关注!
本文所涉及的源码及安装包如下(由于平台限制,请点击以下链接阅读原文下载):
https://www.lxlinux.net/e/stm32/stm32-usart-receive-data-using-idle-dma.html
如果你是个零基础的小白,连 STM32 都没见过,我也给你准备了一个保姆级教程,手把手教你搭建好 STM32 开发环境,并教你如何下载程序,简直业界良心!
https://www.lxlinux.net/e/stm32/stm32-quick-start-for-beginner.html
如果你连代码都不知道怎么烧录到 STM32 的,可以参考下文,提供了 5 种代码烧录方式:
https://www.lxlinux.net/e/stm32/five-ways-to-flash-program-to-stm32.html
如果你想自己搭一个属于自己的工程模板,可以参考下面这篇文章:
https://www.lxlinux.net/e/stm32/create-stm32-hal-project-template.html
在本文中,我们详细来介绍如何使用接收中断+超时判断完成不定长数据的接收,对于接收中断的接收,请查看下文:
https://www.lxlinux.net/e/stm32/stm32-usart-receive-data-using-rxne-time-out.html
2. 什么是空闲中断?
前文已经提到,当接收到一字节数据时,会触发接收中断,对应串口状态寄存器第 5 位被置 1 ;如果串口在空闲时,则会触发空闲中断,第 4 位被置 1 ,如下图所示:
在中断服务函数里,记得一定要清除 IDLE 位,否则将一直触发空闲中断,影响后续的业务处理。
3. DMA
3.1 什么是DMA?
令人头秃的描述:
DMA(Direct Memory Access,直接存储器访问)提供在外设与内存、存储器和存储器、外设与外设之间的高速数据传输使用。它允许不同速度的硬件装置来沟通,而不需要依赖于 CPU ,在这个时间中,CPU 对于内存的工作来说就无法使用。
简单描述:
就是一个数据搬运工!!
3.2 DMA的意义
代替 CPU 搬运数据,为 CPU 减负。
-
数据搬运的工作比较耗时间;
-
数据搬运工作时效要求高(有数据来就要搬走);
-
没啥技术含量(CPU 节约出来的时间可以处理更重要的事)。
3.3 搬运什么数据?
存储器、外设
这里的外设指的是 spi、usart、iic、adc 等基于APB1 、APB2 或 AHB 时钟的外设,而这里的存储器包括自身的闪存(flash)或者内存(SRAM)以及外设的存储设备都可以作为访问地源或者目的。
三种搬运方式:
- 存储器→存储器(例如:复制某特别大的数据 buf )
- 存储器→外设 (例如:将某数据 buf 写入串口 TDR 寄存器)
- 外设→存储器 (例如:将串口 RDR 寄存器写入某数据 buf )
存储器→存储器

存储器→外设

外设→存储器

3.4 DMA 控制器
STM32F103 有 2 个 DMA 控制器,DMA1 有 7 个通道,DMA2 有 5 个通道。对于 STM32F103C8T6 这颗芯片,只有 DMA1 。
一个通道每次只能搬运一个外设的数据!! 如果同时有多个外设的 DMA 请求,则按照优先级进行响应。
DMA1 有 7 个通道:
DMA2 有 5 个通道
3.5 DMA及通道的优先级
优先级管理采用软件+硬件:
-
软件: 每个通道的优先级可以在 DMA_CCRx 寄存器中设置,有4个等级
最高级>高级>中级>低级
-
硬件: 如果 2 个请求,它们的软件优先级相同,则较低编号的通道比较高编号的通道有较高的优先权。
比如:如果软件优先级相同,通道 2 优先于通道 4
3.6 DMA传输方式
-
DMA_Mode_Normal(正常模式)
一次 DMA 数据传输完后,停止 DMA 传送 ,也就是只传输一次
-
DMA_Mode_Circular(循环传输模式)
当传输结束时,硬件自动会将传输数据量寄存器进行重装,进行下一轮的数据传输。 也就是多次传输模式
3.7 指针递增模式
外设和存储器指针在每次传输后可以自动向后递增或保持常量。当设置为增量模式时,下一个要传输的地址将是前一个地址加上增量值。


4. 硬件准备
- STM32 核心板
本文使用 STM32F103C8T6 核心板,非常便宜,某宝上 10 元左右(关键词:STM32 核心板),一杯奶茶的钱不到。
核心板最大的优点是便宜简单,缺点就是需要根据需求自己搭一些电路,对你的动手能力要求比较高。
上面所推荐的这块核心板,主控芯片是 STM32103C8T6 ,64K flash,20K RAM,4 个定时器,3 个串口,网络上资料好几吨,非常适合初学者入门,强烈推荐。
- USB 转 TTL
这种设备主要作用是用来调试或下载程序。价格也很便宜,普遍 5~8 元。
- ST-Link
ST-Link 是一种用于 STM32 微控制器的调试和编程工具,它可以通过 SWD 或 JTAG 接口与开发板进行通信。一般也很便宜,七八元左右。
5. 编程实战
在本实验中,我们将串口 1 作为 log 输出端口,串口 2 作为本次实验的接收端口。
因此我们需要提前创建 uart2 模块,包含 uart2.c 及 uart2.h 两个文件,并加载进工程模板。
5.1 串口初始化
串口的初始化大家应该不陌生,主要步骤为:
- 定义串口句柄
uart2_handle
,并调用HAL_UART_Init
进行初始化; - 初始化串口底层函数,调用
HAL_UART_MspInit
函数。
第一步在 uart2.c 文件里进行:
UART_HandleTypeDef uart2_handle;void uart2_init(uint32_t baudrate)
{uart2_handle.Instance = UART2_INTERFACE; /* UART2 */uart2_handle.Init.BaudRate = baudrate; /* 波特率 */uart2_handle.Init.WordLength = UART_WORDLENGTH_8B; /* 数据位 */uart2_handle.Init.StopBits = UART_STOPBITS_1; /* 停止位 */uart2_handle.Init.Parity = UART_PARITY_NONE; /* 校验位 */uart2_handle.Init.Mode = UART_MODE_TX_RX; /* 收发模式 */uart2_handle.Init.HwFlowCtl = UART_HWCONTROL_NONE; /* 无硬件流控 */uart2_handle.Init.OverSampling = UART_OVERSAMPLING_16; /* 过采样 */HAL_UART_Init(&uart2_handle); /* 使能UART2 */
}
第二步在 usart.c 文件里进行,其实也可以在 uart2.c 文件里做,但我懒~
在最下面两行代码,我们使用 __HAL_UART_ENABLE_IT()
使能接收中断及空闲中断。
void HAL_UART_MspInit(UART_HandleTypeDef *huart)
{GPIO_InitTypeDef gpio_init_struct;if (huart->Instance == USART_UX) /* 如果是串口1,进行串口1 MSP初始化 */{....// 节略串口1相关代码....}else if (huart->Instance == UART2_INTERFACE) /* 如果是UART2 */{UART2_TX_GPIO_CLK_ENABLE(); /* 使能UART2 TX引脚时钟 */UART2_RX_GPIO_CLK_ENABLE(); /* 使能UART2 RX引脚时钟 */UART2_CLK_ENABLE(); /* 使能UART2时钟 */gpio_init_struct.Pin = UART2_TX_GPIO_PIN; /* UART2 TX引脚 */gpio_init_struct.Mode = GPIO_MODE_AF_PP; /* 复用推挽输出 */gpio_init_struct.Pull = GPIO_NOPULL; /* 无上下拉 */gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; /* 高速 */HAL_GPIO_Init(UART2_TX_GPIO_PORT, &gpio_init_struct); /* 初始化UART2 TX引脚 */gpio_init_struct.Pin = UART2_RX_GPIO_PIN; /* UART2 RX引脚 */gpio_init_struct.Mode = GPIO_MODE_INPUT; /* 输入 */gpio_init_struct.Pull = GPIO_NOPULL; /* 无上下拉 */gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; /* 高速 */HAL_GPIO_Init(UART2_RX_GPIO_PORT, &gpio_init_struct); /* 初始化UART2 RX引脚 */HAL_NVIC_SetPriority(UART2_IRQn, 0, 0); /* 抢占优先级0,子优先级0 */HAL_NVIC_EnableIRQ(UART2_IRQn); /* 使能UART2中断通道 */__HAL_UART_ENABLE_IT(huart, UART_IT_RXNE); /* 使能UART2接收中断 */__HAL_UART_ENABLE_IT(huart, UART_IT_IDLE); /* 使能UART2总线空闲中断 */}
}
5.2 串口中断服务函数
前文已经提到,串口触发到一次接收中断,则代表接收到一个字符,我们就可以把这个字符放到接收缓冲区里。这个过程与上一篇文章一样样,可以参考下文:
【STM32串口接收不定长数据(接收中断+超时判断)】
具体代码实现如下:
void UART2_IRQHandler(void)
{uint8_t receive_data = 0; if(__HAL_UART_GET_FLAG(&uart2_handle, UART_FLAG_RXNE) != RESET){ //获取接收RXNE标志位是否被置位if(uart2_rx_len >= sizeof(uart2_rx_buf)) //如果接收的字符数大于接收缓冲区大小,uart2_rx_len = 0; //则将接收计数器清零HAL_UART_Receive(&uart2_handle, &receive_data, 1, 1000); //接收一个字符uart2_rx_buf[uart2_rx_len++] = receive_data; //将接收到的字符保存在接收缓冲区}...// 省略空闲中断代码...
}
串口触发一次空闲中断,则代表接收到一帧数据,也就是收到了一个完整的数据包了,我们就可以将收到的数据包进行处理(比如打印出来),代码如下:
void UART2_IRQHandler(void)
{...// 省略接收中断代码...if (__HAL_UART_GET_FLAG(&uart2_handle, UART_FLAG_IDLE) != RESET) //获取接收空闲中断标志位是否被置位{printf("recv: %s\r\n", uart2_rx_buf); //将接收到的数据打印出来uart2_rx_clear();__HAL_UART_CLEAR_IDLEFLAG(&uart2_handle); //清除UART总线空闲中断}
}
在上面的代码里,一定要记得调用 __HAL_UART_CLEAR_IDLEFLAG()
函数清除 UART 总线空闲中断,否则空闲中断一直处于触发状态,影响下一次接收。
判断是否收到接收/空闲中断,需要用到的是 __HAL_UART_GET_FLAG()
函数,接收中断判断的是 UART_FLAG_RXNE
标志位,而空闲中断判断的是 UART_FLAG_IDLE
标志位。
串口中断服务函数完整代码如下(就是将上面两部分代码合二为一):
void uart2_rx_clear(void)
{memset(uart2_rx_buf, 0, sizeof(uart2_rx_buf)); //清空接收缓冲区uart2_rx_len = 0; //接收计数器清零
}void UART2_IRQHandler(void)
{uint8_t receive_data = 0; if(__HAL_UART_GET_FLAG(&uart2_handle, UART_FLAG_RXNE) != RESET){ //获取接收RXNE标志位是否被置位if(uart2_rx_len >= sizeof(uart2_rx_buf)) //如果接收的字符数大于接收缓冲区大小,uart2_rx_len = 0; //则将接收计数器清零HAL_UART_Receive(&uart2_handle, &receive_data, 1, 1000); //接收一个字符uart2_rx_buf[uart2_rx_len++] = receive_data; //将接收到的字符保存在接收缓冲区}if (__HAL_UART_GET_FLAG(&uart2_handle, UART_FLAG_IDLE) != RESET) //获取接收空闲中断标志位是否被置位{printf("recv: %s\r\n", uart2_rx_buf); //将接收到的数据打印出来uart2_rx_clear();__HAL_UART_CLEAR_IDLEFLAG(&uart2_handle); //清除UART总线空闲中断}
}
对应的 uart2.h 文件完整代码如下:
#include <stdint.h>
#include "usart.h"/* 引脚定义 */
#define UART2_TX_GPIO_PORT GPIOA
#define UART2_TX_GPIO_PIN GPIO_PIN_2
#define UART2_TX_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)#define UART2_RX_GPIO_PORT GPIOA
#define UART2_RX_GPIO_PIN GPIO_PIN_3
#define UART2_RX_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)#define UART2_INTERFACE USART2
#define UART2_IRQn USART2_IRQn
#define UART2_IRQHandler USART2_IRQHandler
#define UART2_CLK_ENABLE() do{ __HAL_RCC_USART2_CLK_ENABLE(); }while(0)/* 错误代码 */
#define UART_EOK 0 /* 没有错误 */
#define UART_ERROR 1 /* 通用错误 */
#define UART_ETIMEOUT 2 /* 超时错误 */
#define UART_EINVAL 3 /* 参数错误 *//* UART收发缓冲大小 */
#define UART2_RX_BUF_SIZE 128
#define UART2_TX_BUF_SIZE 64void uart2_init(uint32_t baudrate);
到这里,实际上我们已经实现了使用空闲中断接收不定长数据的逻辑代码了,烧进板子后效果如下:
对于大多数应用场景下,这种串口接收不定长数据的处理方式已经足够用了。
但如果你串口每次接收的数据量过于庞大,那么就可以请出 DMA 这个数据搬运工了,一旦接收到数据则立马搬走,不占用 CPU 资源。
5.3 加入DMA
既然需要用到 DMA 外设,则在 BSP 目录下创建 dma.c 及 dma.h 两个文件,并加载进工程文件。
在 dma.c 文件里,我们要做的事情就是初始化 DMA 外设,实际上就是指定数据从哪里来、到哪里去,以及数据长度等等。
由于我们使用的是串口2 RX 通道,根据下图可知,用到的 DMA 通道为 DMA1_Channel6 (STM32F103C8T6只有 DMA1 )。
在初始化的最后,一定要记得调用 HAL_UART_Receive_DMA()
函数开启 DMA 接收,否则 DMA 这个搬运工就算请过来了,他还是依然不为你工作。
详细代码如下:
void dma_init(void)
{// UART2 RX DMA配置__HAL_RCC_DMA1_CLK_ENABLE(); /* DMA1时钟使能 */dma_handle.Instance = DMA1_Channel6; /* USART2_RX使用的DMA通道为: DMA1_Channel6 */dma_handle.Init.Direction = DMA_PERIPH_TO_MEMORY; /* 外设到存储器模式 */dma_handle.Init.PeriphInc = DMA_PINC_DISABLE; /* 外设非增量模式 */dma_handle.Init.MemInc = DMA_MINC_ENABLE; /* 存储器增量模式 */dma_handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; /* 外设数据长度:8位 */dma_handle.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; /* 存储器数据长度:8位 */dma_handle.Init.Mode = DMA_NORMAL; /* 外设流控模式 */dma_handle.Init.Priority = DMA_PRIORITY_LOW; /* 低优先级 */HAL_DMA_Init(&dma_handle);__HAL_LINKDMA(&uart2_handle, hdmarx, dma_handle); /* 将DMA与USART2联系起来(发送DMA) */HAL_UART_Receive_DMA(&uart2_handle, uart2_rx_buf, UART2_RX_BUF_SIZE); /* 开启DMA接收 */
}
在串口中断服务函数里,我们可以将接收中断相关代码全部去掉(因为已经有了 DMA 这个搬运工了,没必要让 CPU 一个个字符转移数据了)。
我先把代码贴上来,再详细讲解。
void UART2_IRQHandler(void)
{if (__HAL_UART_GET_FLAG(&uart2_handle, UART_FLAG_IDLE) != RESET){ //获取接收IDLE标志位是否被置位__HAL_UART_CLEAR_IDLEFLAG(&uart2_handle);HAL_UART_DMAStop(&uart2_handle); //停止DMA传输,防止干扰uart2_rx_len = UART2_RX_BUF_SIZE - __HAL_DMA_GET_COUNTER(&dma_handle); //获取接收到的数据长度printf("recv: %s, recv_len: %d\r\n", uart2_rx_buf, uart2_rx_len);uart2_rx_clear();HAL_UART_Receive_DMA(&uart2_handle, uart2_rx_buf, UART2_RX_BUF_SIZE); //重新开启DMA传输}
}
上面的代码有几个要点需要解释一下:
- 停止 DMA 传输
当我们收到空闲中断时,实际上 DMA 已经帮我们把所有的数据搬运到了接收缓冲区了,此时我们可以先把 DMA 传输关闭掉,防止干扰到后续的操作。
- 获取接收到的数据长度
__HAL_DMA_GET_COUNTER()
函数表示 DMA 中待接收的数据长度。什么意思呢?假设我需要 DMA 接收 100 个字符的数据量,但现在实际上只接收到了 30 个字符,那么待接收的数据长度为 70 ,也就是 __HAL_DMA_GET_COUNTER()
函数的返回值为 70 。
所以,我们已经接收到的数据长度,等于接收缓冲区的长度,减去待接收的数据长度,翻译成代码就是:
uart2_rx_len = UART2_RX_BUF_SIZE - __HAL_DMA_GET_COUNTER(&dma_handle);
- 重新开启 DMA 传输
一帧数据处理完成之后,我们肯定要进行下一帧数据的接收,所以需要调用 HAL_UART_Receive_DMA()
重新开启 DMA 传输,否则数据只接收一帧之后就罢工了。
到此 DMA 就加入成功了,烧进去板子后效果如下:
6. 小结
STM32 串口通讯在项目中使用的频率非常高,但由于不知道数据发送方会发送多少数据量,所以串口接收不定长数据成了一个急需解决的问题。
本文使用串口的空闲中断+DMA方法解决了此问题,并给出了详细的教程,希望对读者朋友有所帮助。
相关文章:

STM32串口接收不定长数据(空闲中断+DMA)
玩转 STM32 单片机,肯定离不开串口。串口使用一个称为串行通信协议的协议来管理数据传输,该协议在数据传输期间控制数据流,包括数据位数、波特率、校验位和停止位等。由于串口简单易用,在各种产品交互中都有广泛应用。 但在使用串…...
LeetCode56. Merge Intervals
文章目录 一、题目二、题解 一、题目 Given an array of intervals where intervals[i] [starti, endi], merge all overlapping intervals, and return an array of the non-overlapping intervals that cover all the intervals in the input. Example 1: Input: interva…...
【华为OD题库-083】玩牌高手-Java
题目 给定一个长度为n的整型数组,表示一个选手在n轮内可选择的牌面分数。选手基于规则选牌,请计算所有轮结束后其可以获得的最高总分数。 选择规则如下: 1.在每轮里选手可以选择获取该轮牌面,则其总分数加上该轮牌面分数,为其新的…...

ARM day3
题目:实现3盏灯的流水 代码: .text .global _start _start: 设置RCC寄存器使能 LDR R0,0X50000A28 LDR R1,[R0] ORR R1,R1,#(0X1<<4) ORR R1,R1,#(0X1<<5) STR R1,[R0]设置PE10管脚为输出模式 LDR R0,0X50006000 LDR R1,[R0] BIC R1,R1,…...

[足式机器人]Part2 Dr. CAN学习笔记-自动控制原理Ch1-2稳定性分析Stability
本文仅供学习使用 本文参考: B站:DR_CAN Dr. CAN学习笔记-自动控制原理Ch1-2稳定性分析Stability 0. 序言1. 稳定的分类2. 稳定的对象3. 稳定的系统4. 系统稳定性的讨论5. 补充内容——Transfer Function(传递函数) - nonzero Initial Condition(非零初始…...

Android Audio实战——音频链路分析(二十五)
在 Android 系统的开发过程当中,音频异常问题通常有如下几类:无声、调节不了声音、爆音、声音卡顿和声音效果异常(忽大忽小,低音缺失等)等。尤其声音效果这部分问题通常从日志上信息量较少,相对难定位根因。想要分析此类问题,便需要对声音传输链路有一定的了解,能够在链…...
PHP基础 - 常量字符串
常量 在PHP中,常量是一个简单值的标识符,定义后默认是全局变量,可以在整个运行的脚本的任何地方使用。常量由英文字母、下划线和数字组成,但数字不能作为首字母出现。 PHP中定义常量的方式是使用define()函数,其语法如下: bool define( string $name, mixed $value [,…...

Linux查看命令的绝对路径
linux查看命令的绝对路径 在Linux中,可以使用以下命令来查看命令的绝对路径: 1、which 命令名 例如,要查看chronyc命令的绝对路径,可以运行: which chronyc 2、whereis 命令名 例如,要查看chronyc命令…...

Docker build 无法解析域名
### 报错 Docker build 无法解析域名 报错:ERROR [ 2/12] RUN curl -o /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-7.repo 解决Docker build无法解析域名 # 追加到 etc/docker/daemon.json,注意JSON的格式 {"dn…...

退稿论文重复率太高会怎么样【保姆教程】
大家好,今天来聊聊退稿论文重复率太高会怎么样,希望能给大家提供一点参考。 以下是针对论文重复率高的情况,提供一些修改建议和技巧: 退稿论文重复率太高会怎么样 在学术出版领域,论文的重复率是衡量其原创性和学术诚…...
Flask 最佳实践(一)
Flask是一个轻量级而强大的Python Web框架,它的简洁性和灵活性使其成为许多开发者的首选。然而,为了确保项目的可维护性和可扩展性,我们需要遵循一些最佳实践。本文将探讨Flask中一些关键的最佳实践。 1. 项目结构 构建一个清晰的项目结构是…...

直流电和交流电
直流电(Direct Current,简称DC)和交流电(Alternating Current,简称AC)是电流的两种基本形式。 1. 直流电 直流电是指电流方向始终保持不变的电流。在直流电中,电子只能沿着一个方向移动。直流电…...

『亚马逊云科技产品测评』活动征文|基于亚马逊EC2云服务器安装Prometheus数据可视化监控
授权声明:本篇文章授权活动官方亚马逊云科技文章转发、改写权,包括不限于在 Developer Centre, 知乎,自媒体平台,第三方开发者媒体等亚马逊云科技官方渠道 亚马逊EC2云服务器(Elastic Compute Cloud)是亚马…...
15、SQL注入——Sqlmap
文章目录 一、Sqlmap简介1.1 sqlmap可以对URL干嘛?1.2 Sqlmap支持的注入技术1.3 SQLmap检测注入漏洞的流程1.4 Sqlmap的误报检测机制 二、sqlmap基本使用 一、Sqlmap简介 sqlmap使用教程 1.1 sqlmap可以对URL干嘛? 判断可注入的参数判断可以使用哪一种…...

OSPF路由协议
随着Internet技术在全球范围的飞速发展,OSPF已成为目前应用最广泛的路由协议之一。OSPF(Open Shortest Path First)路由协议是由IETF(Internet Engineering Task Force)IGP工作组提出的,是一种基于SPF算法的…...

设计模式-门面模式(Facade)
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、定义二、结构 前言 在组件构建过程中,某些接口之间直接依赖会带来很多问题,甚至无法直接实现。采用一层间接接口,来隔离…...

语音识别从入门到精通——1-基本原理解释
文章目录 语音识别算法1. 语音识别简介1.1 **语音识别**1.1.1 自动语音识别1.1.2 应用 1.2 语音识别流程1.2.1 预处理1.2.2 语音检测和断句1.2.3 音频场景分析1.2.4 识别引擎(语音识别的模型)1. 传统语音识别模型2. 端到端的语音识别模型基于Transformer的ASR模型基于CNN的ASR模…...

语音识别功能测试:90%问题,可以通过技术解决
现在市面上的智能电子产品千千万,为了达到人们使用更加方便的目的,很多智能产品都开发了语音识别功能,用来语音唤醒进行交互;另外,各大公司也开发出来了各种智能语音机器人,比如小米公司的“小爱”…...
【Go自学版】01-基础
// 变量 var a, b, c 8, 2.3, "hello" var d float64; e : 6var A []int; var B [10]int; C : [10]int{1, 2, 3, 4} for i : 0; i < len(B); i {} for _, value : range C {} D make([]int, 3) // len 4, cap 10, 扩容方式 cap*2 E : make([]int, 4, 10) E …...

软文开头怎么写才能拿捏用户?媒介盒子为您解答
软文标题是吸引用户点击的关键因素,那软文开头就是决定用户能否读下去的主要因素,很多运营er在写文案时经常会面临的情况之一就是好不容易想到一个标题,点击率不错,但是开头不行用户一看开头,跑了!如果不知…...

大话软工笔记—需求分析概述
需求分析,就是要对需求调研收集到的资料信息逐个地进行拆分、研究,从大量的不确定“需求”中确定出哪些需求最终要转换为确定的“功能需求”。 需求分析的作用非常重要,后续设计的依据主要来自于需求分析的成果,包括: 项目的目的…...
【解密LSTM、GRU如何解决传统RNN梯度消失问题】
解密LSTM与GRU:如何让RNN变得更聪明? 在深度学习的世界里,循环神经网络(RNN)以其卓越的序列数据处理能力广泛应用于自然语言处理、时间序列预测等领域。然而,传统RNN存在的一个严重问题——梯度消失&#…...

《用户共鸣指数(E)驱动品牌大模型种草:如何抢占大模型搜索结果情感高地》
在注意力分散、内容高度同质化的时代,情感连接已成为品牌破圈的关键通道。我们在服务大量品牌客户的过程中发现,消费者对内容的“有感”程度,正日益成为影响品牌传播效率与转化率的核心变量。在生成式AI驱动的内容生成与推荐环境中࿰…...
Qwen3-Embedding-0.6B深度解析:多语言语义检索的轻量级利器
第一章 引言:语义表示的新时代挑战与Qwen3的破局之路 1.1 文本嵌入的核心价值与技术演进 在人工智能领域,文本嵌入技术如同连接自然语言与机器理解的“神经突触”——它将人类语言转化为计算机可计算的语义向量,支撑着搜索引擎、推荐系统、…...
Linux云原生安全:零信任架构与机密计算
Linux云原生安全:零信任架构与机密计算 构建坚不可摧的云原生防御体系 引言:云原生安全的范式革命 随着云原生技术的普及,安全边界正在从传统的网络边界向工作负载内部转移。Gartner预测,到2025年,零信任架构将成为超…...
汇编常见指令
汇编常见指令 一、数据传送指令 指令功能示例说明MOV数据传送MOV EAX, 10将立即数 10 送入 EAXMOV [EBX], EAX将 EAX 值存入 EBX 指向的内存LEA加载有效地址LEA EAX, [EBX4]将 EBX4 的地址存入 EAX(不访问内存)XCHG交换数据XCHG EAX, EBX交换 EAX 和 EB…...

自然语言处理——循环神经网络
自然语言处理——循环神经网络 循环神经网络应用到基于机器学习的自然语言处理任务序列到类别同步的序列到序列模式异步的序列到序列模式 参数学习和长程依赖问题基于门控的循环神经网络门控循环单元(GRU)长短期记忆神经网络(LSTM)…...
聊一聊接口测试的意义有哪些?
目录 一、隔离性 & 早期测试 二、保障系统集成质量 三、验证业务逻辑的核心层 四、提升测试效率与覆盖度 五、系统稳定性的守护者 六、驱动团队协作与契约管理 七、性能与扩展性的前置评估 八、持续交付的核心支撑 接口测试的意义可以从四个维度展开,首…...
稳定币的深度剖析与展望
一、引言 在当今数字化浪潮席卷全球的时代,加密货币作为一种新兴的金融现象,正以前所未有的速度改变着我们对传统货币和金融体系的认知。然而,加密货币市场的高度波动性却成为了其广泛应用和普及的一大障碍。在这样的背景下,稳定…...
#Uniapp篇:chrome调试unapp适配
chrome调试设备----使用Android模拟机开发调试移动端页面 Chrome://inspect/#devices MuMu模拟器Edge浏览器:Android原生APP嵌入的H5页面元素定位 chrome://inspect/#devices uniapp单位适配 根路径下 postcss.config.js 需要装这些插件 “postcss”: “^8.5.…...