一、硬件配置
| 串口 |
引脚 |
功能 |
| USART1 |
PA9 (TX) |
发送数据 |
| USART1 |
PA10 (RX) |
接收数据 |
| USART2 |
PA2 (TX) |
发送数据 |
| USART2 |
PA3 (RX) |
接收数据 |
| USART3 |
PB10 (TX) |
发送数据 |
| USART3 |
PB11 (RX) |
接收数据 |
二、完整代码实现
2.1 串口初始化代码
/**
* @file usart.c
* @brief STM32F407 串口驱动程序
*/
#include "usart.h"
#include "string.h"
#include "stdio.h"
// 串口接收缓冲区
#define USART1_RX_BUFFER_SIZE 256
#define USART2_RX_BUFFER_SIZE 256
#define USART3_RX_BUFFER_SIZE 256
static uint8_t usart1_rx_buffer[USART1_RX_BUFFER_SIZE];
static uint8_t usart2_rx_buffer[USART2_RX_BUFFER_SIZE];
static uint8_t usart3_rx_buffer[USART3_RX_BUFFER_SIZE];
static uint16_t usart1_rx_index = 0;
static uint16_t usart2_rx_index = 0;
static uint16_t usart3_rx_index = 0;
// 串口句柄
UART_HandleTypeDef huart1;
UART_HandleTypeDef huart2;
UART_HandleTypeDef huart3;
/**
* @brief USART1 初始化
* @param baudrate 波特率
*/
void USART1_Init(uint32_t baudrate) {
GPIO_InitTypeDef GPIO_InitStruct = {0};
// 1. 使能时钟
__HAL_RCC_USART1_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
// 2. 配置GPIO引脚
GPIO_InitStruct.Pin = GPIO_PIN_9 | GPIO_PIN_10;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF7_USART1;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
// 3. 配置串口参数
huart1.Instance = USART1;
huart1.Init.BaudRate = baudrate;
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;
if (HAL_UART_Init(&huart1) != HAL_OK) {
Error_Handler();
}
// 4. 开启接收中断
HAL_UART_Receive_IT(&huart1, &usart1_rx_buffer[usart1_rx_index], 1);
}
/**
* @brief USART2 初始化
* @param baudrate 波特率
*/
void USART2_Init(uint32_t baudrate) {
GPIO_InitTypeDef GPIO_InitStruct = {0};
__HAL_RCC_USART2_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
GPIO_InitStruct.Pin = GPIO_PIN_2 | GPIO_PIN_3;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF7_USART2;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
huart2.Instance = USART2;
huart2.Init.BaudRate = baudrate;
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;
if (HAL_UART_Init(&huart2) != HAL_OK) {
Error_Handler();
}
HAL_UART_Receive_IT(&huart2, &usart2_rx_buffer[usart2_rx_index], 1);
}
/**
* @brief USART3 初始化
* @param baudrate 波特率
*/
void USART3_Init(uint32_t baudrate) {
GPIO_InitTypeDef GPIO_InitStruct = {0};
__HAL_RCC_USART3_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
GPIO_InitStruct.Pin = GPIO_PIN_10 | GPIO_PIN_11;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF7_USART3;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
huart3.Instance = USART3;
huart3.Init.BaudRate = baudrate;
huart3.Init.WordLength = UART_WORDLENGTH_8B;
huart3.Init.StopBits = UART_STOPBITS_1;
huart3.Init.Parity = UART_PARITY_NONE;
huart3.Init.Mode = UART_MODE_TX_RX;
huart3.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart3.Init.OverSampling = UART_OVERSAMPLING_16;
if (HAL_UART_Init(&huart3) != HAL_OK) {
Error_Handler();
}
HAL_UART_Receive_IT(&huart3, &usart3_rx_buffer[usart3_rx_index], 1);
}
2.2 串口发送函数
/**
* @brief 串口发送单个字节
* @param huart 串口句柄
* @param ch 要发送的字节
*/
void USART_SendByte(UART_HandleTypeDef *huart, uint8_t ch) {
HAL_UART_Transmit(huart, &ch, 1, HAL_MAX_DELAY);
}
/**
* @brief 串口发送字符串
* @param huart 串口句柄
* @param str 要发送的字符串
*/
void USART_SendString(UART_HandleTypeDef *huart, char *str) {
HAL_UART_Transmit(huart, (uint8_t *)str, strlen(str), HAL_MAX_DELAY);
}
/**
* @brief 串口发送数组
* @param huart 串口句柄
* @param data 数据数组
* @param len 数组长度
*/
void USART_SendArray(UART_HandleTypeDef *huart, uint8_t *data, uint16_t len) {
HAL_UART_Transmit(huart, data, len, HAL_MAX_DELAY);
}
/**
* @brief 串口发送整数
* @param huart 串口句柄
* @param num 要发送的整数
*/
void USART_SendInt(UART_HandleTypeDef *huart, int32_t num) {
char str[20];
sprintf(str, "%ld", num);
USART_SendString(huart, str);
}
/**
* @brief 串口发送浮点数
* @param huart 串口句柄
* @param f 要发送的浮点数
* @param decimals 小数位数
*/
void USART_SendFloat(UART_HandleTypeDef *huart, float f, uint8_t decimals) {
char str[20];
sprintf(str, "%.*f", decimals, f);
USART_SendString(huart, str);
}
/**
* @brief 格式化输出(类似printf)
* @param huart 串口句柄
* @param format 格式化字符串
* @param ... 可变参数
*/
void USART_Printf(UART_HandleTypeDef *huart, const char *format, ...) {
char buffer[256];
va_list args;
va_start(args, format);
vsprintf(buffer, format, args);
va_end(args);
USART_SendString(huart, buffer);
}
2.3 串口接收中断处理
/**
* @brief 串口接收完成回调函数
* @param huart 串口句柄
*/
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
if (huart->Instance == USART1) {
// 处理USART1接收到的数据
uint8_t received_byte = usart1_rx_buffer[usart1_rx_index];
// 这里可以添加数据处理逻辑
// 例如:将接收到的数据回显
USART_SendByte(&huart1, received_byte);
// 检查是否接收到完整的一帧数据(例如以换行符结束)
if (received_byte == '\n' || usart1_rx_index >= USART1_RX_BUFFER_SIZE - 1) {
// 处理完整的数据帧
USART_SendString(&huart1, "\r\nReceived: ");
USART_SendArray(&huart1, usart1_rx_buffer, usart1_rx_index);
USART_SendString(&huart1, "\r\n");
// 清空缓冲区
memset(usart1_rx_buffer, 0, USART1_RX_BUFFER_SIZE);
usart1_rx_index = 0;
} else {
usart1_rx_index++;
}
// 重新开启接收中断
HAL_UART_Receive_IT(&huart1, &usart1_rx_buffer[usart1_rx_index], 1);
}
else if (huart->Instance == USART2) {
// 处理USART2接收到的数据
uint8_t received_byte = usart2_rx_buffer[usart2_rx_index];
// 回显数据
USART_SendByte(&huart2, received_byte);
if (received_byte == '\n' || usart2_rx_index >= USART2_RX_BUFFER_SIZE - 1) {
USART_SendString(&huart2, "\r\nUSART2 Received!\r\n");
memset(usart2_rx_buffer, 0, USART2_RX_BUFFER_SIZE);
usart2_rx_index = 0;
} else {
usart2_rx_index++;
}
HAL_UART_Receive_IT(&huart2, &usart2_rx_buffer[usart2_rx_index], 1);
}
else if (huart->Instance == USART3) {
// 处理USART3接收到的数据
uint8_t received_byte = usart3_rx_buffer[usart3_rx_index];
USART_SendByte(&huart3, received_byte);
if (received_byte == '\n' || usart3_rx_index >= USART3_RX_BUFFER_SIZE - 1) {
USART_SendString(&huart3, "\r\nUSART3 Received!\r\n");
memset(usart3_rx_buffer, 0, USART3_RX_BUFFER_SIZE);
usart3_rx_index = 0;
} else {
usart3_rx_index++;
}
HAL_UART_Receive_IT(&huart3, &usart3_rx_buffer[usart3_rx_index], 1);
}
}
2.4 串口DMA接收(高性能版本)
/**
* @file usart_dma.c
* @brief 串口DMA接收实现
*/
#define USART1_DMA_RX_BUFFER_SIZE 512
#define USART1_DMA_TX_BUFFER_SIZE 512
static uint8_t usart1_dma_rx_buffer[USART1_DMA_RX_BUFFER_SIZE];
static uint8_t usart1_dma_tx_buffer[USART1_DMA_TX_BUFFER_SIZE];
DMA_HandleTypeDef hdma_usart1_rx;
DMA_HandleTypeDef hdma_usart1_tx;
/**
* @brief USART1 DMA初始化
*/
void USART1_DMA_Init(void) {
__HAL_RCC_DMA2_CLK_ENABLE();
// 配置DMA接收
hdma_usart1_rx.Instance = DMA2_Stream2;
hdma_usart1_rx.Init.Channel = DMA_CHANNEL_4;
hdma_usart1_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
hdma_usart1_rx.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_usart1_rx.Init.MemInc = DMA_MINC_ENABLE;
hdma_usart1_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
hdma_usart1_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
hdma_usart1_rx.Init.Mode = DMA_CIRCULAR; // 循环模式
hdma_usart1_rx.Init.Priority = DMA_PRIORITY_HIGH;
hdma_usart1_rx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
if (HAL_DMA_Init(&hdma_usart1_rx) != HAL_OK) {
Error_Handler();
}
__HAL_LINKDMA(&huart1, hdmarx, hdma_usart1_rx);
// 配置DMA发送
hdma_usart1_tx.Instance = DMA2_Stream7;
hdma_usart1_tx.Init.Channel = DMA_CHANNEL_4;
hdma_usart1_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;
hdma_usart1_tx.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_usart1_tx.Init.MemInc = DMA_MINC_ENABLE;
hdma_usart1_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
hdma_usart1_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
hdma_usart1_tx.Init.Mode = DMA_NORMAL;
hdma_usart1_tx.Init.Priority = DMA_PRIORITY_HIGH;
hdma_usart1_tx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
if (HAL_DMA_Init(&hdma_usart1_tx) != HAL_OK) {
Error_Handler();
}
__HAL_LINKDMA(&huart1, hdmatx, hdma_usart1_tx);
// 启动DMA接收
HAL_UART_Receive_DMA(&huart1, usart1_dma_rx_buffer, USART1_DMA_RX_BUFFER_SIZE);
}
/**
* @brief DMA接收完成回调函数
*/
void HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef *huart) {
if (huart->Instance == USART1) {
// 半传输完成,处理前半部分数据
// 可以在这里处理数据
}
}
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
if (huart->Instance == USART1) {
// 传输完成,处理后半部分数据
// 可以在这里处理数据
}
}
/**
* @brief 使用DMA发送数据
*/
void USART1_DMA_Send(uint8_t *data, uint16_t len) {
HAL_UART_Transmit_DMA(&huart1, data, len);
}
2.5 主程序示例
/**
* @file main.c
* @brief STM32F407 串口收发测试主程序
*/
#include "stm32f4xx_hal.h"
#include "usart.h"
#include "delay.h"
int main(void) {
// 系统初始化
HAL_Init();
SystemClock_Config();
// 延时初始化
Delay_Init();
// 初始化串口
USART1_Init(115200);
USART2_Init(9600);
USART3_Init(57600);
// 初始化DMA接收(可选)
// USART1_DMA_Init();
printf("STM32F407 USART Test Program\r\n");
printf("System Clock: %lu Hz\r\n", HAL_RCC_GetSysClockFreq());
printf("Starting communication test...\r\n");
// 测试发送
USART_SendString(&huart1, "Hello from USART1!\r\n");
USART_SendString(&huart2, "Hello from USART2!\r\n");
USART_SendString(&huart3, "Hello from USART3!\r\n");
// 格式化输出测试
USART_Printf(&huart1, "Integer: %d, Float: %.2f\r\n", 12345, 3.14159f);
uint8_t counter = 0;
while (1) {
// 每秒发送一次数据
USART_Printf(&huart1, "Counter: %d, Time: %lu ms\r\n",
counter++, HAL_GetTick());
// 发送数组测试
uint8_t test_array[] = {0xAA, 0x55, 0x01, 0x02, 0x03};
USART_SendArray(&huart2, test_array, sizeof(test_array));
// 发送浮点数测试
float temperature = 25.6f + (counter % 10) * 0.1f;
USART_Printf(&huart3, "Temperature: %.1f C\r\n", temperature);
HAL_Delay(1000); // 延时1秒
}
}
2.6 重定向printf到串口
/**
* @file syscalls.c
* @brief 重定向printf到串口
*/
#include "usart.h"
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
// 重定向_write函数
int _write(int fd, char *ptr, int len) {
if (fd == STDOUT_FILENO || fd == STDERR_FILENO) {
// 发送到USART1
USART_SendArray(&huart1, (uint8_t *)ptr, len);
return len;
}
errno = EBADF;
return -1;
}
// 重定向_read函数
int _read(int fd, char *ptr, int len) {
if (fd == STDIN_FILENO) {
// 从USART1读取(这里简化实现)
// 实际应该使用接收缓冲区
return 0;
}
errno = EBADF;
return -1;
}
三、高级功能扩展
3.1 串口命令解析器
/**
* @file cmd_parser.c
* @brief 串口命令解析器
*/
typedef struct {
char command[20];
void (*handler)(void);
} Command_t;
// 命令处理函数
void Cmd_Help(void) {
USART_Printf(&huart1, "Available commands:\r\n");
USART_Printf(&huart1, " help - Show this help\r\n");
USART_Printf(&huart1, " led_on - Turn on LED\r\n");
USART_Printf(&huart1, " led_off - Turn off LED\r\n");
USART_Printf(&huart1, " reset - Reset system\r\n");
}
void Cmd_LED_On(void) {
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET);
USART_Printf(&huart1, "LED turned ON\r\n");
}
void Cmd_LED_Off(void) {
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET);
USART_Printf(&huart1, "LED turned OFF\r\n");
}
void Cmd_Reset(void) {
USART_Printf(&huart1, "System reset...\r\n");
NVIC_SystemReset();
}
// 命令表
Command_t command_table[] = {
{"help", Cmd_Help},
{"led_on", Cmd_LED_On},
{"led_off", Cmd_LED_Off},
{"reset", Cmd_Reset},
};
#define COMMAND_COUNT (sizeof(command_table) / sizeof(Command_t))
/**
* @brief 解析并执行命令
*/
void Parse_Command(char *cmd) {
// 去除换行符
char *newline = strchr(cmd, '\r');
if (newline) *newline = '\0';
newline = strchr(cmd, '\n');
if (newline) *newline = '\0';
// 查找匹配的命令
for (int i = 0; i < COMMAND_COUNT; i++) {
if (strcmp(cmd, command_table[i].command) == 0) {
command_table[i].handler();
return;
}
}
USART_Printf(&huart1, "Unknown command: %s\r\n", cmd);
USART_Printf(&huart1, "Type 'help' for available commands\r\n");
}
3.2 串口环形缓冲区(高性能)
/**
* @file ring_buffer.c
* @brief 串口环形缓冲区
*/
typedef struct {
uint8_t buffer[256];
uint16_t head;
uint16_t tail;
uint16_t count;
} RingBuffer_t;
static RingBuffer_t usart1_rx_ringbuf;
/**
* @brief 初始化环形缓冲区
*/
void RingBuffer_Init(RingBuffer_t *rb) {
rb->head = 0;
rb->tail = 0;
rb->count = 0;
}
/**
* @brief 写入一个字节到环形缓冲区
*/
void RingBuffer_Write(RingBuffer_t *rb, uint8_t data) {
if (rb->count < 256) {
rb->buffer[rb->head] = data;
rb->head = (rb->head + 1) % 256;
rb->count++;
}
}
/**
* @brief 从环形缓冲区读取一个字节
*/
uint8_t RingBuffer_Read(RingBuffer_t *rb) {
uint8_t data = 0;
if (rb->count > 0) {
data = rb->buffer[rb->tail];
rb->tail = (rb->tail + 1) % 256;
rb->count--;
}
return data;
}
/**
* @brief 检查缓冲区是否为空
*/
uint8_t RingBuffer_IsEmpty(RingBuffer_t *rb) {
return (rb->count == 0);
}
// 在串口中断中使用
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
if (huart->Instance == USART1) {
uint8_t received_byte = usart1_rx_buffer[0];
RingBuffer_Write(&usart1_rx_ringbuf, received_byte);
HAL_UART_Receive_IT(&huart1, usart1_rx_buffer, 1);
}
}
参考代码 STM32F407串口进行收发数据 www.youwenfan.com/contentcnv/60392.html
四、使用注意事项
4.1 常见问题解决
| 问题 |
原因 |
解决方案 |
| 串口无输出 |
波特率不匹配 |
检查双方波特率设置 |
| 接收数据乱码 |
时钟配置错误 |
检查系统时钟配置 |
| 接收中断不触发 |
未开启中断 |
确认HAL_UART_Receive_IT()被调用 |
| 数据丢失 |
接收速度过快 |
使用DMA接收或增大缓冲区 |
4.2 性能优化建议
- 使用DMA接收:对于高速数据,必须使用DMA
- 使用环形缓冲区:避免数据覆盖
- 避免在中断中处理复杂逻辑:只做数据接收
- 使用空闲中断:检测一帧数据结束
- 合理设置优先级:串口中断优先级要适当