Initial commit

This commit is contained in:
2026-03-02 21:24:12 +08:00
commit 4c93905128
98 changed files with 54745 additions and 0 deletions

52
Src/board_config.c Normal file
View File

@@ -0,0 +1,52 @@
#include "gd32e23x.h"
#include "board_config.h"
#include "systick.h"
/******************************************************************************/
#define FLASH_SIZE_ADDR (*(const uint8_t *)0x1FFFF7E0) // Flash base address
/******************************************************************************/
/* 前向声明中断处理函数 */
void usart0_irq_handler(void);
void usart1_irq_handler(void);
usart_config_t g_usart_config = {
.rcu_usart = RCU_USART1,
.usart_periph = USART1,
.irq_type = USART1_IRQn,
.irq_handler = usart1_irq_handler // 初始化函数指针
};
uint8_t g_mcu_flash_size = 0;
void mcu_detect_and_config(void) {
g_mcu_flash_size = FLASH_SIZE_ADDR;
switch (g_mcu_flash_size) {
case GD32E23XF4:
g_usart_config.rcu_usart = RCU_USART0;
g_usart_config.usart_periph = USART0;
g_usart_config.irq_type = USART0_IRQn;
g_usart_config.irq_handler = usart0_irq_handler; // 指向USART0处理函数
break;
case GD32E23XF6:
g_usart_config.rcu_usart = RCU_USART1;
g_usart_config.usart_periph = USART1;
g_usart_config.irq_type = USART1_IRQn;
g_usart_config.irq_handler = usart1_irq_handler; // 指向USART1处理函数
break;
default: // Default to GD32E23XF8
g_usart_config.rcu_usart = RCU_USART1;
g_usart_config.usart_periph = USART1;
g_usart_config.irq_type = USART1_IRQn;
g_usart_config.irq_handler = usart1_irq_handler; // 指向USART1处理函数
break;
}
}
uint8_t get_flash_size(void) {
return g_mcu_flash_size;
}

646
Src/command.c Normal file
View File

