STM32提高串口发送函数执行速度并避免丢帧方法


前言

提高串口发送函数的执行速度并尽量避免丢帧,涉及优化数据传输的方法、减少阻塞时间以及合理管理缓冲区。以下是几种有效的方法和详细的实现步骤:


一、使用非阻塞模式(中断或DMA)

a、使用中断驱动的传输

原理:利用 UART 中断驱动数据发送,避免在传输过程中阻塞主线程。每当 UART 发送一个字节完成时,触发中断并发送下一个字节。
实现步骤:
1.初始化 UART 并启用发送中断:

// 假设使用的是 STM32 HAL 库
HAL_UART_Transmit_IT(&huart1, buffer, length);

2.实现中断回调函数:

void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) {
    if (huart->Instance == USART1) {
        // 发送完成后的处理
        // 可以开始下一个传输或设置标志位
    }
}

void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart) {
    if (huart->Instance == USART1) {
        // 错误处理
    }
}

3.管理传输队列:
使用一个环形缓冲区(环形队列)来存储待发送的数据。主线程将数据放入队列,中断处理函数从队列中取出数据并发送。

#define TX_BUFFER_SIZE 256
uint8_t txBuffer[TX_BUFFER_SIZE];
volatile uint16_t txHead = 0;
volatile uint16_t txTail = 0;

void enqueueTxData(uint8_t data) {
    uint16_t nextHead = (txHead + 1) % TX_BUFFER_SIZE;
    if (nextHead != txTail) { // 检查缓冲区是否满
        txBuffer[txHead] = data;
        txHead = nextHead;
    }
    // 处理缓冲区满的情况
}

uint8_t dequeueTxData(void) {
    uint8_t data = 0;
    if (txTail != txHead) { // 检查缓冲区是否为空
        data = txBuffer[txTail];
        txTail = (txTail + 1) % TX_BUFFER_SIZE;
    }
    return data;
}

void startNextTransmission(UART_HandleTypeDef *huart) {
    if (txTail != txHead) {
        uint8_t data = dequeueTxData();
        HAL_UART_Transmit_IT(huart, &data, 1);
    }
}

// 在 HAL_UART_TxCpltCallback 中调用
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) {
    if (huart->Instance == USART1) {
        startNextTransmission(huart);
    }
}

// 主线程发送数据
void sendData(uint8_t *data, uint16_t length) {
    for (uint16_t i = 0; i < length; i++) {
        enqueueTxData(data[i]);
    }
    // 如果没有正在发送,启动传输
    if (txTail == txHead) {
        startNextTransmission(&huart1);
    }
}

b. 使用 DMA 驱动的传输

原理:利用 DMA(Direct Memory Access)控制器进行数据传输,减少 CPU 负担,提高传输效率。DMA 可以在后台自动将数据从内存复制到 UART 数据寄存器,无需 CPU 介入。

实现步骤:
1.初始化 UART 并配置 DMA:

// 在 CubeMX 中配置 USART1 使用 DMA 进行 TX 传输
// 生成初始化代码

2.使用 HAL_UART_Transmit_DMA:

HAL_UART_Transmit_DMA(&huart1, buffer, length);

3.实现 DMA 传输完成回调:

void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) {
    if (huart->Instance == USART1) {
        // DMA 传输完成后的处理
        // 可以开始下一个传输或设置标志位
    }
}

void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart) {
    if (huart->Instance == USART1) {
        // 错误处理
    }
}

4.管理传输队列:
类似于中断驱动的方法,使用环形缓冲区存储待发送数据。当 DMA 传输完成后,从队列中取出下一个数据块并启动新的 DMA 传输。

#define TX_BUFFER_SIZE 1024
uint8_t txBuffer[TX_BUFFER_SIZE];
volatile uint16_t txHead = 0;
volatile uint16_t txTail = 0;

void enqueueTxData(uint8_t *data, uint16_t length) {
    for (uint16_t i = 0; i < length; i++) {
        uint16_t nextHead = (txHead + 1) % TX_BUFFER_SIZE;
        if (nextHead != txTail) { // 检查缓冲区是否满
            txBuffer[txHead] = data[i];
            txHead = nextHead;
        } else {
            // 处理缓冲区满的情况,例如丢弃数据或等待
        }
    }
}

void startNextDMA(UART_HandleTypeDef *huart) {
    if (txTail != txHead) {
        uint16_t remaining = (txHead >= txTail) ? (txHead - txTail) : (TX_BUFFER_SIZE - txTail);
        HAL_UART_Transmit_DMA(huart, &txBuffer[txTail], remaining);
        txTail = (txTail + remaining) % TX_BUFFER_SIZE;
    }
}

