上一章节已经实现了ZigBee组网与无线传输。
由于STM32的多个串口均被使用,UART1用于日志输出,UART2用于与ZigBee的通信。若是这两个串口都使用查询方式,会导致ZigBee与STM32无法正常的通信,为了使得两个串口可以完全并行工作,UART1 的调试输出不会影响 UART2 的业务数据通信,需要在UART2上引入空闲中断,使用中断方式或者DMA方式进行通信。
1.核心结论
| UART1 | 查询模式(阻塞) | 中断模式 | 调试输出、Shell命令交互 |
| UART2 | 中断/DMA (可选) | DMA模式 (默认) | 业务数据通信 |
2.UART1实现分析
2.1 配置与初始化
- 硬件配置
//标准配置
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;
huart1.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE;
huart1.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
- DMA配置
//源码中虽然配置了DMA通道,但在实际项目中未启用
hdma_usart1_rx.Instance = DMA1_Channel5;
hdma_usart1_tx.Instance = DMA1_Channel4;
2.2 工作方式
- 发送:
int fputc(int ch, FILE *f)
{
HAL_UART_Transmit(&uart_debug, (uint8_t *)&ch, 1, 0xFFFF);
return ch;
}
重定向输出,可以直接使用printf实现调试输出。
- 接收:
static void uart_debug_irq(void) {
if(__HAL_UART_GET_FLAG(&uart_debug, UART_FLAG_RXNE) != RESET) {
value = (uint8_t)(uart_debug.Instance->RDR & 0x00FF);
ring_buffer_write(&s_uartdebug_rcv_ring, &value, 1); // 写入环形缓冲区
LOS_SemPost(s_uartdebug_rcv_sync); // 通知任务
}
}
- 工作方式
发送采用阻塞查询模式(HAL_UART_Transmit),接收采用中断驱动模式(UART_IT_RXNE),结合环形缓冲区以及信号量同步
3. UART2实现分析
UART2 在项目中作为业务数据通信通道(连接 CC2530 无线模块),采用了多帧环形缓冲区 + IDLE 帧检测 + DMA/中断双模式的架构设计。其通信架构如下所示:
┌─────────────────────────────────────────────────────────────────────────┐
│ UART2 通信架构 │
├─────────────────────────────────────────────────────────────────────────┤
│ 应用层 │
│ │ │
│ ▼ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ UART2_Tx_Data│ │ UART2_Rx_Data│ │
│ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────────────────────────────────┐ │
│ │ uart2_CB 控制块 │ │
│ │ ┌─────────────┐ ┌─────────────┐ │ │
│ │ │ RxLocation │ │ TxLocation │ │ 各10帧环形队列 │
│ │ │ [0]~[9] │ │ [0]~[9] │ │ │
│ │ └──────┬──────┘ └──────┬──────┘ │ │
│ │ │ │ │ │
│ │ Uart2_RxBuff(512B) Uart2_TxBuff(512B) │
│ └──────┬───────────────────┬──────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌────────────────────────────────────┐ │
│ │ HAL 驱动层 │ │
│ │ huart2 (UART_HandleTypeDef) │ │
│ └──────┬───────────────────┬──────────┘ │
│ │ │ │
│ DMA模式(默认) 中断模式(可选) │
│ │ │ │
│ DMA1_Channel6 USART2_IRQHandler │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────────────────────────────────┐ │
│ │ 硬件层 │ │
│ │ USART2 (PA2-TX, PA3-RX) │ │
│ └──────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────────────────┘
3.1 配置与初始化
- 相关宏定义以及核心数据结构
/* USER CODE BEGIN Private defines */
#define UART2_RX_BUFFER_SIZE 512
#define UART2_TX_BUFFER_SIZE 512
#define UART2_RX_MAX_SIZE 32
#define UART2_FRAME_NUM 10
//#define UART2_DMA_MODE
uint8_t Uart2_RxBuff[UART2_RX_BUFFER_SIZE]; //串口2的接收缓冲区
uint8_t Uart2_TxBuff[UART2_TX_BUFFER_SIZE]; //串口2的发送缓冲区
typedef struct
{
uint8_t *start; //一帧数据的起始位置
uint8_t *end; //一帧数据的结束位置
} Local_CB_t; //位置相关的结构体
typedef struct
{
uint32_t RxCount; //接收缓冲区已使用的字节数
uint32_t TxCount; //发送缓冲区已使用的字节数
// uint8_t TxState; //发送状态,0:空闲 1:正在发送
Local_CB_t RxLocation[UART2_FRAME_NUM]; //接收缓冲区每帧数据的位置信息
Local_CB_t TxLocation[UART2_FRAME_NUM]; //发送缓冲区每帧数据的位置信息
Local_CB_t *RxInPtr; //接收缓冲区的当前输入数据帧位置
Local_CB_t *RxOutPtr; //接收缓冲区的当前输出数据帧位置
Local_CB_t *RxEndPtr; //接收缓冲区的结束位置
Local_CB_t *TxInPtr; //发送缓冲区的当前输入数据帧位置
Local_CB_t *TxOutPtr; //发送缓冲区的当前输出数据帧位置
Local_CB_t *TxEndPtr; //发送缓冲区的结束位置
osal_semp_t rX_fullframe; //接收数据满帧信号量
osal_semp_t rX_emptyframe; //接收数据空帧信号量
osal_mutex_t rx_Lock; //接收缓冲区锁
osal_semp_t tX_fullframe; //发送数据帧满信号量
osal_semp_t tX_emptyframe; //发送数据帧空信号量
osal_mutex_t tx_Lock; //发送缓冲区锁
} Uart_CB_t; //位置相关的结构体
Uart_CB_t uart2_CB;
设计特点:
| RxLocation/TxLocation | 多帧环形队列 |
| RxInPtr/RxOutPtr | 双指针实现生产者-消费者模式 |
| rX_fullframe | 帧满说明该帧完整 |
- 硬件配置
//标准配置
huart2.Instance = USART2;
huart2.Init.BaudRate = 115200;
huart2.Init.WordLength = UART_WORDLENGTH_8B;
huart2.Init.StopBits = UART_STOPBITS_1;
huart2.Init.Parity = UART_PARITY_NONE;
huart2.Init.Mode = UART_MODE_TX_RX;
huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart2.Init.OverSampling = UART_OVERSAMPLING_16;
huart2.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE;
huart2.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
UART2_Init();//初始化环形缓冲区和接收
/*┌─────────────────────────────────────────────────────────────────┐
│ uart2_CB 控制块结构 │
├─────────────────────────────────────────────────────────────────┤
│ RxLocation[10] ──► [帧0][帧1][帧2]…[帧9] (环形帧队列) │
│ │ │
│ ├── RxInPtr → 当前写入帧位置 │
│ ├── RxOutPtr → 当前读取帧位置 │
│ └── RxCount → 已接收字节总数 │
├─────────────────────────────────────────────────────────────────┤
│ TxLocation[10] ──► [帧0][帧1][帧2]…[帧9] (环形帧队列) │
│ │ │
│ ├── TxInPtr → 当前写入帧位置 │
│ ├── TxOutPtr → 当前读取帧位置 │
│ └── TxCount → 已发送字节总数 │
└─────────────────────────────────────────────────────────────────┘
*/
void UART2_Init(void)
{
//Ptr init
uart2_CB.RxInPtr = &uart2_CB.RxLocation[0];//输入指针指向第0帧
uart2_CB.RxOutPtr = &uart2_CB.RxLocation[0];//输出指针指向第0帧
uart2_CB.RxEndPtr = &uart2_CB.RxLocation[UART2_FRAME_NUM – 1];//末尾帧
uart2_CB.RxCount = 0;//已接受字节数清零
uart2_CB.RxInPtr->start = Uart2_RxBuff;//当前帧起始地址
uart2_CB.TxInPtr = &uart2_CB.TxLocation[0];
uart2_CB.TxOutPtr = &uart2_CB.TxLocation[0];
uart2_CB.TxEndPtr = &uart2_CB.TxLocation[UART2_FRAME_NUM – 1];
uart2_CB.TxCount = 0;
uart2_CB.TxInPtr->start = Uart2_TxBuff;
//UART2_IDLE_IT enable
__HAL_UART_ENABLE_IT(&huart2, UART_IT_IDLE);
#ifdef UART2_DMA_MODE
HAL_UART_Receive_DMA(&huart2,uart2_CB.RxInPtr->start,UART2_RX_MAX_SIZE);
#else
HAL_UART_Receive_IT(&huart2,uart2_CB.RxInPtr->start,UART2_RX_MAX_SIZE);
#endif
}
- DMA配置
hdma_usart2_rx.Instance = DMA1_Channel6;
hdma_usart2_rx.Init.Mode = DMA_NORMAL; // 非循环模式
hdma_usart2_tx.Instance = DMA1_Channel7;
3.2 工作方式
- 初始化
void UART2_Init(void){ }
接收流程
- IDLE中断检测帧结束
UART IDLE 标志:当串口接收线上没有数据活动超过一个字符时间时,硬件自动设置此标志,表示一帧数据接收完成。
void USART2_IRQHandler(void) {
HAL_UART_IRQHandler(&huart2); // HAL层标准处理
// 检测IDLE中断
if(__HAL_UART_GET_FLAG(&huart2, UART_FLAG_IDLE)) {
__HAL_UART_CLEAR_IDLEFLAG(&huart2); // 清除标志
// 计算实际接收字节数
#ifdef UART2_DMA_MODE
uart2_CB.RxCount += (UART2_RX_MAX_SIZE – __HAL_DMA_GET_COUNTER(&hdma_usart2_rx));
#else
uart2_CB.RxCount += (UART2_RX_MAX_SIZE – huart2.RxXferCount);
#endif
// 终止当前接收,触发完成回调
HAL_UART_AbortReceive_IT(&huart2);
}
}
- 接收完成回调
void HAL_UART_AbortReceiveCpltCallback (UART_HandleTypeDef *huart)
{
if(huart->Instance == USART2)
{
uart2_CB.RxInPtr->end = &Uart2_RxBuff[uart2_CB.RxCount – 1]; //设置结束指针
//移动输入指针到下一帧位置
if(uart2_CB.RxCount != 0)
{
uart2_CB.RxInPtr ++ ;
}
if(uart2_CB.RxInPtr == uart2_CB.RxEndPtr)
{
uart2_CB.RxInPtr = &uart2_CB.RxLocation[0];
}
//检查缓冲区是否溢出,若是则重置
if((UART2_RX_BUFFER_SIZE – uart2_CB.RxCount) < UART2_RX_MAX_SIZE)
{
uart2_CB.RxCount = 0;
uart2_CB.RxInPtr->start = Uart2_RxBuff;
}
else
{
uart2_CB.RxInPtr->start = &Uart2_RxBuff[uart2_CB.RxCount];
}
osal_semp_post(uart2_CB.rX_fullframe);//通知接收任务有数据可读
#ifdef UART2_DMA_MODE
HAL_UART_Receive_DMA(&huart2,uart2_CB.RxInPtr->start,UART2_RX_MAX_SIZE);//重新启动接收
#else
HAL_UART_Receive_IT(&huart2,uart2_CB.RxInPtr->start,UART2_RX_MAX_SIZE);//重新启动接收
#endif
}
}
发送流程
- 写入缓冲区
//将待发送数据写入发送缓冲区,并启动串口2的发送中断或DMA发送
void UART2_Tx_Data(uint8_t *pData, uint16_t size)
{
if ((UART2_TX_BUFFER_SIZE – uart2_CB.TxCount) >= size)
{
//发送缓冲区有足够空间,直接写入,计算新数据的起始位置
uart2_CB.TxInPtr->start = &Uart2_TxBuff[uart2_CB.TxCount];
}
else
{
//空间不足:重置缓冲区,覆盖旧数据
uart2_CB.TxCount = 0;
uart2_CB.TxInPtr->start = Uart2_TxBuff;
}
memcpy(uart2_CB.TxInPtr->start,pData,size);
uart2_CB.TxCount += size;
uart2_CB.TxInPtr->end = &Uart2_TxBuff[uart2_CB.TxCount – 1];
uart2_CB.TxInPtr ++ ;
if(uart2_CB.TxInPtr == uart2_CB.TxEndPtr)
{
uart2_CB.TxInPtr = &uart2_CB.TxLocation[0];
}
}
- 直接发送
// Hardware/E53_CC2530/E53_CC2530.c:27
HAL_UART_Transmit(&huart2, str, len, 200); // 阻塞发送
- 工作模式
接收采用DMA或者中断模式,发送通过UART2_Tx_Data()写入缓冲区,配合中断/DMA发送
4.工作场景示例
场景:UART1输出调试信息是,UART2同时接收业务数据
时间轴────────────────────────────────────────────────────────────►
UART1 (调试): [发送 "Hello"] ──────► [发送 "Debug: OK"] ─────────► …
▲ ▲
│ │
HAL_UART_Transmit HAL_UART_Transmit
(阻塞查询) (阻塞查询)
UART2 (业务): ────► [DMA接收 0x12] ────► [DMA接收 0x34] ────► [IDLE中断]
▲ ▲ ▲
│ │ │
DMA自动传输 DMA自动传输 通知任务处理