@@ -0,0 +1,646 @@
/**
* @file command.c
* @brief 串口命令解析与处理模块实现
* @details 实现基于状态机的协议解析器,支持 D5 03 LEN [cmd] CRC 格式的命令处理,
* 包含命令帧解析、响应生成和传感器状态管理功能。
* @author Hulk
* @date 2025-08-13
* @version 1.0.0
* @ingroup Command
*/
#include "command.h"
#include "uart_ring_buffer.h"
#include "uart.h"
#include "led.h"
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include "board_config.h"
#include "gd32e23x_usart.h"
#include "i2c.h"
#include "core_cm23.h"
/* ============================================================================
* 协议格式说明
* ============================================================================ */
/**
* @name 协议帧格式
* @{
* @details
* Host -> Device 命令帧格式:
* [0] HEADER = 0xD5 // 包头标识
* [1] BOARD_TYPE = 0x01 // 板卡类型标识
* [2] LEN = 数据区字节数 // 有效载荷长度
* [3..(3+LEN-1)] 数据 // 命令数据,如 "M1", "M2S123"
* [last] CRC = 校验码 // 从索引1到(last-1)的累加和低8位
*
* 最小协议包长度为 6 字节
* 数据示例(两字节命令):"M1" / "M2" / "M3"
*
* Device -> Host 响应帧格式:
* [0] 0xB5 // 响应包头
* [1] TYPE // 响应类型0xF0=成功0xF1..=错误类型)
* [2] LEN // 响应数据长度
* [3..(3+LEN-1)] 数据 // 响应数据,如 "ok", "err"
* [last] CRC // 校验码(同命令帧规则)
* @}
*/
/* ============================================================================
* 协议常量定义
* ============================================================================ */
/** @name 协议帧标识符
* @{ */
#define PROTOCOL_PACKAGE_HEADER 0xD5 /**< 命令帧包头标识 */
/** @} */
/** @name 命令长度限制
* @{ */
#define COMMAND_MIN_LEN 2 /**< 最小命令长度,如"M1" */
#define PROTOCOL_MIN_FRAME_LEN (3 + COMMAND_MIN_LEN + 1) /**< 最小完整帧长度header+type+len+payload+crc = 6 */
#define PROTOCOL_MAX_FRAME_LEN 32 /**< 最大完整帧长度 */
/** @} */
/** @name 响应帧标识符
* @{ */
#define RESP_HEADER 0xB5 /**< 响应帧包头标识 >**/
#define RESP_TYPE_OK 0xF0 /**< 成功响应类型 >**/
#define RESP_TYPE_CRC_ERR 0xF1 /**< CRC校验错误 >**/
#define RESP_TYPE_HEADER_ERR 0xF2 /**< 包头错误 >**/
#define RESP_TYPE_TYPE_ERR 0xF3 /**< 板类型错误 >**/
#define RESP_TYPE_LEN_ERR 0xF4 /**< 长度错误 >**/
#define RESP_TYPE_PARAM_ERR 0xFD /**< 参数错误 >**/
#define RESP_TYPE_CMD_ERR 0xFE /**< 命令错误 >**/
#define RESP_TYPE_ERR 0xFF /**< 通用错误 >**/
/** @} */
/* ============================================================================
* 模块内部变量
* ============================================================================ */
/** @name 预设响应数据
* @{ */
static const uint8_t s_report_status_ok[] __attribute__((unused)) = { 'o', 'k' }; /**< 成功响应数据 */
static const uint8_t s_report_status_err[] = { 'e','r','r' }; /**< 错误响应数据 */
/** @} */
void system_software_reset(void)
{
// 确保所有待处理的内存访问完成
__DSB();
// 执行系统复位
NVIC_SystemReset();
// 以下代码不会执行
while(1);
}
/* Debug output control */
#ifdef COM_DEBUG
#include <stdio.h>
#define COMMAND_DEBUG(fmt, ...) printf("[COMMAND] " fmt "\n", ##__VA_ARGS__)
#else
#define COMMAND_DEBUG(fmt, ...)
#endif
/* ============================================================================
* 公共接口函数
* ============================================================================ */
/**
* @brief 计算协议包的 8 位累加校验值Checksum
* @details 对输入缓冲区逐字节累加并取低 8 位,累加范围为 data[1] 至 data[len-2]
* 即不包含包头 HEADER索引 0与尾部 CRC 字节(索引 len-1
* 当 len 小于最小协议帧长度PACKAGE_MIN_LENGTH时返回 0。
* @param data 指向待校验的完整协议包缓冲区。
* @param len 缓冲区总长度(字节),应满足 header + type + len + payload + crc 的最小格式。
* @return uint8_t 计算得到的 8 位校验值。
* @note 本函数实现为简单求和校验Checksum非多项式 CRC与本协议“从索引 1 累加到 len-2”的规则一致。
* @ingroup Command
*/
static uint8_t command_sum_crc_calc(const uint8_t *data, uint8_t len)
{
uint16_t crc = 0;
// 仅在满足协议最小帧长时计算header + type + len + payload + crc
if (len < PROTOCOL_MIN_FRAME_LEN) return 0;
// 累加从索引 1 到 len-2 的字节(不含 header 和 crc 字节)
for (uint8_t i = 1; i < (len - 1); i++)
{
crc += data[i];
}
return (uint8_t)(crc & 0xFF);
}
/**
* @brief 发送协议响应帧使用GD32E230标准库
* @details 构造并发送格式为 B5 TYPE LEN [payload] CRC 的响应帧,
* 自动计算CRC校验值并通过串口输出。
* @param type 响应类型码(如 RESP_TYPE_OK, RESP_TYPE_CRC_ERR 等)。
* @param payload 指向响应数据的缓冲区当len为0时可为NULL。
* @param len 响应数据长度字节为0时不复制payload数据。
* @note 内部使用固定大小缓冲区,超长响应将被丢弃。
* @warning 使用GD32E230标准库函数发送确保串口已正确初始化。
* @ingroup Command
*/
static void send_response(uint8_t type, const uint8_t *payload, uint8_t len)
{
uint8_t buf_len = (uint8_t)(3 + len + 1);
uint8_t buf[16]; // 简单场景足够,必要时可增大
if (buf_len > sizeof(buf)) return; // 防御
buf[0] = RESP_HEADER;
buf[1] = type;
buf[2] = len;
// 简化逻辑只有当len > 0且payload非空时才复制数据
if (len > 0 && payload != NULL) {
for (uint8_t i = 0; i < len; i++) {
buf[3 + i] = payload[i];
}
}
buf[buf_len - 1] = command_sum_crc_calc(buf, buf_len);
// 使用GD32E230标准库函数逐字节发送标准库实现
for (uint8_t i = 0; i < buf_len; i++) {
// 等待发送缓冲区空
while (usart_flag_get(UART_PHY, USART_FLAG_TBE) == RESET) {}
usart_data_transmit(UART_PHY, buf[i]);
}
// 等待发送完成
while (usart_flag_get(UART_PHY, USART_FLAG_TC) == RESET) {}
// // 使用printf发送通过重定向到串口
// for (uint8_t i = 0; i < buf_len; i++) {
// printf("%c", buf[i]);
// }
// // 刷新缓冲区
// fflush(stdout);
}
/**
* @brief 发送协议响应帧调试用发送到DEBUG_UART
* @details 构造并发送格式为 B5 TYPE LEN [payload] CRC 的响应帧,
* 自动计算CRC校验值并通过DEBUG_UART输出。
* @param type 响应类型码(如 RESP_TYPE_OK, RESP_TYPE_CRC_ERR 等)。
* @param payload 指向响应数据的缓冲区当len为0时可为NULL。
* @param len 响应数据长度字节为0时不复制payload数据。
* @note 内部使用固定大小缓冲区,超长响应将被丢弃。
* @warning 使用GD32E230标准库函数发送确保DEBUG_UART已正确初始化。
* @ingroup Command
*/
static void send_response_debug(uint8_t type, const uint8_t *payload, uint8_t len) __attribute__((unused));
static void send_response_debug(uint8_t type, const uint8_t *payload, uint8_t len)
{
uint8_t buf_len = (uint8_t)(3 + len + 1);
uint8_t buf[16]; // 简单场景足够,必要时可增大
if (buf_len > sizeof(buf)) return; // 防御
buf[0] = RESP_HEADER;
buf[1] = type;
buf[2] = len;
// 简化逻辑只有当len > 0且payload非空时才复制数据
if (len > 0 && payload != NULL) {
for (uint8_t i = 0; i < len; i++) {
buf[3 + i] = payload[i];
}
}
buf[buf_len - 1] = command_sum_crc_calc(buf, buf_len);
// 使用GD32E230标准库函数逐字节发送标准库实现
for (uint8_t i = 0; i < buf_len; i++) {
// 等待发送缓冲区空
while (usart_flag_get(DEBUG_UART, USART_FLAG_TBE) == RESET) {}
usart_data_transmit(DEBUG_UART, buf[i]);
}
// 发送换行符 \r\n
// while (usart_flag_get(DEBUG_UART, USART_FLAG_TBE) == RESET) {}
// usart_data_transmit(DEBUG_UART, '\r');
// while (usart_flag_get(DEBUG_UART, USART_FLAG_TBE) == RESET) {}
// usart_data_transmit(DEBUG_UART, '\n');
// 等待发送完成
while (usart_flag_get(DEBUG_UART, USART_FLAG_TC) == RESET) {}
}
/**
* @brief 判断字符是否为十进制数字字符。
* @param c 待检查的字符ASCII码值
* @return bool 判断结果。
* @retval true 字符为 '0' 到 '9' 之间的数字字符。
* @retval false 字符不是十进制数字字符。
* @ingroup Command
*/
static inline bool is_dec_digit(uint8_t c) { return (c >= '0' && c <= '9'); }
/**
* @brief 将一个无符号整数转换为字符串并追加到缓冲区。
* @param value 要转换的数字。
* @param buffer 指向目标缓冲区的指针,转换后的字符串将写入此处。
* @return uint8_t 写入的字符数。
*/
static uint8_t uint_to_str(uint32_t value, char *buffer) {
char temp[10]; // 32位无符号整数最多10位
int i = 0;
if (value == 0) {
buffer[0] = '0';
return 1;
}
// 将数字逆序转换为字符存入临时数组
while (value > 0) {
temp[i++] = (char)((value % 10) + '0');
value /= 10;
}
// 将逆序的字符串反转并存入目标缓冲区
uint8_t len = (uint8_t)i;
for (int j = 0; j < len; j++) {
buffer[j] = temp[--i];
}
return len;
}
/**
* @brief 将有符号整数转换为字符串
* @param value 要转换的数字
* @param buffer 目标缓冲区
* @return uint8_t 写入的字符数
*/
static uint8_t __attribute__((unused)) int_to_str(int32_t value, char *buffer) {
uint8_t len = 0;
if (value < 0) {
buffer[0] = '-';
len++;
// 处理最小负数溢出问题 (虽然int16不会溢出int32但为了健壮性)
// 这里直接取反转为正数处理
value = -value;
}
len += uint_to_str((uint32_t)value, &buffer[len]);
return len;
}
/**
* @brief 从缓冲区解析十进制无符号整数。
* @details 从指定位置开始连续读取十进制数字字符累加构成32位无符号整数。
* 遇到非数字字符或到达长度限制时停止解析。
* @param s 指向待解析字符缓冲区的起始位置。
* @param n 允许解析的最大字符数。
* @param out 输出参数存储解析结果可为NULL。
* @return uint8_t 实际消耗的字符数。
* @retval 0 首字符不是数字,解析失败。
* @retval >0 成功解析的数字字符个数。
* @note 不处理符号、空白字符或进制前缀。
* @warning 不进行溢出检查超出uint32_t范围时按无符号算术溢出处理。
* @ingroup Command
*/
static uint8_t parse_uint_dec(const uint8_t *s, uint8_t n, uint32_t *out)
{
uint8_t i = 0;
uint32_t v = 0;
while (i < n && is_dec_digit(s[i]))
{
v = v * 10u + (uint32_t)(s[i] - '0');
i++;
}
if (i == 0) return 0; // 未读到数字
if (out) *out = v; //
return i;
}
/**
* @brief 通用T参数解析函数
* @details 解析命令中的T参数定时参数格式为T<数字>
* @param cmd 指向命令缓冲区
* @param cmd_index 当前解析位置的指针(会被更新)
* @param cmd_len 命令总长度
* @param timer_value 输出参数,存储解析到的定时值
* @return bool 解析结果
* @retval true 成功解析到T参数
* @retval false 没有T参数或解析失败
* @note 如果解析成功cmd_index会被更新到T参数后的位置
* @ingroup Command
*/
static bool __attribute__((unused)) parse_timer_parameter(const uint8_t *cmd, uint8_t *cmd_index, uint8_t cmd_len, uint32_t *timer_value)
{
if (*cmd_index >= cmd_len || cmd[*cmd_index] != 'T') {
return false; // 没有T参数
}
uint8_t temp_index = *cmd_index + 1; // T后的位置
const uint8_t used_timer_cmd = parse_uint_dec(&cmd[temp_index], (uint8_t)(cmd_len - temp_index), timer_value);
if (used_timer_cmd == 0) {
return false; // T后面没有有效数字
}
*cmd_index = (uint8_t)(temp_index + used_timer_cmd); // 更新索引
return true;
}
/**
* @brief 检查命令是否完全解析完毕
* @details 验证命令中是否还有未解析的字符,用于格式验证
* @param cmd_index 当前解析位置
* @param cmd_len 命令总长度
* @return bool 检查结果
* @retval true 命令完全解析完毕
* @retval false 还有未解析的字符
* @ingroup Command
*/
static bool __attribute__((unused)) is_command_fully_parsed(uint8_t cmd_index, uint8_t cmd_len)
{
return (cmd_index == cmd_len);
}
/**
* @brief 在命令字符串中查找指定参数的值
* @param cmd 指向命令起始位置Mxxx 之后)
* @param cmd_len 命令剩余长度
* @param key 要查找的参数字符(如 'P', 'S'
* @param value 输出参数,存储解析到的值
* @return bool 是否找到该参数
*/
static bool __attribute__((unused)) find_parameter_value(const uint8_t *cmd, uint8_t cmd_len, char key, uint32_t *value)
{
for (uint8_t i = 0; i < cmd_len; i++) {
if (cmd[i] == (uint8_t)key) {
uint8_t param_idx = i + 1;
if (param_idx >= cmd_len) return false;
if (parse_uint_dec(&cmd[param_idx], cmd_len - param_idx, value) > 0) {
return true;
}
return false;
}
}
return false;
}
/* ============================================================================
* 命令处理函数
* ============================================================================ */
/**
* @brief 解析并处理完整的命令帧。
* @details 处理经过协议校验的完整命令帧,支持以下命令格式:
* - 无参数命令M<数字>(如 M1、M2、M10、M201
* - 带参数命令M<数字>S<参数>(如 M100S123参数为十进制
*
* 支持的命令:
* - M1: 开启LED启用传感器上报
* - M2: 关闭LED禁用传感器上报
* - M100S<value>: 设置PWM值示例
*
* @param frame 指向完整命令帧的缓冲区从包头0xD5开始
* @param len 命令帧总长度(字节)。
* @note 函数内部进行帧格式校验,格式错误时自动发送错误响应。
* @warning 假设输入帧已通过基本协议校验包头、类型、CRC等
* @ingroup Command
*/
void handle_command(const uint8_t *frame, uint8_t len) {
// 帧格式D5 03 LEN [cmd] CRC; cmd 支持变长,如 "M1"、"M10"、"M201"、"M123S400",有最小长度限制和命令长度校验
uint8_t cmd_len = frame[2];
if (len < PROTOCOL_MIN_FRAME_LEN || (uint8_t)(3 + cmd_len + 1) != len) return; // 长度不匹配或者小于最小限制
const uint8_t *cmd = &frame[3]; // 提取命令部分
// 命令必须以 'M' 开头
if (cmd[0] != 'M'){
send_response(RESP_TYPE_TYPE_ERR, s_report_status_err, sizeof(s_report_status_err));
return;
}
// 从 'M' 后开始解析
uint8_t cmd_index = 1;
// 解析M后的十进制数即命令本体
uint32_t base_cmd = 0;
uint8_t used_base_cmd = parse_uint_dec(&cmd[cmd_index], (cmd_len - cmd_index), &base_cmd);
if (used_base_cmd == 0)
{
// 'M' 后没有数字,格式错误
send_response(RESP_TYPE_LEN_ERR, s_report_status_err, sizeof(s_report_status_err));
return;
}
cmd_index = (uint8_t)(cmd_index + used_base_cmd); // 更新索引到命令后
switch (base_cmd) {
case 1u: // M1
send_response(RESP_TYPE_OK, s_report_status_ok, sizeof(s_report_status_ok));
return;
/* ==========================================
* M888 软件重启命令
* ========================================== */
case 888u:
// 先发送确认响应
send_response(RESP_TYPE_OK, s_report_status_ok, sizeof(s_report_status_ok));
// 短暂延时确保响应发送完成
delay_ms(100);
// 执行软件重启
system_software_reset();
return;
/* ==========================================
* M999 输出固件版本号命令
* ========================================== */
case 999u: //M999: 输出固件版本号
{
char version_str[16];
char *p = version_str;
*p++ = 'v';
p += uint_to_str(BOARD_TYPE_CODE, p);
*p++ = '.';
p += uint_to_str(FW_VERSION_MAJOR, p);
*p++ = '.';
p += uint_to_str(FW_VERSION_MINOR, p);
*p++ = '.';
p += uint_to_str(FW_VERSION_PATCH, p);
*p = '\0'; // null-terminate for printf safety
uint8_t n = (uint8_t)(p - version_str);
send_response(RESP_TYPE_OK, (uint8_t *)version_str, n);
COMMAND_DEBUG("Firmware Version: %s", version_str);
}
return;
/* ==========================================
* M9999 进入OTA模式
* ========================================== */
case 9999u: //M9999: 进入OTA模式
__disable_irq(); // 关中断,防止竞态条件
NVIC_SystemReset(); // 触发系统复位进入Bootloader
return;
default:
send_response(RESP_TYPE_CMD_ERR, s_report_status_err, sizeof(s_report_status_err));
break;
}
}
/**
* @brief 处理串口环形缓冲区中的命令数据,解析完整的协议帧。
* @details 本函数实现一个基于状态机的协议解析器,用于处理格式为 D5 03 LEN [cmd] CRC 的命令帧:
* - 状态1等待包头字节 PROTOCOL_PACKAGE_HEADER (0xD5)
* - 状态2接收板卡类型字节 PROTOCOL_BOARD_TYPE
* - 状态3接收长度字段并计算期望的完整帧长度
* - 状态4继续接收剩余数据直到完整帧
* - 状态5对完整帧进行校验包头、板卡类型、CRC并处理
*
* 函数采用非阻塞方式处理,每次调用处理缓冲区中所有可用数据。
* 遇到格式错误、长度异常或校验失败时自动重置状态机。
*
* @note 本函数使用静态变量维护解析状态,因此不可重入。在中断环境中使用需注意并发安全。
* 协议帧最大长度受 PROTOCOL_MAX_FRAME_LEN 限制,超出范围的帧将被丢弃。
*
* @warning 函数依赖 uart_ring_buffer_available() 和 uart_ring_buffer_get()
* 正确实现,若这些函数有缺陷可能导致死循环或数据丢失。
*
* @see handle_command() 用于处理校验通过的完整命令帧
* @see command_sum_crc_calc() 用于计算和校验 CRC 值
* @see send_response() 用于发送错误响应
*
* @ingroup Command
*/
void command_process(void) {
static uint8_t cmd_buf[PROTOCOL_MAX_FRAME_LEN];
static uint8_t cmd_len = 0;
static uint8_t expected_cmd_len = 0; // 0 表示尚未确定总长度
while (uart_ring_buffer_available() > 0) {
int byte = uart_ring_buffer_get();
if (byte < 0) break;
if (cmd_len == 0) {
if ((uint8_t)byte == PROTOCOL_PACKAGE_HEADER) {
cmd_buf[cmd_len++] = (uint8_t)byte;
expected_cmd_len = 0; // 等待进一步字段以确定长度
} else {
// 丢弃非起始字节
}
continue;
}
if (cmd_len >= PROTOCOL_MAX_FRAME_LEN) {
// 防御:缓冲溢出,复位状态机
cmd_len = 0;
expected_cmd_len = 0;
continue;
}
// 缓存后续字节
cmd_buf[cmd_len++] = (uint8_t)byte;
// 当到达长度字段(索引 2确定总长度3 + LEN + 1
if (cmd_len == 3) {
uint8_t payload_len = cmd_buf[2];
expected_cmd_len = (uint8_t)(3 + payload_len + 1);
if (expected_cmd_len > PROTOCOL_MAX_FRAME_LEN) {
// 异常:长度超界,复位状态机
cmd_len = 0;
expected_cmd_len = 0;
}
continue;
}
if (expected_cmd_len > 0 && cmd_len == expected_cmd_len) {
// 到帧尾,进行各项校验
bool verification_status = true;
#ifdef DEBUG_VERBOSE
if (cmd_buf[0] != PROTOCOL_PACKAGE_HEADER) {
send_response(RESP_TYPE_HEADER_ERR, s_report_status_err, sizeof(s_report_status_err));
verification_status = false;
}
#endif
if (verification_status && cmd_buf[1] != PROTOCOL_BOARD_TYPE) {
send_response(RESP_TYPE_TYPE_ERR, s_report_status_err, sizeof(s_report_status_err));
verification_status = false;
}
if (verification_status) {
uint8_t crc_calc = command_sum_crc_calc(cmd_buf, expected_cmd_len);
uint8_t crc_recv = cmd_buf[expected_cmd_len - 1];
if (crc_calc != crc_recv) {
send_response(RESP_TYPE_CRC_ERR, s_report_status_err, sizeof(s_report_status_err));
verification_status = false;
}
}
if (verification_status) {
handle_command(cmd_buf, expected_cmd_len);
} else {
// 验证失败时清空缓冲区,避免后续帧受影响
uart_rx_irq_pause();
uart_ring_buffer_clear();
uart_rx_irq_resume();
}
// 复位,等待下一帧
cmd_len = 0;
expected_cmd_len = 0;
}
}
}
/**
* @brief 执行命令(简化版)
* @details 根据命令字符串直接构造命令帧并执行,无需手动构造协议帧
* @param cmd_str 命令字符串(如"M730S0T1000"、"M731S100"等)
* @note 简化的测试函数自动处理协议帧构造、CRC计算和命令执行
* @ingroup Command
*/
void command_execute(const char *cmd_str)
{
if (cmd_str == NULL) return;
uint8_t cmd_len = (uint8_t)strlen(cmd_str);
uint8_t frame_len = 3 + cmd_len + 1; // header + type + len + cmd + crc
uint8_t frame_buf[32]; // 简单固定缓冲区
if (frame_len > sizeof(frame_buf)) return;
// 构造命令帧
frame_buf[0] = PROTOCOL_PACKAGE_HEADER; // 0xD5
frame_buf[1] = PROTOCOL_BOARD_TYPE; // Board Type
frame_buf[2] = cmd_len; // 命令长度
// 复制命令数据
for (uint8_t i = 0; i < cmd_len; i++) {
frame_buf[3 + i] = (uint8_t)cmd_str[i];
}
// 计算CRC
uint16_t crc = 0;
for (uint8_t i = 1; i < (frame_len - 1); i++) {
crc += frame_buf[i];
}
frame_buf[frame_len - 1] = (uint8_t)(crc & 0xFF);
// 清空缓冲区并执行命令
uart_rx_irq_pause();
uart_ring_buffer_clear();
uart_rx_irq_resume();
for (uint8_t i = 0; i < frame_len; i++) {
uart_ring_buffer_put(frame_buf[i]);
}
command_process();
}

