当前位置:首页 > 嵌入式 > 嵌入式分享
[导读]USART(通用同步/异步收发器)是MCU与外部设备通信最基础也最常用的外设之一。在GD32系列MCU中,USART支持全双工异步通信,通过TX/RX两根信号线即可实现与PC上位机、蓝牙模块、其他MCU的数据交互。

USART(通用同步/异步收发器)是MCU与外部设备通信最基础也最常用的外设之一。在GD32系列MCU中,USART支持全双工异步通信,通过TX/RX两根信号线即可实现与PC上位机、蓝牙模块、其他MCU的数据交互。

中断驱动是USART通信的核心机制。相比轮询方式(CPU不断查询状态寄存器),中断方式在数据到达时由硬件自动触发中断服务函数(ISR),CPU仅在真正有数据需要处理时才介入,其余时间可执行其他任务或进入低功耗模式。对于115200bps的波特率,每个字节传输约87μs,若采用轮询方式,CPU将被大量无效查询消耗;而中断方式下,CPU仅在每87μs被唤醒一次处理一个字节,效率提升数十倍。

本方案涉及的关键原理包括:波特率发生器通过分频系统时钟产生精确的采样时钟(GD32的USART波特率寄存器BRR直接写入分频系数);接收数据寄存器(RDR)在接收到完整帧后自动置位RXNE标志并触发中断;发送数据寄存器(TDR)在数据移入移位寄存器后自动置位TC标志;NVIC(嵌套向量中断控制器)负责管理中断优先级和使能。

上位机通信协议采用"帧头 + 长度 + 命令码 + 数据 + 校验"的自定义格式,上位机通过USB转TTL模块(如CH340/CP2102)连接GD32的USART引脚,以115200bps、8N1格式收发数据。

程序框架分析说明

整个USART中断通信程序采用分层架构,自底向上分为四层:

硬件抽象层(HAL):负责GPIO复用配置(TX引脚设为复用推挽输出,RX引脚设为复用浮空输入)、USART寄存器初始化(波特率、数据位、停止位、校验位)、NVIC中断优先级配置。

驱动层(Driver):封装收发接口。接收侧维护一个环形缓冲区(Ring Buffer),ISR中每收到一个字节即写入缓冲区;发送侧提供usart_send_packet()函数,将应用层数据打包为协议帧后逐字节写入TDR,通过TC中断实现非阻塞发送。

协议解析层(Protocol):实现帧同步与解析。采用状态机模型(空闲→接收帧头→接收长度→接收数据→校验→完成),逐字节解析接收缓冲区中的数据,提取完整帧后回调应用层处理函数。

应用层(App):根据命令码执行具体业务逻辑(如读取传感器数据、控制LED、返回设备信息等),并将响应数据通过usart_send_packet()回传上位机。

程序实现

1. USART与GPIO初始化

以GD32F407的USART0(PA9-TX, PA10-RX)为例,配置115200bps、8数据位、1停止位、无校验:

void usart_init(void)

{

/* 使能时钟 */

rcu_periph_clock_enable(RCU_GPIOA);

rcu_periph_clock_enable(RCU_USART0);

/* PA9: TX - 复用推挽输出 */

gpio_mode_set(GPIOA, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_PIN_9);

gpio_output_options_set(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_9);

gpio_af_set(GPIOA, GPIO_AF_7, GPIO_PIN_9);

/* PA10: RX - 复用浮空输入 */

gpio_mode_set(GPIOA, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_10);

gpio_af_set(GPIOA, GPIO_AF_7, GPIO_PIN_10);

/* USART参数配置 */

usart_deinit(USART0);

usart_baudrate_set(USART0, 115200U);

usart_word_length_set(USART0, USART_WL_8BIT);

usart_stop_bit_set(USART0, USART_STB_1BIT);

usart_parity_config(USART0, USART_PM_NONE);

usart_hardware_flow_rts_config(USART0, USART_RTS_DISABLE);

usart_hardware_flow_cts_config(USART0, USART_CTS_DISABLE);

usart_receive_config(USART0, USART_RECEIVE_ENABLE);

usart_transmit_config(USART0, USART_TRANSMIT_ENABLE);

/* 使能接收中断 */

usart_interrupt_enable(USART0, USART_INT_RBNE);

/* NVIC配置:USART0中断,抢占优先级1,子优先级0 */

nvic_irq_enable(USART0_IRQn, 1, 0);

usart_enable(USART0);

}