void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) {
    if (huart->Instance == USART1) {
        startNextDMA(huart);
    }
}

void sendData(uint8_t *data, uint16_t length) {
    enqueueTxData(data, length);
    // 如果没有正在发送,启动传输
    if (txTail == txHead) {
        startNextDMA(&huart1);
    }
}

优点:
提高传输效率:DMA 可以在后台传输数据,无需 CPU 介入,释放 CPU 资源处理其他任务。
减少中断负担:DMA 传输完成后只触发一次中断,而中断驱动模式每发送一个字节都会触发中断。

注意事项:
缓冲区管理:确保缓冲区足够大,避免溢出。
错误处理:处理 DMA 传输错误,例如缓冲区溢出或传输失败。

二、 增大缓冲区和优化数据管理

a. 增大发送缓冲区

原理:增加发送缓冲区的大小,减少频繁启动传输的次数,提高整体传输效率。

实现步骤:
在环形缓冲区或 DMA 缓冲区中使用较大的数据块进行传输,减少中断或 DMA 传输启动的频率。

#define TX_BUFFER_SIZE 2048 // 根据需求调整
uint8_t txBuffer[TX_BUFFER_SIZE];
volatile uint16_t txHead = 0;
volatile uint16_t txTail = 0;

优点
减少传输启动次数:一次传输大量数据,减少中断或 DMA 启动的频率。
提高传输效率:一次传输多个字节,提高数据吞吐量。

注意事项
内存占用:确保系统有足够的内存来支持较大的缓冲区。
实时性:在实时性要求较高的应用中,可能需要平衡缓冲区大小与实时响应能力。

b. 优化数据传输逻辑

原理:减少不必要的数据复制和传输次数,优化数据管理逻辑,提高传输效率。

实现步骤

避免频繁调用传输函数:批量传输数据,减少函数调用次数。
使用指针操作:直接操作数据指针,减少数据复制。

// 例如,批量传输多个数据块
void sendDataBatch(uint8_t *data, uint16_t length) {
    while (length > 0) {
        uint16_t chunk = (length > MAX_CHUNK_SIZE) ? MAX_CHUNK_SIZE : length;
        enqueueTxData(data, chunk);
        data += chunk;
        length -= chunk;
    }
    // 启动传输
    if (txTail == txHead) {
        startNextTransmission(&huart1);
    }
}

三、 提高 UART 的配置效率

a. 增加 UART 的波特率

原理:增加 UART 的波特率可以提高数据传输速率,减少传输时间。

实现步骤

调整波特率配置
在 CubeMX 中配置 UART 的波特率,例如从 115200 增加到 921600。

确保硬件支持高波特率
确保你的硬件(微控制器和接收端设备)支持更高的波特率,并且信号完整性良好。

优点
提高传输速率:更高的波特率意味着更快的数据传输。
减少传输时间:同样数量的数据在更短的时间内传输完成。

注意事项
信号完整性:高波特率可能导致信号干扰和误码率增加,确保 PCB 设计良好,必要时使用屏蔽和差分信号。
硬件限制:某些 UART 硬件模块对波特率有上限,需查阅具体微控制器的参考手册。

b. 调整 UART 配置参数

原理:优化 UART 的配置参数(如数据位、停止位、奇偶校验)以提高传输效率。

实现步骤

数据位:使用 8 数据位是常见配置,兼容性最好。
停止位:使用 1 个停止位而不是 2 个,可以稍微提高传输速率。
奇偶校验:如果不需要错误检测,可以禁用奇偶校验,以减少传输时间。

示例

huart1.Init.BaudRate = 921600; // 提高波特率
huart1.Init.WordLength = UART_WORDLENGTH_8B;
huart1.Init.StopBits = UART_STOPBITS_1;
huart1.Init.Parity = UART_PARITY_NONE;
HAL_UART_Init(&huart1);

优点
提高传输效率:减少每个数据包的占用时间。
减少协议开销:禁用奇偶校验减少额外的数据位。

注意事项
兼容性:确保接收端设备支持相同的配置参数。
错误率:禁用奇偶校验可能增加数据错误的风险,需权衡传输效率和数据完整性。

四、 优化缓冲区和内存管理

a. 使用环形缓冲区

原理:使用环形缓冲区(环形队列)来管理发送和接收数据,减少数据复制和提高传输效率。

实现步骤

定义环形缓冲区

#define RX_BUFFER_SIZE 512
uint8_t rxBuffer[RX_BUFFER_SIZE];
volatile uint16_t rxHead = 0;
volatile uint16_t rxTail = 0;