123
Src/gd32e23x_it.c Normal file
View File

@@ -0,0 +1,123 @@
/*!
\file gd32e23x_it.c
\brief interrupt service routines
\version 2025-02-10, V2.4.0, demo for GD32E23x
*/
/*
Copyright (c) 2025, GigaDevice Semiconductor Inc.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors
may be used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
OF SUCH DAMAGE.
*/
#include "gd32e23x_it.h"
#include "systick.h"
#include "uart.h"
#include "uart_ring_buffer.h"
#include "led.h"
#include "board_config.h"
/*!
\brief this function handles NMI exception
\param[in] none
\param[out] none
\retval none
*/
void NMI_Handler(void)
{
/* if NMI exception occurs, go to infinite loop */
while(1) {
}
}
/*!
\brief this function handles HardFault exception
\param[in] none
\param[out] none
\retval none
*/
void HardFault_Handler(void)
{
/* if Hard Fault exception occurs, go to infinite loop */
while(1) {
}
}
/*!
\brief this function handles SVC exception
\param[in] none
\param[out] none
\retval none
*/
void SVC_Handler(void)
{
/* if SVC exception occurs, go to infinite loop */
while(1) {
}
}
/*!
\brief this function handles PendSV exception
\param[in] none
\param[out] none
\retval none
*/
void PendSV_Handler(void)
{
/* if PendSV exception occurs, go to infinite loop */
while(1) {
}
}
/*!
\brief this function handles SysTick exception
\param[in] none
\param[out] none
\retval none
*/
void SysTick_Handler(void) {
led_heart_beat(); // LED心跳指示灯
delay_decrement();
}
void USART0_IRQHandler(void) {
// 主配置口使用 USART0 时
if(g_usart_config.usart_periph == USART0 && g_usart_config.irq_handler != 0) {
g_usart_config.irq_handler(); // 通过函数指针调用对应的处理函数
}
// 作为调试口(第二串口)时也接收数据,便于模拟上位机命令
#ifdef DEBUG_MODE
else {
usart0_irq_handler();
}
#endif
}
void USART1_IRQHandler(void) {
// 检查当前配置是否使用USART1并且函数指针不为空
if(g_usart_config.usart_periph == USART1 && g_usart_config.irq_handler != 0) {
g_usart_config.irq_handler(); // 通过函数指针调用对应的处理函数
}
}