2. 环形缓冲区与接收中断服务函数

#define RX_BUF_SIZE 256

typedef struct {

uint8_t buf[RX_BUF_SIZE];

volatile uint16_t head;

volatile uint16_t tail;

} RingBuffer_t;

RingBuffer_t rx_ring = {{0}, 0, 0};

void ring_buffer_write(RingBuffer_t *rb, uint8_t data)

{

uint16_t next = (rb->head + 1) % RX_BUF_SIZE;

if (next != rb->tail) { /* 缓冲区未满 */

rb->buf[rb->head] = data;

rb->head = next;

}

}

uint8_t ring_buffer_read(RingBuffer_t *rb)

{

if (rb->head == rb->tail) return 0xFF; /* 缓冲区为空 */

uint8_t data = rb->buf[rb->tail];

rb->tail = (rb->tail + 1) % RX_BUF_SIZE;

return data;

}

/* USART0中断服务函数 */

void USART0_IRQHandler(void)

{

if (usart_interrupt_flag_get(USART0, USART_INT_FLAG_RBNE)) {

uint8_t byte = usart_data_receive(USART0);

ring_buffer_write(&rx_ring, byte);

}

}

3. 协议帧解析状态机

#define FRAME_HEAD 0xAA

#define FRAME_MAX_LEN 64

typedef enum {

STATE_IDLE,

STATE_LEN,

STATE_CMD,

STATE_DATA,

STATE_CHECK

} ParseState_t;

typedef struct {

uint8_t cmd;

uint8_t len;

uint8_t data[FRAME_MAX_LEN];

} ParsedFrame_t;

ParseState_t parse_state = STATE_IDLE;

uint8_t parse_idx = 0;

ParsedFrame_t current_frame;

void protocol_parse(void)

{

while (rx_ring.head != rx_ring.tail) {

uint8_t byte = ring_buffer_read(&rx_ring);

switch (parse_state) {

case STATE_IDLE:

if (byte == FRAME_HEAD) parse_state = STATE_LEN;

break;

case STATE_LEN:

if (byte > FRAME_MAX_LEN) {

parse_state = STATE_IDLE; /* 长度非法,丢弃 */

} else {

current_frame.len = byte;

parse_idx = 0;

parse_state = STATE_CMD;

}

break;

case STATE_CMD:

current_frame.cmd = byte;

parse_state = (current_frame.len > 0) ? STATE_DATA : STATE_CHECK;

break;

case STATE_DATA:

current_frame.data[parse_idx++] = byte;

if (parse_idx >= current_frame.len) parse_state = STATE_CHECK;

break;

case STATE_CHECK:

/* 校验:帧头 + 长度 + 命令 + 数据 的累加和 */

uint8_t checksum = FRAME_HEAD + current_frame.len

+ current_frame.cmd;

for (int i = 0; i < current_frame.len; i++)

checksum += current_frame.data[i];

if (checksum == byte) {

app_handle_frame(¤t_frame); /* 帧完整,回调应用层 */

}

parse_state = STATE_IDLE;

break;

}

}

}

4. 发送接口与应用层处理

void usart_send_byte(uint8_t data)

{

usart_data_transmit(USART0, data);

while (RESET == usart_flag_get(USART0, USART_FLAG_TBE));

}

void usart_send_packet(uint8_t cmd, uint8_t *data, uint8_t len)

{

uint8_t checksum = FRAME_HEAD + len + cmd;

usart_send_byte(FRAME_HEAD);

usart_send_byte(len);

usart_send_byte(cmd);

for (int i = 0; i < len; i++) {

usart_send_byte(data[i]);

checksum += data[i];

}

usart_send_byte(checksum);

}

/* 应用层帧处理 */

void app_handle_frame(ParsedFrame_t *frame)

{

switch (frame->cmd) {

case 0x01: /* 读取设备ID */

{

uint8_t resp[] = {0x01, 0x02, 0x03, 0x04};

usart_send_packet(0x81, resp, 4);

break;

}

case 0x02: /* 控制LED */

if (frame->data[0]) gpio_bit_set(GPIOC, GPIO_PIN_13);

else gpio_bit_reset(GPIOC, GPIO_PIN_13);

usart_send_packet(0x82, NULL, 0); /* ACK */

break;

}

}

