R60ABD1 串口通信实现
R60ABD1 串口通信实现
引言
在嵌入式开发中,串口通信是一种常见的通信方式,广泛应用于传感器数据采集、设备间通信等场景。本文将详细介绍基于 CH32V30x 微控制器的 R60ABD1 模块的串口通信实现,包括初始化、数据发送与接收、以及数据包处理等功能。适合有一定基础的经验者学习。
本文基于以下代码实现,包含 R60ABD1.c
和 R60ABD1.h
两个文件,代码实现了一个完整的串口通信功能,支持数据包的发送与接收,并结合人机交互界面(HMI)和 MQTT 协议进行数据处理。
代码功能概述
R60ABD1.c
和 R60ABD1.h
实现了一个基于 USART2 的串口通信模块,主要功能包括:
- 串口初始化:配置 CH32V30x 的 USART2,包括波特率、GPIO 引脚、中断等。
- 数据发送:支持单字节、数组、字符串、数字以及格式化字符串的发送。
- 数据包处理:实现数据包的发送与接收,包含包头、数据和包尾的处理。
- 数据解析与应用:接收数据包后,根据特定协议解析数据并通过 HMI 显示或通过 MQTT 协议发送。
代码解析
1. 头文件与全局变量
在 R60ABD1.h
中,定义了必要的函数声明和全局变量:
全局变量:
Serial_TxPacket[4]
:用于存储发送数据包,格式为FF 01 02 03 04 FE
。Serial_RxPacket[6]
:用于存储接收数据包。Serial_RxFlag
:接收数据包的标志位,接收到完整数据包后置 1。setnum[5]
和getnum[3]
:分别用于存储设定值和接收到的数据(如呼吸、心率、体动)。
函数声明:
- 串口初始化、发送字节、发送数组、发送字符串、发送数字、格式化输出、发送数据包、获取接收标志位等。
#ifndef __R60ABD1_H
#define __R60ABD1_H
#include <stdio.h>
#include "ch32v30x.h"
extern uint8_t Serial_TxPacket[];
extern uint8_t Serial_RxPacket[];
void Serial_Init(void);
void Serial_SendByte(uint8_t Byte);
void Serial_SendArray(uint8_t *Array, uint16_t Length);
void Serial_SendString(char *String);
void Serial_SendNumber(uint32_t Number, uint8_t Length);
void Serial_Printf(char *format, ...);
void Serial_SendPacket(void);
uint8_t Serial_GetRxFlag(void);
void R60ABD1_Detect(void);
#endif
2. 串口初始化
Serial_Init
函数负责初始化 USART2,配置包括:
- 时钟使能:启用 USART2 和 GPIOA 的时钟。
- GPIO 配置:
- PA2 配置为复用推挽输出,用于发送(TX)。
- PA3 配置为上拉输入,用于接收(RX)。
- USART 参数:
- 波特率:115200。
- 数据位:8 位。
- 停止位:1 位。
- 无奇偶校验,无硬件流控制。
- 中断配置:启用接收中断(RXNE),设置 NVIC 优先级为组 2,抢占优先级和响应优先级分别为 2。
void Serial_Init(void)
{RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure);GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;GPIO_Init(GPIOA, &GPIO_InitStructure);USART_InitTypeDef USART_InitStructure;USART_InitStructure.USART_BaudRate = 115200;USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;USART_InitStructure.USART_Parity = USART_Parity_No;USART_InitStructure.USART_StopBits = USART_StopBits_1;USART_InitStructure.USART_WordLength = USART_WordLength_8b;USART_Init(USART2, &USART_InitStructure);USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);NVIC_InitTypeDef NVIC_InitStructure;NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;NVIC_Init(&NVIC_InitStructure);USART_Cmd(USART2, ENABLE);
}
3. 数据发送功能
代码提供了多种发送函数,满足不同场景的需求:
- 发送单字节 (
Serial_SendByte
):将单个字节写入 USART 数据寄存器,等待发送完成。 - 发送数组 (
Serial_SendArray
):遍历数组,逐字节发送。 - 发送字符串 (
Serial_SendString
):遍历字符串直到遇到\0
,逐字节发送。 - 发送数字 (
Serial_SendNumber
):将数字按位转换为字符并发送,支持指定长度。 - 格式化输出 (
Serial_Printf
):通过重定向fputc
和使用vsprintf
,实现类似printf
的功能。
void Serial_SendByte(uint8_t Byte)
{USART_SendData(USART2, Byte);while (USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET);
}void Serial_Printf(char *format, ...)
{char String[100];va_list arg;va_start(arg, format);vsprintf(String, format, arg);va_end(arg);Serial_SendString(String);
}
4. 数据包发送
Serial_SendPacket
函数用于发送固定格式的数据包,包头为 0xFF
,数据部分为 Serial_TxPacket
数组(4 字节),包尾为 0xFE
。
void Serial_SendPacket(void)
{Serial_SendByte(0xFF);Serial_SendArray(Serial_TxPacket, 4);Serial_SendByte(0xFE);
}
5. 数据接收与状态机
USART2_IRQHandler
中断函数使用状态机处理接收到的数据包,分为三个状态:
- 状态 0:等待包头(
0x53
或0x59
)。 - 状态 1:接收数据部分,存入
Serial_RxPacket
,接收 6 个字节后进入状态 2。 - 状态 2:验证包尾(
0x54
或0x43
),若正确,设置Serial_RxFlag
为 1。
void USART2_IRQHandler(void) __attribute__((interrupt("WCH-Interrupt-fast")));
void USART2_IRQHandler(void)
{static uint8_t RxState = 0;static uint8_t pRxPacket = 0;if (USART_GetITStatus(USART2, USART_IT_RXNE) == SET){uint8_t RxData = USART_ReceiveData(USART2);if (RxState == 0){if (RxData == 0x53) { pRxPacket = 0; }else if (RxData == 0x59) { RxState = 1; pRxPacket = 0; }}else if (RxState == 1){Serial_RxPacket[pRxPacket] = RxData;pRxPacket++;if (pRxPacket >= 6) { RxState = 2; }}else if (RxState == 2){if (RxData == 0x54) {}if (RxData == 0x43){RxState = 0;Serial_RxFlag = 1;}}USART_ClearITPendingBit(USART2, USART_IT_RXNE);}
}
6. 数据处理与应用
R60ABD1_Detect
函数处理接收到的数据包,根据协议解析并更新 getnum
数组(存储呼吸、心率、体动数据),通过 HMI_send_int
显示在人机界面,并通过 SendMQTT
发送到 MQTT 服务器。
void R60ABD1_Detect(void)
{if (Serial_GetRxFlag() == 1){if (Serial_RxPacket[0] == 0x81 && Serial_RxPacket[1] == 0x02){getnum[0] = Serial_RxPacket[4];HMI_send_int("breath.val", getnum[0]);SendMQTT("breath", &getnum[0], 0, 0);}else if (Serial_RxPacket[0] == 0x85 && Serial_RxPacket[1] == 0x02){getnum[1] = Serial_RxPacket[4];HMI_send_int("head.val", getnum[1]);SendMQTT("head", &getnum[1], 0, 0);}else if (Serial_RxPacket[0] == 0x80 && Serial_RxPacket[1] == 0x03){getnum[2] = Serial_RxPacket[4];HMI_send_int("activity.val", getnum[2]);SendMQTT("activity", &getnum[2], 0, 0);}}
}
使用说明
硬件连接:
- 确保 CH32V30x 微控制器的 PA2(TX)和 PA3(RX)引脚正确连接到 R60ABD1 模块的串口。
- 根据需要连接蜂鸣器(Buzzer)、HMI 屏幕和 ESP8266 模块(用于 MQTT)。
代码部署:
- 将
R60ABD1.c
和R60ABD1.h
集成到项目中。 - 确保包含必要的库文件(如
ch32v30x.h
、buzzer.h
、esp8266.h
、hmi.h
)。 - 在主函数中调用
Serial_Init()
初始化串口,周期性调用R60ABD1_Detect()
处理数据。
- 将
数据协议:
- 发送数据包格式:
FF [4字节数据] FE
。 - 接收数据包格式:
53/59 [6字节数据] 54/43
。 - 根据
Serial_RxPacket[0]
和Serial_RxPacket[1]
判断数据类型(如0x81
表示呼吸数据)。
- 发送数据包格式:
注意事项
- 中断函数名称:
USART2_IRQHandler
必须与启动文件中定义一致,否则中断无法触发。 - 数据包验证:确保接收到的包头和包尾正确,避免数据解析错误。
- 资源占用:串口通信会占用一定 CPU 资源,需合理配置中断优先级。