1. AT32F403AVGT7串口通信实战指南
作为一名嵌入式开发者,我最近在项目中使用了雅特力科技的AT32F403AVGT7微控制器。这款基于ARM Cortex-M4内核的MCU性能出色,但在实际开发中,我发现其串口通信的库函数使用有些特殊注意事项。本文将分享我在串口发送和接收中断方面的实战经验,特别是如何绕过官方库函数的限制实现高效数据传输。
2. 串口数据发送功能深度解析
2.1 官方库函数的局限性
AT32F403的固件库提供了usart_data_transmit函数用于串口发送,但这个函数存在一个明显的限制——每次只能发送单个字节。在实际项目中,我们经常需要发送字符串或数据包,这就显得非常不便。
函数原型如下:
c复制void usart_data_transmit(usart_type *usart_x, uint16_t data);
这个设计可能是出于最简实现的考虑,但对于开发者来说确实不够友好。我在首次使用时也踩了坑,以为可以直接发送数组,结果调试了半天才发现问题。
2.2 连续发送功能的实现方案
为了解决这个问题,我封装了一个更实用的发送函数uasrt_buf_trans,它可以连续发送指定长度的数据缓冲区。下面是完整的实现代码:
c复制#define TX_BUFF_SIZE 64
char txbuff[TX_BUFF_SIZE]; // 发送缓冲区
void uasrt_buf_trans(uint8_t *buff, uint8_t data_size)
{
uint32_t i;
for(i = 0; i < data_size; i++) {
usart_data_transmit(USART1, buff[i]);
while(usart_flag_get(USART1, USART_TDC_FLAG) == RESET);
}
}
这个函数的工作原理是:
- 通过for循环遍历缓冲区中的每个字节
- 对每个字节调用
usart_data_transmit发送 - 使用
usart_flag_get检查USART_TDC_FLAG标志位,确保上一个字节发送完成
重要提示:USART_TDC_FLAG表示传输数据完成标志,必须等待该标志置位才能发送下一个字节,否则会导致数据丢失或混乱。
2.3 实际应用中的优化技巧
在实际项目中,我进一步优化了这个函数:
- 添加超时机制:防止因硬件故障导致死等
c复制uint32_t timeout = 100000; // 超时计数器
while((usart_flag_get(USART1, USART_TDC_FLAG) == RESET) && (timeout--));
if(timeout == 0) {
// 超时处理
}
- 支持多串口:通过参数传递USART外设
c复制void uasrt_buf_trans(usart_type *usart_x, uint8_t *buff, uint8_t data_size)
{
// 函数实现
}
- 缓冲区保护:检查data_size不超过缓冲区大小
c复制if(data_size > TX_BUFF_SIZE) {
data_size = TX_BUFF_SIZE;
}
3. 串口接收中断的完整实现
3.1 中断配置的关键要点
串口接收中断是实时数据采集的关键,但在AT32F403上配置时有个极易忽略的坑——必须在初始化函数中显式使能中断。我在第一次使用时花了半天时间调试才发现这个问题。
正确的初始化流程应该包含:
c复制void wk_usart1_init(void)
{
// 其他初始化代码...
/* 必须添加这两行! */
usart_interrupt_enable(USART1, USART_RDBF_INT, TRUE); // 使能接收缓冲区非空中断
usart_interrupt_enable(USART1, USART_IDLE_INT, TRUE); // 使能空闲线路中断
nvic_irq_enable(USART1_IRQn, 0, 0); // 使能USART1全局中断
}
3.2 中断服务程序详解
下面是我在实际项目中使用的完整中断处理程序,包含接收缓冲和空闲检测:
c复制#define RX_BUFF_SIZE 64
uint8_t rx_buff[RX_BUFF_SIZE];
uint8_t rx_cnt = 0;
uint8_t rx_flag = 0;
void USART1_IRQHandler(void)
{
uint32_t temp;
// 接收缓冲区非空中断处理
if(usart_flag_get(USART1, USART_RDBF_FLAG) != RESET) {
if(rx_cnt < RX_BUFF_SIZE) {
rx_buff[rx_cnt++] = USART1->dt; // 读取数据寄存器
} else {
// 缓冲区溢出处理
rx_cnt = 0;
}
}
// 空闲线路中断处理
if(usart_flag_get(USART1, USART_IDLEF_FLAG) != RESET) {
temp = USART1->sts; // 读状态寄存器清除标志
temp = USART1->dt; // 读数据寄存器清除标志
(void)temp; // 防止编译器警告
rx_flag = 1; // 设置接收完成标志
}
}
3.3 中断处理的注意事项
-
标志清除机制:
- AT32F403的空闲中断标志需要通过先读STS寄存器再读DT寄存器来清除
- 这个操作顺序很重要,否则可能导致标志无法正确清除
-
缓冲区管理:
- 必须检查rx_cnt不超过缓冲区大小
- 实际项目中建议使用环形缓冲区提高效率
-
中断响应时间:
- 保持ISR尽可能简短
- 复杂处理可以设置标志在主循环中执行
4. 常见问题与解决方案
4.1 数据发送不完整
现象:只有部分数据被发送出去
原因:
- 没有正确等待USART_TDC_FLAG标志
- 发送过程中被更高优先级中断打断
解决方案: - 确保每次发送后检查USART_TDC_FLAG
- 调整中断优先级,确保发送过程不被干扰
4.2 无法进入接收中断
现象:数据已到达但无法触发中断
原因:
- 忘记使能USART_RDBF_INT中断
- NVIC中断未使能
解决方案: - 检查
usart_interrupt_enable调用 - 确认
nvic_irq_enable已正确配置
4.3 空闲中断误触发
现象:rx_flag被意外置位
原因:
- 空闲中断标志未正确清除
- 线路噪声导致虚假空闲状态
解决方案: - 严格按照STS+DT的顺序清除标志
- 添加软件去抖机制
5. 性能优化建议
5.1 DMA传输方案
对于高速数据传输,建议使用DMA代替中断:
c复制// DMA发送配置示例
dma_init_type dma_init_struct;
dma_default_para_set(&dma_init_struct);
dma_init_struct.direction = DMA_DIR_MEMORY_TO_PERIPHERAL;
dma_init_struct.memory_data_width = DMA_MEMORY_DATA_WIDTH_BYTE;
dma_init_struct.memory_inc_enable = TRUE;
dma_init_struct.peripheral_data_width = DMA_PERIPHERAL_DATA_WIDTH_BYTE;
dma_init_struct.peripheral_inc_enable = FALSE;
dma_init_struct.priority = DMA_PRIORITY_HIGH;
dma_init(DMA1_CHANNEL4, &dma_init_struct);
5.2 环形缓冲区实现
高效的数据缓冲方案:
c复制typedef struct {
uint8_t buffer[RX_BUFF_SIZE];
uint16_t head;
uint16_t tail;
} ring_buffer_t;
void ring_buffer_put(ring_buffer_t *rb, uint8_t data) {
rb->buffer[rb->head++] = data;
if(rb->head >= RX_BUFF_SIZE) rb->head = 0;
}
uint8_t ring_buffer_get(ring_buffer_t *rb) {
uint8_t data = rb->buffer[rb->tail++];
if(rb->tail >= RX_BUFF_SIZE) rb->tail = 0;
return data;
}
5.3 低功耗优化
在电池供电应用中:
- 仅在需要通信时使能USART时钟
- 使用硬件流控减少CPU唤醒次数
- 合理设置波特率降低功耗
6. 实际项目应用案例
6.1 与上位机的通信协议
基于以上串口函数,我实现了一个简单的通信协议:
c复制#pragma pack(1)
typedef struct {
uint8_t header; // 0xAA
uint8_t cmd;
uint8_t len;
uint8_t data[32];
uint8_t checksum;
} protocol_frame_t;
#pragma pack()
void protocol_send(uint8_t cmd, uint8_t *data, uint8_t len) {
protocol_frame_t frame;
frame.header = 0xAA;
frame.cmd = cmd;
frame.len = len;
memcpy(frame.data, data, len);
frame.checksum = calculate_checksum(&frame);
uasrt_buf_trans(USART1, (uint8_t*)&frame, sizeof(frame));
}
6.2 多设备通信的实践经验
在RS485多设备通信中需要注意:
- 添加方向控制引脚管理收发状态
- 每个数据包后添加适当延时
- 使用唯一设备ID避免冲突
7. 调试技巧与工具推荐
7.1 逻辑分析仪的使用
推荐使用Saleae逻辑分析仪:
- 捕获实际发送的波形
- 验证波特率设置是否正确
- 检查数据是否符合预期
7.2 调试输出技巧
在调试阶段可以添加调试输出:
c复制#define DEBUG_ENABLE 1
void debug_printf(const char *fmt, ...) {
#if DEBUG_ENABLE
char buf[128];
va_list args;
va_start(args, fmt);
vsnprintf(buf, sizeof(buf), fmt, args);
va_end(args);
uasrt_buf_trans(USART1, (uint8_t*)buf, strlen(buf));
#endif
}
7.3 常见波特率误差计算
AT32F403的USART时钟配置公式:
code复制波特率 = USART_CLK / (16 * DIV)
其中DIV = USARTDIV整数部分 + (USARTDIV小数部分 / 16)
计算示例(72MHz主频,115200波特率):
code复制USARTDIV = 72000000 / (115200 * 16) = 39.0625
DIV_Mantissa = 39
DIV_Fraction = 0.0625 * 16 = 1
实际配置:
c复制usart_init_struct.baud_rate = 115200;
usart_init_struct.baud_rate_div = USART_BAUDRATE_DIV(39, 1);