#define TX_BUFFER_SIZE 512
uint8_t txBuffer[TX_BUFFER_SIZE];
volatile uint16_t txHead = 0;
volatile uint16_t txTail = 0;

实现缓冲区管理函数

// Enqueue data
void enqueueTxData(uint8_t data) {
    uint16_t nextHead = (txHead + 1) % TX_BUFFER_SIZE;
    if (nextHead != txTail) { // 检查缓冲区是否满
        txBuffer[txHead] = data;
        txHead = nextHead;
    }
    // 处理缓冲区满的情况
}

// Dequeue data
uint8_t dequeueTxData(void) {
    uint8_t data = 0;
    if (txTail != txHead) { // 检查缓冲区是否为空
        data = txBuffer[txTail];
        txTail = (txTail + 1) % TX_BUFFER_SIZE;
    }
    return data;
}

优点
高效管理数据:环形缓冲区能够高效地管理连续的数据流。
减少内存碎片:避免动态内存分配导致的碎片问题。

注意事项
缓冲区大小:根据应用需求选择合适的缓冲区大小,避免溢出。
线程安全:在多线程环境下,确保缓冲区访问的线程安全,可以使用互斥锁或禁用中断。

b. 减少数据复制

原理:尽量减少数据在传输过程中的复制,直接使用指针或引用传输数据,提升效率。

实现步骤

直接传输数据块:避免逐字节传输,使用批量数据传输。

void sendData(uint8_t *data, uint16_t length) {
    // 确保数据长度不超过缓冲区容量
    for (uint16_t i = 0; i < length; i++) {
        enqueueTxData(data[i]);
    }
    // 启动传输
    if (txTail == txHead) {
        startNextTransmission(&huart1);
    }
}
使用指针管理缓冲区:

c
复制代码
// 使用指针进行数据传输
void sendData(uint8_t *data, uint16_t length) {
    while (length > 0) {
        uint16_t space = TX_BUFFER_SIZE - txHead;
        uint16_t chunk = (length < space) ? length : space;
        memcpy(&txBuffer[txHead], data, chunk);
        txHead = (txHead + chunk) % TX_BUFFER_SIZE;
        data += chunk;
        length -= chunk;
    }
    // 启动传输
    if (txTail == txHead - chunk) {
        startNextTransmission(&huart1);
    }
}

优点
提高传输效率:减少数据复制步骤,提高传输速度。
简化代码逻辑:通过指针管理,简化数据传输逻辑。

注意事项
内存管理:确保数据指针有效,避免悬空指针或越界访问。
数据对齐:确保数据块对齐,避免传输过程中出现对齐错误。

五、使用更高效的传输函数

a. 直接操作寄存器

原理:直接操作 UART 数据寄存器,绕过 HAL 库的封装,减少函数调用开销,提升传输速度。

实现步骤

实现非阻塞传输函数

void UART_SendByte(UART_HandleTypeDef *huart, uint8_t byte) {
    // 等待发送寄存器为空
    while (!(huart->Instance->SR & USART_SR_TXE));
    huart->Instance->DR = byte;
}

void UART_SendData(UART_HandleTypeDef *huart, uint8_t *data, uint16_t length) {
    for (uint16_t i = 0; i < length; i++) {
        UART_SendByte(huart, data[i]);
    }
    // 等待传输完成
    while (!(huart->Instance->SR & USART_SR_TC));
}

使用 DMA 或中断优化
结合 DMA 或中断驱动的数据发送,进一步提升效率。

优点
减少开销:直接操作寄存器,避免 HAL 函数的额外开销。
提升速度:更快速的传输响应。

注意事项
可维护性:直接操作寄存器可能降低代码的可读性和可维护性。
错误处理:需要手动实现错误检测和处理机制。

b. 使用轻量级库或自定义驱动

原理:使用更轻量级的 UART 驱动库,减少不必要的功能和开销,提升传输速度。

实现步骤
选择轻量级驱动:例如 ChibiOS 或其他开源实时操作系统,提供更高效的 UART 驱动。
自定义优化:根据项目需求自定义优化 UART 驱动,去除不必要的功能,提升传输效率。

优点
灵活性:可以根据具体需求进行定制和优化。
性能提升:去除不必要的功能,提高驱动效率。

注意事项
开发时间:需要花费更多时间进行驱动开发和测试。
兼容性:确保自定义驱动与现有系统和硬件兼容。

六、优化中断和任务优先级

a. 提高 UART 中断优先级

原理:提高 UART 中断的优先级,确保数据传输的及时处理,减少中断延迟。

实现步骤

