缓冲区设定
使用RAM_D2作为读写缓冲区配置
STM32H7 _ _ _ _ _ x_FLASH.ld配置
.dma_buffer : /* 冒号前有一个空格 */
{
*(.dma_buffer)
} >RAM_D2
然后在需要进行读写缓冲区的文件中添加以下定义:
/// 这里通过宏定义来定义DMA_BUFFER
/// __ICCARM__:表示 IAR EWARM 编译器
/// 所以如果是GCC的话就使用else块中的定义了
#if defined( __ICCARM__ )
#define DMA_BUFFER \
_Pragma("location=\".dma_buffer\"")
#else
#define DMA_BUFFER \
__attribute__((section(".dma_buffer")))
#endif
#define BUFFER_SIZE 256
DMA_BUFFER uint8_t tx_buffer[BUFFER_SIZE];
static uint8_t tx_idx = 0;
DMA_BUFFER uint8_t rx_buffer[BUFFER_SIZE];
串口异常处理函数
如果不对串口异常进行处理,清理相关错误标记,可能无法继续执行中断函数
/// 错误码是定义如下
#define HAL_UART_ERROR_NONE (0x00000000U) /*!< No error */
#define HAL_UART_ERROR_PE (0x00000001U) /*!< Parity error */
#define HAL_UART_ERROR_NE (0x00000002U) /*!< Noise error */
#define HAL_UART_ERROR_FE (0x00000004U) /*!< Frame error */
#define HAL_UART_ERROR_ORE (0x00000008U) /*!< Overrun error */
#define HAL_UART_ERROR_DMA (0x00000010U) /*!< DMA transfer error */
#define HAL_UART_ERROR_RTO (0x00000020U) /*!< Receiver Timeout error */
/// 下面错误处理函数中把奇偶校验错误以及数据溢出错误标志清除
/// 可以直接在调试时打断点查看huart->ErrorCode的值
void HAL_UART_ErrorCallback(UART_HandleTypeDef* huart) {
if (huart->Instance == USART6) {
__HAL_UART_CLEAR_OREFLAG(huart);
__HAL_UART_CLEAR_PEFLAG(huart);
huart->RxState = HAL_UART_STATE_READY;
HAL_UARTEx_ReceiveToIdle_DMA(huart, rx_buffer, BUFFER_SIZE);
__HAL_DMA_DISABLE_IT(&hdma_usart6_rx, DMA_IT_HT);
}
}
关闭半中断
在调用HAL_UARTEx_ReceiveToIdle_DMA函数时默认打开“半中断”,数据在接收到一半(左右)时会触发HAL_UARTEx_RxEventCallback回掉函数, 如果仅需要处理完整数据的可以在调用HAL_UARTEx_ReceiveToIdle_DMA函数后直接执行__HAL_DMA_DISABLE_IT(&hdma_usart6_rx, DMA_IT_HT);,即可关闭半中断
HAL_UARTEx_ReceiveToIdle_DMA(huart, rx_buffer, BUFFER_SIZE);
__HAL_DMA_DISABLE_IT(&hdma_usart6_rx, DMA_IT_HT);
内存保护单元配置(MPU)
| Name | Value |
|---|---|
| MPU Region | Enabled |
| MPU Region Base Address | 0x24000000 |
| MPU Region Size | 512K |
| MPU SubRegion Disable | 0x0 |
| MPU TEX field level | level 0 |
| MPU Access Permission | ALL ACCESS PERMITTED |
| MPU Instruction Access | ENABLE |
| MPU Shareability Permission | ENABLE |
| MPU Cacheable Permission | ENABLE |
| MPU Bufferable Permission | DISABLE |
接收 & 发送(非常重要)
H7系列的芯片不论是接收还是发送,在传输数据之前一定要清理并失效缓冲区的数据,否则收发数据将无法正常进行(不会报错但数据是不会被更新的)
以下代码能够正常收发数据:
// DMA缓冲区
DMA_BUFFER uint8_t tx_buffer[TX_BUFFER_SIZE];
DMA_BUFFER uint8_t rx_buffer[RX_BUFFER_SIZE];
// 将数据写入UART发送缓存,并通过DMA非阻塞方式发送。
// 参数: buf - 待发送数据指针;size - 数据字节数。
// 返回: 如果数据超出缓存大小返回-1,否则启动传输并返回0。
int write(const uint8_t* buf, uint16_t size) {
if (size > TX_BUFFER_SIZE) {
return -1; // 错误:数据长度超过缓冲区限制
}
memcpy(tx_buffer, buf, size);
// 清理缓存以确保DMA读取到最新数据
SCB_CleanInvalidateDCache_by_Addr((uint32_t*)(tx_buffer),
TX_BUFFER_SIZE / 4);
// 通过DMA发送数据(非阻塞方式)
int ret = HAL_UART_Transmit_DMA(huart, tx_buffer, size);
return ret;
}
// 启动DMA接收指定的字节数。
void read(size) {
SCB_CleanInvalidateDCache_by_Addr((uint32_t*)(rx_buffer),
RX_BUFFER_SIZE / 4);
HAL_UART_Receive_DMA(huart, rx_buffer, size);
}
数据的接收方式
HAL_UART_Receive_DMA:
接收size个字节,收满后触发中断,回调函数是void HAL_UART_RxCpltCallback(UART_HandleTypeDef* huart)
HAL_UARTEx_ReceiveToIdle_DMA
空闲中断,默认情况下开启半满中断,当缓冲区处于半满或全满状态以及空闲状态时会触发中断,回调函数是void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef* huart, uint16_t size),可以通过__HAL_DMA_DISABLE_IT(&hdma_usartx_rx, DMA_IT_HT)关闭半满中断
循环模式(Circular)
DMA接收和读取都可以开启Circular模式,在模型情况下,需要在读中断处理函数中需要重启开启读中断,否则中断函数只会被触发一次。而在循环模式中,只需要在初始化是调用下读取函数,DMA会自动读取数据,当数据到达缓冲区末尾时会回到头部继续写入。
可以通过以下方式获取当前缓冲区中已写入的数据(可读数据)
uint16_t pos = RX_BUFFER_SIZE - __HAL_DMA_GET_COUNTER(&hdma_usartx_rx);
为了能够将DMA的数据完整的接收,不要在中断中执行任何耗时操作,而应该尽快将缓冲区的数据存储到FIFO队列中,然后在RTOS的任务或其他任务中消费
// UART接收事件回调:将新接收数据写入环形缓冲区后释放信号量。
// 参数: huart - UART硬件句柄;size - 接收数据字节数。
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef* huart, uint16_t size) {
if (huart == huartx) {
static uint16_t old_pos = 0; // 显式初始化为0
uint16_t pos;
pos = RX_BUFFER_SIZE - __HAL_DMA_GET_COUNTER(&hdma_usartx_rx);
if (pos != old_pos) {
if (pos > old_pos) {
rb_write(&rx_rb, &(rx_buffer[old_pos]), pos - old_pos);
} else {
// 数据发生环绕,分两段写入缓冲区
rb_write(&rx_rb, &(rx_buffer[old_pos]), RX_BUFFER_SIZE - old_pos);
if (pos > 0) {
rb_write(&rx_rb, rx_buffer, pos);
}
}
old_pos = pos;
// 激活信号量,让RTOS消费数据
osSemaphoreRelease(taskBinarySemHandle);
}
}
}
🦉 如何选择
如果封包是整包发送的(一次发送完整的含有包头和包体+校验),推荐使用空闲中断模式,一次接收整个包然后解析 如果封包不是整包发送的,可以使用常规模式先接收包头,然后从包头拿到包体长度后再接收包体 如果有大量数据需要接收(文件,音频流)可使用循环模式+FIFO,先快速将数据挪到FIFO中,然后再对FIFO中的数据进行处理 调试 由于DMA是独立于MCU运行, 所以打断点是无法使其停止的。如果尝试通过打断点去观测DMA接收缓冲区的数据是不可控的。推荐的调试方案还是通过其他方式的输出(串口或网口)把DMA接收到的数据实时输出进行观察。