STM32F103 HAL多实例通用USART驱动 - 高效DMA+RingBuffer方案,量产级工程模板
导言
《STM32F103_LL库+寄存器学习笔记12.2 - 串口DMA高效收发实战2:进一步提高串口接收的效率》前阵子完成的LL库与寄存器版本的代码,有一个明显的缺点是不支持多实例化。最近,计划基于HAL库系统地梳理一遍bootloader程序开发。在bootloader程序开发项目里,需要用到HAL库的串口驱动代码。所以,继续把代码优化优化,将多实例化模块做出来。
bsp_usart_hal 驱动代码特点总结:
-
支持多实例通用管理
- 设计为USART_Driver_t结构体+接口函数,支持多个USART端口同时独立驱动,便于单板多串口应用、项目横向复用、一套代码多项目。
- 各实例参数(缓冲区、DMA句柄、UART句柄)解耦,代码维护简便。
-
DMA收发全流程、双缓冲高效传输
- 接收采用DMA环形模式,支持半传输(HT)、全传输(TC)、IDLE三类中断协同搬运,保证任意长度数据不丢包、无粘包。
- 发送采用DMA块传输,自动调度RingBuffer队列,避免CPU阻塞。
- 收发均使用DMA,极大减轻CPU负担,适用于高带宽、高实时性场景。
-
RingBuffer无缝缓存机制
- 内置收发双向RingBuffer,自动管理协议包拼接、数据缓冲,简化上层协议处理。
- 支持超大缓冲、溢出自动覆盖或丢弃旧数据,易于移植第三方协议栈。
-
健壮的错误统计与自恢复机制
- 内置DMA传输错误、串口硬件错误等统计计数(errorDMATX/errorDMARX/errorRX),便于产线异常监控、后期故障溯源。
- 检测到DMA错误时,自动执行自恢复:自动重启DMA、自动清理异常、可定制连续多次异常的告警或自动复位,保障系统长期稳定运行。
-
工程化量产导向
- 代码结构清晰、注释标准,适用于量产项目长期维护。
- CubeMX工程直接集成,无需魔改HAL库代码。
- 支持多串口、多DMA。
测试效果如下,接收与发送都没有丢包。
项目地址:
github: https://github.com/q164129345/MCU_Develop/tree/main/stm32f103_hal_usart_dma_ringbuffer
gitee(国内): https://gitee.com/wallace89/MCU_Develop/tree/main/stm32f103_hal_usart_dma_ringbuffer
一、CubeMX
1.1、Clock Configuration
1.2、USART1
波特率115200,数据长度8bits,停止位长度1bit。
使能USART1全局中断。
USART1_RX的DMA接收模式一定要选择“Circular”模式,长度选择Byte(字节) = 8bits。
USART1_RX的DMA发送模式一定要选择“Normal”模式,长度选择Byte(字节) = 8bits。
GPIO Setting的设置如上所示。
二、代码
2.1、bsp_usart_hal.h
/*** @file bsp_usart_hal.h* @brief STM32F1系列 USART + DMA + RingBuffer HAL库底层驱动接口(多实例、可变缓冲区)* @author Wallace.zhang* @version 2.0.0* @date 2025-05-23*/
#ifndef __BSP_USART_HAL_H
#define __BSP_USART_HAL_H#ifdef __cplusplus
extern "C" {
#endif#include "main.h"
#include "usart.h"
#include "dma.h"
#include "lwrb/lwrb.h"/*** @brief USART驱动结构体(DMA + RingBuffer + 统计)*/
typedef struct
{volatile uint8_t txDMABusy; /**< DMA发送忙标志(1:发送中,0:空闲) */volatile uint64_t rxMsgCount; /**< 统计接收字节总数 */volatile uint64_t txMsgCount; /**< 统计发送字节总数 */volatile uint16_t dmaRxLastPos; /**< DMA接收缓冲区上次处理到的位置 */volatile uint32_t errorDMATX; /**< DMA发送错误统计 */volatile uint32_t errorDMARX; /**< DMA接收错误统计 */volatile uint32_t errorRX; /**< 串口接收错误统计 */DMA_HandleTypeDef *hdma_rx; /**< HAL库的DMA RX句柄 */DMA_HandleTypeDef *hdma_tx; /**< HAL库的DMA TX句柄 */UART_HandleTypeDef *huart; /**< HAL库的UART句柄 *//* RX方向 */uint8_t *rxDMABuffer; /**< DMA接收缓冲区 */uint8_t *rxRBBuffer; /**< 接收RingBuffer缓存区 */uint16_t rxBufSize; /**< DMA接收/RX Ringbuffer缓冲区大小 */lwrb_t rxRB; /**< 接收RingBuffer句柄 *//* TX方向 */uint8_t *txDMABuffer; /**< DMA发送缓冲区 */uint8_t *txRBBuffer; /**< 发送RingBuffer缓存区 */uint16_t txBufSize; /**< DMA发送/TX Ringbuffer缓冲区大小 */lwrb_t txRB; /**< 发送RingBuffer句柄 */} USART_Driver_t;/*** @brief 阻塞方式发送以 NUL 结尾的字符串*/
void USART_SendString_Blocking(USART_Driver_t* const usart, const char* str);
/*** @brief USART发送DMA中断处理函数*/
void USART_DMA_TX_Interrupt_Handler(USART_Driver_t *usart);
/*** @brief USART接收DMA中断处理函数*/
void USART_DMA_RX_Interrupt_Handler(USART_Driver_t *usart);
/*** @brief USART空闲接收中断处理函数(支持DMA+RingBuffer,适用于多实例)*/
void USART_RX_IDLE_Interrupt_Handler(USART_Driver_t *usart);
/*** @brief 将数据写入指定USART驱动的发送 RingBuffer中*/
uint8_t USART_Put_TxData_To_Ringbuffer(USART_Driver_t *usart, const void* data, uint16_t len);
/*** @brief USART模块定时任务处理函数,建议主循环1ms周期回调*/
void USART_Module_Run(USART_Driver_t *usart);
/*** @brief 获取USART接收RingBuffer中的可读字节数*/
uint32_t USART_Get_The_Existing_Amount_Of_Data(USART_Driver_t *usart);
/*** @brief 从USART接收RingBuffer中读取一个字节数据*/
uint8_t USART_Take_A_Piece_Of_Data(USART_Driver_t *usart, uint8_t* data);
/*** @brief DMA传输错误后的自动恢复操作(含错误统计)*/
void USART_DMA_Error_Recover(USART_Driver_t *usart, uint8_t dir);
/*** @brief 初始化USART驱动,配置DMA、RingBuffer与中断*/
void USART_Config(USART_Driver_t *usart,uint8_t *rxDMABuffer, uint8_t *rxRBBuffer, uint16_t rxBufSize,uint8_t *txDMABuffer, uint8_t *txRBBuffer, uint16_t txBufSize);#ifdef __cplusplus
}
#endif#endif /* __BSP_USART_HAL_H */
2.2、bsp_usart_hal.c
/*** @file bsp_usart_hal.c* @brief STM32F1系列 USART + DMA + RingBuffer HAL库底层驱动实现(多实例、可变缓冲区)* @author Wallace.zhang* @version 2.0.0* @date 2025-05-23*/#include "bsp_usart_hal.h"/*** @brief 阻塞方式发送以NUL结尾字符串(调试用,非DMA)* @param usart 指向USART驱动结构体的指针* @param str 指向以'\0'结尾的字符串* @note 通过HAL库API逐字节发送,底层会轮询TXE位(USART_SR.TXE)。* @retval 无*/
void USART_SendString_Blocking(USART_Driver_t* usart, const char* str)
{if (!usart || !str) return;HAL_UART_Transmit(usart->huart, (uint8_t*)str, strlen(str), 1000);
}/*** @brief 配置并启动USART的DMA接收(环形模式)* @param usart 指向USART驱动结构体的指针* @note 必须保证huart、hdma_rx已通过CubeMX正确初始化* - 调用本函数会停止原有DMA,然后重新配置DMA并启动环形接收* - 使能USART的IDLE中断,实现突发/不定长帧高效处理* @retval 无*/
static void USART_Received_DMA_Configure(USART_Driver_t *usart)
{if (!usart) return;HAL_DMA_Abort(usart->hdma_rx); //! 先关闭HAL_UART_Receive_DMA(usart->huart, usart->rxDMABuffer, usart->rxBufSize); //! 启动环形DMA__HAL_UART_ENABLE_IT(usart->huart, UART_IT_IDLE); //! 使能IDLE中断usart->dmaRxLastPos = 0;
}/*** @brief 启动DMA方式串口发送* @param usart 指向USART驱动结构体的指针* @param data 指向待发送的数据缓冲区* @param len 待发送的数据字节数* @note 发送前需确保txDMABusy为0,否则应等待前一帧发送完成* 启动后DMA自动填充USART_DR寄存器,实现高效异步发送* @retval 无*/
static void USART_SendString_DMA(USART_Driver_t *usart, uint8_t *data, uint16_t len)
{if (!usart || !data || len == 0 || len > usart->txBufSize) return;while (usart->txDMABusy); // 等待DMA空闲usart->txDMABusy = 1;HAL_UART_Transmit_DMA(usart->huart, data, len);
}/*** @brief 写入数据到接收RingBuffer* @param usart 指向USART驱动结构体的指针* @param data 指向要写入的数据缓冲区* @param len 要写入的数据长度(单位:字节)* @retval 0 数据成功写入,无数据丢弃* @retval 1 ringbuffer剩余空间不足,丢弃部分旧数据以容纳新数据* @retval 2 数据长度超过ringbuffer总容量,仅保留新数据尾部(全部旧数据被清空)* @retval 3 输入数据指针为空* @note* - 本函数通过lwrb库操作ringbuffer。* - 当len > ringbuffer容量时,强行截断,仅保留最新usart->rxRBBuffer字节。* - 若空间不足,自动调用lwrb_skip()丢弃部分旧数据。*/
static uint8_t Put_Data_Into_Ringbuffer(USART_Driver_t *usart, const void *data, uint16_t len)
{//! 检查输入指针是否合法if (!usart || !data) return 3;lwrb_t *rb = &usart->rxRB;uint16_t rb_size = usart->rxBufSize;//! 获取当前RingBuffer剩余空间lwrb_sz_t free_space = lwrb_get_free(rb);//! 分三种情况处理:长度小于、等于、大于RingBuffer容量uint8_t ret = 0;if (len < rb_size) {//! 数据小于RingBuffer容量if (len <= free_space) {//! 空间充足,直接写入lwrb_write(rb, data, len);} else {//! 空间不足,需丢弃部分旧数据lwrb_sz_t used = lwrb_get_full(rb);lwrb_sz_t skip_len = len - free_space;if (skip_len > used) {skip_len = used;}lwrb_skip(rb, skip_len); //! 跳过(丢弃)旧数据lwrb_write(rb, data, len);ret = 1;}} else if (len == rb_size) { //! 数据刚好等于RingBuffer容量if (free_space < rb_size) {lwrb_reset(rb); //! 空间不足,重置RingBufferret = 1;}lwrb_write(rb, data, len);} else { //! 数据超过RingBuffer容量,仅保留最后rb_size字节const uint8_t *byte_ptr = (const uint8_t *)data;data = (const void *)(byte_ptr + (len - rb_size));lwrb_reset(rb);lwrb_write(rb, data, rb_size);ret = 2;}return ret;
}/*** @brief 从DMA环形缓冲区搬运新收到的数据到RingBuffer(支持环绕)* @param usart 指向USART驱动结构体的指针* @note 支持IDLE、DMA HT/TC等多中断共同调用* - 本函数计算DMA环形缓冲区的新数据,并搬运到RingBuffer* - 支持一次性或分段搬运(缓冲区环绕时自动分两段处理)* @retval 无*/
static void USART_DMA_RX_Copy(USART_Driver_t *usart)
{uint16_t bufsize = usart->rxBufSize;uint16_t curr_pos = bufsize - __HAL_DMA_GET_COUNTER(usart->hdma_rx);uint16_t last_pos = usart->dmaRxLastPos;if (curr_pos != last_pos) {if (curr_pos > last_pos) {//! 普通情况,未环绕Put_Data_Into_Ringbuffer(usart, usart->rxDMABuffer + last_pos, curr_pos - last_pos);usart->rxMsgCount += (curr_pos - last_pos);} else {//! 环绕,分两段处理Put_Data_Into_Ringbuffer(usart, usart->rxDMABuffer + last_pos, bufsize - last_pos);Put_Data_Into_Ringbuffer(usart, usart->rxDMABuffer, curr_pos);usart->rxMsgCount += (bufsize - last_pos) + curr_pos;}usart->dmaRxLastPos = curr_pos;}
}/*** @brief DMA接收搬运环形Buffer(推荐在IDLE、DMA中断等调用)* @param usart 指向USART驱动结构体的指针* @retval 无*/
void USART_DMA_RX_Interrupt_Handler(USART_Driver_t *usart)
{if (!usart) return;USART_DMA_RX_Copy(usart);
}/*** @brief 串口IDLE中断处理(需在USARTx_IRQHandler中调用)* @param usart 指向USART驱动结构体的指针* @note 检查并清除IDLE标志,及时触发DMA搬运* @retval 无*/
void USART_RX_IDLE_Interrupt_Handler(USART_Driver_t *usart)
{if (!usart) return;if (__HAL_UART_GET_FLAG(usart->huart, UART_FLAG_IDLE)) {__HAL_UART_CLEAR_IDLEFLAG(usart->huart);USART_DMA_RX_Copy(usart);}
}/*** @brief DMA发送完成回调(由用户在HAL库TxCpltCallback中调用)* @param usart 指向USART驱动结构体的指针* @note 一定要调用,否则无法再次DMA发送* @retval 无*/
void USART_DMA_TX_Interrupt_Handler(USART_Driver_t *usart)
{if (!usart) return;usart->txDMABusy = 0;
}/*** @brief 将数据写入指定USART驱动的发送 RingBuffer 中* @param usart 指向USART驱动结构体的指针* @param data 指向要写入的数据缓冲区* @param len 要写入的数据长度(字节)* @retval 0 数据成功写入,无数据丢弃* @retval 1 ringbuffer 空间不足,丢弃部分旧数据以容纳新数据* @retval 2 数据长度超过 ringbuffer 总容量,仅保留最新 TX_BUFFER_SIZE 字节* @retval 3 输入数据指针为空* @note* - 使用 lwrb 库操作发送 RingBuffer(usart->txRB)。* - 若 len > ringbuffer 容量,会自动截断,仅保留最新的数据。* - 若空间不足,将调用 lwrb_skip() 丢弃部分旧数据。*/
uint8_t USART_Put_TxData_To_Ringbuffer(USART_Driver_t *usart, const void* data, uint16_t len)
{if (!usart || !data) return 3; //! 检查输入数据指针有效性lwrb_t *rb = &usart->txRB;uint16_t capacity = usart->txBufSize;lwrb_sz_t freeSpace = lwrb_get_free(rb);uint8_t ret = 0;//! 情况1:数据长度小于ringbuffer容量if (len < capacity) {if (len <= freeSpace) {lwrb_write(rb, data, len); //! 剩余空间充足,直接写入} else {//! 空间不足,需丢弃部分旧数据lwrb_sz_t used = lwrb_get_full(rb);lwrb_sz_t skip_len = len - freeSpace;if (skip_len > used) skip_len = used;lwrb_skip(rb, skip_len);lwrb_write(rb, data, len);ret = 1;}} else if (len == capacity) { //! 情况2:数据长度等于ringbuffer容量if (freeSpace < capacity) { //! 如果ringbuffer已有数据lwrb_reset(rb);ret = 1;}lwrb_write(rb, data, len);} else { //! 情况3:数据长度大于ringbuffer容量,仅保留最后 capacity 字节const uint8_t *ptr = (const uint8_t*)data + (len - capacity);lwrb_reset(rb);lwrb_write(rb, ptr, capacity);ret = 2;}return ret;
}/*** @brief 初始化USART驱动,配置DMA、RingBuffer与中断* @param usart 指向USART驱动结构体的指针* @param rxDMABuffer DMA接收缓冲区指针* @param rxRBBuffer 接收RingBuffer缓冲区指针* @param rxBufSize 接收缓冲区大小* @param txDMABuffer DMA发送缓冲区指针* @param txRBBuffer 发送RingBuffer缓冲区指针* @param txBufSize 发送缓冲区大小* @retval 无* @note 需先通过CubeMX完成串口、DMA相关硬件配置和句柄赋值*/
void USART_Config(USART_Driver_t *usart,uint8_t *rxDMABuffer, uint8_t *rxRBBuffer, uint16_t rxBufSize,uint8_t *txDMABuffer, uint8_t *txRBBuffer, uint16_t txBufSize)
{if (!usart) return;usart->rxDMABuffer = rxDMABuffer;usart->rxRBBuffer = rxRBBuffer;usart->rxBufSize = rxBufSize;lwrb_init(&usart->rxRB, usart->rxRBBuffer, usart->rxBufSize);usart->txDMABuffer = txDMABuffer;usart->txRBBuffer = txRBBuffer;usart->txBufSize = txBufSize;lwrb_init(&usart->txRB, usart->txRBBuffer, usart->txBufSize);USART_Received_DMA_Configure(usart); // 初始化DMA RXusart->txDMABusy = 0;usart->dmaRxLastPos = 0;usart->rxMsgCount = 0;usart->txMsgCount = 0;usart->errorDMATX = 0;usart->errorDMARX = 0;usart->errorRX = 0;
}/*** @brief USART模块主循环调度函数(DMA + RingBuffer高效收发)* @param usart 指向USART驱动结构体的指针* @note 建议主循环定时(如1ms)调用* - 检查发送RingBuffer是否有待发送数据,且DMA当前空闲* - 若条件满足,从发送RingBuffer读取一段数据到DMA发送缓冲区,并通过DMA启动异步发送* - 自动维护已发送数据统计* @retval 无*/
void USART_Module_Run(USART_Driver_t *usart)
{if (!usart) return;uint16_t available = lwrb_get_full(&usart->txRB);if (available && usart->txDMABusy == 0) {uint16_t len = (available > usart->txBufSize) ? usart->txBufSize : available;lwrb_read(&usart->txRB, usart->txDMABuffer, len);usart->txMsgCount += len;USART_SendString_DMA(usart, usart->txDMABuffer, len);}
}/*** @brief 获取USART接收RingBuffer中的可读字节数* @param usart 指向USART驱动结构体的指针* @retval uint32_t 可读取的数据字节数* @note 通常在主循环或数据解析前调用,用于判断是否需要读取数据。*/
uint32_t USART_Get_The_Existing_Amount_Of_Data(USART_Driver_t *usart)
{if (!usart) return 0;return lwrb_get_full(&usart->rxRB);
}/*** @brief 从USART接收RingBuffer中读取一个字节数据* @param usart 指向USART驱动结构体的指针* @param data 指向存放读取结果的缓冲区指针* @retval 1 读取成功,有新数据存入 *data* @retval 0 读取失败(无数据或data为NULL)* @note 本函数不会阻塞,无数据时直接返回0。*/
uint8_t USART_Take_A_Piece_Of_Data(USART_Driver_t *usart, uint8_t* data)
{if (!usart || !data) return 0;return lwrb_read(&usart->rxRB, data, 1);
}/*** @brief DMA传输错误后的自动恢复操作(含错误统计)* @param usart 指向USART驱动结构体* @param dir 方向:0=RX, 1=TX* @note 检测到DMA传输错误(TE)时调用,自动进行统计并恢复* RX方向会自动重启DMA,TX方向建议等待主循环调度新发送* @retval 无*/
void USART_DMA_Error_Recover(USART_Driver_t *usart, uint8_t dir)
{if (!usart) return;if (dir == 0) { //! RX方向usart->errorDMARX++; //! DMA接收错误计数 */HAL_DMA_Abort(usart->hdma_rx);HAL_UART_Receive_DMA(usart->huart, usart->rxDMABuffer, usart->rxBufSize);//! 可以加入极端情况下的USART复位等} else { //! TX方向usart->errorDMATX++; //! DMA发送错误计数 */HAL_DMA_Abort(usart->hdma_tx);//! 一般等待主循环触发新的DMA发送}//! 可插入报警、日志
}
2.3、stm32f1xx_it.c
2.4、main.c
缓存分别是DMA缓存与ringbuffer缓存。
2.5、编译代码、下载代码
三、测试代码
如上所示,从成员rxMsgCount与txMsgCount看到,数据在正常收发。errorDMATX、errorDMARX、errorRX一直保持0,证明没有发生错误。
相关文章:

STM32F103 HAL多实例通用USART驱动 - 高效DMA+RingBuffer方案,量产级工程模板
导言 《STM32F103_LL库寄存器学习笔记12.2 - 串口DMA高效收发实战2:进一步提高串口接收的效率》前阵子完成的LL库与寄存器版本的代码,有一个明显的缺点是不支持多实例化。最近,计划基于HAL库系统地梳理一遍bootloader程序开发。在bootloader程…...

HTML回顾
html全称:HyperText Markup Language(超文本标记语言) 注重标签语义,而不是默认效果 规则 块级元素包括: marquee、div等 行内元素包括: span、input等 规则1:块级元素中能写:行内元素、块级元素&…...

机器视觉6-halcon高级教程
机器视觉6-halcon高级教程 双目立体视觉原理视差外极线几何双目标定 双目立体视觉之Halcon标定一.标定结果二.Halcon标定过程1.获取左右相机图像中标定板的区域;2.提取左右相机图像中标定板的MARK点坐标和摄像机外部参数;3.执行双目标定;4.获取非标准外极线几何到标…...

YOLOv8 的双 Backbone 架构:解锁目标检测新性能
一、开篇:为何踏上双 Backbone 探索之路 在目标检测的领域中,YOLOv8 凭借其高效与精准脱颖而出,成为众多开发者和研究者的得力工具。然而,传统的单 Backbone 架构,尽管已经在诸多场景中表现出色,但仍存在一…...

1.4 TypeScript 编译是如何工作的?
TypeScript 是 JavaScript 的超集,最显著的优势是引入了静态类型检查。它能帮助开发者在编写代码阶段捕获错误,从而提升代码的健壮性和可维护性。虽然 TypeScript 本身不能直接在浏览器或 Node.js 中运行,但它可以被编译成标准的 JavaScript&…...
【HTML-4】HTML段落标签:构建内容结构的基础
在网页开发中,段落标签<p>是最基础也是最重要的HTML元素之一。这篇博客将深入探讨段落标签的用法、最佳实践以及相关技术细节。 1. 段落标签的基本用法 HTML段落标签用于定义文本段落,浏览器会自动在段落前后添加一定的空白(margin&a…...
国际前沿知识系列五:时间序列建模方法在头部撞击运动学测量数据降噪中的应用
目录 国际前沿知识系列五:时间序列建模方法在头部撞击运动学测量数据降噪中的应用 一、引言 二、时间序列建模方法 (一)ARIMA 模型 (二)指数平滑法 (三)小波变换 三、实际案例分析 &…...
未授权访问漏洞利用链实战总结
一、渗透测试核心思路 攻击链路径: 未授权访问 → 接口信息泄露 → 敏感数据获取 → 账户爆破 → 权限提升 → 系统控制 二、关键步骤拆解与分析 信息收集阶段 初始突破口: 系统登录页看似无效,但通过JS文件分析发现隐藏接口(如 …...
Centos上搭建 OpenResty
一、OpenResty简介 OpenResty 是基于 Nginx 的扩展平台,完全兼容 Nginx 的核心功能(如 HTTP 服务和反向代理),同时通过内嵌 LuaJIT 支持,允许开发者用 Lua 脚本灵活扩展业务逻辑。它简化了动态逻辑的实现。 二、安装…...

Web 服务、 Nfs 服务器以及 Dns 服务器综合实验
要求: 1.web 服务的资源文件通过 nfs 服务器共享 www.luntan.com 2.确保所有主机时间同步 3.定义本地 dns 服务器解析 web 主机域名 实验: 主机服务程序192.168.96.142dns、nfs192.168.96.132web 服务器说明: 设备 IP服务端 192…...
保证数据库 + redis在读写分离场景中事务的一致性
在 Spring Boot 中实现数据库与 Redis 的一致性,特别是处理读写分离时,确保数据修改的事务一致性是一个常见的挑战。因为 Redis 是一个内存数据库,通常用于缓存,而关系型数据库是持久化存储,两者之间的数据同步和一致性…...

汇编语言的子程序魔法:解锁四则运算的奥秘
在嵌入式系统的世界里,汇编语言就像是魔法师手中的魔杖,能够直接操控硬件,实现各种神奇的功能。今天,我将带你走进一场充满乐趣的实验:如何用汇编语言实现四则运算,并将它们封装成子程序。这不仅是一次技术…...

快速解决Linux 中yum镜像拉取失败问题
在linux中使用yum命令拉取镜像的时候,如果出现如下类似报错: 我这里是安装Erlang环境也是同样报错: 其实就是网络环境的问题,更换为国内的镜像源就行了,可以选择cmd的ssh连接方式(命令:ssh root192.168.xxx…...
C#核心概念解析:析构函数、readonly与this关键字
🔍 析构函数:资源清理的最后防线 核心作用 析构函数(~ClassName)在对象销毁前执行,专用于释放非托管资源(如文件句柄、非托管内存)。托管资源(如.NET对象)由GC自动回收…...

HarmonyOS基础组件:Button三种类型的使用
简介 HarmonyOS在明年将正式不再兼容Android原生功能,这意味着对于客户端的小伙伴不得不开始学习HarmonyOS开发语言。本篇文章主要介绍鸿蒙中的Button使用。 HarmonyOS中的Button相较于Android原生来说,功能比较丰富,扩展性高,减…...
深入理解设计模式之适配器模式
深入理解设计模式之适配器模式 1. 适配器模式概述 适配器模式(Adapter Pattern)是一种结构型设计模式,它允许将一个类的接口转换为客户端所期望的另一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的类能够协同工作,扮演了"转换器&quo…...
预训练模型:深度学习的通用特征引擎
预训练模型是深度学习领域的重要技术,其核心思想是通过大规模数据预先学习通用特征,再迁移到具体任务中进行微调。以下是其定义、原理及与其他模型的对比分析: 一、预训练模型的定义与原理 基本概念 预训练模型(Pre-trained Model…...
C++题解(33)2025年顺德区中小学生程序设计展示活动(初中组C++)U560876 美丽数(一)和 U560878 美丽数(二)题解
U560876 美丽数(一) 题目描述 小明很喜欢3和5这两个数字,他将能被3或5整除的数叫做美丽数。现在给你一个整数n,你能告诉小明第n个美丽数是多少吗? 输入格式 输入有多行,每行只有一个整数${n_i}$。 输出格式…...

产业互联网+三融战略:重构企业增长密码
产业互联网时代:用"三融"重构企业增长飞轮 在产业互联网浪潮下,企业面临资源分散、资金短缺、人才难聚的三重挑战。本文提出的"融人、融资、融资源"顶层设计,正为新时代企业构建增长新引擎。 一、三级合伙人体系&#x…...
centos yum源,docker源
yum源repo文件: wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repodocker源repo文件: yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo安装docker和docker c…...
通过设备节点获取已注册的 i2c client
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言代码分析 前言 另一个驱动通过设备节点 获取已注册的i2c client 代码分析 #include <linux/kernel.h> #include <linux/init.h> #include <li…...

Centos系统资源镜像配置
主要体现 yum 命令执行报错,排除网络连接问题 解决步骤: 下载安装工具 # 安装 wget curl vim yum install -y wget curl vim 原有repo文件备份 # 进入配置文件所在文件夹 cd /etc/yum.repos.d# 创建 backup 文件夹 mkdir backup# 备份文件放置文件夹 m…...

【Linux网络篇】:Socket网络套接字以及简单的UDP网络程序编写
✨感谢您阅读本篇文章,文章内容是个人学习笔记的整理,如果哪里有误的话还请您指正噢✨ ✨ 个人主页:余辉zmh–CSDN博客 ✨ 文章所属专栏:Linux篇–CSDN博客 文章目录 网络编程套接字一.预备知识1.理解源IP地址和目的IP地址2.认识端…...

学习路之uniapp--unipush2.0推送功能--给自己发通知
学习路之uniapp--unipush2.0推送功能--给自己发通知 一、绑定云空间及创建云函数二、编写发送界面三、效果后期展望: 一、绑定云空间及创建云函数 package.json {"name": "server-push","dependencies": {},"main": "…...
Java面向对象 一
系列文章目录 Java面向对象 二-CSDN博客 目录 系列文章目录 前言 一、初步认识面向对象 1.类和对象的简单理解 2.类的构成 二、类的实例化 1.对象的创建 2.对象的初始化 三、this引用的作用 四、构造方法 1.构造方法的提供 2.对象的构造 3.构造方法的重载 4.th…...
怎么开发一个网络协议模块(C语言框架)之(二) 数据结构设计
一、数据结构设计模板分析 (gdb) p gVrrpInstance $3 = { INT4 socketV4 = 107, .... vrrpStatisticsEntry_t SvrrpStatistics = {delIp4Count = 0, delIp6Count = 0, delIp4Error = 0, delIp6Error = 0, addIp4Count = 0, addIp6Count = 3, addIp4Error = 0, addIp6Error …...
30天自制操作系统day5(vram和显存)(GDT和IDT)(c语言结构体)(汇编-c)(ai辅助整理)
day5 harib02d c语言结构体的一些解释 struct BOOTINFO { char cyls, leds, vmode, reserve; short scrnx, scrny; char *vram; }; //最开始的struct命令只是把一串变量声明集中起来,统一叫做“struct BOOTINFO”。 //最初是1字节的变量cyls,接着是1字…...
【音频】drc 限幅器、多带限幅器、压缩器、多带压缩器
以下是关于 DRC 限幅器、多带限幅器、压缩器、多带压缩器的详细解释,它们均为音频处理领域的动态范围控制设备,主要用于调整音频信号的动态范围(即最大音量与最小音量的差值),以优化音质或满足特定播放需求: 一、DRC 限幅器(Dynamic Range Compression Limiter) 核心功…...

leetcode hot100刷题日记——12.反转链表
解答: /*** Definition for singly-linked list.* struct ListNode {* int val;* ListNode *next;* ListNode() : val(0), next(nullptr) {}* ListNode(int x) : val(x), next(nullptr) {}* ListNode(int x, ListNode *next) : val(x), next(n…...
osgEarth中视角由跟随模式切换到漫游模式后没有鼠标拖拽功能问题分析及解决方法
遇到了一个棘手的问题,就是在由跟随模式切换到漫游模式的时候,鼠标无法实现拖拽功能。后来发现是前面给自己挖的坑。 因为要实现鼠标点选某个模型后,模型需要变红色显示,所以添加了一个事件处理程序。 // 创建 场景中模型的点选功能 事件处理程序 ModelSelectionHandler* …...