串口解析
串口的配置
模式
异步模式(Asynchronous):这是最标准的串口通信模式。数据以字符帧的形式传输 包含起始位 数据位 校验位 停止位。通信双方不需要共享时钟 依靠内部时钟和波特率进行同步
同步模式(Synchronous):在此模式下 发送放需要额外提供一个时钟信号给接收方 用于同步数据传输。数据传输速率可以更高 且对时钟精度要求低。
半线半双工(Single Wire/Half-Duplex):仅用一根数据线(TX)进行发送和接收。需要精确控制数据方向的切换。
其他
波特率(Baud Rate)
波特率定义了每秒传输的比特数,是串口通信速率的关键参数。
115200非常常用,还有9600 19200 等
数据位(Word Length)
每次数据帧中包含的数据位数,例如7bits 8bits 9bits
如果使用校验位(Oarity),实际数据位会少一位成为校验位。
停止位(Stop Bits)
停止位用于明确标识一个数据帧的结束,并提供接收方处理数据所需的时间间隙
常用的有1Stop Bits还有2Stop Bits 1.5/0.5Stop Bits。
校验位(Parity)
有奇校验和偶校验 一般不选校验位
控制流(Flow Contronl)
硬件流控制 (Hardware Flow Control) 使用额外的 RTS (Request To Send) 和 CTS (Clear To Send) 信号线来协调数据传输,防止因接收方处理不过来而导致数据丢失。
过采样(Over Samping)
过采样是指 UART 接收器在一个比特周期内对 RX 线进行多次采样的技术。目的是更准确地找到每个比特位的中心点,以正确解码数据,并提高对噪声和时钟偏差的容忍度
中断(Interrupts)
UART/USART 可以产生多种中断事件,允许 CPU 在特定条件发生时执行相应的处理程序,而不是持续轮询状态寄存器。
RXNE (Read Data Register Not Empty) 中断:
触发条件: 当接收数据寄存器 (RDR) 中有新的数据到达时触发。
用途: 常用接收 最常用的接收中断。在中断服务程序 (ISR) 中读取 RDR 即可清除此中断标志。
应用场景: 处理少量、低速的串口数据接收。
注意: 如果 CPU 处理不够快,高速数据流可能导致 ORE (Overrun Error) 错误。
TXE (Transmit Data Register Empty) 中断:
触发条件: 当发送数据寄存器 (TDR) 为空,可以写入下一个待发送数据时触发。
用途: 实现非阻塞发送。在 ISR 中向 TDR 写入数据。当所有数据发送完毕后,需要禁用此中断,否则会一直触发。
应用场景: 发送少量数据,避免程序阻塞等待发送完成。效率低于 DMA 发送。
TC (Transmission Complete) 中断:
触发条件: 当发送移位寄存器中的数据完全发送出去,并且 TDR 也为空时触发。表示一帧数据(包括停止位)已完全发送完毕。
用途: RS485 确认数据完全发送完成,常用于半双工模式(如 RS485)切换发送/接收状态。
应用场景: RS485 通信控制方向切换、精确控制发送结束时间点。
IDLE (Idle Line Detected) 中断:
触发条件: 当 RX 线上检测到持续一个完整帧时间的空闲状态(高电平)时触发。
用途: DMA接收 通常配合 DMA 接收使用,用于检测一包(不定长)数据的结束。
应用场景: 高效处理不定长数据包接收。在 IDLE 中断 ISR 中,可以读取 DMA 已传输的字节数,获知本次接收到的数据长度,然后重置 DMA。
注意: 需要先读状态寄存器(SR)再读数据寄存器(DR)来清除 IDLE 标志。
错误中断 (PE, FE, NE, ORE):
PE (Parity Error): 接收到的数据奇偶校验失败。
FE (Framing Error): 未检测到有效的停止位。
NE (Noise Error): 在数据帧期间检测到噪声(需要过采样支持)。
ORE (Overrun Error): 常见问题 在上一个数据还未被读取时,新的数据覆盖了接收寄存器。通常发生在 CPU 处理速度跟不上数据接收速度时。
用途: 诊断通信链路问题,进行错误统计或恢复处理。
DMA配置
DMA (Direct Memory Access) 控制器允许外设 (如 UART) 与内存之间直接传输数据,无需 CPU 的持续干预,极大地提高了数据传输效率并降低了 CPU 负载
在 CubeMX 中,为 UART 配置 DMA 通常涉及以下步骤:
在 “Pinout & Configuration” 视图中选择目标 UART 外设 (e.g., USART1)。
在下方的 “Configuration” 面板中,切换到 “DMA Settings” 标签页。
点击 “Add” 按钮,选择需要添加的 DMA 请求:
USARTx_RX: 用于串口接收。
USARTx_TX: 用于串口发送。
为每个添加的 DMA 请求进行配置:
DMA Request: 已选定 (e.g., USART1_RX)。
Stream: 选择一个可用的 DMA 通道/流 (e.g., DMA2 Stream 5)。CubeMX 通常会自动选择一个。
Direction: 方向。对于 RX,是 Peripheral To Memory;对于 TX,是 Memory To Peripheral。CubeMX 会自动设置。
Priority: DMA 通道优先级 (Low, Medium, High, Very High)。当多个 DMA 请求同时发生时,优先级高的先被响应。
Mode:
Normal: 传输指定数量的数据后停止。常用于 TX。
Circular: 传输到缓冲区末尾后自动回到开头继续传输。常用于 RX,实现环形缓冲区。
Increment Address:
Peripheral: 外设地址是否自增。对于 UART,数据寄存器地址固定,不勾选。
Memory: 内存地址是否自增。对于 UART 收发,数据需要在内存缓冲区中移动,勾选。
Data Width:
Peripheral: 外设数据宽度。对于 UART,通常是 Byte。
Memory: 内存数据宽度。通常也设置为 Byte,与外设匹配。
Use FIFO / FIFO Threshold: 是否使用 DMA 的 FIFO 缓冲及其阈值。通常保持默认即可,除非有特殊性能优化需求。
(可选) 在 “NVIC Settings” 标签页中,启用 DMA 传输完成中断 (DMAx_Streamy_IRQn) 或半满中断 (HT) 或错误中断 (TE)。对于 UART DMA,通常不直接使用 DMA 完成中断,而是依赖 UART 自身的 IDLE 中断或应用层逻辑。
串口初始化流程
使能GPIO时钟(以PA端口为例)
__HAL_RCC_GPIOA_CLK_ENABLE();
配置GPIO引脚为复用功能
GPIO_MODE_AF_PP:复用推挽模式(常见选择,引脚可输出高低电平)。
GPIO_MODE_AF_OD:复用开漏模式(需外部上拉电阻,某些协议可能使用)。
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
设置复用引脚功能编号
GPIO 引脚可以复用到多个外设(如 UART、SPI 等),因此需要指定具体的复用功能编号(Alternate Function Number),以明确连接到哪个 USART/UART 外设。
如何确定编号
查阅 STM32 数据手册中的“Alternate Function Mapping”表。例如,GPIOA 的引脚 9 和 10 用于 USART1 时,复用编号可能是 GPIO_AF7_USART1。
GPIO_InitStruct.Alternate = GPIO_AF7_USART1;
配置引脚的具体参数
GPIO_InitStruct.Pin = GPIO_PIN_9;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
GPIO_InitStruct.Pull = GPIO_NOPULL;
使能USART/UART时钟
__HAL_RCC_USART1_CLK_ENABLE();
配置串口参数
UART_HandleTypeDef huart1;
huart1.Instance = USART1;
huart1.Init.BaudRate = 115200; // 波特率
huart1.Init.WordLength = UART_WORDLENGTH_8B; // 8 位数据
huart1.Init.StopBits = UART_STOPBITS_1; // 1 停止位
huart1.Init.Parity = UART_PARITY_NONE; // 无校验
huart1.Init.Mode = UART_MODE_TX_RX; // 发送和接收模式
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE; // 无硬件流控制
huart1.Init.OverSampling = UART_OVERSAMPLING_16; // 16 倍过采样
HAL_UART_Init(&huart1);
使能UART/USART
HAL_UART_Init(&huart1);
完整代码演示
#include "stm32fxxx_hal.h" // 根据你的 STM32 型号替换 xxx
UART_HandleTypeDef huart1;
void MX_GPIO_Init(void)
{
// 步骤 1:使能 GPIOA 时钟
__HAL_RCC_GPIOA_CLK_ENABLE();
GPIO_InitTypeDef GPIO_InitStruct = {0};
// 步骤 2-4:配置 GPIOA 引脚 9 为 USART1_TX
GPIO_InitStruct.Pin = GPIO_PIN_9;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF7_USART1;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
// 配置 GPIOA 引脚 10 为 USART1_RX
GPIO_InitStruct.Pin = GPIO_PIN_10;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}
void MX_USART1_UART_Init(void)
{
// 步骤 5:使能 USART1 时钟
__HAL_RCC_USART1_CLK_ENABLE();
// 步骤 6:配置 USART1 参数
huart1.Instance = USART1;
huart1.Init.BaudRate = 115200;
huart1.Init.WordLength = UART_WORDLENGTH_8B;
huart1.Init.StopBits = UART_STOPBITS_1;
huart1.Init.Parity = UART_PARITY_NONE;
huart1.Init.Mode = UART_MODE_TX_RX;
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart1.Init.OverSampling = UART_OVERSAMPLING_16;
// 步骤 7:使能 USART1
HAL_UART_Init(&huart1);
}
int main(void)
{
HAL_Init(); // 初始化 HAL 库
SystemClock_Config(); // 配置系统时钟(需自行实现)
MX_GPIO_Init(); // 初始化 GPIO
MX_USART1_UART_Init(); // 初始化 USART1
// 发送测试消息
char *msg = "Hello, STM32!\r\n";
HAL_UART_Transmit(&huart1, (uint8_t*)msg, strlen(msg), HAL_MAX_DELAY);
while (1)
{
// 主循环
}
}
配置复用引脚的作用
配置 GPIO_InitStruct.Alternate = GPIO_AF7_USART1; 的目的是明确指定 GPIO 引脚的复用功能,使其与 USART1 外设正确连接。这是 STM32 HAL 库中串口 GPIO 初始化不可或缺的一步,具体作用包括:
将 GPIO 引脚的控制权交给 USART1。
确保串口通信的正常运行。
避免引脚功能冲突或失效。
简单来说,这句话就像是给 GPIO 引脚贴上一个标签,告诉 STM32:“这个引脚是给 USART1 用的!” 如果没有它,串口功能就无法实现。
串口的超时解析
它的核心思想是:
设置一个接收缓冲区(就是一个数组),用来存放收到的字节。
启动 UART 接收(通常使用中断方式,每收到一个字节就触发一次中断)。
在中断服务函数里:
将收到的字节存入缓冲区。
记录当前收到字节的时间(或者说重置一个计时器)。
再次启动下一次接收。
在主循环(或一个定时任务)里,不断检查:当前时间距离上次收到字节的时间,是否超过了预设的超时时间?
如果超过了超时时间,并且缓冲区里有数据,就说明一帧数据接收完毕!可以对缓冲区里的数据进行处理了。处理完后,清空缓冲区,准备接收下一帧。
这种方法非常灵活,不需要知道数据具体长度,也不依赖特殊结束符。接下来,我们就用代码来实现这个逻辑。
代码解析
用来把接收的的数据(buffer) 发送出去(串口)
int my_printf(UART_HandleTypeDef *huart, const char *format, ...)
{
char buffer[512]; // 临时存储格式化后的字符串
va_list arg; // 处理可变参数
int len; // 最终字符串长度
va_start(arg, format);
// 安全地格式化字符串到 buffer
len = vsnprintf(buffer, sizeof(buffer), format, arg);
va_end(arg);
// 通过 HAL 库发送 buffer 中的内容
HAL_UART_Transmit(huart, (uint8_t *)buffer, (uint16_t)len, 0xFF);
return len;
}
HAL_UART_Transmit(huart, (uint8_t *)buffer, (uint16_t)len, 0xFF);将my_printf传入的buffer通过串口发送出去。指定发送操作的超时时间,单位为毫秒。这里 0xFF 等于 255 毫秒,表示如果数据发送未在 255 毫秒内完成,函数将返回超时错误。
在你的 my_printf 函数中,HAL_UART_Transmit 用于将格式化后的字符串(存储在 buffer 中)通过 UART 接口发送到外部设备,例如:
调试终端(如串口调试助手)。
其他微控制器或设备。
串口通信模块(如蓝牙模块 HC-05 或 Wi-Fi 模块)。
回调函数,在接受完一个字节时被调用
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
// 1. 核对身份:是 USART1 的快递员吗?
if (huart->Instance == USART1)
{
// 2. 更新收货时间:记录下当前时间
uart_rx_ticks = uwTick;
// 3. 货物入库:将收到的字节放入缓冲区(HAL库已自动完成)
// 并增加计数器
// (注意:实际入库由 HAL_UART_Receive_IT 触发,这里只更新计数)
uart_rx_index++;
// 4. 准备下次收货:再次告诉硬件,我还想收一个字节
HAL_UART_Receive_IT(&huart1, &uart_rx_buffer[uart_rx_index], 1);
}
}
HAL_UART_Receive_IT这个函数将串口1(huart1)的接收地址指向 uart_rx_buffer 中的指定位置,并在接收完成一个字节数据后触发接收中断(而中断的处理程序会在 ISR 中运行)。需要在初始化时就调用此函数不然无法接受第一次数据也就无法进入这个中断回调函数
进行解析
void uart_task(void)
{
// 1. 检查货架:如果计数器为0,说明没货或刚处理完,休息。
if (uart_rx_index == 0)
return;
// 2. 检查手表:当前时间 - 最后收货时间 > 规定的超时时间?
if (uwTick - uart_rx_ticks > UART_TIMEOUT_MS) // 核心判断
{
// --- 3. 超时!开始理货 ---
// "uart_rx_buffer" 里从第0个到第 "uart_rx_index - 1" 个
// 就是我们等到的一整批货(一帧数据)
my_printf(&huart1, "uart data: %s\n", uart_rx_buffer);
// (在这里加入你自己的处理逻辑,比如解析命令控制LED)
// --- 理货结束 ---
// 4. 清理现场:把处理完的货从货架上拿走,计数器归零
memset(uart_rx_buffer, 0, uart_rx_index);
uart_rx_index = 0;
}
// 如果没超时,啥也不做,等下次再检查
}
将它放在任务调度器里循环调用当超过时间进行解析通过``my_printf`函数把buffer里的数据通过串口huart1发送出去,再进行清理现场。
DMA的配置及初始化
模式选择
字符宽度:
定义每次传输的数据位数(通常8位)。
影响数据帧结构和通信兼容性,需与通信双方保持一致。
初始化流程
void MX_DMA_Init(void)
{
/* DMA controller clock enable */
__HAL_RCC_DMA2_CLK_ENABLE();
/* DMA interrupt init */
/* DMA2_Stream2_IRQn interrupt configuration */
HAL_NVIC_SetPriority(DMA2_Stream2_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(DMA2_Stream2_IRQn);
}
启用 DMA 控制器时钟
__HAL_RCC_DMA2_CLK_ENABLE();
功能:启用 DMA2 控制器的时钟。
背景:在 STM32 微控制器中,所有外设(包括 DMA)在工作前必须启用其时钟信号。时钟由 RCC(复位和时钟控制)模块管理。
实现:__HAL_RCC_DMA2_CLK_ENABLE() 是一个宏,来自 STM32 的 HAL(硬件抽象层)库。它通过设置 RCC 寄存器中的特定位(例如 RCC_AHB1ENR 中的 DMA2 时钟使能位)来启用 DMA2 的时钟。
作用:没有时钟信号,DMA 模块无法运行,因此这是初始化的第一步。
注意:代码中使用的是 DMA2,而不是 DMA1,这取决于具体项目中使用的 DMA 控制器。STM32 型号不同,可能有多个 DMA 控制器(如 DMA1、DMA2),需参考芯片手册确认。
设置中断优先级
HAL_NVIC_SetPriority(DMA2_Stream2_IRQn, 0, 0);
功能:设置 DMA2 Stream 2 中断的优先级。
参数:
DMA2_Stream2_IRQn:中断号,表示 DMA2 Stream 2 的中断请求。
第一个 0:优先级组(Preemption Priority,主优先级),值越低优先级越高。
第二个 0:子优先级(Subpriority),用于在主优先级相同时进一步区分。
实现:HAL_NVIC_SetPriority() 是 HAL 库提供的函数,用于配置 NVIC(嵌套向量中断控制器)的中断优先级。
作用:优先级决定了当多个中断同时触发时,CPU 响应哪个中断。在这里,0, 0 表示最高优先级(具体优先级机制取决于 STM32 的 NVIC 配置)。
注意:优先级值的具体含义可能因项目中 NVIC 的分组设置而异,需参考 NVIC_PriorityGroupConfig 的配置。
启用中断
HAL_NVIC_EnableIRQ(DMA2_Stream2_IRQn);
功能:启用 DMA2 Stream 2 的中断请求。
实现:HAL_NVIC_EnableIRQ() 是 HAL 库函数,通过设置 NVIC 寄存器中的使能位,允许 DMA2 Stream 2 在特定事件(如传输完成或错误)时触发中断。
作用:启用后,当 DMA Stream 2 完成数据传输或发生其他配置的事件时,会向 CPU 发送中断信号,CPU 将跳转到对应的中断服务函数(例如 DMA2_Stream2_IRQHandler)处理。
代码整体功能:
时钟启用:通过 __HAL_RCC_DMA2_CLK_ENABLE() 为 DMA2 控制器提供时钟信号,使其能够运行。
中断配置:通过 HAL_NVIC_SetPriority() 和 HAL_NVIC_EnableIRQ() 配置并启用 DMA2 Stream 2 的中断,使 CPU 能在特定事件发生时得到通知。
典型用途:这段代码是为 DMA2 Stream 2 准备的初始化,可能用于数据传输任务(如 UART、SPI 或 ADC 的数据搬运)。后续代码通常会进一步配置 DMA 的传输参数(如源地址、目标地址、数据量等)。
调用位置:在 STM32CubeMX 生成的项目中,MX_DMA_Init() 通常在 main.c 的初始化部分调用,例如在 main() 的硬件初始化阶段。
DMA+空闲中断
HAL_StatusTypeDef HAL_UARTEx_ReceiveToIdle_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);
告诉串口如何获取信息接收函数,指定串口 存入的哪个数组 数据的大小
在串口初始化时调用
__HAL_DMA_DISABLE_IT(&, DMA_IT_HT);
关闭半满中断
hdma_usart1_rx是dma的句柄
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)空闲中断的回调函数
当 UART 检测到总线空闲(IDLE 事件),或者 DMA 传输达到预期的一半/全部(如果使能了 HT/TC 中断)时,会触发 UART 的全局中断。
Size: 至关重要! 这个参数告诉你,从上次启动 HAL_UARTEx_ReceiveToIdle_DMA 到本次事件(特别是空闲事件)发生时,DMA 搬运工实际搬运了多少个字节的数据到内存缓冲区。这个值可能小于、等于你启动 DMA 时设定的缓冲区总大小。
空闲中断的定义
在 UART 通信中,当没有数据传输时,通信线路(RX 线)会保持在高电平状态(逻辑 ‘1’),这被称为"空闲状态"或"标记状态 (Mark State)"。
所谓的"检测到空闲",是指硬件检测到 RX 线上连续保持高电平的时间,超过了一个完整数据帧(包括起始位、数据位、可能的校验位和停止位)所需要的时间。这通常意味着发送方已经发送完了最后一个字节的停止位,并且之后没有紧接着发送新的起始位,表明一帧(或一个数据包)的传输告一段落。
中断是如何触发的(确保开启了串口中断)
我们已经接触了各种回调函数,它们像是在特定时刻被触发的"警报响应"。但这些响应是如何被精确调度的?真正的"大戏"发生在 UART 的"中断控制室"——HAL_UART_IRQHandler 函数中
当你调用
HAL_UART_Receive_IT(&huart1, buffer, 10)
HAL 库内部会打开 RXNEIE(允许接收中断)和相关的错误中断开关 (PEIE, EIE)。
它还会记录下你的缓冲区地址、期望接收的字节数 (10),并将 UART 状态设置为"忙于中断接收"。
这时,控制室的"剧本"就是:主要关注 RXNE 信号,每次收到字节就存起来并计数,直到收满 10 个,然后调用 RxCpltCallback。
当你调用
HAL_UARTEx_ReceiveToIdle_DMA(&huart1, dma_buffer, size)
HAL 库会打开 IDLEIE(允许空闲中断)和错误中断开关。
它会配置好 DMA 控制器,让 DMA 开始工作,并可能打开 DMA 相关的中断(虽然我们可能手动关掉 HT)。
UART 状态设置为"忙于 DMA 接收"。
这时,控制室的"剧本"变为:主要关注 IDLE 信号,一旦触发,就去询问 DMA 收了多少数据,然后调用 RxEventCallback。RXNE 信号(单个字节到达)通常会被忽略或不由 UART IRQHandler 直接处理(因为 DMA 在后台做了)。
因此,HAL_UART_IRQHandler 本身是一个通用的中断处理框架。而你选择使用的具体 HAL 函数(_IT, _DMA, Ex 等),就如同给这个框架加载了不同的"插件"和"配置",决定了它在中断发生时具体会执行哪个流程分支,以及最终会触发哪个回调函数来通知你的应用程序。
环形缓存区(ringbuffer)
struct rt_ringbuffer
{
rt_uint8_t *buffer_ptr; // 指向实际存储数据的内存区域
rt_uint16_t read_mirror : 1; // 读指针的镜像位 (0 或 1)
rt_uint16_t read_index : 15; // 读指针索引 (0 ~ 32767)
rt_uint16_t write_mirror : 1;// 写指针的镜像位 (0 或 1)
rt_uint16_t write_index : 15;// 写指针索引 (0 ~ 32767)
rt_int16_t buffer_size; // 缓冲区总大小 (字节)
};
定义一个ringbuffer需要的结构体,在后面初始化时使用
buffer_ptr: 指向你分配的用于存储数据的内存块(比如一个全局数组)。
read_index / write_index (15位): 这就是实际的读/写索引,它们在 0 到 buffer_size - 1 的范围内移动。因为只用了 15 位,所以单个环形缓冲区的最大容量理论上被限制在 32KB。
read_mirror / write_mirror (1位): 这就是实现空/满状态判断的关键。每次对应指针跨越缓冲区边界时,该位翻转。通过比较读写指针的索引值和镜像位,就能区分空(索引相同,镜像位相同)和满(索引相同,镜像位不同)状态。
buffer_size: 初始化时指定的缓冲区总大小。
void rt_ringbuffer_init(struct rt_ringbuffer *rb, rt_uint8_t *pool, rt_int16_t size);
通过这个初始化函数将上面的结构体和自定义的数组(pool)进行绑定,使自定义的数组(pool)为环形缓存区
绑定缓存区后 方便操作 后续只需要传入结构体就行了
初始化结构体的内容
void rt_ringbuffer_reset(struct rt_ringbuffer *rb);
进行初始化环形缓存区
只需要传入结构体
rt_size_t rt_ringbuffer_put(struct rt_ringbuffer *rb, const rt_uint8_t *ptr, rt_uint16_t length);
将数据写入环形缓存区
rb是传入的结构体
prt是需要存入环形缓存区的数据
length是需要存入缓存区的数据长度(字节)
函数返回值实际成功放入缓冲区的数据长度。
rt_size_t rt_ringbuffer_get(struct rt_ringbuffer *rb, rt_uint8_t *ptr, rt_uint16_t length);
将环形缓存区的数据取出
rb是环形缓存区需要取出来的
prt是数据取出来存放的地址
length希望是取出来的长度
函数返回值是实际取出来的长度
rt_size_t rt_ringbuffer_data_len(struct rt_ringbuffer *rb);
获取当前环形缓冲区中有效数据的长度(有多少字节可读)。
以上是ringbuffer的主要函数。
————————————————
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/2501_90934603/article/details/148386956
|
|