1138
Src/i2c.c Normal file

File diff suppressed because it is too large Load Diff

57
Src/led.c Normal file
View File

@@ -0,0 +1,57 @@
#include "led.h"
/**
* @brief LED心跳指示灯功能
* @details 实现类似心跳的LED闪烁模式快闪两次然后暂停
* 适合在SysTick中断中调用通过计数器控制闪烁节拍
* @note 假设SysTick中断频率为1ms心跳周期约为2秒
* 心跳模式亮200ms->灭200ms->亮200ms->灭1400ms循环
*/
void led_heart_beat(void)
{
static uint16_t heart_beat_counter = 0;
// 心跳周期2000ms (假设SysTick为1ms中断)
// 模式亮200ms -> 灭200ms -> 亮200ms -> 灭1400ms
heart_beat_counter++;
if (heart_beat_counter <= 200) {
// 第一次亮0-200ms
led_on();
}
else if (heart_beat_counter <= 400) {
// 第一次灭200-400ms
led_off();
}
else if (heart_beat_counter <= 600) {
// 第二次亮400-600ms
led_on();
}
else if (heart_beat_counter <= 2000) {
// 长时间灭600-2000ms
led_off();
}
else {
// 重置计数器,开始新的心跳周期
heart_beat_counter = 0;
}
}
void led_init(void) {
rcu_periph_clock_enable(LED_RCU);
gpio_mode_set(LED_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, LED_PIN);
gpio_output_options_set(LED_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, LED_PIN);
gpio_bit_set(LED_PORT, LED_PIN);
}
void led_on(void) {
gpio_bit_reset(LED_PORT, LED_PIN);
}
void led_off(void) {
gpio_bit_set(LED_PORT, LED_PIN);
}
void led_toggle(void) {
gpio_bit_toggle(LED_PORT, LED_PIN);
}

83
Src/main.c Normal file
View File

@@ -0,0 +1,83 @@
/*!
\file main.c
\brief running LED
\version 2025-02-10, V2.4.0, demo for GD32E23x
*/
/*
Copyright (c) 2025, GigaDevice Semiconductor Inc.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors
may be used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
OF SUCH DAMAGE.
*/
#include "gd32e23x.h"
#include "systick.h"
#include "uart.h"
#include "uart_ring_buffer.h"
#include "led.h"
#include "command.h"
#include <stdio.h>
#include "i2c.h"
#include "board_config.h"
/*!
\brief main function
\param[in] none
\param[out] none
\retval none
*/
int main(void)
{
led_init();
mcu_detect_and_config();
// delay_ms(1000);
systick_config();
uart_ring_buffer_init();
uart_init();
i2c_config();
#ifdef DEBUG_MODE
printf("Hello World!\r\n");
#endif
#ifdef DEBUG_VERBOSE
i2c_scan();
i2c_bus_reset();
#endif
/* ========== Command Testing ========== */
/* ========== */
while(1){
command_process(); /* Process UART commands */
delay_ms(10);
}
}

171
Src/syscalls.c Normal file
View File

