串口通信
串口通信实验
波特率
在串行通信中,波特率(Baud Rate)是指每秒钟传输的位数(bits),即数据传输的速度,单位为 bps(bits per second)。
波特率的设置直接影响通信的效率和可靠性。
常见的标准波特率包括 2400、4800、9600、19200、38400、57600 和 115200 bps 等。
在进行串口通信时,通信双方必须设置相同的波特率,以确保数据能够正确传输和接收。
如果波特率设置不一致,可能导致数据传输错误或通信失败。
因此,选择合适的波特率并确保通信双方一致,是实现可靠串口通信的关键。
此外,波特率的选择还应考虑传输介质的特性和应用需求。
较高的波特率可以提高数据传输速度,但可能增加传输错误的风险;较低的波特率虽然传输速度较慢,但在噪声较大的环境中可能更稳定。
因此,在实际应用中,需要根据具体情况选择合适的波特率。
在 STM32 等微控制器中,波特率的设置通常通过配置 UART(通用异步收发传输器)的相关寄存器来实现。
开发者可以根据系统时钟频率和所需的波特率,计算并设置相应的寄存器值,以实现所需的通信速率。
总之,波特率的设置是串口通信中至关重要的环节,直接影响数据传输的速度和可靠性。
在实际应用中,应根据系统要求和环境条件,选择并配置合适的波特率,以确保通信的有效性和稳定性。
UART:通用异步
USART:通用同步异步