配置中断优先级

// 使用 NVIC 设置 USART1 中断优先级
HAL_NVIC_SetPriority(USART1_IRQn, 1, 0);
HAL_NVIC_EnableIRQ(USART1_IRQn);

确保中断处理及时
确保中断处理函数简洁高效,避免长时间占用 CPU 资源。

优点
及时处理数据:高优先级中断可以更快地响应数据传输请求。
减少丢帧风险:及时处理中断,减少数据丢失的可能性。

注意事项
优先级冲突:避免将 UART 中断优先级设置得过高,导致其他关键任务被阻塞。
中断嵌套:小心处理中断嵌套,避免优先级反转和死锁。

b. 合理安排任务优先级(RTOS 环境)

原理:在使用实时操作系统(如 FreeRTOS)的情况下,合理安排任务优先级,确保 UART 传输任务具有足够的优先级。

实现步骤

定义任务优先级

#define UART_TASK_PRIORITY (osPriorityAboveNormal)

创建 UART 传输任务

void UART_Task(void *argument) {
    while (1) {
        // 处理发送队列
        sendDataFromQueue();
        osDelay(1); // 适当的延迟
    }
}

// 在主函数中创建任务
osThreadDef(UART_Thread, UART_Task, UART_TASK_PRIORITY, 0, 128);
osThreadCreate(osThread(UART_Thread), NULL);

优点
提高任务响应:高优先级任务能够更快地响应数据传输需求。
减少任务延迟:合理安排任务优先级,确保关键任务不会被低优先级任务阻塞。

注意事项
优先级分配:确保高优先级任务不会导致低优先级任务长时间被阻塞。
资源管理:避免优先级反转和资源争用,确保任务调度的公平性。

七、实现双缓冲技术

原理
双缓冲技术使用两个缓冲区交替进行数据传输,一个缓冲区用于数据准备,另一个缓冲区用于传输,减少等待时间和提高传输效率。

实现步骤
定义双缓冲区

#define BUFFER_SIZE 512
uint8_t buffer1[BUFFER_SIZE];
uint8_t buffer2[BUFFER_SIZE];
volatile uint8_t currentBuffer = 0;

实现缓冲区切换

void switchBuffer(void) {
    if (currentBuffer == 0) {
        HAL_UART_Transmit_DMA(&huart1, buffer1, BUFFER_SIZE);
        currentBuffer = 1;
    } else {
        HAL_UART_Transmit_DMA(&huart1, buffer2, BUFFER_SIZE);
        currentBuffer = 0;
    }
}

void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) {
    if (huart->Instance == USART1) {
        switchBuffer();
    }
}

void prepareBuffer(uint8_t *data, uint16_t length) {
    if (currentBuffer == 0) {
        memcpy(buffer2, data, length); // 准备下一个缓冲区
    } else {
        memcpy(buffer1, data, length);
    }
    // 传输已经在中断回调中处理
}

优点
减少传输等待时间:一个缓冲区传输时,另一个缓冲区可以准备数据,减少传输等待。
提高吞吐量:双缓冲技术能够更高效地利用 DMA 传输,提高整体数据吞吐量。

注意事项
同步管理:确保缓冲区切换和数据准备的同步,避免数据覆盖或丢失。
内存占用:双缓冲技术需要额外的内存来存储两个缓冲区。

八、 其他优化建议

a. 避免使用阻塞函数

原理:阻塞函数会阻塞当前线程,导致 CPU 无法处理其他任务,影响整体系统性能。

实现步骤

使用非阻塞传输:使用中断或 DMA 驱动的传输方式,避免阻塞函数的使用。
优化主循环:确保主循环或任务能够高效执行,避免长时间占用 CPU。

优点
提高系统响应性:非阻塞传输允许系统同时处理多个任务,提高响应速度。
减少丢帧风险:避免因阻塞导致的数据传输延迟或丢帧。

注意事项
复杂性:非阻塞传输可能增加代码复杂性,需要合理管理状态和回调。

b. 优化系统时钟配置

原理:提高系统时钟频率可以提升整个系统的运行速度,包括 UART 传输。

实现步骤

调整系统时钟配置
在 CubeMX 中配置更高的系统时钟频率(例如,从 72 MHz 提高到 168 MHz)。

确保硬件支持
确保微控制器的时钟配置稳定,电源设计满足更高频率下的需求。

优点
提高系统性能:更高的系统时钟频率提升 CPU 和外设的运行速度。
加快数据处理:更高的 CPU 频率可以更快地处理数据传输和其他任务。