@@ -0,0 +1,171 @@
/* Support files for GNU libc. Files in the system namespace go here.
Files in the C namespace (ie those that do not start with an
underscore) go in .c. */
#include <_ansi.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/fcntl.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <sys/time.h>
#include <sys/times.h>
#include <errno.h>
#include <reent.h>
#include <unistd.h>
#include <sys/wait.h>
#include "gd32e23x_usart.h"
#include "board_config.h"
#undef errno
extern int errno;
extern int __io_putchar(int ch) __attribute__((weak));
extern int __io_getchar(void) __attribute__((weak));
caddr_t _sbrk(int incr)
{
extern char _end[];
static char *curbrk = _end;
if ((curbrk + incr < _end))
return NULL - 1;
curbrk += incr;
return curbrk - incr;
}
/*
* _gettimeofday primitive (Stub function)
* */
int _gettimeofday (struct timeval * tp, struct timezone * tzp)
{
/* Return fixed data for the timezone. */
if (tzp)
{
tzp->tz_minuteswest = 0;
tzp->tz_dsttime = 0;
}
return 0;
}
void initialise_monitor_handles()
{
}
int _getpid(void)
{
return 1;
}
int _kill(int pid, int sig)
{
errno = EINVAL;
return -1;
}
void _exit (int status)
{
_kill(status, -1);
while (1) {}
}
int _write(int file, char *ptr, int len)
{
int DataIdx;
for (DataIdx = 0; DataIdx < len; DataIdx++)
{
__io_putchar( *ptr++ );
}
return len;
}
int _close(int file)
{
return -1;
}
int _fstat(int file, struct stat *st)
{
st->st_mode = S_IFCHR;
return 0;
}
int _isatty(int file)
{
return 1;
}
int _lseek(int file, int ptr, int dir)
{
return 0;
}
int _read(int file, char *ptr, int len)
{
int DataIdx;
for (DataIdx = 0; DataIdx < len; DataIdx++)
{
*ptr++ = __io_getchar();
}
return len;
}
int _open(char *path, int flags, ...)
{
/* Pretend like we always fail */
return -1;
}
int _wait(int *status)
{
errno = ECHILD;
return -1;
}
int _unlink(char *name)
{
errno = ENOENT;
return -1;
}
int _times(struct tms *buf)
{
return -1;
}
int _stat(char *file, struct stat *st)
{
st->st_mode = S_IFCHR;
return 0;
}
int _link(char *old, char *new)
{
errno = EMLINK;
return -1;
}
int _fork(void)
{
errno = EAGAIN;
return -1;
}
int _execve(char *name, char **argv, char **env)
{
errno = ENOMEM;
return -1;
}
// UART printf重定向实现
int __io_putchar(int ch) {
// 等待发送缓冲区空
while (usart_flag_get(DEBUG_UART, USART_FLAG_TBE) == RESET) {}
usart_data_transmit(DEBUG_UART, (uint8_t)ch);
return ch;
}

451
Src/system_gd32e23x.c Normal file
View File