单片机接受的数据存储在缓冲中,通过调用Transmit函数将数据发送给电脑
TC:Transmit complete
传输完成才置SET
HAL_UART_Transmit_IT和HAL_UART_Transmit
| 特性 | HAL_UART_Transmit | HAL_UART_Transmit_IT |
|---|---|---|
| 发送模式 | 阻塞模式 | 非阻塞模式 |
| 实时性 | 较低 | 较高 |
| 效率 | 较低 | 较高 |
| 实现复杂度 | 简单 | 需要中断和回调支持 |
| 适用场景 | 小数据量、低实时性任务 | 大数据量或实时性任务 |
HAL_UART_Transmit_IT传输完成之后可以重写回调函数
串口重定向
重定向:重写<stdio.h>里面的函数
1.#include<stdio.h>
2.在keil中勾选
3.在main.c或者其他地方添加
int fputc(int ch,FILE*f){
HAL_UART_Transmit(&huart1,(uint8_t *)&ch,1,0xffff);
return ch;
}4.原理
- printf("42") → 解析为字符 '4' 和 '2'。
- 逐个调用 fputc('4', stdout) 和 fputc('2', stdout)。
- fputc 调用 HAL_UART_Transmit,将 '4'(ASCII 52)和 '2'(ASCII 50)发送到 UART。
- UART 硬件将字节通过 TX 引脚发送到外部设备。
流程:
- printf → fputc → HAL 驱动 → UART 寄存器(USARTx->DR) → 硬件发送
串口进阶
RXNE和IDLE中断
使能了RXNE(Receive Data Register Not Empty)中断,这是导致每字节中断一次的关键
代码
void ATK_MW8266D_UART_IRQHandler(void)
{
uint8_t tmp;
/* 1. 处理 ORE 溢出错误标志 */
if (__HAL_UART_GET_FLAG(&g_uart_handle, UART_FLAG_ORE) != RESET)
{
__HAL_UART_CLEAR_OREFLAG(&g_uart_handle);
(void)g_uart_handle.Instance->SR;
(void)g_uart_handle.Instance->DR;
}
/* 2. 处理接收数据寄存器非空中断 (RXNE) */
if (__HAL_UART_GET_FLAG(&g_uart_handle, UART_FLAG_RXNE) != RESET)
{
/* 读取接收到的数据 */
tmp = (uint8_t)(g_uart_handle.Instance->DR & 0xFF);
if (g_uart_rx_frame.sta.len < (ATK_MW8266D_UART_RX_BUF_SIZE - 1))
{
g_uart_rx_frame.buf[g_uart_rx_frame.sta.len++] = tmp;
}
else
{
/* 缓冲区满,重置缓冲区并从当前字节开始 */
g_uart_rx_frame.sta.len = 0;
g_uart_rx_frame.buf[g_uart_rx_frame.sta.len++] = tmp;
}
}
/* 3. IDLE 空闲中断检测帧结束 */
if (__HAL_UART_GET_FLAG(&g_uart_handle, UART_FLAG_IDLE) != RESET)
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
__HAL_UART_CLEAR_IDLEFLAG(&g_uart_handle);
g_uart_rx_frame.sta.finsh = 1;
/* 4. 通过队列通知任务处理数据 */
if (xUartRxQueue != NULL)
{
uint16_t len = g_uart_rx_frame.sta.len;
g_uart_rx_frame.sta.len=0;
xQueueSendFromISR(xUartRxQueue, &len, &xHigherPriorityTaskWoken);
}
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
}例子
时间轴和中断触发:
┌─────────┬─────────┬─────────┬─────────┬─────────┐
│ 收到'H' │ 收到'e' │ 收到'l' │ 收到'l' │ 收到'o' │
├─────────┼─────────┼─────────┼─────────┼─────────┤
│中断1触发│中断2触发│中断3触发│中断4触发│中断5触发│
│读取'H' │读取'e' │读取'l' │读取'l' │读取'o' │
└─────────┴─────────┴─────────┴─────────┴─────────┘
↑ ↑ ↑ ↑ ↑
RXNE置位 RXNE置位 RXNE置位 RXNE置位 RXNE置位DMA+IDLE中断+环形缓冲
这些缓冲指的都是软件层面定义的,不是寄存器
每帧一次中断
CNDTR是当前缓冲区还剩多少个字节。初始大小是Size这个参数决定的
HAL_UART_Receive_DMA(UART_HandleTypeDef *huart,
uint8_t *pData,
uint16_t Size)深入理解
// HAL库源码(简化版)
HAL_StatusTypeDef HAL_UART_Receive_DMA(UART_HandleTypeDef *huart,
uint8_t *pData,
uint16_t Size)
{
/* 1. 设置DMA的CNDTR寄存器 */
huart->hdmarx->Instance->CNDTR = Size; // ← 这里设置了CNDTR!
/* 2. 设置DMA的内存地址 */
huart->hdmarx->Instance->CMAR = (uint32_t)pData;
/* 3. 设置DMA的外设地址 */
huart->hdmarx->Instance->CPAR = (uint32_t)&huart->Instance->DR;
/* 4. 使能DMA通道 */
__HAL_DMA_ENABLE(huart->hdmarx);
/* 5. 使能串口的DMA接收 */
SET_BIT(huart->Instance->CR3, USART_CR3_DMAR);
return HAL_OK;
}
//简易
void USART2_IRQHandler(void)
{
/* USER CODE BEGIN USART2_IRQn 0 */
uint32_t tmp_flag = 0;
uint32_t temp;
tmp_flag = __HAL_UART_GET_FLAG(&huart2, UART_FLAG_IDLE);
if ((tmp_flag != RESET)) {
__HAL_UART_CLEAR_IDLEFLAG(&huart2);
// 清除IDLE标志所需的操作
temp = huart2.Instance->SR;
temp = huart2.Instance->DR;
//为什么要在IDLE中断中停止DMA再读取CNDTR?
// 如果不停止DMA,CNDTR可能正在变化,读取的值可能不准确
// 想象一下:
// 时间点1:CPU开始读取CNDTR,读到值=100
// 时间点2:DMA又传输了1个字节,CNDTR变成99
// 时间点3:CPU读取完成,得到100,但实际应该是99
// 正确做法:
HAL_UART_DMAStop(&huart2); // 先停止DMA
temp = huart2.hdmarx->Instance->CNDTR; // 安全读取
Rx_Len = Rx_Max - temp; // 计算长度
RX_Flag = 1;
}
/* USER CODE END USART2_IRQn 0 */
HAL_UART_IRQHandler(&huart2);
/* USER CODE BEGIN USART2_IRQn 1 */
/* USER CODE END USART2_IRQn 1 */
}
//加环形缓冲
uart_DMA_init(); //要先 dma
uart_init(115200);
HAL_UART_Receive_DMA(&UART1_Handler,UartRxBuf,RX_BUF_SIZE);//上面的两个执行完了才能有这个
// 串口1中断服务函数
void USART1_IRQHandler(void)
{
if (__HAL_UART_GET_FLAG(&UART1_Handler, UART_FLAG_IDLE))
{
__HAL_UART_CLEAR_IDLEFLAG(&UART1_Handler);
uint16_t rx_new_pos;
static uint16_t rx_old_pos = 0;
// 计算新的接收位置:缓冲区总大小 - DMA剩余计数器
rx_new_pos = RX_BUF_SIZE - UART1_Handler.hdmarx->Instance->NDTR;
// 处理环形缓冲区数据块
if (rx_new_pos >= rx_old_pos)
{
// 数据在缓冲区中是连续的
chunk.start = rx_old_pos;
chunk.len = rx_new_pos - rx_old_pos;
}
else
{
// 数据跨越缓冲区末尾
chunk.start = rx_old_pos;
chunk.len = RX_BUF_SIZE - rx_old_pos + rx_new_pos;
}
// 打印调试信息
printf("\r\nNDTR=%d, old_pos=%d, new_pos=%d, len=%d\r\n",
UART1_Handler.hdmarx->Instance->NDTR, rx_old_pos, rx_new_pos, chunk.len);
// 如果有新数据,更新位置并标记数据块
if (chunk.len > 0)
{
rx_old_pos = rx_new_pos;
chunk.finish = RX_finish_flag;
}
}
// HAL_UART_IRQHandler(&UART1_Handler);
}
// DMA初始化函数
void uart_DMA_init()
{
__HAL_RCC_DMA2_CLK_ENABLE(); // 使能DMA2时钟
DMA2_Stream2_Handler.Instance = DMA2_Stream2; // DMA2数据流2
DMA2_Stream2_Handler.Init.Channel = DMA_CHANNEL_4; // DMA通道4
DMA2_Stream2_Handler.Init.Direction = DMA_PERIPH_TO_MEMORY; // 外设到存储器
DMA2_Stream2_Handler.Init.PeriphInc = DMA_PINC_DISABLE; // 外设地址不增加
DMA2_Stream2_Handler.Init.MemInc = DMA_MINC_ENABLE; // 存储器地址增加
DMA2_Stream2_Handler.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; // 外设数据对齐:字节
DMA2_Stream2_Handler.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; // 存储器数据对齐:字节
DMA2_Stream2_Handler.Init.Mode = DMA_CIRCULAR; // 循环模式
DMA2_Stream2_Handler.Init.Priority = DMA_PRIORITY_HIGH; // 高优先级
DMA2_Stream2_Handler.Init.FIFOMode = DMA_FIFOMODE_DISABLE; // 禁用FIFO
HAL_DMA_Init(&DMA2_Stream2_Handler); // 初始化DMA
// 将DMA与UART关联
__HAL_LINKDMA(&UART1_Handler, hdmarx, DMA2_Stream2_Handler);
}
void data_Process_Task()
{
if(chunk.finish)
{
chunk.finish=RX_not_finish_flag;
printf("RX[%d]: ", chunk.len);
for (uint16_t i = 0; i < chunk.len; i++)
{
uint16_t index = (chunk.start + i) % RX_BUF_SIZE;
printf("%c", UartRxBuf[index]);
}
printf("\r\n");
}
}补充
UART_HandleTypeDef huart2;
DMA_HandleTypeDef hdma_usart2_rx;
清除标志位
可以看出,清除这些标志位都是先读SR,再读DR
#define __HAL_UART_CLEAR_PEFLAG(__HANDLE__) \
do{ \
__IO uint32_t tmpreg = 0x00U; \
tmpreg = (__HANDLE__)->Instance->SR; \
tmpreg = (__HANDLE__)->Instance->DR; \
UNUSED(tmpreg); \
} while(0U)
/** @brief Clears the UART FE pending flag.
* @param __HANDLE__ specifies the UART Handle.
* UART Handle selects the USARTx or UARTy peripheral
* (USART,UART availability and x,y values depending on device).
* @retval None
*/
#define __HAL_UART_CLEAR_FEFLAG(__HANDLE__) __HAL_UART_CLEAR_PEFLAG(__HANDLE__)
/** @brief Clears the UART NE pending flag.
* @param __HANDLE__ specifies the UART Handle.
* UART Handle selects the USARTx or UARTy peripheral
* (USART,UART availability and x,y values depending on device).
* @retval None
*/
#define __HAL_UART_CLEAR_NEFLAG(__HANDLE__) __HAL_UART_CLEAR_PEFLAG(__HANDLE__)
/** @brief Clears the UART ORE pending flag.
* @param __HANDLE__ specifies the UART Handle.
* UART Handle selects the USARTx or UARTy peripheral
* (USART,UART availability and x,y values depending on device).
* @retval None
*/
#define __HAL_UART_CLEAR_OREFLAG(__HANDLE__) __HAL_UART_CLEAR_PEFLAG(__HANDLE__)
/** @brief Clears the UART IDLE pending flag.
* @param __HANDLE__ specifies the UART Handle.
* UART Handle selects the USARTx or UARTy peripheral
* (USART,UART availability and x,y values depending on device).
* @retval None
*/
#define __HAL_UART_CLEAR_IDLEFLAG(__HANDLE__) __HAL_UART_CLEAR_PEFLAG(__HANDLE__)
/** @brief Enable the specified UART interrupt.
* @param __HANDLE__ specifies the UART Handle.
* UART Handle selects the USARTx or UARTy peripheral
* (USART,UART availability and x,y values depending on device).
* @param __INTERRUPT__ specifies the UART interrupt source to enable.
* This parameter can be one of the following values:
* @arg UART_IT_CTS: CTS change interrupt
* @arg UART_IT_LBD: LIN Break detection interrupt
* @arg UART_IT_TXE: Transmit Data Register empty interrupt
* @arg UART_IT_TC: Transmission complete interrupt
* @arg UART_IT_RXNE: Receive Data register not empty interrupt
* @arg UART_IT_IDLE: Idle line detection interrupt
* @arg UART_IT_PE: Parity Error interrupt
* @arg UART_IT_ERR: Error interrupt(Frame error, noise error, overrun error)
* @retval None
*/