注意事项
功耗增加:更高的系统时钟频率会增加功耗,需要权衡性能和功耗。
散热管理:高频率运行可能导致温度升高,确保良好的散热设计。

c. 使用双核或多核微控制器

原理:利用多核微控制器将数据传输任务和其他任务分离,提高整体系统性能和传输效率。

实现步骤

选择多核微控制器
选择支持双核或多核架构的微控制器,如 ARM Cortex-M7 与 Cortex-M4 的组合。

任务分离
将数据传输任务分配到一个核心,其他任务分配到另一个核心,减少任务间的干扰和延迟。

优点
并行处理:提高系统的并行处理能力,提升传输效率和系统响应速度。
任务隔离:减少任务间的干扰,提高系统的可靠性和稳定性。

注意事项
复杂性增加:多核系统需要更复杂的任务管理和同步机制。
成本和功耗:多核系统通常成本更高,功耗也更大。

九、示例代码:使用 DMA 和环形缓冲区优化 UART 传输

以下是一个综合示例,展示如何结合 DMA 驱动和环形缓冲区来优化 UART 传输,提升传输速度并减少丢帧。

a. 定义缓冲区和管理变量

#define TX_BUFFER_SIZE 1024
uint8_t txBuffer[TX_BUFFER_SIZE];
volatile uint16_t txHead = 0;
volatile uint16_t txTail = 0;
volatile uint8_t txInProgress = 0;

b. 实现缓冲区管理函数

// Enqueue data into the transmit buffer
void enqueueTxData(uint8_t *data, uint16_t length) {
    for (uint16_t i = 0; i < length; i++) {
        uint16_t nextHead = (txHead + 1) % TX_BUFFER_SIZE;
        if (nextHead != txTail) { // Check if buffer is not full
            txBuffer[txHead] = data[i];
            txHead = nextHead;
        } else {
            // Buffer is full, handle overflow (e.g., discard data or wait)
        }
    }
}

// Start DMA transmission if not already in progress
void startDMA(UART_HandleTypeDef *huart) {
    if (!txInProgress && txTail != txHead) {
        uint16_t length = (txHead > txTail) ? (txHead - txTail) : (TX_BUFFER_SIZE - txTail);
        HAL_UART_Transmit_DMA(huart, &txBuffer[txTail], length);
        txInProgress = 1;
    }
}

// Callback function when DMA transmission is complete
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) {
    if (huart->Instance == USART1) {
        txTail = (txTail + (huart->TxXferSize)) % TX_BUFFER_SIZE;
        txInProgress = 0;
        startDMA(huart);
    }
}

void sendData(uint8_t *data, uint16_t length) {
    enqueueTxData(data, length);
    startDMA(&huart1);
}

c. 主函数和初始化

int main(void) {
    HAL_Init();
    SystemClock_Config(); // 由 CubeMX 生成的系统时钟配置函数
    MX_USART1_UART_Init(); // 由 CubeMX 生成的 UART 初始化函数

    // 主循环
    while (1) {
        uint8_t dataToSend[] = "Hello, UART DMA!\n";
        sendData(dataToSend, sizeof(dataToSend) - 1);
        HAL_Delay(1000); // 发送间隔
    }
}

解释
环形缓冲区:使用 txBuffer 存储待发送的数据,通过 txHead 和 txTail 管理缓冲区的头尾位置。
DMA 传输:当缓冲区有数据且没有传输正在进行时,启动 DMA 传输。传输完成后,更新 txTail 并检查是否有更多数据需要传输。
避免丢帧:通过合理管理缓冲区和 DMA 传输,确保数据按顺序传输,减少丢帧风险。

优点
高效传输:利用 DMA 驱动高效传输大量数据。
低延迟:环形缓冲区确保数据及时传输,减少传输延迟。
灵活管理:可以根据需要调整缓冲区大小和传输逻辑,适应不同的应用场景。

总结

要提高 HAL_UART_Transmit 函数的执行速度并尽量避免丢帧,可以采取以下策略:

使用非阻塞传输
中断驱动模式。
DMA 驱动模式。

优化缓冲区管理
使用环形缓冲区。
增大缓冲区容量。

提高 UART 配置效率
增加波特率。
优化数据位、停止位和奇偶校验设置。

优化系统和任务管理
提高 UART 中断优先级。
合理安排任务优先级(在 RTOS 环境下)。

采用双缓冲或更高效的传输函数
实现双缓冲技术。
直接操作寄存器或使用轻量级驱动。

系统时钟优化
提高系统时钟频率。
确保系统时钟配置稳定。
通过结合上述方法,可以显著提升 UART 传输性能,确保数据高效、稳定地传输,减少丢帧风险。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值