@@ -0,0 +1,451 @@
/*!
\file system_gd32e23x.c
\brief CMSIS Cortex-M23 Device Peripheral Access Layer Source File for
GD32E23x Device Series
*/
/* Copyright (c) 2012 ARM LIMITED
Copyright (c) 2025, GigaDevice Semiconductor Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
- Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
- Neither the name of ARM nor the names of its contributors may be used
to endorse or promote products derived from this software without
specific prior written permission.
*
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDERS AND CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
---------------------------------------------------------------------------*/
/* This file refers the CMSIS standard, some adjustments are made according to GigaDevice chips */
#include "gd32e23x.h"
/* system frequency define */
#define __IRC8M (IRC8M_VALUE) /* internal 8 MHz RC oscillator frequency */
#define __HXTAL (HXTAL_VALUE) /* high speed crystal oscillator frequency */
#define __SYS_OSC_CLK (__IRC8M) /* main oscillator frequency */
#define VECT_TAB_OFFSET (uint32_t)0x00 /* vector table base offset */
/* select a system clock by uncommenting the following line */
//#define __SYSTEM_CLOCK_8M_HXTAL (__HXTAL)
//#define __SYSTEM_CLOCK_8M_IRC8M (__IRC8M)
// #define __SYSTEM_CLOCK_72M_PLL_HXTAL (uint32_t)(72000000)
#define __SYSTEM_CLOCK_72M_PLL_IRC8M_DIV2 (uint32_t)(72000000)
/* The following is to prevent Vcore fluctuations caused by frequency switching.
It is strongly recommended to include it to avoid issues caused by self-removal.
*/
#define RCU_MODIFY(__delay) do{ \
volatile uint32_t i,reg; \
if(0 != __delay){ \
reg = RCU_CFG0; \
reg &= ~(RCU_CFG0_AHBPSC); \
/* CK_AHB = SYSCLK/2 */ \
reg |= RCU_AHB_CKSYS_DIV2; \
RCU_CFG0 = reg; \
for(i=0; i<__delay; i++){ \
} \
reg = RCU_CFG0; \
reg &= ~(RCU_CFG0_AHBPSC); \
reg |= RCU_AHB_CKSYS_DIV4; \
/* CK_AHB = SYSCLK/4 */ \
RCU_CFG0 = reg; \
for(i=0; i<__delay; i++){ \
} \
} \
}while(0)
#define SEL_IRC8M 0x00
#define SEL_HXTAL 0x01
#define SEL_PLL 0x02
/* set the system clock frequency and declare the system clock configuration function */
#ifdef __SYSTEM_CLOCK_8M_HXTAL
uint32_t SystemCoreClock = __SYSTEM_CLOCK_8M_HXTAL;
static void system_clock_8m_hxtal(void);
#elif defined (__SYSTEM_CLOCK_72M_PLL_HXTAL)
uint32_t SystemCoreClock = __SYSTEM_CLOCK_72M_PLL_HXTAL;
static void system_clock_72m_hxtal(void);
#elif defined (__SYSTEM_CLOCK_72M_PLL_IRC8M_DIV2)
uint32_t SystemCoreClock = __SYSTEM_CLOCK_72M_PLL_IRC8M_DIV2;
static void system_clock_72m_irc8m(void);
#else
uint32_t SystemCoreClock = __SYSTEM_CLOCK_8M_IRC8M;
static void system_clock_8m_irc8m(void);
#endif /* __SYSTEM_CLOCK_8M_HXTAL */
/* configure the system clock */
static void system_clock_config(void);
/* software delay to prevent the impact of Vcore fluctuations.
It is strongly recommended to include it to avoid issues caused by self-removal. */
static void _soft_delay_(uint32_t time)
{
__IO uint32_t i;
for(i=0; i<time*10; i++){
}
}
/*!
\brief setup the microcontroller system, initialize the system
\param[in] none
\param[out] none
\retval none
*/
void SystemInit (void)
{
/* enable IRC8M */
RCU_CTL0 |= RCU_CTL0_IRC8MEN;
while(0U == (RCU_CTL0 & RCU_CTL0_IRC8MSTB)){
}
if(((RCU_CFG0 & RCU_CFG0_SCSS) == RCU_SCSS_PLL)){
RCU_MODIFY(0x80);
}
RCU_CFG0 &= ~RCU_CFG0_SCS;
_soft_delay_(100);
RCU_CTL0 &= ~(RCU_CTL0_HXTALEN | RCU_CTL0_CKMEN | RCU_CTL0_PLLEN | RCU_CTL0_HXTALBPS);
/* reset RCU */
RCU_CFG0 &= ~(RCU_CFG0_SCS | RCU_CFG0_AHBPSC | RCU_CFG0_APB1PSC | RCU_CFG0_APB2PSC |\
RCU_CFG0_ADCPSC | RCU_CFG0_CKOUTSEL | RCU_CFG0_CKOUTDIV | RCU_CFG0_PLLDV);
RCU_CFG0 &= ~(RCU_CFG0_PLLSEL | RCU_CFG0_PLLMF | RCU_CFG0_PLLMF4 | RCU_CFG0_PLLDV);
RCU_CFG1 &= ~(RCU_CFG1_PREDV);
RCU_CFG2 &= ~(RCU_CFG2_USART0SEL | RCU_CFG2_ADCSEL);
RCU_CFG2 &= ~RCU_CFG2_IRC28MDIV;
RCU_CFG2 &= ~RCU_CFG2_ADCPSC2;
RCU_CTL1 &= ~RCU_CTL1_IRC28MEN;
RCU_INT = 0x00000000U;
/* configure system clock */
system_clock_config();
#ifdef VECT_TAB_SRAM
nvic_vector_table_set(NVIC_VECTTAB_RAM,VECT_TAB_OFFSET);
#else
nvic_vector_table_set(NVIC_VECTTAB_FLASH,VECT_TAB_OFFSET);
#endif
}
/*!
\brief configure the system clock
\param[in] none
\param[out] none
\retval none
*/
static void system_clock_config(void)
{
#ifdef __SYSTEM_CLOCK_8M_HXTAL
system_clock_8m_hxtal();
#elif defined (__SYSTEM_CLOCK_72M_PLL_HXTAL)
system_clock_72m_hxtal();
#elif defined (__SYSTEM_CLOCK_72M_PLL_IRC8M_DIV2)
system_clock_72m_irc8m();
#elif defined (__SYSTEM_CLOCK_72M_PLL_IRC48M_DIV2)
system_clock_72m_irc48m();
#else
system_clock_8m_irc8m();
#endif /* __SYSTEM_CLOCK_8M_HXTAL */
}
#ifdef __SYSTEM_CLOCK_8M_HXTAL
/*!
\brief configure the system clock to 8M by HXTAL
\param[in] none
\param[out] none
\retval none
*/
static void system_clock_8m_hxtal(void)
{
uint32_t timeout = 0U;
uint32_t stab_flag = 0U;
__IO uint32_t reg_temp;
/* enable HXTAL */
RCU_CTL0 |= RCU_CTL0_HXTALEN;
/* wait until HXTAL is stable or the startup time is longer than HXTAL_STARTUP_TIMEOUT */
do{
timeout++;
stab_flag = (RCU_CTL0 & RCU_CTL0_HXTALSTB);
}
while((0U == stab_flag) && (HXTAL_STARTUP_TIMEOUT != timeout));
/* if fail */
if(0U == (RCU_CTL0 & RCU_CTL0_HXTALSTB)){
while(1){
}
}
/* HXTAL is stable */
/* AHB = SYSCLK */
RCU_CFG0 |= RCU_AHB_CKSYS_DIV1;
/* APB2 = AHB */
RCU_CFG0 |= RCU_APB2_CKAHB_DIV1;
/* APB1 = AHB */
RCU_CFG0 |= RCU_APB1_CKAHB_DIV1;
reg_temp = RCU_CFG0;
/* select HXTAL as system clock */
reg_temp &= ~RCU_CFG0_SCS;
reg_temp |= RCU_CKSYSSRC_HXTAL;
RCU_CFG0 = reg_temp;
/* wait until HXTAL is selected as system clock */
while(RCU_SCSS_HXTAL != (RCU_CFG0 & RCU_CFG0_SCSS)){
}
}
#elif defined (__SYSTEM_CLOCK_72M_PLL_HXTAL)
/*!
\brief configure the system clock to 72M by PLL which selects HXTAL as its clock source
\param[in] none
\param[out] none
\retval none
*/
static void system_clock_72m_hxtal(void)
{
uint32_t timeout = 0U;
uint32_t stab_flag = 0U;
__IO uint32_t reg_temp;
/* enable HXTAL */
RCU_CTL0 |= RCU_CTL0_HXTALEN;
/* wait until HXTAL is stable or the startup time is longer than HXTAL_STARTUP_TIMEOUT */
do{
timeout++;
stab_flag = (RCU_CTL0 & RCU_CTL0_HXTALSTB);
}
while((0U == stab_flag) && (HXTAL_STARTUP_TIMEOUT != timeout));
/* if fail */
if(0U == (RCU_CTL0 & RCU_CTL0_HXTALSTB)){
while(1){
}
}
FMC_WS = (FMC_WS & (~FMC_WS_WSCNT)) | WS_WSCNT_2;
/* HXTAL is stable */
/* AHB = SYSCLK */
RCU_CFG0 |= RCU_AHB_CKSYS_DIV1;
/* APB2 = AHB */
RCU_CFG0 |= RCU_APB2_CKAHB_DIV1;
/* APB1 = AHB */
RCU_CFG0 |= RCU_APB1_CKAHB_DIV1;
/* PLL = HXTAL * 9 = 72 MHz */
RCU_CFG0 &= ~(RCU_CFG0_PLLSEL | RCU_CFG0_PLLMF | RCU_CFG0_PLLDV);
RCU_CFG0 |= (RCU_PLLSRC_HXTAL | RCU_PLL_MUL9);
/* enable PLL */
RCU_CTL0 |= RCU_CTL0_PLLEN;
/* wait until PLL is stable */
while(0U == (RCU_CTL0 & RCU_CTL0_PLLSTB)){
}
reg_temp = RCU_CFG0;
/* select PLL as system clock */
reg_temp &= ~RCU_CFG0_SCS;
reg_temp |= RCU_CKSYSSRC_PLL;
RCU_CFG0 = reg_temp;
/* wait until PLL is selected as system clock */
while(RCU_SCSS_PLL != (RCU_CFG0 & RCU_CFG0_SCSS)){
}
}
#elif defined (__SYSTEM_CLOCK_72M_PLL_IRC8M_DIV2)
/*!
\brief configure the system clock to 72M by PLL which selects IRC8M/2 as its clock source
\param[in] none
\param[out] none
\retval none
*/
static void system_clock_72m_irc8m(void)
{
uint32_t timeout = 0U;
uint32_t stab_flag = 0U;
__IO uint32_t reg_temp;
/* enable IRC8M */
RCU_CTL0 |= RCU_CTL0_IRC8MEN;
/* wait until IRC8M is stable or the startup time is longer than IRC8M_STARTUP_TIMEOUT */
do{
timeout++;
stab_flag = (RCU_CTL0 & RCU_CTL0_IRC8MSTB);
}
while((0U == stab_flag) && (IRC8M_STARTUP_TIMEOUT != timeout));
/* if fail */
if(0U == (RCU_CTL0 & RCU_CTL0_IRC8MSTB)){
while(1){
}
}
FMC_WS = (FMC_WS & (~FMC_WS_WSCNT)) | WS_WSCNT_2;
/* AHB = SYSCLK */
RCU_CFG0 |= RCU_AHB_CKSYS_DIV1;
/* APB2 = AHB */
RCU_CFG0 |= RCU_APB2_CKAHB_DIV1;
/* APB1 = AHB */
RCU_CFG0 |= RCU_APB1_CKAHB_DIV1;
/* PLL = (IRC8M/2) * 18 = 72 MHz */
RCU_CFG0 &= ~(RCU_CFG0_PLLSEL | RCU_CFG0_PLLMF);
RCU_CFG0 |= (RCU_PLLSRC_IRC8M_DIV2 | RCU_PLL_MUL18);
/* enable PLL */
RCU_CTL0 |= RCU_CTL0_PLLEN;
/* wait until PLL is stable */
while(0U == (RCU_CTL0 & RCU_CTL0_PLLSTB)){
}
reg_temp = RCU_CFG0;
/* select PLL as system clock */
reg_temp &= ~RCU_CFG0_SCS;
reg_temp |= RCU_CKSYSSRC_PLL;
RCU_CFG0 = reg_temp;
/* wait until PLL is selected as system clock */
while(RCU_SCSS_PLL != (RCU_CFG0 & RCU_CFG0_SCSS)){
}
}
#else
/*!
\brief configure the system clock to 8M by IRC8M
\param[in] none
\param[out] none
\retval none
*/
static void system_clock_8m_irc8m(void)
{
uint32_t timeout = 0U;
uint32_t stab_flag = 0U;
__IO uint32_t reg_temp;
/* enable IRC8M */
RCU_CTL0 |= RCU_CTL0_IRC8MEN;
/* wait until IRC8M is stable or the startup time is longer than IRC8M_STARTUP_TIMEOUT */
do{
timeout++;
stab_flag = (RCU_CTL0 & RCU_CTL0_IRC8MSTB);
}
while((0U == stab_flag) && (IRC8M_STARTUP_TIMEOUT != timeout));
/* if fail */
if(0U == (RCU_CTL0 & RCU_CTL0_IRC8MSTB)){
while(1){
}
}
/* AHB = SYSCLK */
RCU_CFG0 |= RCU_AHB_CKSYS_DIV1;
/* APB2 = AHB */
RCU_CFG0 |= RCU_APB2_CKAHB_DIV1;
/* APB1 = AHB */
RCU_CFG0 |= RCU_APB1_CKAHB_DIV1;
reg_temp = RCU_CFG0;
/* select IRC8M as system clock */
reg_temp &= ~RCU_CFG0_SCS;
reg_temp |= RCU_CKSYSSRC_IRC8M;
RCU_CFG0 = reg_temp;
/* wait until IRC8M is selected as system clock */
while(RCU_SCSS_IRC8M != (RCU_CFG0 & RCU_CFG0_SCSS)){
}
}
#endif /* __SYSTEM_CLOCK_8M_HXTAL */
/*!
\brief update the SystemCoreClock with current core clock retrieved from cpu registers
\param[in] none
\param[out] none
\retval none
*/
void SystemCoreClockUpdate (void)
{
uint32_t sws = 0U;
uint32_t pllmf = 0U, pllmf4 = 0U, pllsel = 0U, prediv = 0U, idx = 0U, clk_exp = 0U;
/* exponent of AHB clock divider */
const uint8_t ahb_exp[16] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 6, 7, 8, 9};
sws = GET_BITS(RCU_CFG0, 2, 3);
switch(sws){
/* IRC8M is selected as CK_SYS */
case SEL_IRC8M:
SystemCoreClock = IRC8M_VALUE;
break;
/* HXTAL is selected as CK_SYS */
case SEL_HXTAL:
SystemCoreClock = HXTAL_VALUE;
break;
/* PLL is selected as CK_SYS */
case SEL_PLL:
/* get the value of PLLMF[3:0] */
pllmf = GET_BITS(RCU_CFG0, 18, 21);
pllmf4 = GET_BITS(RCU_CFG0, 27, 27);
/* high 16 bits */
if(1U == pllmf4){
pllmf += 17U;
}else if(15U == pllmf){
pllmf = 16U;
} else {
pllmf += 2U;
}
/* PLL clock source selection, HXTAL or IRC8M/2 */
pllsel = GET_BITS(RCU_CFG0, 16, 16);
if(0U != pllsel){
prediv = (GET_BITS(RCU_CFG1, 0, 3) + 1U);
SystemCoreClock = (HXTAL_VALUE / prediv) * pllmf;
} else {
SystemCoreClock = (IRC8M_VALUE >> 1) * pllmf;
}
break;
/* IRC8M is selected as CK_SYS */
default:
SystemCoreClock = IRC8M_VALUE;
break;
}
/* calculate AHB clock frequency */
idx = GET_BITS(RCU_CFG0, 4, 7);
clk_exp = ahb_exp[idx];
SystemCoreClock >>= clk_exp;
}
#ifdef __FIRMWARE_VERSION_DEFINE
/*!
\brief get firmware version
\param[in] none
\param[out] none
\retval firmware version
*/
uint32_t gd32e23x_firmware_version_get(void)
{
return __GD32E23x_STDPERIPH_VERSION;
}
#endif /* __FIRMWARE_VERSION_DEFINE */