int main(void)

{

system_init();

usart_init();

while (1) {

protocol_parse(); /* 主循环中持续解析接收数据 */

}

}

应用

上位机调试工具:PC端通过串口助手或自研上位机软件发送命令帧,实时读取GD32的ADC采样值、GPIO状态、寄存器配置等,实现参数在线调整和固件调试。

传感器数据上报:GD32采集温湿度、压力、加速度等传感器数据,按协议帧格式打包后通过USART上报至上位机或网关设备,上位机实时绘制波形曲线。

固件OTA升级:上位机通过USART发送固件分片数据帧,GD32接收后写入Flash指定区域,校验通过后跳转执行新固件,实现串口IAP升级。

多设备级联通信:多个GD32节点通过USART串联(RS485总线),上位机以广播帧下发命令,各节点根据设备地址过滤响应,实现一对多控制。

蓝牙/WiFi透传:GD32的USART连接蓝牙模块(如HC-05),手机APP通过蓝牙发送数据,GD32接收解析后执行对应操作,实现无线远程控制。

通过中断驱动 + 环形缓冲区 + 协议状态机的三层架构,本方案在保证通信可靠性的同时,将CPU占用率降至最低,为GD32与上位机的高效数据交互提供了一套完整且可复用的实现框架。

本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除( 邮箱:macysun@21ic.com )。
换一批
延伸阅读

程序跑了三天突然HardFault,DMA传着传着数据全乱了,I2C读到一坨0xFF——这些场景几乎是每个GD32开发者都经历过的"至暗时刻"。本文从程序原理、框架设计到具体实现,系统梳理GD32 C...

关键字: GD32 C语言开发

异常处理的本质是"兜底"。‌ C语言没有try-catch,但GD32的硬故障异常(HardFault、BusFault、UsageFault、MemManage)天然提供了最后一道防线。当程序跑飞、...

关键字: C语言 GD32

对于嵌入式开发者来说,Keil MDK无疑是最熟悉的开发环境之一。但对于初次接触GD32系列MCU的工程师,即使是“点个灯”这样看似简单的任务,也可能在工程配置环节卡上半天。Keil社区版(MDK-Community)的...

关键字: Keil环境 GD32

当你手握一块主频108MHz的GD32F103,满心期待它能跑出漂亮的运算成绩,却发现无优化编译下的Dhrystone跑分竟与隔壁同样主频的ARM Cortex-M3相差近三倍——这不是芯片的问题,是你的编译器在&quo...

关键字: Dhrystone GD32

该项目是一个基于微控制器的C语言编写的音频生成器,配备三个按钮,用户可通过按钮播放声音或调节音高。它作为基本GPIO控制和按钮逻辑的实际演示。

关键字: 微控制器 C语言 音频生成器 STM32F407ZGT6

在C语言的众多运算符中,三目运算符是唯一需要三个操作数的特殊存在,它的语法形式为“条件表达式 ? 表达式1 : 表达式2”。很多开发者对它的认知停留在“if-else的简写”层面,日常写代码时要么过度滥用把代码写得晦涩难...

关键字: C语言 运算符

在C语言开发中,struct(结构体)是最基础也最灵活的语法特性之一。几乎每个C语言开发者都会用struct定义自定义数据类型,但大多数人只停留在“把不同类型变量打包”的基础用法上,很少深挖它能玩出多少花样。实际上,从数...

关键字: struct C语言

在计算机系统设计中,缓存是提升数据访问性能的核心技术——无论是CPU的高速缓存,还是操作系统的页面置换,亦或是后端服务的热点数据缓存,都离不开高效的缓存淘汰策略。在众多淘汰算法中,LRU(Least Recently U...

关键字: LRU C语言

在嵌入式开发领域,C语言始终是绝对的主流,而指针则是C语言最核心、最灵活也最容易踩坑的特性。对于嵌入式开发来说,我们需要直接操作硬件寄存器、管理内存缓冲区、处理网络数据包、回调驱动事件,几乎所有核心功能都离不开指针。而随...

关键字: C语言 嵌入式
关闭