118
Src/systick.c Normal file
View File

@@ -0,0 +1,118 @@
/**
* ************************************************************************
*
* @file systick.c
* @author GD32
* @brief 通过 SysTick 定时器进行微秒级别和毫秒级别的延时函数
*
* ************************************************************************
* @copyright Copyright (c) 2024 GD32
* ************************************************************************
*/
#include "gd32e23x.h"
#include "systick.h"
volatile static uint32_t delay_count = 0;
/**
* ************************************************************************
* @brief 配置 SysTick 定时器
*
*
* ************************************************************************
*/
void systick_config(void)
{
//设置了 SysTick 定时器的时钟源为 HCLK
systick_clksource_set(SYSTICK_CLKSOURCE_HCLK);
// 配置SysTick为1ms周期中断
// 注意SysTick_Config会自动设置时钟源为HCLK所以需要使用SystemCoreClock/1000
SysTick_Config(SystemCoreClock / 1000U); // 1ms中断
NVIC_SetPriority(SysTick_IRQn, 0x00U);
}
/**
* ************************************************************************
* @brief delay_ms 毫秒延时函数
*
* @param[in] count 毫秒值
*
* ************************************************************************
*/
void delay_10us(uint32_t count)
{
// 基于系统时钟的简单循环延时
// 这是一个粗略的估计,实际延时可能有偏差 实测10.2us
uint32_t loops_per_10us = SystemCoreClock / 1700000; // 粗略估计每10微秒的循环次数
for(uint32_t i = 0; i < count; i++) {
for(volatile uint32_t j = 0; j < loops_per_10us; j++);
}
}
/**
* ************************************************************************
* @brief delay_ms 毫秒延时函数
*
* @param[in] count 毫秒值
*
* ************************************************************************
*/
void delay_ms(uint32_t count)
{
delay_count = count; // 设置延时计数
while (delay_count != 0U);
}
/**
* ************************************************************************
* @brief 每个 SysTick 中断调用时,减少延时计数
*
* @param[in] void
*
* ************************************************************************
*/
void delay_decrement(void)
{
if (delay_count != 0U)
{
delay_count--;
}
}
// /**
// * ************************************************************************
// * @brief delay_ms_safe 毫秒延时函数不干扰SysTick中断
// * @details 使用简单循环实现延时不会重新配置SysTick
// * @param[in] count 毫秒值
// * ************************************************************************
// */
// void delay_ms_safe(uint32_t count)
// {
// // 基于系统时钟的简单循环延时
// // 这是一个粗略的估计,实际延时可能有偏差
// uint32_t loops_per_ms = SystemCoreClock / 14000; // 粗略估计
// for(uint32_t i = 0; i < count; i++) {
// for(volatile uint32_t j = 0; j < loops_per_ms; j++);
// }
// }
// /**
// * ************************************************************************
// * @brief delay_us_safe 微秒延时函数不干扰SysTick中断
// * @details 使用简单循环实现延时不会重新配置SysTick
// * @param[in] count 微秒值
// * ************************************************************************
// */
// void delay_us_safe(uint32_t count)
// {
// // 基于系统时钟的简单循环延时
// // 这是一个粗略的估计,实际延时可能有偏差
// uint32_t loops_per_us = SystemCoreClock / 22000000; // 粗略估计,每微秒的循环次数
// for(uint32_t i = 0; i < count; i++) {
// for(volatile uint32_t j = 0; j < loops_per_us; j++);
// }
// }

109
Src/uart.c Normal file
View File

@@ -0,0 +1,109 @@
#include "uart.h"
#include "gd32e23x_usart.h"
#include "gd32e23x_rcu.h"
#include "gd32e23x_gpio.h"
#include "board_config.h"
#include "uart_ring_buffer.h"
void uart_init(void) {
/* 使能 GPIOA 和 USART 时钟 */
rcu_periph_clock_enable(UART_GPIO_RCU);
rcu_periph_clock_enable(UART_RCU);
/* 配置 PA2 为 USART_TXPA3 为 USART_RX */
gpio_af_set(UART_GPIO_PORT, GPIO_AF_1, UART_TX_PIN | UART_RX_PIN);
gpio_mode_set(UART_GPIO_PORT, GPIO_MODE_AF, GPIO_PUPD_PULLUP, UART_TX_PIN | UART_RX_PIN);
gpio_output_options_set(UART_GPIO_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, UART_TX_PIN | UART_RX_PIN);
/* 配置波特率、数据位、停止位等 */
usart_deinit(UART_PHY);
usart_word_length_set(UART_PHY, USART_WL_8BIT);
usart_stop_bit_set(UART_PHY, USART_STB_1BIT);
usart_parity_config(UART_PHY, USART_PM_NONE);
usart_baudrate_set(UART_PHY, UART_BAUDRATE);
usart_receive_config(UART_PHY, USART_RECEIVE_ENABLE);
usart_transmit_config(UART_PHY, USART_TRANSMIT_ENABLE);
usart_enable(UART_PHY);
nvic_irq_enable(UART_IRQ, 0);
usart_interrupt_enable(UART_PHY, USART_INT_RBNE);
}
void uart_rx_irq_pause(void) {
usart_interrupt_disable(UART_PHY, USART_INT_RBNE);
}
void uart_rx_irq_resume(void) {
usart_interrupt_enable(UART_PHY, USART_INT_RBNE);
}
/******************************************************************************/
/* 具体的中断处理函数实现 */
/******************************************************************************/
void usart0_irq_handler(void) {
// 处理USART0的接收中断
if(usart_interrupt_flag_get(USART0, USART_INT_FLAG_RBNE)) {
uint8_t data = usart_data_receive(USART0);
// 使用原有的环形缓冲区处理逻辑
(void)uart_ring_buffer_put(data); // 缓冲满时丢弃,返回值可用于统计
}
// 处理USART0的空闲中断
if(usart_interrupt_flag_get(USART0, USART_INT_FLAG_IDLE)) {
usart_interrupt_flag_clear(USART0, USART_INT_FLAG_IDLE);
// 在这里添加空闲中断处理逻辑
}
}
void usart1_irq_handler(void) {
// 处理USART1的接收中断
if(usart_interrupt_flag_get(USART1, USART_INT_FLAG_RBNE)) {
uint8_t data = usart_data_receive(USART1);
// 使用原有的环形缓冲区处理逻辑
(void)uart_ring_buffer_put(data); // 缓冲满时丢弃,返回值可用于统计
}
// 处理USART1的空闲中断
if(usart_interrupt_flag_get(USART1, USART_INT_FLAG_IDLE)) {
usart_interrupt_flag_clear(USART1, USART_INT_FLAG_IDLE);
// 在这里添加空闲中断处理逻辑
}
}
/* 临时调试串口初始化 (PA9/PA10 USART0) */
void debug_usart_init(void)
{
/* 使能 GPIOA 和 USART0 时钟 */
rcu_periph_clock_enable(RCU_GPIOA);
rcu_periph_clock_enable(RCU_USART0);
/* 配置 PA9(TX) 和 PA10(RX) 复用功能 */
gpio_af_set(GPIOA, GPIO_AF_1, GPIO_PIN_9 | GPIO_PIN_10);
/* 配置 GPIO 模式 */
gpio_mode_set(GPIOA, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_PIN_9 | GPIO_PIN_10);
gpio_output_options_set(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_9 | GPIO_PIN_10);
/* USART0 配置 */
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);
usart_enable(USART0);
/* NVIC 使能串口0中断优先级可略低于主口 */
nvic_irq_enable(USART0_IRQn, 1);
}

121
Src/uart_ring_buffer.c Normal file
View File

@@ -0,0 +1,121 @@
/**
* @file uart_ring_buffer.c
* @brief 字节环形接收缓冲区的实现。
* @details 适用于中断接收(写)与主循环解析(读)的典型串口场景;
* 采用“预留一格”区分空/满,最大可用容量为 UART_RX_BUFFER_SIZE-1。
* @ingroup RingBuffer
*/
#include "uart_ring_buffer.h"
#include "gd32e23x.h"
static volatile uint8_t uart_rx_buffer[UART_RX_BUFFER_SIZE];
static volatile uint8_t write_index = 0;
static volatile uint8_t read_index = 0;
static volatile uint32_t dropped_bytes = 0;
static inline uint32_t irq_save(void) {
uint32_t primask = __get_PRIMASK();
__disable_irq();
return primask;
}
static inline void irq_restore(uint32_t primask) {
if (primask == 0U) {
__enable_irq();
}
}
/**
* @brief 重置环形缓冲区状态。
* @details 将读指针、写指针与丢弃计数清零,不清空数据区内容。
* @note 内部工具函数;对外请优先使用 uart_ring_buffer_init()/uart_ring_buffer_clear()。
* @ingroup RingBuffer
*/
static void uart_ring_buffer_reset_state(void) {
write_index = 0;
read_index = 0;
dropped_bytes = 0;
}
/**
* @brief 初始化环形缓冲区。
* @details 调用内部重置逻辑,复位读写索引与丢弃计数,准备接收数据。
* @note 若在中断环境使用,初始化前建议关闭相关接收中断以避免并发竞争。
* @ingroup RingBuffer
*/
void uart_ring_buffer_init(void) {
uint32_t irq_key = irq_save();
uart_ring_buffer_reset_state();
irq_restore(irq_key);
}
/**
* @brief 获取当前可读的字节数。
* @details 通过读/写指针的快照计算可读长度,范围为 [0, UART_RX_BUFFER_SIZE-1]。
* @return uint8_t 可读字节数。
* @note 预留一个空槽区分“空/满”,因此满时返回 UART_RX_BUFFER_SIZE-1。
* @ingroup RingBuffer
*/
uint8_t uart_ring_buffer_available(void) {
/* 使用快照减少并发不一致窗口 */
uint8_t w = write_index;
uint8_t r = read_index;
return (uint8_t)((w + UART_RX_BUFFER_SIZE - r) % UART_RX_BUFFER_SIZE);
}
/**
* @brief 从环形缓冲区读取一个字节。
* @details 若缓冲区非空,返回队头字节并推进读指针;若为空,返回 -1。
* @return int 读取到的字节0..255),或 -1 表示缓冲区为空。
* @ingroup RingBuffer
*/
int uart_ring_buffer_get(void) {
if (read_index == write_index) return -1; // 空
uint8_t data = uart_rx_buffer[read_index];
read_index = (read_index + 1) % UART_RX_BUFFER_SIZE;
return data;
}
/**
* @brief 向环形缓冲区写入一个字节。
* @details 尝试写入一个新字节;若缓冲区已满则丢弃并计数。
* @param data 待写入的字节。
* @return bool 是否写入成功。
* @retval true 写入成功。
* @retval false 写入失败(缓冲区已满,数据被丢弃并计数)。
* @note 如需“覆盖写入”策略,可在满时先推进读指针再写入。
* @ingroup RingBuffer
*/
bool uart_ring_buffer_put(uint8_t data) {
uint8_t next = (write_index + 1) % UART_RX_BUFFER_SIZE;
if (next != read_index) { // 缓冲区未满
uart_rx_buffer[write_index] = data;
write_index = next;
return true;
} else {
/* 缓冲区已满,丢弃新字节并计数 */
dropped_bytes++;
return false;
}
}
/**
* @brief 清空环形缓冲区。
* @details 复位读写索引与丢弃计数,相当于逻辑上丢弃所有已接收数据,不擦除数据区内容。
* @ingroup RingBuffer
*/
void uart_ring_buffer_clear(void) {
uint32_t irq_key = irq_save();
uart_ring_buffer_reset_state();
irq_restore(irq_key);
}
/**
* @brief 获取因满而被丢弃的字节累计数量。
* @details 写入时缓冲区满会丢弃新字节并累加计数;该计数在 init/clear 时清零。
* @return uint32_t 丢弃的累计字节数。
* @ingroup RingBuffer
*/
uint32_t uart_ring_buffer_drop_count(void) {
return dropped_bytes;
}